当前位置: 首页 > backend >正文

containerd 之使用 ctr 和 runc 进行底层容器操作与管理

containerd 是目前业界标准的容器运行时,它负责容器生命周期的方方面面,如镜像管理、容器执行、存储和网络等。而 ctr 是 containerd 自带的命令行工具,虽然不如 Docker CLI 用户友好,但它提供了直接与 containerd API 交互的能力,非常适合调试和学习底层机制。runc 则是遵循 OCI (Open Container Initiative) 规范的最常用的容器运行时实现,containerd 默认使用 runc 来创建和运行容器。

本文将结合实际操作日志,带大家深入了解如何使用 ctr 和 runc 进行一些核心的容器操作,包括:

  1. 安装 runc:作为容器执行的基础。
  2. 使用 ctr 实现数据持久化:通过绑定挂载(Bind Mounts)。
  3. 使用 ctr 与私有镜像仓库(如 Harbor)交互:推送与拉取镜像。
  4. 探索 Docker 与 containerd 的关系:理解 Docker 如何利用 containerd

目标读者:对容器底层原理感兴趣的开发者、系统管理员、SRE、Kubernetes 管理员。

一、安装 runC:OCI 容器运行时的基石

runc 是一个轻量级的工具,用于根据 OCI 规范运行容器。containerd 利用 runc 来启动和管理容器进程。通常 containerd 安装时会自带或依赖 runc,但了解如何手动安装或更新它也很有用。

1. 下载 runC 二进制文件

您可以从 runc 的 GitHub Releases 页面找到最新的稳定版本。这里我们以下载 v1.1.12 版本为例:

# 切换到合适的工作目录,例如 /usr/local/bin 或 /tmp
# cd /usr/local/bin# 下载适用于 amd64 架构的 runc 二进制文件
[root@docker102 ~]# wget https://github.com/opencontainers/runc/releases/download/v1.1.12/runc.amd64

参考链接: https://github.com/opencontainers/runc/releases

2. 安装与配置 (最佳实践)

下载后,通常需要进行重命名、移动到系统 $PATH 路径下,并赋予执行权限:

# 重命名
mv runc.amd64 runc# 移动到推荐的路径 (如果不在 /usr/local/bin)
# mv runc /usr/local/bin/# 赋予执行权限
chmod +x runc# 验证安装
runc --version

生产环境提示:建议验证下载文件的 SHA256 校验和,确保文件未被篡改。

二、使用 ctr 实现容器数据持久化 (Bind Mount)

容器默认是无状态的,其文件系统是临时的。当容器被删除时,写入的数据也会丢失。为了持久化数据,需要使用卷(Volume)或绑定挂载(Bind Mount)。ctr 支持通过 --mount 参数实现绑定挂载,将宿主机上的目录或文件挂载到容器内部。

1. 准备镜像和 Namespace

containerd 使用 Namespace 来隔离资源。可以为特定的项目或环境创建 Namespace。

# 查看现有 Namespace
[root@docker102:~]# ctr ns ls
NAME    LABELS
default# 拉取镜像到指定的 Namespace (如果 Namespace 不存在,会自动创建)
# 我们将镜像拉取到 'oldboyedu-linux92' Namespace
[root@docker102:~]# ctr -n oldboyedu-linux92 image pull registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v2
# ... (省略拉取过程输出)
unpacking linux/amd64 sha256:3ac38ee6161e11f2341eda32be95dcc6746f587880f923d2d24a54c3a525227e...
done: 532.516825ms# 确认 Namespace 已创建
[root@docker102:~]# ctr ns ls
NAME              LABELS
default
oldboyedu-linux92# 查看 Namespace 下的镜像
[root@docker102:~]# ctr -n oldboyedu-linux92 images ls
REF                                                       TYPE                                                 DIGEST                                                                  SIZE    PLATFORMS   LABELS
registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v2 application/vnd.docker.distribution.manifest.v2+json sha256:3ac38ee6161e11f2341eda32be95dcc6746f587880f923d2d24a54c3a525227e 9.6 MiB linux/amd64 -

2. 创建带有绑定挂载的容器

使用 ctr container create 命令创建容器定义,并通过 --mount 指定挂载。

# --mount 参数格式: type=bind,src=<host-path>,dst=<container-path>,options=<options>
# options=rbind:rw 表示递归绑定,且读写权限
[root@docker102:~]# ctr -n oldboyedu-linux92 container create \--mount type=bind,src=/oldboyedu/games,dst=/usr/local/nginx/html,options=rbind:rw \registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v2 \xiuxian

注意:此时只是创建了容器的元数据,容器进程还未启动。

3. 准备宿主机目录并启动容器任务

绑定挂载需要宿主机上的源路径 (src) 确实存在。

# 创建宿主机上的挂载源目录
[root@docker102:~]# mkdir -pv /oldboyedu/games
mkdir: created directory '/oldboyedu/games'# 启动容器任务 (后台运行 -d)
[root@docker102:~]# ctr -n oldboyedu-linux92 task start -d xiuxian
# ... (容器启动日志)

现在,容器 xiuxian 正在运行,并且宿主机的 /oldboyedu/games 目录已经挂载到了容器内的 /usr/local/nginx/html

4. 验证数据持久化

我们可以在宿主机和容器内分别操作挂载点,验证数据是否同步和持久。

# 在宿主机上放入一个文件
[root@docker102:~]# cp /etc/os-release /oldboyedu/games/# 进入容器内部查看
# ctr task exec [options] <container-id> <command>
# --exec-id $RANDOM: 为 exec 进程指定一个随机 ID
# -t: 分配一个 TTY
[root@docker102:~]# ctr -n oldboyedu-linux92 tasks exec --exec-id $RANDOM -t xiuxian sh
/ # ls -l /usr/local/nginx/html  # 查看挂载点内容,应包含 os-release
total 4
-rw-r--r--    1 root     root           382 Jul 27 08:42 os-release# 在容器内写入数据
/ # echo haha > /usr/local/nginx/html/haha.log
/ # ls -l /usr/local/nginx/html  # 确认写入成功
total 8
-rw-r--r--    1 root     root             5 Jul 27 08:45 haha.log
-rw-r--r--    1 root     root           382 Jul 27 08:42 os-release
/ # exit# 返回宿主机,检查宿主机目录,数据应已同步
[root@docker102:~]# ll /oldboyedu/games/
total 16
drwxr-xr-x 2 root root 4096 Jul 27 16:45 ./
drwxr-xr-x 5 root root 4096 Jul 27 16:40 ../
-rw-r--r-- 1 root root    5 Jul 27 16:45 haha.log  # 容器内写入的文件
-rw-r--r-- 1 root root  382 Jul 27 16:42 os-release # 宿主机放入的文件

5. 清理容器并验证数据保留

删除容器及其任务,验证宿主机上的数据是否依然存在。

# 停止并删除容器任务 (-f 强制删除)
[root@docker102:~]# ctr -n oldboyedu-linux92 tasks rm -f xiuxian
WARN[0000] task xiuxian exit with non-zero exit code 137# 确认任务已删除
[root@docker102:~]# ctr -n oldboyedu-linux92 tasks ls
TASK    PID    STATUS# 删除容器定义
[root@docker102:~# ctr -n oldboyedu-linux92 container rm xiuxian# 确认容器已删除
[root@docker102:~# ctr -n oldboyedu-linux92 container ls
CONTAINER    IMAGE    RUNTIME# 检查宿主机目录,数据仍然存在
[root@docker102:~# ll /oldboyedu/games/
total 16
drwxr-xr-x 2 root root 4096 Jul 27 16:45 ./
drwxr-xr-x 5 root root 4096 Jul 27 16:40 ../
-rw-r--r-- 1 root root    5 Jul 27 16:45 haha.log
-rw-r--r-- 1 root root  382 Jul 27 16:42 os-release

结论:通过绑定挂载,容器的数据成功持久化到了宿主机上,即使容器被删除,数据也不会丢失。这对于需要持久化存储的应用(如数据库、Web 服务器日志、配置文件等)非常关键。

三、使用 ctr 与私有仓库 (Harbor) 交互

在生产环境中,我们通常使用私有镜像仓库(如 Harbor)来存储和分发内部镜像。ctr 也可以配置与这些私有仓库进行交互。

1. 配置 containerd 以信任私有仓库

如果您的私有仓库使用自签名证书或通过 HTTP 提供服务,需要配置 containerd 以信任它。

编辑 containerd 的配置文件 (/etc/containerd/config.toml):

# /etc/containerd/config.toml
# version = 2 # 确保配置格式版本正确# ... 其他配置 ...# [plugins] # 可能在外层或内层,取决于你的 config.toml 结构
#   [plugins."io.containerd.grpc.v1.cri"]
#     [plugins."io.containerd.grpc.v1.cri".registry]
#       [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
#         # 为特定的仓库配置镜像和端点
#         [plugins."io.containerd.grpc.v1.cri".registry.mirrors."10.0.0.101"]
#           # endpoint 指定仓库地址,如果是 HTTP,需要明确写出 http://
#           endpoint = ["http://10.0.0.101"] # 使用 HTTP 协议# 注意: 如果仓库需要认证,还需要配置 auth 部分,例如:
#       [plugins."io.containerd.grpc.v1.cri".registry.configs]
#         [plugins."io.containerd.grpc.v1.cri".registry.configs."10.0.0.101".auth]
#           username = "your_username"
#           password = "your_password"
# 或者使用 Docker 配置文件 (~/.docker/config.json) 中的凭证# ... 其他配置 ...

重要提示

  • 生产环境中强烈建议使用 HTTPS。如果必须使用 HTTP,需要像上面那样配置 endpoint
  • 路径 plugins."io.containerd.grpc.v1.cri" 是针对 CRI 插件的配置,直接使用 ctr 时,可能需要配置在不同的路径下,或者使用更通用的方式,例如:
    [plugins."io.containerd.internal.v1.cri".registry.configs."10.0.0.101".tls]insecure_skip_verify = true
    # 或者对于 registry host 本身
    [plugins."io.containerd.internal.v1.remote".registry.mirrors."10.0.0.101"]endpoint = ["http://10.0.0.101"]
    
    请根据您的 containerd 版本和具体 config.toml 结构调整。最简单的方式通常是在 [plugins."io.containerd.grpc.v1.cri".registry.mirrors] 下配置。

2. 重启 containerd 服务

修改配置后,需要重启 containerd 使其生效。

[root@docker102:~]# systemctl restart containerd

3. 标记 (Tag) 镜像

将本地已有的镜像标记为目标私有仓库的地址格式。

# ctr image tag <source-image> <target-image>
# 这里我们将之前拉取的 apps:v1 标记为推送到 Harbor 的路径
# 假设 Harbor 地址是 10.0.0.101,项目是 oldboyedu-games,镜像名是 xiuxian,标签是 v1
[root@docker102:~]# ctr i tag registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v1 10.0.0.101/oldboyedu-games/xiuxian:v1
10.0.0.101/oldboyedu-games/xiuxian:v1 # 注意:日志中显示 'already exists',可能是之前操作过。正常应无此提示或直接成功。

4. 推送 (Push) 镜像到 Harbor

使用 ctr image push 命令推送镜像。

# --plain-http: 如果仓库使用 HTTP,需要此参数
# -u <username>:<password>: 如果仓库需要认证,需要此参数 (日志中未显示,但通常是必需的)
[root@docker102:~]# ctr i push --plain-http 10.0.0.101/oldboyedu-games/xiuxian:v1 # -u user:pass (如果需要认证)
# ... (省略推送过程输出)
elapsed: 15.1 s  total:  9.4 Mi (636.6 KiB/s)

5. 从 Harbor 拉取 (Pull) 镜像

验证是否可以从私有仓库拉取镜像。

# 先删除本地的对应镜像,以便测试拉取
[root@docker102:~]# ctr i rm 10.0.0.101/oldboyedu-games/xiuxian:v1
# ...# 从 Harbor 拉取
[root@docker102:~]# ctr i pull --plain-http 10.0.0.101/oldboyedu-games/xiuxian:v1 # -u user:pass (如果需要认证)
# ... (省略拉取过程输出)
unpacking linux/amd64 sha256:d9c54b04f7131c9f4e2e18e8d8b438f688533f5df57bce408186f44f01f1f91f...
done

总结:通过修改 containerd 配置并使用 ctr i push/pull 命令,我们可以方便地与私有镜像仓库(包括使用 HTTP 或需要认证的仓库)进行交互。

四、探索 Docker 与 containerd 的关系

现代版本的 Docker 引擎实际上是构建在 containerd 之上的。当我们使用 Docker 命令创建和管理容器时,Docker Daemon 会调用 containerd API 来完成实际的容器生命周期管理。containerd 将这些 Docker 创建的容器放在一个名为 moby 的特定 Namespace 下。

1. 使用 Docker 运行容器

[root@docker102:~]# docker run -d --name c1 registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v3
dd6a8fe737b21c55ce7b86f374e56c25391724f9240ce0ad2dfc7c78e772424f

2. 在 containerd 的 moby Namespace 中观察

我们可以使用 ctr 查看 moby Namespace 中的容器和任务。

# 查看 containerd 的所有 Namespace,确认 'moby' 存在
[root@docker102:~]# ctr ns ls
NAME              LABELS
default
moby
oldboyedu-linux92# 查看 'moby' Namespace 下的容器 (Container ID 通常是 Docker 的长 ID)
[root@docker102:~]# ctr -n moby container ls
CONTAINER                                                           IMAGE    RUNTIME
dd6a8fe737b21c55ce7b86f374e56c25391724f9240ce0ad2dfc7c78e772424f    -        io.containerd.runc.v2# 查看 'moby' Namespace 下的运行中任务 (Task)
[root@docker102:~]# ctr -n moby task ls
TASK                                                                PID       STATUS
dd6a8fe737b21c55ce7b86f374e56c25391724f9240ce0ad2dfc7c78e772424f    633467    RUNNING

3. 验证 PID 和网络

可以看到 ctr 列出的 Task PID 与 docker inspect 获取的容器主进程 PID 是一致的。我们甚至可以用 ctr task exec 进入 Docker 管理的容器。

# 获取 Docker 容器的 PID
[root@docker102:~]# docker inspect -f "{{.State.Pid}}" c1
633467# 使用 ctr 进入 Docker 容器执行命令,查看 IP 地址
[root@docker102:~]# ctr -n moby task exec --exec-id $RANDOM dd6a8fe737b21c55ce7b86f374e56c25391724f9240ce0ad2dfc7c78e772424f ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:14:00:01inet addr:172.20.0.1  Bcast:172.20.0.255  Mask:255.255.255.0...# 使用 docker exec 查看 IP 地址 (结果应相同)
[root@docker102:~]# docker exec c1 ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:14:00:01inet addr:172.20.0.1  Bcast:172.20.0.255  Mask:255.255.255.0...

4. Docker 删除容器与 containerd 同步

当使用 Docker 删除容器时,containerd 中对应的 Task 和 Container 也会被清理。

# 使用 Docker 删除容器
[root@docker102:~# docker container rm -f c1
c1# 再次查看 containerd 'moby' Namespace
[root@docker102:~# ctr -n moby task ls
TASK    PID    STATUS
[root@docker102:~# ctr -n moby c ls
CONTAINER    IMAGE    RUNTIME

理解这一点的好处

  • 调试 Kubernetes 节点:Kubernetes (使用 Containerd 作为 CRI 时) 管理的 Pod 和容器也可以通过 ctr 在节点的 k8s.io Namespace 下观察到。这对于排查节点上的 Pod 问题非常有帮助。
  • 理解架构:清晰地看到 Docker 如何作为 containerd 的一个客户端,有助于理解容器生态系统的分层架构。

五、Docker 数据持久化 (Volume) 与 containerd 观察

Docker 提供了卷(Volume)机制来实现数据持久化,这是比绑定挂载更推荐的方式,因为它由 Docker 管理,更易于备份、迁移和跨平台。

1. 使用 Docker 创建带 Volume 的容器

# -v /oldboyedu-data: 这会创建一个 Docker 管理的匿名卷,并挂载到容器的 /oldboyedu-data 目录
# 注意:这不同于 -v /host/path:/container/path (绑定挂载)
[root@docker102:~]# docker run -d --name c1 -p 81:80 -v /oldboyedu-data registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v3
6e29d2aa3fdb4781487712a59d80331c0bf07b30488188c6bd0a1dfd8ef40080

2. 在 containerd 中观察 Task

[root@docker102:~]# ctr -n moby task ls
TASK                                                                PID       STATUS
6e29d2aa3fdb4781487712a59d80331c0bf07b30488188c6bd0a1dfd8ef40080    634196    RUNNING

3. 查找 Docker Volume 在宿主机上的实际路径

Docker Volume 的数据实际存储在宿主机的特定目录下(通常是 /var/lib/docker/volumes/)。

[root@docker102:~]# docker inspect -f "{{range .Mounts}}{{.Source}}{{end}}" c1
/var/lib/docker/volumes/32022aa9c9904e65a7d37c7ae49e48b73118f3b9795c277576cb1cfad15a589e/_data

4. 验证数据持久化 (通过宿主机和 ctr)

# 在宿主机上向 Volume 目录写入文件
[root@docker102:~]# cp /etc/fstab /var/lib/docker/volumes/32022aa9c9904e65a7d37c7ae49e48b73118f3b9795c277576cb1cfad15a589e/_data# 使用 ctr 进入容器,查看挂载点 /oldboyedu-data
[root@docker102:~]# ctr -n moby task exec -t --exec-id $RANDOM 6e29d2aa3fdb4781487712a59d80331c0bf07b30488188c6bd0a1dfd8ef40080 sh
/ # ls /oldboyedu-data/
fstab
/ # exit

对比:虽然最终效果都是持久化,但 Docker Volume 由 Docker 统一管理,与宿主机文件系统耦合度更低;而 ctr 的绑定挂载则直接将宿主机路径映射进容器,更直接但也需要手动管理宿主机路径。

总结

通过本文的实践,我们深入了解了:

  • 如何安装和验证 OCI 运行时 runc
  • 如何使用 ctr 命令行工具,结合 containerd 的 Namespace 概念,管理镜像和容器。
  • 如何利用 ctr 的 --mount 参数实现基于绑定挂载的数据持久化。
  • 如何配置 containerd 并使用 ctr 与私有 HTTP 镜像仓库(如 Harbor)进行交互,包括推送和拉取镜像。
  • Docker 与 containerd 之间的协作关系,以及如何使用 ctr 在 moby Namespace 下观察和操作由 Docker 管理的容器。
  • Docker Volume 持久化方式及其在宿主机上的体现。

虽然 ctr 被定位为面向开发和调试的工具,不如 Docker CLI 或 Podman 那样用户友好,但它提供了一种直接、底层的方式来与 containerd 交互。掌握 ctr 对于理解容器运行时的工作原理、排查 Kubernetes 集群节点问题(当 CRI 为 containerd 时)、或进行特定的底层操作非常有价值。

http://www.xdnf.cn/news/5866.html

相关文章:

  • mysql5.7安装
  • 视频监控汇聚平台EasyCVR安防监控系统:在应用中,机房及监控系统施工如何有效实现防雷?
  • huggingface transformers中Dataset是一种什么数据类型
  • spaCy基础入门
  • transforms.Compose()
  • ARFoundation 图片识别,切换图片克隆不同的追踪模型
  • Rodrigues旋转公式-绕任意轴旋转
  • Excel宏和VBA的详细分步指南
  • Linux系统:文件系统前言,详解CHSLBA地址
  • 如何创建maven项目
  • java之网络编程
  • uniapp(vue3)动态计算swiper高度封装自定义hook
  • SD-HOST Controller design-----SD CLK 设计
  • 深度学习之优化器【从梯度下降到自适应学习率算法】(pytorch版)
  • 华为鸿蒙电脑能否作为开发机?开发非鸿蒙应用?
  • 微服务的“导航系统”:使用Spring Cloud Eureka实现服务注册与发现
  • 销售具备的能力有哪些
  • JAVA研发+前后端分离,ZKmall开源商城B2C商城如何保障系统性能?
  • Python中元组(Tuple)使用详解和注意事项
  • Kotlin 中的 Unit 类型的作用以及 Java 中 Void 的区别
  • 拓扑排序+dp
  • STM32-DMA数据转运(8)
  • 直接在Excel中用Python Matplotlib/Seaborn/Plotly......
  • Linux 内核网络协议栈:从 Socket 类型到协议注册的深度解析
  • 思迈特软件携手天阳科技,打造ChatBI金融智能分析新标杆
  • 适应性神经树:当深度学习遇上决策树的“生长法则”
  • Spring Boot 整合 Redis 实战
  • MySQL 事务(二)
  • 在 Qt Creator 中为 QDockWidget 设置隐藏和显示按钮
  • 中电金信参编的国家标准《信息技术 中间件 消息中间件技术要求》正式发布