0%

zookeeper安装

1
2
3
4
5
6
7
brew install zookeeper #安装
zkServer start #启动
zkcli -server localhost:2181 #客户端连接zk服务
[zk: localhost:2181(CONNECTED)] ls / # ls进行查看连接资源
[zk: localhost:2181(CONNECTED) 13] ls /dubbo/com.api.ITestService/providers #查看连接的提供者
#docker方式启动
docker run --name some-zookeeper -p 2181:2181 --restart always -d zookeeper

springboot 引入dubbo

消费者和提供者公共配置

  1. 添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <dependency>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>0.2.0</version>
    </dependency>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.6.6</version>
    </dependency>
    <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.33.Final</version>
    </dependency>
  2. 启用dubbo,在启动类添加注解@EnableDubbo

  3. 配置文件添加zk,spring.dubbo.registry.address = zookeeper://localhost:2181

  4. 添加接口

    1
    2
    3
    public interface ITestService {
    public String hello();
    }

提供者代码

1
2
3
4
5
6
7
8
9
import com.alibaba.dubbo.config.annotation.Service;

@Service
public class TestService implements ITestService {
@Override
public String hello() {
return "hello world!";
}
}

消费者代码

1
2
3
4
5
6
7
8
9
10
11
12
import com.alibaba.dubbo.config.annotation.Reference;

@RestController
public class TestController {

@Reference(check = false)
ITestService iTestService;

@GetMapping("hello")
public String hello(){
return iTestService.hello();
}

常见问题

  1. 异常信息

    1
    2
    cause: io/netty/bootstrap/ServerBootstrap
    at com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol.createServer(DubboProtocol.java:287)[dubbo-2.6.6.jar:2.6.6]

    解决:添加netty依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.33.Final</version>
    </dependency>
  2. 异常信息

    1
    2
    java.lang.IllegalArgumentException: Specified invalid port from env value:0
    at com.alibaba.dubbo.config.ServiceConfig.parsePort(ServiceConfig.java:691) ~[dubbo-2.6.6.jar:2.6.6]

    解决:环境变量设置有问题,在vm上覆写参数-DDUBBO_PORT_TO_BIND=20880

  3. 异常信息

    1
    2
    failed to connect to server /10.0.0.54:20880, error message is:connection timed out: /10.0.0.54:20880
    at com.alibaba.dubbo.remoting.transport.netty4.NettyClient.doConnect(NettyClient.java:127) ~[dubbo-2.6.6.jar:2.6.6]

    解决:多网卡下ip绑定错误,导致连接超时,在vm添加参数-DDUBBO_IP_TO_BIND=10.9.98.133绑定ip

  4. 异常信息

    1
    2
    3
    c.c.b.s.w.a.GlobalExceptionHandler : application error
    com.alibaba.dubbo.rpc.RpcException: No provider available from registry 192.168.240.15:2181 for service cn.com.api.dubbo.service.TestDubboService:1.0.0 on consumer 10.233.73.69 use dubbo version 2.6.6, please check status of providers(disabled, not registered or in blacklist).
    at com.alibaba.dubbo.registry.integration.RegistryDirectory.doList(RegistryDirectory.java:590)

    解决:如果zookeeper都显示提供者和消费者都注册通知正常,但是消费者缓存里面拿不到提供者的服务地址,检查spring.dubbo.invoke.invalid.ips 这个配置是否把你的ip拦截了

Keepalived介绍

大白话:就是虚拟一个ip,然后虚拟机绑定该ip,如果该虚拟机挂掉,该另一个虚拟机就绑定虚拟ip。

keepalived是基于VRRP协议为LVS服务提供高可用方案。主要提供了负载均衡和高可用功能,用来避免单点故障。负载均衡是通过linux的IPVS(ip虚拟服务器)实现,高可用通过VRRP实现多机故障转移。
  keepalived一般是2个节点运行keepalived,一台是主节点(MASTER),一台是备节点(BACKUP)对外表现都是一个虚拟IP,主节点会通过组播方式发送特定的消息给备节点,如果备节点收不到这个特定消息时,说明主节点就宕机了,此时备节点就会接管VIP进行服务提供,这就实现了高可用。

  • VRRP虚拟路由器冗余协议(英语:Virtual Router Redundancy Protocol,缩写为 VRRP)是一种网络协议,可以为参与的路由器自动分配可用的IP地址。这个协议通过在子网中,自动选取默认网关,来增加路由的可用性和可靠性。

    VRRP原理:是一种容错协议,保证当主机的下一条路由器出现故障时,由另一台路由器来代替出现故障的路由器进行工作,从而保持网络通信的连续性和可靠性。

    VRRP相关术语:

    1. 虚拟路由器:由一个Master路由器和一个或多个Backup路由器组成。
    2. VRID:虚拟路由器的标识。同一虚拟路由器内的路由器有着相同的VRID。
    3. VIP(虚拟IP地址):路由器组(虚拟路由器)的IP地址。
    4. 抢占模式与非抢占模式:Master会以组播方式不断的向虚拟路由器组内发送自己的心跳报文,一旦Backup在设定时间内没有收到心跳信息的次数超过了设定次数,则会将Master的所有权转移到优先级最高的Backup,则就是抢占模式。非抢占模式是指只有在主节点完全故障时才能将backup变为master。
  • 组播:网卡支持并开启组播功能,组播通信方式是一种很节省资源的通信方式。其余还有单播、广播。

  • LVSLinux虚拟服务器(Linux Virtual Server)是一个虚拟的服务器集群系统,用于实现负载平衡

  • VIP虚拟IP地址(Virtual IP Address),主要是用来进行不同主机之间的切换,主要用在服务器的主从切换。

L2ohL9.png

实战

常用命令

1
2
3
4
#安装
yum install -y keepalived
# 启动keepalived
systemctl start keepalived

10.0.0.10双主热备配置文件 /etc/keepalived/keepalived.conf10.0.0.11修改相反即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
! Configuration File for keepalived

global_defs {
router_id NGINX_DEVEL1 # 标识节点的字符串,一般设置为hostname
}

vrrp_script chk_nginx { #检测业务进程(nginx)来调整keepalived权重
script "/etc/keepalived/check_nginx.sh"
interval 2 #每2s检测一次
#weight -5 #检测失败(脚本返回非0)则优先级 -5
weight 2 #检测失败(脚本返回非0)则优先级 +2?
fall 3 #检测连续3次失败才算确定是真失败。
rise 2 #检测 2 次成功就算成功。但不修改优先级
}

vrrp_instance VI_1 {
state MASTER # 标识主节点服务(只有MASTER和BACKUP两种,大写)
interface eth0 # 当前ip的网卡接口
mcast_src_ip 10.0.0.10 # 本机IP地址
virtual_router_id 51 # 虚拟路由 ID(0-255),在一个 VRRP 实例中主备服务器 ID 必须一样
priority 200 # 优先级值设定:MASTER 要比 BACKUP 的值大
advert_int 1 # MASTER和BACKUP节点之间的同步检查时间间隔,单位为秒,主被设置一致

track_interface { # 设置额外的监控,eth0网卡出现问题也会切换
eth0
}

authentication { # 认证机制,主从节点保持一致即可
auth_type PASS
auth_pass password123 # MASTER和BACKUP使用相同明文才可以互通
}

virtual_ipaddress { # 虚拟IP地址池,可以多个IP
10.0.0.20/24 # 虚拟IP N个(VIP)
}

track_script {
chk_nginx
}
}

vrrp_instance VI_2 {
state BACKUP
interface eth0
mcast_src_ip 10.0.0.11
virtual_router_id 52
priority 180
advert_int 1

track_interface {
eth0
}

authentication {
auth_type PASS
auth_pass password123
}

virtual_ipaddress {
10.0.0.21/24
}

track_script {
chk_nginx
}
}

后面就可以通过vip进行访问了。

ip a s查看vip

1
2
3
4
5
6
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 52:54:99:e5:7e:13 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.10/24 brd 10.255.243.255 scope global noprefixroute dynamic eth0
valid_lft 58597sec preferred_lft 58597sec
inet 10.0.0.20/24 scope global secondary eth0
valid_lft forever preferred_lft forever

/etc/keepalived/check_nginx.sh脚本内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash

function check_nginx() {
for ((i=0;i<5;i++));do
nginx_job_id=$(pgrep nginx)
if [[ ! -z $nginx_job_id ]];then
return
else
sleep 2
fi
nginx_job_id=0
done
}

# 1: running 0: stopped
check_nginx
if [[ $nginx_job_id -eq 0 ]]; then
/usr/bin/systemctl stop keepalived
exit 1
else
exit 0
fi

百度经验助手

名称:百度经验助手

匹配规则:https://jingyan.baidu.com/edit/content*

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
'use strict';
//修改标题和图标
var link = document.querySelector("link[rel*='icon']") || document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = 'https://blog.iexxk.com/images/favicon-32x32-next.png';
document.getElementsByTagName('head')[0].appendChild(link);
document.title = '经验笔记编辑';
//隐藏不用的元素
document.getElementById("js_float_high_quality_wrap").style.visibility = "hidden";
document.getElementById("header").style.display = "none";
document.querySelector("div.wgt-benefit").style.display = "none";
document.querySelector("div.video-guide-left").style.visibility = "hidden";
//检测敏感词汇
var eb=new Array("女人","代理");
var bz = document.querySelector(".ca-step-item");
bz.onclick = function(){
for (const v of eb) {
var dom = $(':contains('+v+')').filter(function(){
return $(this).text()==v;
});
dom.css("color","red");
}
}
// 格式化输入内容,加租加句号
var ed= document.querySelectorAll('.edui-body-container');
var submit = document.querySelector(".release-btn");
var catalogListUl =document.getElementById("catalogList"); //在左侧悬浮窗生成发布
var li =document.createElement("li"); //createElement生成button对象
var input =document.createElement("input"); //createElement生成input对象
input.style="width:96px;";
li.innerHTML = '发布';
li.onclick = function(){
var ed= document.querySelectorAll('.edui-body-container');
for(var inp of ed){
var lp= inp.lastElementChild;
var fp= inp.firstElementChild;
if(lp.textContent.indexOf(",") == -1){ //不包含逗号,进行格式话
lp.textContent="在“"+lp.textContent+"”界面,点击“"+fp.textContent+"”按钮。";
fp.textContent="点击“"+fp.textContent+"”";
}
if(!lp.textContent.endsWith('。')){ //不包含句话,添加句号
lp.textContent=lp.textContent+"。";
}
if(inp.id != 'editor-brief'){ //加粗
var b=document.createElement("strong");
b.textContent=fp.textContent;
fp.textContent="";
fp.appendChild(b);
}
inp.focus();
}

input.focus();
submit.click();
}
catalogListUl.appendChild(li);
catalogListUl.appendChild(input);
//添加下拉软件选择
function soft(input){
var s=input[4];
s.setAttribute("list","soft");
var l=document.createElement("datalist");
l.setAttribute("id","soft");
var opvs=new Map();
opvs.set("微信","8.0.20");
opvs.set("QQ","v8.8.85.636");
opvs.set("kubesphere","v3.1.0");
opvs.set("国家医保服务平台","v1.3.7");
opvs.set("Zepp Life","6.0.3");
opvs.set("米家","v7.4.203");
opvs.set("支付宝","10.2.60");
opvs.set("访达","12.3");
opvs.set("Confluence","6.14.1");
opvs.set("IDEA","2022.1.1");
opvs.set("热血航线","1.7.1");
opvs.set("铁路12306","5.5.0.18");
opvs.set("网易云音乐","8.7.35");
opvs.set("保卫萝卜3","2.2.4");
opvs.set("微信读书","6.2.3");
opvs.set("闲鱼","7.4.80");
opvs.set("哔哩哔哩","6.71.0");
opvs.set("Apifox","2.0.2");
opvs.set("DaVinci Resolve studio 17","17.2.2");
opvs.set("中国银行","7.3.2");
opvs.set("炉石传说","23.2.138759");
opvs.set("云闪付","9.1.3");
opvs.set("必剪","2.13.0");
opvs.set("自动操作","2.10");
for (const v of opvs.keys()) {
var op= document.createElement("option");
op.setAttribute("value",v);
l.appendChild(op);
}
s.appendChild(l);
s.onchange = function() {
console.log(s.value);
input[5].value=opvs.get(s.value);
input[5].focus();
}
}
// 添加类型选择事件
var ss = document.getElementById("category").querySelectorAll("select")[2]; //获取第二个下拉选择框
ss.onchange = function() { //监听下拉框选择事件
console.log(ss.value);
if (ss.value == 16) { //电脑
var pcInput= document.getElementById("js-software-list").querySelectorAll("input");
pcInput[0].focus();
pcInput[0].value = "macOS Monterey";
pcInput[1].focus();
pcInput[1].value = "12.2.1";
pcInput[2].focus();
pcInput[2].value = "MacBook Pro";
pcInput[3].focus();
pcInput[3].value = "2017";
//添加下拉软件选择
soft(pcInput);
} else if (ss.value == 20||ss.value == 32) { //手机
var phoneInput= document.getElementById("js-software-list").querySelectorAll("input");
phoneInput[0].focus();
phoneInput[0].value = "iOS";
phoneInput[1].focus();
phoneInput[1].value = "15.4.1";
phoneInput[2].focus();
phoneInput[2].value = "iPhone";
phoneInput[3].focus();
phoneInput[3].value = "13";
//添加下拉软件选择
soft(phoneInput);
}
document.getElementById("is-origin").click();//自动勾选原创
document.querySelector("input[name='title']").focus();
};

轻量K8s集群

对比

  • MicroK8s:集成度高,配置部署简单,Kubernetes 发行版,旨在运行快速、自我修复和高度可用的 Kubernetes 集群,适合在云、本地开发环境以及边缘和物联网设备。
  • K3s:插件独立,更轻量,部署配置复杂,用于物联网边缘设备生产级Kubernetes工作负载。
  • MINIKUBE:本地Kubernetes安装程序,常用于本地运行。(独立安装docker)
  • colima:工具可以单独使用docker,k8s,但是docker k8s需要自己装

MicroK8s(不采用)

multipass使用hyperkit创建了一个microk8s-vm虚拟机,发布的 v1.14 Kubernetes 将标志着 MicroK8s 切换到 Containerd 并增强安全性

1
2
3
4
5
6
7
8
9
10
11
12
13
brew install ubuntu/microk8s/microk8s
microk8s install #会提示是否安装multipass,点击是
microk8s status --wait-ready
microk8s enable dashboard dns registry istio
microk8s dashboard-proxy
microk8s start/stop
#查看虚拟机,
multipass list
#进入ubuntu虚拟机
multipass shell microk8s-vm
#卸载
microk8s uninstall

Multipass:Multipass 是一款可运行于 Linux、Windows 和 MacOS 的轻量级虚拟机管理器,它专为希望使用单个命令即可启动全新 Ubuntu 环境的开发人员而设计。它在 Linux 上使用 KVM、在 Windows 上使用 Hyper-V、在 MacOS 上使用 HyperKit,以便以最小的开销运行虚拟机。它还可以在 Windows 和 MacOS 上使用 VirtualBox。

最终还是采用docker desktop

基础服务

名称 说明
kubernetes(k8s) k8s集群
kubeadm k8s集群部署工具
kubesphere(青云) 容器管理平台
Prometheus-Operator k8s监控平台
Grafana Loki 日志收集平台
jaeger 分布式链路追踪
skywalking 分布式链路追踪
KubeKey kk一键安装k8s

k8s组件概览

k8s

控制平面组件

  • kube-apiserver:API 服务器是 Kubernetes 控制面的前端,各组件通讯中转站,接收外部请求。
  • etcd:兼具一致性和高可用性的键值数据库,可以作为保存 Kubernetes 所有集群数据的后台数据库。
  • kubu-scheduler:节点资源调度(调度决策考虑的因素包括单个 Pod 和 Pod 集合的资源需求、硬件/软件/策略约束、亲和性和反亲和性规范、数据位置、工作负载间的干扰和最后时限。)
  • kube-controller-manager:控制器进程组件(节点、任务、端点、账户和令牌控制器)
  • cloud-controller-manager:云平台交互控制器(节点、路由、服务控制器)非必需

Node组件(每个节点上运行)

  • kubelet:维护节点状态等
  • kube-proxy:负责节点网络代理

容器/插件(DNS必须)/Web(管理界面)/监控/日志

架构模式

  • master节点:包含kube-apiserver、kubu-scheduler、kube-controller-manager组件,主要用于管理集群。
  • etcd节点:包含etcd组件,存储重要的集群有状态信息
  • worker节点:工作节点,主要用于运行业务服务容器等

etcd和master分开部署和部署到一台区别?

高可用

  • Master的kube-apiserver、kube-controller-mansger和kube-scheduler服务至少三个结点的多实例方式部署。
  • ETCD至少以3个结点的集群模式部署。
  • 负载均衡 Load Balance使用双机热备,向Node暴露虚拟IP作为入口地址,供客户端访问。

高安全

  • Master、ETCD集群启用基于CA认证的HTTPS安全机制,Master启用RBAC授信机制。

快速部署k8s青云集群

部署步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
#-- 所有机器执行
yum update -y
# kk安装前所需依赖socat conntrack是必须安装的,ebtables ipset是建议安装的
yum install -y socat conntrack ebtables ipset
# 检查时间,还需检查是否安装sudo/curl/openssl软件
date
#-- 单机执行,如果执行没反应可以单独下载,然后重命名文件,然后执行export VERSION=v2.2.1
export KKZONE=cn
curl -sfL https://get-kk.kubesphere.io | VERSION=v1.1.0 sh -
# 创建带kubernetes和kubesphere的配置文件,也可以只安装kubernetes,安装kubesphere必会安装kubernetes
./kk create config --with-kubernetes v1.18.6 --with-kubesphere v3.1.0
export KKZONE=cn
./kk create cluster -f config-sample.yaml | tee kk.log

all in one部署

all in one部署属于单机部署,是所有东西部署到一台机器,适合实验环境。

注意事项:

  • 虚拟机需要设置内存8G以上,cpu设置8个虚拟cpu。
  • 每个kk命令执行前都执行下export KKZONE=cn命令,解决网络问题,该命令只对下个命令生效。
  • 安装最好不要选择最新的版本,选择最新的前一个版本,因为国内资源也许没有更新这么快,导致一些资源下载失败。

组件介绍

组件安装日志查看命令kubectl logs -n kubesphere-system $(kubectl get pod -n kubesphere-system -l app=ks-installer -o jsonpath='{.items[0].metadata.name}') -f

或者

kubectl logs -n kubesphere-system $(kubectl get pod -n kubesphere-system -l 'app in (ks-install, ks-installer)' -o jsonpath='{.items[0].metadata.name}') -f

组件启用分安装时启用和安装后启用

  • 安装时启用

    1
    2
    3
    #下载https://github.com/kubesphere/ks-installer/releases/download/v3.2.1/kubesphere-installer.yaml
    #修改kubesphere-installer.yaml然后在里面启用组件
    kubectl apply -f cluster-configuration.yaml
  • 安装后启用

    命令模式:执行kubectl -n kubesphere-system edit clusterconfiguration ks-installer

      graph LR
    A[集群管理] --> B[自定义资源CRD] --> C[clusterconfiguration] --> D[ks-installer] --> E[编辑配置启用组件] --> F[服务组件查看组件状态]

devops

  • 流水线队列中,检查是否设置ci节点,集群设置标签node-role.kubernetes.io/worker设置ci
  • 工作台–>企业空间–>DevOps工程
  • 自定义 Jenkins Agent,在集群管理->集群->配置中心->配置->jenkins-casc-config添加自定义镜像的配置,然后登陆 Jenkins 重新加载(新版本也许可以不用),最后在jenkins部署脚本就可以使用对应的agent.node.lable使用自定义镜像编译了。

logging(monitoring)

  • 日志监测

Istio(servicemesh)

  • 服务网格

metrics_server

  • 资源监控

    1
    2
    #开启才有的命令
    kubectl top node

常见错误

  1. 安装kubesphere提示如下错误

    1
    2
    ERRO[16:03:26 CST] Failed to exec command: sudo -E /bin/sh -c "/usr/local/bin/kubectl -n kubesphere-monitoring-system create secret generic kube-etcd-client-certs --from-file=etcd-client-ca.crt=/etc/ssl/etcd/ssl/ca.pem --from-file=etcd-client.crt=/etc/ssl/etcd/ssl/node-k8s-etcd-242.14.pem --from-file=etcd-client.key=/etc/ssl/etcd/ssl/node-k8s-etcd-242.14-key.pem"
    error: error reading /etc/ssl/etcd/ssl/node-k8s-etcd-242.14.pem: no such file or directory: Process exited with status 1 node=10.255.242.10

    解决:

    1
    2
    3
    4
    # 修改所有member开头的为node
    cp /etc/ssl/etcd/ssl/member-k8s-etcd-242.14-key.pem /etc/ssl/etcd/ssl/node-k8s-etcd-242.14-key.pem
    # 修改完再次执行部署脚本
    ./kk create cluster -f config-sample.yaml | tee kk.log
  2. 在worker节点执行KK提示如下错误

    1
    2
    3
    4
    [upload-certs] Storing the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace
    error execution phase upload-certs: error uploading certs: error creating token: timed out waiting for the condition
    ....
    Failed to get cluster status: Failed to upload kubeadm certs: Failed to exec command: sudo -E /bin/sh -c "/usr/local/bin/kubeadm init phase upload-certs --upload-certs"

    解决:检查 controlPlaneEndpoint配置的负载均衡服务是否正常,lb正常还是不成功,先将负载设置成master节点的ip,后面部署好了再添加负载,修改负载节点过后执行./kk delete cluster -f config-sample.yaml 在执行安装命令,不然之前安装存的还是旧的负载均衡。

  3. 流水线添加节点报错

    1
    java.net.ProtocolException: Expected HTTP 101 response but was '400 Bad Request'

    解决:在jenkins-casc-config添加的新节点配置错误,最好的解决方案是复制之前的模版,修改所有名称,例如替换所有nodejsnodejs1415,不要采用继承模式,可能新版本才支持。

  4. 访问某个页面提示Internal Server Error

    1
    Internal Server Error: "/apis/clusters/sim-1/apiextensions.k8s.io/v1beta1/customresourcedefinitions/clusterconfigurations.installer.kubesphere.io": http2: invalid Connection request header: ["upgrade"]

    解决:在负载均衡服务器(keepalived)的nginx配置添加如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # /apis/clusters/sim-1/apiextensions.k8s.io和错误异常的地址对应
    location /apis/clusters/sim-1/apiextensions.k8s.io {
    proxy_http_version 1.1;
    proxy_redirect off;
    proxy_pass http://实际域名地址;
    proxy_set_header Host $host:$server_port;
    #proxy_set_header Upgrade $http_upgrade;
    proxy_set_header X-Forwarded-Proto $scheme;
    #proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
  5. helm部署的时候push报如下错误Error: UPGRADE FAILED: pre-upgrade hooks failed: unable to build kubernetes object for deleting hook appchart-uat/templates/alertReceiver.yaml: unable to recognize "": no matches for kind "Receiver" in version "notification.kubesphere.io/v2beta2"

    解决:安装kubesphere/notification-manager

    分析:根据错误发现是webhook的问题,检查组件Monitoring发现只有10个服务,少了一个[notification-manager-webhook](https://host-kslb.mh.xxx.com.cn/clusters/innovation-lab/components/kubesphere-monitoring-system/notification-manager-webhook),因此根据官方文档重新安装下

    1
    2
    3
    4
    5
    6
    # Deploy CRDs and the Notification Manager Operator:
    kubectl apply -f https://github.com/kubesphere/notification-manager/releases/download/v2.0.1/bundle.yaml
    # Deploy default template:
    kubectl apply -f https://github.com/kubesphere/notification-manager/releases/download/v2.0.1/template.yaml
    # Deploy built-in language packs.
    kubectl apply -f https://github.com/kubesphere/notification-manager/releases/download/v2.0.1/zh-cn.yaml
  6. 删除log组件之后,提示no endpoints available for service ":ks-apiserver:"具体错误找不到了,大概是9443端口连接不上

    解决:同第五个问题

  7. 流水线错误:Error: UPGRADE FAILED: pre-upgrade hooks failed: receivers.notification.kubesphere.io "-webhook-receiver" is forbidden: User "xxx" cannot delete resource "receivers" in API group

    解决:xxx用户没有集群的权限,修改为有权限的用户kubeconfig

    原因:会根据xxx用户的集群凭证去创建CRD,但是被拒绝了。

  8. ks-controller-manager部署启动报错,不断重启,错误信息如下:

    关键错误信息:E0425 16:45:32.489641 1 runtime.go:78] Observed a panic: "invalid memory address or nil pointer dereference" (runtime error: invalid memory address or nil pointer dereference)

    错误定位:可在日志中看到是(*VirtualServiceController).getSubsets该方法报错,去kubesphere/pkg/controller/virtualservice/virtualservice_controller.go的代码里面找到getSubsets方法

    可以看到是strategy应用出了问题。

    解决:在自定义资源CRD,搜索Strategy点击进去,删除可能又问题的crd资源,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    apiVersion: servicemesh.kubesphere.io/v1alpha2
    kind: Strategy
    metadata:
    annotations:
    app.kubernetes.io/icon: /assets/bookinfo.svg
    kubesphere.io/creator: '81006638'
    servicemesh.kubesphere.io/newWorkloadName: reviews-v2
    servicemesh.kubesphere.io/oldWorkloadName: reviews-v1
    servicemesh.kubesphere.io/workloadReplicas: '1'
    servicemesh.kubesphere.io/workloadType: deployments
    labels:
    app: reviews
    app.kubernetes.io/name: bookinfo
    app.kubernetes.io/version: v1
    name: v2
    namespace: dairk-test
    spec:
    principal: v1
    selector:
    matchLabels:
    app: reviews
    app.kubernetes.io/name: bookinfo
    app.kubernetes.io/version: v1

部署配置config-sample.yaml

主要修改hosts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
apiVersion: kubekey.kubesphere.io/v1alpha1
kind: Cluster
metadata:
name: sample
spec:
hosts:
## 添加虚拟机
- {name: k8s-master-240.23, address: 10.5.240.23, internalAddress: 10.5.240.23, user: root, password: pwd}
- {name: k8s-master-240.24, address: 10.5.240.24, internalAddress: 10.5.240.24, user: root, password: pwd}
- {name: k8s-master-240.25, address: 10.5.240.25, internalAddress: 10.5.240.25, user: root, password: pwd}
- {name: k8s-etcd-240.26, address: 10.5.240.26, internalAddress: 10.5.240.26, user: root, password: pwd}
- {name: k8s-etcd-240.27, address: 10.5.240.27, internalAddress: 10.5.240.27, user: root, password: pwd}
- {name: k8s-etcd-240.28, address: 10.5.240.28, internalAddress: 10.5.240.28, user: root, password: pwd}
- {name: k8s-worker-240.29, address: 10.5.240.29, internalAddress: 10.5.240.29, user: root, password: pwd}
- {name: k8s-worker-240.30, address: 10.5.240.30, internalAddress: 10.5.240.30, user: root, password: pwd}
roleGroups:
## 分配虚拟机角色
etcd:
- k8s-etcd-240.26
- k8s-etcd-240.27
- k8s-etcd-240.28
master:
- k8s-master-240.23
- k8s-master-240.24
- k8s-master-240.25
worker:
- k8s-worker-240.29
- k8s-worker-240.30
controlPlaneEndpoint:
## 高可用
internalLoadbalancer: haproxy
domain: lb.kubesphere.local
## 地址需要修改
address: "10.255.240.23"
port: 6443
kubernetes:
version: v1.18.6
imageRepo: kubesphere
clusterName: cluster.local
network:
plugin: calico
kubePodsCIDR: 10.233.64.0/18
kubeServiceCIDR: 10.233.0.0/18
registry:
registryMirrors: []
insecureRegistries: []
addons: []
---
apiVersion: installer.kubesphere.io/v1alpha1
kind: ClusterConfiguration
metadata:
name: ks-installer
namespace: kubesphere-system
labels:
version: v3.1.0
spec:
persistence:
storageClass: ""
authentication:
jwtSecret: ""
zone: ""
local_registry: ""
etcd:
monitoring: false
endpointIps: localhost
port: 2379
tlsEnable: true
common:
redis:
enabled: false
redisVolumSize: 2Gi
openldap:
enabled: false
openldapVolumeSize: 2Gi
minioVolumeSize: 20Gi
monitoring:
endpoint: http://prometheus-operated.kubesphere-monitoring-system.svc:9090
es:
elasticsearchMasterVolumeSize: 4Gi
elasticsearchDataVolumeSize: 20Gi
logMaxAge: 7
elkPrefix: logstash
basicAuth:
enabled: false
username: ""
password: ""
externalElasticsearchUrl: ""
externalElasticsearchPort: ""
console:
enableMultiLogin: true
port: 30880
alerting:
enabled: false
# thanosruler:
# replicas: 1
# resources: {}
auditing:
enabled: false
devops:
enabled: false
jenkinsMemoryLim: 2Gi
jenkinsMemoryReq: 1500Mi
jenkinsVolumeSize: 8Gi
jenkinsJavaOpts_Xms: 512m
jenkinsJavaOpts_Xmx: 512m
jenkinsJavaOpts_MaxRAM: 2g
events:
enabled: false
ruler:
enabled: true
replicas: 2
logging:
enabled: false
logsidecar:
enabled: true
replicas: 2
metrics_server:
enabled: false
monitoring:
storageClass: ""
prometheusMemoryRequest: 400Mi
prometheusVolumeSize: 20Gi
multicluster:
clusterRole: none
network:
networkpolicy:
enabled: false
ippool:
type: none
topology:
type: none
notification:
enabled: false
openpitrix:
store:
enabled: false
servicemesh:
enabled: false
kubeedge:
enabled: false
cloudCore:
nodeSelector: {"node-role.kubernetes.io/worker": ""}
tolerations: []
cloudhubPort: "10000"
cloudhubQuicPort: "10001"
cloudhubHttpsPort: "10002"
cloudstreamPort: "10003"
tunnelPort: "10004"
cloudHub:
advertiseAddress:
- ""
nodeLimit: "100"
service:
cloudhubNodePort: "30000"
cloudhubQuicNodePort: "30001"
cloudhubHttpsNodePort: "30002"
cloudstreamNodePort: "30003"
tunnelNodePort: "30004"
edgeWatcher:
nodeSelector: {"node-role.kubernetes.io/worker": ""}
tolerations: []
edgeWatcherAgent:
nodeSelector: {"node-role.kubernetes.io/worker": ""}
tolerations: []

参考

Kubernetes核心架构与高可用集群详解(含100%部署成功的方案)

kk多节点安装

k8s-快速入门

相关参数

QPS(Queries Per Second/吞吐量):每秒能够响应的查询次数,也即是最大吞吐能力(吞吐量)。

TPS(Transactions Per Second):每秒处理的事务数目。一个事务是指一个客户端向服务器发送请求然后服务器做出反应的过程。TPS 的过程包括:客户端请求服务端、服务端内部处理、服务端返回客户端。

例如:访问一个页面会请求服务器 3 次,那么访问这一个页面就会产生一个TPS,三个QPS。

jester dubbo测试插件

参考

JMeter之Ramp-up Period(in seconds)说明

基础名词

  • 节点/node:一个节点是一个运行Kubernetes 中的主机。
  • 容器组/pod:一个 Pod 对应于由若干容器组成的一个容器组,同个组内的容器共享一个存储卷(volume)。
  • 服务/services:一个 Kubernetes 服务是容器组逻辑的高级抽象,同时也对外提供访问容器组的策略。
  • 容器组生命周期/pos-states:包含所有容器状态集合,包括容器组状态类型,容器组生命周期,事件,重启策略,以及 replication controllers。

基础命令

1
2
3
4
5
6
7
8
9
10
#创建部署
kubectl create deployment test --image=nginx:latest
#获取部署信息
kubectl get deployments
#获取集群节点
kubectl get nodes
#获取容器组
kubectl get pods --all-namespaces
#获取服务
kubectl get service --all-namespaces

kubeconfig

是远程连接集群的授权证书配置文件,不管集群管理,还是流水线发布都会用到,集群下面的是默认管理员的账号,最好是用个人自己的集群kubeConfig。

安装成功后位于~/..kube/config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#----集群参数配置---
apiVersion: v1 #标识客户端解析器的版本,不应手动编辑
clusters: #可以配置多个集群
- cluster: #通过kubectl config指令生成
certificate-authority-data: LS....tLQo=
server: https://ip:6443 #集群访问方式
name: cluster.local #集群名字
#----上下文参数配置---
contexts: #多个上下文
- context: #关联集群clusters和用户users
cluster: cluster.local #集群名字
user: kubernetes-admin #客户端用户
name: kubernetes-admin@cluster.local #上下文名字
current-context: kubernetes-admin@cluster.local #当前使用那个上下文,就可以确定使用那个集群环境了
kind: Config #标识客户端解析器的模式,不应手动编辑
preferences: {} #指定可选(和当前未使用)的 kubectl 首选项
#----客户端认证参数配置---
users:
- name: kubernetes-admin
user:
client-certificate-data: LS0tLS1....LS0tLQo=
client-key-data: LS0tL...S0tLQo=
创建用户认证授权的 kubeconfig 文件(待验证)

参考创建用户认证授权的 kubeconfig 文件

  1. /etc/kubernetes/ssl目录创建devuser-csr.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    {
    "CN": "devuser",
    "hosts": [],
    "key": {
    "algo": "rsa",
    "size": 2048
    },
    "names": [
    {
    "C": "CN",
    "ST": "BeiJing",
    "L": "BeiJing",
    "O": "k8s",
    "OU": "System"
    }
    ]
    }
  2. 生成CA证书和密钥cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes devuser-csr.json | cfssljson -bare devuser

  3. 创建用户的kubeconfig 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # 设置集群参数
    export KUBE_APISERVER="https://172.20.0.113:6443"
    kubectl config set-cluster kubernetes \
    --certificate-authority=/etc/kubernetes/ssl/ca.pem \
    --embed-certs=true \
    --server=${KUBE_APISERVER} \
    --kubeconfig=devuser.kubeconfig

    # 设置客户端认证参数
    kubectl config set-credentials devuser \
    --client-certificate=/etc/kubernetes/ssl/devuser.pem \
    --client-key=/etc/kubernetes/ssl/devuser-key.pem \
    --embed-certs=true \
    --kubeconfig=devuser.kubeconfig

    # 设置上下文参数
    kubectl config set-context kubernetes \
    --cluster=kubernetes \
    --user=devuser \
    --namespace=dev \
    --kubeconfig=devuser.kubeconfig

    # 设置默认上下文
    kubectl config use-context kubernetes --kubeconfig=devuser.kubeconfig
  4. 查看上下文,执行kubectl config get-contexts,会显示~/.kube/config的账号

  5. 替换后,执行cp -f ./devuser.kubeconfig ~/.kube/config可以显示新的账号

  6. 绑定角色,限制用户可以访问那几个空间(namespace),例如

    1
    2
    3
    #---这样 devuser 用户对 dev 和 test 两个 namespace 具有完全访问权限。
    kubectl create rolebinding devuser-admin-binding --clusterrole=admin --user=devuser --namespace=dev
    kubectl create rolebinding devuser-admin-binding --clusterrole=admin --user=devuser --namespace=test
  7. 测试,执行kubectl get pods --namespace default会返回Forbidden无权限

后端技术

  • SpringBoot框架: 开箱即用 编码(注解)/配置/部署/监控/集成/开发简单
  • SpringCloud框架:简化了分布式系统开发,服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控

spring

  • springBoot Actuator(接口)/Admin(服务) 服务监控中心

  • Sentinel 熔断与降级

  • Ribbon 负载均衡

  • Spring Security 安全控制

  • feign 服务调用

  • swagger+ knife4j

  • minio 分布式文件系统

  • 分布式日志elk

  • docker容器部署

  • mongodb

  • redis

  • Seata 分布式事务

  • Sleuth 链路追踪

  • Quartz

  • 缓存穿透:很多不存在的key直接请求到数据库

    解决:参数校验、布隆过滤器、缓存时间、内存缓存

  • 缓存雪崩:大量的数据直接请求到数据库,例如redis缓存key大面积过期

    解决:集群、限流、过期时间

    • Eureka 服务发现框架
    • Ribbon 进程内负载均衡器
    • Open Feign 服务调用映射
    • Hystrix 服务降级熔断器
    • Zuul 微服务网关
    • Config 微服务统一配置中心
    • Bus 消息总线

基础概念

sidecar: 微服务异构,就是指可以让其他第三方(语言)服务,接入springcloud(nacos)里面进行管理等

框架源码:alibaba/spring-cloud-alibaba:Sidecar

需求

  1. 需要接入第三方服务,第三方服务以接口方式提供

  2. 第三方服务可以被其他第三方服务替换

  3. 第三方服务可能不支持集群部署,就是部署多个相同的实例,数据不共享

  4. 需要支持集群部署

  5. 需要监控第三方服务

  6. 集成到alibaba springcloud框架

  7. 接入方式feign

设计

项目框架采用边车模式(sidecar),但是不集成alibaba-sidecar,手动进行实现,因为需要支持多同类型第三方服务,需要对数据进行包装,

备选方案:集成alibaba-sidecar,因为异构只能直接代理,因此数据的包装可以采用过滤器和解码器进行处理

支持同类型第三方服务扩展替换

采用工厂设计模式进行搭建工程

支持集群部署

采用边车系统部署模式,一个第三方服务一个该服务

支持第三方服务监控

采用重写心跳,在心跳里面对第三方服务进行监控并绑定为自己的服务状态。

测试发现心跳是down的状态不熔断,只是降级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Component
public class SidecarHealthIndicator extends AbstractHealthIndicator {

@Autowired
AiConfig aiConfig;

@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
try {
String result;
if (aiConfig.aiFaceType.equals(FaceType.NT.name())) {
result = HttpUtil.get(aiConfig.aiFaceUrl + "/version", aiConfig.aiFaceUrlTimeout);
builder.withDetail("version", result);
} else if (aiConfig.aiFaceType.equals(FaceType.KS.name())) {
result = HttpUtil.get(aiConfig.aiFaceUrl + "/version", aiConfig.aiFaceUrlTimeout);
JSONObject r = JSONUtil.parseObj(result);
builder.withDetail("version", r.getStr("platform_version"));
} else {
result = HttpUtil.get(aiConfig.aiFaceUrl + "/version", aiConfig.aiFaceUrlTimeout);
builder.withDetail("version", result);
}
builder.up();
} catch (Exception e) {
builder.down(e);
}
}
}

第三方服务不支持集群,数据不共享(不考虑异常情况)

方案1: 在业务包装接口里面实现向其他实例进行数据同步

在数据存储类型的接口里面查询该服务的其他实例,然后发同样的数据到该服务的其他实例。

注意事项:由于该服务也部署了复数个实例,因此估计需要采用redis等中间件实现那些服务已经发送过,不然会形成服务间的死循环

方案2: 利用feign的重试机制

在接口里面返回指定错误码,然后根据错误码进行重试,然后计数重试次数(可采用redis进行计数),当重试次数达到了实例的个数,就说明每个实例都请求了一次了,数据都存在于每个实例了。

缺点:如果10个实例,每个实例处理请求时间2s,10个就需要20s,因为是按顺序进行请求的

方案3: 利用feign拦截器异步请求其他实例(目前采用)

可以在拦截器里面设置header标志,标志其他服务不需要拦截,向其他服务请求,不然也会形成服务间的死循环

拦截器两种实现方式

  • 在feign指定配置类@FeignClient(...,configuration = MyConfiguration.class)
  • 实现1⃣️feign.RequestInterceptor/2⃣️HandlerInterceptor/3⃣️ClientHttpRequestInterceptor接口,进行全局拦截

这里采用接口拦截模式,配置模式会在其他项目里面引入

拦截器用2⃣️HandlerInterceptor,因为1⃣️feign.RequestInterceptor不知道为什么拦截不生效

具体实现见附录一:spring HandlerInterceptor器的实现并读取body

步骤:
  1. 继承HttpServletRequestWrapper实现一个读取并保存requestBody的类BodyReaderHttpServletRequestWrapper.java

  2. 新建一个过滤器BodyReadFilter.java用于调用BodyReaderHttpServletRequestWrapper进行保存body

  3. 新建一个拦截器StatefulFeignInterceptor.java实现HandlerInterceptor中的preHandle

  4. 新建一个配置StatefulConfig.java用于启用拦截器StatefulFeignInterceptor

注意:如果要在拦截器里面使用@Autowired功能,就必须使用bean注入该类,不能用注解@Component等进行注入

向其他服务发送请求的逻辑,在StatefulFeignInterceptor里面的preHandle进行实现就可以了,代码如下

sub的作用时为了防止死循环,子服务不进行转发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if ("true".equals(request.getHeader("sub"))) {
log.info("sub request " + request.getRequestURI());
} else {
ThreadUtil.execAsync(() -> {
String uri = request.getRequestURI();
log.info("main request " + uri);
List<String> urls = aiConfig.aiFaceStatefulUrls;
if (urls.contains(uri)) {
BodyReaderHttpServletRequestWrapper requestWrapper = null;
try {
requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
} catch (IOException e) {
log.error("read body error: {}", e.getMessage());
}
String body = IoUtil.read(requestWrapper.getInputStream(), requestWrapper.getCharacterEncoding());
log.debug("请求体:{}", body);
String ip = discoveryProperties.getIp();
List<ServiceInstance> instanceList = discoveryClient.getInstances("xkiot-ai");
for (ServiceInstance serviceInstance : instanceList) {
if (!ip.equals(serviceInstance.getHost())) {
String url = serviceInstance.getUri().toString() + uri;
HttpRequest.post(url).header("sub", "true").body(body).execute(true).body();
}
}
}
});
}
return true;

注意事项:如果服务里面需要创建一个用户id,然后每台服务的用户id要一致,只能通过接口传入用户id,或者把用户id共享到redis内存里面(比较麻烦)

方案4: 利用feign解码器异步请求其他实例

解码器是对请求结果进行处理,因此如果使用该模式,估计需要用中间件redis来解决服务间的死循环

方案5: 幻想方案,在某个地方设置或重写,可以让feign支持向所有实例发送请求
方案6: 幻想方案,利用事务或异步请求合并处理结果,该模式可以解决异常情况
方案7: 解决第三方有状态服务的部署,第三方服务实现数据共享

附录一:spring HandlerInterceptor器的实现并读取body

BodyReaderHttpServletRequestWrapper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import org.springframework.util.StreamUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {

private byte[] requestBody = null;//用于将流保存下来

public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
requestBody = StreamUtils.copyToByteArray(request.getInputStream());
}

@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream bodyStream = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public int read() {
return bodyStream.read(); // 读取 requestBody 中的数据
}

@Override
public boolean isFinished() {
return false;
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setReadListener(ReadListener readListener) {
}
};
}

@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}

}

BodyReadFilter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
@WebFilter(urlPatterns = "/**", filterName = "BodyReadFilter")
public class BodyReadFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if (servletRequest instanceof HttpServletRequest) {
requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) servletRequest);
}
if (requestWrapper == null) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
filterChain.doFilter(requestWrapper, servletResponse);
}
}
}

StatefulFeignInterceptor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import cn.hutool.core.io.IoUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@Slf4j
public class StatefulFeignInterceptor implements HandlerInterceptor {

@Autowired
AiConfig aiConfig;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

if (aiConfig.aiFaceStatefulUrls.contains(request.getRequestURI())) {
BodyReaderHttpServletRequestWrapper requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
String body = IoUtil.read(requestWrapper.getInputStream(), requestWrapper.getCharacterEncoding());
log.debug("请求体:{}", body);
}
return true;
}

}

StatefulConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class StatefulConfig implements WebMvcConfigurer {

/**
* 解决StatefulFeignInterceptor里面的使用Autowired注入为null的问题
*
* @return
*/
@Bean
public StatefulFeignInterceptor statefulFeignInterceptor() {
return new StatefulFeignInterceptor();
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(statefulFeignInterceptor()).addPathPatterns("/**");
}
}

额外

Nacos 的cp/ap模式

AP模式(nacos默认模式)不支持数据一致性,所以只支持服务注册的临时实例

CP模式支持服务注册的永久实例,满足数据的一致性

这里的数据一致性,让我一度认为是指服务的所有实例数据一致,让我以为可以设置过后,每个实例都会发请求

参考

SpringBoot常用拦截器(HandlerInterceptor,ClientHttpRequestInterceptor,RequestInterceptor)

Swagger 整合knife4j

ruoyi-cloud/cloud/swagger

knife4j

Spring Cloud Gateway集成Knife4j

在xkiot-common-swagger的pom.xml添加如下依赖

1
2
3
4
5
 <dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-micro-spring-boot-starter</artifactId>
<version>2.0.8</version>
</dependency>

然后在xkiot-gateway的pom.xml添加如下依赖

1
2
3
4
5
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.8</version>
</dependency>

原生swagger访问http://{网关ip}:{port}/swagger-ui.html通过网关进行访问,里面可以进行选择切换服务

整合knife4j后访问http://{网关ip}:{port}/doc.html