1 为什么用 nerdctl ?

随着 Kubernetes 于 2022 年中旬发布首个彻底废除 Dockershim 兼容层的 1.24 版本,Docker 垄断容器技术的时代正式成为历史。而新建的 Kubernetes 集群大多会使用直接兼容 CRIcontainerdCRI-O 等容器运行时而非 Docker

针对我们现有的 containerd 技术,可以使用 ctr 操作管理 containerd 镜像容器,但是大家都习惯了使用 docker clictr 使用起来可能还是不太顺手。为了能够让大家更好的转到 containerd 上面来,社区提供了一个新的命令行工具:<font style="color:#DF2A3F;">nerdctl</font>

它与 Docker CLI 具有相似的命令和功能,因此对于已经熟悉 Docker 的开发者来说,迁移到 nerdctl 是比较容易的。

2 nerdctl VS docker

nerdctl 是一个与 docker cli 风格兼容的 containerd 客户端工具,而且直接兼容 docker compose 的语法的。这就大大提高了直接将 containerd 作为本地开发、测试或者单机容器部署使用的效率。

nerdctl 的使用和 docker 一致,与 docker 具有相同的体验,主要特征如下:

  • dockerUI/UX 相同
  • 支持 docker-compose ( 例如:nerdctl compose up)
  • [可选] 支持 rootless 模式,无 slirp 开销(bypass4netns)
  • [可选] 支持延迟拉取(Stargz、Nydus、OverlayBD)
  • [可选] 支持加密镜像(ocicrypt)
  • [可选] 支持 P2P 镜像分发 (IPFS) (*1)
  • [可选] 支持容器镜像签名和验证(cosign)
  • [可选] 支持 containerd 的命名空间查看,nerdctl 不仅可以管理 Docker 容器,也可以直接管理本地的的 Kubernetes pod
  • [可选] 支持将 Docker Image Manifest 镜像转换为 OCI 镜像、estargz 镜像。

3 架构原理

🐧[Containerd] 基于 Kubernetes 1.27.4 的 nerdctl - 图1

4 实验环境

  • Nerdctl 版本:1.6.2
  • Runc 版本:1.1.8

  • Containerd 版本:1.6.22

  • Kubernetes 版本:1.27.4

5 nerdctl 安装和使用

nerdctl 的安装分为 Minimal 精简安装 和包含一些插件的 Full 完整安装

精简版只包含 nerdctl,完整版包含 nerdctlCNI 插件等依赖(当然你也可以在精简安装的基础上再自己添加 CNI 等插件)。

  1. Minimal (nerdctl-1.6.2-linux-amd64.tar.gz): nerdctl only
  2. Full (nerdctl-full-1.6.2-linux-amd64.tar.gz): Includes dependencies such as containerd, runc, and CNI

:::info 💡nerdctl 下载安装

:::

官方下载地址:https://github.com/containerd/nerdctl/releases,在 Asset 中选择下载精简或者完全安装包(本例精简安装)。

  1. $ wget https://github.com/containerd/nerdctl/releases/download/v1.6.2/nerdctl-1.6.2-linux-amd64.tar.gz
  2. $ tar -xzvf nerdctl-*-linux-amd64.tar.gz -C /usr/local/bin/
  3. $ nerdctl version
  4. WARN[0000] unable to determine buildctl version: exec: "buildctl": executable file not found in $PATH
  5. Client:
  6. Version: v1.6.2
  7. OS/Arch: linux/amd64
  8. Git commit: e3dc23be348efded17d2cd244397b4f7018e0794
  9. buildctl:
  10. Version:
  11. Server:
  12. containerd:
  13. Version: 1.6.22
  14. GitCommit: 8165feabfdfe38c65b599c4993d227328c231fca
  15. runc:
  16. Version: 1.1.8
  17. GitCommit: v1.1.8-0-g82f18fe

6 nerdctl 基本使用方法

从个人用户及开发者的角度看,nerdctl 客户端跟 dockerpodman 大致相同,例如:

操作 nerdctl 命令 docker 命令 podman 命令
查看本地镜像 nerdctl images docker images podman images
删除本地镜像 nerdctl rmi <镜像名称> docker rmi <镜像名称> podman rmi <镜像名称>
创建容器 nerdctl run <镜像名称> docker run <镜像名称> podman run <镜像名称>
查看运行中的容器 nerdctl ps docker ps podman ps
查看所有容器 nerdctl ps -a docker ps -a podman ps -a
删除容器 nerdctl rm <容器名称> docker rm <容器名称> podman rm <容器名称>

7 更多 nerdctl 用法

7.1 注意项

Kubernetes 默认使用 <font style="color:rgb(233, 105, 0);">Kubernetes.io(k8s.io)</font>,而 <font style="color:rgb(233, 105, 0);">nerdctl</font> 默认使用 <font style="color:rgb(233, 105, 0);">default namspace</font>。如果需要查看 Kubernetes 相关镜像需要加上”<font style="color:rgb(233, 105, 0);">--namespace=Kubernetes.io(k8s.io)</font>“来指定。

  1. $ nerdctl images --namespace=Kubernetes.io # (--namespace=k8s.io)
  2. $ nerdctl -n=Kubernetes.io images # (-n=k8s.io)

或者在nerdctl 配置文件中指定 nerdctl 默认使用 Kubernetes.io namespace

  1. $ mkdir -pv /etc/nerdctl/
  2. $ cat >> /etc/nerdctl/nerdctl.toml << EOF
  3. namespace = "k8s.io"
  4. EOF

7.2 更多命令操作

  1. #nerdctl run :创建容器
  2. nerdctl run -d -p 80:80 --name=nginx --restart=always nginx
  3. #nerdctl exec :进入容器
  4. nerdctl exec -it nginx /bin/sh
  5. #nerdctl ps :列出容器
  6. nerdctl ps -a
  7. #nerdctl inspect :获取容器的详细信息
  8. nerdctl inspect nginx
  9. #nerdctl logs :获取容器日志
  10. nerdctl logs -f nginx
  11. #nerdctl stop :停止容器
  12. nerdctl stop nginx
  13. #nerdctl rm :删除容器
  14. nerdctl rm -f nginx
  15. nerdctl rmi -f <IMAGE ID>
  16. #nerdctl images:镜像列表
  17. nerdctl images
  18. nerdctl -n=Kubernetes.io images
  19. nerdctl -n=Kubernetes.io images | grep -v '<none>'
  20. #nerdctl pull :拉取镜像
  21. nerdctl pull nginx
  22. #使用 nerdctl login --username xxx --password xxx 进行登录,使用 nerdctl logout 可以注销退出登录
  23. nerdctl login
  24. nerdctl logout
  25. #nerdctl tag :镜像标签
  26. nerdctl tag nginx:latest harbor.Kubernetes/image/nginx:latest
  27. #nerdctl push :推送镜像
  28. nerdctl push harbor.Kubernetes/image/nginx:latest
  29. #nerdctl save :导出镜像
  30. nerdctl save -o busybox.tar.gz busybox:latest
  31. #nerdctl load :导入镜像
  32. nerdctl load -i busybox.tar.gz
  33. #nerdctl rmi :删除镜像
  34. nerdctl rmi busybox

7.3 buildkitd 镜像构建

镜像构建是平时非常频繁的一个需求,我们知道 ctr 并没有构建镜像的命令,而现在我们不使用 Docker 了,那么如何进行镜像构建了?

幸运的是 nerdctl 就提供了 nerdctl build 这样的镜像构建命令。

buildkit 项目也是 Docker 公司开源的一个构建工具包,支持 OCI 标准的镜像构建。它主要包含以下部分:

  • 服务端 buildkitd:当前支持 runc 和 containerd 作为 worker,默认是 runc,我们这里使用 containerd。
  • 客户端 buildctl:负责解析 Dockerfile,并向服务端 buildkitd 发出构建请求。

buildkit 是典型的 C/S 架构,客户端和服务端是可以不在一台服务器上,而 nerdctl 在构建镜像的时候也作为 buildkitd 的客户端,所以需要我们安装并运行 buildkitd

若要使用 nerdctl build 命令自行构建及上传容器镜像,则需要安装 buildkitd 并启用该服务,详情请参考官方 GitHub 仓库 [18] ,我们会安装 0.12.2 版本:

  1. $ wget https://github.com/moby/buildkit/releases/download/v0.12.2/buildkit-v0.12.2.linux-amd64.tar.gz
  2. $ tar -xzvf buildkit-v0.12.2.linux-amd64.tar.gz -C /usr/local/
  3. $ mkdir -p /usr/local/lib/systemd/system/
  4. $ cat << EOF | sudo tee /usr/local/lib/systemd/system/buildkitd.service
  5. [Unit]
  6. Description=BuildKit
  7. Documentation=https://github.com/moby/buildkit
  8. [Service]
  9. ExecStart=/usr/local/bin/buildkitd
  10. [Install]
  11. WantedBy=multi-user.target
  12. EOF
  13. # 启动buildkitd服务
  14. $ systemctl daemon-reload && systemctl enable --now buildkitd.service
  15. $ systemctl status buildkitd.service
  16. buildkitd.service - BuildKit
  17. Loaded: loaded (/usr/local/lib/systemd/system/buildkitd.service; enabled; vendor preset: disabled)
  18. Active: active (running) since Mon 2023-11-06 13:35:11 CST; 46s ago
  19. Docs: https://github.com/moby/buildkit
  20. Main PID: 30332 (buildkitd)
  21. Tasks: 9
  22. Memory: 6.9M
  23. CGroup: /system.slice/buildkitd.service
  24. └─30332 /usr/local/bin/buildkitd

比如现在我们定制一个 nginx 镜像,新建一个如下所示的 Dockerfile 文件:

  1. cat > Dockerfile <<EOF
  2. FROM nginx:alpine
  3. RUN echo -e "Hello ZHDYA From Containerd" > /usr/share/nginx/html/index.html
  4. EOF

然后在文件所在目录执行镜像构建命令:

  1. $ nerdctl build -t nginx:nerdctl -f Dockerfile .

查看验证:

  1. $ nerdctl images
  2. REPOSITORY TAG IMAGE ID CREATED PLATFORM SIZE BLOB SIZE
  3. nginx nerdctl a0fd917f4722 47 seconds ago linux/amd64 43.1 MiB 16.2 MiB

使用上面我们构建的镜像来启动一个容器进行测试:

  1. $ nerdctl run -d -p 80:80 --name=nginx --restart=always nginx:nerdctl
  2. 4af4648264330c1ca46ae97299fb03ff7f3c98e9f627618e679eccf6b9df131d
  3. $ nerdctl ps
  4. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  5. 4af464826433 docker.io/library/nginx:nerdctl "/docker-entrypoint.…" 18 seconds ago Up 0.0.0.0:80->80/tcp nginx
  6. $ curl localhost
  7. Hello ZHDYA From Containerd

7.4 一键部署 nerdctl 和 buildkitd 服务

  1. #!/bin/bash
  2. # FileName: install-nerdctl-buildkit.sh
  3. # Description:Install nerdctl command and buildkitd service
  4. # 安装nerdctl
  5. echo "Install nerdctl"
  6. wget https://github.com/containerd/nerdctl/releases/download/v1.7.1/nerdctl-1.7.1-linux-amd64.tar.gz
  7. tar -zxvf nerdctl-1.7.1-linux-amd64.tar.gz -C /usr/local/bin/
  8. # 查看nerdctl的版本情况
  9. echo "nerdctl Version"
  10. nerdctl version
  11. # 设置默认的命名空间
  12. mkdir -p /etc/nerdctl/
  13. tee /etc/nerdctl/nerdctl.toml << __EOF__
  14. namespace = "k8s.io"
  15. __EOF__
  16. # 测试是否可用
  17. echo "nerdctl images"
  18. # 安装buildkitd服务
  19. echo "Install buildkitd"
  20. wget https://github.com/moby/buildkit/releases/download/v0.12.2/buildkit-v0.12.2.linux-amd64.tar.gz
  21. tar -xzvf buildkit-v0.12.2.linux-amd64.tar.gz -C /usr/local/
  22. echo "Start buildkitd.service"
  23. # 编辑buildkitd服务配置文件
  24. mkdir -p /usr/local/lib/systemd/system/
  25. cat << __EOF__ | sudo tee /usr/local/lib/systemd/system/buildkitd.service
  26. [Unit]
  27. Description=BuildKit
  28. Documentation=https://github.com/moby/buildkit
  29. [Service]
  30. ExecStart=/usr/local/bin/buildkitd
  31. [Install]
  32. WantedBy=multi-user.target
  33. __EOF__
  34. # 启动buildkitd服务
  35. echo "Start the buildkitd service"
  36. systemctl daemon-reload && systemctl enable --now buildkitd.service
  37. systemctl status buildkitd.service
  38. # 删除无用的压缩包
  39. echo "Delete unnecessary compressed packages"
  40. rm -rf ./nerdctl-1.7.1-linux-amd64.tar.gz
  41. rm -rf ./buildkit-v0.12.2.linux-amd64.tar.gz

7.5 nerdctl 管理 Docker Compose

当然现在仍有很多企业在使用 Docker Compose。在 containerd 模式下,我们也可以使用 nerdctl 来兼容该功能。

同样我们可以使用如下命令来管理 Compose 服务:

  1. # nerdctl compose 管理 DockerCompose 应用
  2. $ nerdctl compose
  3. $ nerdctl compose up
  4. $ nerdctl compose logs
  5. $ nerdctl compose build
  6. $ nerdctl compose down`

这样使用 containerd、nerdctl 结合 buildkit 等工具就完全可以替代 docker 在镜像构建、镜像容器方面的管理功能了。

8 基于 containerd & nerdctl 实战

8.1 实战一、快速部署 Redis 数据库服务

1)创建持久化目录&配置文件

  1. mkdir -p /data/redis/data
  2. tee /data/redis/redis.conf <<'EOF'
  3. # 绑定任意接口、服务端口、后台运行。
  4. bind 0.0.0.0
  5. port 6379
  6. # 容器里必须设置为no
  7. daemonize no
  8. supervised auto
  9. # redis服务pid进程文件名
  10. pidfile "/var/run/redis.pid"
  11. # 关闭保护模式,并配置使用密码访问
  12. protected-mode no
  13. requirepass "qigedeKubernetes"
  14. # 数据文件保存路径,rdb/AOF文件也保存在这里
  15. dir "/data"
  16. # 日志文件记录文件(notice / verbose)
  17. # logfile "/logs/redis.log"
  18. # loglevel verbose
  19. # 最大客户端连接数
  20. maxclients 10000
  21. # 客户端连接空闲多久后断开连接,单位秒,0表示禁用
  22. timeout 60
  23. tcp-keepalive 60
  24. # Redis 数据持久化(rdb/aof)配置
  25. # RDB 文件名
  26. dbfilename "dump.rdb"
  27. # 数据自动保存脚本条件例如300s中有10key发生变化
  28. save 300 10
  29. save 60 10000
  30. # 对RDB文件进行压缩,建议以(磁盘)空间换(CPU)时间。
  31. rdbcompression yes
  32. # 版本5的RDB有一个CRC64算法的校验和放在了文件的最后。这将使文件格式更加可靠。
  33. rdbchecksum yes
  34. # RDB自动触发策略是否启用,默认为yes
  35. rdb-save-incremental-fsync yes
  36. # AOF开启
  37. appendonly yes
  38. # AOF文件名
  39. appendfilename "appendonly.aof"
  40. # 可选值 always, everysec,no,建议设置为everysec
  41. appendfsync everysec
  42. # Redis风险命令重命名
  43. # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
  44. rename-command FLUSHDB b840fc02d524045429941cc15f59e41cb7be6c53
  45. rename-command FLUSHALL b840fc02d524045429941cc15f59e41cb7be6c54
  46. rename-command EVAL b840fc02d524045429941cc15f59e41cb7be6c55
  47. rename-command DEBUG b840fc02d524045429941cc15f59e41cb7be6c56
  48. # rename-command SHUTDOWN SHUTDOWN
  49. EOF

2)执行如下命令进行快速创建容器并运行redis服务:

  1. $ nerdctl run -d -p 6379:6379 \
  2. -v /data/redis/redis.conf:/etc/redis/redis.conf \
  3. -v /data/redis/data:/data \
  4. --name redis-server redis:6.2.6-alpine3.15 redis-server /etc/redis/redis.conf
  5. 5e854a58087ae1bba5a661b2941474560cbecc37f54c7f4e7a28afbaed6aebf0

3)查看创建的容器并验证redis服务是否正常:

  1. $ nerdctl ps
  2. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  3. 539dd82dce37 docker.io/library/redis:6.2.6-alpine3.15 "docker-entrypoint.s…" 5 seconds ago Up 0.0.0.0:6379->6379/tcp redis-server
  4. $ nerdctl logs redis-server
  5. 1:C 06 Nov 2023 10:34:22.929 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
  6. 1:C 06 Nov 2023 10:34:22.929 # Redis version=6.2.6, bits=64, commit=00000000, modified=0, pid=1, just started
  7. 1:C 06 Nov 2023 10:34:22.929 # Configuration loaded
  8. 1:M 06 Nov 2023 10:34:22.934 # You requested maxclients of 10000 requiring at least 10032 max file descriptors.
  9. 1:M 06 Nov 2023 10:34:22.934 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted.
  10. 1:M 06 Nov 2023 10:34:22.934 # Current maximum open files is 1024. maxclients has been reduced to 992 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
  11. 1:M 06 Nov 2023 10:34:22.934 * monotonic clock: POSIX clock_gettime
  12. 1:M 06 Nov 2023 10:34:22.935 * Running mode=standalone, port=6379.
  13. 1:M 06 Nov 2023 10:34:22.936 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
  14. 1:M 06 Nov 2023 10:34:22.936 # Server initialized
  15. 1:M 06 Nov 2023 10:34:22.937 * Ready to accept connections
  16. # 如下返回,表示 redis 服务状态是正常的
  17. $ nerdctl exec -it redis-server redis-cli -a qigedeKubernetes ping
  18. Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
  19. PONG
  20. $ nerdctl exec -it redis-server redis-cli -a qigedeKubernetes info
  21. Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
  22. # Server
  23. redis_version:6.2.6
  24. redis_git_sha1:00000000
  25. redis_git_dirty:0
  26. redis_build_id:63421500bb103677
  27. redis_mode:standalone
  28. os:Linux 3.10.0-1160.el7.x86_64 x86_64
  29. arch_bits:64
  30. multiplexing_api:epoll
  31. atomicvar_api:atomic-builtin
  32. gcc_version:10.3.1
  33. process_id:1
  34. process_supervised:no
  35. run_id:7baff4cbd3c620249034a11a87dc17e42b227132

4)直接用telnet工具连接创建的redis容器中的redis数据库服务:

  1. $ telnet 192.10.192.16 6379
  2. Trying 192.10.192.16...
  3. Connected to 192.10.192.16.
  4. Escape character is '^]'.
  5. auth qigedeKubernetes
  6. +OK
  7. set name woshiqige
  8. +OK
  9. get name
  10. $9
  11. woshiqige

8.2 实战二、nerdctl 全流程操作演示

创建一个 hello-containerd/ 目录并 cd 进去吧,我们将构建一个名为 hello-containerd 的 APP:

  1. $ mkdir -p hello-containerd/ && cd hello-containerd/

然后写一些 HTML 代码,并存到 index.html 文档中:

  1. $ cat > index.html << EOF
  2. <!DOCTYPE HTML>
  3. <html>
  4. <head>
  5. <meta charset="utf-8" />
  6. <title>Hello containerd!</title>
  7. </head>
  8. <body>
  9. <h1>Hello containerd!</h1>
  10. <h2>Hello nerdctl!</h2>
  11. <h3>Hello buildkitd!</h3>
  12. </body>
  13. </html>
  14. EOF

接着创建一个 Dockerfile ,该文件将告知 nerdctl 如何一步步构建我们的 APP 镜像:

  1. cat > Dockerfile << EOF
  2. FROM alpine:3.18
  3. RUN apk add -U python3
  4. WORKDIR /app
  5. COPY index.html .
  6. CMD ["python3", "-m", "http.server", "80"]
  7. EOF

然后构建我们的 hello-containerd APP,版本为 0.1.0

  1. $ nerdctl build -t harbor-local.kubernets.cn/demo/hello-containerd:0.1.0 .

现在我们可以透过运行该 APP 验证 APP 镜像能正常使用:

  1. $ nerdctl run --name my-hello-server -d -p 8080:80 harbor-local.kubernets.cn/demo/hello-containerd:0.1.0
  2. $ nerdctl ps
  3. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  4. 2d20d221f741 harbor-local.kubernets.cn/demo/hello-containerd:0.1.0 "python3 -m http.ser…" 8 seconds ago Up 0.0.0.0:8080->80/tcp my-hello-server

测试验证:

  1. $ curl localhost:8080
  2. <!DOCTYPE HTML>
  3. <html>
  4. <head>
  5. <meta charset="utf-8" />
  6. <title>Hello containerd!</title>
  7. </head>
  8. <body>
  9. <h1>Hello containerd!</h1>
  10. <h2>Hello nerdctl!</h2>
  11. <h3>Hello buildkitd!</h3>
  12. </body>
  13. </html>

先把刚启动的 my-hello-server 容器停掉并删除:

  1. $ nerdctl stop my-hello-server
  2. my-hello-server
  3. $ nerdctl rm my-hello-server
  4. my-hello-server

上传镜像前,以 Harbor 用户名登录 ;若不指明使用特定镜像仓库,nerdctl 会默认使用 docker.io

  1. $ nerdctl --insecure-registry login harbor-local.kubernets.cn --username admin --password xxx

然后用 nerdctl push 命令把 APP 镜像上传到 Harbor 私仓:

  1. $ nerdctl push --insecure-registry harbor-local.kubernets.cn/demo/hello-containerd:0.1.0

9 升级nerdctl

升级到 nerdctl 1.7.0,首先下载并解压

  1. # 备份当前可执行文件
  2. $ cp /usr/local/bin/nerdctl /usr/local/bin/nerdctlbak
  3. $ wget https://github.com/containerd/nerdctl/releases/download/v1.7.0/nerdctl-1.7.0-linux-amd64.tar.gz

然后去把正在执行的nerdctl进程都停下来,类似以下操作

  1. # 查看正在运行的容器
  2. $ nerdctl container ls
  3. # 停止 nerdctl 服务以及其他服务

拷贝nerdctl到系统二进制命令路径下

  1. $ tar -xzvf nerdctl-*-linux-amd64.tar.gz -C /usr/local/bin/

重新启动 nerdctl 控制的服务!

10 总结

nerdctl 作为一个轻量级的容器运行时工具,旨在提供简洁、高效的容器管理体验。它是基于 Docker CLI 的替代品,并采用了 Containerd 作为底层运行时引擎。同时,文章也提到了 nerdctl 作为一个开源工具,拥有活跃的社区支持和更新的版本发布。

中部位置我解释了 nerdctl 的工作原理。它使用 Containerd 作为底层运行时引擎,通过与Containerd API交互来管理和运行容器。这种架构使 nerdctl 更加轻巧、快速,并且具有更好的性能表现。

文章也详细介绍了 nerdctl 的各种功能和命令。它与 Docker CLI 具有相似的命令和功能,对于已经熟悉 Docker 的开发者来说,迁移到 nerdctl 是比较容易的。同时,nerdctl 还提供了一些额外的功能和改进,如支持通过文件进行镜像构建、创建和管理容器网络、容器日志等。使得容器管理更加便捷、灵活和可控。