Featured image of post K8s 从零到实战 00(下):内核参数与容器运行时(containerd 三关)

K8s 从零到实战 00(下):内核参数与容器运行时(containerd 三关)

k8s 安装前的最后准备:内核模块与 iptables 桥接原理、containerd 安装与三大关键修改(SystemdCgroup、certs.d 镜像代理、pause 镜像),附集群装完后的三层验收清单。

目标:完成 k8s 安装前的最后两项准备——内核网络参数(k8s 的神经网络)和容器运行时(k8s 的手)。 前置:已完成 阶段 0(上):虚拟机与系统初始化。 步骤编号延续上篇(从第 5 步开始);所有命令在 Rocky 机器上以 root 执行。

5. 加载内核模块并开启 iptables 桥接流量(必须)

为什么必须——k8s 的 Service 网络(阶段 3 主角)依赖三样底层能力:

  • overlay:容器镜像分层存储的文件系统,containerd 启动容器必备
  • br_netfilter + bridge-nf-call-iptables=1:强制网桥流量经过 iptables。Service 虚拟 IP 靠 iptables 规则转换(kube-proxy 写入),桥内流量若绕过 iptables,Service 全部失灵
  • ip_forward=1:允许节点转发数据包,跨节点 Pod 通信的前提

缺了任何一样,阶段 1 的 kubeadm init 预检会直接报错拒绝安装。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
cat > /etc/modules-load.d/k8s.conf <<EOF
overlay
br_netfilter
EOF

modprobe overlay
modprobe br_netfilter

cat > /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables  = 1
net.ipv4.ip_forward                 = 1
EOF

sysctl --system

验证:lsmod | grep br_netfilter 应有输出,例如:

1
2
br_netfilter           32768  0
bridge                315392  1 br_netfilter

输出读法(四列 = 模块名 / 大小(字节) / 引用计数 / 被谁引用): 引用计数 0 不等于没用——br_netfilter 靠 sysctl 开关起作用,不靠被谁引用; bridge 行尾出现 br_netfilter,说明它是 bridge 网桥驱动的"扩展插件", 专门给网桥加 netfilter(iptables)钩子。两行都在 = 这对搭档都在岗。 (/etc/modules-load.d/ 管开机自动加载,modprobe 管立即生效,缺一不可。)

6. 安装容器运行时 containerd

k8s 自己不会跑容器——它只是调度大脑(决定 Pod 放哪台机器),真正拉镜像、起容器的是容器运行时。kubelet 通过标准接口 CRI 指挥 containerd 干活:kubectl 命令 → apiserver → scheduler → kubelet → containerd → 容器进程。 Kubernetes 从 1.24 起不再直接支持 Docker(它对 k8s 来说太重),containerd 是主流选择。

6.1 安装

1
2
3
4
5
6
7
8
# download.docker.com 国内不可达,使用阿里云 docker-ce 源(只借用它里面的 containerd 包)
dnf config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 安装 containerd
dnf -y install containerd.io

# 生成默认配置
containerd config default > /etc/containerd/config.toml
  • containerd 的官方发布渠道是 Docker 公司的软件源 download.docker.com(国内不可达)。阿里把它整个镜像了一份,我们借仓库只取 containerd.io 这一个包,不装 docker-ce 本身——虚拟机里不会有 Docker
  • 包名是 containerd.io(不带 .io 的是另一个老包,别搞混)
  • containerd 没有开箱即用的配置文件,必须先用 config default 生成底版再修改

6.2 关键修改 1:SystemdCgroup = true(与 kubelet 统一资源管理)

1
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

为什么必改:cgroup 是 Linux 给进程分配/限制资源(CPU、内存)的机制,k8s 的 requests/limits 全靠它。 cgroup 有两种"管理员":cgroupfs(传统写法)和 systemd(Rocky 9 等现代发行版由 systemd 统一管理)。 一台机器只能有一个管理员——kubelet 用 systemd,containerd 默认却是 cgroupfs(false), 两个管理员各记一套账,会导致资源统计错乱、节点不稳定。这行 sed 把 containerd 也划归 systemd 阵营。

6.3 关键修改 2:certs.d 镜像代理(国内拉镜像的生存指南)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Docker Hub 国内被墙,给 docker.io 配置镜像代理(daocloud)
# containerd 的 registry 配置采用 certs.d 目录(类似 docker 的 registry-mirrors)
mkdir -p /etc/containerd/certs.d/docker.io
cat > /etc/containerd/certs.d/docker.io/hosts.toml <<EOF
server = "https://docker.io"
[host."https://docker.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
EOF
# 同理给 registry.k8s.io 配代理:它的 CDN 是 Google 的,国内同样被墙
#(kubeadm 装集群时我们改用阿里云源规避;但 ingress-nginx、metrics-server 等
#  第三方组件的镜像也在 registry.k8s.io,阶段 6/8 会用到这个代理)
mkdir -p /etc/containerd/certs.d/registry.k8s.io
cat > /etc/containerd/certs.d/registry.k8s.io/hosts.toml <<EOF
server = "https://registry.k8s.io"
[host."https://k8s.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
EOF
# 启用 certs.d 目录(containerd 2.x 默认配置里 config_path 为空,指过去即可)
sed -i "/^\s*config_path = ''\s*$/s/''/'\/etc\/containerd\/certs.d'/" /etc/containerd/config.toml

原理:certs.d 是 containerd 的"仓库导航改写本"。它拉镜像时的决策过程是:

1
2
3
4
5
6
要拉 nginx:1.27
  ↓ 补全为完整地址 docker.io/library/nginx:1.27
  ↓ 查导航本:/etc/containerd/certs.d/ 下有没有叫 docker.io 的目录?
  ↓ 有!读里面的 hosts.toml:docker.io 的请求改走 docker.m.daocloud.io
  ↓
实际从 daocloud 下载,但镜像在本地的"户口本"仍登记为 docker.io/library/nginx:1.27

目录结构就是映射关系——要拉哪个仓库的镜像,就用哪个目录下的 hosts.toml:

1
2
3
/etc/containerd/certs.d/
├── docker.io/hosts.toml           ← 拉 nginx 等 Docker Hub 镜像时生效
└── registry.k8s.io/hosts.toml     ← 拉 k8s 官方组件镜像时生效

hosts.toml 三行的含义:

1
2
3
server = "https://docker.io"                  # 这份配置管哪个仓库("户主")
[host."https://docker.m.daocloud.io"]         # 实际去哪拉("代取点",可写多个按顺序试)
  capabilities = ["pull", "resolve"]          # 代取点只允许"解析+拉取",禁止 push(安全)

最后那条 sed 是总开关:containerd 2.x 默认配置里 config_path = ''(空 = 根本不看 certs.d 目录), 把它指向该目录,导航本才生效。

为什么这样设计,而不是别的办法:

  • ❌ 直接改镜像名(把 nginx 写成 docker.m.daocloud.io/library/nginx):镜像名写在全世界的 YAML、Helm Chart、kubeadm 生成的清单里,改不完也改不到;换代理时还要全局替换
  • ❌ 配 HTTP 代理:多一个要维护的中间故障点,且企业环境未必有代理可用
  • ✅ certs.d:只在一个地方声明映射,全世界照常用官方镜像名,拉取被悄悄改道; daocloud 挂了改两行 hosts.toml 换一家即可,业务零改动。 本质是逻辑地址(镜像名)与物理地址(镜像站)解耦,对 kubelet/YAML/Helm 完全透明

registry.k8s.io 那份的必要性:阶段 1 装集群时核心组件可以换阿里云源, 但 ingress-nginx(阶段 6)、metrics-server(阶段 8)等第三方组件的官方清单里写死了 registry.k8s.io 地址,不便改也没法改。配上这条,届时 kubectl apply 官方清单直接跑通。

6.4 关键修改 3:pause(sandbox)镜像指向阿里云(国内搭集群第一坑)

1
2
3
# kubelet 1.34 起改为从 containerd 获取 pause 镜像地址(旧的 --pod-infra-container-image 已失效),
# 而 containerd 默认的 registry.k8s.io/pause 的 CDN 国内被墙,不改会导致所有 Pod 卡在创建阶段
sed -i "/^  \[plugins.'io.containerd.cri.v1.images'\]$/a\    sandbox_image = \"registry.aliyuncs.com/google_containers/pause:3.10.2\"" /etc/containerd/config.toml

pause 容器是什么:一个 Pod 可以装多个容器,它们共享同一个网络命名空间(同一个 IP)。 这个"共享"需要一个先行者先把网络命名空间创建好、占住位置——就是 pause 容器(沙箱容器)。 每个 Pod 里都有一个看不见的 pause,几乎不占资源,但没它 Pod 无法创建。

问题链条:pause 镜像默认在 registry.k8s.io/pause(Google CDN,国内被墙)→ 老办法是 kubelet 参数 --pod-infra-container-image,但 kubelet 1.34 起该参数失效, 改为从 containerd 的 sandbox_image 读取 → 不改的后果是所有 Pod 永远卡在 ContainerCreating。 这条 sed 在该插件节下追加一行,指向阿里云镜像站(a\ = 在匹配行之后追加)。

三关总结:SystemdCgroup 让 containerd 与 kubelet 和平共处; certs.d 让 Docker Hub / k8s 官方镜像拉得到;sandbox_image 让 Pod 能创建。 少任何一个,阶段 1 都会以不同的姿势翻车。

6.5 (按需)虚拟机直连出网被拦时:给 containerd 配代理

containerd 是系统服务,不读你终端里 export 的代理变量,必须用 systemd drop-in 单独配置 (192.168.5.2 是 Lima 内置的"Mac 本机"地址,长期有效,排查思路见 阶段 0(上)附录 A):

1
2
3
4
5
6
7
mkdir -p /etc/systemd/system/containerd.service.d
cat > /etc/systemd/system/containerd.service.d/proxy.conf <<EOF
[Service]
Environment="HTTP_PROXY=http://192.168.5.2:7897"
Environment="HTTPS_PROXY=http://192.168.5.2:7897"
Environment="NO_PROXY=localhost,127.0.0.1,192.168.5.0/24,192.168.64.0/24,192.168.0.0/16,10.96.0.0/12"
EOF

⚠️ 配了这个代理后,将来直连恢复或 Clash 关闭时必须删掉它并重启 containerd (rm 该文件 && systemctl daemon-reload && systemctl restart containerd), 否则 containerd 拉镜像全部失败。原则:代理配置和代理软件同开同关。

6.6 启动与验证

1
2
3
4
5
# 启动并设为开机自启
systemctl daemon-reload
systemctl enable --now containerd
systemctl restart containerd
systemctl status containerd --no-pager

验证:

1
ctr version        # 能看到 Client/Server 版本号即成功

💡 镜像代理说明:配置后,所有 nginx、mysql 这类来自 Docker Hub 的镜像 会经由 docker.m.daocloud.io 代理拉取,对 Kubernetes 完全透明(镜像名不用改)。 如果 daocloud 将来失效,换其他可用代理地址改 hosts.toml 即可。 k8s 官方镜像仓库 registry.k8s.io 本教程网络实测可达,无需代理; 如果你的网络不行,阶段 1 有阿里云替代方案。

7. 检查清单

进入下一阶段前确认:

1
2
3
4
5
swapon --show                    # 无输出(swap 已关)
getenforce                       # Permissive
systemctl is-active containerd   # active
sysctl net.ipv4.ip_forward       # = 1
lsmod | grep br_netfilter        # 有输出

常见问题

Q: 第 6 步装 containerd / 拉镜像时网络不通? containerd 是系统服务,不读终端环境变量。先按上篇附录 A 的分层法确认是不是"虚拟机直连被拦", 是的话用第 6.5 节的 systemd drop-in 给 containerd 配代理; 将来 Pod 出现 ImagePullBackOff 也先怀疑这层。

Q: containerd 配置文件里的插件名为什么和网上旧教程不一样? containerd 2.x 使用了新版配置格式(如 io.containerd.cri.v1.images), 旧教程里的 io.containerd.grpc.v1.cri 是 1.x 的写法。本教程的 sed 命令两种都兼容。

附录 B:集群装好后如何验证(阶段 1 完成后使用)

进程活着 ≠ 集群可用,能跑起一个真 Pod 才是终极证明。按三层验证(虚拟机里、root 执行):

第一层:节点状态

1
2
kubectl get nodes
# 期望:STATUS 是 Ready

第二层:系统组件 Pod 全部 Running(最关键)

1
2
3
4
5
6
kubectl get pods -n kube-system
# 期望:etcd、kube-apiserver、kube-scheduler、kube-controller-manager、
#       coredns(2个)、calico-node 全部 Running 且 READY 是 1/1

kubectl get pods -A
# 不能有 Pending / CrashLoopBackOff / ImagePullBackOff

第三层:跑一个真容器(终极证明)

1
2
3
4
5
kubectl run nginx-test --image=nginx
kubectl get pods -w                # 看到 Running 后 Ctrl+C(这步同时验证了调度、镜像拉取、pause 镜像、CNI)
kubectl get pods -o wide           # Pod 拿到 IP = Calico 网络正常
kubectl exec -it nginx-test -- curl -sI localhost | head -1    # 期望:HTTP/1.1 200 OK
kubectl delete pod nginx-test      # 清理

辅助定位(上面不正常时查):

1
2
systemctl is-active kubelet containerd    # 都应为 active
crictl ps | head                          # containerd 层面的容器(应有 pause 和业务容器)

判断标准速记:get nodes Ready → 集群成立;kube-system 全 Running → 控制面健康; 自建 Pod 到 Running 且能 exec → 端到端真通了(也是本阶段 containerd 三个关键修改的验收)。 若 kubectl 报 “connection to localhost:8080 refused”,说明 kubeconfig 没配,先做阶段 1 的 mkdir -p $HOME/.kube && cp /etc/kubernetes/admin.conf $HOME/.kube/config。

动手练习

  1. 查看 containerd 配置文件 /etc/containerd/config.toml,找到你刚改的 SystemdCgroup 和 config_path 两行。
  2. 查看 /etc/containerd/certs.d/docker.io/hosts.toml,理解镜像代理的配置结构。

下一步

➡️ 阶段 1:搭建集群(系列第 1 篇,即将发布——从这里开始正式安装 Kubernetes)

使用 Hugo 构建
主题 Stack 由 Jimmy 设计