Atlantis
GitHub Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

1. kube-apiserver高可用

1. 前言

K8s 集群高可用本质上是 etcd 的高可用,因为控制面组件都是无状态的,但这里不讨论 etcd,只讨论 kube-apiserver。

前司老架构容器集群产品使用四层负载均衡实现高可用,简单来讲就是 keepalived + LVS,etcd 集群独立部署,Master 节点以 Pod 形式运行在底座上,每个 Pod 里包含多个控制面容器(kube-controller-manager、kube-scheduler 等),通过 localhost 接入到本地 kube-apiserver,工作节点和用户需要经过四层负载均衡访问 kube-apiserver,这个四层负载均衡也不支持绑定 EIP,在云下访问需要转发。

alt text

进入新架构后,我们从标准 K8s 切换到了基于 OpenShift Container Platform 改造的集群,Master 节点以虚拟机形式部署,etcd 与 HAProxy 也运行在上面,由于混合云团队不再提供内部四层负载均衡,只能切换到七层负载均衡云产品,它实际上就是 keepalived + Nginx,为了成本和易用性,同时负责 kube-apiserver 与 ingress-controller 的代理,不过只用到了四层端口转发能力,好处是可以绑定 EIP,用户可以按需开启公网访问。

alt text

由于四层负载均衡还是七层负载均衡都不是容器团队的产品,因此一直只有比较浅薄的了解,这里就在 PVE 上搭建一个测试环境,研究下负载均衡是如何工作的。

2. 四层负载均衡

首先部署一个三节点 etcd 集群和两个 keepalived + LVS 负载均衡器,然后创建三个 Master 节点,最后接入两个 Worker 节点,全部使用虚拟机部署,操作系统为 Debian 12,涉及的 IP 如下:

  1. etcd: 192.168.123.101,192.168.123.102,192.168.123.103
  2. 负载均衡:192.168.123.91,192.168.123.92,虚 IP 为 192.168.123.90
  3. Master 节点:192.168.123.95,192.168.123.96,192.168.123.97
  4. Worker 节点:192.168.123.98,192.168.123.99

2.1 部署etcd

这里使用 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_3HOST_1~HOST_3,将脚本中的 THIS_NAMETHIS_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

2.2 部署负载均衡

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

2.3 部署Master

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

2.4 部署Worker

现在负载均衡与 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

2.5 测试

完成上述操作后,就可以开始测试高可用。

2.5.1 VIP高可用

当主负载均衡器无法工作时,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 会再次漂移到原先的机器上。

2.5.2 kube-apiserver高可用

在这个环境中,我们已经将 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 虚拟机:

alt text

再登录 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 重新开机恢复后,一切恢复正常:

alt text

➜  ~ 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

3. 七层负载均衡

严格意义上来说,我们还是使用七层负载均衡(Nginx)的四层能力来代理 kube-apiserver,并不去解析 HTTP 请求内容,如果需要启用七层的能力,那么需要:

  1. 证书同步:负载均衡需要从 kube-apiserver 同步证书配置,确保可以正确验证和卸载 HTTPS 请求
  2. 传递客户端身份:使用七层负载均衡器时,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,提供负载均衡能力。

3.1 配置keepalived

这里移除掉原先的 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 即可。

3.2 配置Nginx

这里使用一个简单的配置实现 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

3.3 测试

在本地执行 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))