阿里slb做为k8s的负载均衡的限制
keepalived的限制
如果在本地搭建,我们可以使用haproxy+keepalived方式轻松实现k8s中的负载均衡,但是阿里的ecs不能使用keepalived,所以我们被迫只能使用阿里的 slb了。
理论
既然keepalived的方式不能使用,那我们就使用阿里的slb进行负载均衡呗,由于该负载均衡不需要被外部访问,只提供对k8s集群节点之间的访问,所以我们就使用私网的slb。
[图片上传失败...(image-b02d7-1604545387128)]
实践
我们保证该slb和k8s集群节点的node属于同一个局域网内,具体配置如下
系统角色 | ip | 节点角色 | CPU | Memory | Hostname」 |
---|---|---|---|---|---|
CentOS 7.6 64位 | 192.168.0.161 | master | 2 vCPU | 4 GiB | master1 |
CentOS 7.6 64位 | 192.168.0.162 | master | 2 vCPU | 4 GiB | master2 |
CentOS 7.6 64位 | 192.168.0.163 | master | 2 vCPU | 4 GiB | master3 |
私网slb | 192.168.4.11 | 负载均衡 |
slb创建后端服务器
第一步就是监听该slb的6443端口,该端口后端的服务器组分别是3台ecs的6443端口(apiserver服务)。接着我们可以在master1节点上执行如下命令
nc -v 192.168.4.11 6433
由于后端服务器组的 apiserver 都尚未运行,预期会出现一个连接拒绝错误。然而超时意味着负载均衡器不能和控制平面节点通信。 如果发生超时,请重新配置负载均衡器与控制平面节点进行通信。
kubeadm-config.yaml文件
我们在master1节点上创建kubeadm-config.yaml文件,用于初始化控制平面节点,如下。
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: 1.19.2
controlPlaneEndpoint: 192.168.4.11:6443
networking:
# This CIDR is a Calico default. Substitute or remove for your CNI provider.
podSubnet: "172.22.0.0/16"
# # 控制平面节点,安装kube-apiserver,kube-cotroller-manager,kube-scheduler,kube-proxy,这些组件的版本最好和k8s的版本相同,还有安装pause,etcd,coredns
imageRepository: phperall
内网slb的限制
接着我们在master1节点上执行如下命令初始化
kubeadm init --config kubeadm-config.yaml --upload-certs
最后结果如下
[kubelet-check] Initial timeout of 40s passed.
Unfortunately, an error has occurred:
timed out waiting for the condition
This error is likely caused by:
- The kubelet is not running
- The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)
If you are on a systemd-powered system, you can try to troubleshoot the error with the following commands:
- 'systemctl status kubelet'
- 'journalctl -xeu kubelet'
Additionally, a control plane component may have crashed or exited when started by the container runtime.
To troubleshoot, list all containers using your preferred container runtimes CLI.
Here is one example how you may list all Kubernetes containers running in docker:
- 'docker ps -a | grep kube | grep -v pause'
Once you have found the failing container, you can inspect its logs with:
- 'docker logs CONTAINERID'
error execution phase wait-control-plane: couldn't initialize a Kubernetes cluster
To see the stack trace of this error execute with --v=5 or higher
看上面的日志好像是kubelet的问题。我们先确认kubelet是否运行,发现处于running状态。
[root@master ~]# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; vendor preset: disabled)
Drop-In: /usr/lib/systemd/system/kubelet.service.d
└─10-kubeadm.conf
Active: active (running) since Wed 2020-11-04 10:36:54 CST; 7min ago
Docs: https://kubernetes.io/docs/
Main PID: 23284 (kubelet)
Tasks: 13
Memory: 29.5M
CGroup: /system.slice/kubelet.service
接着查看kubelet的日志
[root@master ~]# journalctl -xeu kubelet
Nov 04 10:45:36 master kubelet[23284]: E1104 10:45:36.317572 23284 kubelet.go:2183] node "master" not found
Nov 04 10:45:36 master kubelet[23284]: E1104 10:45:36.360976 23284 controller.go:136] failed to ensure node lease exists, will retry in 7s, error: Get "https://192.168.4.11:6443/apis/coordination.k8s.io/v1/namespaces/kube-node-lease/leases/master?timeout
Nov 04 10:45:36 master kubelet[23284]: E1104 10:45:36.417732 23284 kubelet.go:2183] node "master" not found
Nov 04 10:45:36 master kubelet[23284]: E1104 10:45:36.517936 23284 kubelet.go:2183] node "master" not found
Nov 04 10:45:36 master kubelet[23284]: E1104 10:45:36.618138 23284 kubelet.go:2183] node "master" not found
Nov 04 10:45:36 master kubelet[23284]: E1104 10:45:36.718290 23284 kubelet.go:2183] node "master" not found
Nov 04 10:45:36 master kubelet[23284]: E1104 10:45:36.818500 23284 kubelet.go:2183] node "master" not found
Nov 04 10:45:36 master kubelet[23284]: E1104 10:45:36.918713 23284 kubelet.go:2183] node "master" not found
Nov 04 10:45:36 master kubelet[23284]: E1104 10:45:36.944451 23284 kubelet.go:2103] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized
Nov 04 10:45:37 master kubelet[23284]: E1104 10:45:37.018813 23284 kubelet.go:2183] node "master" not found
Nov 04 10:45:37 master kubelet[23284]: E1104 10:45:37.118960 23284 kubelet.go:2183] node "master" not found
发现一个奇怪的问题,https://192.168.4.11:6443提示timeout。
接着我们在master1节点上首先测试本地的6443端口是否已经启用
[root@master ~]# telnet 192.168.0.161 6443
Trying 192.168.0.161...
Connected to 192.168.0.161.
Escape character is '^]'.
[root@master ~]# netstat -anp | grep 6443
tcp6 0 0 :::6443 :::* LISTEN 23931/kube-apiserve
看到master1节点的6443端口已经被占用,接着我们在master1节点测试slb的6443端口服务,按理说master1节点的6443服务已经启用,那么slb的6443服务也应该是可用可连通的。
[root@master ~]# telnet 192.168.4.11 6443
Trying 192.168.4.11...
遗憾的是slb的6443端口并没有连通,我们在master2,master3节点上分别连接slb的6443端口,发现都timeout。我们又找了同一局域网内的另一台ecs,该ecs不属于slb的后端服务器,在该ecs上却能连接slb的6443端口,现在问题找到了:
对于
私网slb
,后端服务器组的服务已经启用,但是在后端节点上
连接slb时却报连接超时,也正是这个原因导致控制平面节点初始化失败。
带着这个疑问我们提了阿里工单,客服最后给出结论。
对于四层监听的后端服务器无法访问私网SLB问题,是由于目前负载均衡不支持同时作为客户端和服务端,因为SLB tcp协议监听,是直接转发客户端IP和连接给后端ECS,当后端ECS连接SLB端口,SLB转发该连接时,后端ECS“看到”数据包是来自自己的IP,回包就不会回给SLB了,无法正常建立连接,所以telnet会不通。
而ECS可以telnet 公网SLB端口,是因为使用的是ECS的公网IP,VPC ECS的公网IP是在网络层映射到ECS的内网IP上的,ECS内部并没有该公网IP,所以可以telnet通公网SLB的端口。
私网的slb是不可以使用了,我们换成公网slb之后重新按照上述流传执行一遍,最后初始化控制平面节点成功。
初始化过程
初始化之前slb的6443端口负载的后端服务器组的6443服务肯定都没有启动。
初始化开始后先在本地拉取相关镜像,随后apiserver等服务启动起来,也就是本地的6443服务已经启动。
接着验证slb的6443的连通性,由于master1节点的6443服务已经启动,那么slb的后端组在健康检查中就会发现有master1节点6443端口一起启动,所以slb的6443端口服务也就正常启动了。
通过上面的描述,在控制平面节点
上大致需要满足以下俩点才能初始化成功
- telnet 192.168.0.161 6443 显示连接成功
- telnet 192.168.4.11 6443 显示连接成功
公网slb方式优缺点
优点:可以将kubeconfig文件复制到你笔记本电脑上,进而可以在你本地访问集群,也正是由于这种方式可能造成安全泄漏的问题。
缺点:apiserver暴露的ip是公网ip,一是各个节点之间沟通的效率变低,二是具有安全性问题。
私网slb+haproxy+node方式
如果公司非得使用私网的话,我们可以采取这种方式,大概拓扑图如下
拓扑图
最上层是一个私网的slb,该slb的后端服务器组为俩个haproxy,使用俩台haproxy可以避免haproxy的单点故障,haproxy的后端服务器为3台k8s的master节点。
估计看到这里有人会有疑问,上面介绍的私网slb方式会导致四层监听的后端服务器无法访问私网SLB问题,那么该种方式就不会有这个问题吗?我们带着疑问进行测试。
环境准备
我们准备6台ecs,配置如下
系统角色 | ip | 节点角色 | CPU | Memory | Hostname」 |
---|---|---|---|---|---|
CentOS 7.6 64位 | 192.168.0.161 | master | 2 vCPU | 4 GiB | master1 |
CentOS 7.6 64位 | 192.168.0.162 | master | 2 vCPU | 4 GiB | master2 |
CentOS 7.6 64位 | 192.168.0.163 | master | 2 vCPU | 4 GiB | master3 |
CentOS 7.6 64位 | 192.168.0.164 | haproxy | 2 vCPU | 4 GiB | |
CentOS 7.6 64位 | 192.168.0.165 | haproxy | 2 vCPU | 4 GiB | |
私网slb | 192.168.4.11 | 负载均衡 |
部署
- slb配置
slb监听6443端口,该端口的后端服务器组为俩台haproxy并监听8888端口。
- haproxy配置
haproxy监听8888端口,该端口的后端服务器组为3台控制平面节点并监听6443端口,haproxy.cfg文件如下。
global
# 设置日志文件输出定向
log 127.0.0.1 local3 info
# 改变当前工作目录
chroot /usr/local/haproxy
# 用户与用户组
#user haproxy
#group haproxy
# 守护进程启动,运维方式为后台工作
daemon
# 最大连接数
maxconn 4000
#pid文件位置
pidfile /var/run/haproxy.pid
# 作用于其后紧跟的listen块,直至下一个defaults 块,下一个default 将替换上一个块作用于以后的listen
defaults
# 启用每个实例日志记录事件和流量。
log global
# 默认的模式mode { tcp|http|health },tcp是4层,http是7层,health只会返回OK
mode http
#option forwardfor #如果后端服务器需要获得客户端的真实ip,需要配置的参数,记录客户端IP在X-Forwarded-For头域中,这里设置的话在backend段里面则不需要设置
# maxconn 65535 maxconn 每个进程可用的最大连接数
# retries 3 当对server的connection失败后,重试的次数
# option abortonclose 启用或禁用在队列中挂起的中止请求的早期丢弃
# option redispatch 启用或禁用在连接故障情况下的会话重新分配
# option dontlognull 启用和禁用 记录 空连接
# option httpclose 每次请求完毕后主动关闭http通道,HA-Proxy不支持keep-alive模式
# option forwardfor 获得客户端IP
# option httplog 记录HTTP 请求,session 状态和计时器
option abortonclose #当服务器负载很高的时候,自动结束掉当前队列处理比较久的链接
option redispatch #当serverid对应的服务器挂掉后,强制定向到其他健康的服务器
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
listen k8s-control-plane
#访问的IP和端口
bind 0.0.0.0:8888
#网络协议
mode tcp
#负载均衡算法(轮询算法)
#轮询算法:roundrobin
#权重算法:static-rr
#最少连接算法:leastconn
#请求源IP算法:source
balance roundrobin
#日志格式
option tcplog
#在MySQL中创建一个没有权限的haproxy用户,密码为空。Haproxy使用这个账户对MySQL数据库心跳检测
server master1 192.168.0.161:6443 check weight 1 maxconn 2000
server master2 192.168.0.162:6443 check weight 1 maxconn 2000
server master2 192.168.0.163:6443 check weight 1 maxconn 2000
我们使用haproxy:1.7镜像,在俩台haproxy所在节点分别执行如下操作:
- 创建Dockerfile
FROM haproxy:1.7
RUN mkdir -p /usr/local/haproxy/
COPY ./haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
- 构建镜像
docker build -t my-haproxy .
- 启动容器
docker run -it --name haproxy -p 8888:8888 --privileged my-haprox
- 控制平面节点初始化
kubeadm-config文件中controlPlaneEndpoint参数应为私网slb+6443端口,配置文件如下
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: 1.19.2
controlPlaneEndpoint: 192.168.4.11:6443
networking:
# This CIDR is a Calico default. Substitute or remove for your CNI provider.
podSubnet: "172.22.0.0/16"
# # 控制平面节点,安装kube-apiserver,kube-cotroller-manager,kube-scheduler,kube-proxy,这些组件的版本最好和k8s的版本相同,还有安装pause,etcd,coredns
imageRepository: phperall
执行初始化,发现可以初始化成功
kubeadm init --config kubeadm-config.yaml --upload-certs
- 测试连通性
以下所有测试在master1
节点上测试
我们首先测试master1节点的apserver服务,6443端口是否已经被占用
[root@master ~]# telnet 192.168.0.161 6443
Trying 192.168.0.161...
Connected to 192.168.0.161.
Escape character is '^]'
master1节点的6443端口显示已经被占用,接着我们测试haproxy节点的8888端口是否连通
[root@master ~]# telnet 192.168.0.164 8888
Trying 192.168.0.164...
Connected to 192.168.0.164.
Escape character is '^]'
显示haproxy的8888端口已经连通,接着测试slb的6443端口是否被占用,发现可以连通
[root@master ~]# telnet 192.168.4.11 6443
Trying 192.168.4.11...
Connected to 192.168.4.11.
Escape character is '^]'.
到此说明我们的3层架构都已经连通,说明此方案是可以执行的。
最后我们在
haproxy
所在节点上测试下是否可以连通slb的6443端口
[root@master ~]# telnet 192.168.4.11 6443
Trying 192.168.4.11...
之前提的那个疑问我们现在得到了答案。 但有一点是需要特别注意的
haproxy所在节点一定不能在master所在节点上,需要另外俩台ecs,至于为什么,相信大家已经有答案了。
优缺点
优点:由于中间多了一层haproxy,所以巧妙的解决了私网slb四层监听的后端服务器无法访问私网SLB问题。
缺点:很显而易见了,中间多了一层haproxy的转发代理,而且也增加了成本。
总结
现在大概有俩中方式可以实现k8s的高可用,一种是使用公网slb的方式,另一种是使用私网+haproxy+node的方式,这俩中方式各有优缺点,结合自己的实际情况选择适合的方案。