ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Kubernetes 1.31极简部署手册:Containerd运行时配置与避坑指南

Kubernetes 1.31极简部署手册:Containerd运行时配置与避坑指南 写这个手册的起因很简单我帮团队搭测试环境时按网上大部分教程装 Kubernetes 1.31结果kubeadm init一跑完kubelet就像抽风一样反复重启crictl ps直接报“cannot connect to endpoint”。折腾了大半夜才反应过来问题全出在容器运行时上——默认安装只会把 Docker 给你装好但 Kubernetes 从 1.24 开始就已经把 Containerd 作为默认的 CRI 运行时版本都到 1.31 了还在用旧思路怎么可能不翻车。这套极简部署手册就是冲着这个痛点来的。全程只需要复制一条脚本装完 Containerd 和 Kubernetes 1.31 组件再执行两三条命令就能得到一个 Ready 的控制平面节点。文章不会跟你扯太多抽象概念重点讲清楚脚本里的每一行在干什么、为什么要这样配置、哪些参数不能乱改以及在真实环境中最容易踩的 5 个坑。适合刚接触 Kubernetes 的运维新手也适合想快速搭一套本地或测试环境验证业务的老手直接抄作业。1. 容器运行时换装为什么我放弃默认 Docker 运行时1.1 从一次 kubeadm init 崩溃说起先复盘一下我最初翻车的现场。执行kubeadm init的时候命令其实没报错提示初始化成功但随后kubelet的状态就是不对。systemctl status kubelet显示 active (running)可日志里全是“Container runtime network not ready”和“failed to get sandbox image”。用crictl ps想看容器列表直接提示找不到/var/run/dockershim.sock。查了一圈才发现Kubernetes 1.31 的kubeadm默认会去连 Containerd 的 socket也就是unix:///run/containerd/containerd.sock。而机器上虽然装了 Docker但 Docker 的运行时 socket 路径和 kubelet 期望的根本对不上于是 kubelet 就卡在“找不到容器运行时”这个状态里反复重试。这个问题在网上被问了几百遍核心原因就是一个你装的运行时和 kubelet 期望的 CRI 不匹配。1.2 Containerd 和 Docker 的定位差异很多人一听到 Containerd 就以为是个陌生玩意其实它一点都不神秘。Docker 本身是一个上层封装底层真正干活的就是 Containerd它负责镜像管理、容器生命周期、网络挂载这些脏活累活。你在 docker 里跑一个容器真正创建和运行容器进程的是 ContainerdDocker 只是前面那层 CLI 和 API。Kubernetes 在 1.24 版本正式移除 dockershim 之后Containerd 就成了事实上的默认 CRI 运行时。它更轻量、资源占用更小、不依赖 Docker 那一整套守护进程链路而且 Kubernetes 官方对它的支持和测试也是最充分的。所以到了 1.31 这个版本你完全没必要再绕一圈装 Docker直接把 Containerd 作为唯一运行时反而干净利落。1.3 什么时候你依然需要 Docker CLI这不代表 Docker 就没用了。如果你平时习惯了用docker build打镜像、用docker compose起本地依赖那 Docker CLI 依然有价值。但在 Kubernetes 节点上建议只装 Containerd不装 Docker这样能避免端口冲突、iptables 规则互相干扰、以及 docker 和 containerd 双守护进程抢资源的问题。真要构建镜像可以在开发机上用 Docker构建完推到镜像仓库Kubernetes 节点只负责从仓库拉取并运行这个流程完全绕开节点上的 Docker。我个人的习惯是Kubernetes 节点一律只装 Containerd日常开发机保留 Docker两边互不干扰。这个方案实测下来最稳集群资源占用也低一截。2. 安装前的版本账本资源下限与系统准备清单2.1 版本搭配与硬件底线先把版本账算清楚这是后面所有操作不出乱子的前提。这套手册基于以下版本组合都是我实测过的稳定搭配组件推荐版本备注操作系统Ubuntu 22.04 / Debian 12内核 5.15CentOS 7 需要单独处理内核模块Containerd1.7.xCRI 插件默认开启稳定Kubernetes1.31.x使用 kubeadm 部署网络插件Calico 3.28 或 Flannel二选一下文会解释区别硬件方面单节点测试环境最低 2 核 4G建议 4 核 8G。如果是在云服务器上搭注意要选带公网 IP 的机器因为有些镜像拉取和网络插件下载需要出网。控制平面节点对 CPU 的要求其实比内存高API Server、etcd 都是 CPU 密集型2 核只能勉强跑pod 一多就明显卡顿。2.2 主机名与系统基础配置主机名不要随便起特别不要用带下划线的名字Kubernetes 对主机名格式有要求。我一般统一命名为k8s-master、k8s-node01这种格式DNS 解析也要确认好hostname -i能正常返回内网 IP 而不是 127.0.0.1。hostnamectl set-hostname k8s-master # 验证主机名解析/etc/hosts 中必须有本机 IP 对应主机名记录 echo 192.168.1.10 k8s-master /etc/hosts这个细节很多人忽略。我在真实环境里见过主机名解析不对导致 kubelet 启动后拿不到正确的 Node IP节点一直 NotReady排查半天才发现是/etc/hosts里少了本机记录。2.3 内核模块、系统参数与 swap 处理Kubernetes 依赖两个内核模块overlay镜像分层文件系统和br_netfilteriptables 对网桥流量的处理。不加载br_netfilterPod 之间的通信和 Service 转发会出各种诡异问题。cat EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter加载完模块还要把几个网络参数写进 sysctl。关键是net.ipv4.ip_forward1这个不开Pod 网络和宿主机网络之间的转发就是断的。另外两个桥接参数是让 iptables 能正确处理桥接流量cat EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sudo sysctl --systemswap 必须关掉因为 kubelet 在默认配置下不允许宿主机启用 swapkubeadm init会直接报错“running with swap on is not supported”。关闭命令是sudo swapoff -a # 确保重启后不自动挂载 swap sudo sed -i / swap / s/^/#/ /etc/fstab顺带提一嘴如果你用的是云服务器很多默认镜像会带 swap 分区或者 swapfile只执行swapoff -a重启后又回来了所以/etc/fstab那行注释一定不能省。2.4 时间同步一个小参数引发的调度问题还有一个容易被忽略的点时间同步。Kubernetes 的证书签发、事件记录、日志时间戳都依赖节点时间准确。如果节点时间偏差超过 5 分钟kubeadm init生成的证书在校验时就会出现“x509: certificate has expired or is not yet valid”这类报错非常迷惑。sudo apt install -y chrony sudo systemctl enable --now chrony如果你已经有 NTP 服务在跑这一步可以跳过但一定要确认timedatectl里时间是同步状态。这个毛病我是在一个内网离线环境里遇到的当时折腾了很久最后发现是机器 BIOS 时间不对导致集群里所有节点的时间互相不一致。3. 一键脚本拆开看每行命令在解决什么问题3.1 脚本的完整轮廓与执行前提说好的一键式脚本先把完整命令放出来。这个脚本适用于 Ubuntu 22.04/Debian 12 全新机器执行后会自动完成系统配置、Containerd 安装、Kubernetes 组件安装并输出后续 init 所需的提示#!/bin/bash set -e # 1. 配置主机名与 hosts HOSTNAMEk8s-master hostnamectl set-hostname $HOSTNAME echo 127.0.0.1 $(hostname) /etc/hosts # 2. 关闭 swap swapoff -a sed -i / swap / s/^/#/ /etc/fstab # 3. 加载内核模块 cat EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter # 4. 配置内核参数 cat EOF | tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sysctl --system # 5. 安装 containerd apt-get update apt-get install -y containerd # 6. 生成并修改 containerd 配置 mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml sed -i s/SystemdCgroup false/SystemdCgroup true/g /etc/containerd/config.toml sed -i s#sandbox_image registry.k8s.io/pause:3.8#sandbox_image registry.aliyuncs.com/google_containers/pause:3.10#g /etc/containerd/config.toml systemctl restart containerd # 7. 添加 Kubernetes apt 源 apt-get install -y apt-transport-https ca-certificates curl gpg curl -fsSL https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | 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/apt/ kubernetes-xenial main | tee /etc/apt/sources.list.d/kubernetes.list # 8. 安装 kubeadm kubelet kubectl 并锁定版本 apt-get update apt-get install -y kubelet kubeadm kubectl apt-mark hold kubelet kubeadm kubectl # 9. 配置 crictl 连接 containerd cat EOF | tee /etc/crictl.yaml runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock timeout: 10 debug: false EOF echo 环境准备完成 echo 接下来执行sudo kubeadm init --cri-socketunix:///run/containerd/containerd.sock --image-repositoryregistry.aliyuncs.com/google_containers --pod-network-cidr10.244.0.0/163.2 系统环境初始化这段在做什么脚本前 4 步本质上就是把系统调整到 Kubernetes 能接受的状态。set -e是保证任何一步出错就立即终止避免带着残缺环境继续往下跑。关闭 swap 和配置内核参数的逻辑我在上一节已经讲了这里再强调一下顺序必须先加载模块再配置 sysctl不然sysctl --system会提示找不到参数。3.3 Containerd 安装与两个关键配置第 5、6 步是整个脚本的灵魂。containerd config default会生成一份默认配置文件但这份默认配置有两个地方必须改。第一个是SystemdCgroup false改成true。这个参数决定了容器 cgroup 驱动是采用 cgroupfs 还是 systemd。Kubernetes 官方推荐在 systemd 作为 init 系统的机器上使用 systemd cgroup 驱动因为 kubelet 默认用的就是 systemd。如果不改kubelet 和 containerd 的 cgroup 驱动不一致会导致 Pod 启动后内存和 CPU 资源统计异常严重时 Pod 一创建就被杀掉。这个坑我踩过不止一次每次都是容器能创建但持续重启排查日志才发现是 cgroup driver 不匹配。第二个是沙箱镜像sandbox_image。默认值指向registry.k8s.io这个地址在部分网络环境下拉不动。我把它替换成阿里云镜像仓库里的 pause 镜像这是每个 Pod 启动前必须先拉取的“占位”镜像拉不到它Pod 就会一直ContainerCreating。这里的版本号3.10要和 Kubernetes 1.31 匹配一般不会有问题。3.4 Kubernetes 组件安装与 apt 源细节第 7、8 步添加的是阿里云的 Kubernetes apt 源。国内网络环境从packages.cloud.google.com拉包是肯定不行的阿里云镜像同步得比较及时1.31 的 kubeadm、kubelet、kubectl 都能直接装到。这里有一个容易被忽略的版本问题apt 源里的kubernetes-xenial这个代号并不是说你只能用 Ubuntu 16.04而是阿里云沿用了 Kubernetes 官方仓库的命名。Debian 12 和 Ubuntu 22.04 都能正常使用这个源。apt-mark hold这步很关键。如果不锁定版本以后执行apt-get upgrade时 kubelet、kubeadm、kubectl 可能被升级到更高版本而集群里的其他组件还没跟上版本不一致会导致控制面组件之间 API 兼容问题。锁住之后你要手动解除再升级这是一个好习惯。3.5 crictl 配置文件让调试工具找到运行时第 9 步写了一个/etc/crictl.yaml。这是给crictl命令行工具用的。Kubernetes 节点上调试容器时用docker命令已经行不通了因为没装 Docker得用crictl。但这个工具默认并不知道怎么连 containerd必须告诉它 socket 地址。配好之后crictl ps、crictl logs就能像docker ps、docker logs一样查看容器状态。这一步很多人漏掉导致排障时拿起crictl一执行就报错还以为工具没装好。4. 从 init 到 Ready单节点集群的完整启动路径4.1 kubeadm init 推荐参数背后的逻辑脚本执行完环境就已经就绪了。接下来初始化控制平面节点。这里不能直接裸执行kubeadm init需要加几个关键参数sudo kubeadm init \ --cri-socketunix:///run/containerd/containerd.sock \ --image-repositoryregistry.aliyuncs.com/google_containers \ --pod-network-cidr10.244.0.0/16第一个参数--cri-socket就是你明确告诉 kubeadm容器运行时在这个 socket 上别再去猜。这个参数其实可以省略因为 kubeadm 会自动探测但省略之后一旦系统里存在多个 socket比如装了 Docker探测结果就可能出错。我建议永远显式指定省心。第二个参数--image-repository指定从阿里云镜像仓库拉取控制面组件镜像包括 kube-apiserver、kube-controller-manager、kube-scheduler、etcd、coredns 等。不指定的话默认走registry.k8s.io网络慢的情况下要等很久。第三个参数--pod-network-cidr指定 Pod 网络地址段。这个网段必须和你后面要装的网络插件期望的网段一致。Flannel 默认用10.244.0.0/16Calico 默认用192.168.0.0/16。我这里的示例是为 Flannel 准备的。如果你后面决定用 Calico就把这个参数改成192.168.0.0/16并相应调整 Calico 的配置。4.2 网络插件选择Calico 与 Flannel 的实际对比这是搭建集群时绕不开的选择题。我给一个基于实际场景的对比维度FlannelCalico网络模型VXLAN/OverlayBGP 路由 Overlay 混合性能一般较好尤其适合大规模网络策略不支持支持 NetworkPolicy配置难度极低中等资源占用低中等偏高如果你只是搭一个测试环境、个人学习、跑几个示例应用Flannel 完全够用配置就是一条kubectl apply -f。如果你的集群要承担真实业务而且对 Pod 间通信延迟敏感、需要配置网络策略隔离租户那直接上 Calico。我自己的选择是单机测试用 Flannel多节点生产环境用 Calico。这样划分的原因是 Flannel 简单出问题好排查Calico 的 BGP 路由在跨节点通信时确实比 VXLAN 封包解包少了开销。4.3 单节点部署去污点与业务验证初始化成功后kubeadm 会输出一长串提示包括kubectl配置文件的拷贝命令和kubeadm join的命令。先执行配置mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config如果只有这一个节点那它同时扮演控制平面和工作节点的角色。但控制平面节点默认带了一个node-role.kubernetes.io/control-plane的污点普通 Pod 不会被调度上去。测试环境要允许调度业务 Pod就去掉这个污点kubectl taint nodes --all node-role.kubernetes.io/control-plane-然后安装网络插件以 Flannel 为例kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml等一两分钟kubectl get nodes看到Readykubectl get pods -n kube-flannel看到 Flannel Pod 处于Running控制平面就真正可用了。这时候可以跑一个 Nginx 验证端到端链路kubectl create deployment test-nginx --imagenginx kubectl expose deployment test-nginx --port80 --typeNodePort kubectl get svc test-nginx通过输出的 NodePort 端口访问宿主机 IP能看到 Nginx 欢迎页说明从客户端到 Service、再到 Pod、再到容器运行时整条链路都是通的。5. 工作节点加进来多机部署 join 全程记录5.1 工作节点的最小前置条件单节点集群能跑起来但真正的 Kubernetes 集群至少得有控制平面节点和工作节点。加工作节点的前置条件其实就是在另一台机器上把第 3 节那个脚本原样执行一遍。工作节点不需要初始化控制平面但需要同样的内核参数、swap 关闭、Containerd 安装和配置。你可以把这个脚本理解成“Kubernetes 节点通用准备脚本”Control Plane 和 Worker 节点都用它。加一个细节工作节点的主机名不要和控制平面重复最好也加入 hosts 解析方便后面排查跨节点通信。5.2 join 命令的正确获取方式初始化完成后控制平面那台机器上会显示 join 命令。但很多人当时没存下来没关系随时可以重新生成kubeadm token create --print-join-command输出类似kubeadm join 192.168.1.10:6443 --token xxxxx --discovery-token-ca-cert-hash sha256:xxxxx在工作节点上执行这条命令时同样需要显式指定--cri-socket因为工作节点上的 kubeadm 也会探测运行时。命令变成sudo kubeadm join 192.168.1.10:6443 \ --token xxxxx \ --discovery-token-ca-cert-hash sha256:xxxxx \ --cri-socketunix:///run/containerd/containerd.sock--discovery-token-ca-cert-hash是用来验证控制平面证书的指纹防止连到错误的节点。token 默认有效期是 24 小时过期后重新执行kubeadm token create即可。5.3 join 完成后的节点验证join 命令执行完工作节点上的 kubelet 会自动启动并尝试连接控制平面。回到控制平面执行kubectl get nodes -o wide如果看到工作节点状态是Ready说明加入成功。如果卡在NotReady最常见的两个原因是网络插件没有覆盖到新节点等几分钟一般会好或者工作节点的时间和控制平面不一致。这时候先去工作节点看 kubelet 日志journalctl -u kubelet -f日志里如果有 “failed to get sandbox image” 或者 “network plugin is not ready” 相关字样基本就是两个方向一是 cgroup 驱动没改成 systemd二是 Pod 网段冲突。两个问题我都遇到过排查思路放在下一节一起讲。6. Ready 之后我踩过的 5 个真实故障与排查链路6.1 故障一crictl 无法连接 socket现象执行crictl ps报错failed to connect: failed to dial: dial unix /var/run/dockershim.sock: connect: no such file or directory。原因crictl默认连接的还是旧版 dockershim 的 socket 路径。新版 containerd 的 socket 在/run/containerd/containerd.sock。如果你在脚本里配置了/etc/crictl.yaml就不会碰到这个问题。已经碰到的话手动创建/etc/crictl.yaml并写入 runtime-endpoint 即可。6.2 故障二Pod 一直 ContainerCreating现象kubectl get pods显示 Pod 一直ContainerCreatingkubectl describe pod里能看到类似failed to pull image registry.k8s.io/pause:3.10的警告。原因sandbox 镜像拉不下来。这个镜像在 Pod 创建时必须先拉取用于创建 Pod 的网络命名空间。国内网络环境下从registry.k8s.io拉取困难。解决办法就是我在脚本第 6 步做的那样把sandbox_image改成registry.aliyuncs.com/google_containers/pause:3.10。改完配置后记得systemctl restart containerd然后删除已有的 Pod 让它重新创建。6.3 故障三容器反复重启运行几秒就退出现象Pod 能创建但容器一直重启kubectl logs里看不到业务日志journalctl -u kubelet里有failed to create containerd task: failed to create shim task或OCI runtime create failed相关错误。原因大概率是 cgroup 驱动不一致。判定方法是在节点上执行cat /etc/containerd/config.toml | grep SystemdCgroup对照可得两个方向如果这里显示false改成true并重启 containerd如果已经是true再去控制面检查 kubelet 的 cgroup 驱动配置。只要 containerd 和 kubelet 两边的 cgroup driver 对齐这个问题就能解决。实际上我在新装的 1.31 集群里遇到过不止一次忘记改SystemdCgroup的情况所以脚本里特意加了这一步。6.4 故障四节点 NotReadyFlannel 或 Calico Pod 异常现象网络插件 Pod 处于CrashLoopBackOff节点状态一直是NotReady。原因最常见的是之前提到的 Pod CIDR 不一致。比如你kubeadm init时用了--pod-network-cidr192.168.0.0/16Calico 默认但后面装的 Flannel 默认逻辑是10.244.0.0/16两个网段对不上Flannel 无法分配子网节点就一直 NotReady。排查方法kubectl describe pod -n kube-flannel flannel-pod-name日志里一般会显示 mismatched networks或 failed to create subnetmanager之类的信息。解决办法就是卸载当前的网络插件改用与 init 参数匹配的另一个或者重新 init 时使用与网络插件匹配的网段。对于已经生产的集群不建议直接改 CIDR这时候更稳妥的做法是# 在控制平面删除网络插件 kubectl delete -f kube-flannel.yml # 使用与 init 参数一致的 Calico 安装 kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28/manifests/calico.yaml如果你最初 init 用的是10.244.0.0/16那装 Calico 时需要修改它的 ConfigMap把默认网段改成10.244.0.0/16。Calico 的默认规划是192.168.0.0/16不修改的话同样会出现子网冲突问题。这一步很容易被忽略。6.5 故障五kubeadm init 直接报错“Port 6443 is already in use”现象执行kubeadm init时提示Port 6443 is already in use或Port 10250 is already in use。原因这台机器上之前可能初始化过集群而旧的 kubelet 进程或 kube-apiserver 容器还在运行。尤其是测试机器很容易出现重复 init 的情况。排查方法ss -lntp | grep -E 6443|10250 # 找出占用端口的进程并处理 sudo kill -9 pid更彻底的做法是在重新 init 前执行kubeadm resetsudo kubeadm reset -f它会清理/etc/kubernetes下的配置文件、iptables 规则和 CNI 插件痕迹但同时也会把之前加入集群的状态清掉。执行完 reset 后重新开始kubeadm init就不会再撞端口了。最后说一个我自己长期使用的习惯每次部署完一套新集群我都会把kubeadm init或kubeadm join的完整命令存到/root/cluster-join-command.txt里。kubeadm 生成的 token 有效期短但过期的生成命令可以通过kubeadm token create --print-join-command快速补齐。另外给 containerd 改完配置后我习惯先执行ctr version确认 CRI 插件正常工作再继续下一步。这套部署流程经历了从 1.24 到 1.31 多个版本迭代踩过的坑基本都记录在案照着走一遍大概率能避免我当初熬过的那些夜。
返回列表