跳到主要内容

集群安装与生命周期管理

集群不是装完就万事大吉的,它有一整个生命周期:安装、升级、扩缩容、证书续期、备份恢复、节点维护,最后到退役。这篇笔记按顺序记录两件事:怎么把集群装起来(kubeadm 为主,二进制部署讲思路),以及装完之后怎么把它养活(升级、证书、etcd 备份、扩缩容、维护窗口)。

示例环境:Ubuntu 22.04 + containerd 1.7 + Kubernetes v1.30,三台虚拟机。命令以 Debian/Ubuntu 系为准,CentOS/Rocky 系把 apt 换成 dnf、包名基本一样,差异处会单独说明。

版本只是示例

文中所有版本号(v1.30.4、Calico v3.28 等)都是写笔记时的稳定版示例,实际操作时替换成你要装的版本即可,流程完全一样。

kubeadm​

kubeadm 是官方出的集群引导工具,定位是「把搭集群这件事标准化」:生成证书、写各组件的 kubeconfig、把控制平面组件以静态 Pod 的形式跑起来、发放入集群的 token。它不装容器运行时、不装 CNI 网络插件,这两块要自己准备。

一条 kubeadm init 实际做了这些事:

  • 在 /etc/kubernetes/pki/ 下生成整套 CA 和组件证书
  • 在 /etc/kubernetes/ 下生成 admin.conf、kubelet.conf 等 kubeconfig 文件
  • 在 /etc/kubernetes/manifests/ 下写静态 Pod 清单,kubelet 据此拉起 etcd、apiserver、controller-manager、scheduler
  • 创建 bootstrap token 和集群配置 ConfigMap,供后续节点 join 使用

下面是从零到可用的完整流程。

节点规划​

主机名IP角色最低配置
k8s-master172.16.1.10control plane2C4G
k8s-node1172.16.1.11worker2C4G
k8s-node2172.16.1.12worker2C4G
网段提前规划好

Pod 网段(示例用 192.168.0.0/16)和 Service 网段(默认 10.96.0.0/12)不能和宿主机网段、公司内网网段冲突,否则路由会乱,问题非常难排查。示例里宿主机用 172.16.1.0/24,和两个网段都错开了;如果你的内网恰好占了 192.168.0.0/16,就把 Pod 网段换成 10.244.0.0/16 之类。

第一步:系统准备(所有节点)​

设置主机名和 hosts 解析:

# 各节点分别设置自己的主机名,以 master 为例
sudo hostnamectl set-hostname k8s-master

# 所有节点都写入同样的解析记录
cat <<EOF | sudo tee -a /etc/hosts
172.16.1.10 k8s-master
172.16.1.11 k8s-node1
172.16.1.12 k8s-node2
EOF

关闭 swap(kubelet 默认拒绝在开启 swap 的机器上工作):

# 立即关闭
sudo swapoff -a

# 永久关闭:注释掉 fstab 里的 swap 行,防止重启后恢复
sudo sed -i '/ swap / s/^/#/' /etc/fstab

加载内核模块,开启 iptables 桥接和转发:

# 开机自动加载 overlay 和 br_netfilter 模块
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

# 立即加载
sudo modprobe overlay
sudo modprobe br_netfilter

# 内核参数:让 iptables 能看到经过网桥的流量,开启转发
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF

# 生效
sudo sysctl --system

另外三件小事:

  • 软件源:把 Ubuntu 基础 apt 源换成阿里云,后面装前置包、打补丁都快得多:
# 备份后一键替换(22.04 是 jammy,其他版本对应更换代号)
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
sudo sed -i 's|http://archive.ubuntu.com|https://mirrors.aliyun.com|g; s|http://security.ubuntu.com|https://mirrors.aliyun.com|g' /etc/apt/sources.list
sudo apt-get update
  • 时间同步:集群各节点时间必须一致(证书校验、etcd 都依赖时间),用 timedatectl 确认 NTP 已开启,或装 chronyd。
  • 防火墙:学习环境直接 sudo ufw disable(CentOS 是 sudo systemctl disable --now firewalld)省事;生产环境按端口放行,控制平面需要 6443、2379-2380、10250、10257、10259,worker 需要 10250 和 NodePort 范围 30000-32767。

第二步:安装容器运行时 containerd(所有节点)​

kubeadm 只认 CRI 接口的运行时,现在主流选择就是 containerd。Ubuntu 上最省事的方式是添加 apt 源装 containerd.io,国内直接用阿里云的 Docker CE 镜像源:

# 添加阿里云 Docker CE 镜像源(只为装 containerd.io,不装 Docker 本体)
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt-get update
sudo apt-get install -y containerd.io

装完必须改两处配置(原因见本文「containerd」一节):

# 生成默认配置
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

# 1. 打开 SystemdCgroup,和 kubelet 的 cgroup 驱动保持一致
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

# 2. pause 沙箱镜像换成国内可拉取的地址
sudo sed -i 's|sandbox_image = ".*"|sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"|' /etc/containerd/config.toml

sudo systemctl restart containerd
sudo systemctl enable containerd
pause 镜像版本以 kubeadm 为准

pause 版本随 Kubernetes 版本变化(v1.30 对应 3.9,v1.31 起是 3.10)。装好 kubeadm 后可以执行 sudo kubeadm config images list 查看这个版本期望的完整镜像清单,containerd 里的 sandbox_image 保持和清单里的 pause 一致,避免 init 预拉取一套、运行时又拉另一套。

第三步:安装 kubeadm、kubelet、kubectl(所有节点)​

Kubernetes 官方 apt 源(pkgs.k8s.io)国内拉不动,直接用阿里云的镜像仓库。K8s 的 apt 源按 minor 版本分仓库,这里以 v1.30 为例:

curl -fsSL https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.30/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.30/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl

# 锁定版本,防止 apt upgrade 顺手把集群组件升了
sudo apt-mark hold kubelet kubeadm kubectl

在海外机器或有代理的环境,把上面两条里的地址换成官方源 https://pkgs.k8s.io/core:/stable:/v1.30/deb/ 即可,其余命令不变。

三个组件的分工
  • kubelet:每个节点都要跑,真正干活的,负责拉起 Pod。
  • kubeadm:只在装集群、升集群、加节点时用,平时不运行。
  • kubectl:命令行客户端,只需要装在你要执行命令的机器上(一般就是 master)。

第四步:初始化控制平面(只在 master 执行)​

sudo kubeadm init \
--apiserver-advertise-address=172.16.1.10 \
--pod-network-cidr=192.168.0.0/16 \
--service-cidr=10.96.0.0/12 \
--kubernetes-version=v1.30.4 \
--image-repository=registry.aliyuncs.com/google_containers \
--cri-socket=unix:///run/containerd/containerd.sock

几个关键参数:

参数作用
apiserver-advertise-addressapiserver 对外宣告的地址,填 master 本机 IP
pod-network-cidrPod 网段,要和后面装的 CNI 插件默认值一致(Calico 默认 192.168.0.0/16,Flannel 默认 10.244.0.0/16)
service-cidrService 网段,默认 10.96.0.0/12,一般不用改
kubernetes-version明确指定版本,避免拉错
image-repository镜像仓库,国内换成阿里云镜像,不然拉不动 registry.k8s.io
cri-socket告诉 kubeadm 容器运行时的 socket 地址,用 containerd 就要显式指定

初始化成功会输出类似内容:

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 172.16.1.10:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:3b5a5c...(省略)
保存好这段输出

最后那行 join 命令是 worker 节点入集群的凭证,复制保存。token 有效期 24 小时,过期了也没关系,用 kubeadm token create --print-join-command 重新生成(见「节点扩容」一节)。

如果 init 失败或想重来,先重置再执行:

sudo kubeadm reset --cri-socket unix:///run/containerd/containerd.sock
sudo rm -rf /etc/cni/net.d $HOME/.kube

第五步:配置 kubectl 凭证(在 master 执行)​

init 的输出里已经给了这三条命令,照抄即可:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

验证:

kubectl get nodes
NAME         STATUS     ROLES           AGE   VERSION
k8s-master NotReady control-plane 2m v1.30.4

此时节点是 NotReady,因为还没装网络插件,属于正常现象。

第六步:安装 CNI 网络插件(在 master 执行)​

没有 CNI,Pod 之间不通,节点也不会 Ready。Calico 和 Flannel 二选一即可。

Calico(推荐,功能全,支持网络策略):

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.1/manifests/tigera-operator.yaml
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.1/manifests/custom-resources.yaml

Calico 默认 Pod 网段就是 192.168.0.0/16,和 init 时的 --pod-network-cidr 一致,不用改。国内拉不动 raw.githubusercontent.com 的话,先在本机把两个 yaml 下载下来,scp 上去再 kubectl create -f。

Flannel(简单够用,只提供基础网络):

kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

注意 Flannel 默认网段是 10.244.0.0/16,如果用它,第四步 init 时的 --pod-network-cidr 就要写成 10.244.0.0/16。

装完观察插件 Pod 起来没有:

kubectl get pods -n kube-system
kubectl get nodes

等所有系统 Pod Running,节点状态变成 Ready:

NAME         STATUS   ROLES           AGE   VERSION
k8s-master Ready control-plane 8m v1.30.4

第七步:worker 节点加入集群(在各 worker 执行)​

在两个 worker 上执行 init 输出里保存的 join 命令,记得补 cri-socket:

sudo kubeadm join 172.16.1.10:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:3b5a5c...(省略) \
--cri-socket=unix:///run/containerd/containerd.sock

回 master 验证:

kubectl get nodes -o wide
NAME         STATUS   ROLES           AGE   VERSION   INTERNAL-IP    CONTAINER-RUNTIME
k8s-master Ready control-plane 20m v1.30.4 172.16.1.10 containerd://1.7.20
k8s-node1 Ready <none> 10m v1.30.4 172.16.1.11 containerd://1.7.20
k8s-node2 Ready <none> 10m v1.30.4 172.16.1.12 containerd://1.7.20

三个节点全部 Ready,单控制平面集群搭建完成。跑个 nginx 冒烟测试一下:

kubectl create deployment nginx --image=nginx:1.27 --replicas=2
kubectl expose deployment nginx --port=80 --type=NodePort
kubectl get svc nginx

二进制部署​

二进制部署是指不走 kubeadm,自己下载每个组件的二进制文件,自己签证书,自己写 systemd 服务,一个组件一个组件地启动集群。Kubernetes 早期的「Kubernetes the Hard Way」就是这条路。

和 kubeadm 的区别​

对比项kubeadm二进制部署
自动化程度一条 init 命令搞定证书、配置、启动全部手工,几十步
证书管理自动签发、可一键续期用 cfssl/openssl 自己签,续期也要自己来
升级kubeadm upgrade 标准化流程逐组件替换二进制、改配置
定制能力有边界,受 kubeadm 约定限制完全自由,参数、路径、CA 策略都能定
学习价值知其然知其所以然,每个组件怎么连的都清清楚楚
出问题概率低,流程经过大量验证高,证书 CN/SAN 错一点就连不通

什么时候用二进制​

  • 学习目的:想彻底搞懂 apiserver 怎么连 etcd、kubelet 怎么向 apiserver 注册,手搓一遍比看十篇文档有用。
  • 生产定制:要对接公司已有的 PKI/CA 体系、要放在离线环境里逐包审计、组件参数有特殊要求,kubeadm 的约定满足不了。

日常生产和测试环境用 kubeadm 就够了,没必要为装而装。

需要部署的组件清单​

类别组件说明
证书CA、etcd CA、front-proxy CA、sa 密钥对先签 CA,再逐组件签服务端/客户端证书,CN 和 SAN 必须按组件身份填对
etcdetcd独立集群,3 节点,存储全部集群数据
控制平面kube-apiserver集群入口,指向 etcd,带一大堆证书和准入参数
控制平面kube-controller-manager各类控制器
控制平面kube-scheduler调度器
节点kubelet每个节点都要,配 bootstrap kubeconfig 实现证书自动签发
节点kube-proxy每个节点都要,负责 Service 转发规则
运行时containerd同 kubeadm 方案
插件CNI、CoreDNS网络插件和集群 DNS,以 yaml 形式部署

大致顺序:签证书 → 起 etcd 集群 → 起 apiserver → 起 controller-manager 和 scheduler → 各节点起 kubelet、kube-proxy → kubectl certificate approve 审批 kubelet 的证书请求 → 部署 CNI 和 CoreDNS。

真要自动化可以考虑这些

kubeasz(Ansible 剧本)、Kubespray、Sealos 都是二进制思路的自动化封装,适合想绕开 kubeadm 又不想纯手工的场景。

高可用架构​

单控制平面集群里 master 一挂,虽然已有业务 Pod 还在跑,但整个集群失去管理能力:不能调度、不能扩缩、kubectl 全部失灵。生产环境必须做高可用(HA)。

两种 etcd 拓扑​

Stacked etcd(堆叠式):etcd 和控制平面组件跑在同一批节点上,kubeadm 默认就是这种方式。

堆叠 etcd 高可用拓扑

External etcd(外置式):etcd 单独一组节点,控制平面节点只跑 K8s 组件。

外置 etcd 高可用拓扑

对比项Stacked etcdExternal etcd
机器数量3 台搞定至少 6 台(3 控制平面 + 3 etcd)
故障影响挂一台 master 同时损失一个控制平面和一个 etcd 成员故障域隔离,互不影响
运维复杂度低,kubeadm 原生支持高,etcd 集群要单独部署维护
适用场景中小规模集群大规模、对稳定性要求高的集群

APIServer 前面挂负载均衡​

多个 apiserver 是对等的,但需要给集群一个统一入口,这个入口还不能是单点。经典方案是 haproxy + keepalived:

  • haproxy 装在每个 master 上,监听一个端口(如 8443),把流量轮询转发到三台 apiserver 的 6443,带健康检查,自动剔除挂掉的 apiserver。
  • keepalived 负责维护一个虚拟 IP(VIP,如 172.16.1.100),通过 VRRP 在 master 节点间漂移;挂一台,VIP 漂到另一台,入口不中断。

haproxy 配置片段:

frontend k8s-apiserver
bind *:8443
mode tcp
default_backend k8s-masters

backend k8s-masters
mode tcp
balance roundrobin
option tcp-check
server master1 172.16.1.10:6443 check fall 3 rise 2
server master2 172.16.1.20:6443 check fall 3 rise 2
server master3 172.16.1.30:6443 check fall 3 rise 2

keepalived 关键配置(主节点):

vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
virtual_ipaddress {
172.16.1.100/24
}
}

LB 就绪后,init 时用 --control-plane-endpoint 指向 VIP,加 --upload-certs 把控制平面证书上传到集群(有效期 2 小时),供后续 master 加入时下载:

# 第一台 master
sudo kubeadm init \
--control-plane-endpoint="172.16.1.100:8443" \
--upload-certs \
--pod-network-cidr=192.168.0.0/16 \
--cri-socket=unix:///run/containerd/containerd.sock

# 第二、三台 master 用带 --control-plane 的 join 命令加入
sudo kubeadm join 172.16.1.100:8443 --token xxx \
--discovery-token-ca-cert-hash sha256:xxx \
--control-plane --certificate-key xxx \
--cri-socket=unix:///run/containerd/containerd.sock

之后所有组件(kubelet、kubectl、worker 节点)都只认 VIP,不再直接连任何一台 master。

云环境可以更简单

云厂商有现成的负载均衡(SLB/CLB),直接买一个 TCP 四层 LB 指向三台 master 的 6443,就不需要自己搞 keepalived 了。

为什么是 3 节点起步​

高可用的数学基础是 etcd 的 Raft 协议:写入必须得到**多数派(quorum)**确认,法定人数为 (n/2)+1。

etcd 节点数法定人数可容忍故障数
110
220
321
431
532
642
743

看点在两个地方:

  • 2 节点没有任何意义:quorum 是 2,挂一台就失去多数派,和单点一样脆弱。
  • 偶数节点浪费:4 节点和 3 节点一样只能容忍 1 台故障,却多维护一台机器,还多一份网络分区的概率。

所以控制平面/etcd 永远用奇数:3 台起步(容忍 1 台故障),规模大、要求高再上 5 台(容忍 2 台)。一般不超过 7 台,节点太多 Raft 协商本身的开销会拖慢写入。

containerd​

containerd 是容器运行时,kubelet 通过 CRI(Container Runtime Interface)gRPC 接口调用它来拉镜像、起容器。装集群时真正需要操心的就是它的配置文件 /etc/containerd/config.toml,有三个要点。

要点一:SystemdCgroup 必须为 true​

kubeadm 部署的集群里,kubelet 默认用 systemd 作为 cgroup 驱动;如果 containerd 还在用默认的 cgroupfs,两套 cgroup 管理混在一起,轻则 kubelet 起不来报驱动不一致,重则节点莫名其妙 NotReady。kubeadm init 的预检会直接拦下这个错误。

默认生成的配置里这一项是 false,必须改:

containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd

对应配置位置:

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true

要点二:pause 沙箱镜像替换​

每个 Pod 的第一个容器其实是 pause(沙箱容器),负责持有 Pod 的网络命名空间。默认地址 registry.k8s.io/pause:3.9 国内拉不到,改成阿里云镜像:

[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"

版本号用 sudo kubeadm config images list 查当前 Kubernetes 版本对应的 pause 版本,两者保持一致。

要点三:镜像加速与私有仓库​

containerd 1.5 之后推荐用 config_path 目录方式配置镜像加速,每个仓库一个目录:

[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d"

比如给 Docker Hub 配加速,创建 /etc/containerd/certs.d/docker.io/hosts.toml:

server = "https://docker.io"

[host."https://docker.m.daocloud.io"]
capabilities = ["pull", "resolve"]

私有仓库的自签名证书、HTTP 明文仓库、认证信息,同样在这个目录下按仓库名建目录配置。改完 sudo systemctl restart containerd 生效。

旧写法也能用

老的 registry.mirrors 表写法(直接在 config.toml 里写 mirrors 列表)目前仍兼容,但官方推荐迁移到 certs.d 目录方式,新环境直接用新方式。

日常调试用 crictl​

集群里没有 Docker CLI,排查节点上的容器用 crictl。先写个配置省得每次敲 endpoint:

# /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10

常用命令:

crictl ps                 # 看运行中的容器
crictl ps -a # 包括已退出的
crictl images # 看镜像
crictl logs <容器ID> # 看容器日志
crictl inspect <容器ID> # 看容器详情
crictl rmi --prune # 清理无用镜像
想要 docker 手感就装 nerdctl

nerdctl 是 containerd 的 Docker 风格 CLI,命令和 docker 几乎一样(nerdctl ps、nerdctl run),运维习惯 docker 的话可以装一个。

RuntimeClass​

一个集群里可以共存多种容器运行时,RuntimeClass 就是用来选「这个 Pod 用哪种运行时跑」的机制。最典型的场景:默认用 runc(普通容器),对安全性要求高的多租户业务用 gVisor(runsc,用户态内核拦截系统调用)或 Kata Containers(轻量虚拟机)。

工作流程​

  1. 在节点上装好备选运行时(比如 gVisor 的 runsc)。
  2. 在 containerd 的 config.toml 里注册这个运行时:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"
  1. 创建 RuntimeClass 资源,handler 就是上面注册的运行时名字:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
  1. Pod 里通过 runtimeClassName 引用:
apiVersion: v1
kind: Pod
metadata:
name: nginx-gvisor
spec:
runtimeClassName: gvisor
containers:
- name: nginx
image: nginx:1.27

不写 runtimeClassName 的 Pod 走集群默认运行时(runc),互不影响。

备选运行时要装到节点上

只有部分节点装了 gVisor 时,可以在 RuntimeClass 里加 scheduling.nodeSelector 限制这类 Pod 只调度到装了 runsc 的节点,避免调度到没装的节点上起不来。

集群升级​

Kubernetes 每 4 个月左右发一个 minor 版本,每个版本维护约 14 个月,不及时升级就会失去补丁支持。kubeadm 集群的升级有固定套路。

版本偏差策略(升级前必看)​

  • 不能跨 minor 版本升级:1.29 → 1.30 → 1.31 一个一个来,不允许 1.29 直接跳 1.31。
  • 先升控制平面,再升工作节点:kubelet 可以比 apiserver 低最多 3 个 minor 版本,但反过来不行。
  • kubectl 和 apiserver 相差 1 个 minor 以内。
升级前两件事
  1. 备份 etcd(见下文「ETCD 备份恢复」),升级翻车还能回滚。
  2. 查插件兼容性:CNI(Calico/Flannel)、CoreDNS、ingress-controller 对新版本的支持矩阵,必要时先升插件。

升级控制平面(以 1.29.6 → 1.30.4 为例)​

# 1. 解锁并升级 kubeadm(先升它,用新版 kubeadm 来执行升级)
sudo apt-mark unhold kubeadm
sudo apt-get update && sudo apt-get install -y kubeadm=1.30.4-1.1
sudo apt-mark hold kubeadm

# 2. 查看升级计划,确认目标版本和要做的动作
sudo kubeadm upgrade plan

# 3. 执行升级(拉新镜像、滚动重启静态 Pod)
sudo kubeadm upgrade apply v1.30.4

# 4. 腾空节点,准备升级 kubelet
kubectl drain k8s-master --ignore-daemonsets

# 5. 升级 kubelet 和 kubectl
sudo apt-mark unhold kubelet kubectl
sudo apt-get install -y kubelet=1.30.4-1.1 kubectl=1.30.4-1.1
sudo apt-mark hold kubelet kubectl

# 6. 重启 kubelet,恢复调度
sudo systemctl daemon-reload
sudo systemctl restart kubelet
kubectl uncordon k8s-master

多控制平面集群里,后续 master 用 sudo kubeadm upgrade node(不是 apply)走第 2、3 步,其余相同。

升级工作节点​

# 在 master 上:腾空节点,把业务 Pod 赶走
kubectl drain k8s-node1 --ignore-daemonsets --delete-emptydir-data

# 在被升级的节点上:
sudo apt-mark unhold kubeadm && sudo apt-get install -y kubeadm=1.30.4-1.1 && sudo apt-mark hold kubeadm
sudo kubeadm upgrade node
sudo apt-mark unhold kubelet kubectl && sudo apt-get install -y kubelet=1.30.4-1.1 kubectl=1.30.4-1.1 && sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload && sudo systemctl restart kubelet

# 在 master 上:恢复调度
kubectl uncordon k8s-node1

一台一台滚动升,升一台验证一台,业务靠副本和 PDB 保证不中断。

kubectl get nodes    # 确认所有节点 VERSION 都已到 v1.30.4

证书更新​

kubeadm 签的组件证书(apiserver、etcd、front-proxy 等)有效期只有一年(根 CA 是十年)。到期不续,apiserver 直接不可用,kubectl 报证书过期错误,集群瘫痪——这是 kubeadm 集群最经典的「一年之痒」。

检查过期时间​

sudo kubeadm certs check-expiration
CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf Jun 10, 2026 09:12 UTC 364d no
apiserver Jun 10, 2026 09:12 UTC 364d ca no
apiserver-etcd-client Jun 10, 2026 09:12 UTC 364d etcd-ca no
apiserver-kubelet-client Jun 10, 2026 09:12 UTC 364d ca no
...
CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
ca Jun 08, 2035 09:12 UTC 9y no
etcd-ca Jun 08, 2035 09:12 UTC 9y no
front-proxy-ca Jun 08, 2035 09:12 UTC 9y no

建议把这条命令放进季度巡检,剩余时间少于 90 天就安排续期。

一键续期全部证书​

sudo kubeadm certs renew all

续期只更新 /etc/kubernetes/pki/ 下的证书文件和 admin.conf 等 kubeconfig 里的证书,正在运行的控制平面组件不会自动加载新证书,必须重启静态 Pod 才生效:

# 方法一(优雅):停掉静态 Pod 的容器,kubelet 会自动用新证书重建
sudo crictl ps | grep -E 'kube-apiserver|kube-controller-manager|kube-scheduler|etcd'
sudo crictl stop <容器ID> # 逐个停,kubelet 会自动拉起

# 方法二(简单粗暴):维护窗口内直接重启节点
sudo reboot

重启后再 check-expiration 确认 RESIDUAL TIME 都回到了 364 天左右,并验证 kubectl 可用。

外部 CA 签的证书续不了

kubeadm certs renew 只对 kubeadm 自己管理的 CA 签发的证书有效(check-expiration 输出里 EXTERNALLY MANAGED 为 no 的那些)。如果证书是公司 CA 体系签的(yes),续期要走对应的 CSR 流程。

ETCD 备份恢复​

集群的全部状态(所有资源对象、配置、密钥)都在 etcd 里,节点可以重装,etcd 数据丢了集群就没了。备份 etcd 是集群运维的第一要务。

备份命令​

etcd 在 kubeadm 集群里是静态 Pod,宿主机上不一定有 etcdctl。两种方式任选:

# 方式一:宿主机装了 etcdctl
sudo ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-snapshot-$(date +%Y%m%d%H%M).db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key

# 方式二:没有 etcdctl,直接 exec 进 etcd 容器用里面的
kubectl exec -n kube-system etcd-k8s-master -- etcdctl snapshot save /tmp/snapshot.db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key

四个参数的含义:endpoints 指定连哪个 etcd;cacert 验证服务端身份;cert 和 key 是客户端证书,证明自己有权限操作。kubeadm 集群这三样都在 /etc/kubernetes/pki/etcd/ 下,直接引用即可。

验证快照完好:

sudo ETCDCTL_API=3 etcdctl --write-out=table snapshot status /backup/etcd-snapshot-202610231000.db
+----------+----------+------------+------------+
| HASH | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| a1b2c3d4 | 98765 | 1234 | 8.2 MB |
+----------+----------+------------+------------+

定时备份​

快照备份是在线的,不影响集群运行,放心加 crontab。建议每天一次,保留最近 7 到 14 天,并同步一份到集群外的存储(对象存储、另一台机器),防止 master 磁盘整个挂掉连备份一起丢。

# crontab -e,每天凌晨 2 点备份,顺便清理 14 天前的旧备份
0 2 * * * ETCDCTL_API=3 /usr/bin/etcdctl snapshot save /backup/etcd-$(date +\%Y\%m\%d).db --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key
15 2 * * * find /backup -name "etcd-*.db" -mtime +14 -delete
别忘了证书

快照只含 etcd 数据。恢复集群还需要 /etc/kubernetes/pki/ 下的 CA 证书,建议把这个目录也一起备份(tar 打包即可,注意权限保密)。

恢复(单控制平面示例)​

恢复是把快照数据灌回一个全新的 etcd 数据目录。期间 apiserver 必须停掉:

# 1. 移走静态 Pod 清单,kubelet 会停掉 apiserver 和 etcd
sudo mv /etc/kubernetes/manifests /etc/kubernetes/manifests.bak

# 2. 从快照恢复出新数据目录
sudo ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-snapshot-202610231000.db \
--data-dir=/var/lib/etcd-restore

# 3. 替换 etcd 数据目录(旧目录先留档)
sudo mv /var/lib/etcd /var/lib/etcd.bak
sudo mv /var/lib/etcd-restore /var/lib/etcd

# 4. 恢复清单,kubelet 拉起 etcd 和 apiserver
sudo mv /etc/kubernetes/manifests.bak /etc/kubernetes/manifests

# 5. 等一两分钟后验证
kubectl get nodes
多节点 etcd 集群要一起恢复

stacked HA 集群里,三个 etcd 成员必须用同一个快照恢复,并且 restore 时要带 --name、--initial-cluster、--initial-advertise-peer-urls 等参数重建集群成员关系。只恢复一个成员、其余成员还带着旧数据,集群会脑裂。多成员恢复流程较长,动手前先读官方文档「Restoring an etcd cluster」。

节点扩容​

业务量涨了,加一台 worker 是常规操作。流程就是「装环境 + join」:

# 1. 新节点(k8s-node3,172.16.1.13)按「kubeadm」一节的前三步准备好:
# 系统准备 → 装 containerd(记得改 SystemdCgroup 和 sandbox_image)→ 装 kubelet/kubeadm
# 版本和集群保持一致

# 2. 在 master 上重新生成 join 命令(token 有效期 24 小时,过期重新生成)
kubeadm token create --print-join-command
kubeadm join 172.16.1.10:6443 --token 7f3a2b.c9d8e1f0a1b2c3d4 --discovery-token-ca-cert-hash sha256:3b5a5c...(省略)
# 3. 在新节点上执行(补上 cri-socket)
sudo kubeadm join 172.16.1.10:6443 --token 7f3a2b.c9d8e1f0a1b2c3d4 \
--discovery-token-ca-cert-hash sha256:3b5a5c...(省略) \
--cri-socket=unix:///run/containerd/containerd.sock

# 4. 回 master 验证
kubectl get nodes
# 可选:给节点打个 worker 标签,让 ROLES 列好看点
kubectl label node k8s-node3 node-role.kubernetes.io/worker=

HA 集群里加控制平面节点,join 时加 --control-plane --certificate-key,证书 key 过期的话先在 master 上用 sudo kubeadm init phase upload-certs --upload-certs 重新上传生成。

节点缩容​

缩容(下线节点)比扩容讲究,绝不能直接关机释放,否则节点一直挂在 NotReady,被驱逐的 Pod 没人重建,控制平面节点还会拖垮 etcd quorum。标准流程是「先 drain,再 delete」:

# 1. 驱逐节点上的业务 Pod(Pod 会被调度到其他节点重建)
kubectl drain k8s-node2 --ignore-daemonsets --delete-emptydir-data

# 2. 从集群中删除节点记录
kubectl delete node k8s-node2

# 3. 在被下线的节点上清理现场,归还一台干净的机器
sudo kubeadm reset --cri-socket unix:///run/containerd/containerd.sock
sudo rm -rf /etc/cni/net.d $HOME/.kube

两个 drain 参数的含义:

参数作用
--ignore-daemonsetsDaemonSet 的 Pod(calico-node、kube-proxy 等)驱逐不掉,只能忽略
--delete-emptydir-data连使用 emptyDir 的 Pod 一起删(emptyDir 数据随 Pod 删除丢失,确认能接受再用)

旧版本这个参数叫 --delete-local-data,1.20 之后改名。

下线的是控制平面节点时先摘 etcd 成员

如果是 stacked etcd 的 HA 集群要下线一台 master,必须先从 etcd 集群里移除它的成员身份,否则剩下的 etcd 会一直等它投票:

kubectl exec -n kube-system etcd-k8s-master -- etcdctl member list \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key

# 找到待下线节点的 member ID,然后
kubectl exec -n kube-system etcd-k8s-master -- etcdctl member remove <成员ID> \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key

摘完成员再走 drain、delete、reset 流程。

Node Maintenance​

节点要升级内核、打补丁、换硬件,就得进入维护状态。kubectl 提供三个命令管理节点的可调度性,经常混,先分清楚:

命令作用对已有 Pod 的影响
kubectl cordon <节点>标记为不可调度无影响,已运行的 Pod 原地不动,新 Pod 不会再调度上来
kubectl drain <节点>驱逐节点先 cordon,再把节点上的业务 Pod 优雅驱逐走(在其他节点重建)
kubectl uncordon <节点>恢复调度无影响,只是重新允许新 Pod 调度上来

典型维护操作序列:

# 1. 驱逐业务(drain 自带 cordon,不用先单独 cordon)
kubectl drain k8s-node1 --ignore-daemonsets --delete-emptydir-data

# 2. 确认业务 Pod 已在其他节点 Running
kubectl get pods -A -o wide | grep k8s-node1

# 3. 做维护:升级内核、打补丁、重启、换硬件……
sudo apt-get upgrade -y
sudo reboot

# 4. 机器回来后恢复调度
kubectl uncordon k8s-node1

# 5. 验证节点 Ready
kubectl get nodes
drain 的两个拦路虎
  • PDB(PodDisruptionBudget):如果业务配了 PDB 且剩余副本不满足要求,drain 会卡在驱逐那一步。这是 PDB 在保护你,别直接上 --force 硬闯,先看 kubectl get pdb 确认是不是副本数不够。
  • 裸 Pod:不受任何控制器管理的 Pod(直接用 kubectl run 出来的)被驱逐后不会自动重建,drain 会警告并跳过,需要 --force 才删,删了就没了。

Cluster 生命周期管理​

把前面所有内容串起来,一个集群从生到死的完整生命周期和各阶段要点:

阶段关键动作对应章节
规划定规模、选 HA 拓扑、规划网段(Pod/Service/宿主机不冲突)、选版本高可用架构
创建系统准备、装运行时、init/join、装 CNI、验证 Readykubeadm、containerd
接入业务部署应用、配 ingress、配监控告警、备首个 etcd 快照—
日常运维定时备份 etcd、巡检证书有效期、监控节点资源ETCD 备份恢复、证书更新
升级备份 → 逐 minor 升控制平面 → 逐台升 worker → 验证插件集群升级
扩容新节点装环境、生成 token、join、打标签节点扩容
缩容/维护drain → 维护或 delete → uncordon 或 resetNode Maintenance、节点缩容
退役迁移业务 → 最终备份归档 → 逐节点下线 → 释放资源见下

退役(集群下线) checklist:

  1. 确认业务已全部迁走,DNS、LB 后端不再指向这个集群。
  2. 做最后一次 etcd 快照,连同 /etc/kubernetes/pki/ 打包归档到集群外(合规要求下备份要保留若干年)。
  3. 逐节点执行 drain、delete、kubeadm reset 清理。
  4. 释放虚拟机/物理机,清理 DNS 记录、负载均衡配置、监控对象、堡垒机授权。
  5. 销毁或归档集群的 CA 私钥——它还能签发这个集群的合法证书,泄露等于留后门。
一句话总结

创建靠 kubeadm,活着靠备份和巡检,长大靠 join,变老靠逐版本升级,离开前记得最后备份一次。