尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

RHEL 9.7搭建K8s v1.34:Docker+cri-dockerd实战

RHEL 9.7搭建K8s v1.34:Docker+cri-dockerd实战 先说结论在 RHEL 9.7 上搭 Kubernetes v1.34还要坚持用 Docker 作为运行时这个组合看起来有点“逆流而行”但实际场景里确实存在而且并不少。很多团队的镜像构建、CI/CD 流水线、开发自测环境全都绑定在 Docker 的工作流上突然让自己切换 containerd内部阻力比技术阻力大得多。这篇东西就是给这类团队看的围绕 RHEL 9.7、Kubernetes v1.34、Docker、cri-dockerd 以及国内源配置把一套能落地的集群搭建过程完整捋一遍。整个流程我自己实测过不只是把命令贴一遍还会把底层逻辑、容易踩的坑、排查思路讲清楚。适合三类人参考一是公司要求基础环境必须是 RHEL 的运维/平台工程师二是被 Kubernetes 官方“移除 dockershim”打乱节奏、不得不找方案的老 Docker 用户三是想搞懂 cri-dockerd 到底是怎么衔接 kubelet 和 Docker 的进阶学习者。1. 为什么这个组合值得搭Docker 与 Kubernetes 的适配逻辑1.1 v1.24 移除 dockershim 之后Docker 的生存之道Kubernetes 从 v1.24 开始正式移除了内置的 dockershim 组件。这个改动对很多人的认知冲击很大因为在此之前kubelet 可以直接通过 dockershim 调用 Docker 来创建和管理容器。移除之后kubelet 只认 CRIContainer Runtime Interface接口而 Docker 本身不实现 CRI想继续用 Docker 就得在中间加一层适配器。这个适配器就是 cri-dockerd主要由 Mirantis 维护。它的作用简单说就是对外提供 CRI 兼容的 gRPC 接口对内转换成 Docker API 调用让 kubelet 以为自己还是在跟一个标准 CRI 运行时通信而实际干活的是 Docker Engine。这里有个很容易混淆的点要理清Kubernetes 废弃的是 dockershim不是 Docker也不是 Docker 镜像。你现有的 Dockerfile、docker build 产物、docker-compose 编排这些通通不受影响。K8s 集群里用 cri-dockerd 跑起来的 Pod本质上是 Docker 容器镜像格式和 OCI 镜像完全兼容里面跑什么业务、用什么技术栈跟直接用 Docker 没有任何区别。选这个组合的一个核心动机是降低迁移成本。团队里如果已经有大量基于 Docker 的监控、日志采集、镜像扫描、安全加固等周边工具切到 containerd 意味着这些工具链全部要重新适配一遍。而通过 cri-dockerd 继续用 Docker这些存量资产一个都不用动。所以从工程效率角度讲这不是技术落后而是一种合理妥协。1.2 cri-dockerd 的工作机制与踩坑预警cri-dockerd 的工作机制可以简化成一条链路kubelet --(CRI gRPC)-- cri-dockerd --(Docker API)-- Docker Engine -- containerd/runc注意最后的 containerd/runc。Docker 自身也依赖 containerd 来管理容器生命周期所以这条链路上实际出现了两次 containerd 的影子一次在 Docker 内部一次是 cri-dockerd 转发过去的。性能上确实比 containerd 直连多一跳但这个损耗在绝大多数业务场景下微乎其微不值得为了那点理论性能把整个运维体系推翻。真正要留意的是下面这几个坑我搭建时全都遇到过cgroup 驱动不一致。kubelet 和容器运行时必须使用同一个 cgroup 驱动。RHEL 9 上 systemd 是默认 init 系统Docker 默认却用 cgroupfs两者不一致会导致 Pod 创建失败或节点状态异常。解决办法是在 Docker daemon 配置里显式指定native.cgroupdriversystemd。pause 镜像拉取失败。Kubernetes 创建 Pod 之前要先启动一个 infra 容器pause这个镜像是集群运行的基础。国内网络环境下从官方仓库拉取基本不可行需要在 cri-dockerd 启动参数里指定国内镜像地址。Docker 与 Podman 的冲突。RHEL 9 默认带了部分容器管理工具最典型的是 podman。如果系统里已经装了 podman-docker它会跟 Docker 抢/usr/bin/docker这个路径造成命令都被 podman 接管排查起来非常隐蔽。系统仓库里没有 cri-dockerd 包。RHEL 的 AppStream 和 EPEL 都不提供 cri-dockerd只能从 GitHub Releases 手动下载 RPM或者自己编译。很多人在这一步就卡住了误以为缺少什么前置依赖。把这些坑提前排掉后面整个流程就会顺很多。2. 开工前把环境调对RHEL 9.7 系统初始化与软件源替换2.1 规划节点与基础系统配置这次搭建我准备了两台节点一台控制平面一台工作节点如果你只有一台机器也可以把控制平面设为可调度来模拟多节点但生产环境不建议这么干。硬件建议控制平面至少 2 核 4G 内存工作节点至少 2 核 2G磁盘容量看镜像数量一般 50G 起步比较宽裕。角色主机名IP 示例配置建议控制平面master01192.168.10.104C8GSSD工作节点worker01192.168.10.114C8GSSD系统安装完成后第一件事是确认 RHEL 9.7 的软件仓库可用。如果你有 Red Hat 订阅直接subscription-manager register注册即可如果没有订阅可以申请 Red Hat 开发者计划里的免费订阅或者用 Rocky Linux 9 作为兼容替代发行版命令和配置基本通用。接着调整主机名、关闭交换分区、加载内核模块这几个基础操作我这边直接给出一组命令你在每台节点上都要执行# 设置主机名按实际角色调整 hostnamectl set-hostname master01 # 关闭 swapkubeadm 的要求 swapoff -a sed -i /swap/s/^/#/ /etc/fstab # 加载桥接过滤模块iptables 处理 Pod 流量需要 modprobe br_netfilter cat /etc/modules-load.d/k8s.conf EOF br_netfilter EOF # 内核参数调整 cat /etc/sysctl.d/k8s.conf EOF net.bridge.bridge-nf-call-iptables 1 net.ipv4.ip_forward 1 net.bridge.bridge-nf-call-ip6tables 1 EOF sysctl --systemnet.ipv4.ip_forward这个参数很多人会忽略它是 Pod 之间、Pod 与外部通信的基础不开的话集群网络会出一堆诡异问题。bridge-nf-call-iptables则是保证经过 Linux 网桥的流量能被 iptables 规则正确过滤Service 的 ClusterIP 转发依赖它。SELinux 这块RHEL 上默认是 enforcing 模式。Kubernetes 和 Docker 理论上都能在 enforcing 模式下运行但需要配置大量布尔值和文件上下文维护成本不低。我这次为了把精力聚焦在集群搭建上选择把 SELinux 切到 permissivesetenforce 0 sed -i s/^SELINUXenforcing/SELINUXpermissive/ /etc/selinux/config注意一点permissive 只是记录告警不强制阻断系统审计功能依然在工作应用层安全并不会因此完全失守很多生产环境实际上也是这么跑的。如果你所在的安全部门要求必须 enforcing那需要额外配置 Docker 的文件上下文这篇文章不做展开。防火墙方面如果你在云上安全组规则要放行下面这些端口。如果是本地虚拟机直接关掉 firewalld 最省事systemctl stop firewalld systemctl disable firewalld这里给出端口清单方便你在云控制台配置安全组时参考协议端口用途TCP6443Kubernetes API ServerTCP2379-2380etcd 客户端与对等通信TCP10250kubelet 指标与 execTCP10259kube-schedulerTCP10257kube-controller-managerTCP30000-32767NodePort 服务端口段TCP/UDP8472VXLANCalico 默认2.2 国内 yum 源、Docker 源与 Kubernetes 源替换RHEL 如果走官方订阅源在国内的访问速度大概率很难受。这里提供一个通用做法把仓库文件指向阿里云镜像。RHEL 9 与 Rocky Linux 9 包级兼容直接用 Rocky 9 的仓库文件替换官方源是可行且广泛使用的做法。操作前先备份原有仓库配置mkdir -p /etc/yum.repos.d/backup mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup/然后新建一个rocky.repo内容如下[BaseOS] nameRocky Linux $releasever - BaseOS baseurlhttps://mirrors.aliyun.com/rockylinux/9/BaseOS/$basearch/os/ gpgcheck1 enabled1 gpgkeyhttps://mirrors.aliyun.com/rockylinux/9/BaseOS/$basearch/os/RPM-GPG-KEY-Rocky-9 [AppStream] nameRocky Linux $releasever - AppStream baseurlhttps://mirrors.aliyun.com/rockylinux/9/AppStream/$basearch/os/ gpgcheck1 enabled1 gpgkeyhttps://mirrors.aliyun.com/rockylinux/9/AppStream/$basearch/os/RPM-GPG-KEY-Rocky-9 [Extras] nameRocky Linux $releasever - Extras baseurlhttps://mirrors.aliyun.com/rockylinux/9/extras/$basearch/os/ gpgcheck1 enabled1 gpgkeyhttps://mirrors.aliyun.com/rockylinux/9/extras/$basearch/os/RPM-GPG-KEY-Rocky-9这里不建议直接写$releasever因为 RHEL 9.7 的$releasever会解析成 “9.7”而 Rocky Linux 镜像目录通常只同步到大的 9.x 系列目录直接写成9能避免因为镜像站点还没来得及同步 9.7 目录而出现的 404。替换源后执行yum clean all yum makecache能顺利列出软件包就说明源没问题。接着添加 Docker 和 Kubernetes 的官方镜像源# Docker CE 源 yum install -y yum-utils yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/rhel/docker-ce.repo # Kubernetes 源路径里直接带 v1.34 cat /etc/yum.repos.d/kubernetes.repo EOF [kubernetes] nameKubernetes baseurlhttps://mirrors.aliyun.com/kubernetes-new/core/stable/v1.34/rpm/ enabled1 gpgcheck1 repo_gpgcheck0 gpgkeyhttps://mirrors.aliyun.com/kubernetes-new/core/stable/v1.34/rpm/repodata/repomd.xml.key EOF # EPEL 源有些编译工具链会用到 yum install -y epel-release这里有个细节阿里云的 Kubernetes 源如果直接配gpgcheck1有时候会因为 key 路径变化导致报错。如果yum install时提示公钥校验失败把gpgcheck0和repo_gpgcheck0临时关掉就可以内网离线环境下很多人也是这么干的。Docker 源和 Kubernetes 源添加完成后执行一次yum repolist确认三个源都处于 enabled 状态。到这里系统层面的基础准备就算做完了。3. 运行时层搭建Docker cri-dockerd 的安装与联动3.1 安装 Docker Engine 并配置镜像加速与 cgroup 驱动直接用上一步配好的 docker-ce 源安装yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin这里会带上 containerd.io 这个包。有人会疑惑Docker 不是自带 containerd 吗为什么还要单独装因为 Docker 调用容器运行时时需要独立的 containerd 二进制和管理工具docker-ce源里默认拆出来单独发版了装就对了。启动前先把 daemon.json 写好{ exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 }, registry-mirrors: [ https://your-id.mirror.aliyuncs.com ] }重点说两个配置项。native.cgroupdriversystemd是必须的。前面提过systemd 已经是 RHEL 9 的 init 系统Docker 如果不改成 systemd cgroup 驱动kubelet 默认用 systemd 驱动两边一碰撞Pod 沙箱创建就会报 cgroup 相关的错误。这个错误不会在 Docker 日志里出现而是表现在 kubelet 日志和kubectl get nodes显示 NotReady。registry-mirrors需要填你自己在阿里云容器镜像服务控制台申请到的专属加速地址一般形如https://xxxx.mirror.aliyuncs.com。现在公共的 Docker 加速器很多都关了或者限流了自己申请一个地址比较可靠。如果你所在的网络环境能直连 Docker Hub这步可以跳过但按我的经验不配置的话后面拉pause、coredns这等基础镜像分分钟超时。启动 Docker 并验证systemctl start docker systemctl enable docker # 验证 cgroup driver docker info | grep -A2 Cgroup输出里应该同时看到Cgroup Driver: systemd和Cgroup Version: 2。RHEL 9 默认使用 cgroup v2这个组合没问题。3.2 编译或下载 cri-dockerd版本选择与安装验证接下来装 cri-dockerd。先到 GitHub 的 Mirantis/cri-dockerd Releases 页面找到对应 RHEL 9 的 RPM 包文件名一般是cri-dockerd-0.3.x-0.el9.x86_64.rpm这种格式。下载后直接安装wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.20/cri-dockerd-0.3.20-0.el9.x86_64.rpm yum install -y ./cri-dockerd-0.3.20-0.el9.x86_64.rpm如果 GitHub 下载不稳定可以找一台能访问 GitHub 的机器先拉下来再通过内网传过去。也可以用源码编译安装但需要 Go 工具链和额外的依赖全部拉下来编译一遍时间成本不小非特殊情况不推荐。安装完成后cri-dockerd 会注册一个 systemd 服务但默认的启动参数不太适合国内环境。重点调整的是 pause 镜像地址。Kubernetes 每个 Pod 都会先创建一个 pause 容器这个镜像如果从官方地址拉基本必失败。建议改掉 service 文件里的--pod-infra-container-image参数。先看一下当前启动参数systemctl cat cri-docker输出里找到ExecStart那一行然后编辑 service 文件vim /usr/lib/systemd/system/cri-docker.service把 ExecStart 改成类似这样ExecStart/usr/bin/cri-dockerd --container-runtime-endpoint fd:// --pod-infra-container-image registry.aliyuncs.com/google_containers/pause:3.10关于 pause 的具体版本不同的 Kubernetes 小版本对应的镜像 tag 可能不一样。一个稳妥的做法是等后面 kubeadm 装完后用kubeadm config images list查一下它期望的 pause 版本回头再改这里。如果你图省事就直接写pause:3.10Kubernetes v1.28 到 v1.34 之间的大多数版本都认这个 tag。改完后重新加载并启动systemctl daemon-reload systemctl start cri-docker systemctl enable cri-docker # 验证 socket 文件已经生成 ls -l /var/run/cri-docker.sock看到cri-docker.sock存在就说明 cri-dockerd 已经在正常监听了。这个 socket 就是之后 kubelet 要连接的 CRI 端点。3.3 cgroup 驱动一致性校验Docker 与 kubelet 之间的隐藏雷区上一节提到 cgroup 驱动不一致会导致节点 NotReady这里展开讲一下因为这是 Docker cri-dockerd 方案里最容易踩的坑而且报错信息非常有迷惑性。当 kubelet 用 systemd cgroup 驱动、Docker 用 cgroupfs 时kubelet 在创建 Pod 沙箱时会调用 cri-dockerdcri-dockerd 再调用 Docker 创建容器。systemd 管理的进程 cgroup 路径在/sys/fs/cgroup/system.slice/下面而 Docker 默认会把容器放到/sys/fs/cgroup/docker/下面。两边对进程的 cgroup 归属判断不一致kubelet 就会认为容器沙箱没有正常启动节点状态变成 NotReady。典型的错误日志长这样failed to create pod sandbox: rpc error: code Unknown desc failed to start sandbox container这条日志不会直接告诉你“cgroup 驱动不一致”很多人会误以为是网络问题或者镜像问题排查半天。所以最稳妥的方式是在安装阶段就固定好两边的配置Docker 的/etc/docker/daemon.json里配置native.cgroupdriversystemd。kubelet 的配置里指定cgroupDriver: systemdRHEL 9 的 kubeadm 默认就是 systemd不用特意改。装完 Docker 后可以做一次快速校验docker info | grep -i cgroup如果显示Cgroup Driver: systemd并且和你待会儿kubeadm init时的配置一致这个雷就算排掉了。4. 控制平面部署kubeadm 初始化与 Pod 网络插件4.1 安装 kubeadm、kubelet、kubectl版本锁定很关键接下来安装 Kubernetes 核心组件。用前面配置好的 Kubernetes 源yum install -y kubelet kubeadm kubectl这里强烈建议加一个版本锁定操作防止未来某次yum update把 kubelet 升到其他版本和集群整体版本脱节yum install -y kubelet-1.34.* kubeadm-1.34.* kubectl-1.34.* # 或者安装后锁定版本 yum versionlock kubelet kubeadm kubectlyum versionlock这个命令来自yum-plugin-versionlock包如果你系统里没装先yum install -y yum-plugin-versionlock。在管理多套 K8s 集群时版本锁定能帮你省掉无数个凌晨的故障电话。安装完后把 kubelet 设成开机自启但先不要启动它。kubelet 需要 kubeadm init 或 join 生成的配置文件才能正常工作提前启动只会刷一堆报错日志systemctl enable kubelet接着验证一下 manifest 里的版本是否符合预期kubeadm version输出应该是kubeadm version: version.Info{Major:1, Minor:34, ...}这样。4.2 kubeadm init 参数解析镜像仓库、Pod 网段与 cri-socket在控制平面节点上执行初始化。建议先用默认配置生成一份 init 配置文件再按实际网络环境修改kubeadm config print init-defaults kubeadm-init.yaml打开文件看一下重点修改三个字段imageRepository默认是registry.k8s.io国内基本拉不动改成registry.aliyuncs.com/google_containers。localAPIEndpoint.advertiseAddress改成控制平面节点的实际 IP。nodeRegistration.criSocket改成unix:///var/run/cri-docker.sock。另外如果你不希望手动去算 Pod 网段和 Service 网段可以在ClusterConfiguration里加上networking: podSubnet: 192.168.0.0/16 serviceSubnet: 10.96.0.0/12podSubnet这个取值要和你后面选择的网络插件保持一致。我这里打算用 Calico它默认的 IP Pool 就是192.168.0.0/16所以这里直接复用后面部署 Calico 时就不用改配置文件了。有一个参数需要特别注意不要在你的公司内网、VPC 网段里选跟你业务重叠的地址段。比如你的服务器本身就在192.168.0.0/16网段内那 Pod 网段就必须换掉否则路由冲突会非常难排查。换一个不冲突的私有网段即可比如10.244.0.0/16。修改完成后执行kubeadm init --config kubeadm-init.yaml如果一切正常尾部会输出一段内容包含kubeconfig配置命令和工作节点加入集群的kubeadm join命令。这段 join 命令建议复制保存下来后面工作节点入群要用。init 过程会拉取一组镜像包括 API Server、Controller Manager、Scheduler、etcd、CoreDNS、pause 等。因为我们已经把imageRepository指到了阿里云这一步基本能顺利通过。如果极个别镜像还是拉取超时可以用kubeadm config images pull --config kubeadm-init.yaml单独拉取排查网络问题。初始化完成后按输出提示配置 kubeconfigmkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config这里顺带提一下 kubectl 的补全配置后面用起来会舒服很多echo source (kubectl completion bash) ~/.bashrc kubectl completion bash /etc/bash_completion.d/kubectl4.3 部署 Calico 网络插件为什么选择它以及配置要点节点初始化成功了不代表集群可用CNI 网络插件不装的话节点的状态会一直是 NotReady。我这次选了 Calico原因是它在功能性、稳定性上对生产环境更友好支持 NetworkPolicy而且 BGP 模式下性能表现不错。安装方式仍然用 yaml 文件部署但建议先拉到本地再 apply避免在线 apply 时因为 GitHub Raw 不稳定而失败wget https://raw.githubusercontent.com/projectcalico/calico/master/manifests/calico.yaml kubectl apply -f calico.yamlCalico 安装完成后会以 DaemonSet 的方式在每个节点各启动一个calico-nodePod。检查状态kubectl get pods -n kube-system -w如果你按上面的建议把podSubnet设成了192.168.0.0/16那 calico.yaml 不需要任何修改默认配置直接可用。如果改了其他网段要在 calico.yaml 里找到CALICO_IPV4POOL_CIDR这个环境变量改成你的 Pod 网段。这里解释一下 Calico 的工作边界它负责给每个 Pod 分配 IP同时承担 Pod 之间、节点之间的路由工作。RHEL 9 上如果之前动过 firewalld 和 iptablesCalico 的 BPF 或 iptables 模式可能会被干扰所以我在前面的准备阶段直接建议关掉 firewalld。如果你用的是云主机且启用了安全组还要确认安全组放行了 TCP/UDP 8472 端口VXLAN 模式或 BGP 的 179 端口BGP 模式。默认情况下 Calico 用 IPIP 或 VXLAN 封装8472 端口必须放行。等所有calico-nodePod 都进入 Running 状态后再查看节点状态kubectl get nodes如果显示Ready控制平面部分就算大功告成了。4.4 工作节点入群三件套安装与 join 细节工作节点的准备流程和控制平面基本一样但不用kubeadm init。需要在 worker01 上完成这些操作系统初始化关闭 swap、加载内核模块、设置 SELinux permissive和第 2 章内容一致。安装并配置 Docker cri-dockerd和第 3 章内容一致。安装 kubeadm、kubelet、kubectl和第 4.1 节内容一致同样建议锁版本。都完成后用控制平面 init 时输出的 join 命令执行。默认的 join 命令如果没带 cri-socket 参数建议手动追加kubeadm join 192.168.10.10:6443 --token xxx \ --discovery-token-ca-cert-hash sha256:xxx \ --cri-socket unix:///var/run/cri-docker.sock--cri-socket这个参数很关键。kubeadm 默认会优先探测containerd.sock如果探测到了 containerd而你的容器运行时实际是 Docker cri-dockerd那 join 之后 kubelet 以为自己在用 containerd结果 Pod 创建失败节点状态也会异常。手动指定 cri-socket 能彻底规避这个问题。join 成功后在控制平面节点执行kubectl get nodes应该能看到 master01 和 worker01 都是 Ready 状态。5. 集群自检与常见故障排查5.1 验证集群功能从节点状态到跑一个业务 Pod集群搭起来之后不要急着把业务往里搬先把集群功能做一轮自检。我的习惯是分三层验证第一层节点和系统组件状态检查kubectl get nodes -o wide kubectl get pods -n kube-system -o wide节点状态应该全是 Readykube-system 下的所有 Pod 都应该处于 Running 或者 Completed 状态。如果你发现某个 Pod 一直 CrashLoopBackOff或者 ImagePullBackOff大概率是镜像拉取问题后面的 5.3 节会详细说。第二层DNS 功能验证。CoreDNS 是集群内部服务发现的基础需要确认它能正常解析 Service 名称。用 busybox 或直接部署一个临时 Pod 来做 DNS 查询kubectl run -it --rm test-dns --imagebusybox:1.28 --restartNever -- nslookup kubernetes.default.svc.cluster.local如果能解析出 ClusterIP说明 DNS 链路正常。这里注意 busybox 的 nslookup 版本有的版本解析行为略有差异实在不行换dig工具或者临时跑一个 alpine 加dig。第三层业务 Pod 和 Service 联通性验证。部署一个简单的 nginxkubectl create deployment nginx --imagenginx:latest kubectl expose deployment nginx --port80 --target-port80 --typeNodePort kubectl get svc nginx用输出的 NodePort 在外部浏览器或 curl 访问一下http://192.168.10.10:3xxxx。能访问就说明 Pod 创建、Service 转发、节点端口映射整条链路都是通的。5.2 常见故障清单识别原因比执行命令更重要搭建过程中最花时间的是排障这里把典型的故障现象和根因对应起来帮你快速定位问题。现象最常见原因排查命令kubectl get nodes显示 NotReadyCNI 网络插件没装或异常kubectl get pods -n kube-system -o widekubelet 日志报failed to create pod sandboxcgroup 驱动不一致或 pause 镜像拉不到docker info | grep -i cgroup、journalctl -u kubelet -fjoin 后节点一直 NotReadycri-socket 指向了不存在的 containerd socketjournalctl -u kubelet -f、确认/var/run/cri-docker.sock存在控制平面组件 Pod 反复重启镜像拉取失败或被防火墙拦截kubectl describe pod -n kube-system pod-name跑业务 Pod 一直 Pending节点资源不足或 nodeSelector 不匹配kubectl describe pod pod-name外部访问 NodePort 超时安全组或本地防火墙未放行端口段ss -tlnp | grep 3xxxx这些命令里kubectl describe和journalctl -u kubelet -f用的频率最高故障排查时第一个想到的应该就是它们。5.3 拉镜像失败的排查链路一个典型排障案例以最典型的镜像拉取失败为例完整走一遍排查链路。第一次看到ImagePullBackOff时先kubectl describe pod这个输出里会直接带上拉取失败的错误代码。常见错误大概是Failed to pull image registry.k8s.io/pause:3.10: failed to resolve reference看到这类信息就说明是网络或者镜像源不可达。接下来按这个顺序排查在节点上用docker pull registry.aliyuncs.com/google_containers/pause:3.10手动拉一次验证镜像仓库是否可达。如果手动拉也不通检查 Docker 的registry-mirrors配置是否正确。如果手动拉能通说明问题在 cri-dockerd 的 pause 镜像地址配置上检查/usr/lib/systemd/system/cri-docker.service里的--pod-infra-container-image。如果镜像地址和配置都没问题看 cri-dockerd 日志journalctl -u cri-docker -f日志里如果有connect: network is unreachable多半是节点出网路由或 DNS 有问题。整个排查链路走一趟绝大多数镜像问题都能定位。很多时候不是网络真不通而是配置里的域名写错了或者镜像 tag 跟 kubelet 期望的不一致。5.4 日常维护里容易被忽略的时间同步问题Kubernetes 集群对时间同步的要求非常高。这应该算是我最近几年排障经验里最能省时间的一条经验了因为它的症状非常隐蔽。平时跑着的集群某天突然出现证书验证失败、kubelet 报 x509 错误、Pod 调度异常第一反应往往是证书过期了或者网络出问题查了一圈才发现是节点时间漂移了。Kubernetes 的组件之间大量使用 TLS 证书进行双向认证时间偏移超过 5 分钟证书有效期验证就会出问题而且是随机性的问题跟你访问 API Server 的时机有关。解决方式很成熟用 chrony 做时间同步。RHEL 9 自带 chrony检查一下状态systemctl status chronyd timedatectl如果没启用启动并设置开机自启systemctl start chronyd systemctl enable chronyd timedatectl set-ntp true集群里的所有节点控制平面和工作节点都必须指向同一个时间源否则还是会出现时间差。企业内网可以搭一台 NTP 服务器让所有节点指向它云上环境一般可以直接用云平台提供的 NTP 服务。时间同步的事放到“运维经验”里说往往被当成可有可无的提醒但我在实际工作里因为这个问题熬过不止一次夜。搭完集群顺手看一眼时间同步状态这是成本最低的一个检查项。最后再分享一个实际使用中发现的小技巧cri-dockerd 方案下Docker 的日志轮转一定要配置好。Kubernetes 节点上容器日志增长很快如果 daemon.json 里不设置log-opts的max-size和max-file短期内可能没事跑上几个月之后/var/lib/docker/containers目录会大得吓人极端情况下会直接打满数据盘进而影响 kubelet 的稳定性。所以我在第 3 章的 daemon.json 里专门加了日志轮转配置这两行配置建议不要省。整套流程走下来基于 RHEL 9.7 的 Kubernetes v1.34 集群配合 Docker 运行时和 cri-dockerd 适配层再加上国内镜像源配置就是一个可以稳定运行的基础环境。后面你再做镜像仓库对接、监控系统部署、日志采集方案都有了一个干净的底座不会再被底层运行时的问题反复绊住。
返回列表