1. 问题现象与背景分析
最近在维护一个基于kubeadm部署的Kubernetes生产集群时,遇到了一个令人头疼的启动依赖问题。这个集群的底层容器运行时采用的是Docker,并通过cri-dockerd作为CRI接口来桥接kubelet与Docker。在服务器重启或关机再开机的场景下,控制平面组件(kube-apiserver、kube-controller-manager、kube-scheduler)经常无法自动恢复,导致整个集群处于不可用状态。
具体表现为:
- 服务器重启后,虽然已经配置了systemd依赖(After=cri-dockerd.service),但控制平面Pod仍然无法自动拉起
- 关机后再开机的场景下,问题更加严重,必须手动执行
systemctl restart docker cri-dockerd kubelet命令才能恢复集群 - 查看服务状态时,经常看到"activating (auto-restart)"的提示,表明服务在尝试自动恢复但未成功
2. 依赖关系与问题根源
2.1 组件依赖链条分析
要理解这个问题,首先需要理清各个组件之间的启动依赖关系:
code复制kubelet → cri-dockerd → docker
在这个依赖链中:
- kubelet是Kubernetes的节点代理,负责管理Pod和容器
- cri-dockerd是Docker的CRI接口实现,作为kubelet和Docker之间的桥梁
- Docker是实际的容器运行时
2.2 问题根本原因
经过多次测试和分析,发现问题主要出在以下几个方面:
-
systemd服务依赖配置不完整:虽然已经配置了kubelet对cri-dockerd的依赖,但未充分考虑所有可能的启动场景
-
关机与重启的差异:
- 服务器重启时,systemd会尝试保持服务间的依赖顺序
- 完全关机后再开机,systemd的依赖管理可能不会按预期工作
-
服务启动超时:某些服务在依赖服务未完全就绪时启动,导致超时失败
-
资源竞争:多个服务同时启动可能导致资源竞争,特别是网络资源
3. 解决方案与配置调整
3.1 修改kubelet服务配置
首先需要对kubelet的systemd服务文件进行调整:
bash复制vi /usr/lib/systemd/system/kubelet.service
修改内容如下:
ini复制[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=https://kubernetes.io/docs/
# 注释掉原有的网络依赖
#Wants=network-online.target
#After=network-online.target
# 添加对cri-dockerd的明确依赖
Wants=cri-dockerd.service
After=cri-dockerd.service
[Service]
ExecStart=/usr/bin/kubelet
Restart=always
StartLimitInterval=0
RestartSec=10
[Install]
WantedBy=multi-user.target
修改后执行:
bash复制systemctl daemon-reload
systemctl restart kubelet
3.2 增强cri-dockerd的依赖配置
同样需要确保cri-dockerd对Docker有正确的依赖关系:
bash复制vi /usr/lib/systemd/system/cri-dockerd.service
添加或修改以下内容:
ini复制[Unit]
Description=CRI Interface for Docker
Documentation=https://github.com/Mirantis/cri-dockerd
# 明确依赖Docker服务
Wants=docker.service
After=docker.service
[Service]
ExecStart=/usr/bin/cri-dockerd --container-runtime-endpoint fd://
Restart=always
StartLimitInterval=0
RestartSec=10
[Install]
WantedBy=multi-user.target
3.3 配置Docker服务
确保Docker服务本身配置正确:
bash复制vi /usr/lib/systemd/system/docker.service
检查并确保有以下配置:
ini复制[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
# 确保有基本的网络依赖
After=network-online.target firewalld.service
Wants=network-online.target
[Service]
Type=notify
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
Restart=always
StartLimitInterval=0
RestartSec=10
[Install]
WantedBy=multi-user.target
4. 彻底解决方案
4.1 创建启动顺序保障脚本
为了确保在关机再开机的场景下也能正常工作,可以创建一个systemd服务来协调启动顺序:
bash复制vi /usr/local/bin/k8s-startup-sequencer.sh
脚本内容:
bash复制#!/bin/bash
# 等待Docker服务完全启动
while ! systemctl is-active --quiet docker; do
sleep 1
done
# 等待cri-dockerd服务完全启动
while ! systemctl is-active --quiet cri-dockerd; do
sleep 1
done
# 最后启动kubelet
systemctl restart kubelet
设置可执行权限:
bash复制chmod +x /usr/local/bin/k8s-startup-sequencer.sh
创建systemd服务:
bash复制vi /etc/systemd/system/k8s-startup-sequencer.service
内容如下:
ini复制[Unit]
Description=Kubernetes Startup Sequencer
After=docker.service cri-dockerd.service
Requires=docker.service cri-dockerd.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/k8s-startup-sequencer.sh
[Install]
WantedBy=multi-user.target
启用服务:
bash复制systemctl daemon-reload
systemctl enable k8s-startup-sequencer.service
4.2 调整服务启动超时设置
某些情况下,服务启动超时可能导致问题,可以适当延长超时时间:
bash复制vi /etc/systemd/system.conf
修改或添加:
ini复制DefaultTimeoutStartSec=300s
DefaultTimeoutStopSec=120s
然后重新加载配置:
bash复制systemctl daemon-reload
5. 验证与测试
5.1 手动测试服务启动顺序
可以手动模拟重启过程来测试配置是否生效:
bash复制# 停止所有相关服务
systemctl stop kubelet cri-dockerd docker
# 按顺序启动服务
systemctl start docker
systemctl start cri-dockerd
systemctl start kubelet
# 检查服务状态
systemctl status docker cri-dockerd kubelet --no-pager -l
5.2 实际重启测试
进行实际的服务器重启测试:
bash复制reboot
重启后检查:
bash复制kubectl get nodes
kubectl get pods -A
systemctl status docker cri-dockerd kubelet --no-pager -l
5.3 关机再开机测试
进行完整的关机再开机测试:
bash复制shutdown -h now
开机后检查服务状态和集群健康状况。
6. 常见问题与排查技巧
6.1 服务启动失败排查
如果服务仍然无法自动启动,可以按以下步骤排查:
- 检查服务日志:
bash复制journalctl -u docker --no-pager -l
journalctl -u cri-dockerd --no-pager -l
journalctl -u kubelet --no-pager -l
- 检查依赖关系:
bash复制systemctl list-dependencies kubelet
- 检查服务启动顺序:
bash复制systemd-analyze plot > bootup.svg
6.2 网络问题排查
有时网络未就绪会导致问题:
bash复制# 检查网络接口
ip addr show
# 检查网络连接
ping -c 4 8.8.8.8
# 检查DNS解析
nslookup kubernetes.default
6.3 资源限制问题
检查系统资源使用情况:
bash复制# 查看内存使用
free -h
# 查看CPU使用
top
# 查看磁盘空间
df -h
7. 高级配置建议
7.1 使用systemd drop-in文件
为了避免直接修改主服务文件,可以使用systemd的drop-in配置:
bash复制mkdir -p /etc/systemd/system/kubelet.service.d
vi /etc/systemd/system/kubelet.service.d/10-dependencies.conf
内容:
ini复制[Unit]
Wants=cri-dockerd.service
After=cri-dockerd.service
然后重新加载:
bash复制systemctl daemon-reload
7.2 配置kubelet启动参数
可以调整kubelet的启动参数来改善启动行为:
bash复制vi /etc/default/kubelet
添加或修改:
ini复制KUBELET_EXTRA_ARGS="--runtime-request-timeout=10m --pod-infra-container-image=k8s.gcr.io/pause:3.6"
7.3 使用Kubernetes静态Pod
对于控制平面组件,可以考虑使用静态Pod方式部署:
bash复制vi /etc/kubernetes/manifests/kube-apiserver.yaml
确保有正确的重启策略:
yaml复制spec:
containers:
- name: kube-apiserver
# ...
restartPolicy: Always
8. 长期维护建议
-
定期检查服务依赖:在升级Kubernetes或Docker版本后,重新检查服务依赖关系
-
监控集群健康状态:设置监控告警,及时发现集群组件异常
-
文档化恢复流程:即使实现了自动恢复,也应保留手动恢复的文档
-
考虑使用更高可用架构:对于生产环境,建议使用多控制平面节点的高可用架构
-
定期测试重启场景:在维护窗口期定期测试服务器重启和关机场景,确保恢复机制始终有效
在实际生产环境中,我通过上述方法成功解决了服务器重启和关机后Kubernetes控制平面无法自动恢复的问题。关键点在于彻底理清各组件间的依赖关系,并通过systemd的精确控制确保启动顺序。同时,添加启动顺序协调脚本作为后备方案,可以应对更复杂的启动场景。