hello云胜

技术与生活

0%

IPVS

ipvs是工作在内核态的4层负载均衡,和iptables一样都是基于内核底层netfilter实现,netfilter主要通过各个链的钩子实现包处理和转发。ipvsadm和ipvs的关系,就好比netfilter和iptables的关系,它运行在用户态,提供简单的CLI接口进行ipvs配置。

由于ipvs工作在内核态,直接基于内核处理包转发,所以最大的特点就是性能非常好。又由于它工作在4层,因此不会处理应用层数据,经常有人问ipvs能不能做SSL证书卸载、或者修改HTTP头部数据,显然这些都不可能做的。

而ipvs工作在内核态,只处理四层协议,因此只能基于路由或者NAT进行数据转发,可以把ipvs当作一个特殊的路由器网关,这个网关可以根据一定的算法自动选择下一跳,或者把ipvs当作一个多重DNAT,按照一定的算法把ip包的目标地址DNAT到其中真实服务的目标IP。针对如上两种情况分别对应ipvs的两种模式–网关模式和NAT模式,另外ipip模式则是对网关模式的扩展

用法

ipvsadm命令行用法和iptables命令行用法非常相似,毕竟是兄弟,比如-L列举,-A添加,-D删除。

1
ipvsadm -A -t 192.168.193.172:32016 -s rr

但是其实ipvsadm相对iptables命令简直太简单了,因为没有像iptables那样存在各种table,table嵌套各种链,链里串着一堆规则,ipvsadm就只有两个核心实体,分别为service和server,service就是一个负载均衡实例,而server就是后端member,ipvs术语中叫做real server,简称RS。

如下命令创建一个service实例172.17.0.1:32016-t指定监听的为TCP端口,-s指定算法为轮询算法rr(Round Robin),ipvs支持简单轮询(rr)、加权轮询(wrr)、最少连接(lc)、源地址或者目标地址散列(sh、dh)等10种调度算法。

1
ipvsadm -A -t 172.17.0.1:32016 -s rr

然后把1x.xx.1.2:8080、1x.xx.1.3:8080、1x.xx.3.2:8080添加到service后端member中。

1
2
3
ipvsadm -a -t 172.17.0.1:32016 -r 1x.xx.1.2:8080 -m -w 1
ipvsadm -a -t 172.17.0.1:32016 -r 1x.xx.1.3:8080 -m -w 1
ipvsadm -a -t 172.17.0.1:32016 -r 1x.xx.3.2:8080 -m -w 1

其中-t指定service实例,-r指定server地址,-w指定权值,-m即前面说的转发模式,其中-m表示为masquerading,即NAT模式,-ggatewaying,即直连路由模式,-iipip,ji即IPIP隧道模式。

与iptables-save、iptables-restore对应的工具ipvs也有ipvsadm-save、ipvsadm-restore。

NAT(network access translation)模式

NAT模式由字面意思理解就是通过NAT实现的,但究竟是如何NAT转发的,我们通过实验环境验证下。

现环境中LB节点IP为192.168.193.197,三个RS节点如下:

  • 192.168.193.172:30620
  • 192.168.193.194:30620
  • 192.168.193.226:30620

为了模拟LB节点IP和RS不在同一个网络的情况,在LB节点中添加一个虚拟IP地址:

1
ip addr add 10.222.0.1/24 dev ens5

创建负载均衡Service并把RS添加到Service中:

1
2
3
4
ipvsadm -A -t 10.222.0.1:8080 -s rr
ipvsadm -a -t 10.222.0.1:8080 -r 192.168.193.194:30620 -m
ipvsadm -a -t 10.222.0.1:8080 -r 192.168.193.226:30620 -m
ipvsadm -a -t 10.222.0.1:8080 -r 192.168.193.172:30620 -m

这里需要注意的是,和应用层负载均衡如haproxy、nginx不一样的是,haproxy、nginx进程是运行在用户态,因此会创建socket,本地会监听端口,而ipvs的负载是直接运行在内核态的,因此不会出现监听端口:

1
2
3
4
5
6
root@ip-192-168-193-197:/var/log# netstat -lnpt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN 674/systemd-resolve
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 950/sshd
tcp6 0 0 :::22 :::* LISTEN 950/sshd

可见并没有监听10.222.0.1:8080 Socket

Client节点IP为192.168.193.226,为了和LB节点的虚拟IP 10.222.0.1通,我们手动添加静态路由如下:

1
ip r add 10.222.0.1 via 192.168.193.197 dev ens5

此时Client节点能够ping通LB节点VIP:

1
2
3
4
5
6
7
8
root@ip-192-168-193-226:~# ping -c 2 -w 2 10.222.0.1
PING 10.222.0.1 (10.222.0.1) 56(84) bytes of data.
64 bytes from 10.222.0.1: icmp_seq=1 ttl=64 time=0.345 ms
64 bytes from 10.222.0.1: icmp_seq=2 ttl=64 time=0.249 ms

--- 10.222.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1022ms
rtt min/avg/max/mdev = 0.249/0.297/0.345/0.048 ms

可见Client节点到VIP的链路没有问题,那是否能够访问我们的Service呢?

我们验证下:

1
2
root@ip-192-168-193-226:~# curl -m 2 --retry 1 -sSL 10.222.0.1:8080
curl: (28) Connection timed out after 2001 milliseconds

非常意外的结果是并不通。

在RS节点抓包如下:

img

我们发现数据包的源IP为Client IP,目标IP为RS IP,换句话说,LB节点IPVS只做了DNAT,把目标IP改成RS IP了,而没有修改源IP。此时虽然RS和Client在同一个子网,链路连通性没有问题,但是由于Client节点发出去的包的目标IP和收到的包源IP不一致,因此会被直接丢弃,相当于给张三发信,李四回的信,显然不受信任。

既然IPVS没有给我们做SNAT,那自然想到的是我们手动做SNAT,在LB节点添加如下iptables规则:

1
2
iptables -t nat -A POSTROUTING -m ipvs  --vaddr 10.222.0.1 --vport 8080 -j LOG --log-prefix '[int32bit ipvs]'
iptables -t nat -A POSTROUTING -m ipvs --vaddr 10.222.0.1 --vport 8080 -j MASQUERADE

再次检查Service是否可以访问:

1
2
root@ip-192-168-193-226:~# curl -m 2 --retry 1 -sSL 10.222.0.1:8080
curl: (28) Connection timed out after 2001 milliseconds

服务依然不通。并且在LB节点的iptables日志为空:

1
2
root@ip-192-168-193-197:~# cat /var/log/syslog | grep 'int32bit ipvs'
root@ip-192-168-193-197:~#

也就是说,ipvs的包根本不会经过iptables nat表POSTROUTING链?

那mangle表呢?我们打开LOG查看下:

1
iptables -t mangle -A POSTROUTING -m ipvs --vaddr 10.222.0.1 --vport 8080 -j LOG --log-prefix "[int32bit ipvs]"

此时查看日志如下:

img

我们发现在mangle表中可以看到DNAT后的包。

只是可惜mangle表的POSTROUTING并不支持NAT功能:

img

对比Kubernetes配置发现需要设置如下系统参数:

1
sysctl net.ipv4.vs.conntrack=1

再次验证:

1
2
3
4
5
6
7
8
root@ip-192-168-193-226:~# curl -i 10.222.0.1:8080
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Wed, 27 Nov 2019 15:28:06 GMT
Connection: keep-alive
Transfer-Encoding: chunked

Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-v1-c5ccf9784-g9bkx | v=1

终于通了,查看RS抓包:

img

如期望,修改了源IP为LB IP。

原来需要配置net.ipv4.vs.conntrack=1参数,这个问题折腾了一个晚上,不得不说目前ipvs的文档都太老了。

前面是通过手动iptables实现SNAT的,性能可能会有损耗,于是如下开源项目通过修改lvs直接做SNAT:

除了SNAT的办法,是否还有其他办法呢?想想我们最初的问题,Client节点发出去的包的目标IP和收到的包源IP不一致导致包被丢弃,那解决问题的办法就是把包重新引到LB节点上,只需要在所有的RS节点增加如下路由即可:

1
ip r add 192.168.193.226 via 192.168.193.197 dev ens5

此时我们再次检查我们的Service是否可连接:

1
2
3
4
5
6
7
8
root@ip-192-168-193-226:~# curl -i -m 2 --retry 1 -sSL 10.222.0.1:8080
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Wed, 27 Nov 2019 03:21:47 GMT
Connection: keep-alive
Transfer-Encoding: chunked

Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-v1-c5ccf9784-4v9z4 | v=1

结果没有问题。

不过我们是通过手动添加Client IP到所有RS的明细路由实现的,如果Client不固定,这种方案仍然不太可行,所以通常做法是干脆把所有RS默认路由指向LB节点,即把LB节点当作所有RS的默认网关。

由此可知,用户通过LB地址访问服务,LB节点IPVS会把用户的目标IP由LB IP改为RS IP,源IP不变,包不经过iptables的OUTPUT直接到达POSTROUTING转发出去,包回来的时候也必须先到LB节点,LB节点把目标IP再改成用户的源IP,最后转发给用户。

显然这种模式来回都需要经过LB节点,因此又称为双臂模式。

2.4 网关(Gatewaying)模式

网关模式(Gatewaying)又称为直连路由模式(Direct Routing)、透传模式,所谓透传即LB节点不会修改数据包的源IP、端口以及目标IP、端口,LB节点做的仅仅是路由转发出去,可以把LB节点看作一个特殊的路由器网关,而RS节点则是网关的下一跳,这就相当于对于同一个目标地址,会有多个下一跳,这个路由器网关的特殊之处在于能够根据一定的算法选择其中一个RS作为下一跳,达到负载均衡和冗余的效果。

既然是通过直连路由的方式转发,那显然LB节点必须与所有的RS节点在同一个子网,不能跨子网,否则路由不可达。换句话说,这种模式只支持内部负载均衡(Internal LoadBalancer)

另外如前面所述,LB节点不会修改源端口和目标端口,因此这种模式也无法支持端口映射,换句话说LB节点监听的端口和所有RS节点监听的端口必须一致

现在假设有LB节点IP为192.168.193.197,有三个RS节点如下:

  • 192.168.193.172:30620
  • 192.168.193.194:30620
  • 192.168.193.226:30620

创建负载均衡Service并把RS添加到Service中:

1
2
3
4
ipvsadm -A -t 192.168.193.197:30620 -s rr
ipvsadm -a -t 192.168.193.197:30620 -r 192.168.193.194:30620 -g
ipvsadm -a -t 192.168.193.197:30620 -r 192.168.193.226:30620 -g
ipvsadm -a -t 192.168.193.197:30620 -r 192.168.193.172:30620 -g

注意到我们的Service监听的端口30620和RS的端口是一样的,并且通过-g参数指定为直连路由模式(网关模式)。

Client节点IP为192.168.193.226,我们验证Service是否可连接:

1
2
root@ip-192-168-193-226:~# curl -m 5 -sSL 192.168.193.197:30620
curl: (28) Connection timed out after 5001 milliseconds

我们发现并不通,在其中一个RS节点192.168.193.172上抓包:

img

正如前面所说,LB是通过路由转发的,根据路由的原理,源MAC地址修改为LB的MAC地址,而目标MAC地址修改为RS MAC地址,相当于RS是LB的下一跳。

并且源IP和目标IP都不会修改。问题就来了,我们Client期望访问的是RS,但RS收到的目标IP却是LB的IP,发现这个目标IP并不是自己的IP,因此不会通过INPUT链转发到用户空间,这时要不直接丢弃这个包,要不根据路由再次转发到其他地方,总之两种情况都不是我们期望的结果。

那怎么办呢?为了让RS接收这个包,必须得让RS有这个目标IP才行。于是不妨在lo上添加个虚拟IP,IP地址伪装成LB IP 192.168.193.197:

1
ifconfig lo:0 192.168.193.197/32

问题又来了,这就相当于有两个相同的IP,IP重复了怎么办?办法是隐藏这个虚拟网卡,不让它回复ARP,其他主机的neigh也就不可能知道有这么个网卡的存在了,参考Using arp announce/arp ignore to disable ARP

1
2
sysctl net.ipv4.conf.lo.arp_ignore=1
sysctl net.ipv4.conf.lo.arp_announce=2

此时再次从客户端curl:

1
2
root@ip-192-168-193-226:~# curl -m 2 --retry 1 -sSL 192.168.193.197:30620
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-v1-c5ccf9784-4v9z4 | v=1

终于通了。

我们从前面的抓包中知道,源IP为Client IP 192.168.193.226,因此直接回包给Client即可,不可能也不需要再回到LB节点了,即A->B,B->C,C->A,流量方向是三角形状的,因此这种模式又称为三角模式。

我们从原理中不难得出如下结论:

  • Client、LB以及所有的RS必须在同一个子网。
  • LB节点直接通过路由转发,因此性能非常高。
  • 不能做端口映射。

2.5 ipip隧道模式

前面介绍了网关直连路由模式,要求所有的节点在同一个子网,而ipip隧道模式则主要解决这种限制,LB节点IP和RS可以不在同一个子网,此时需要通过ipip隧道进行传输。

现在假设有LB节点IP为192.168.193.77/25,在该节点上增加一个VIP地址:

ip addr add 192.168.193.48/25 dev eth0

有三个RS节点如下:

  • 192.168.193.172:30620
  • 192.168.193.194:30620
  • 192.168.193.226:30620

如上三个RS节点子网掩码均为255.255.255.128,即25位子网,显然和VIP 192.168.193.48/25不在同一个子网。

创建负载均衡Service并把RS添加到Service中:

1
2
3
4
ipvsadm -A -t 192.168.193.48:30620 -s rr
ipvsadm -a -t 192.168.193.48:30620 -r 192.168.193.194:30620 -i
ipvsadm -a -t 192.168.193.48:30620 -r 192.168.193.226:30620 -i
ipvsadm -a -t 192.168.193.48:30620 -r 192.168.193.172:30620 -i

注意到我们的Service监听的端口30620和RS的端口是一样的,并且通过-i参数指定为ipip隧道模式。

在所有的RS节点上加载ipip模块以及添加VIP(和直连路由类型):

1
2
3
4
modprobe ipip
ifconfig tunl0 192.168.193.48/32
sysctl net.ipv4.conf.tunl0.arp_ignore=1
sysctl net.ipv4.conf.tunl0.arp_announce=2

Client节点IP为192.168.193.226/25,我们验证Service是否可连接:

1
2
3
4
5
6
7
8
9
root@ip-192-168-193-226:~# curl -i -sSL 192.168.193.48:30620
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Wed, 27 Nov 2019 07:05:40 GMT
Connection: keep-alive
Transfer-Encoding: chunked

Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-v1-c5ccf9784-dgn74 | v=1
root@ip-192-168-193-226:~#

Service可访问,我们在RS节点上抓包如下:

img

我们发现和直连路由一样,源IP和目标IP没有修改。

所以IPIP模式和网关(Gatewaying)模式原理基本一样,唯一不同的是网关(Gatewaying)模式要求所有的RS节点和LB节点在同一个子网,而IPIP模式则可以支持跨子网的情况,为了解决跨子网通信问题,使用了ipip隧道进行数据传输。

2.4 总结

ipvs是一个内核态的四层负载均衡,支持NAT、Gateway以及IPIP隧道模式,Gateway模式性能最好,但LB和RS不能跨子网,IPIP性能次之,通过ipip隧道解决跨网段传输问题,因此能够支持跨子网。而NAT模式没有限制,这也是唯一一种支持端口映射的模式。

我们不难猜想,由于Kubernetes Service需要使用端口映射功能,因此kube-proxy必然只能使用ipvs的NAT模式。

3 kube-proxy使用ipvs模式

3.1 配置kube-proxy使用ipvs模式

使用kubeadm安装Kubernetes可参考文档Cluster Created by Kubeadm,不过这个文档的安装配置有问题kubeadm #1182,如下官方配置不生效:

1
2
3
4
5
kubeProxy:
config:
featureGates:
SupportIPVSProxyMode: true
mode: ipvs

需要修改为如下配置:

1
2
3
4
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs

可以通过如下命令确认kube-proxy是否修改为ipvs:

1
2
# kubectl get configmaps kube-proxy -n kube-system -o yaml | awk '/mode/{print $2}'
ipvs

3.2 Service ClusterIP原理

创建一个ClusterIP类似的Service如下:

1
2
# kubectl get svc | grep kubernetes-bootcamp-v1
kubernetes-bootcamp-v1 ClusterIP 1x.xx.54.11 <none> 8080/TCP 2m11s

ClusterIP 1x.xx.54.11为我们查看ipvs配置如下:

1
2
3
4
5
# ipvsadm -S -n | grep 1x.xx.54.11
-A -t 1x.xx.54.11:8080 -s rr
-a -t 1x.xx.54.11:8080 -r 1x.xx.1.2:8080 -m -w 1
-a -t 1x.xx.54.11:8080 -r 1x.xx.1.3:8080 -m -w 1
-a -t 1x.xx.54.11:8080 -r 1x.xx.2.2:8080 -m -w 1

可见ipvs的LB IP为ClusterIP,算法为rr,RS为Pod的IP。

另外我们发现使用的模式为NAT模式,这是显然的,因为除了NAT模式支持端口映射,其他两种均不支持端口映射,所以必须选择NAT模式。

由前面的理论知识,ipvs的VIP必须在本地存在,我们可以验证:

1
2
3
4
5
6
7
8
9
10
11
# ip addr show kube-ipvs0
4: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default
link/ether 46:6b:9e:af:b0:60 brd ff:ff:ff:ff:ff:ff
inet 1x.xx.0.1/32 brd 1x.xx.0.1 scope global kube-ipvs0
valid_lft forever preferred_lft forever
inet 1x.xx.0.10/32 brd 1x.xx.0.10 scope global kube-ipvs0
valid_lft forever preferred_lft forever
inet 1x.xx.54.11/32 brd 1x.xx.54.11 scope global kube-ipvs0
valid_lft forever preferred_lft forever
# ethtool -i kube-ipvs0 | grep driver
driver: dummy

可见kube-proxy首先会创建一个dummy虚拟网卡kube-ipvs0,然后把所有的Service IP添加到kube-ipvs0中。

(dummy网卡是内核虚拟出来的网卡)

我们知道基于iptables的Service,ClusterIP是一个虚拟的IP,因此这个IP是ping不通的,但ipvs中这个IP是在每个节点上真实存在的,因此可以ping通:

img

当然由于这个IP就是配置在本地虚拟网卡上,所以对诊断问题没有一点用处的。

我们接下来研究下ClusterIP如何传递的。

当我们通过如下命令连接服务时:

1
curl 1x.xx.54.11:8080

此时由于1x.xx.54.11就在本地,所以会以这个IP作为出口地址,即源IP和目标IP都是1x.xx.54.11,此时相当于:

1
1x.xx.54.11:xxxx -> 1x.xx.54.11:8080

其中xxxx为随机端口。

然后经过ipvs,ipvs会从RS ip列中选择其中一个Pod ip作为目标IP,假设为1x.xx.2.2:

1
2
3
4
5
1x.xx.54.11:xxxx -> 1x.xx.54.11:8080
|
| IPVS
v
1x.xx.54.11:xxxx -> 1x.xx.2.2:8080

我们从iptables LOG可以验证:

img

我们查看OUTPUT安全组规则如下:

1
2
3
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A KUBE-SERVICES ! -s 1x.xx.0.0/16 -m comment --comment "Kubernetes service cluster ip + port for masquerade purpose" -m set --match-set KUBE-CLUSTER-IP dst,dst -j KUBE-MARK-MASQ
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000

其中ipset集合KUBE-CLUSTER-IP保存着所有的ClusterIP以及监听端口。

如上规则的意思就是除了Pod以外访问ClusterIP的包都打上0x4000/0x4000

到了POSTROUTING链:

1
2
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE

如上规则的意思就是只要匹配mark0x4000/0x4000的包都做SNAT,由于1x.xx.2.2是从flannel.1出去的,因此源ip会改成flannel.1的ip 1x.xx.0.0

1
2
3
4
5
6
7
8
9
1x.xx.54.11:xxxx -> 1x.xx.54.11:8080
|
| IPVS
v
1x.xx.54.11:xxxx -> 1x.xx.2.2:8080
|
| MASQUERADE
v
1x.xx.0.0:xxxx -> 1x.xx.2.2:8080

最后通过Vxlan 隧道发到Pod的Node上,转发给Pod的veth,回包通过路由到达源Node节点,源Node节点通过之前的MASQUERADE再把目标IP还原为1x.xx.54.11。

3.3 NodePort实现原理

查看Service如下:

1
2
3
4
root@ip-192-168-193-172:~# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 1x.xx.0.1 <none> 443/TCP 30h
kubernetes-bootcamp-v1 NodePort 1x.xx.54.11 <none> 8080:32016/TCP 8h

Service kubernetes-bootcamp-v1的NodePort为32016。

现在假设集群外的一个IP 192.168.193.197访问192.168.193.172:32016:

1
192.168.193.197:xxxx -> 192.168.193.172:32016

最先到达PREROUTING链:

1
2
3
4
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A KUBE-SERVICES -m addrtype --dst-type LOCAL -j KUBE-NODE-PORT
-A KUBE-NODE-PORT -p tcp -m comment --comment "Kubernetes nodeport TCP port for masquerade purpose" -m set --match-set KUBE-NODE-PORT-TCP dst -j KUBE-MARK-MASQ
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000

如上4条规则看起来复杂,其实就做一件事,如果目标地址为NodeIP,则把包标记0x40000x4000

我们查看ipvs:

1
2
3
4
5
# ipvsadm -S -n | grep 32016
-A -t 192.168.193.172:32016 -s rr
-a -t 192.168.193.172:32016 -r 1x.xx.1.2:8080 -m -w 1
-a -t 192.168.193.172:32016 -r 1x.xx.1.3:8080 -m -w 1
-a -t 192.168.193.172:32016 -r 1x.xx.3.2:8080 -m -w 1

我们发现和ClusterIP实现原理非常相似,ipvs Service的VIP为Node IP,端口为NodePort。ipvs会选择其中一个Pod IP作为DNAT目标,这里假设为1x.xx.3.2:

1
2
3
4
5
192.168.193.197:xxxx -> 192.168.193.172:32016
|
| DNAT
v
192.168.193.197:xxx --> 1x.xx.3.2:8080

剩下的到了POSTROUTING链就和Service ClusterIP完全一样了,只要匹配0x4000/0x4000的包就会做SNAT。

3.4 总结

Kubernetes的ClusterIP和NodePort都是通过ipvs service实现的,Pod当作ipvs service的server,通过NAT MQSQ实现转发。

简单来说kube-proxy主要在所有的Node节点做如下三件事:

  1. 如果没有dummy类型虚拟网卡,则创建一个,默认名称为kube-ipvs0;
  2. 把Kubernetes ClusterIP地址添加到kube-ipvs0,同时添加到ipset中。
  3. 创建ipvs service,ipvs service地址为ClusterIP以及Cluster Port,ipvs server为所有的Endpoint地址,即Pod IP及端口。

使用ipvs作为kube-proxy后端,不仅提高了转发性能,结合ipset还使iptables规则变得更“干净”清楚,从此再也不怕iptables。

更多关于kube-proxy ipvs参考IPVS-Based In-Cluster Load Balancing Deep Dive.

subpath的用法

一直没用过挂载的subpath参数。写了个资源文件测试了一下,发现自己之前的理解错的离谱。

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
apiVersion: v1
metadata:
name: my-lamp-site-data
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: nfs-client
---
apiVersion: v1
kind: Pod
metadata:
name: my-lamp-site
spec:
containers:
- name: mysql
image: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "rootpasswd"
volumeMounts:
- mountPath: /var/lib/mysql
name: site-data
subPath: mysql
- name: php
image: php:7.0-apache
volumeMounts:
- mountPath: /var/www/html
name: site-data
subPath: html
volumes:
- name: site-data
persistentVolumeClaim:
claimName: my-lamp-site-data

创建一个pod。有两个容器,php和mysql。使用同一个volume。

特别注意mountPathsubPath的写法, 最后的path要保持一致。

这个volume是一个pvc。

创建好之后,进入php容器查看下

1
2
3
4
5
6
7
8
9
[root@paas-m-k8s-master-1 ~]# kc exec -it my-lamp-site -c php  -- /bin/sh
# pwd
/var/www/html
# touch a.txt
# ls
a.txt
# cat > a.txt << EOF
> asdhkljfga
> EOF

同样进入mysql容器,发现并没有a.txt

也创建一个b.txt

image-20231220105346872

回到php容器,同样也是看不到的

image-20231220105600246

去pv挂载的目录看看,我这里用的是nfs

1
2
3
4
5
6
7
8
9
[root@paas-m-k8s-nfs default-my-lamp-site-data-pvc-33ac110d-d2b3-4b91-be84-b481f1a4bd0f]# ll
总用量 4
drwxrwxrwx 2 root root 19 12月 20 10:49 html
drwxrwxrwx 6 polkitd root 4096 12月 20 10:52 mysql
[root@paas-m-k8s-nfs default-my-lamp-site-data-pvc-33ac110d-d2b3-4b91-be84-b481f1a4bd0f]# ls html/
a.txt
[root@paas-m-k8s-nfs default-my-lamp-site-data-pvc-33ac110d-d2b3-4b91-be84-b481f1a4bd0f]# ls mysql/
auto.cnf binlog.000002 b.txt ca.pem ...

发现根本就是两个目录。

我以前的理解是完全错误的,subpath不是为了多个容器之间共享数据。而是为了使用一个volume,但是分目录来使用。

要使一个pod的多个容器能够共享数据,直接使用一个挂载就好

总之,什么时候应该使用 subpath

  1. 场景一: 一个共享卷, 挂载多个路径.
  2. 场景二: ConfigMap或Secret挂载到特定目录的特定路径, 而 该目录下已经有其他文件且不希望被覆盖

误删pvc的血泪教训

顺便测试下pv删除时的保留策略

查看pv的策略是Delete

image-20231220110332320

删除pvc之后,服务器上的目录直接会被删除

image-20231220111204838

pv的reclaim policy是继承自storage class

改一下storage class

image-20231220111343980

不能直接改成Retain,因为storage class一旦创建就无法修改。

只能删除重新创建。

所以,我们最好是创建个新的storage class。不要动原来的东西。

新的storageclass

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
apiVersion: v1
items:
- allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
annotations:
storageclass.kubernetes.io/is-default-class: "true"
storageclass.kubesphere.io/support-snapshot: "false"
storageclass.kubesphere.io/supported_access_modes: '["ReadWriteOnce","ReadOnlyMany","ReadWriteMany"]'
labels:
app: nfs-provisioner
chart: nfs-client-provisioner-1.1.2
heritage: Tiller
release: nfs-client-retain
name: nfs-client-retain
mountOptions:
- nfsvers=3
parameters:
archiveOnDelete: "false"
provisioner: cluster.local/nfs-client-nfs-client-provisioner
reclaimPolicy: Retain
volumeBindingMode: Immediate
kind: List
metadata:
resourceVersion: ""
selfLink: ""

1
2
# kubectl apply -f storageClass-retain.yaml
storageclass.storage.k8s.io/nfs-client-retain created
1
2
3
4
5
[root@paas-m-k8s-master-1 yaml]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-client cluster.local/nfs-client-nfs-client-provisioner Delete Immediate true 2y160d
nfs-client-reatain (default) cluster.local/nfs-client-nfs-client-provisioner Retain Immediate true 106s
rook-ceph-block (default) rook-ceph.rbd.csi.ceph.com Delete Immediate true 2y145d

多个我们新创建的nfs-client-reatain

使用nfs-client-retain再做测试

1
2
3
4
5
6
7
8
9
10
11
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: my-lamp-site-data
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: nfs-client-retain

删除pod和pvc之后。

image-20231220113202898

可以看到目录还在

建议将storageclass的保留策略reclaimPolicy设置为Retain。

因为发生过误删除pvc的血泪教训。

固定IP搭建redis集群方案二

在方案一中我们使用statefuset的6个副本,固定在6个ip的ip池中。

但是仍然会有IP绑定变换的问题,虽然测试效果显示集群整体功能不受影响,但是谨慎起见,我仍然有些不放心。

既然我们可以给pod绑定ip了,那么我直接编写6个deployment,每个deployment使用1个副本,并且直接写死ip。

应该就可以搭建出完全固定ip的容器化redis-cluster方案。

建一个ippool

给redis容器专门建一个大的IPpool。以后业务申请的redis ip全部从这里分配

1
2
3
4
5
6
7
8
9
10
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: redis-ippool
spec:
blockSize: 26
cidr: 10.10.0.0/20
ipipMode: Always
natOutgoing: true

image-20230718100213762

blockSize 设置为26。那么这个k8s集群可以容纳64个主机,足够了。每个ip段是64个ip。

1
2
3
4
[root@paas-m-k8s-master-1 calico]# calicoctl get ippool
NAME CIDR SELECTOR
default-ipv4-ippool 100.64.0.0/10 all()
redis-ippool 10.10.0.0/20 all()

建6个deployment

举一个例子,其余5个类似

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
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: redis-pvc-0
namespace: redis-test
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
storageClassName: nfs-client
volumeMode: Filesystem
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: redis-test
name: redis-cluster-0
labels:
app: redis-cluster-0
spec:
replicas: 1
selector:
matchLabels:
app: redis-cluster-0
template:
metadata:
labels:
app: redis-cluster-0
annotations:
"cni.projectcalico.org/ipAddrs": "[\"10.10.0.1\"]"
spec:
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: redis-pvc-0
- name: config
configMap:
name: redis-config
containers:
- name: redis
image: redis:5.0.5-alpine
command:
- "sh"
- "-c"
- "redis-server /data/conf/redis.conf"
ports:
- containerPort: 6379
name: client
- containerPort: 16379
name: gossport
resources:
limits:
cpu: "1"
volumeMounts:
- name: redis-data
mountPath: /data/data
- name: config
mountPath: /data/conf

全部创建出来

1
2
3
4
5
6
kc apply -f redis-deploy-0.yaml
kc apply -f redis-deploy-1.yaml
kc apply -f redis-deploy-2.yaml
kc apply -f redis-deploy-3.yaml
kc apply -f redis-deploy-4.yaml
kc apply -f redis-deploy-5.yaml

image-20230718113151524

全部启动成功,然后组成集群

1
kubectl -n redis-test exec -it redis-cluster-0-77dbcddbb8-w29hm -- redis-cli --cluster create --cluster-replicas 1 -a 123456 10.10.0.1:6379 10.10.0.2:6379 10.10.0.3:6379 10.10.0.4:6379 10.10.0.5:6379 10.10.0.6:6379 

完成。

测试

image-20230718113748035

全部删除deploy后重新部署deploy

image-20230718144518127

一切正常。

测试单个master宕机,主备切换也没问题。

现在的问题

现在的问题是,我发现所有的pod都分配在同一台node上,这是因为我们在分配ip时,是连续使用的,这个ip段都是在这一台node节点上。

calico根据blockSize给每个node分配地址段。

1
2
blockSize: 26
cidr: 10.10.0.0/20

这里我们用的blockSize是26位,所以每个ip段是64个ip。

我这个测试k8s,node节点只有2个,node5和node6

现在用了10.10.10.1~6左右的ip都落在了node6上,可以看到calico建了一条路由,10.10.0.0,掩码255.255.255.192

都到node6上

image-20230718160117696

我们创建一个ip是10.10.0.100的看下

image-20230718161122335

还是落在node6

建了一个10.10.15.254试试

image-20230718161212654

终于落到node5

现在的路由表

image-20230718161312683

关于calico的ip分配

Calico分配IP地址的原则为,将整个IPPool分为多个地址块,每个Node获得一个Block

在Calico默认的使用模式中,Calico每个Node一个分配一个Block,每个Block默认为64个IP,当单个Node启动的Pod超过64时,才会分配下一个Block。

因为现在我测试环境只有两个node,所以,如果我使用下面的ippool配置

1
2
blockSize: 21
cidr: 11.11.0.0/20

那么就只能分出2个block,正好一个node一个。

每个block里有2^11个ip。2048

image-20230718163605930

起两个nginx镜像测试下路由分配。

image-20230718163812076

image-20230718163851450

可以看到这条路由的掩码是21位

image-20230718164248652

所以下一个block可用ip是11.11.8.1

然而很可惜,下一个block,也分配给了node5

image-20230718165556227

查了一下,calico分配block有一个Block affinity亲和性策略,不好把握。

看3.24版本的calico开始可以配置Block affinity。但是我的calico版本还不支持。

目前研究到这里。

总结

这个方案的问题在于,ip和node节点绑死了,如果按照递增的方式来分配redis的ip,将导致所有的redis节点全部部署在一个node上,一旦node宕机,这些pod无法在其他node拉起,这样失去了k8s调度的意义。

如果想采用将ip block分配到不同的机器,再从这些block中选ip的方式,其操作很难控制。

所以,我认为这个方案也不好。

现在我觉得固定ip和ip地址池的方案都不好。。。

[TOC]

sealos+nfs+kubesphere3.0

主机清单及软件版本

主机名 IP 集群角色 操作系统 磁盘规划
dts-paas-middleware-dev-master-0 1x.xxx.1.156 master CentOS Linux 7.9.2009
dts-paas-middleware-dev-master-1 1x.xxx.1.157 master CentOS Linux 7.9.2009
dts-paas-middleware-dev-master-2 1x.xxx.1.158 master CentOS Linux 7.9.2009
dts-paas-middleware-dev-node-0 1x.xxx.1.159 node CentOS Linux 7.9.2009
dts-paas-middleware-dev-node-1 1x.xxx.1.160 node CentOS Linux 7.9.2009
dts-paas-middleware-dev-node-2 1x.xxx.1.161 node CentOS Linux 7.9.2009
dts-paas-middleware-dev-node-3 1x.xxx.1.162 node CentOS Linux 7.9.2009
dts-paas-middleware-dev-node-4 1x.xxx.1.163 node CentOS Linux 7.9.2009
dts-paas-middleware-dev-node-5 1x.xxx.1.164 nfs服务器 CentOS Linux 7.9.2009 nfs服务器

sealos版本 v3.3.9-alpha.2

kubenetes版本 v1.18.8

rook版本 v1.4.4

kubesphere版本 3.0

准备工作

配置master到node的免密登录

sealos使用ansible部署kubernetes,本例使用master节点作为ansible的agent,ansible会从agent ssh到集群内所有机器上,为了方便配置master到node的免密登录,否则再使用sealos时需要指定登录node的password或者私钥。

登录master,本例为dts-paas-middleware-dev-master-0

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
#执行ssh-keygen生成私钥/公钥
[root@d-paas-k8s-master-0 ~]# ssh-keygen
直接回车
cd .ssh
#执行以下命令,将上一步生成的公钥上传到1x.xx.66.1,会得到一个包含url的返回值
[root@d-paas-k8s-master-0 .ssh]# curl --upload-file id_rsa.pub 1x.xx.66.1
http://1x.xx.66.1/z7EVd/id_rsa.pub

#wget fire_auth.sh脚本并修改
[root@d-paas-k8s-master-0 ~]# wget http://1x.xx.66.1/gKVtY/fire_auth.sh
[root@d-paas-k8s-master-0 ~]# vi fire_auth.sh
wget http://1x.xx.66.1/IXJfv/id_rsa.pub #修改为上一步返回的url
cat id_rsa.pub >> .ssh/authorized_keys
systemctl start firewalld.service
firewall-cmd --add-masquerade --permanent
# only if you want NodePorts exposed on control plane IP as well
firewall-cmd --permanent --add-port=0-65535/tcp
firewall-cmd --permanent --add-port=0-65535/udp
firewall-cmd --permanent --zone=trusted --change-interface=docker0
firewall-cmd --permanent --add-rich-rule="rule family="ipv4" source address="1x.xxx.1.156/32" port protocol="tcp" port="22" accept" #address修改为master IP,本例为1x.xxx.1.156
systemctl restart firewalld
firewall-cmd --reload
systemctl enable firewalld
systemctl disable firewalld
systemctl stop firewalld
echo "sshd: 1x.xxx.1.156" >> /etc/hosts.allow #IP国旗为 master IP 本例为1x.xxx.1.156

#将修改过的脚本复制到集群内主机。
curl --upload-file fire_auth.sh 1x.xx.66.1
http://1x.xx.66.1/tUl4q/fire_auth.sh
#集群内所有机器都需要执行此脚本,包括master本身。
[root@d-paas-k8s-master-0 ~]# ./fire_auth.sh
[root@d-paas-k8s-0-node-0 ~]# ./fire_auth.sh
[root@d-paas-k8s-0-node-1 ~]# ./fire_auth.sh
[root@d-paas-k8s-0-node-2 ~]# ./fire_auth.sh

检查时间同步

在每台机器上执行

1
2
3
4
5
6
7
8
9
10
11
12
[root@d-paas-k8s-master-0 ~]# yum install -y chrony
[root@d-paas-k8s-master-0 ~]# systemctl enable --now chronyd
[root@d-paas-k8s-master-0 ~]# timedatectl set-timezone Asia/Shanghai
[root@d-paas-k8s-master-0 ~]# timedatectl
Local time: Wed 2020-11-11 11:08:52 CST
Universal time: Wed 2020-11-11 03:08:52 UTC
RTC time: Wed 2020-11-11 03:08:52
Time zone: Asia/Shanghai (CST, +0800)
NTP enabled: yes
NTP synchronized: yes
RTC in local TZ: no
DST active: n/a

sealos部署kubernetes集群

准备软件包

在其中一个master节点上执行,本例为dts-paas-middleware-dev-master-0

1
2
3
4
5
6
7
8
9
10
11
#下载sealos二进制文件
[root@d-paas-k8s-master-0 ~]# wget -c https://sealyun.oss-cn-beijing.aliyuncs.com/v3.3.9-alpha.2/sealos && chmod +x sealos && mv sealos /usr/bin

或者
wget http://1x.xx.66.1/6GGuB/sealos && chmod +x sealos && mv sealos /usr/bin

#下载kubernetes 离线安装包
[root@d-paas-k8s-master-0 ~]# wget -c https://sealyun.oss-cn-beijing.aliyuncs.com/cd3d5791b292325d38bbfaffd9855312-1.18.8/kube1.18.8.tar.gz

或者
wget http://1x.xx.66.1/NPUq8/kube1.18.8.tar.gz

部署安装

在master节点上执行,本例为dts-paas-middleware-dev-master-0

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
#查看网卡信息选择集群间可以进行通信的网卡,本例是team0
[root@d-paas-k8s-master-0 ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: em1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master team0 state UP group default qlen 1000
link/ether 80:18:44:e7:84:28 brd ff:ff:ff:ff:ff:ff
3: em2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master team0 state UP group default qlen 1000
link/ether 80:18:44:e7:84:28 brd ff:ff:ff:ff:ff:ff
4: em3: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN group default qlen 1000
link/ether 80:18:44:e7:84:2a brd ff:ff:ff:ff:ff:ff
5: em4: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN group default qlen 1000
link/ether 80:18:44:e7:84:2b brd ff:ff:ff:ff:ff:ff
6: team0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 80:18:44:e7:84:28 brd ff:ff:ff:ff:ff:ff
inet 1x.xx.11.7/24 brd 1x.xx.11.255 scope global noprefixroute team0
valid_lft forever preferred_lft forever

如果是这种eth0的,下面的命令可以不指定--interface
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether fa:16:3e:a3:fe:ec brd ff:ff:ff:ff:ff:ff
inet 1x.xxx.1.156/24 brd 1x.xxx.1.255 scope global noprefixroute dynamic eth0
valid_lft 72505sec preferred_lft 72505sec
inet6 fe80::f816:3eff:fea3:feec/64 scope link
valid_lft forever preferred_lft forever


#运行sealos命令,等待命令运行结束并出现sealos logo
[root@d-paas-k8s-master-0 ~]# sealos init --interface team0 \ #可用网卡名称,上一步得到的team0
--master 1x.xxx.1.156 \
--master 1x.xxx.1.157 \
--master 1x.xxx.1.158 \
--node 1x.xxx.1.159 \
--node 1x.xxx.1.160 \
--node 1x.xxx.1.161 \
--node 1x.xxx.1.162 \
--node 1x.xxx.1.163 \
--pkg-url kube1.18.8.tar.gz \
--version v1.18.8
不加--interface
sealos init \
--master 1x.xxx.1.156 \
--master 1x.xxx.1.157 \
--master 1x.xxx.1.158 \
--node 1x.xxx.1.159 \
--node 1x.xxx.1.160 \
--node 1x.xxx.1.161 \
--node 1x.xxx.1.162 \
--node 1x.xxx.1.163 \
--pkg-url kube1.18.8.tar.gz \
--version v1.18.8

#检查kube-system namespace下 pod是否都是running状态
[root@d-paas-k8s-master-0 ~]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-84445dd79f-5vqjl 1/1 Running 0 20h
calico-node-5qsqv 1/1 Running 0 20h
calico-node-cnbj9 1/1 Running 0 20h
calico-node-g6cmr 1/1 Running 0 20h
calico-node-x9tmz 1/1 Running 3 20h
coredns-66bff467f8-f7n76 1/1 Running 0 20h
coredns-66bff467f8-xhlvg 1/1 Running 2 20h
etcd-d-paas-k8s-master-0 1/1 Running 3 20h
kube-apiserver-d-paas-k8s-master-0 1/1 Running 3 20h
kube-controller-manager-d-paas-k8s-master-0 1/1 Running 2 20h
kube-proxy-4n5vp 1/1 Running 0 20h
kube-proxy-cw95q 1/1 Running 0 20h
kube-proxy-hvssj 1/1 Running 0 20h
kube-proxy-r6gm8 1/1 Running 2 20h
kube-scheduler-d-paas-k8s-master-0 1/1 Running 2 20h
kube-sealyun-lvscare-d-paas-k8s-0-node-0 1/1 Running 0 20h
kube-sealyun-lvscare-d-paas-k8s-0-node-1 1/1 Running 0 20h
kube-sealyun-lvscare-d-paas-k8s-0-node-2 1/1 Running 0 20h

遇到的坑

第一次使用sealos 安装时,使用的如下命令,并未使用–interface指定网卡

1
sealos init --master 1x.xx.11.7 --node 1x.xx.11.4 --node 1x.xx.11.5 --node 1x.xx.11.6  --pkg-url kube1.18.8.tar.gz --version v1.18.8

sealos提示安装成功后,

kubectl get pods -nkube-system 检查pod状态,发现calico-node-xxxx不是Running状态,通过kubectl logs查看其中一个calico-node的log,发现报错找不到包含eth.|en.|em.*的可用网卡。

使用sealos时,如果不用–interface 指定网卡,默认会使用正则表达式寻找eth.|en.|em.*开头的可用网卡,本例中主机间通信的网卡名称是team0刚好不在正则表达式搜索的范围内。

出现此问题,两种解法方案

第一种方案:sealos清理集群,重新部署加上–interface指定可用网卡

1
2
3
4
5
6
7
8
9
10
#删除k8s集群
[root@d-paas-k8s-master-0 ~]# sealos clean --all
#重新部署
[root@d-paas-k8s-master-0 ~]# sealos init --interface team0 \ #可用网卡名称,上一步得到的team0
--master 1x.xx.11.7 \
--node 1x.xx.11.4 \
--node 1x.xx.11.5 \
--node 1x.xx.11.6 \
--pkg-url kube1.18.8.tar.gz \
--version v1.18.8

如果需要指定pod网段需要增加–podcidr 1x.xxx.128.0/23

第二种方案:直接修改calio-node daemonset里面的环境变量,网卡信息写在了里面,修改完毕后重启calio-node daemonset

1
2
3
4
5
6
7
#编辑calio-node deamonset,找到env下面name: IP_AUTODETECTION_METHOD项目value 改为 interface=指定网卡
[root@d-paas-k8s-master-0 ~]# kubectl edit ds calico-node -n kube-system
- name: IP_AUTODETECTION_METHOD
value: interface=team0

#重新部署calico-node deamonset
[root@d-paas-k8s-master-0 ~]# kubectl -nkube-system rollout restart ds calico-node

K8S创建使用NFS的storageClass

搭建NFS共享存储

选择一台worker节点的磁盘作为共享存储。这里选了1x.xxx.1.164

1, 创建nfs共享目录

1
mkdir /nfs

2,安装nfs组件

nfs服务端和客户端都安装,所有节点都安装

1
2
3
4
yum -y install nfs-utils
设置开机启动
systemctl enable rpcbind.service
systemctl enable nfs-server.service

3,编辑exports文件

服务端执行

1
2
3
vim /etc/exports
添加如下内容
/nfs 1x.xxx.1.0/24(rw,sync,no_root_squash,no_wdelay)

含义:

  • /nfs为第一步创建的共享目录

  • 1x.xxx.1.0/24表示共享给ip为1x.xxx.1.*的机器

  • 权限列表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    rw表示可读写,ro只读
    sync :同步模式,内存中数据实时写入磁盘;
    async :不同步,数据在内存中,定期写入磁盘
    no_root_squash :加上这个选项后,root用户就会对共享的目录像是对本机的目录一样拥有最高权限。
    root_squash:和上面的选项对应,root用户对共享目录的权限不高,只有普通用户的权限
    all_squash:不管使用NFS的用户是谁,他的身份都会被限定成为一个指定的普通用户身份
    anonuid=xxx/anongid=xxx :要和root_squash 以及all_squash一同使用,用于指定使用NFS的用户限定后的uid和gid,前提是本机的/etc/passwd中存在这个uid和gid
    wdelay(默认):检查是否有相关的写操作,如果有则将这些写操作一起执行,这样可以提高效率;
    no_wdelay:若有写操作则立即执行,应与sync配合使用;

4,启动nfs服务

服务端执行

1
2
systemctl start rpcbind
systemctl start nfs

客户端执行

1
systemctl start rpcbind

5,客户端挂载

1
mount -t nfs 1x.xxx.1.164:/nfs /nfs

可通过下面的命令查看

1
showmount -e 1x.xxx.1.164

客户端命令整合:

mkdir /nfs && yum -y install nfs-utils && systemctl enable rpcbind.service && systemctl enable nfs-server.service && systemctl start rpcbind && mount -t nfs 1x.xxx.151.207:/nfs /nfs && showmount -e 1x.xxx.151.207

创建sc

0,k8s1.6+默认开启rbac,所以必须执行如下命令授权provisioner

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
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "endpoints"]
verbs: ["get"]
- apiGroups: ["extensions"]
resources: ["podsecuritypolicies"]
resourceNames: ["nfs-provisioner"]
verbs: ["use"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-provisioner
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-provisioner
apiGroup: rbac.authorization.k8s.io
1
kubectl apply -f rbac.yaml

2,创建创建nfs客户端deployment。nfs-provisioner.yaml

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
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs-provisioner
spec:
replicas: 1
selector:
matchLabels:
app: nfs-provisioner
release: nfs-client
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-provisioner
release: nfs-client
spec:
serviceAccount: nfs-provisioner
containers:
- name: nfs-provisioner
image: kubesphere/nfs-client-provisioner:v3.1.0-k8s1.11
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: cluster.local/nfs-client-nfs-client-provisioner
- name: NFS_SERVER
value: 1x.xxx.1.164
- name: NFS_PATH
value: /nfs
volumes:
- name: nfs-client-root
nfs:
server: 1x.xxx.1.164
path: /nfs
1
kubectl apply -f nfs-provisioner.yaml

3,创建storageClass.yaml

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
apiVersion: v1
items:
- allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
annotations:
storageclass.kubernetes.io/is-default-class: "true"
storageclass.kubesphere.io/support-snapshot: "false"
storageclass.kubesphere.io/supported_access_modes: '["ReadWriteOnce","ReadOnlyMany","ReadWriteMany"]'
labels:
app: nfs-provisioner
chart: nfs-client-provisioner-1.1.2
heritage: Tiller
release: nfs-client
name: nfs-client
mountOptions:
- nfsvers=3
parameters:
archiveOnDelete: "false"
provisioner: cluster.local/nfs-client-nfs-client-provisioner
reclaimPolicy: Delete
volumeBindingMode: Immediate
kind: List
metadata:
resourceVersion: ""
selfLink: ""
1
kubectl apply -f  storageClass.yaml
1
2
3
[root@idp-restore-master01 sc]# kubectl get storageClass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-client (default) cluster.local/nfs-client-nfs-client-provisioner Delete Immediate true 9s

创建pvc验证(不必须)

1,创建一个pvc

1
2
3
4
5
6
7
8
9
10
11
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: nfs-client
1
kubectl apply -f my-pvc.yaml

查看下pvc的状态

1
kubectl get pvc

image-20210510164348228

测试(不必须)

使用pvc创建一个测试pod,目的是测试在共享目录下创建一个SUCCESS文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
kind: Pod
apiVersion: v1
metadata:
name: test-pod
spec:
containers:
- name: test-pod
image: willdockerhub/busybox:1.24
command:
- "/bin/sh"
args:
- "-c"
- "touch /mnt/SUCCESS && exit 0 || exit 1"
volumeMounts:
- name: nfs-pvc
mountPath: "/mnt"
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: my-pvc
1
kubectl apply -f test-pod.yaml

之后去nfs服务器查看

image-20210511100535175

部署kubesphere

准备软件包

1
2
[root@d-paas-k8s-master-0 ~]# wget https://github.com/kubesphere/ks-installer/releases/download/v3.0.0/kubesphere-installer.yaml
[root@d-paas-k8s-master-0 ~]# wget https://github.com/kubesphere/ks-installer/releases/download/v3.0.0/cluster-configuration.yaml

备用地址

wget http://1x.xx.66.1/SiVrh/kubesphere-installer.yaml

wget http://1x.xx.66.1/XrrTk/cluster-configuration.yaml

修改cluster-configuration.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#配置所有组件enabletrue  
console:
enableMultiLogin: true # enable/disable multiple sing on, it allows an account can be used by different users at the same time.
port: 30880
alerting: # (CPU: 0.3 Core, Memory: 300 MiB) Whether to install KubeSphere alerting system. It enables Users to customize alerting policies to send messages to receivers in time with different time intervals and alerting levels to choose from.
enabled: true
auditing: # Whether to install KubeSphere audit log system. It provides a security-relevant chronological set of records,recording the sequence of activities happened in platform, initiated by different tenants.
enabled: true
devops: # (CPU: 0.47 Core, Memory: 8.6 G) Whether to install KubeSphere DevOps System. It provides out-of-box CI/CD system based on Jenkins, and automated workflow tools including Source-to-Image & Binary-to-Image.
enabled: true
.......
logging: # (CPU: 57 m, Memory: 2.76 G) Whether to install KubeSphere logging system. Flexible logging functions are provided for log query, collection and management in a unified console. Additional log collectors can be added, such as Elasticsearch, Kafka and Fluentd.
enabled: true
........
metrics_server: # (CPU: 56 m, Memory: 44.35 MiB) Whether to install metrics-server. IT enables HPA (Horizontal Pod Autoscaler).
enabled: true
monitoring:
enabled: true
notification: # Email Notification support for the legacy alerting system, should be enabled/disabled together with the above alerting option.
enabled: true
openpitrix: # (2 Core, 3.6 G) Whether to install KubeSphere Application Store. It provides an application store for Helm-based applications, and offer application lifecycle management.
enabled: true
servicemesh: # (0.3 Core, 300 MiB) Whether to install KubeSphere Service Mesh (Istio-based). It provides fine-grained traffic management, observability and tracing, and offer visualization for traffic topology.
enabled: true

部署kubesphere

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
[root@d-paas-k8s-master-0 ~]# kubectl apply -f kubesphere-installer.yaml
[root@d-paas-k8s-master-0 ~]# kubectl apply -f cluster-configuration.yaml
#查看部署日志
[root@d-paas-k8s-master-0 ~]# kubectl logs -n kubesphere-system $(kubectl get pod -n kubesphere-system -l app=ks-install -o jsonpath='{.items[0].metadata.name}') -f

#当出现以下信息时,证明部署完成
#####################################################
### Welcome to KubeSphere! ###
#####################################################

Console: http://1x.xx.11.4:30880
Account: admin
Password: P@88w0rd

NOTES:
1. After you log into the console, please check the
monitoring status of service components in
"Cluster Management". If any service is not
ready, please wait patiently until all components
are up and running.
2. Please change the default password after login.

#####################################################
https://kubesphere.io 2020-11-11 09:29:12
#####################################################

遇到的坑:

日志卡在Failed to ansible-playbook result-info.yaml,查看kubectl get all -n kubesphere-system,有很多pod没起来,比如redis的。

kubectl describe pod/redis-ha-server-0 -n kubesphere-system

看到报错

1
2
3
4
5
6
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedMount 7m31s (x134 over 3h41m) kubelet, idp-restore-master02 (combined from similar events): MountVolume.SetUp failed for volume "pvc-e88e0ca4-0047-47fa-ad2c-17499d5b44c3" : mount failed: exit status 32
。。。
mount: wrong fs type, bad option, bad superblock on 1x.xxx.1.164:/nfs/kubesphere-system-data-redis-ha-server-0-pvc-e88e0ca4-0047-47fa-ad2c-17499d5b44c3,

是nfs挂载的问题

之前master节点没有安装nfs客户端,安装一下就好了。

然后重新执行ks-installer

kubectl rollout restart deployment/ks-installer -n kubesphere-system

配置kubesphere链接ldap

卸载k8s

1
sealos clean --all=true

kubeAdm部署K8S高可用集群

前置准备

服务器环境

修改hostname
1
vim /etc/hostname
打通ssh免密登录

选一台master机器到其他机器的免密ssh登录

关闭防火墙
1
2
systemctl stop firewalld
systemctl disable firewalld
禁用selinux

getenforce 如果是disable即为已经关闭,否则执行

1
2
setenforce 0
sed -i 's/^SELINUX=.*$/SELINUX=disable/' /etc/selinux/config
关闭swap分区
1
2
3
4
5
swapoff -a
echo "vm.swappiness=0" >> /etc/sysctl.conf
sysctl -p /etc/sysctl.conf
sed -i 's$/dev/mapper/centos-swap$#/dev/mapper/centos-swap$g' /etc/fstab
free -m
ulimit
1
2
3
4
5
6
7
8
9
10
11
ulimit -n
1024

临时生效设置如下:
# ulimit -SHn 65535

永久生效设置,添加如下两行
# vim /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535

时间同步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
先查看一下系统是否安装ntp,如下命令:
[root@k8s-master21 ~]# rpm -qa ntp

如果没有安装ntp,则如下安装:
[root@k8s-master21 ~]# yum install ntp -y

先设置好时区,如下:
[root@k8s-master21 ~]# ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
[root@k8s-master21 ~]# echo "Asia/Shanghai" > /etc/timezone

再设置阿里云的时间同步服务器,如下:
[root@k8s-master21 ~]# ntpdate time2.aliyun.com
26 Dec 15:35:57 ntpdate[7043]: step time server 203.107.6.88 offset -8.227227 sec

再将时间同步添加到系统定时任务中,如下命令后,添加 */5 * * * * ntpdate time2.aliyun.com 保存退出即可:
[root@k8s-master21 ~]# crontab -e
*/5 * * * * ntpdate time2.aliyun.com

最后,将时间同步添加到开机自启动中,打开/etc/rc.local 添加 ntpdate time2.aliyun.com 保存退出即可:
[root@k8s-master21 ~]# vim /etc/rc.local
ntpdate time2.aliyun.com
开启ipv4 转发

为了让 Kubernetes 能够检查、转发网络流量

1
2
3
4
5
cat > /etc/sysctl.d/k8s.conf << EOF
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
1
2
modprobe br_netfilter
sysctl -p /etc/sysctl.d/k8s.conf
加载ip_vs模块
1
2
3
4
5
6
7
8
cat > /etc/sysconfig/modules/ipvs.modules <<EOF
#!/bin/bash
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack_ipv4
EOF
1
chmod 755 /etc/sysconfig/modules/ipvs.modules && bash  /etc/sysconfig/modules/ipvs.modules && lsmod | grep -e ip_vs -e nf_conntrack_ipv4

部署docker

移除旧版本docker,避免干扰
1
2
3
4
5
6
7
8
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
安装依赖
1
yum install -y yum-utils device-mapper-persistent-data lvm2
指定阿里云镜像源
1
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
部署docker
1
yum install docker-ce
1
2
3
4
5
6
7
8
mkdir -p /etc/docker/

cat > /etc/docker/daemon.json <<EOF
{
"exec-opts": ["native.cgroupdriver=systemd"],
"registry-mirrors": ["https://6kx4zyno.mirror.aliyuncs.com"]
}
EOF

“exec-opts”: [“native.cgroupdriver=systemd”] 为kubelete启动必须

然后重启docker

1
systemctl enable docker && systemctl daemon-reload &&  systemctl restart docker

部署K8S

部署kubeadm kubelet kubectl

配置镜像源
1
2
3
4
5
6
7
8
9
cat > /etc/yum.repos.d/kubernetes.repo <<EOF
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
1
2
3
4
5
yum install -y kubectl-1.22.8 kubelet-1.22.8 kubeadm-1.22.8

# 也可安装kubelet kubeadm kubectl 其他版本 但注意需要和docker版本匹配
# 查看 可安装 kubelet kubeadm kube版本
# yum list kubeadm kubelet kubectl --showduplicates|sort -r
配置kubelet的pause镜像

默认配置的pause镜像使用gcr.io仓库,国内可能无法访问,所以这里配置Kubelet使用阿里云的pause镜像:

1
2
3
cat >/etc/sysconfig/kubelet<<EOF
KUBELET_EXTRA_ARGS="--cgroup-driver=systemd --pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.2"
EOF
设置开机启动
1
systemctl daemon-reload && systemctl enable --now kubelet

安装master

使用kubeadm init需要一个config配置文件

可以使用先init一个默认config文件

1
kubeadm config print init-defaults

所有master节点

1
vim /root/kubeadm-config.yaml

内容按实际情况修改,kubeadm 配置 (v1beta3) | Kubernetes

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
apiVersion: kubeadm.k8s.io/v1beta3
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: abcdef.0123456789abcdef
ttl: 24h0m0s
usages:
- signing
- authentication
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 1x.xx.12.181
bindPort: 6443
nodeRegistration:
criSocket: /var/run/dockershim.sock
imagePullPolicy: IfNotPresent
name: k8s-master1
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
---
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controlPlaneEndpoint: 1x.xx.12.181:6443
controllerManager: {}
dns: {}
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers
kind: ClusterConfiguration
kubernetesVersion: 1.22.0
networking:
dnsDomain: cluster.local
podSubnet: 192.168.0.0/16
serviceSubnet: 1x.xx.0.0/12
scheduler: {}

提前下载镜像,可以节省初始化时间:

1
kubeadm config images pull --config /root/kubeadm-config.yaml 

在master1节点进行初始化

1
kubeadm init --config /root/kubeadm-config.yaml  --upload-certs

初始化以后会在/etc/kubernetes目录下生成对应的证书和配置文件,之后其他Master节点加入Master1即可。

成功之后会打印

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

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of the control-plane node running the following command on each as root:

kubeadm join 1x.xx.12.181:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:30e24f2bf41b30864c9a2aff54aece349f3bdff2882b9b54145cd9c7976e8ef9 \
--control-plane --certificate-key e1fec22ea86d49cf3f74bab846075cf234fb19f4de18243bcdd220537fa4285f

Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 1x.xx.12.181:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:30e24f2bf41b30864c9a2aff54aece349f3bdff2882b9b54145cd9c7976e8ef9

kubeadm 的配置管理是通过 pod 管理的,所有的组件都是通过容器启动的,通过 /etc/kubernetes/manifests 目录下面的 yaml 文件启动,这就是 kubelet 生命周期管理的目录,在这里面配置一个 pod 的 yaml 文件,它就会为你管理 pod 的生命周期。
进入到该目录中,可以看到以下文件

1
2
3
cd /etc/kubernetes/manifests
ls
etcd.yaml kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml

master1节点配置环境变量,用于kubectl访问Kubernetes集群。

通过 admin.conf 文件和 k8s 通讯

1
2
3
4
cat <<EOF >> /root/.bashrc
export KUBECONFIG=/etc/kubernetes/admin.conf
EOF
source /root/.bashrc

查看节点状态:

1
2
3
[root@k8s-master1 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master1 NotReady control-plane,master 13m v1.22.8

查看service

1
2
3
4
[root@k8s-master1 ~]# kubectl get svc -A
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default kubernetes ClusterIP 1x.xx.0.1 <none> 443/TCP 19m
kube-system kube-dns ClusterIP 1x.xx.0.10 <none> 53/UDP,53/TCP,9153/TCP 18m

采用初始化安装方式,所有的系统组件均以容器的方式运行并且在kube-system命名空间内

查看pod

1
2
3
4
5
6
7
8
9
10
[root@k8s-master1 ~]# kubectl get pod -A -owide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system coredns-7d89d9b6b8-6xpvs 0/1 Pending 0 49m <none> <none> <none> <none>
kube-system coredns-7d89d9b6b8-d5q2h 0/1 Pending 0 49m <none> <none> <none> <none>
kube-system etcd-k8s-master1 1/1 Running 0 50m 1x.xx.12.181 k8s-master1 <none> <none>
kube-system kube-apiserver-k8s-master1 1/1 Running 1 (34m ago) 51m 1x.xx.12.181 k8s-master1 <none> <none>
kube-system kube-controller-manager-k8s-master1 1/1 Running 1 (108s ago) 3m10s 1x.xx.12.181 k8s-master1 <none> <none>
kube-system kube-proxy-kb9w9 1/1 Running 0 49m 1x.xx.12.181 k8s-master1 <none> <none>
kube-system kube-scheduler-k8s-master1 1/1 Running 1 (109s ago) 3m10s 1x.xx.12.181 k8s-master1 <none> <none>

全部running

加入其他master

使用前面init成功后打印的join

1
2
3
kubeadm join 1x.xx.12.181:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:e8158944761027754f2ff819e8d299af1095bbc12699a2f34b6d8d80a67d8b6f \
--control-plane --certificate-key 707aa3ccb1d514ea0270683f7c0ed351f08976617cebe902f1771f6198b6b344

注意,token的有效期默认设置是24小时,过期后需要重新生成

1
2
3
4
5
[root@k8s-master1 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master1 NotReady control-plane,master 59m v1.22.8
k8s-master2 NotReady control-plane,master 2m38s v1.22.8
k8s-master3 NotReady control-plane,master 2m37s v1.22.8

image-20220803174716699

加入node节点

1
2
kubeadm join 1x.xx.12.181:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:e8158944761027754f2ff819e8d299af1095bbc12699a2f34b6d8d80a67d8b6f

image-20220803175123082

安装Calico

在master1节点进行

问题解决:

安装docker需要container-selinux >= 2:2.74

1
2
错误:软件包:containerd.io-1.6.6-3.1.el7.x86_64 (docker-ce-stable)
需要:container-selinux >= 2:2.74

http://mirror.centos.org/centos/7/extras/x86_64/Packages/

image-20220803141326417

1
yum install -y http://mirror.centos.org/centos/7/extras/x86_64/Packages/container-selinux-2.119.2-1.911c772.el7_8.noarch.rpm

更好的解决办法:

#进入yum 源配置文件夹
cd /etc/yum.repos.d
mv CentOS-Base.repo CentOS-Base.repo_bak

在/etc/yum.repos.d/docker-ce.repo文件顶部添加一个条目,内容如下:
[centos-extras]
name=Centos extras - $basearch
baseurl=http://mirror.centos.org/centos/7/extras/x86_64
enabled=1
gpgcheck=0

保存退出

#然后安装命令:
yum -y install slirp4netns fuse-overlayfs container-selinux

controller-manager和scheduler状态异常

1
2
3
4
5
6
7
8
9
[root@k8s-master1 ~]# kubectl get pod -A -owide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system coredns-7d89d9b6b8-6xpvs 0/1 Pending 0 28m <none> <none> <none> <none>
kube-system coredns-7d89d9b6b8-d5q2h 0/1 Pending 0 28m <none> <none> <none> <none>
kube-system etcd-k8s-master1 1/1 Running 0 30m 1x.xx.12.181 k8s-master1 <none> <none>
kube-system kube-apiserver-k8s-master1 1/1 Running 1 (14m ago) 30m 1x.xx.12.181 k8s-master1 <none> <none>
kube-system kube-controller-manager-k8s-master1 0/1 CrashLoopBackOff 7 (4m31s ago) 30m 1x.xx.12.181 k8s-master1 <none> <none>
kube-system kube-proxy-kb9w9 1/1 Running 0 28m 1x.xx.12.181 k8s-master1 <none> <none>
kube-system kube-scheduler-k8s-master1 0/1 CrashLoopBackOff 6 (4m27s ago) 30m 1x.xx.12.181 k8s-master1 <none> <none>

看到kube-controller-manager-k8s-master1和kube-scheduler-k8s-master1都是CrashLoopBackOff

查看原因

1
Liveness probe failed: Get "https://127.0.0.1:10257/healthz": dial tcp 127.0.0.1:10257: connect: connection refused

出现这种情况是kube-controller-manager.yaml和kube-scheduler.yaml设置的默认端口是0,在文件中注释掉就可以了。(每台master节点都要执行操作)

image-20220803173233845

1
2
vim /etc/kubernetes/manifests/kube-scheduler.yaml
vim /etc/kubernetes/manifests/kube-controller-manager.yaml

重启kubelet

1
systemctl restart kubelet.service

Error from server: etcdserver: request timed out

1
2
docker ps -a
docker logs -f etcd的容器id

image-20220804155119899

看到一个错误信息

1
leader failed to send out heartbeat on time; took too long, leader is overloaded likely from slow disk

查看服务器的io性能

image-20220804162104655

看起来不妙啊,再用sar看看

1
sar -d 1 10
1
2
3
4
平均时间:       DEV       tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util
平均时间: vda 12.80 0.00 248.00 19.38 2.28 177.77 35.69 45.68
平均时间: vdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
平均时间: centos-root 24.70 0.00 252.00 10.20 2.29 92.52 40.52 100.09

await:平均每次设备 I/O 操作的等待时间

svctm:平均每次设备 I/O 操作的服务时间

%util:一秒中有百分之几的时间用于 I/O 操作

如果 svctm 的值与 await 很接近,表示几乎没有 I/O 等待,磁盘性能很好,如果 await 的值远高于 svctm 的值,则表示 I/O 队列等待太长,系统上运行的应用程序将变慢,此时可以通过更换更快的硬盘来解决问题。

1
iostat -dxk 1 10

通过iostat 也可看磁盘性能,现在发现磁盘性能确实不行。

我测了其他一台机器

1
2
3
平均时间:       DEV       tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util
平均时间: vda 51.10 0.00 554.40 10.85 0.03 0.65 0.70 3.56
平均时间: vdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

差距极大。

这几台测试机器是openstack虚拟化的老旧服务器,磁盘是老旧的sata盘,也没做优化。这性能无法支持etcd集群。只能装单节点。

1
The connection to the server 1x.xx.12.181:6443 was refused - did you specify the right host or port?

检查各服务状态

1
2
3
systemctl status docker.service
systemctl status kubelet.service
systemctl status firewalld.service #关闭状态

正常

检查端口是否有监听

1
netstat -pnlt | grep 6443

无监听

查看kubelet日志

1
journalctl -xeu kubelet

image-20220804154816188

卸载

1
2
3
4
5
6
7
8
9
10
11
kubeadm reset -f
modprobe -r ipip
yum -y remove kubeadm* kubectl* kubelet* docker*
rm -rf ~/.kube/
rm -rf /etc/kubernetes/
rm -rf /etc/systemd/system/kubelet.service.d
rm -rf /etc/systemd/system/kubelet.service
rm -rf /usr/bin/kube*
rm -rf /var/lib/etcd
rm -rf /var/etcd

制作镜像

好了,前面是使用make run进行测试运行。现在我们把operator打出镜像进行分发。

先修改一下Dockerfile,否则可能下载依赖有问题

1
2
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.cn,direct

然后,默认的这个FROM gcr.io/distroless/static:nonroot也是下不到的

替换成这个

1
anjia0532/distroless.static:nonroot

image-20240314175005139 制作镜像部署.assets\image-20240314175005139.png)

我这里是自己搭的私有harbor

1
make docker-build docker-push IMG=harbor-test.xxx.net/paas/demo-operator:1.0

image-20240314173521885 制作镜像部署.assets\image-20240314173521885.png)

部署operator镜像

部署有两种方案

make deploy

使用项目自带的deploy指令,这种方式是将operator部署到本地集群中,其实和make run差不多

1
make deploy IMG=harbor-test.xxx.net/paas/demo-operator:1.0

也可修改~/.kube/config来连接其他集群,但还是太麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kustomize build config/default | kubectl apply -f -
namespace/demo-operator-system created
customresourcedefinition.apiextensions.k8s.io/demoes.tutorial.demo.com unchanged
serviceaccount/demo-operator-controller-manager created
role.rbac.authorization.k8s.io/demo-operator-leader-election-role created
clusterrole.rbac.authorization.k8s.io/demo-operator-manager-role created
clusterrole.rbac.authorization.k8s.io/demo-operator-metrics-reader created
clusterrole.rbac.authorization.k8s.io/demo-operator-proxy-role created
rolebinding.rbac.authorization.k8s.io/demo-operator-leader-election-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/demo-operator-manager-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/demo-operator-proxy-rolebinding created
configmap/demo-operator-manager-config created
service/demo-operator-controller-manager-metrics-service created
deployment.apps/demo-operator-controller-manager created

查看部署情况

image-20240315092100583 制作镜像部署.assets\image-20240315092100583.png)

查看一下pod的日志

image-20240315092820168 制作镜像部署.assets\image-20240315092820168.png)

我们再部署一个demo测试一下

1
kubectl apply -f config/samples/tutorial_v1_demo.yaml

image-20240315093227608 制作镜像部署.assets\image-20240315093227608.png)

执行调谐完成

如果你的部署遇到问题,可能会遇到镜像下载不下来的问题。

原因还是gcr.io/kubebuilder/kube-rbac-proxy被墙了

改一下

image-20240314175854575 制作镜像部署.assets\image-20240314175854575.png)

yaml部署

我们需要的当然是把写的operator分发到别的集群部署。

通过分析make deploy脚本,来编写yaml

1
2
3
4
.PHONY: deploy
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
$(KUSTOMIZE) build config/default | kubectl apply -f -

这个脚本的本质就是用kustomize对config下的manager和default中的yaml进行变量替换

然后整合成一个yaml,传给kubectl apply执行

所以啊,我们只要执行下这两行就可以得到我们想要的yaml文件,然后就可以随便到别的集群执行了哦

1
2
3
cd config/manager && /usr/local/bin/kustomize edit set image controller=harbor-test.xxx.net/paas/demo-operator:1.0

cd ../.. && /usr/local/bin/kustomize build config/default > demo-operator.yaml

输出这样一个yaml

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Namespace
metadata:
labels:
control-plane: controller-manager
name: demo-operator-system
---
太长了,不贴了

去别的集群,部署试试

部署operator

1
kubectl apply -f demo-operator.yaml

部署一个demo crd

1
kubectl apply -f demo-simple.yaml

image-20240315095308702 制作镜像部署.assets\image-20240315095308702.png)

完成