生产环境 K8s 高可用架构实战:kubeadm HA + Haproxy+Keepalived + etcd 运维备份恢复与集群升级
生产环境 K8s 高可用架构实战:kubeadm HA + Haproxy+Keepalived + etcd 运维备份恢复与集群升级
本手册由 2026/8/17 的 8 份日志(
master30×2、worker31、worker32、etcd、etcd1/2/3)整理而成。
全部内容严格按时间戳排序:早上各节点init 0关机 → 10:24 单机 etcd 体验 → 10:45 三节点 etcd 集群搭建 → 下午 14:01 起断断续续的备份/恢复演练 → 17:1x snapshot 快照恢复;最后整合生产 HA 部署全流程(base/k8s 模版 → etcd 正式集群 → Keepalived/Haproxy → kubeadm 初始化 → 集群升级)。
命令提示符保留真实时间戳(如root@etcd1 ~ 10:54:54#)
📑 目录
| # | 章节 | 核心内容 |
|---|---|---|
| 一 | 实验总览(按时间戳) | 8/17 完整时间线、节点环境、主题速览 |
| 二 | 基础环境准备:base 模版 | 系统分区、仓库、IP/hosts、IPVS、sethost、快照 |
| 三 | 单机 etcd 快速体验 | /etc/default/etcd、member list、put/get |
| 四 | etcd 三节点集群搭建 | 静态配置、member add/remove、多节点验证 |
| 五 | etcdctl 数据操作与命令补全 | put/get/del、prefix、completion bash |
| 六 | etcd 数据备份与恢复 | 目录备份、snapshot 快照、跨节点恢复、K8s etcd |
| 七 | 生产高可用概述与主机规划 | HA 架构、etcd11-13、master21-23、ha01/02 |
| 八 | 部署 etcd 正式集群 | 三节点 etcd11/12/13 静态配置与验证 |
| 九 | 部署 Keepalived + Haproxy | VIP 10.1.8.100、6443 轮询、监控面板 |
| 十 | kubeadm 初始化 HA 集群 | 外部 etcd、calico、master/worker 加入、验证 |
| 十一 | 集群升级 v1.30.2 → v1.30.4 | upgrade apply/node、drain/uncordon、故障恢复 |
| 十二 | 常见错误与排查(重点) | 全部真实报错:原因 + 修复 |
| 十三 | 命令速查表与小结 | etcd/HA 部署/升级常用命令 |
一、实验总览(按时间戳)
1.1 8/17 完整时间线
说明:当天实验分上午/下午两段,中间节点全部关机;下午 14:01 起多个节点并行操作、断断续续,以下时间线按各日志时间戳合并排布。
1.2 节点环境
| 节点 | 主机名 | IP | 角色(当天) |
|---|---|---|---|
| etcd | etcd.ningcode.cn | 10.1.8.10 | 单机 etcd 体验(上午) |
| etcd1 | etcd1.ningcode.cn | 10.1.8.11 | etcd 集群节点(演练) |
| etcd2 | etcd2.ningcode.cn | 10.1.8.12 | etcd 集群节点(演练) |
| etcd3 | etcd3.ningcode.cn | 10.1.8.13 | etcd 集群节点(演练) |
| master30 | master30.ningcode.cn | 10.1.8.30 | 已有 K8s 集群(etcd 数据浏览/快照恢复) |
| worker31/32 | worker31/32.ningcode.cn | 10.1.8.31/32 | 已有 K8s 集群工作节点 |
| ha01/ha02 | ha01/02.ningcode.cn | 10.1.8.101/102 | 生产 HA:Keepalived + Haproxy(计划) |
| etcd11-13 | etcd11/12/13.ningcode.cn | 10.1.8.111-113 | 生产 HA:etcd 正式集群(计划) |
| master21-23 | master21/22/23.ningcode.cn | 10.1.8.121-123 | 生产 HA:控制平面(计划) |
| worker31-33 | worker31/32/33.ningcode.cn | 10.1.8.131-133 | 生产 HA:工作节点(计划) |
1.3 主题速览
| 主题 | 核心知识点 | 时间 | 来源 |
|---|---|---|---|
| 单机 etcd | etcdctl 基本操作、配置存储到 etcd | 10:24-10:36 | etcd.log |
| etcd 集群 | /etc/default/etcd 静态配置、member add/remove | 10:45-11:24 | etcd1/2/3.log |
| 数据备份恢复 | 目录级 cp -a 备份、snapshot save/restore、chown | 14:01-17:19 | etcd1/2/3.log |
| K8s etcd 运维 | etcdls/etcdget 浏览 /registry、快照恢复 | 14:02/17:19-17:21 | master30.log |
| 生产 HA 部署 | base/k8s 模版、etcd、Keepalived、Haproxy、kubeadm | 课程笔记 | 生产环境K8s部署.md |
| 集群升级 | kubeadm upgrade apply/node、drain/uncordon | 课程笔记 | 同上 |
二、基础环境准备:base 模版
生产 HA 集群共 11 台虚拟机(2×HA + 3×etcd + 3×master + 3×worker),全部由同一台 base 模版克隆而来。本章按课程笔记整理 base 模版的制作过程:装系统 → 基础包 → 仓库源 → IP/hosts → SSH → IPVS → sethost 脚本 → 打快照
base。
2.1 安装系统
Ubuntu 24.04 最小化安装,不需要 swap 分区,按以下要求分区:
/boot2G/90G
2.2 安装基础软件包
[root@ubuntu2404 ~]# apt update && apt install -y vim bash-completion open-vm-tools apt-transport-https lrzsz unzip
bash-completion是后面 kubectl/etcdctl 命令补全的前置依赖(当天 etcd1 上就因缺它踩了坑,见第 12 章)。
2.3 配置仓库源
操作系统仓库换成华为云,速度更快:
[root@ubuntu2404 ~]# cat /etc/apt/sources.list.d/ubuntu.sources
Types: deb
URIs: http://mirrors.huaweicloud.com/ubuntu/
Suites: noble noble-updates noble-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
containerd 仓库:
[root@ubuntu2404 ~]# curl -fsSL https://mirrors.huaweicloud.com/docker-ce/linux/ubuntu/gpg | gpg --dearmour -o /etc/apt/trusted.gpg.d/containerd.gpg
[root@ubuntu2404 ~]# cat << 'EOF' > /etc/apt/sources.list.d/docker-ce.list
deb [arch=amd64] https://mirrors.huaweicloud.com/docker-ce/linux/ubuntu noble stable
EOF
kubernetes 仓库(1.28+ 新路径,示例为 v1.30,换版本改路径字符串即可,该源支持 v1.24–v1.30):
[root@ubuntu2404 ~]# curl -fsSL https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.30/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
[root@ubuntu2404 ~]# echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.30/deb/ /" > /etc/apt/sources.list.d/kubernetes.list
[root@ubuntu2404 ~]# apt update
2.4 设置 IP(netplan)
[root@ubuntu2404 ~]# mkdir /etc/netplan/origin
[root@ubuntu2404 ~]# mv /etc/netplan/*yaml /etc/netplan/origin
[root@ubuntu2404 ~]# cat > /etc/netplan/00-installer-config.yaml <<EOF
network:
ethernets:
ens32:
dhcp4: no
addresses:
- 10.1.8.10/24
routes:
- to: default
via: 10.1.8.2
nameservers:
addresses:
- 10.1.8.2
- 223.5.5.5
version: 2
EOF
[root@ubuntu2404 ~]# chmod 600 /etc/netplan/00-installer-config.yaml
[root@ubuntu2404 ~]# netplan apply
2.5 设置 /etc/hosts(全量主机名)
[root@ubuntu2404 ~]# cat << 'EOF' >> /etc/hosts
####### kubernetes #####
10.1.8.100 k8s.ningcode.cn k8s
10.1.8.101 ha01.ningcode.cn ha01
10.1.8.102 ha02.ningcode.cn ha02
10.1.8.111 etcd11.ningcode.cn etcd11
10.1.8.112 etcd12.ningcode.cn etcd12
10.1.8.113 etcd13.ningcode.cn etcd13
10.1.8.121 master21.ningcode.cn master21
10.1.8.122 master22.ningcode.cn master22
10.1.8.123 master23.ningcode.cn master23
10.1.8.131 worker31.ningcode.cn worker31
10.1.8.132 worker32.ningcode.cn worker32
10.1.8.133 worker33.ningcode.cn worker33
EOF
注意
10.1.8.100 k8s.ningcode.cn是 VIP(虚拟 IP),由 Keepalived 在第 9 章漂移产生。
2.6 对时、SSH、IPVS 与内核参数
对时(chrony):
[root@ubuntu2404 ~]# apt-get install -y chrony
[root@ubuntu2404 ~]# systemctl enable chrony --now
SSH 优化(免反解、免首次交互、免密自己):
[root@ubuntu2404 ~]# echo 'UseDNS no' >> /etc/ssh/sshd_config
[root@ubuntu2404 ~]# echo 'StrictHostKeyChecking no' >> /etc/ssh/ssh_config
[root@ubuntu2404 ~]# ssh-keygen -N '' -f ~/.ssh/id_rsa -t rsa
[root@ubuntu2404 ~]# ssh-copy-id root@localhost
配置 IPVS(kube-proxy ipvs 模式必需):
# 1. 安装 ipvs 依赖包
[root@ubuntu2404 ~]# apt install -y iptables ipvsadm ipset conntrack
# 2. 临时加载内核模块(立即生效)
[root@ubuntu2404 ~]# modprobe overlay && modprobe br_netfilter
[root@ubuntu2404 ~]# modprobe ip_vs && modprobe ip_vs_rr && modprobe ip_vs_wrr
[root@ubuntu2404 ~]# modprobe ip_vs_lc && modprobe ip_vs_sh && modprobe nf_conntrack
# 3. 永久加载(重启生效)
[root@ubuntu2404 ~]# cat > /etc/modules-load.d/k8s-net.conf << EOF
# K8s 基础网络
br_netfilter
overlay
# IPVS 必需
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_lc
ip_vs_sh
nf_conntrack
EOF
模块说明:
br_netfilter允许桥接流量过 iptables;overlay是容器分层镜像所需;ip_vs_*对应轮询/加权轮询/最少连接/源地址哈希四种调度算法;nf_conntrack做连接跟踪。
其他内核参数:
[root@ubuntu2404 ~]# cat > /etc/sysctl.d/k8s.conf << 'EOF'
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.ip_forward=1
vm.swappiness=0
EOF
[root@ubuntu2404 ~]# sysctl -p /etc/sysctl.d/k8s.conf
2.7 sethost 脚本(一键设置主机名+IP)
克隆出来的每台机器只需执行 sethost <编号> 即可改主机名和 IP:
[root@ubuntu2404 ~]# cat > /usr/local/bin/sethost <<'EOF'
#!/bin/bash
# 以 root 身份运行
[ $UID -ne 0 ] && echo 'Please run as root.' && exit 1
net_config=/etc/netplan/00-installer-config.yaml
domain=ningcode.cn
usage (){
echo "Usage: $0 101-102 | 111-113 | 121-123 | 131-133"
exit 1
}
function set_ip () {
IP=10.1.8.$1
sed -ri "s#10.1.8.[0-9]{,3}/#$IP/#g" ${net_config}
chmod 600 ${net_config}
netplan apply
}
function set_hostname () {
HOSTNAME_END=$[ $1 - 100 ].$domain
case $1 in
10[1-2]) HOSTNAME=ha0${HOSTNAME_END} ;;
11[1-3]) HOSTNAME=etcd${HOSTNAME_END} ;;
12[1-3]) HOSTNAME=master${HOSTNAME_END} ;;
13[1-3]) HOSTNAME=worker${HOSTNAME_END} ;;
*) usage ;;
esac
hostnamectl hostname $HOSTNAME
}
function main() {
set_hostname $1
set_ip $1
bash -c 'clear;hostname;echo;ip -br a;echo'
while true
do
echo -ne "Press the Enter key, and the system will shut down in 5 seconds.";read
for i in {5..1}; do echo "The system will shut down in $i seconds."; sleep 1; done
echo "Shutdown system Now." && init 0
done
}
main $*
EOF
[root@ubuntu2404 ~]# chmod +x /usr/local/bin/sethost
脚本逻辑:
sethost 101→ 主机名ha01.ningcode.cn、IP10.1.8.101,设置完自动关机,便于在虚拟化平台打快照。
2.8 打快照 base
[root@ubuntu2404 ~]# init 0
关闭虚拟机,在虚拟化平台打快照,名称 base。后续所有 HA/etcd 节点都从该快照克隆。
2.9 准备 k8s 模版(基于 base 继续配置)
关闭 swap(K8s 不需要 swap):
[root@ubuntu2404 ~]# swapoff -a && sed -i '/^.*swap/d' /etc/fstab
[root@ubuntu2404 ~]# rm -f /swap.img
配置 containerd(版本 1.7.20 + SystemdCgroup + 镜像加速):
[root@ubuntu2404 ~]# apt-get install -y containerd.io=1.7.20-1 cri-tools
[root@ubuntu2404 ~]# crictl config runtime-endpoint unix:///var/run/containerd/containerd.sock
[root@ubuntu2404 ~]# containerd config default > /etc/containerd/config.toml
[root@ubuntu2404 ~]# sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
[root@ubuntu2404 ~]# sed -i 's|sandbox_image = ".*"|sandbox_image = "registry.k8s.io/pause:3.9"|' /etc/containerd/config.toml
# 配置镜像加速(crictl/CRI 走这里)
[root@ubuntu2404 ~]# vim /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://docker.m.daocloud.io","https://docker.1ms.run","https://docker.xuanyuan.me"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.k8s.io"]
endpoint = ["https://k8s.m.daocloud.io","https://registry.cn-hangzhou.aliyuncs.com/google_containers"]
[root@ubuntu2404 ~]# systemctl restart containerd.service
[root@ubuntu2404 ~]# crictl pull busybox
Image is up to date for sha256:925ff61909aebae4bcc9bc04bb96a8bd15cd2271f13159fe95ce4338824531dd
安装 nerdctl + CNI 插件:
[root@ubuntu2404 ~]# wget http://192.168.46.100/01.softwares/03.stage-3/nerdctl-1.7.7-linux-amd64.tar.gz
[root@ubuntu2404 ~]# tar -xf nerdctl-1.7.7-linux-amd64.tar.gz -C /usr/bin/
[root@ubuntu2404 ~]# wget http://192.168.46.100/01.softwares/03.stage-3/cni-plugins-linux-amd64-v1.6.0.tgz
[root@ubuntu2404 ~]# mkdir -p /opt/cni/bin
[root@ubuntu2404 ~]# tar -xf cni-plugins-linux-amd64-v1.6.0.tgz -C /opt/cni/bin
⚠️ 关键区别:
crictl走 containerd CRI 接口,读config.toml的registry.mirrors;nerdctl走 containerd 原生 API,不读 CRI 配置,需单独配置hosts.toml加速(docker.io / registry.k8s.io 各一份,见课程笔记,此处从略)。
安装 kubeadm/kubelet/kubectl(v1.30.2):
[root@ubuntu2404 ~]# apt-cache madison kubeadm | head
[root@ubuntu2404 ~]# apt install -y kubeadm=1.30.2-1.1 kubelet=1.30.2-1.1 kubectl=1.30.2-1.1
[root@ubuntu2404 ~]# systemctl enable kubelet --now
此时 kubelet 处于
activating,等集群初始化完成后才变active,属正常。
配置命令补全:
[root@ubuntu2404 ~]# mkdir /etc/bash_completion.d
[root@ubuntu2404 ~]# crictl completion bash > /etc/bash_completion.d/crictl
[root@ubuntu2404 ~]# nerdctl completion bash > /etc/bash_completion.d/nerdctl
[root@ubuntu2404 ~]# echo 'export CONTAINERD_NAMESPACE=k8s.io' >> /etc/bash_completion.d/nerdctl
[root@ubuntu2404 ~]# kubectl completion bash > /etc/bash_completion.d/kubectl
[root@ubuntu2404 ~]# kubeadm completion bash > /etc/bash_completion.d/kubeadm
[root@ubuntu2404 ~]# source /etc/bash_completion.d/{crictl,nerdctl,kubectl,kubeadm}
⚠️ 必须设置
CONTAINERD_NAMESPACE=k8s.io,否则 nerdctl 默认把镜像导入default命名空间,K8s 拉不到镜像。
2.10 打快照 k8s
[root@ubuntu2404 ~]# init 0
关闭虚拟机,打快照名称 k8s。K8s 节点(master21-23、worker31-33)从此快照克隆。
2.11 准备集群节点
[root@ubuntu2404 ~]# sethost
Usage: /usr/local/bin/sethost 101-102 | 111-113 | 121-123 | 131-133
[root@ubuntu2404 ~]# sethost 101 # ha01;其余节点类推
- 通过
base快照克隆:ha01/ha02、etcd11/12/13 - 通过
k8s快照克隆:master21/22/23、worker31/32/33 - 每台机器用
sethost <编号>设置主机名和 IP
三、单机 etcd 快速体验(10.1.8.10)
上午 10:24–10:36,在独立节点
etcd@10.1.8.10上用 apt 安装的单机 etcd 做入门练习:看配置、启动、读写、把整个/etc/hosts存进 etcd。理解 etcd 的 KV 模型后,下午的集群与备份实验才顺理成章。
3.1 查看默认配置
root@etcd ~ 10:24:44# cat /etc/default/etcd
## etcd(1) daemon options
## Use environment to override, for example: ETCD_NAME=default
ETCD_NAME=*default*
ETCD_DATA_DIR="/var/lib/etcd/default"
ETCD_LISTEN_CLIENT_URLS="http://localhost:2379,http://10.1.8.10:2379"
ETCD_ADVERTISE_CLIENT_URLS="http://localhost:2379,http://10.1.8.10:2379"
Ubuntu 的 etcd 服务通过
/etc/default/etcd注入环境变量;数据目录默认/var/lib/etcd/default,客户端端口 2379(peer 端口 2380 未配置,单机不需要)。
3.2 启动并查看集群成员
root@etcd ~ 10:24:50# systemctl restart etcd
root@etcd ~ 10:24:58# etcdctl member list
8e9e05c52164694d, started, *default*, http://localhost:2380, http://10.1.8.10:2379,http://localhost:2379, false
3.3 put / get 基本读写
root@etcd ~ 10:25:04# etcdctl put /xxx yyy
OK
root@etcd ~ 10:25:17# etcdctl get /xxx
/xxx
yyy
3.4 把 /etc/hosts 存进 etcd(多行值)
思路:把 hosts 文件内容当"值"存到 /etc/hosts 这个"键",实现配置的集中存储(后续可配合 watch 分发配置)。
先尝试手敲多行值(换行输入麻烦,且容易粘错):
root@etcd ~ 10:25:21# etcdctl put /etc/hosts '10.1.8.11 etcd1.ningcode.cn etcd1
> 10.1.8.12 etcd2.ningcode.cn etcd2'
OK
root@etcd ~ 10:26:44# etcdctl edit /etc/hosts
Error: unknown command "edit" for "etcdctl"
Run 'etcdctl --help' for usage.
etcdctl 没有
edit子命令(那是 vim 的用法)。更优雅的做法是用管道把文件内容直接写入:
root@etcd ~ 10:29:36# cat /etc/hosts | etcdctl put /etc/hosts
OK
root@etcd ~ 10:29:43# etcdctl get /etc/hosts --print-value-only
127.0.0.1 localhost
127.0.1.1 ubuntu2204
...
###### kubernetes #####
10.1.8.30 master30.ningcode.cn master30
10.1.8.31 worker31.ningcode.cn worker31
10.1.8.32 worker32.ningcode.cn worker32
192.168.46.201 hub.laoma.cloud
10.1.8.10 etcd.ningcode.cn etcd
--print-value-only只输出值,适合看配置文件类数据;cat file | etcdctl put key是"整文件入 etcd"的标准姿势。
3.5 收尾:卸载与关机
root@etcd ~ 10:29:47# systemctl stop etcd.service
root@etcd ~ 10:34:34# apt remove -y etcd-server etcd-client
root@etcd ~ 10:34:44# rm -rf /var/lib/etcd/default
# 顺手把 hosts 里的旧记录整理后,尝试重装(apt update 下载 Contents 太慢被 Ctrl+C)
root@etcd ~ 10:35:33# apt update && apt install -y etcd-server etcd-client
...^C
root@etcd ~ 10:36:07# apt remove -y etcd-server etcd-client
Package 'etcd-server' is not installed, so not removed
...
root@etcd ~ 10:36:15# poweroff
这台机器之后被克隆成 etcd1/2/3 三台集群节点(下一章)。
3.6 小结
- etcd 是 KV 存储:
put <key> <value>写、get <key>读、--print-value-only只看值 - 配置入口
/etc/default/etcd,数据目录/var/lib/etcd/default,客户端端口 2379 - 整文件入 etcd:
cat 文件 | etcdctl put 键;没有edit子命令
四、etcd 三节点集群搭建(10.1.8.11/12/13)
上午 10:45–11:24,把 10.1.8.10 的 base 模版克隆成 etcd1/etcd2/etcd3 三台,练习 etcd 集群两种建群方式:静态配置一次性建 2 节点集群 + member add 动态扩容到 3 节点,再演练 member remove。多节点并行操作、断断续续,本节按时间戳合并。
4.1 节点准备
三台节点均由 base 模版克隆而来,先确认主机名/IP,再安装 etcd:
# etcd1(10.1.8.11)
root@etcd1 ~ 10:50:09# hostname;ip -br a|tail -1|grep -o 10.1.8.1[1-3]
etcd1.ningcode.cn
10.1.8.11
# 三台都执行(输出长省略)
root@etcd1 ~ 10:49:04# apt install -y etcd-server etcd-client
...(etcd-server 3.4.30 / etcd-client 3.4.30)
主机名/IP 由 base 模版的
sethost脚本设置(见 2.7);etcd1 一开始忘了装包,systemctl status etcd报Unit etcd.service could not be found,补装即可。
4.2 静态配置建 2 节点集群(etcd1 + etcd2)
先在 etcd2 上写好完整静态配置(ETCD_INITIAL_CLUSTER_STATE="new" 表示首次建群,列出所有成员):
root@etcd2 ~ 10:46:39# vim /etc/default/etcd
root@etcd2 ~ 10:46:52# cat /etc/default/etcd
ETCD_NAME="etcd2"
ETCD_DATA_DIR="/var/lib/etcd/default"
ETCD_LISTEN_PEER_URLS="http://localhost:2380,http://10.1.8.12:2380"
ETCD_LISTEN_CLIENT_URLS="http://localhost:2379,http://10.1.8.12:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.1.8.12:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://localhost:2379,http://10.1.8.12:2379"
ETCD_INITIAL_CLUSTER="etcd1=http://10.1.8.11:2380,etcd2=http://10.1.8.12:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcdcluster"
ETCD_INITIAL_CLUSTER_STATE="new"
etcd1 配置相同(ETCD_NAME="etcd1"、IP 换成 10.1.8.11)。两台都启动:
root@etcd2 ~ 10:50:04# systemctl start etcd
root@etcd1 ~ 10:50:04# systemctl start etcd
4.3 验证 2 节点集群 + 跨节点读写
root@etcd2 ~ 10:50:16# etcdctl member list
5567a2bd9a697ce1, started, etcd1, http://10.1.8.11:2380, http://10.1.8.11:2379,http://localhost:2379, false
7b0f8f8abc119055, started, etcd2, http://10.1.8.12:2380, http://10.1.8.12:2379,http://localhost:2379, false
# 在 etcd2 上写入,在 etcd2/etcd1 上都能读到(数据已同步)
root@etcd2 ~ 10:50:48# etcdctl put /users/user1/name ningcode
OK
root@etcd2 ~ 10:52:10# etcdctl get /users/user1/name
/users/user1/name
ningcode
root@etcd1 ~ 10:52:17# etcdctl put /users/user1/name ningcode
OK
两个成员都是
started,集群形成。Raft 集群任一节点写入,其余节点自动同步。
4.4 动态扩容:member add 加入 etcd3
在 etcd1 上把 etcd3 声明为成员,命令会打印 etcd3 需要的配置模板:
root@etcd1 ~ 10:54:54# etcdctl member add etcd3 --peer-urls=http://10.1.8.13:2380
Member 2dfa2f7721d4dbb3 added to cluster e1d7ed63825279b6
ETCD_NAME="etcd3"
ETCD_INITIAL_CLUSTER="etcd3=http://10.1.8.13:2380,etcd1=http://10.1.8.11:2380,etcd2=http://10.1.8.12:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.1.8.13:2380"
ETCD_INITIAL_CLUSTER_STATE="existing" # 关键:加入已有集群必须写 existing
把 etcd1 的配置 scp 给 etcd3,改好名字/IP 后启动:
root@etcd1 ~ 10:55:44# scp /etc/default/etcd root@etcd3:/etc/default/etcd
etcd 100% 510 2.1MB/s 00:00
# etcd3 上修改:ETCD_NAME=etcd3、IP 换 10.1.8.13、INITIAL_CLUSTER_STATE=existing
root@etcd3 ~ 10:58:33# vim /etc/default/etcd
root@etcd3 ~ 10:58:49# systemctl start etcd
# 回到 etcd1 确认 3 节点全部 started
root@etcd1 ~ 10:58:01# etcdctl member list
2dfa2f7721d4dbb3, started, etcd3, http://10.1.8.13:2380, http://10.1.8.13:2379,http://localhost:2379, false
5567a2bd9a697ce1, started, etcd1, http://10.1.8.11:2380, http://10.1.8.11:2379,http://localhost:2379, false
7b0f8f8abc119055, started, etcd2, http://10.1.8.12:2380, http://10.1.8.12:2379,http://localhost:2379, false
4.5 演练移除成员:member remove
etcd3 先停掉服务并清数据,然后从集群中移除该成员:
root@etcd3 ~ 10:59:02# systemctl stop etcd.service
root@etcd1 ~ 10:59:09# etcdctl member remove 2dfa2f7721d4dbb3
Member 2dfa2f7721d4dbb3 removed from cluster e1d7ed63825279b6
root@etcd1 ~ 11:03:05# etcdctl member list
5567a2bd9a697ce1, started, etcd1, http://10.1.8.11:2380, http://10.1.8.11:2379,http://localhost:2379, false
7b0f8f8abc119055, started, etcd2, http://10.1.8.12:2380, http://10.1.8.12:2379,http://localhost:2379, false
移除后回到 2 节点。etcd1 随后把配置里的 initial_cluster 改回 2 节点并重启(11:03–11:05),保持配置与集群一致。
4.6 上午收尾
三台节点配置完命令补全后全部关机(详见第 5 章):
root@etcd1 ~ 11:21:02# poweroff
root@etcd2 ~ 11:23:43# init 0
root@etcd3 ~ 11:24:15# init 0
4.7 小结
- 静态建群:所有节点
ETCD_INITIAL_CLUSTER列出全部成员 +STATE=new,同时启动即可成团 - 动态扩容:
etcdctl member add <名> --peer-urls=http://IP:2380→ 新节点用输出模板配STATE=existing启动 - 移除成员:
etcdctl member remove <ID>(ID 从 member list 拿) - 成员增删都要保持配置文件与实际集群一致,否则重启会校验失败(下午 17:02 就因此踩坑,见第 12 章)
五、etcdctl 数据操作与命令补全
etcd 数据是"键值对",键用
/分层组织(如/users/user1/name),看起来像目录。本章整理下午 14:2x 数据实验中的读写删操作,以及上午 11:0x–11:2x 的命令补全配置(etcd1 上还踩了缺bash-completion的坑)。
5.1 基本读写删操作
root@etcd1 ~ 14:01:53# etcdctl put /users/user1/name ningcode
OK
root@etcd1 ~ 14:22:44# etcdctl put /users/user1/age 18
OK
root@etcd1 ~ 14:23:04# etcdctl put /users/user1/name zhangsan # 覆盖写
OK
root@etcd1 ~ 14:23:18# etcdctl put /users/user1/name ningcode # 再改回
OK
root@etcd1 ~ 14:23:27# etcdctl put /users/user2/name zhangsan
OK
删除(del 返回删除条数,删不存在的键返回 0):
root@etcd1 ~ 14:24:47# etcdctl del /users/user2/name
1
root@etcd1 ~ 14:25:03# etcdctl del /users/user2/name
0
查看某个"目录"下的所有键值(等价的目录式浏览):
root@etcd1 ~ 14:31:46# etcdctl get /users/user1/
/users/user1/age
18
/users/user1/name
ningcode
5.2 命令误用三连(真实踩坑)
操作时把 etcdctl 误写成 etcd、systemctl,都会报错——管理命令只有 etcdctl:
root@etcd1 ~ 15:07:17# etcd get /users/user1/name ningcode
2026-08-17 15:07:30.384939 E | etcdmain: error verifying flags, 'get' is not a valid flag. See 'etcd --help'.
root@etcd1 ~ 15:07:37# systemctl del /users/user2/name
Unknown command verb 'del', did you mean 'help'?
root@etcd1 ~ 15:08:10# etcd del /users/user2/name
2026-08-17 15:08:15.209702 E | etcdmain: error verifying flags, 'del' is not a valid flag.
root@etcd1 ~ 15:08:15# etcdctl del /users/user2/name # 正确
1
记忆点:
etcd是服务端程序,etcdctl是客户端工具,读写删全部用etcdctl。
5.3 命令补全:etcdctl completion bash
root@etcd1 ~ 11:05:31# mkdir /etc/bash_completion.d
root@etcd1 ~ 11:11:36# etcdctl completion bash > /etc/bash_completion.d/etcdctl
root@etcd1 ~ 11:12:00# source /etc/bash_completion.d/etcdctl
坑:按 Tab 报 _get_comp_words_by_ref: command not found —— base 模版没装 bash-completion 包,补全函数缺失:
root@etcd1 ~ 11:12:06# etcdctl get l<TAB>
bash: _get_comp_words_by_ref: command not found
修复:补装 bash-completion 并重开 shell:
root@etcd1 ~ 11:19:35# apt install -y bash-com*
# 安装输出省略
root@etcd1 ~ 11:20:23# bash # 新开子 shell 让补全生效
root@etcd1 ~ 11:21:02# etcdctl member <TAB> # 正常补全:list add remove ...
etcd2/etcd3 上同样的三行命令直接生效(他们的模版装过 bash-completion),对比可见 2.2 里"安装基础软件包"含
bash-completion的意义。
5.4 小结
- 写
put 键 值、读get 键、删del 键(返回删除条数)、目录浏览get 前缀/ - 只有
etcdctl能操作数据;etcd/systemctl不行 - 补全三步:
etcdctl completion bash > /etc/bash_completion.d/etcdctl→source该文件 →(缺包时)apt install -y bash-completion
六、etcd 数据备份与恢复(重点)
etcd 是 K8s 的"数据库",丢了它集群就废了。当天下午(14:01–17:21)做了大量备份/恢复演练,两种主流方式都练到了:
① 目录级备份:停 etcd →cp -a数据目录 → 出问题用备份整体还原;
② snapshot 快照:在线etcdctl snapshot save→ 清库 →snapshot restore恢复到指定目录。
先讲演练,再讲 K8s 数据在 etcd 里的结构与真实恢复。
6.1 目录级备份恢复演练(etcd1,15:02–15:09)
标准流程:先造数据 → 备份 → 删数据 → 用备份还原 → 验证。备份时用 cp -a(保留属主权限):
# ① 造数据
root@etcd1 ~ 15:02:04# etcdctl put /users/user1/name u1
OK
root@etcd1 ~ 15:02:17# etcdctl put /users/user2/name u2
OK
root@etcd1 ~ 15:02:21# etcdctl put /users/user1/age 18
OK
# ② 停止服务后备份数据目录
root@etcd1 ~ 15:02:28# systemctl stop etcd
root@etcd1 ~ 15:02:37# cp -a /var/lib/etcd/default{,-bak}
root@etcd1 ~ 15:02:55# systemctl start etcd
# ③ 模拟事故:删除数据
root@etcd1 ~ 15:03:03# etcdctl del /users/user1/name
1
root@etcd1 ~ 15:03:26# etcdctl get /users/user1/name # 已丢
root@etcd1 ~ 15:03:31# etcdctl get /users/user2/name # u2 还在
/users/user2/name
u2
# ④ 还原:停服 → 挪走坏目录 → 备份拷回 → 修复属主 → 启动
root@etcd1 ~ 15:03:35# systemctl stop etcd
root@etcd1 ~ 15:03:43# mv /var/lib/etcd/default /var/lib/etcd/default-bad
root@etcd1 ~ 15:04:04# mv /var/lib/etcd/default-bak/ /var/lib/etcd/defaul # 手滑少个 t
root@etcd1 ~ 15:04:12# mv /var/lib/etcd/defaul /var/lib/etcd/default # 补回来
root@etcd1 ~ 15:04:17# systemctl start etcd
# ⑤ 验证:数据全部回来
root@etcd1 ~ 15:04:23# etcdctl get /users/user2/name
/users/user2/name
u2
root@etcd1 ~ 15:04:26# etcdctl get /users/user1/name
/users/user1/name
u1
完整恢复口诀:stop → 备份挪走/拷回 → chown -R etcd:etcd → start。
chown必须做,否则 etcd 以 etcd 用户启动时读不了 root 拷入的文件。早期几轮演练还踩过cp -a目标已存在导致目录嵌套(default/default/)的坑,注意备份名别重复使用。
6.2 坑:配置被还原导致 bind 失败(14:56)
演练过程中误把 /etc/default/etcd.ori(原始配置,监听 10.1.8.10)还原回去,etcd 起不来:
root@etcd1 ~ 14:55:44# cp /etc/default/etcd.ori /etc/default/etcd
root@etcd1 ~ 14:55:52# systemctl start etcd
Job for etcd.service failed because the control process exited with error code.
# journalctl 定位根因
root@etcd1 ~ 14:56:00# journalctl -u etcd -n 20
... etcd[4266]: listen tcp 10.1.8.10:2379: bind: cannot assign requested address
原因:这台机器 IP 是 10.1.8.11,配置却让它监听 10.1.8.10,绑定失败。修复:清空配置用默认值启动:
root@etcd1 ~ 14:56:38# vim /etc/default/etcd
root@etcd1 ~ 14:57:02# > /etc/default/etcd # 清空(etcd 用默认参数启动)
root@etcd1 ~ 14:57:07# systemctl start etcd
root@etcd1 ~ 14:57:19# ls /var/lib/etcd/default/
member
6.3 K8s 数据在 etcd 中的结构(master30,14:02–14:05)
K8s 的所有对象都以 /registry/... 前缀存在 etcd 里。master30 上先装 etcd-client,再自写两个小脚本方便浏览:
root@master30 ~ 14:02:01# apt install etcd-client -y
etcdls 脚本(类 ls:按前缀列举"目录",PREFIX 默认 /):
root@master30 ~ 14:02:15# vim /usr/local/bin/etcdls
#!/bin/bash
export ETCDCTL_API=3
key_file=/etc/kubernetes/pki/etcd/server.key
cert_file=/etc/kubernetes/pki/etcd/server.crt
cacert_file=/etc/kubernetes/pki/etcd/ca.crt
endpoints=https://127.0.0.1:2379
PREFIX=${1:-/}
tmpfile=$(mktemp)
etcdctl --key=${key_file} --cert=${cert_file} --cacert=${cacert_file} \
--endpoints=${endpoints} get $PREFIX --keys-only --prefix |grep -v '^$'|egrep "$PREFIX[^/]*/" > $tmpfile
count=$(cat $tmpfile | egrep -o "$PREFIX[^/]*/" | sort | uniq | wc -l)
if [ $count -eq 1 ];then
cat $tmpfile | egrep -o "^$PREFIX[^/]*/[^/]*" | sort | uniq
else
cat $tmpfile | egrep -o "^$PREFIX[^/]*" | sort | uniq
fi
rm -f $tmpfile
root@master30 ~ 14:02:35# chmod +x /usr/local/bin/etcdls
etcdget 脚本(直接取键值):
root@master30 ~ 14:03:01# vim /usr/local/bin/etcdget
#!/bin/bash
export ETCDCTL_API=3
key_file=/etc/kubernetes/pki/etcd/server.key
cert_file=/etc/kubernetes/pki/etcd/server.crt
cacert_file=/etc/kubernetes/pki/etcd/ca.crt
endpoints=https://127.0.0.1:2379
etcdctl --key=${key_file} --cert=${cert_file} --cacert=${cacert_file} --endpoints=${endpoints} get $*
K8s 的 etcd 走 HTTPS(端口 2379 + 证书认证),所以脚本固定带上
/etc/kubernetes/pki/etcd/下的三个证书参数。
浏览 /registry 目录树:
root@master30 ~ 14:03:34# etcdls /registry/ser
/registry/serviceaccounts
/registry/services
root@master30 ~ 14:03:52# etcdls /registry/services
/registry/services/endpoints
/registry/services/specs
root@master30 ~ 14:04:00# etcdls /registry/services/endpoints
/registry/services/endpoints/default
/registry/services/endpoints/ingress-nginx
/registry/services/endpoints/kube-storage
/registry/services/endpoints/kube-system
/registry/services/endpoints/kubernetes-dashboard
/registry/services/endpoints/metallb-system
root@master30 ~ 14:04:05# etcdls /registry/services/endpoints/kube-system
/registry/services/endpoints/kube-system/kube-dns
/registry/services/endpoints/kube-system/metrics-server
6.4 etcdget 取 K8s 对象:值是二进制
# 忘了给 etcdget 加执行权限 → Permission denied
root@master30 ~ 14:04:19# etcdget /registry/services/endpoints/kube-system/kube-dns --print-value-only
-bash: /usr/local/bin/etcdget: Permission denied
# 想用 strings 解码,但 binutils 没装(提示:strings 由 binutils 包提供)
root@master30 ~ 14:04:25# etcdget ... | strings
-bash: strings: command not found
-bash: /usr/local/bin/etcdget: Permission denied
root@master30 ~ 14:04:50# chemod +x /usr/local/bin/etcdget # 拼错
-bash: chemod: command not found
root@master30 ~ 14:05:41# chmod +x /usr/local/bin/etcdget
root@master30 ~ 14:05:47# etcdget /registry/services/endpoints/kube-system/kube-dns --print-value-only
k8s
v1 Endpoints...kube-dns kube-system...(protobuf 二进制,肉眼不可读)
K8s 存入 etcd 的是 protobuf 序列化数据,直接 get 是乱码属正常;想读原文可
| strings(需apt install -y binutils)。这里理解"K8s 对象在 etcd 中的存储路径"即可。
6.5 snapshot 快照:保存、恢复、跨节点分发(17:16–17:19)
重建后的 2 节点集群(etcd1 + etcd2,见第 4 章小节)上做快照演练。先在线备份:
root@etcd1 ~ 17:15:59# export ETCDCTL_API=3
root@etcd1 ~ 17:16:34# etcdctl snapshot save /root/snapshot1.db
{"level":"info","ts":...,"msg":"saved","path":"/root/snapshot1.db","size":"25 kB"}
Snapshot saved at /root/snapshot1.db
在 etcd1 上清库并恢复:
root@etcd1 ~ 17:16:40# systemctl stop etcd
root@etcd1 ~ 17:17:01# rm -fr /var/lib/etcd/default/
root@etcd1 ~ 17:17:15# etcdctl snapshot restore /root/snapshot1.db --data-dir=/var/lib/etcd/default
{"level":"info","ts":...,"msg":"restoring snapshot","path":"/root/snapshot1.db",...}
{"level":"info","ts":...,"msg":"restored snapshot",...}
root@etcd1 ~ 17:17:29# chown -R etcd:etcd /var/lib/etcd/default # ★ 别忘了
root@etcd1 ~ 17:17:37# systemctl start etcd
跨节点分发:scp 快照到 etcd2 恢复:
root@etcd1 ~ 17:17:41# # 确认etcd1上快照存在
ls -lh /root/snapshot1.db
# scp传到etcd2的/root目录
scp /root/snapshot1.db root@10.1.8.12:/root/
-rw------- 1 root root 25K Aug 17 17:16 /root/snapshot1.db
snapshot1.db 100% 24KB 30.7MB/s 00:00
etcd2 上等快照文件到达后才能恢复(17:17、17:18 两次都报文件不存在,17:19 文件到了才成功):
root@etcd2 ~ 17:17:16# etcdctl snapshot restore /root/snapshot1.db --data-dir=/var/lib/etcd/default
Error: open /root/snapshot1.db: no such file or directory
root@etcd2 ~ 17:18:13# ls -lh /root/ # 确认文件还没到
...(只有 cni-plugins、nerdctl,无 snapshot1.db)
root@etcd2 ~ 17:19:12# etcdctl snapshot restore /root/snapshot1.db --data-dir=/var/lib/etcd/default
{"level":"info","ts":...,"msg":"restored snapshot",...}
root@etcd2 ~ 17:19:14# chown -R etcd:etcd /var/lib/etcd/default
root@etcd2 ~ 17:19:19# systemctl start etcd
snapshot 恢复要点:①
--data-dir指定恢复目录,目录必须不存在或先清空;② 恢复后chown -R etcd:etcd;③ 多节点间先 scp 再恢复,避免"文件不存在"空跑。
6.6 K8s etcd 快照恢复实战(master30,17:19–17:21)
在现有 K8s 集群上完整走一遍"备份 → 删资源 → 清库 → 恢复 → 数据回来":
# ① 造一个 Deployment
root@master30 ~ 17:19:52# kubectl create deployment web --image=hub.laoma.cloud/library/httpd --replicas=2
deployment.apps/web created
# ② 在线备份 etcd 快照(带证书,7.6MB)
root@master30 ~ 17:20:01# etcdctl --key=${key_file} --cert=${cert_file} --cacert=${cacert_file} \
--endpoints=${endpoints} snapshot save snapshot.db
Snapshot saved at snapshot.db
# ③ 删除 Deployment,并清空 etcd 数据目录
root@master30 ~ 17:20:10# kubectl delete deployments.apps web
deployment.apps "web" deleted
root@master30 ~ 17:20:17# rm -fr /var/lib/etcd/
# ④ 恢复(第一次 --name 写错成 laoma.cloud)
root@master30 ~ 17:20:23# etcdctl --key=... snapshot restore snapshot.db --data-dir=/var/lib/etcd \
--name=master30.laoma.cloud --initial-cluster=master30.laoma.cloud=https://10.1.8.30:2380 \
--initial-advertise-peer-urls=https://10.1.8.30:2380
...restored snapshot...
root@master30 ~ 17:20:38# etcdctl ... snapshot restore snapshot.db --data-dir=/var/lib/etcd \
--name=master30.ningcode.cn --initial-cluster=master30.ningcode.cn=https://10.1.8.30:2380 ...
Error: data-dir "/var/lib/etcd" exists # 第一次已生成目录,先清掉
root@master30 ~ 17:21:10# rm -rf /var/lib/etcd/
root@master30 ~ 17:21:20# etcdctl ... snapshot restore snapshot.db --data-dir=/var/lib/etcd \
--name=master30.ningcode.cn --initial-cluster=master30.ningcode.cn=https://10.1.8.30:2380 \
--initial-advertise-peer-urls=https://10.1.8.30:2380
...restored snapshot...
# ⑤ 验证:被删的 web 回来了
root@master30 ~ 17:21:23# kubectl get deployments.apps
NAME READY UP-TO-DATE AVAILABLE AGE
web 2/2 2 2 89s
关键点:
--name和--initial-cluster必须与集群实际成员名一致(这里是master30.ningcode.cn),写错会导致 etcd 以错误身份启动、API Server 读不到数据(当时kubectl get deployments一度显示 No resources)。恢复后 etcd 数据目录需重启 etcd 静态 Pod 生效。
6.7 小结
- 目录备份:
cp -a整体拷贝,恢复 = 挪走坏的 + 拷回 +chown -R etcd:etcd,适合同机快速回滚 - 快照备份:
etcdctl snapshot save在线做,恢复用snapshot restore --data-dir,跨节点先 scp - 恢复三件套:先停服/清目录 → restore → chown;
--name必须匹配集群成员名 - K8s 数据在
/registry/...下、protobuf 二进制存储;etcd 走 HTTPS 需带证书
七、生产高可用概述与主机规划
从本章开始整合课程笔记《生产环境 Kubernetes 集群部署.md》并优化:把 HA 理论、11 台主机规划、模版准备、正式部署、集群升级按可操作顺序重排,实验/排障部分与前面日志互相印证。
7.1 为什么需要高可用
K8s 业务本身靠控制器实现负载均衡与高可用,但单节点 Master 有两个致命问题:
- 一旦 Master 故障,整个集群不可用(无管理面,业务失控)
- 单节点 Master 工作负载能力有限(证书/调度/控制器都挤在一台)
为消除单点故障,需要为以下组件分别建立高可用方案:
| 组件 | 无状态? | HA 思路 |
|---|---|---|
| etcd | 否(强一致) | 3/5 节点独立集群 |
| kube-apiserver | 是 | 统一外部入口(LB 或网络层) |
| kube-controller-manager / kube-scheduler | 是 | 多实例,选主(leader election) |
| kube-dns / CoreDNS | 是 | 多副本 + anti-affinity 分散节点 |
7.2 etcd 高可用的三种方案
| 方案 | 做法 | 优点 | 适合 |
|---|---|---|---|
| ① 独立 etcd 集群 | 3/5 台独立服务器跑 etcd,K8s 外置连接 | 节点增减显式通知,滚动升级方便;前面可再加 HA+LB | 预算充足但保守 |
| ② K8s 内 static pod | 每台 Master 用 static pod 跑 etcd,多台组集群 | 注册进 K8s,监控/日志组件可正常工作,可管理性强 | 折中 |
| ③ self-hosted etcd | etcd 跑在 K8s 之上,用 etcd-operator 运维 | 自动化运维,最贴合 K8s 习惯 | 想一步到位、愿担风险 |
本实验(日志部分)用的是方案 ② 的变体——直接在独立虚拟机上用 apt 装 etcd 组成外部集群(etcd1/2/3 演练,etcd11/12/13 正式部署),对应方案 ① 的简化版:K8s 用外部 etcd(
etcd.external.endpoints指向 3 台 etcd)。
7.3 kube-apiserver 高可用
apiserver 无状态,HA 的关键是统一入口:
- 外部负载均衡器(推荐公有云):云 LB / 私有云 LVS / HaProxy;需额外保证 LB 自身 HA
- 网络层负载均衡(推荐私有云):Master 上 BGP ECMP,或 Node 上 iptables NAT;无需额外服务,但对网络配置有要求
本次实践选择 Keepalived(VIP)+ Haproxy(6443 TCP 轮询),即"外部负载均衡器"路线,同时用 Keepalived 解决 LB 自身的高可用。
7.4 kube-controller-manager / kube-scheduler / kube-dns
- controller-manager 与 scheduler:Master 组件,多实例 +
--leader-elect自动选主,多台 Master 跑 static pod 即可 - kube-dns(CoreDNS):本质是 Node 上的业务,用 Service 提供 DNS;replicas 设为 ≥2,并用 anti-affinity 把副本分散到不同 Node,避免单点
7.5 高可用架构拓扑(两种 etcd 布局)
| 拓扑 | 说明 | 适用 |
|---|---|---|
| 堆叠 etcd(stacked) | etcd 与 kube-apiserver 同机(static pod),3 个控制面 = 3 个 etcd 成员 | 控制面节点 ≥3 且数量为奇数,管理简单 |
| 外部 etcd(external) | etcd 独立集群,apiserver 通过 --etcd-servers 连接,与 Master 解耦 | 控制面 2 台也可以,etcd 单独扩容/升级 |
本实验采用外部 etcd:etcd11/12/13 独立三节点 + master21/22/23 控制平面 + ha01/ha02 入口。
7.6 主机规划(11 台)
| 集群 | 节点 | 地址 | 角色 | 硬件 |
|---|---|---|---|---|
| 高可用 | ha01.ningcode.cn | 10.1.8.101 | Keepalived + Haproxy | 1C/1G |
| 高可用 | ha02.ningcode.cn | 10.1.8.102 | Keepalived + Haproxy | 1C/1G |
| etcd | etcd11.ningcode.cn | 10.1.8.111 | etcd 节点 | 1C/1G |
| etcd | etcd12.ningcode.cn | 10.1.8.112 | etcd 节点 | 1C/1G |
| etcd | etcd13.ningcode.cn | 10.1.8.113 | etcd 节点 | 1C/1G |
| Kubernetes | master21.ningcode.cn | 10.1.8.121 | master 节点 | 2C/4G |
| Kubernetes | master22.ningcode.cn | 10.1.8.122 | master 节点 | 2C/4G |
| Kubernetes | master23.ningcode.cn | 10.1.8.123 | master 节点 | 2C/4G |
| Kubernetes | worker31.ningcode.cn | 10.1.8.131 | worker 节点 | 2C/4G |
| Kubernetes | worker32.ningcode.cn | 10.1.8.132 | worker 节点 | 2C/4G |
| Kubernetes | worker33.ningcode.cn | 10.1.8.133 | worker 节点 | 2C/4G |
- VIP:
10.1.8.100 k8s.ningcode.cn(Keepalived 漂移地址,见第 9 章) - 所有节点共用
10.1.8.0/24网段;Pod 网段10.224.0.0/16、Service 网段10.96.0.0/12 - 制作流程:先做 base 模版(第 2 章)→ 再做 k8s 模版 → 克隆 11 台 →
sethost批量设置 → 按第 8–10 章部署
八、部署 etcd 正式集群(etcd11/12/13)
本章起进入生产 HA 部署实操,内容来自课程笔记《生产环境 Kubernetes 集群部署.md》(当天 15:00-17:00 左右与日志演练并行完成,故命令提示符不包含实时时间戳)。
与前面日志演练的 etcd1/2/3 不同,生产集群使用独立的 etcd11/12/13 三台节点,K8s 将以外置 etcd 方式连接(见 7.6 主机规划)。
8.1 安装 etcd
三台节点操作相同(etcdN 泛指 etcd11/12/13):
[root@etcdN ~]# apt update && apt install -y etcd-server etcd-client
[root@etcdN ~]# systemctl stop etcd.service
[root@etcdN ~]# rm -fr /var/lib/etcd/default
卸载残留数据:第一次启动前清空
/var/lib/etcd/default,避免旧数据目录导致启动失败(对应第 12 章data-dir exists一类问题)。
8.2 编写 /etc/default/etcd(静态配置)
三台节点除
ETCD_NAME与自身 IP 不同外,ETCD_INITIAL_CLUSTER都写完整的 3 个成员(initial cluster 必须全量列出),ETCD_INITIAL_CLUSTER_STATE="new"表示首次创建集群。
etcd11:
[root@etcd11 ~]# cp /etc/default/etcd /etc/default/etcd.ori
[root@etcd11 ~]# cat > /etc/default/etcd <<'EOF'
ETCD_NAME="etcd11"
ETCD_DATA_DIR="/var/lib/etcd/default"
ETCD_LISTEN_PEER_URLS="http://localhost:2380,http://10.1.8.111:2380"
ETCD_LISTEN_CLIENT_URLS="http://localhost:2379,http://10.1.8.111:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.1.8.111:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://localhost:2379,http://10.1.8.111:2379"
ETCD_INITIAL_CLUSTER="etcd11=http://10.1.8.111:2380,etcd12=http://10.1.8.112:2380,etcd13=http://10.1.8.113:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"
EOF
etcd12:与 etcd11 相同,仅改名字和 IP:
[root@etcd12 ~]# cp /etc/default/etcd /etc/default/etcd.ori
[root@etcd12 ~]# cat > /etc/default/etcd <<'EOF'
ETCD_NAME="etcd12"
ETCD_DATA_DIR="/var/lib/etcd/default"
ETCD_LISTEN_PEER_URLS="http://localhost:2380,http://10.1.8.112:2380"
ETCD_LISTEN_CLIENT_URLS="http://localhost:2379,http://10.1.8.112:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.1.8.112:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://localhost:2379,http://10.1.8.112:2379"
ETCD_INITIAL_CLUSTER="etcd11=http://10.1.8.111:2380,etcd12=http://10.1.8.112:2380,etcd13=http://10.1.8.113:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"
EOF
etcd13:同理,ETCD_NAME="etcd13"、IP 换成 10.1.8.113,ETCD_INITIAL_CLUSTER 不变。
端口约定:
2379客户端端口(K8s apiserver 连接用),2380集群内部 peer 通信端口。
8.3 启动并验证集群
[root@etcdN ~]# systemctl enable etcd
[root@etcdN ~]# systemctl start etcd
# 在任意节点查看成员(三台应全部 started)
[root@etcd11 ~]# etcdctl member list
56ee9a8b1143f1f6, started, etcd12, http://10.1.8.112:2380, http://10.1.8.112:2379,http://localhost:2379, false
b09f448fe279511f, started, etcd11, http://10.1.8.111:2380, http://10.1.8.111:2379,http://localhost:2379, false
b83c09e0fc6318a6, started, etcd13, http://10.1.8.113:2380, http://10.1.8.113:2379,http://localhost:2379, false
8.4 配置 etcdctl 命令补全
[root@etcdN ~]# mkdir -p /etc/bash_completion.d
[root@etcdN ~]# etcdctl completion bash > /etc/bash_completion.d/etcdctl
[root@etcdN ~]# source /etc/bash_completion.d/etcdctl
提醒:必须提前安装
bash-completion包(第 2 章 base 模版),否则补全时报_get_comp_words_by_ref: command not found(上午日志真实踩坑,见第 12 章)。
九、部署 Keepalived + Haproxy(VIP 10.1.8.100)
两台 HA 节点(ha01/ha02)上同时部署 Keepalived 与 Haproxy:
- Keepalived:虚拟出一个 VIP
10.1.8.100(k8s.ningcode.cn),两节点互为主备,解决入口单点;- Haproxy:把 VIP 上的
6443流量按 roundrobin 转发到 3 台 master,K8s 客户端统一连10.1.8.100:6443。
浏览器访问http://HA节点IP:1080/haproxy-stats(账号 admin / 密码 123456)可实时查看后端健康状态。
9.1 安装 keepalived
[root@haN ~]# apt install -y keepalived
先配 ha02(低优先级),再配 ha01(高优先级),这样 VIP 会落在 ha01 上。
9.2 ha02 配置(BACKUP,priority 100)
[root@ha02 ~]# cp /etc/keepalived/keepalived.conf{.sample,}
[root@ha02 ~]# cat > /etc/keepalived/keepalived.conf <<'EOF'
! Configuration File for keepalived
global_defs {
router_id ha02
}
vrrp_instance k8s {
state BACKUP
interface ens32
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
10.1.8.100/24
}
}
EOF
[root@ha02 ~]# systemctl enable keepalived.service
[root@ha02 ~]# systemctl restart keepalived.service
[root@ha02 ~]# ip -br a
lo UNKNOWN 127.0.0.1/8 ::1/128
ens32 UP 10.1.8.102/24 fe80::20c:29ff:fea5:36a1/64
此时 ha02 上看不到 VIP,因为 ha01 优先级更高,VIP 暂时由 ha01 持有(BACKUP + 低优先级,不抢占)。
9.3 ha01 配置(BACKUP,priority 200 + nopreempt)
[root@ha01 ~]# cp /etc/keepalived/keepalived.conf{.sample,}
[root@ha01 ~]# cat > /etc/keepalived/keepalived.conf <<'EOF'
! Configuration File for keepalived
global_defs {
router_id ha01
}
vrrp_instance k8s {
state BACKUP
interface ens32
virtual_router_id 51
priority 200
nopreempt
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
10.1.8.100/24
}
}
EOF
[root@ha01 ~]# systemctl enable keepalived.service
[root@ha01 ~]# systemctl restart keepalived.service
[root@ha01 ~]# ip -br a
lo UNKNOWN 127.0.0.1/8 ::1/128
ens32 UP 10.1.8.101/24 10.1.8.100/24 fe80::20c:29ff:fe39:b65c/64
关键点:两节点都写
state BACKUP,靠 priority 决定主备(200 > 100);nopreempt表示主节点恢复后不主动抢回 VIP,避免网络抖动引起 VIP 频繁漂移。
9.4 部署 Haproxy(两台 HA 节点都装)
[root@haN ~]# apt install -y haproxy
# 追加统计面板 + k8s 后端配置
[root@haN ~]# cat >> /etc/haproxy/haproxy.cfg <<'EOF'
# 新增统计面板配置
listen stats
bind *:1080 # 监听 1080 端口(可自定义,如 8080)
mode http
stats enable # 启用统计面板
stats uri /haproxy-stats # 访问路径(如 http://HA节点IP:1080/haproxy-stats)
stats refresh 3s # 页面自动刷新间隔
stats auth admin:123456 # 登录账号密码(生产环境务必修改)
stats hide-version # 隐藏 HAProxy 版本(安全)
# 新增 k8s 面板配置
listen k8s
mode tcp
bind *:6443
balance roundrobin
server master21 10.1.8.121:6443 check inter 3000ms fall 3 rise 2
server master22 10.1.8.122:6443 check inter 3000ms fall 3 rise 2
server master23 10.1.8.123:6443 check inter 3000ms fall 3 rise 2
EOF
[root@haN ~]# systemctl enable haproxy
[root@haN ~]# systemctl restart haproxy.service
9.5 验证负载均衡
- 统计面板(账号密码
admin:123456):http://10.1.8.101:1080/haproxy-statshttp://10.1.8.102:1080/haproxy-stats
- 未初始化集群前,
k8s后端的 3 台 master 显示 DOWN 属正常现象;master21 初始化完成后应全部变绿。
说明:原笔记此处有 Haproxy 统计面板截图(
image-20260817154840272),图片文件未随笔记保存,此处以文字描述代替,实验时按上面 URL 访问即可。
十、kubeadm 初始化 HA 集群
在 master21 上执行初始化,关键配置:
controlPlaneEndpoint: 10.1.8.100:6443(走第 9 章 VIP),etcd 使用外部集群10.1.8.111-113:2379,Pod 网段10.224.0.0/16。
10.1 生成并修改初始化配置
# 生成默认初始化配置
[root@master21 ~]# kubeadm config print init-defaults | tee kubeadm-config.yaml
# 按需修改
[root@master21 ~]# vim kubeadm-config.yaml
修改后的完整内容(两个文档用 --- 分隔,第二个是 kube-proxy 的 ipvs 配置):
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
apiServer:
timeoutForControlPlane: 4m0s
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
# 添加控制平面(统一入口,走 Keepalived VIP)
controlPlaneEndpoint: "10.1.8.100:6443"
# 修改etcd:使用外部 etcd 集群,而不是本地 static pod
etcd:
external:
endpoints:
- "http://10.1.8.111:2379"
- "http://10.1.8.112:2379"
- "http://10.1.8.113:2379"
dns: {}
controllerManager: {}
scheduler: {}
# 按需修改,实验环境使用提前导入的镜像
imageRepository: registry.k8s.io
# 设置版本
kubernetesVersion: 1.30.2
networking:
# 设置域名
dnsDomain: kubernetes.ningcode.cn
# 设置pod子网
podSubnet: 10.224.0.0/16
serviceSubnet: 10.96.0.0/12
---
# 添加kube-proxy配置
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
ipvs:
scheduler: rr
clientConnection:
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
注意:
etcd.external模式不会生成 etcd 相关证书(apiserver 通过 HTTP 明文连接内部 etcd),日志中会出现External etcd mode: Skipping ...的提示,属正常现象。
10.2 初始化集群
[root@master21 ~]# kubeadm init --config kubeadm-config.yaml
输出要点(长输出省略):
[init] Using Kubernetes version: v1.30.2
[preflight] Running pre-flight checks
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] apiserver serving cert is signed for DNS names [kubernetes ... master21.ningcode.cn] and IPs [10.96.0.1 10.1.8.121 10.1.8.100]
[certs] External etcd mode: Skipping etcd/ca certificate authority generation
......
[control-plane] Creating static Pod manifest for "kube-apiserver"
[kubelet-start] Starting the kubelet
[api-check] The API server is healthy after 5.043608027s
[mark-control-plane] Marking the node master21.ningcode.cn as control-plane ...
[bootstrap-token] Using token: b0pnwt.1o94h2j82okh7gzt
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy
Your Kubernetes control-plane has initialized successfully!
初始化成功后,务必保存输出末尾的两段 join 命令(token 和 hash 只显示一次,过期/丢失可用
kubeadm token create --print-join-command重新生成):
- 控制平面节点 join(master22/23):
kubeadm join ... --control-plane- 工作节点 join(worker31/32/33):
kubeadm join ...(不带--control-plane)
10.3 配置 kubectl 凭据
[root@master21 ~]# mkdir -p $HOME/.kube
[root@master21 ~]# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@master21 ~]# sudo chown $(id -u):$(id -g) $HOME/.kube/config
10.4 部署 calico 网络
下载 calico 配置(v3.30.7):
[root@master21 ~]# wget --no-check-certificate https://raw.githubusercontent.com/projectcalico/calico/v3.30.7/manifests/calico.yaml
确认集群 pod 网段并修改 calico 的 CALICO_IPV4POOL_CIDR:
[root@master21 ~]# kubectl get cm -n kube-system kubeadm-config -o yaml|grep podSubnet
podSubnet: 10.224.0.0/16
# 取消 CALICO_IPV4POOL_CIDR 注释,并把默认 192.168 网段改成集群的 10.224.0.0/16
[root@master21 ~]# sed -i "s|# - name: CALICO_IPV4POOL_CIDR|- name: CALICO_IPV4POOL_CIDR|g" calico.yaml
[root@master21 ~]# sed -i "s|# value: \"192.*| value: \"10.224.0.0/16\"|g" calico.yaml
下载镜像到所有节点(calico.yaml 里引用 3 个镜像):
[root@master21 ~]# grep image: calico.yaml | uniq
image: docker.io/calico/cni:v3.30.7
image: docker.io/calico/node:v3.30.7
image: docker.io/calico/kube-controllers:v3.30.7
# 所有节点(master + worker)提前拉取,避免在线拉取超时
[root@all-node ~]# nerdctl pull docker.io/calico/cni:v3.30.7
[root@all-node ~]# nerdctl pull docker.io/calico/node:v3.30.7
[root@all-node ~]# nerdctl pull docker.io/calico/kube-controllers:v3.30.7
部署 calico:
[root@master21 ~]# kubectl apply -f calico.yaml
10.5 master22 / master23 加入集群(控制平面)
控制平面节点 join 前,必须先把 master21 的 CA 与 SA 密钥拷过去,否则会因证书不一致加入失败。
# 需拷贝的 6 个文件:ca.crt ca.key sa.key sa.pub front-proxy-ca.crt front-proxy-ca.key
[root@master22-23 ~]# mkdir -p /etc/kubernetes/pki && scp master21:/etc/kubernetes/pki/{ca.crt,ca.key,front-proxy-ca.crt,front-proxy-ca.key,sa.key,sa.pub} /etc/kubernetes/pki/
# 其他 master 节点加入集群(--control-plane 表示控制平面)
[root@master22-23 ~]# kubeadm join 10.1.8.100:6443 --token b0pnwt.1o94h2j82okh7gzt \
--discovery-token-ca-cert-hash sha256:0bd96f3415a5913f0fe28e6ad29c6bdfd487f4f7f46aa2dcfdf13f3bca00464f \
--control-plane
join 输出要点:
[preflight] Running pre-flight checks
[certs] Using the existing "sa" key
[check-etcd] Skipping etcd check in external mode
[mark-control-plane] Marking the node master22.ningcode.cn as control-plane ...
This node has joined the cluster and a new control plane instance was created:
......
Run 'kubectl get nodes' to see this node join the cluster.
master23 操作与 master22 完全相同。
10.6 worker 节点加入集群
# 所有 worker 节点(worker31/32/33)执行,命令不带 --control-plane
[root@workerN ~]# kubeadm join 10.1.8.100:6443 --token b0pnwt.1o94h2j82okh7gzt \
--discovery-token-ca-cert-hash sha256:0bd96f3415a5913f0fe28e6ad29c6bdfd487f4f7f46aa2dcfdf13f3bca00464f
10.7 验证集群状态
# 6 个节点全部 Ready:master21/22/23 + worker31/32/33
[root@master21 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master21.ningcode.cn Ready control-plane 14m v1.30.2
master22.ningcode.cn Ready control-plane 9m47s v1.30.2
master23.ningcode.cn Ready control-plane 9m15s v1.30.2
worker31.ningcode.cn Ready <none> 8m18s v1.30.2
worker32.ningcode.cn Ready <none> 8m17s v1.30.2
worker33.ningcode.cn Ready <none> 8m15s v1.30.2
# 核心组件全部 Running(calico-node 每节点一个、apiserver/controller-manager/scheduler 每台 master 一个)
[root@master21 ~]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-564985c589-v9njx 1/1 Running 0 10m
calico-node-bdzbb 1/1 Running 0 8m48s
calico-node-h6rrw 1/1 Running 0 10m
coredns-7b5944fdcf-5sd4q 1/1 Running 0 14m
kube-apiserver-master21.ningcode.cn 1/1 Running 0 14m
kube-apiserver-master22.ningcode.cn 1/1 Running 0 10m
kube-controller-manager-master21.ningcode.cn 1/1 Running 0 14m
kube-proxy-7ww8c 1/1 Running 0 9m45s
kube-scheduler-master22.ningcode.cn 1/1 Running 0 10m
......(其余 pod 略)
10.8 部署测试资源(验证多节点调度与负载均衡)
[root@master21 ~]# kubectl create deployment webapp --image=hub.ningcode.cn/library/nginx --replicas 3
# 3 个副本应分布在 3 台不同 worker 上
[root@master21 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
webapp-7fb6dc88f9-48kcx 1/1 Running 0 57s 10.224.113.129 worker32.ningcode.cn
webapp-7fb6dc88f9-9z87c 1/1 Running 0 57s 10.224.19.1 worker31.ningcode.cn
webapp-7fb6dc88f9-wtn6t 1/1 Running 0 57s 10.224.162.129 worker33.ningcode.cn
# 给每个 pod 写入自己的名字,方便 curl 验证轮询
[root@master21 ~]# for pod in $(kubectl get pods -o name | awk -F/ '{print $2}')
do
kubectl exec -it $pod -- bash -c "echo $pod > /usr/share/nginx/html/index.html"
done
[root@master21 ~]# kubectl expose deployment webapp --port 80 --target-port 80
[root@master21 ~]# kubectl get svc webapp
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
webapp ClusterIP 10.102.27.219 <none> 80/TCP 13s
# curl 30 次,三个 pod 各被访问约 10 次(Service 轮询)
[root@master21 ~]# for i in {1..30};do curl -s 10.102.27.219:80;done|sort |uniq -c
9 webapp-7fb6dc88f9-48kcx
10 webapp-7fb6dc88f9-9z87c
11 webapp-7fb6dc88f9-wtn6t
10.9 验证集群高可用(关机演练)
| 演练 | 操作 | 预期 |
|---|---|---|
| 关 HA 节点 | 关闭 ha01 | VIP 漂移到 ha02,集群访问不受影响 |
| 关 master 节点 | 关闭 master21 | 其余 master 接管,kubectl 仍可用 |
| 关 worker 节点 | 关闭 worker31 | 其上 pod 在其他 worker 重建,应用不中断 |
| 关 etcd 节点 | 关闭 etcd11 | etcd12/13 仍构成多数(2/3),集群正常 |
验证要点:etcd 容忍
(n-1)/2台故障,3 节点最多挂 1 台;控制平面、HA 入口同理,任意单点故障都不影响集群。
十一、集群升级 v1.30.2 → v1.30.4
升级实验在原有单主集群(master30 + worker31/worker32,已运行 53 天)上演示,流程同样适用于本章刚建好的 HA 集群。
学习参考:kubernetes 升级
11.1 升级必要性
- 功能性增强(新特性、新 API)。
- 漏洞修复(安全补丁)。
- 版本维护周期支持。
11.2 升级注意事项与流程
注意事项
- 若集群只有一个 master 节点,升级 master 期间整个集群不可用(控制面停止)。
- 若只有两个 worker 节点,升级第二个 worker 时,负载会在第一个 worker 上重建,应用短暂不可用,时长取决于 pod 重建时间。
升级流程
- 节点角度:先逐个升级 master,再逐个升级 worker。
- 软件角度:每个节点都按 kubeadm → 集群组件(kubeadm upgrade)→ kubelet/kubectl 的顺序升级。
11.3 升级前准备
# 确认当前版本
root@master30:~# kubectl version
Client Version: v1.30.2
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.30.2
root@master30:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master30.ningcode.cn Ready control-plane 53d v1.30.2
worker31.ningcode.cn Ready <none> 53d v1.30.2
worker32.ningcode.cn Ready <none> 53d v1.30.2
# 查看仓库可升级版本
root@master30:~# apt list kubeadm -a|grep amd64
kubeadm/unknown 1.30.4-1.1 amd64 [upgradable from: 1.30.2-1.1]
kubeadm/unknown 1.30.3-1.1 amd64
kubeadm/unknown,now 1.30.2-1.1 amd64 [installed,upgradable to: 1.30.4-1.1]
......(更早版本略)
11.4 升级 master 节点
① 升级 kubeadm
root@master30:~# apt install -y kubeadm=1.30.4-1.1
如果 kubeadm 被 hold 住了,先解除:
apt-mark unhold kubeadm。
② 查看升级计划
root@master30:~# kubeadm upgrade plan
首次执行可能遇到健康检查 Job 超时([ERROR CreateJob] ... did not complete in 15s),直接重跑一次即可(第二次仅出现 WARNING,不影响):
[preflight] Running pre-flight checks.
[upgrade/versions] Cluster version: 1.30.2
[upgrade/versions] kubeadm version: v1.30.4
[upgrade/versions] Target version: v1.30.4
Upgrade to the latest version in the v1.30 series:
COMPONENT NODE CURRENT TARGET
kube-apiserver master30.ningcode.cn v1.30.2 v1.30.4
kube-controller-manager master30.ningcode.cn v1.30.2 v1.30.4
kube-scheduler master30.ningcode.cn v1.30.2 v1.30.4
kube-proxy 1.30.2 v1.30.4
CoreDNS v1.11.1 v1.11.1
etcd master30.ningcode.cn 3.5.12-0 3.5.12-0
......(kubelet 手动升级列表略)
③ 升级控制面组件(master 首节点)
# 关键参数:--force 强制升级、避免二次确认;--etcd-upgrade 默认 true,外部 etcd 集群可设 false
root@master30:~# kubeadm upgrade apply v1.30.4 --force
执行过程要点:
[upgrade/apply] Upgrading your Static Pod-hosted control plane to version "v1.30.4" (timeout: 5m0s)...
[upgrade/staticpods] Renewing apiserver certificate
[upgrade/staticpods] Moved new manifest to "/etc/kubernetes/manifests/kube-apiserver.yaml" and backed up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-.../kube-apiserver.yaml"
[apiclient] Found 1 Pods for label selector component=kube-apiserver
[upgrade/staticpods] Component "kube-apiserver" upgraded successfully!
[upgrade/staticpods] Component "kube-controller-manager" upgraded successfully!
[upgrade/staticpods] Component "kube-scheduler" upgraded successfully!
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy
[upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.30.4". Enjoy!
kubeadm upgrade apply 完成的工作:
- 检查集群是否可升级(API 可访问、节点 Ready、控制面健康)
- 强制执行版本偏差策略;确保控制面镜像可用
- 升级控制面组件(失败自动回滚);续签相关证书
- 应用新的 CoreDNS / kube-proxy 清单,重建 RBAC 规则
- 旧清单与证书备份到
/etc/kubernetes/tmp/kubeadm-backup-manifests-<时间戳>/
④ 升级其他 master 节点(如有)
root@master30:~# kubeadm upgrade node
该命令在其他控制平面节点上:拉取集群 ClusterConfiguration → (可选)备份 apiserver 证书 → 升级控制面组件静态 Pod 清单 → 升级本节点 kubelet 配置。
⑤ 驱逐 master 节点并升级 kubelet/kubectl
# 驱逐本节点 pod,并标记不可调度
root@master30:~# kubectl drain master30.ningcode.cn --ignore-daemonsets
root@master30:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master30.ningcode.cn Ready,SchedulingDisabled control-plane 42h v1.30.2
worker31.ningcode.cn Ready <none> 42h v1.30.2
worker32.ningcode.cn Ready <none> 42h v1.30.2
# 升级 kubelet 和 kubectl,然后重启 kubelet
root@master30:~# apt install -y kubelet=1.30.4-1.1 kubectl=1.30.4-1.1
root@master30:~# systemctl restart kubelet.service
⑥ 恢复调度并验证
root@master30:~# kubectl uncordon master30.ningcode.cn
root@master30:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master30.ningcode.cn Ready control-plane 48m v1.30.4
worker31.ningcode.cn Ready <none> 37m v1.30.2
worker32.ningcode.cn Ready <none> 37m v1.30.2
11.5 升级 worker31
# ① 升级 kubeadm
root@worker31:~# apt install -y kubeadm=1.30.4-1.1
# ② 升级节点组件(从集群取回 ClusterConfiguration,升级本节点 kubelet 配置)
root@worker31:~# kubeadm upgrade node
# ③ 在 master 上驱逐 worker31
root@master30:~# kubectl drain worker31.ningcode.cn --ignore-daemonsets
root@master30:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master30.ningcode.cn Ready control-plane 50m v1.30.4
worker31.ningcode.cn Ready,SchedulingDisabled <none> 39m v1.30.2
worker32.ningcode.cn Ready <none> 39m v1.30.2
# ④ 升级 kubelet/kubectl 并重启
root@worker31:~# apt install -y kubelet=1.30.4-1.1 kubectl=1.30.4-1.1
root@worker31:~# systemctl restart kubelet.service
# ⑤ 恢复调度并验证
root@master30:~# kubectl uncordon worker31.ningcode.cn
root@master30:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master30.ningcode.cn Ready control-plane 51m v1.30.4
worker31.ningcode.cn Ready <none> 40m v1.30.4
worker32.ningcode.cn Ready <none> 40m v1.30.2
11.6 升级 worker32
与 worker31 完全相同的五步:apt install kubeadm → kubeadm upgrade node → kubectl drain → apt install kubelet/kubectl + systemctl restart kubelet → kubectl uncordon。
root@worker32:~# apt install -y kubeadm=1.30.4-1.1
root@worker32:~# kubeadm upgrade node
root@master30:~# kubectl drain worker32.ningcode.cn --ignore-daemonsets
root@worker32:~# apt install -y kubelet=1.30.4-1.1 kubectl=1.30.4-1.1
root@worker32:~# systemctl restart kubelet.service
root@master30:~# kubectl uncordon worker32.ningcode.cn
11.7 最终验证
root@master30:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master30.ningcode.cn Ready control-plane 53d v1.30.4
worker31.ningcode.cn Ready <none> 53d v1.30.4
worker32.ningcode.cn Ready <none> 53d v1.30.4
root@master30:~# kubectl version
Client Version: v1.30.4
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.30.4
11.8 从故障状态恢复
kubeadm upgrade是幂等的:若升级失败且未自动回滚(如升级期间节点意外关机),直接再次运行即可。- 也可不改版本重跑:
kubeadm upgrade apply --force,最终确保实际状态与期望状态一致。 - 升级期间 kubeadm 会把备份写入
/etc/kubernetes/tmp/:kubeadm-backup-etcd-<date>-<time>:本地 etcd 成员数据备份;外部 etcd 时该目录为空。etcd 升级失败且无法自动回滚时,把目录内容拷回/var/lib/etcd手工修复。kubeadm-backup-manifests-<date>-<time>:控制面 static pod 清单备份;组件清单未变化时不会生成对应备份。升级失败无法自动回滚时,拷回/etc/kubernetes/manifests/手工恢复。
十二、常见错误与排查(重点)
以下全部为 8/17 实验中的真实报错,按"现象 → 原因 → 修复"整理,实验前先扫一遍可少走弯路。
| # | 现象 | 原因 | 修复 |
|---|---|---|---|
| 1 | Failed to start etcd.service: Unit etcd.service not found | 未安装 etcd 软件包 | apt install -y etcd-server etcd-client |
| 2 | listen tcp 10.1.8.10:2379: bind: cannot assign requested address | /etc/default/etcd 里监听的 IP 不是本机 IP(etcd1 残留了单机 etcd 的 10.1.8.10 配置) | 改成各节点自身 IP,systemctl restart etcd;用 ip -br a 先确认本机 IP |
| 3 | Error: unknown command "edit" for "etcdctl" | etcdctl 没有 edit 子命令(误以为有) | 修改数据用 etcdctl put <key> <value> |
| 4 | Tab 补全时报 _get_comp_words_by_ref: command not found | 缺 bash-completion 软件包 | apt install -y bash-completion,重开终端再 source |
| 5 | bash: cd: /etc/bash_completion: Not a directory | 路径写错:补全目录是 /etc/bash_completion.d/ | 用 cd /etc/bash_completion.d/(.d 别丢) |
| 6 | Job for etcd.service failed because the control process exited with error code | etcd 启动失败(数据目录损坏/配置错误/端口占用等) | journalctl -u etcd.service -e 看具体报错;常见为 2 号错误或数据目录冲突 |
| 7 | -bash: etcdutl: command not found | Ubuntu 的 etcd 3.4 没有 etcdutl 工具(3.5+ 才有) | 成员管理用 etcdctl member remove <id> |
| 8 | Error: context deadline exceeded(member add 时) | etcd 端不可达/集群未就绪(网络、/etc/hosts、对端没起来) | 先 etcdctl member list + ping 确认 3 节点互通,再执行 add |
| 9 | scp root@10.1.8.12 root@10.1.8.13 /etc/hosts → /etc/hosts: Not a directory | scp 远程主机忘了写冒号 : | 正确写法:scp root@10.1.8.12:/etc/hosts /etc/hosts(远程路径带 host:) |
| 10 | etcdctl snapshot restore ... Error: open xxx.db: no such file or directory | 当前目录/文件名不对,快照文件不存在 | 用绝对路径 snapshot restore /root/snapshot1.db,先 ls 确认文件在 |
| 11 | Error: data-dir "/var/lib/etcd" exists | snapshot restore 的目标目录必须为空 | rm -rf /var/lib/etcd(或换新目录)后再 restore |
| 12 | /usr/local/bin/etcdget: Permission denied | 自写脚本没有执行权限(还出现过把 chmod 打成 chemod 的笔误) | chmod +x /usr/local/bin/etcdget |
| 13 | `etcdget … | strings: command not found` | strings 命令属于 binutils 包,未安装 |
| 14 | systemctl del /users/user2/name → 命令不存在 | 误用 systemctl 删除数据(systemctl 只管理服务) | 删除数据用 etcdctl del /users/user2/name |
| 15 | kubeadm upgrade plan → [ERROR CreateJob] Job ... did not complete in 15s | 升级健康检查 Job 超时(偶发) | 直接重跑 kubeadm upgrade plan,第二次仅 WARNING 可继续 |
| 16 | -bash: daf: command not found / -bash: quit: command not found | 手滑笔误(本想敲其他命令) | 命令回显检查一下再回车,习惯用 Tab 补全 |
排查口诀:先看报错原文 →
journalctl/-v看细节 → 检查 IP、hosts、路径、权限 → 再重试。
十三、命令速查表与小结
13.1 etcd 集群速查
| 操作 | 命令 |
|---|---|
| 安装 | apt install -y etcd-server etcd-client |
| 启停 | systemctl enable --now etcd / systemctl restart etcd |
| 查看成员 | etcdctl member list |
| 添加成员 | etcdctl member add <name> --peer-urls=http://<IP>:2380 |
| 删除成员 | etcdctl member remove <member-id> |
| 写/读/删 | etcdctl put <key> <value> / etcdctl get <key> --prefix / etcdctl del <key> |
| 备份快照 | etcdctl snapshot save snapshot.db |
| 恢复快照 | etcdctl snapshot restore snapshot.db --data-dir=/var/lib/etcd/default --name=<host> --initial-cluster=... --initial-advertise-peer-urls=... |
| 命令补全 | etcdctl completion bash > /etc/bash_completion.d/etcdctl && source /etc/bash_completion.d/etcdctl |
13.2 Keepalived + Haproxy 速查
| 操作 | 命令 |
|---|---|
| 安装 | apt install -y keepalived haproxy |
| 配置文件 | /etc/keepalived/keepalived.conf、/etc/haproxy/haproxy.cfg |
| 启停 | systemctl enable --now keepalived.service haproxy |
| 看 VIP 落在哪 | ip -br a(含 10.1.8.100/24 即主节点) |
| 监控面板 | http://<ha节点IP>:1080/haproxy-stats(admin:123456) |
| 看后端状态 | echo "show servers state k8s" | socat stdio /run/haproxy/admin.sock(可选) |
13.3 kubeadm 集群速查
| 操作 | 命令 |
|---|---|
| 生成默认配置 | kubeadm config print init-defaults | tee kubeadm-config.yaml |
| 初始化 | kubeadm init --config kubeadm-config.yaml |
| 控制面加入 | kubeadm join 10.1.8.100:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash> --control-plane |
| 工作节点加入 | 同上,去掉 --control-plane |
| 重新生成 join 命令 | kubeadm token create --print-join-command |
| 配置 kubectl | mkdir -p $HOME/.kube && cp -i /etc/kubernetes/admin.conf $HOME/.kube/config && chown $(id -u):$(id -g) $HOME/.kube/config |
| 部署网络 | wget .../calico.yaml(改 CIDR)→ nerdctl pull 3 个镜像 → kubectl apply -f calico.yaml |
| 查看节点/pod | kubectl get nodes / kubectl get pods -n kube-system -o wide |
13.4 集群升级速查
| 操作 | 命令 |
|---|---|
| 升级 kubeadm | apt install -y kubeadm=1.30.4-1.1(被 hold 先 apt-mark unhold kubeadm) |
| 升级计划 | kubeadm upgrade plan |
| 升级控制面(首台 master) | kubeadm upgrade apply v1.30.4 --force |
| 升级其他节点 | kubeadm upgrade node |
| 驱逐/恢复调度 | kubectl drain <node> --ignore-daemonsets / kubectl uncordon <node> |
| 升级 kubelet/kubectl | apt install -y kubelet=1.30.4-1.1 kubectl=1.30.4-1.1 && systemctl restart kubelet |
| 故障恢复 | 重跑 kubeadm upgrade apply --force;备份在 /etc/kubernetes/tmp/kubeadm-backup-* |
13.5 小结
- etcd 是 K8s 的"真相来源":数据全部存
/registry下,日常维护 = 备份快照 + 会恢复;3 节点集群容忍 1 台故障,cp -a目录级备份适合演练,生产建议snapshot save。 - HA 三件套分工明确:Keepalived 出 VIP(主备 + nopreempt)、Haproxy 做 6443 四层轮询、kubeadm 用
controlPlaneEndpoint指到 VIP,etcd 独立成外部集群。 - 升级顺序不可乱:master 逐个升(
upgrade apply→ 其他upgrade node→ drain → kubelet/kubectl → uncordon),worker 再逐个升;先 kubeadm、再组件、最后 kubelet/kubectl。
更多推荐
所有评论(0)