1. kube-apiserver高可用
K8s 集群高可用本质上是 etcd 的高可用,因为控制面组件都是无状态的,但这里不讨论 etcd,只讨论 kube-apiserver。
前司老架构容器集群产品使用四层负载均衡实现高可用,简单来讲就是 keepalived + LVS,etcd 集群独立部署,Master 节点以 Pod 形式运行在底座上,每个 Pod 里包含多个控制面容器(kube-controller-manager、kube-scheduler 等),通过 localhost 接入到本地 kube-apiserver,工作节点和用户需要经过四层负载均衡访问 kube-apiserver,这个四层负载均衡也不支持绑定 EIP,在云下访问需要转发。
进入新架构后,我们从标准 K8s 切换到了基于 OpenShift Container Platform 改造的集群,Master 节点以虚拟机形式部署,etcd 与 HAProxy 也运行在上面,由于混合云团队不再提供内部四层负载均衡,只能切换到七层负载均衡云产品,它实际上就是 keepalived + Nginx,为了成本和易用性,同时负责 kube-apiserver 与 ingress-controller 的代理,不过只用到了四层端口转发能力,好处是可以绑定 EIP,用户可以按需开启公网访问。
由于四层负载均衡还是七层负载均衡都不是容器团队的产品,因此一直只有比较浅薄的了解,这里就在 PVE 上搭建一个测试环境,研究下负载均衡是如何工作的。
首先部署一个三节点 etcd 集群和两个 keepalived + LVS 负载均衡器,然后创建三个 Master 节点,最后接入两个 Worker 节点,全部使用虚拟机部署,操作系统为 Debian 12,涉及的 IP 如下:
- etcd: 192.168.123.101,192.168.123.102,192.168.123.103
- 负载均衡:192.168.123.91,192.168.123.92,虚 IP 为 192.168.123.90
- Master 节点:192.168.123.95,192.168.123.96,192.168.123.97
- Worker 节点:192.168.123.98,192.168.123.99
这里使用 v3.5.15 版本的 etcd,参考官网的部署文档:https://etcd.io/docs/v3.5/tutorials/how-to-setup-cluster/。
首先下载二进制文件:
ETCD_VER=v3.5.15
# choose either URL
GOOGLE_URL=https://storage.googleapis.com/etcd
GITHUB_URL=https://github.com/etcd-io/etcd/releases/download
DOWNLOAD_URL=${GOOGLE_URL}
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
rm -rf /tmp/etcd-download-test && mkdir -p /tmp/etcd-download-test
curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
/tmp/etcd-download-test/etcd --version
/tmp/etcd-download-test/etcdctl version
/tmp/etcd-download-test/etcdutl version
然后将二进制文件拷贝到三台 etcd 宿主机上:
scp etcd etcdctl etcdutl [email protected]:/usr/local/bin
scp etcd etcdctl etcdutl [email protected]:/usr/local/bin
scp etcd etcdctl etcdutl [email protected]:/usr/local/bin
接下来编写用于启动 etcd 的脚本 cluster.sh
#!/bin/bash
TOKEN=tkn
CLUSTER_STATE=new
NAME_1=machine-1
NAME_2=machine-2
NAME_3=machine-3
HOST_1=192.168.123.101
HOST_2=192.168.123.102
HOST_3=192.168.123.103
CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2380,${NAME_3}=http://${HOST_3}:2380
THIS_NAME=${NAME_1}
THIS_IP=${HOST_1}
etcd \
--name ${THIS_NAME} \
--data-dir=/var/lib/etcd \
--initial-advertise-peer-urls http://${THIS_IP}:2380 \
--listen-peer-urls http://${THIS_IP}:2380 \
--advertise-client-urls http://${THIS_IP}:2379 \
--listen-client-urls http://${THIS_IP}:2379 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state ${CLUSTER_STATE} \
--initial-cluster-token ${TOKEN}
三台机器分别对应 NAME_1~NAME_3 与 HOST_1~HOST_3,将脚本中的 THIS_NAME 与 THIS_IP 分别替换后拷贝到三台机器的 /usr/loca/bin/cluster.sh。
然后编写一个 systemd 的 service 用于开机启动 etcd:
[Unit]
Description=etcd cluster
After=network.target
[Service]
ExecStart=/usr/local/bin/cluster.sh
[Install]
WantedBy=multi-user.target
将文件拷贝到三台机器的 /lib/systemd/system/etcd.service,并执行 systemdctl enable –now etcd 启动服务,最后可以使用 etcdctl 命令访问 etcd 集群:
➜ ~ export ETCDCTL_API=3
➜ ~ export ETCDCTL_ENDPOINTS=192.168.123.101:2379,192.168.123.102:2379,192.168.123.103:2379
➜ ~ etcdctl member list
5640a3d876739168, started, machine-3, http://192.168.123.103:2380, http://192.168.123.103:2379, false
cecb22d998638446, started, machine-2, http://192.168.123.102:2380, http://192.168.123.102:2379, false
cf50412f3d98e8eb, started, machine-1, http://192.168.123.101:2380, http://192.168.123.101:2379, false
如果遇到容器集群部署异常,可以手动清理 etcd 集群数据,再继续使用:
# 检查所有的 key
etcdctl get --prefix / --keys-only
# 删除所有的数据
etcdctl del / --prefix
LVS 只具备转发功能,这里使用 keepalived 实现高可用和健康检查,首先在两台机器上安装 keepalived 与 ipvsadm:
apt install -y keepalived ipvsadm
我们以 192.168.123.90 作为虚 IP,192.168.123.91 作为主负载均衡,192.168.123.92 作为备负载均衡,并使用 DR 模式转发请求,后端的 Real Server 是 192.168.123.95:6443,192.168.123.96:6443,192.168.123.97:6443。
192.168.123.91 上 /etc/keepalived/keepalived.conf 配置如下:
! Configuration File for Keepalived
global_defs {
notification_email {
[email protected]
}
notification_email_from [email protected]
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id LB_DEVEL_01
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.123.90
}
}
virtual_server 192.168.123.90 6443 {
delay_loop 6
lb_algo rr
lb_kind DR
persistence_timeout 0
protocol TCP
real_server 192.168.123.95 6443 {
weight 1
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
real_server 192.168.123.96 6443 {
weight 1
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
real_server 192.168.123.97 6443 {
weight 1
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}
192.168.123.92 上 /etc/keepalived/keepalived.conf 配置如下:
! Configuration File for Keepalived
global_defs {
notification_email {
[email protected]
}
notification_email_from [email protected]
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id LB_DEVEL_02
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 90
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.123.90
}
}
virtual_server 192.168.123.90 6443 {
delay_loop 6
lb_algo rr
lb_kind DR
persistence_timeout 0
protocol TCP
real_server 192.168.123.95 6443 {
weight 1
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
real_server 192.168.123.96 6443 {
weight 1
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
real_server 192.168.123.97 6443 {
weight 1
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}
两者只有 global_defs 的 router_id,vrrp_instance 的 state 和 priority 有差异,其余保持一致。
保存配置后执行 systemctl enable keepalived && systemctl restart keepalived
,查看 keepalived 的日志可以确认当前实例状态:
➜ ~ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether bc:24:11:e7:9c:c2 brd ff:ff:ff:ff:ff:ff
altname enp6s18
inet 192.168.123.91/24 brd 192.168.123.255 scope global eth0
valid_lft forever preferred_lft forever
inet 192.168.123.90/32 scope global eth0
valid_lft forever preferred_lft foreve
➜ ~ journalctl -u keepalived -f
Aug 16 10:24:42 lb1 Keepalived_vrrp[829]: (VI_1) Entering MASTER STATE
Aug 16 10:24:44 lb1 Keepalived_healthcheckers[828]: TCP_CHECK on service [192.168.123.96]:tcp:6443 failed.
Aug 16 10:24:44 lb1 Keepalived_healthcheckers[828]: Removing service [192.168.123.96]:tcp:6443 from VS [192.168.123.90]:tcp:6443
Aug 16 10:24:44 lb1 Keepalived_healthcheckers[828]: smtp fd 10 returned write error
Aug 16 10:24:47 lb1 Keepalived_healthcheckers[828]: TCP_CHECK on service [192.168.123.95]:tcp:6443 failed.
Aug 16 10:24:47 lb1 Keepalived_healthcheckers[828]: Removing service [192.168.123.95]:tcp:6443 from VS [192.168.123.90]:tcp:6443
Aug 16 10:24:47 lb1 Keepalived_healthcheckers[828]: smtp fd 12 returned write error
Aug 16 10:24:49 lb1 Keepalived_healthcheckers[828]: TCP_CHECK on service [192.168.123.97]:tcp:6443 failed.
Aug 16 10:24:49 lb1 Keepalived_healthcheckers[828]: Removing service [192.168.123.97]:tcp:6443 from VS [192.168.123.90]:tcp:6443
Aug 16 10:24:49 lb1 Keepalived_healthcheckers[828]: Lost quorum 1-0=1 > 0 for VS [192.168.123.90]:tcp:6443
Aug 16 10:24:49 lb1 Keepalived_healthcheckers[828]: smtp fd 10 returned write error
在先启动的 192.168.123.91 上可以看到当前实例进入了 Master 状态,但由于后端服务还未运行,三个 Real Server 都被移除了,在部署完 Master 才会恢复正常。
keepalived 使用 VRRP 协议来管理 VIP 的切换和故障转移,它将节点划分为 Master 和 Backup 两种状态,Master 节点负责处理所有流量,Backup 节点处于待命状态,一旦 Master 节点出现故障,Backup 节点会立即接管 VIP,成为新的 Master,我们在 Master 上能看到当前的虚 IP。
在 NAT、TUN、DR 三种 IP 负载均衡的技术中,DR 和 TUN 模式都需要在真实服务器上对 arp_ignore 和 arp_announce 参数进行配置,主要是实现禁止响应对 VIP 的 ARP 请求。其中 DR 模式具有最高的性能,客户端数据包到达负载均衡器时,它会直接修改目标 MAC 地址,将数据包转发到 RS,RS 处理器请求后,无需经过负载均衡器,响应数据直接返回给客户端。
这里使用了 DR 模式,因此还需要配置三个 Master 节点配置网卡与内核参数,登录三个 Master 执行以下命令:
#!/bin/bash
vip=192.168.123.90
ifconfig lo:0 $vip broadcast $vip netmask 255.255.255.255 up
route add -host $vip lo:0
echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce
也可以将这段脚本保存为 /usr/local/bin/vip-setup.sh,并创建一个 systemd 服务 /lib/system/systemd/vip-setup.service 开机执行:
[Unit]
Description=VIP Setup Service
After=network.target
[Service]
ExecStart=/usr/local/bin/vip-setup.sh
Type=oneshot
[Install]
WantedBy=multi-user.target
当我们部署完 Master 后,在 192.168.123.91 上可以看到三个 Real Server 正常工作:
➜ ~ ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.123.90:6443 rr
-> 192.168.123.95:6443 Route 1 0 0
-> 192.168.123.96:6443 Route 1 0 0
-> 192.168.123.97:6443 Route 1 0 0
K3s 的二进制文件托管在 GitHub 可以随时获取,部署脚本一直使用链接引用,这里做一个备份:get-k3s-io.sh
这里使用 K3s 的 v1.25.16+k3s4 部署容器集群,首先在第一个 Master 上执行以下命令:
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="v1.25.16+k3s4" INSTALL_K3S_EXEC="server" K3S_TOKEN="FxwdFFD1kGY01MTutCqyZ8" sh -s - --datastore-endpoint http://192.168.123.101:2379,http://192.168.123.102:2379,http://192.168.123.103:2379 --tls-san=192.168.123.90
这里参考了 K3s 官方文档:https://docs.k3s.io/datastore/ha-embedded,我们预设了一个 K3S_TOKEN 用于添加 Master,自定义了外部 etcd 存储,并添加了负载均衡用的 VIP 到证书中。
执行成功后,可以看到第一个 Master 正常运行:
➜ ~ kubectl get node
NAME STATUS ROLES AGE VERSION
k3s1 Ready control-plane,master 111s v1.25.16+k3s4
➜ ~ kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-8b9777675-88w6b 1/1 Running 0 98s
kube-system helm-install-traefik-crd-6zhwz 0/1 Completed 0 98s
kube-system helm-install-traefik-wj8bj 0/1 Completed 2 98s
kube-system local-path-provisioner-69dff9496c-h2l9p 1/1 Running 0 98s
kube-system metrics-server-854c559bd-5g49v 1/1 Running 0 98s
kube-system svclb-traefik-e67a9355-c5wbn 2/2 Running 0 47s
kube-system traefik-54c4f4ffd8-r6p58 1/1 Running 0 47s
然后使用第一个 Master 的地址添加其余两个 Master:
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="v1.25.16+k3s4" INSTALL_K3S_EXEC="server" K3S_TOKEN="FxwdFFD1kGY01MTutCqyZ8" sh -s - --server https://192.168.123.95:6443 --datastore-endpoint http://192.168.123.101:2379,http://192.168.123.102:2379,http://192.168.123.103:2379 --tls-san=192.168.123.90
执行成功后,可以看到三个 Master 都正常运行:
➜ ~ kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k3s1 Ready control-plane,master 5m25s v1.25.16+k3s4 192.168.123.95 <none> Debian GNU/Linux 12 (bookworm) 6.1.0-18-amd64 containerd://1.7.7-k3s1
k3s2 Ready control-plane,master 84s v1.25.16+k3s4 192.168.123.96 <none> Debian GNU/Linux 12 (bookworm) 6.1.0-18-amd64 containerd://1.7.7-k3s1
k3s3 Ready control-plane,master 28s v1.25.16+k3s4 192.168.123.97 <none> Debian GNU/Linux 12 (bookworm) 6.1.0-18-amd64 containerd://1.7.7-k3s1
从 Master 上拷贝 /etc/rancher/k3s/k3s.yaml 到本地 ~/.kube/config,将服务器地址修改为 https://192.168.123.90:6443,使用该凭证可以正常访问:
➜ ~ kubectl get node -v=6
I0816 11:27:14.687529 11424 loader.go:374] Config loaded from file: /root/.kube/config
I0816 11:27:14.701571 11424 round_trippers.go:553] GET https://192.168.123.90:6443/api/v1/nodes?limit=500 200 OK in 6 milliseconds
NAME STATUS ROLES AGE VERSION
k3s1 Ready control-plane,master 10m v1.25.16+k3s4
k3s2 Ready control-plane,master 6m14s v1.25.16+k3s4
k3s3 Ready control-plane,master 5m18s v1.25.16+k3s4
现在负载均衡与 Master 都正常工作了,我们可以使用 K3S_TOKEN 与 VIP 地址添加 Worker 节点,在两个 Worker 节点上执行以下命令:
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="v1.25.16+k3s4" INSTALL_K3S_EXEC="agent" K3S_TOKEN="FxwdFFD1kGY01MTutCqyZ8" sh -s - --server https://192.168.123.90:6443
执行成功后,可以看到两个 Worker 都正常运行:
➜ ~ kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k3a1 Ready <none> 27m v1.25.16+k3s4 192.168.123.98 <none> Debian GNU/Linux 12 (bookworm) 6.1.0-18-amd64 containerd://1.7.7-k3s1
k3a2 Ready <none> 25m v1.25.16+k3s4 192.168.123.99 <none> Debian GNU/Linux 12 (bookworm) 6.1.0-18-amd64 containerd://1.7.7-k3s1
k3s1 Ready control-plane,master 50m v1.25.16+k3s4 192.168.123.95 <none> Debian GNU/Linux 12 (bookworm) 6.1.0-18-amd64 containerd://1.7.7-k3s1
k3s2 Ready control-plane,master 46m v1.25.16+k3s4 192.168.123.96 <none> Debian GNU/Linux 12 (bookworm) 6.1.0-18-amd64 containerd://1.7.7-k3s1
k3s3 Ready control-plane,master 45m v1.25.16+k3s4 192.168.123.97 <none> Debian GNU/Linux 12 (bookworm) 6.1.0-18-amd64 containerd://1.7.7-k3s1
完成上述操作后,就可以开始测试高可用。
当主负载均衡器无法工作时,VIP 会漂移到备负载均衡器,同时广播 ARP 请求包告知 VIP 的新 MAC 地址(ARP 更新过程是基于二层广播的,如果需要在不同子网之间转发 ARP 请求,可能需要使用专门的 ARP 代理软件),我们在可以在主备切换前后使用 arp 命令查看指定 IP 地址的 MAC 地址。
这里选择 k3a1 节点测试,先检查 192.168.123.90 对应的 MAC 地址:
➜ ~ arp 192.168.123.90
Address HWtype HWaddress Flags Mask Iface
192.168.123.90 ether bc:24:11:e7:9c:c2 C eth0
然后停止主负载均衡器 192.168.123.91 上的 keepalived:
➜ ~ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether bc:24:11:e7:9c:c2 brd ff:ff:ff:ff:ff:ff
altname enp6s18
inet 192.168.123.91/24 brd 192.168.123.255 scope global eth0
valid_lft forever preferred_lft forever
inet 192.168.123.90/32 scope global eth0
valid_lft forever preferred_lft forever
➜ ~ systemctl stop keepalived
➜ ~ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether bc:24:11:e7:9c:c2 brd ff:ff:ff:ff:ff:ff
altname enp6s18
inet 192.168.123.91/24 brd 192.168.123.255 scope global eth0
valid_lft forever preferred_lft forever
可以看到 VIP 从 eth0 上移除了,接下来在 k3a1 上再次检查 192.168.123.90 对应的 MAC 地址:
➜ ~ arp 192.168.123.90
Address HWtype HWaddress Flags Mask Iface
192.168.123.90 ether bc:24:11:ca:25:0d C eth0
可以看到 MAC 地址发生了变化,当我们恢复主负载均衡器后,VIP 会再次漂移到原先的机器上。
在这个环境中,我们已经将 etcd 独立部署,这样 Master 上只需要运行无状态的控制面组件,当某个 Master 异常时,负载均衡器会将它从 Real Server 列表剔除,集群内部 default 命名空间下 kubernetes 的 endpoint 也会更新。
这里选择 k3s1 节点测试,首先检查负载均衡器的后端节点及 kubernetes 的 endpoint:
➜ ~ kubectl get node
NAME STATUS ROLES AGE VERSION
k3a1 Ready <none> 42m v1.25.16+k3s4
k3a2 Ready <none> 40m v1.25.16+k3s4
k3s1 Ready control-plane,master 10m v1.25.16+k3s4
k3s2 Ready control-plane,master 6m14s v1.25.16+k3s4
k3s3 Ready control-plane,master 5m18s v1.25.16+k3s4
➜ ~ ssh [email protected] "kubectl get ep"
NAME ENDPOINTS AGE
kubernetes 192.168.123.95:6443,192.168.123.96:6443,192.168.123.97:6443 61m
➜ ~ ssh [email protected] "ipvsadm -Ln"
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.123.90:6443 rr
-> 192.168.123.95:6443 Route 1 0 0
-> 192.168.123.96:6443 Route 1 0 0
-> 192.168.123.97:6443 Route 1 0 0
可以看到三个 Master 都正常运行,接下来停止 k3s1 虚拟机:
再登录 k3s2 与负载均衡器检查:
➜ ~ ssh [email protected] "kubectl get node"
NAME STATUS ROLES AGE VERSION
k3a1 Ready <none> 42m v1.25.16+k3s4
k3a2 Ready <none> 40m v1.25.16+k3s4
k3s1 NotReady control-plane,master 65m v1.25.16+k3s4
k3s2 Ready control-plane,master 61m v1.25.16+k3s4
k3s3 Ready control-plane,master 60m v1.25.16+k3s4
➜ ~ ssh [email protected] "kubectl get ep"
NAME ENDPOINTS AGE
kubernetes 192.168.123.96:6443,192.168.123.97:6443 64m
➜ ~ ssh [email protected] "ipvsadm -Ln"
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.123.90:6443 rr
-> 192.168.123.96:6443 Route 1 0 0
-> 192.168.123.97:6443 Route 1 0 0
可以看到 k3s1 已经 NotReady,而负载均衡器后端也剔除了异常节点,由于我们已经将 etcd 外置,即使关闭 k3s2 只保留一台 Master 集群也可以正常运行:
➜ ~ ssh [email protected] "kubectl get node"
NAME STATUS ROLES AGE VERSION
k3a1 Ready <none> 45m v1.25.16+k3s4
k3a2 Ready <none> 43m v1.25.16+k3s4
k3s1 NotReady control-plane,master 67m v1.25.16+k3s4
k3s2 NotReady control-plane,master 63m v1.25.16+k3s4
k3s3 Ready control-plane,master 63m v1.25.16+k3s4
➜ ~ ssh [email protected] "kubectl get ep"
NAME ENDPOINTS AGE
kubernetes 192.168.123.97:6443 68m
➜ ~ ssh [email protected] "ipvsadm -Ln"
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.123.90:6443 rr
-> 192.168.123.97:6443 Route 1 0 0
当我们把 k3s1 与 k3s2 重新开机恢复后,一切恢复正常:
➜ ~ ssh [email protected] "kubectl get node"
NAME STATUS ROLES AGE VERSION
k3a1 Ready <none> 50m v1.25.16+k3s4
k3a2 Ready <none> 49m v1.25.16+k3s4
k3s1 Ready control-plane,master 73m v1.25.16+k3s4
k3s2 Ready control-plane,master 69m v1.25.16+k3s4
k3s3 Ready control-plane,master 68m v1.25.16+k3s4
➜ ~ ssh [email protected] "kubectl get ep"
NAME ENDPOINTS AGE
kubernetes 192.168.123.95:6443,192.168.123.96:6443,192.168.123.97:6443 73m
➜ ~ ssh [email protected] "ipvsadm -Ln"
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.123.90:6443 rr
-> 192.168.123.95:6443 Route 1 0 0
-> 192.168.123.96:6443 Route 1 0 0
-> 192.168.123.97:6443 Route 1 0 0
严格意义上来说,我们还是使用七层负载均衡(Nginx)的四层能力来代理 kube-apiserver,并不去解析 HTTP 请求内容,如果需要启用七层的能力,那么需要:
- 证书同步:负载均衡需要从 kube-apiserver 同步证书配置,确保可以正确验证和卸载 HTTPS 请求
- 传递客户端身份:使用七层负载均衡器时,kube-apiserver 将无法直接获取客户端的身份认证信息,负载均衡器上配置将客户端身份信息通过自定义的 HTTP 头传递给 kube-apiserver
无论是 Nginx 还是 HAProxy,估计需要额外的功能开发才能实现上述要求,而现实是在新架构中,我们只有标准的七层负载均衡器(SLB)可用,它只提供了标准的四层端口转发和七层 HTTP/HTTPS 请求转发,还有就是 OpenShift Container Platform 默认使用 HAProxy 作为 ingress-controller(相比 Nginx 增加了基于 SNI 的 TLS 路由、自定义域名证书管理等功能),它在每个 Master 节点都部署一个副本,与 K3s 的 traefik 功能类似,只要将七层负载均衡器的 443 端口绑定到 Master 的 443 端口后,用户就可以使用 ingress 暴露服务,听起来是个一举多得的操作,这个 SLB 实际上承担了 Master 的端口暴露。如果新架构中有一个能支持绑定 EIP 的四层负载均衡产品,那就不需要七层负载均衡了。
言归正传,同样是使用四层负载均衡功能,LVS 与 HAProxy/Nginx 有什么差异呢?
特性 | LVS | HAProxy/Nginx |
---|---|---|
实现原理 | 工作在内核空间,是基于 Linux 内核的负载均衡技术,DR 模式下实际上工作在数据链路层(第2层),过修改网络数据包的目的 MAC 地址来实现负载均衡 | 基于用户空间的应用程序,工作在传输层(第4层)和应用层(第7层) |
性能和吞吐量 | 具有非常高的性能和吞吐量 | 用户态应用,性能逊于 LVS,但在处理复杂的应用层逻辑方面更加灵活 |
配置复杂度 | 配置相对简单,主要使用 ipvsadm 命令行工具,需要对 RS 做配置 | 配置相对复杂,需要编写配置文件来定义负载均衡规则,无需对 RS 做配置 |
高可用性 | 通过 keepalived 实现主备切换的高可用方案 | 通过 keepalived 实现主备切换的高可用方案 |
适用场景 | 大规模、高性能的四层负载均衡场景 | 需要应用层控制和复杂配置的场景 |
可以说七层负载均衡相对于 LVS 是切好满足了我们当时的额外需求,接下来使用 Nginx 来代替上一步中的 LVS,提供负载均衡能力。
这里移除掉原先的 virtual_server 的配置即可,只需要 VIP 高可用的功能。
192.168.123.91 上 /etc/keepalived/keepalived.conf 配置如下:
! Configuration File for Keepalived
global_defs {
notification_email {
[email protected]
}
notification_email_from [email protected]
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id LB_DEVEL_01
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.123.90
}
}
192.168.123.92 上 /etc/keepalived/keepalived.conf 配置如下:
! Configuration File for Keepalived
global_defs {
notification_email {
[email protected]
}
notification_email_from [email protected]
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id LB_DEVEL_02
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 90
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.123.90
}
}
保存配置后重启 keepalived 即可。
这里使用一个简单的配置实现 TCP 转发与健康检查:
events {}
stream {
upstream k3s_servers {
server 192.168.123.95:6443;
server 192.168.123.96:6443;
server 192.168.123.97:6443;
}
server {
listen 6443;
proxy_pass k3s_servers;
}
}
使用 docker 运行 Nginx 容器挂载配置文件:
docker run -d --restart unless-stopped --name nginx \
--network host \
-v ${PWD}/nginx.conf:/etc/nginx/nginx.conf \
nginx:stable
在本地执行 kubectl get node -w 访问 kube-apiserver 后,登录主负载均衡器,可以看到 6443 端口的连接情况:
➜ ~ ss -atupn|grep 6443
tcp LISTEN 0 511 0.0.0.0:6443 0.0.0.0:* users:(("nginx",pid=1988,fd=5),("nginx",pid=1959,fd=5))
tcp ESTAB 0 0 192.168.123.90:6443 192.168.123.3:49754 users:(("nginx",pid=1988,fd=3))
tcp ESTAB 0 0 192.168.123.91:50284 192.168.123.96:6443 users:(("nginx",pid=1988,fd=10))
关闭主负载均衡器后,再在本地执行 kubectl get node -w,登录备负载均衡器检查 6443 端口连接情况,可以看到请求正常转发:
➜ ~ ss -atupn|grep 6443
tcp LISTEN 0 511 0.0.0.0:6443 0.0.0.0:* users:(("nginx",pid=2143,fd=5),("nginx",pid=1131,fd=5))
tcp ESTAB 0 0 192.168.123.90:6443 192.168.123.3:51324 users:(("nginx",pid=2143,fd=3))
tcp ESTAB 0 0 192.168.123.92:40721 192.168.123.96:6443 users:(("nginx",pid=2143,fd=10))