DevOps 项目阶段笔记二:Docker Engine + cri-dockerd

项目Gitee地址

1. 本阶段目标

上一阶段已经完成 VM 基础初始化、SSH 免密、Ansible Inventory、静态 IP、Kubernetes 节点 swap/内核参数等配置。

本阶段从 Docker 开始,原计划完成:

Ansible 自动安装 Docker Engine
        ↓
配置 Docker systemd cgroup driver
        ↓
在 Kubernetes 节点安装 cri-dockerd
        ↓
验证 Docker
        ↓
验证 cri-dockerd
        ↓
为后续 Kubernetes 安装准备 CRI


Kubernetes (K8s)
    │
    ├── 需要容器运行时(Container Runtime)来运行 Pod 里的容器
    │
    └── CRI(Container Runtime Interface)
            │  ← Kubernetes 定义的统一接口标准
            │
     ┌──────┴──────┐
     │             │
  containerd     cri-dockerd
     │             │
     │             └── 调用 Docker Engine(dockerd)
     │                         │
     │                         └── 最终管理容器
     │
  (直接通过 CRI 与 K8s 通信,效率更高)

一句话总结:

  • Docker = 底层引擎,会干活但不讲 K8s 的语言
  • CRI = K8s 规定的通用语言标准
  • cri-dockerd = 翻译官,把 K8s 的指令转成 Docker 能听懂的命令

所以你的流程本质上是:装了个只会说英语的人(Docker),再给他配个翻译(cri-dockerd),让他能跟只说中文的老板(K8s)沟通。

节点规划:

IP主机名角色
192.168.205.141k8s-masterMaster
192.168.205.142k8s-node1Worker
192.168.205.143k8s-node2Worker
192.168.205.144devopsDevOps 工具
192.168.205.145elkELK 日志

Docker 计划安装到: k8s-master, k8s-node1, k8s-node2, devops

cri-dockerd 只安装到: k8s-master, k8s-node1, k8s-node2

ELK 节点本阶段不安装 Docker。


2. 本阶段架构设计

Kubernetes 不直接使用 Docker Engine 作为 CRI,因此本项目采用:

kubelet
    ↓
cri-dockerd
    ↓
Docker Engine

后续 kubeadm 初始化时计划使用:

unix:///run/cri-dockerd.sock

Docker cgroup driver 统一计划配置为:

systemd

后续 kubelet 也将保持:

systemd

目标是保证:

Docker cgroup driver  = systemd
kubelet cgroup driver = systemd

3. 本阶段新增文件

Ansible 项目目录新增:

~/devops-lab/ansible/
├── ansible.cfg
├── init.yml
├── network.yml
├── docker.yml          ← 新增
├── inventory/
│   └── hosts.ini
└── scripts/
    ├── init.sh
    ├── docker-install.sh          ← 新增
    └── cri-dockerd-install.sh     ← 新增

本阶段主要文件:

  • docker.yml
  • scripts/docker-install.sh
  • scripts/cri-dockerd-install.sh

4. Docker 安装方案

4.1 docker-install.sh

Docker 安装通过 scripts/docker-install.sh 统一执行。

脚本主要工作:

检查 Ubuntu 系统
    ↓
处理可能冲突的软件包
    ↓
安装 ca-certificates / curl / gnupg
    ↓
配置 Docker APT repository

5. Docker daemon 配置

Docker 配置文件:/etc/docker/daemon.json

使用的核心配置:

{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  },
  "storage-driver": "overlay2"
}

其中最重要的是:

"exec-opts": ["native.cgroupdriver=systemd"]

用途: 将 Docker cgroup driver 设置为 systemd,与 kubelet 保持一致。

另外 Docker 日志采用 json-file,并限制单个日志文件 max-size=100mmax-file=3,避免实验环境中容器日志无限增长。


6. cri-dockerd 安装方案

使用版本: cri-dockerd 0.4.4

下载文件: cri-dockerd-0.4.4.amd64.tgz

下载地址:

https://github.com/Mirantis/cri-dockerd/releases/download/v0.4.4/cri-dockerd-0.4.4.amd64.tgz

SHA256:

109b7540053a507dd85ad7b9fee9cee9caae55feedcc28365d3e7ab4fb2172d5

安装后的二进制路径: /usr/local/bin/cri-dockerd

计划生成 systemd 单元:

  • /etc/systemd/system/cri-docker.service
  • /etc/systemd/system/cri-docker.socket

最终 CRI Socket: /run/cri-dockerd.sock

Kubernetes 后续使用: unix:///run/cri-dockerd.sock


7. Ansible Playbook 设计

docker.yml 分成两个 Play。

Play 1: Docker

目标组: hosts: "k8s_nodes:devops_servers"

也就是:k8s-master, k8s-node1, k8s-node2, devops

主要任务:

  1. 运行 docker-install.sh
  2. 加入 docker group
  3. 检查 docker.service
  4. 检查 Docker cgroup driver
  5. 输出状态

由于当前使用 Ansible 2.9.6,script 模块继续使用 free-form 写法:

script: "./scripts/docker-install.sh"

没有使用较新版本的:

script:
  cmd: ...

Play 2: cri-dockerd

目标组: hosts: k8s_nodes

即:k8s-master, k8s-node1, k8s-node2

主要任务:

  1. 运行 cri-dockerd-install.sh
  2. 检查 cri-docker.service
  3. 检查 cri-docker.socket
  4. 检查 /run/cri-dockerd.sock
  5. 输出 CRI 状态

8. 问题一:docker.yml YAML 语法错误

问题现象

第一次运行:

ansible-playbook \
  -i inventory/hosts.ini \
  docker.yml \
  --syntax-check

报错:

ERROR! Syntax Error while loading YAML.
mapping values are not allowed in this context

定位:docker.yml: line 25, column 47

问题行:

-name: Check Docker cgroup driver
shell: docker info 2>/dev/null| awk -F':' '/Cgroup Driver/{print$2}'

错误位置:-F': 后面的 : 被 YAML 误解。

原因

Shell 命令本身没有问题,问题来自 YAML 解析。命令中 : 即冒号后面跟空格,YAML 会尝试将它解释成 key: value,因此一整行没有被正确识别为普通 shell 字符串。

修复

改用 YAML folded scalar:

- name: Check Docker cgroup driver
  shell: >
    docker info 2>/dev/null | awk -F':' '/Cgroup Driver/{print$2}'
  register: docker_cgroup
  changed_when: false
  failed_when: docker_cgroup.stdout != "systemd"

之后继续执行 Playbook。

实践经验

复杂 Shell 命令中如果包含 : . " ${} 等符号,在 Ansible YAML 中最好避免直接写成长单行。优先使用 shell: >shell: | 的多行写法。简单命令仍然可以写成单行,如:

shell: systemctl is-active docker

9. 问题二:Install cri-dockerd 阶段疑似卡住

问题现象

执行 docker.yml 时出现:

TASK [Install cri-dockerd]
changed: [k8s-node1]
changed: [k8s-node2]

之后长时间没有新的输出。

此时可以确认:

  • k8s-node1 cri-dockerd task 已返回 changed ✓
  • k8s-node2 cri-dockerd task 已返回 changed ✓
  • k8s-master 尚未正常返回

最初怀疑:master 下载 GitHub release 太慢。


10. 对 master 的第一次排查

登录 k8s-master,检查:

ps -ef | grep -E 'curl|cri-dockerd|systemctl' | grep -v grep

结果:无输出

说明当时 master 上不存在明显的 curl、cri-dockerd、systemctl 安装进程。

进一步执行:

ps -ef | grep -E 'docker-install|cri-dockerd-install|ansible|python|bash|sh|apt|dpkg|tar|gpg' | grep -v grep

没有发现正在执行的 cri-dockerd-install.sh、curl、tar、sha256sum、apt、dpkg。

但发现 SSH/SFTP 相关进程,例如:

sshd: master@notty
/usr/lib/openssh/sftp-server

11. 确认 master 实际没有安装成功

在 master 上检查:

/usr/local/bin/cri-dockerd --version

结果:

-bash: /usr/local/bin/cri-dockerd: No such file or directory

检查服务:

sudo systemctl is-active cri-docker.service

结果:inactive

检查 socket:

sudo systemctl is-active cri-docker.socket

结果:inactive

检查 Unix Socket:

ls -l /run/cri-dockerd.sock

结果:

ls: cannot access '/run/cri-dockerd.sock': No such file or directory

由此确认: k8s-master 的 cri-dockerd 当时实际上没有安装成功,而不是"已经安装成功,只是 Ansible 没返回"。


12. 排查 GitHub 下载是否存在问题

由于安装脚本需要从 GitHub 下载 cri-dockerd-0.4.4.amd64.tgz,因此在 master 上进行独立下载测试。

进入 /tmp,执行:

curl -fL \
  "https://github.com/Mirantis/cri-dockerd/releases/download/v0.4.4/cri-dockerd-0.4.4.amd64.tgz" \
  -o cri-dockerd-test.tgz

实际结果:

100  15.5M  100  15.5M

下载大约数秒完成。

说明:

  • GitHub 可访问 ✓
  • DNS 正常 ✓
  • Release URL 正常 ✓
  • 下载速度正常 ✓

因此排除了"master 因 GitHub 下载过慢导致卡死"这一判断。


13. 校验下载文件

执行:

sha256sum /tmp/cri-dockerd-test.tgz

实际输出:

109b7540053a507dd85ad7b9fee9cee9caae55feedcc28365d3e7ab4fb2172d5  /tmp/cri-dockerd-test.tgz

与脚本预期 SHA256 完全一致:

109b7540053a507dd85ad7b9fee9cee9caae55feedcc28365d3e7ab4fb2172d5

因此进一步排除:

  • 下载文件损坏 ✗
  • 版本 URL 错误 ✗
  • checksum 错误 ✗

14. 对下载命令进行健壮性优化

最初脚本中的下载命令:

curl -fL \
  "${DOWNLOAD_URL}" \
  -o cri-dockerd.tgz

为了避免未来网络异常时长时间无响应,修改为:

curl -fL \
  --connect-timeout 10 \
  --max-time 300 \
  --retry 3 \
  --retry-delay 5 \
  "${DOWNLOAD_URL}" \
  -o cri-dockerd.tgz

含义:

参数含义
--connect-timeout 10连接建立最多等待 10 秒
--max-time 300整个 curl 最多运行 300 秒
--retry 3失败后最多重试 3 次
--retry-delay 5两次重试之间等待 5 秒

这不是本次故障的最终原因,但提高了脚本在自动化环境中的可靠性。


15. 只针对 master 重新执行 cri-dockerd 安装

没有重新执行整个 Docker 阶段。而是在 Ansible Control Node devops 上单独运行:

ansible k8s-master \
  -i inventory/hosts.ini \
  -b \
  -m script \
  -a './scripts/cri-dockerd-install.sh' \
  -vv

结果:

k8s-master | CHANGED

返回码:rc: 0

说明脚本成功执行。


16. master 最终安装输出

此次执行确认:

=== cri-dockerd installation ===
Host: k8s-master
Version: 0.4.4

Docker 检查:
[INFO] Docker is running

下载成功:
100  15.5M  100  15.5M

SHA256:
cri-dockerd.tgz: OK
[OK] SHA256 verification passed

systemd unit 启用:
Created symlink /etc/systemd/system/multi-user.target.wants/cri-docker.service
Created symlink /etc/systemd/system/sockets.target.wants/cri-docker.socket

cri-dockerd 版本:
cri-dockerd 0.4.4 (98c4823)

Socket:
srw-rw--- 1 root docker ... /run/cri-dockerd.sock

最终输出:
[OK] cri-dockerd installation completed.

17. 关于 SSH stderr

Ansible 返回中出现:

Shared connection to 192.168.205.141 closed.

它位于 stderr,但本次 rc: 0,并且任务状态 CHANGED,cri-dockerd 的版本、socket、systemd 启用过程也全部完成。因此这条信息本身不代表安装失败,而是 SSH 复用连接关闭时产生的信息。


18. 本次"卡住"的最终结论

可以明确排除:

  • GitHub 下载速度慢 ✗
  • DNS 故障 ✗
  • Release URL 错误 ✗
  • 下载文件损坏 ✗
  • SHA256 错误 ✗
  • cri-dockerd 0.4.4 包本身无法下载 ✗

因为 master 手动下载 15.5 MB 几秒即可完成,并且 SHA256 完全匹配。

之后使用相同 Ansible script 模块单独针对 master 重试,成功完成。

结合故障时看到的 sshd: master@notty/usr/lib/openssh/sftp-server,以及安装脚本本身没有继续运行的现象,怀疑第一次执行时 Ansible/SSH/SFTP 会话出现了临时异常。

但是: 该根因没有通过进一步日志或复现得到最终确认。因此工程笔记中不把它写成确定原因。

正确记录应是:

项目内容
故障现象首次通过 docker.yml 执行时,master 的 cri-dockerd task 未正常结束
已排除GitHub/DNS/下载速度/下载文件/SHA256
处理单独使用 Ansible script 模块针对 master 重试
结果成功
根因疑似首次 Ansible SSH/SFTP 会话异常,但本阶段未进一步复现,因此待确认,其实就是忘了开代理

19. 本阶段已经确认完成的内容

k8s-master

已经确认:

项目状态
Docker 正在运行
cri-dockerd 0.4.4
SHA256 校验
cri-docker.service unit 已创建/启用
cri-docker.socket unit 已创建/启用
/run/cri-dockerd.sock 已生成

最终 socket:/run/cri-dockerd.sock

k8s-node1

Ansible 原始执行结果:

TASK [Install cri-dockerd]
changed: [k8s-node1]

因此 cri-dockerd 安装任务成功返回。更完整的版本、service、socket 独立验收输出:本阶段未单独留存。

k8s-node2

Ansible 原始执行结果:

TASK [Install cri-dockerd]
changed: [k8s-node2]

因此 cri-dockerd 安装任务成功返回。更完整的版本、service、socket 独立验收输出:本阶段未单独留存。

devops

本阶段设计为安装 Docker Engine。但是当前记录中没有保存 devops 节点最终 docker --versionsystemctl is-active dockerdocker info 的单独执行输出。

因此严格记录为:

  • Docker 安装任务:已纳入 docker.yml ✓
  • 最终独立验收:本阶段未留存

elk

本阶段:不安装 Docker,不安装 cri-dockerd。


20. 统一验收结果

本阶段已完成 Docker 与 cri-dockerd 的全节点统一验收,因此不再标记为"待执行"。

20.1 Docker 四节点验收

执行:

ansible 'k8s_nodes:devops_servers' \
  -i inventory/hosts.ini \
  -b \
  -m shell \
  -a 'echo "===$(hostname)==="; docker --version; systemctl is-active docker; docker info 2>/dev/null | grep "Cgroup Driver"' \
  -K

验收作用: 一次确认以下三项:

  1. docker --version — 确认 Docker Engine 已正确安装,并记录实际版本
  2. systemctl is-active docker — 确认 Docker daemon 正常运行,正确结果为 active
  3. docker info | grep "Cgroup Driver" — 确认 Docker cgroup driver 已按照 Kubernetes 规划配置为 systemd

实际验收结果:

节点Docker 版本服务状态Cgroup Driver
k8s-masterDocker version 28.1.1, build 4eba377activesystemd
k8s-node1Docker version 28.1.1, build 4eba377activesystemd
k8s-node2Docker version 28.1.1, build 4eba377activesystemd
devopsDocker version 28.1.1, build 4eba377activesystemd

结论:

节点Docker 28.1.1activesystemd
k8s-master
k8s-node1
k8s-node2
devops

Docker 四节点统一验收:✓ 通过

20.2 cri-dockerd 三节点综合验收

执行:

ansible k8s_nodes \
  -i inventory/hosts.ini \
  -b \
  -m shell \
  -a 'echo "===$(hostname)==="; /usr/local/bin/cri-dockerd --version; systemctl is-active cri-docker.service; systemctl is-active cri-docker.socket; test -S /run/cri-dockerd.sock && echo "CRI socket OK"' \
  -K

验收作用: 确认三个 Kubernetes 节点上的:

  1. cri-dockerd 二进制及版本
  2. cri-docker.service
  3. cri-docker.socket
  4. /run/cri-dockerd.sock

其中:

  • /usr/local/bin/cri-dockerd --version — 确认 cri-dockerd 已正确安装
  • systemctl is-active cri-docker.service — 确认 cri-dockerd 服务处于运行状态
  • systemctl is-active cri-docker.socket — 确认 systemd socket 处于运行状态
  • test -S /run/cri-dockerd.sock — 确认该路径不仅存在,而且确实是 Unix Socket

实际验收结果:

三台节点均返回:

cri-dockerd 0.4.4 (98c4823)
active
active
CRI socket OK

具体状态:

节点cri-dockerd 版本servicesocketCRI socket
k8s-master0.4.4 (98c4823)activeactiveOK
k8s-node10.4.4 (98c4823)activeactiveOK
k8s-node20.4.4 (98c4823)activeactiveOK

结论:

节点cri-dockerdservice activesocket activeCRI socket OK
k8s-master
k8s-node1
k8s-node2

cri-dockerd 三节点统一验收:✓ 通过

20.3 CRI Socket 路径与权限验收

执行:

ansible k8s_nodes \
  -i inventory/hosts.ini \
  -b \
  -m shell \
  -a 'ls -l /run/cri-dockerd.sock' \
  -K

验收作用: 进一步确认 Kubernetes 后续实际使用的 CRI Socket /run/cri-dockerd.sock 已经在三台 Kubernetes 节点正确创建,并检查其文件类型、属主和属组。

实际结果:

节点结果
k8s-mastersrw-rw--- 1 root docker ... /run/cri-dockerd.sock
k8s-node1srw-rw--- 1 root docker ... /run/cri-dockerd.sock
k8s-node2srw-rw--- 1 root docker ... /run/cri-dockerd.sock

其中 s 表示该文件类型为 Unix Socket,属主和属组为 root:docker

后续 kubeadm 使用的 CRI Endpoint:unix:///run/cri-dockerd.sock

CRI Socket 三节点验收:✓ 通过

20.4 最终验收结论

本阶段统一验收最终结果:

Docker 四节点:

节点Docker 版本状态Cgroup Driver
k8s-master28.1.1activesystemd ✓
k8s-node128.1.1activesystemd ✓
k8s-node228.1.1activesystemd ✓
devops28.1.1activesystemd ✓

cri-dockerd 三节点:

节点版本servicesocket
k8s-master0.4.4activeactive ✓
k8s-node10.4.4activeactive ✓
k8s-node20.4.4activeactive ✓

CRI Socket:

节点路径状态
k8s-master/run/cri-dockerd.sock
k8s-node1/run/cri-dockerd.sock
k8s-node2/run/cri-dockerd.sock

因此此前"Docker/cri-dockerd 全节点批量验收待执行/待确认"正式更新为:

项目状态
Docker 四节点批量验收
cri-dockerd 三节点批量验收
/run/cri-dockerd.sock 三节点验收

阶段 2:Docker Engine + systemd cgroup + cri-dockerd ✓ 验收完成

后续除非修改 Docker 配置、重装容器运行时,或 Kubernetes 故障排查需要重新确认 CRI,否则无需重复进行本阶段验收。

注:Ansible 使用 shell 模块执行这些检查时显示 CHANGED 属正常现象。本次验收主要依据命令 $? rc=0 以及实际输出是否符合预期判断,并不表示验收命令修改了系统配置。


21. 本阶段关键配置汇总

Docker 节点:

  • k8s-master
  • k8s-node1
  • k8s-node2
  • devops

cri-dockerd 节点:

  • k8s-master
  • k8s-node1
  • k8s-node2

Docker cgroup 目标配置: systemd

Docker storage driver 配置: overlay2

最终批量实际状态: 已统一验收 ✓

cri-dockerd:

项目
版本0.4.4
Master 已确认版本cri-dockerd 0.4.4 (98c4823)
binary 路径/usr/local/bin/cri-dockerd
CRI Socket/run/cri-dockerd.sock
Kubernetes 后续 CRI Endpointunix:///run/cri-dockerd.sock

22. 本阶段学到的内容

1. Kubernetes 使用 Docker 需要 CRI 适配层

当前项目不是:

kubelet
    ↓
Docker

而是:

kubelet
    ↓
cri-dockerd
    ↓
Docker Engine

因此后面 kubeadm 必须显式使用 unix:///run/cri-dockerd.sock

2. Docker 与 kubelet 的 cgroup driver 必须统一规划

本项目统一选择 systemd,显式配置。后续 Kubernetes 阶段仍需验证 kubelet 的对应配置。

3. YAML 错误不一定是 Shell 错误

这次 awk -F':' Shell 本身合法,但 YAML 对 : 有特殊语义。因此复杂 shell 命令优先使用 shell: > 可以减少 YAML 引号和转义问题。

4. Ansible script 看起来"卡住"时不能直接判断为下载慢

script 模块执行期间,控制台不会像手工终端一样持续展示脚本内部的所有实时输出。所以看到 TASK [Install...] 长时间没有变化,只能说明 task 尚未正常返回,不能直接推出 curl 下载很慢。应该通过 pssystemctl、文件是否生成、手工 curl、Ansible -vv 逐层定位。

5. 排障要先区分"服务有问题"还是"自动化执行有问题"

本次 master 排查很典型:cri-dockerd binary 不存在、service inactive、socket inactive、socket 文件不存在——说明不是"服务装好了但启动失败",而是"安装过程本身没有完成"。排障方向因此转向安装脚本/Ansible/SSH/下载,而不是继续研究 cri-docker service 日志。

6. 网络问题最好直接做最小化验证

通过 curl + sha256sum 直接确认 DNS、网络、GitHub、URL、速度、文件完整性,比反复重跑整个 Playbook 更容易缩小故障范围。

7. 故障根因没有证据时不要强行下结论

虽然第一次失败过程中出现了 sshd: master@nottysftp-server,并且后续单独重新执行成功,但没有完整日志证明 SSH/SFTP 就是根因。因此当前合理结论是"疑似 Ansible SSH/SFTP 临时会话异常,根因待确认",而不是把猜测记录成事实。

8. 自动化脚本需要主动设置网络超时

即使这次并不是下载慢导致的问题,也暴露了原脚本 curl -fL ... 缺少网络失败边界。优化后加入 connect timeout、overall timeout、retry、retry delay,可以避免未来 CI/CD 或批量 Ansible 执行时某一台机器永久等待。


23. 本阶段状态

当前可记录为:

项目状态
Docker 自动安装脚本✓ 已编写并执行
Docker daemon systemd cgroup 配置✓ 已写入方案
cri-dockerd 0.4.4 安装脚本
k8s-master cri-dockerd✓ 已明确验证
k8s-node1 cri-dockerd✓ Ansible task 成功
k8s-node2 cri-dockerd✓ Ansible task 成功
CRI socket on master✓ 已明确验证
Docker/cri-dockerd 全节点批量验收✓ 已完成

因此阶段主体已经完成。


更多推荐