一 、kubeadm 部署的核心分层逻辑

kubeadm 是官方标准化的集群部署工具,它将复杂的 Kubernetes 集群拆解为四层依赖, 从上到下逐层依赖,下层出错上层必然卡死:

版本不兼容 → cgroup 驱动不匹配 → 容器创建失败 → 控制平面起不来 → CNI 装 不上 → 节点永远 NotReady。

上述问题中的一个,就足可以导致安装失败。

二、环境准备

离线部署核心约束:所有节点的镜像都必须提前导入到 containerd 的 k8s.io namespace,否则 kubelet 看不到镜像会陷入 ImagePullBackOff 。

三 、全流程分步详解(1 主 2 从 + containerd + Calico + 离线环境)

所有操作严格区分「所有节点执行」和「仅 Master 执行」,按顺序操作零踩坑。

3.1 阶段 1:所有节点通用系统初始化(必须先做)

1. __主机名与域名解析

集群内节点名必须唯一,且所有节点能通过主机名互相通信:

# Master执行
hostnamectl set-hostname master
# Node1执行
hostnamectl set-hostname node1
# Node2执行
hostnamectl set-hostname node2

所有节点配置 hosts解析(替换为你的实际 IP)

cat >> /etc/hosts << EOF
192.168.26.11 master
192.168.26.12 node1
192.168.26.13 node2
EOF

以下是用于测试主机名和 IP 配置的 for 循环脚本:

# 测试所有节点主机名解析和网络连通性
for node in master node1 node2; do
echo "=== 测试节点: $node ==="

# 测试主机名解析
echo -n "主机名解析: "
if ping -c 1 -W 1 $node >/dev/null 2>&1; then
echo "✓成功"
else
echo "✗失败"
fi

# 获取对应 IP并显示
ip=$(grep "$node" /etc/hosts | awk '{print $1}')
echo "对应 IP: $ip"

# 测试 IP连通性
echo -n "IP连通性: "
if ping -c 1 -W 1 $ip >/dev/null 2>&1; then
echo "✓成功"
else
echo "✗失败"
fi

# 显示 hosts文件中的条目
echo -n "hosts记录: "
grep "$node" /etc/hosts || echo "未找到"

echo ""
done

# 额外验证:反向测试(从当前节点到各节点)
echo "=== 当前节点到其他节点的连通性 ==="
current_host=$(hostname)
echo "当前节点: $current_host"

for ip in 192.168.26.11 192.168.26.12 192.168.26.13; do
echo -n "连接到 $ip: "
if ping -c 1 -W 1 $ip >/dev/null 2>&1; then
echo "✓可达"
else
echo "✗不可达"
fi
done

# 验证主机名唯一性
echo ""
echo "=== 验证主机名唯一性 ==="
echo "集群节点主机名:"
for node in master node1 node2; do 
echo " $node"
done

简化版本(更实用):

# 一键测试所有节点
echo "开始测试集群节点连通性..."
for node in master node1 node2; do
printf "%-15s %-20s " "$node" "$(grep $node /etc/hosts | awk '{print $1}')"
if ping -c 1 -W 1 $node >/dev/null 2>&1; then
echo "✓连通正常"
else
echo "✗无法连通"
fi
done

# 验证 hosts配置完整性
echo ""
echo "hosts文件配置验证:"
cat /etc/hosts | grep -E "master|node1|node2"

预期输出示例:

故障排查提示:

1.如果 ping 失败,检查:

网络接口配置:ip addr

防火墙状态:systemctl status firewalld

路由表:ip route

2.如果主机名解析失败,检查:

/etc/hosts 文件权限:ls -l /etc/hosts

DNS 配置:cat /etc/resolv.conf

2. 关闭防火墙与 SELinux
# 关闭防火墙并禁用开机自启
systemctl stop firewalld
systemctl disable firewalld

# 临时关闭 SELinux
setenforce 0
# 永久关闭 SELinux
sed -i 's/^SELINUX=enforcing$/SELINUX=disabled/' /etc/selinux/config
3. 永久关闭 Swap

K8s 官 方 强 制 要 求 关 闭 Swap , 否 则 kubelet 无 法 正 常 工 作 ( 你 之 前 用 --ignore-preflight-errors=Swap 跳过,属于隐患):

# 临时关闭
swapoff -a
# 永久关闭(注释 fstab中的 swap行)
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab 
4. __加载内核模块与网络参数

这是 CNI 网络、Service 转发的底层基础,没开启会导致 Pod 网络完全不通:

# 加载网桥过滤模块
modprobe br_netfilter
modprobe overlay

# 写入永久配置
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
EOF

# 生效配置
sysctl --system

5. __时间同步

集群证书、etcd 对时间差极度敏感,偏差超过 5 分钟会出现认证失败:

启动并启用 Chrony 服务

确保所有节点都执行此操作,以保证开机自启

# 启动 chronyd 服务
systemctl start chronyd

# 设置 chronyd 开机自启
systemctl enable chronyd

# 查看服务状态(可选,确认 Active 状态)
systemctl status chronyd --no-pager
6. __时区设置

Kubernetes 集群通常要求统一使用 UTC 或统一的本地时区。在中国,我们通常设置为 Asia/Shanghai。

# 设置系统时区为上海(东八区)
timedatectl set-timezone Asia/Shanghai

# 查看当前时区设置
timedatectl status | grep "Time zone"

这是最关键的一步,需要确认时间不仅正确,而且确实在同步。

方法一:使用 chronyc 检查(推荐)

这是检查 Chrony 同步状态最直接的方式。

# 1. 查看时间同步源
chronyc sources -v

# 2. 查看时间同步状态(核心指标)
chronyc tracking # 2. 查看时间同步状态(核心指标)
chronyc tracking 

关键指标解读 (chronyc tracking):

Reference ID: 显示当前同步的时间服务器地址。如果是 203.107.6.88 (阿里云) 或 216.239.35.0 (Google),说明正在同步公网时间。

Stratum: 层级。通常显示 3 或 4 是正常的(离基准时钟越近数字越小)。

Last offset: 最后一次同步的偏移量。这个值越小越好(通常在毫秒级别,如 0.000123)。

RMS offset: 偏移量的均方根。

Leap status: 应该是 Normal。

方法二:使用 timedatectl 检查

这个命令可以一眼看出系统时钟是否已同步。

timedatectl status

验证要点(必须全部满足):

NTP enabled: yes

NTP synchronized: yes (这是最关键的指标,表示已成功同步)

System clock synchronized: yes

Time zone: Asia/Shanghai

RTC in local TZ: no (建议设为 no,避免双系统时间错乱)

方法三:集群内多节点一致性校验(For 循环)

为了确保集群内所有节点时间一致(偏差小于 5 分钟,实际上我们要求秒级一致),可以在任意一台机器上(或者所有节点分别执行)测试:

# 在所有节点上执行,查看时间是否一致
for node in master node1 node2; do
echo -n "$node 时间: "
ssh $node "date '+%Y-%m-%d %H:%M:%S'"
done 

3.2 阶段 2:所有节点部署 containerd 容器运行时

在 Kubernetes 集群里,master 和 node 都是"节点",都需要一个符合 CRI 标准的 容器运行时来干活。

Master:运行 API Server、Scheduler、Controller Manager 等控制面组件(它 们本身也是容器)

Node:运行业务 Pod + 网络插件(Calico 等)的 Pod

两者都离不开 containerd。所以你贴的这套步骤,node 节点要原封不动地再来一遍。

简单理解:K8s 集群里除了 kubeadm init(只在 master 跑一次)和 kubeadm join(node 加入集群)这种"集群级"操作有主次之分外,容器运行时、kubelet 这类"节点级" 组件,是每个节点都要独立安装的。

你踩过的那个"cgroup 驱动不统一导致沙箱创建失败"的坑——node 节点如果不改 SystemdCgroup = true,照样会踩。所以这套配置在 node 上是必选项,不是可选项。

为什么 node 节点一样不能少

假设你已经通过 U 盘/内网仓库把 rpm 包和离线镜像 tar 包拷贝到了 node 机器上。

1. 安装 containerd
cd /root/k8s-offline/rpms
yum localinstall -y containerd.io-1.6.21-3.1.el7.x86_64.rpm \
container-selinux-2.119.2-1.911c772.el7_8.noarch.rpm

# 确认安装版本
rpm -qa | grep containerd
containerd --version

输出结果如下

如果输出是 containerd.io-1.6.21-3.1.el7.x86_64,说明版本完全匹配你打算装的包, 直接往下走。

如果输出是 更高版本(比如 1.6.28、1.6.33 之类),也没问题,containerd 已经在 了,同样往下走即可。

只有在版本不对且你想严格锁定 1.6.21时,才需要先卸载再重装(见文末兜底方 案)。

离线部署 K8s 1.24+ 用 1.6.x 系列的 containerd 都没问题,不必纠结必须是 1.6.21。

2. 生成并修改核心配置(重中之重)

containerd 装好后,/etc/containerd/config.toml 默认是不存在的,需要手动生成:

mkdir -pv /etc/containerd
containerd config default > /etc/containerd/config.toml

# 关键 1:cgroup 驱动统一为 systemd(不改必踩坑)
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml

# 关键 2:指定本地 pause 镜像(版本要和你离线包里的 pause 对齐)
sed -i 's#sandbox_image = ".*"#sandbox_image = "registry.k8s.io/pause:3.7"#g' /etc/containerd/config.toml
3. 重启服务并验证
systemctl daemon-reload
systemctl restart containerd
systemctl enable containerd
# 验证两个关键配置生效
grep -E "SystemdCgroup|sandbox_image" /etc/containerd/config.toml 
输出应该看到:
sandbox_image = "registry.k8s.io/pause:3.7"
SystemdCgroup = true
4. 离线镜像导入(离线环境专属坑)

情况 A:手上有离线 tar 包(推荐)

如果之前是把这些镜像 docker save 成的 tar 包,直接导入:

在每台节点(master + 所有 node)上都执行

# 业务基础镜像
ctr -n k8s.io images import /root/k8s-offline/k8s-base-images.tar
# Calico 网络插件镜像
ctr -n k8s.io images import /root/k8s-offline/calico-images.tar
# 验证
ctr -n k8s.io images list

这是离线部署最容易翻车的一步:containerd 有独立命名空间,K8s 只认 k8s.io 这个命名空间。如果你用 docker load 导入或者用 ctr images import(默认 namespace 是 default),K8s 都看不到这些镜像。

那个 -n k8s.io 绝对不能省,省了就会导到 default 命名空间,K8s 还是看不见。

5. 确认 containerd 健康
systemctl status containerd 

ctr version

应该是 active (running)

能看到 client 和 server 版本

当你所有 master + node 都完成了 containerd 的部署,并且 systemctl status containerd 都是 running 状态后:

  1. Master 节点执行 kubeadm init … 初始化集群

  2. Node 节点拿到 master 输出的 kubeadm join … 命令后执行,加入集群

  3. 在 master 上 kubectl get nodes 能看到 node 状态变成 Ready

给小白的几个避坑提醒

⚠三个最容易栽跟头的地方:

  1. 每个 node 都要重复整套流程——不能只在 master 装了就以为完事了。K8s 集群 里"节点级"操作没有"主次",只有"集群级"操作(init/join)才有

  2. pause 镜 像 版 本 要 对 得 上 : config.toml 里 写 的 版 本 , 必 须 和 你 k8s-base-images.tar 里实际包含的 pause 镜像版本一致,差一个 minor 版本都会导致拉 镜像失败。

  3. 镜像必须导入到 k8s.io namespace:ctr -n k8s.io images import 这个 -n k8s.io 不能漏。漏了的话,后面 Pod 调度到这台 node 上就会 ImagePullBackOff。

3.3 阶段 3:所有节点安装 K8s 核心三件套

目的:安装 kubelet(节点代理)、kubeadm(集群安装工具)、kubectl(命令行客户 端)。

你踩的坑:kubelet 1.28 与控制平面 1.24 版本差 4 个大版本,远超官方支持的± 1 个小版本偏差,直接不兼容。

1. 强制安装指定版本
# 进入你的 rpm 目录
cd /root/k8s-offline/rpms

yum localinstall -y \
kubernetes-cni-1.1.1-0.x86_64.rpm \
cri-tools-1.24.0-0.x86_64.rpm \
*kubelet-1.24.17*.rpm \
*kubeadm-1.24.17*.rpm \
*kubectl-1.24.17*.rpm
2. 设置 kubelet __开机自启
systemctl enable kubelet

✅此时 kubelet 循环重启属于正常现象:它还没有配置文件,在等待 kubeadm 生成 配置。集群初始化完成后会自动恢复正常。

3. 验证版本一致性
kubelet --version
kubeadm version
kubectl version --client

输出如下:

Kubernetes v1.24.17

kubeadm version: &version.Info{Major:“1”, Minor:“24”, GitVersion:“v1.24.17”, GitCommit:“22a9682c8fe855c321be75c5faacde343f909b04”, GitTreeState:“clean”, BuildDate:“2023-08-23T23:43:11Z”, GoVersion:“go1.20.7”, Compiler:“gc”, Platform:“linux/amd64”}

WARNING: This version information is deprecated and will be replaced with the output from kubectl version --short. Use --output=yaml|json to get the full version.

Client Version: version.Info{Major:“1”, Minor:“24”, GitVersion:“v1.24.17”, GitCommit:“22a9682c8fe855c321be75c5faacde343f909b04”, GitTreeState:“clean”, BuildDate:“2023-08-23T23:44:35Z”, GoVersion:“go1.20.7”, Compiler:“gc”, Platform:“linux/amd64”}

Kustomize Version: v4.5.4

三者必须全部为 v1.24.17,差一个版本都不要继续。

3.4 阶段 4:仅 Master 节点执行集群初始化

目的:生成集群证书、启动控制平面静态 Pod、生成节点加入凭证。

你踩的坑:中途 Ctrl+C 中断导致残留,未指定 CRI 套接字。

1. __初始化前确认

如果之前执行过失败的 init,必须先重置干净,不然会报端口占用、文件已存在:

kubeadm reset -f
iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X
ipvsadm -C
rm -rfv /etc/kubernetes /var/lib/etcd /var/lib/kubelet $HOME/.kube
2. 执行初始化命令 所有参数都有明确作用,不要乱加多余参数:
kubeadm init \
--apiserver-advertise-address=192.168.26.11 \
--kubernetes-version=v1.24.17 \
--service-cidr=10.96.0.0/12 \
--pod-network-cidr=192.168.0.0/16 \
--image-repository=registry.k8s.io \
--cri-socket=unix:///run/containerd/containerd.sock 

核心参数说明:

–apiserver-advertise-address:apiserver 对外宣告的 IP,必须是节点本机 IP

–pod-network-cidr:Pod 的 IP 网段,必须和后续 Calico 配置的网段完全一致

–cri-socket:指定 containerd 的 CRI 套接字,不指定会找不到运行时

不要加–ignore-preflight-errors=all,预检报错就是在提前提醒问题

初始化成功,输出如下:

kubeadm join 192.168.26.11:6443 --token txhefv.17h7pjs6rkj01f2e \

–discovery-token-ca-cert-hash sha256:3916a40fc3e45ede1f670295e90380c82050e28db77acb23bc294930e63ba41d

报错

重新加载模块

modprobe br_netfilter

3. 初始化执行流程拆解

kubeadm init 内部按顺序执行 6 步,卡在哪一步就对应哪一层问题:

1.预检阶段:检查系统环境、版本、运行时 → 报错对应阶段 1/2/3

2.拉取镜像:拉取控制平面镜像 → 报错对应镜像不存在

3.证书生成:生成所有集群证书 → 报错对应主机名/IP 配置错误

4.配置 kubelet:写入/var/lib/kubelet/config.yaml

5.启动静态 Pod:kubelet 启动 etcd、apiserver、控制器、调度器

6.等待控制平面就绪:等待 6443 端口可用 → 超时 90%是容器创建失败(cgroup 驱动/

镜像问题)

3.5 阶段 5:配置 kubectl 与验证控制平面

1. __配置管理员权限

目的:获得集群管理员操作权限,确认控制平面正常运行。

master节点执行

mkdir -pv $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
2. 验证控制平面
kubectl get pods -n kube-system

正常输出:etcd、kube-apiserver、kube-controller-manager、kube-scheduler 四个 静态 Pod 全部 Running。

✅此时节点状态为 NotReady 是正常中间状态:还没有部署 CNI 网络插件,kubelet 认 为节点网络未就绪。

3.6 阶段 6:部署 Calico CNI 网络插件(最容易卡壳的 一步)

目的:打通 Pod 跨节点通信,让节点变为 Ready 状态。

你踩的死循环坑:节点 NotReady → 自带 node.kubernetes.io/not-ready 污点 → Pod 无法调度 → 装不上 CNI → 永远 NotReady。

1. 破局:移除 Master __节点污点

单主集群推荐直接移除 Master 的调度污点,让网络组件可以调度上去,解开死循环:

kubectl taint nodes master node-role.kubernetes.io/master-

末尾的-是固定语法,代表删除该污点。

2. 部署正确的 Calico __配置

绝对不能用下载错的 HTML 网页文件,必须使用合法的 K8s 资源清单。离线环境确保:

yaml 文件是标准 K8s 资源定义

Calico 镜像已经导入 containerd 的 k8s.io 命名空间

网段和–pod-network-cidr 一致(192.168.0.0/16)

部署命令:

kubectl apply -f /root/k8s-offline/calico.yaml
3. 监控启动状态
kubectl get pods -n kube-system -w

等待 calico-node-xxx 和 calico-kube-controllers-xxx 全部变为 Running。

4. 验证节点就绪
kubectl get nodes

正常输出:master 状态变为 Ready。

3.7 阶段 7:Worker 节点加入集群

目的:扩容工作节点,形成完整集群。

1. Master __节点生成加入命令

kubeadm token create --print-join-command 会输出一行完整的 join 命令,复制备用

2. Worker __节点执行加入命令

必须追加–cri-socket 参数,适配 containerd 运行时: # 替换为你实际的 token 和 hash

kubeadm join 192.168.26.11:6443 --token xxx \ --discovery-token-ca-cert-hash sha256:xxx \

–cri-socket=unix:///run/containerd/containerd.sock

kubeadm join 192.168.26.11:6443 --token txhefv.17h7pjs6rkj01f2e \
--discovery-token-ca-cert-hash sha256:3916a40fc3e45ede1f670295e90380c82050e28db77acb23bc294930e63ba41d

node1节点输出如下:

[root@node1 rpms]# kubeadm join 192.168.26.11:6443 --token txhefv.17h7pjs6rkj01f2e \

    --discovery-token-ca-cert-hash sha256:3916a40fc3e45ede1f670295e90380c82050e28db77acb23bc294930e63ba41d

[preflight] Running pre-flight checks

[preflight] Reading configuration from the cluster…

[preflight] FYI: You can look at this config file with ‘kubectl -n kube-system get cm kubeadm-config -o yaml’

[kubelet-start] Writing kubelet configuration to file “/var/lib/kubelet/config.yaml”

[kubelet-start] Writing kubelet environment file with flags to file “/var/lib/kubelet/kubeadm-flags.env”

[kubelet-start] Starting the kubelet

[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap…

This node has joined the cluster:

  • Certificate signing request was sent to apiserver and a response was received.

  • The Kubelet was informed of the new secure connection details.

Run ‘kubectl get nodes’ on the control-plane to see this node join the cluster.

node2节点输出如下:

[root@node2 rpms]# kubeadm join 192.168.26.11:6443 --token txhefv.17h7pjs6rkj01f2e \

    --discovery-token-ca-cert-hash sha256:3916a40fc3e45ede1f670295e90380c82050e28db77acb23bc294930e63ba41d

[preflight] Running pre-flight checks

[preflight] Reading configuration from the cluster…

[preflight] FYI: You can look at this config file with ‘kubectl -n kube-system get cm kubeadm-config -o yaml’

[kubelet-start] Writing kubelet configuration to file “/var/lib/kubelet/config.yaml”

[kubelet-start] Writing kubelet environment file with flags to file “/var/lib/kubelet/kubeadm-flags.env”

[kubelet-start] Starting the kubelet

[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap…

This node has joined the cluster:

  • Certificate signing request was sent to apiserver and a response was received.

  • The Kubelet was informed of the new secure connection details.

Run ‘kubectl get nodes’ on the control-plane to see this node join the cluster.

Worker 节点必须提前做完「阶段 1-阶段 3」的所有操作,不然加入会失败。

3. Master 验证节点加入
kubectl get nodes -w

两台 Worker 节点会自动调度 calico-node Pod,随后依次变为 Ready。

3.8 阶段 8:集群最终验收

所有节点就绪后,执行最终验证:

1. 所有节点状态为 Ready
kubectl get nodes -o wide
2. 所有系统 Pod 正常运行
kubectl get pods -n kube-system
3. 创建测试 Pod 验证网络
kubectl run test-nginx --image=nginx
kubectl get pods -o wide

3.9 你一下午踩过的所有坑汇总

版本严重不兼容:kubelet 1.28 搭配控制平面 1.24,远超官方支持的版本偏差范围

cgroup 驱动不统一:containerd 用 cgroupfs、kubelet 用 systemd,导致 runc 无法创建容器,控制平面静态 Pod 全部启动失败

镜像命名空间错误:离线镜像未导入 k8s.io 命名空间,containerd 识别不到 K8s 镜像 Calico 文件错误:下载的 yaml 实际是阿里云镜像站的 HTML 网页,根本不是 K8s 资源

清单

调度死循环:Master 节点污点 + NotReady 污点,导致 CNI Pod 无法调度,节点永远 NotReady

中断残留问题:中途 Ctrl+C 终止初始化,端口和文件残留,导致下次 init 直接报错

未指定 CRI 套接字:kubelet 无法自动识别 containerd,对接不上运行时

3.10 你当前环境的快速修复方案(不用全重装)

针对你现在的状态,按顺序执行即可快速跑通:

1. 确认 containerd cgroup 驱动已改为 true
grep SystemdCgroup /etc/containerd/config.toml
2. 移除 Master 节点污点,解开调度死循环
kubectl taint nodes master node-role.kubernetes.io/master-
3. 删除错误的 calico 部署,重新部署正确的 yaml
kubectl delete -f /opt/k8s-offline/calico.yaml
# 替换为正确的 calico.yaml 后重新部署
kubectl apply -f /opt/k8s-offline/calico.yaml
4. 监控 Pod 启动状态
kubectl get pods -n kube-system -w

所有 calico Pod 变为 Running 后,节点会自动变为 Ready,之后再加入 Worker 节点即可。

更多推荐