抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Kubernetes 是一个开源的容器编排引擎,可用来对容器化应用进行自动化部署、扩缩和管理。本文将会描述如何搭建一个可用于生产环境的高可用集群。

若第一次接触 k8s,建议看完 k8s - 快速入门 再查看此文。

文档:k3s 高可用嵌入式 etcd

一、节点准备(每台都要执行)

准备 3 台同网段的服务器,另预留一个空闲 IP 做 VIP

  • VIP: 192.168.5.50(网段里没有被使用的ip即可)
  • Server node1: 192.168.5.101
  • Server node2: 192.168.5.102
  • Server node3: 192.168.5.103

1. 关闭 swap(可选,生产建议)

k3s 会把 kubelet 的 failSwapOn 设为 false,不关 swap 也能正常启动;但生产环境建议关闭:避免系统进程换出拖慢 kubelet 心跳和 etcd 选举,也让内存压力信号更准确。
若保留 swap,Pod 默认也不会使用(kubelet 默认 swapBehavior: NoSwap),只有系统进程会用。

1
2
3
sudo cp /etc/fstab /etc/fstab.bak
sudo swapoff -a
sudo sed -i '/swap/s/^/#/' /etc/fstab

回滚:sudo cp /etc/fstab.bak /etc/fstab 后执行 sudo swapon -a 即可恢复。

2. 确认网卡和 IP

1
2
3
4
$ ip -br a
lo UNKNOWN 127.0.0.1/8
ens2 UP 192.168.5.101/24
br-21e49344073e DOWN 172.16.99.1/24

获得网卡 ens2 和 IP 192.168.5.101

3. 安装 keepalived(VIP 高可用)

1
sudo apt install -y keepalived

配置文件如下 /etc/keepalived/keepalived.conf(第 1 台):

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
# 健康检查:返回 0 表示健康,非 0 则表示本机服务挂了,按 weight 降低本机优先级
# keepalived 就会选举出其他可用的作为服务提供节点
vrrp_script chk_service {
script "/usr/bin/curl -sfk https://127.0.0.1:6443/healthz" # 本文检查 apiserver
interval 2
weight -50
fall 3
rise 2
}

vrrp_instance K3S_API {
state BACKUP
interface ens2 # ⚠️ 网卡
priority 100 # ⚠️ 优先级,需要每台不一样
advert_int 1
virtual_router_id 50

unicast_src_ip 192.168.5.101 # ⚠️ 改成自己 ip
unicast_peer { # ⚠️ 里面为其他节点ip
192.168.5.102
192.168.5.103
}

authentication {
auth_type PASS
auth_pass <三台一致的密码> # ⚠️ 三台必须相同
}

virtual_ipaddress {
192.168.5.50/24 # ⚠️ VIP
}

track_script {
chk_service
}
}

顺便一提:当你想把 VIP 用于监测其他服务时,修改 script 即可

1
2
script "/usr/bin/curl -sf http://127.0.0.1:80/healthz"   # 例如实现 Nginx 高可用
script "/usr/bin/nc -z 127.0.0.1 3306" # 例如实现 MySQL 高可用

4. 其他节点

第 2/3 台需要修改 ⚠️ 部分包括 priority(分别改为99和98)、unicast_src_ip(节点自己的 IP)、unicast_peer(不含自己的另外两台);

其他配置则保持一致,特别是 virtual_router_id、auth_pass、virtual_ipaddress 。

5. 确认

启动并确认:

1
2
sudo systemctl enable --now keepalived
sleep 3 && ip -br a

三台都写 state BACKUP,靠 priority 选主;不想让恢复的节点抢回 VIP(减少连接抖动)就在 vrrp_instance 里加 nopreempt。
防火墙要放行 VRRP(IP 协议号 112),只开端口没用。
健康检查必须查本机服务,而不是去 ping 其他节点或检查 VIP:查别人只能证明别人活着,跟本机能不能对外服务无关。

二、安装 Server

1. 编写 config.yaml

只需要改下面三个变量,其余不用动:

1
2
3
4
# ⚠️ 分别修改为本机 IP、网卡和 keepalived 的 VIP
export NODE_IP=192.168.5.101 \
&& export IFACE=ens2 \
&& export VIP=192.168.5.50

并直接执行命令,命令会将配置写入到 /etc/rancher/k3s/config.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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
sudo mkdir -p /etc/rancher/k3s && sudo tee /etc/rancher/k3s/config.yaml > /dev/null <<EOF
# k3s 配置:/etc/rancher/k3s/config.yaml,启动时自动读取
# 除 cluster-init / server / token 外,其余参数所有 server 必须一致

# 用嵌入式 etcd 初始化集群,仅第 1 台用;第 2/3 台改成 server + token
# 不写则默认 SQLite(单节点无 HA);SQLite 可以迁到 etcd,但 etcd 不能迁到外部 MySQL/PG
cluster-init: true

# 节点通信 IP,写进 Node 的 InternalIP
# 不填会自动选默认路由所在网卡,有 tailscale0/docker0 时经常选错
node-ip: $NODE_IP

# 额外写进 apiserver 证书 SAN 的地址,VIP 必须包含,否则 kubectl 报 x509
tls-san:
- $NODE_IP
- $VIP

# ---------- 网络规划(装完不可改)----------

# flannel 发包网卡,必须和 node-ip 一致
# 选错可能落到 tailscale0,MTU 只剩 1230,延迟翻倍
flannel-iface: $IFACE

# Pod 网段,/12 可切 4096 个 /24
# 注意不能与物理网段重叠,否则节点访问内网服务会被 flannel 劫持
cluster-cidr: 10.0.0.0/12

# ClusterIP 池,不能与 cluster-cidr 或真实网段重叠
service-cidr: 10.16.0.0/16

# CoreDNS 的 ClusterIP,必须在 service-cidr 内且避开 10.16.0.1(kubernetes Service 占用)
cluster-dns: 10.16.0.10

# 每节点 Pod 子网,/23 = 510 个 IP、最多 2048 节点;不写默认 /24(254 个 / 4096 节点)
kube-controller-manager-arg: [node-cidr-mask-size=23]

# ---------- 组件参数 ----------

# 1.40 起默认改 nftables,IPVS 已弃用;nftables 下 NodePort 只能用节点默认 IP 访问
kube-proxy-arg:
- proxy-mode=nftables

# Pod 上限 400(默认 110),/23 的 510 个 IP 留出余量;实际按节点内存定
# Pod 多要放宽 kubelet 与 apiserver 通信配额(默认 5 QPS / 10 burst 不够)
# 镜像 GC:用到 80% 开始回收,删到 70% 停止;etcd 与镜像同盘,留 20% 给它
kubelet-arg:
- max-pods=400
- kube-api-qps=50
- kube-api-burst=100
- image-gc-high-threshold=80
- image-gc-low-threshold=70

# ---------- 安全 ----------

# Secret 加密存储,默认 false 即明文;开启后已有 Secret 要 reencrypt 重写
# 务必单独备份 cred/encryption-config.json,丢了 Secret 无法解密
secrets-encryption: false

# 让非 root 用户能直接 kubectl(默认 600)
# 注意 644 等于把管理员凭据开放给本机所有用户,多用户机器建议 640 + write-kubeconfig-group
write-kubeconfig-mode: "644"

# ---------- etcd 备份与容量 ----------

# 每 6 小时快照一次、保留 20 份,默认存在 server/db/snapshots;只存本机,生产要自己同步出去
etcd-snapshot-schedule-cron: "0 */6 * * *"
etcd-snapshot-retention: 20

# quota-backend-bytes:数据库上限 8GB(默认 2GB),写满后 etcd 只读
# auto-compaction-retention:保留 1 小时历史版本(默认 0 不压缩);压缩不缩文件,碎片靠 defrag
# bootstrap-defrag:etcd 3.6+ 启动时自动 defrag,默认 0 不启用
etcd-arg:
- quota-backend-bytes=8589934592
- auto-compaction-retention=1h
- bootstrap-defrag-threshold-megabytes=512

# ---------- 组件裁剪 ----------

# 不装自带 Traefik,自行选 ingress(避免和 ingress-nginx 抢 80/443)
disable:
- traefik
EOF

写完查看配置内容,确保如 node-ip 等参数为真实参数而非变量占位符

1
sudo cat /etc/rancher/k3s/config.yaml

2. 安装命令

执行命令安装 K3s Server

1
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn INSTALL_K3S_VERSION=v1.37.1+k3s1 sh -

装完确认 apiserver 和 VIP 都能访问:

1
2
sudo kubectl get nodes
curl -k https://192.168.5.50:6443/healthz

三、安装其他 Server

etcd 需要奇数台 server 才能保证多数派:3 台挂 1 台仍可读写,挂 2 台就不能写了。第 1 台已经初始化好集群,其余节点只要拿到 token 并指向 VIP。

1. 获取 token

在第一台 Server 执行命令查看 token

1
2
3
sudo cat /var/lib/rancher/k3s/server/token

# 例如输出 K10xxxx::server:xxxx

2. 编写 config.yaml

同样修改变量为节点自己的

1
2
3
4
export NODE_IP=192.168.5.102 \
&& export IFACE=ens2 \
&& export VIP=192.168.5.50 \
&& export TOKEN='粘贴第 1 台 cat 出来的 token'

然后执行命令写入配置

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
sudo mkdir -p /etc/rancher/k3s && sudo tee /etc/rancher/k3s/config.yaml > /dev/null <<EOF
server: https://$VIP:6443
token: $TOKEN
node-ip: $NODE_IP
tls-san:
- $NODE_IP
- $VIP
flannel-iface: $IFACE
cluster-cidr: 10.0.0.0/12
service-cidr: 10.16.0.0/16
cluster-dns: 10.16.0.10
kube-controller-manager-arg: [node-cidr-mask-size=23]
kube-proxy-arg:
- proxy-mode=nftables
kubelet-arg:
- max-pods=400
- kube-api-qps=50
- kube-api-burst=100
- image-gc-high-threshold=80
- image-gc-low-threshold=70
secrets-encryption: false
write-kubeconfig-mode: "644"
etcd-snapshot-schedule-cron: "0 */6 * * *"
etcd-snapshot-retention: 20
etcd-arg:
- quota-backend-bytes=8589934592
- auto-compaction-retention=1h
- bootstrap-defrag-threshold-megabytes=512
disable:
- traefik
EOF

cluster-cidr、service-cidr、cluster-dns、disable、secrets-encryption 必须在所有 server 上一致,否则 join 会失败。

3. 安装

执行命令将 server-node2 加入集群

1
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn INSTALL_K3S_VERSION=v1.37.1+k3s1 sh -

建议等第 2 台 Ready 之后再装第 3 台。

4. 验证

最终确认三台都是 Ready 且 ROLES 带 etcd 说明成功,此时挂掉任意一台,集群仍可以工作。

1
2
3
4
5
$ sudo kubectl get nodes
NAME STATUS ROLES AGE VERSION
node1 Ready control-plane,etcd,master 10m v1.37.1+k3s1
node2 Ready control-plane,etcd,master 3m v1.37.1+k3s1
node3 Ready control-plane,etcd,master 1m v1.37.1+k3s1

5. 注意事项

装错要先清理:节点数据目录里一旦有 etcd 数据,server: 参数会被直接忽略,改成单机模式就再也 join 不进去。要重来必须先执行 sudo /usr/local/bin/k3s-uninstall.sh。

固定注册地址:本文用 keepalived 的 VIP(192.168.5.50)作为 server: 地址,第 1 台挂了新节点照样能加入;如果写第 1 台的真实 IP,那台一挂就加不进新节点了。

业务节点:k3s 的 server 默认可调度,不加 agent 也能跑业务;要单独跑业务节点,用 sh -s - agent 安装,配置文件里只保留 server、token、node-ip、flannel-iface、kubelet-arg 这类 agent 侧参数。

四、安装 Agent

server 默认就能跑业务,加 Agent 的目的是把业务和 etcd 隔开:Agent 不存 etcd 数据、不参与选举,加多少台都不影响 etcd 的奇数多数派,挂掉也不影响集群读写。示例新增一台业务节点 192.168.5.111。

1. 确认能连到集群

Agent 只需要能访问 apiserver,不需要装 keepalived:

1
curl -k https://192.168.5.50:6443/healthz

返回 ok 即可,报 x509 说明 VIP 没写进 server 的 tls-san。

2. 生成临时 token(在任意 server 上)

前文加 server 用的是 /var/lib/rancher/k3s/server/token 里的静态 token,但它是集群主密钥:能用来加入 server、创建后改不了,泄露等于失守。只加业务节点用临时 token 即可。

1
2
sudo k3s token create --ttl 2h
# 例如 abcd12.3f4e5d6c7b8a9012

3. 编写 config.yaml

别把 server 那份配置复制过来:cluster-init、cluster-cidr、service-cidr、cluster-dns、disable、etcd-*、secrets-encryption 都是 server 侧参数,由 server 下发。Agent 写了不生效,日志里只会出现 WARN Unknown flag --xxx found in config.yaml, skipping,看着「配置了」其实没配。

同样只改变量:

1
2
3
4
export NODE_IP=192.168.5.111 \
&& export IFACE=ens2 \
&& export VIP=192.168.5.50 \
&& export TOKEN='粘贴上一步 k3s token create 输出的 token'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sudo mkdir -p /etc/rancher/k3s && sudo tee /etc/rancher/k3s/config.yaml > /dev/null <<EOF
# agent 配置:/etc/rancher/k3s/config.yaml,启动时自动读取
# 只有 server + token 是必须的,其余都是可选的 agent 侧参数

# 连 VIP 而不是某一台 server 的真实 IP,否则那台挂了新节点就加不进来
server: https://$VIP:6443
token: $TOKEN

# 节点通信 IP,写进 Node 的 InternalIP
node-ip: $NODE_IP

# flannel 发包网卡,必须和 node-ip 一致
flannel-iface: $IFACE

# kubelet 参数按节点生效,必须和 server 侧保持一致,否则同集群行为不统一
# Pod 上限 400、镜像 GC 80%/70%、放宽 kubelet 与 apiserver 通信配额
kubelet-arg:
- max-pods=400
- kube-api-qps=50
- kube-api-burst=100
- image-gc-high-threshold=80
- image-gc-low-threshold=70
EOF

需要打标签或污点时,也在这里加 node-label: [disk=ssd]、node-taint: [gpu=true:NoSchedule],安装时一并生效。

4. 安装

1
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn INSTALL_K3S_VERSION=v1.37.1+k3s1 sh -s - agent

注意结尾是 sh -s - agent,不是 server;写错会把业务节点装成第 4 台 server,etcd 变成偶数台(4 台挂 2 台就不可用)。

5. 验证

在任意 server 上查看:

1
2
3
4
5
6
$ sudo kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP
node1 Ready control-plane,etcd,master 1h v1.37.1+k3s1 192.168.5.101
node2 Ready control-plane,etcd,master 55m v1.37.1+k3s1 192.168.5.102
node3 Ready control-plane,etcd,master 30m v1.37.1+k3s1 192.168.5.103
node4 Ready <none> 1m v1.37.1+k3s1 192.168.5.111

ROLES 显示 <none> 是正常的,Agent 本来就没有 control-plane/etcd 角色;INTERNAL-IP 应该是 node-ip 填的地址,不对说明网卡或 IP 选错了。

在 Agent 本机确认服务状态:

1
sudo systemctl status k3s-agent --no-pager

五、让 server 不再跑业务(可选)

加 Agent 不会自动让 server 停止调度,server 上仍然会继续创建 Pod。要让业务只落在 Agent 上,需要给 server 打污点,例如在每台 server 的 config.yaml 里加:

1
2
node-taint:
- node-role.kubernetes.io/control-plane:NoSchedule

然后 sudo systemctl restart k3s 重启生效,再把 server 上残留的 Pod 清掉:

1
2
sudo kubectl drain node1 --ignore-daemonsets --delete-emptydir-data
sudo kubectl uncordon node1

NoSchedule 只拦新调度,不会动已经在跑的 Pod,所以要 drain 一次;drain 会顺手把节点标成不可调度,得再 uncordon 回来(污点已经在拦新 Pod,不需要留着 cordon)。操作前确认业务有多副本,单副本服务会中断。

六、注意事项

  • Agent 也能持 VIP:本文 VIP 只服务 apiserver,健康检查查的是 6443。如果希望 ingress 也通过 VIP 对外,需要在跑 ingress 的节点上装 keepalived,并把 vrrp_script 的检查命令改成对应端口。
  • VIP 变了要改 kubeconfig:/etc/rancher/k3s/k3s.yaml 里写的是 https://192.168.5.50:6443,VIP 换地址或不再使用 keepalived 时,所有 kubectl 客户端(包括拷到本地的 ~/.kube/config)都要同步改,否则直接连不上。

七、卸载 Agent

在 server 上摘掉节点,再在业务节点上执行卸载脚本:

1
2
3
# server 上
sudo kubectl drain node4 --ignore-daemonsets --delete-emptydir-data
sudo kubectl delete node node4
1
2
# node4 上
sudo /usr/local/bin/k3s-agent-uninstall.sh

顺序不能反。先卸载再删 Node,集群里会留一个永远 NotReady 的节点,补一句 kubectl delete node node4 即可清掉。

八、卸载 Server

1
2
# 存活的 server 上
sudo kubectl delete node node3
1
2
# node3 上
sudo /usr/local/bin/k3s-uninstall.sh

卸载不可逆:脚本会停掉 k3s 和所有 Pod,删掉本机 etcd 数据和节点配置。动手前先 sudo k3s etcd-snapshot save 备份,快照在 /var/lib/rancher/k3s/server/db/snapshots/。

3 台卸掉 1 台,剩 2 台仍可读写;但不能一台台卸成 1 台,剩下那台凑不齐 etcd 多数派,缩容要用 k3s server --cluster-reset。

卸载后按需清理残留:sudo ip link delete cni0、sudo ip link delete flannel.1,以及不再需要的 keepalived(sudo systemctl disable --now keepalived)。

评论