Containerd 仍然采用标准的 C/S 架构,服务端通过 GRPC 协议提供稳定的 API,客户端通过调用服务端的 API 进行高级的操作。
为了解耦,Containerd 将不同的职责划分给不同的组件,每个组件就相当于一个子系统(subsystem)。连接不同子系统的组件被称为模块。
总体上 Containerd 被划分为两个子系统:

  • Bundle : 在 Containerd 中,Bundle 包含了配置、元数据和根文件系统数据,你可以理解为容器的文件系统。而 Bundle 子系统允许用户从镜像中提取和打包 Bundles。
  • Runtime : Runtime 子系统用来执行 Bundles,比如创建容器。

其中,每一个子系统的行为都由一个或多个模块协作完成(架构图中的 Core 部分)。每一种类型的模块都以插件的形式集成到 Containerd 中,而且插件之间是相互依赖的。例如,上图中的每一个长虚线的方框都表示一种类型的插件,包括 Service PluginMetadata PluginGC PluginRuntime Plugin 等,其中 Service Plugin 又会依赖 Metadata Plugin、GC Plugin 和 Runtime Plugin。每一个小方框都表示一个细分的插件,例如 Metadata Plugin 依赖 Containers Plugin、Content Plugin 等。总之,万物皆插件,插件就是模块,模块就是插件。
Containerd - 图1
这里介绍几个常用的插件:

  • Content Plugin : 提供对镜像中可寻址内容的访问,所有不可变的内容都被存储在这里。
  • Snapshot Plugin : 用来管理容器镜像的文件系统快照。镜像中的每一个 layer 都会被解压成文件系统快照,类似于 Docker 中的 graphdriver
  • Metrics : 暴露各个组件的监控指标。

从总体来看,Containerd 被分为三个大块:StorageMetadataRuntime,可以将上面的架构图提炼一下:
Containerd - 图2
这是使用 bucketbenchDockercrioContainerd 的性能测试结果,包括启动、停止和删除容器,以比较它们所耗的时间:
Containerd - 图3
可以看到 Containerd 在各个方面都表现良好,总体性能还是优越于 Dockercrio 的。

3. Containerd 安装

了解了 Containerd 的概念后,就可以动手安装体验一把了。本文的演示环境为 Ubuntu 18.04

安装依赖

为 seccomp 安装依赖:

  1. 🐳 sudo apt-get update
  2. 🐳 sudo apt-get install libseccomp2

下载并解压 Containerd 程序

Containerd 提供了两个压缩包,一个叫 containerd-${VERSION}.${OS}-${ARCH}.tar.gz,另一个叫 cri-containerd-${VERSION}.${OS}-${ARCH}.tar.gz。其中 cri-containerd-${VERSION}.${OS}-${ARCH}.tar.gz 包含了所有 Kubernetes 需要的二进制文件。如果你只是本地测试,可以选择前一个压缩包;如果是作为 Kubernetes 的容器运行时,需要选择后一个压缩包。
Containerd 是需要调用 runc 的,而第一个压缩包是不包含 runc 二进制文件的,如果你选择第一个压缩包,还需要提前安装 runc。所以我建议直接使用 cri-containerd 压缩包。
首先从 release 页面下载最新版本的压缩包,当前最新版本为 1.4.3:

  1. 🐳 wget https://github.com/containerd/containerd/releases/download/v1.4.3/cri-containerd-cni-1.4.3-linux-amd64.tar.gz
  2. # 也可以替换成下面的 URL 加速下载
  3. 🐳 wget https://download.fastgit.org/containerd/containerd/releases/download/v1.4.3/cri-containerd-cni-1.4.3-linux-amd64.tar.gz

可以通过 tar 的 -t 选项直接看到压缩包中包含哪些文件:

  1. 🐳 tar -tf cri-containerd-cni-1.4.3-linux-amd64.tar.gz
  2. etc/
  3. etc/cni/
  4. etc/cni/net.d/
  5. etc/cni/net.d/10-containerd-net.conflist
  6. etc/crictl.yaml
  7. etc/systemd/
  8. etc/systemd/system/
  9. etc/systemd/system/containerd.service
  10. usr/
  11. usr/local/
  12. usr/local/bin/
  13. usr/local/bin/containerd-shim-runc-v2
  14. usr/local/bin/ctr
  15. usr/local/bin/containerd-shim
  16. usr/local/bin/containerd-shim-runc-v1
  17. usr/local/bin/crictl
  18. usr/local/bin/critest
  19. usr/local/bin/containerd
  20. usr/local/sbin/
  21. usr/local/sbin/runc
  22. opt/
  23. opt/cni/
  24. opt/cni/bin/
  25. opt/cni/bin/vlan
  26. opt/cni/bin/host-local
  27. opt/cni/bin/flannel
  28. opt/cni/bin/bridge
  29. opt/cni/bin/host-device
  30. opt/cni/bin/tuning
  31. opt/cni/bin/firewall
  32. opt/cni/bin/bandwidth
  33. opt/cni/bin/ipvlan
  34. opt/cni/bin/sbr
  35. opt/cni/bin/dhcp
  36. opt/cni/bin/portmap
  37. opt/cni/bin/ptp
  38. opt/cni/bin/static
  39. opt/cni/bin/macvlan
  40. opt/cni/bin/loopback
  41. opt/containerd/
  42. opt/containerd/cluster/
  43. opt/containerd/cluster/version
  44. opt/containerd/cluster/gce/
  45. opt/containerd/cluster/gce/cni.template
  46. opt/containerd/cluster/gce/configure.sh
  47. opt/containerd/cluster/gce/cloud-init/
  48. opt/containerd/cluster/gce/cloud-init/master.yaml
  49. opt/containerd/cluster/gce/cloud-init/node.yaml
  50. opt/containerd/cluster/gce/env

直接将压缩包解压到系统的各个目录中:

  1. 🐳 sudo tar -C / -xzf cri-containerd-cni-1.4.3-linux-amd64.tar.gz

/usr/local/bin/usr/local/sbin 追加到 ~/.bashrc 文件的 $PATH 环境变量中:

  1. export PATH=$PATH:/usr/local/bin:/usr/local/sbin

立即生效:

  1. 🐳 source ~/.bashrc

查看版本:

  1. 🐳 ctr version
  2. Client:
  3. Version: v1.4.3
  4. Revision: 269548fa27e0089a8b8278fc4fc781d7f65a939b
  5. Go version: go1.15.5
  6. Server:
  7. Version: v1.4.3
  8. Revision: 269548fa27e0089a8b8278fc4fc781d7f65a939b
  9. UUID: d1724999-91b3-4338-9288-9a54c9d52f70

生成配置文件

Containerd 的默认配置文件为 /etc/containerd/config.toml,我们可以通过命令来生成一个默认的配置:

  1. 🐳 mkdir /etc/containerd
  2. 🐳 containerd config default > /etc/containerd/config.toml

镜像加速

由于某些不可描述的因素,在国内拉取公共镜像仓库的速度是极慢的,为了节约拉取时间,需要为 Containerd 配置镜像仓库的 mirror。Containerd 的镜像仓库 mirror 与 Docker 相比有两个区别:

  • Containerd 只支持通过 CRI 拉取镜像的 mirror,也就是说,只有通过 crictl 或者 Kubernetes 调用时 mirror 才会生效,通过 ctr 拉取是不会生效的。
  • Docker 只支持为 Docker Hub 配置 mirror,而 Containerd 支持为任意镜像仓库配置 mirror。

配置镜像加速之前,先来看下 Containerd 的配置结构,乍一看可能会觉得很复杂,复杂就复杂在 plugin 的配置部分:

  1. [plugins]
  2. [plugins."io.containerd.gc.v1.scheduler"]
  3. pause_threshold = 0.02
  4. deletion_threshold = 0
  5. mutation_threshold = 100
  6. schedule_delay = "0s"
  7. startup_delay = "100ms"
  8. [plugins."io.containerd.grpc.v1.cri"]
  9. disable_tcp_service = true
  10. stream_server_address = "127.0.0.1"
  11. stream_server_port = "0"
  12. stream_idle_timeout = "4h0m0s"
  13. enable_selinux = false
  14. sandbox_image = "k8s.gcr.io/pause:3.1"
  15. stats_collect_period = 10
  16. systemd_cgroup = false
  17. enable_tls_streaming = false
  18. max_container_log_line_size = 16384
  19. disable_cgroup = false
  20. disable_apparmor = false
  21. restrict_oom_score_adj = false
  22. max_concurrent_downloads = 3
  23. disable_proc_mount = false
  24. [plugins."io.containerd.grpc.v1.cri".containerd]
  25. snapshotter = "overlayfs"
  26. default_runtime_name = "runc"
  27. no_pivot = false
  28. [plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]
  29. runtime_type = ""
  30. runtime_engine = ""
  31. runtime_root = ""
  32. privileged_without_host_devices = false
  33. [plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime]
  34. runtime_type = ""
  35. runtime_engine = ""
  36. runtime_root = ""
  37. privileged_without_host_devices = false
  38. [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
  39. [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
  40. runtime_type = "io.containerd.runc.v1"
  41. runtime_engine = ""
  42. runtime_root = ""
  43. privileged_without_host_devices = false
  44. [plugins."io.containerd.grpc.v1.cri".cni]
  45. bin_dir = "/opt/cni/bin"
  46. conf_dir = "/etc/cni/net.d"
  47. max_conf_num = 1
  48. conf_template = ""
  49. [plugins."io.containerd.grpc.v1.cri".registry]
  50. [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
  51. [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
  52. endpoint = ["https://registry-1.docker.io"]
  53. [plugins."io.containerd.grpc.v1.cri".x509_key_pair_streaming]
  54. tls_cert_file = ""
  55. tls_key_file = ""
  56. [plugins."io.containerd.internal.v1.opt"]
  57. path = "/opt/containerd"
  58. [plugins."io.containerd.internal.v1.restart"]
  59. interval = "10s"
  60. [plugins."io.containerd.metadata.v1.bolt"]
  61. content_sharing_policy = "shared"
  62. [plugins."io.containerd.monitor.v1.cgroups"]
  63. no_prometheus = false
  64. [plugins."io.containerd.runtime.v1.linux"]
  65. shim = "containerd-shim"
  66. runtime = "runc"
  67. runtime_root = ""
  68. no_shim = false
  69. shim_debug = false
  70. [plugins."io.containerd.runtime.v2.task"]
  71. platforms = ["linux/amd64"]
  72. [plugins."io.containerd.service.v1.diff-service"]
  73. default = ["walking"]
  74. [plugins."io.containerd.snapshotter.v1.devmapper"]
  75. root_path = ""
  76. pool_name = ""
  77. base_image_size = ""

每一个顶级配置块的命名都是 plugins."io.containerd.xxx.vx.xxx" 这种形式,其实每一个顶级配置块都代表一个插件,其中 io.containerd.xxx.vx 表示插件的类型,vx 后面的 xxx 表示插件的 ID。可以通过 ctr 一览无余:

  1. 🐳 ctr plugin ls
  2. TYPE ID PLATFORMS STATUS
  3. io.containerd.content.v1 content - ok
  4. io.containerd.snapshotter.v1 btrfs linux/amd64 error
  5. io.containerd.snapshotter.v1 devmapper linux/amd64 error
  6. io.containerd.snapshotter.v1 aufs linux/amd64 ok
  7. io.containerd.snapshotter.v1 native linux/amd64 ok
  8. io.containerd.snapshotter.v1 overlayfs linux/amd64 ok
  9. io.containerd.snapshotter.v1 zfs linux/amd64 error
  10. io.containerd.metadata.v1 bolt - ok
  11. io.containerd.differ.v1 walking linux/amd64 ok
  12. io.containerd.gc.v1 scheduler - ok
  13. io.containerd.service.v1 containers-service - ok
  14. io.containerd.service.v1 content-service - ok
  15. io.containerd.service.v1 diff-service - ok
  16. io.containerd.service.v1 images-service - ok
  17. io.containerd.service.v1 leases-service - ok
  18. io.containerd.service.v1 namespaces-service - ok
  19. io.containerd.service.v1 snapshots-service - ok
  20. io.containerd.runtime.v1 linux linux/amd64 ok
  21. io.containerd.runtime.v2 task linux/amd64 ok
  22. io.containerd.monitor.v1 cgroups linux/amd64 ok
  23. io.containerd.service.v1 tasks-service - ok
  24. io.containerd.internal.v1 restart - ok
  25. io.containerd.grpc.v1 containers - ok
  26. io.containerd.grpc.v1 content - ok
  27. io.containerd.grpc.v1 diff - ok
  28. io.containerd.grpc.v1 events - ok
  29. io.containerd.grpc.v1 healthcheck - ok
  30. io.containerd.grpc.v1 images - ok
  31. io.containerd.grpc.v1 leases - ok
  32. io.containerd.grpc.v1 namespaces - ok
  33. io.containerd.internal.v1 opt - ok
  34. io.containerd.grpc.v1 snapshots - ok
  35. io.containerd.grpc.v1 tasks - ok
  36. io.containerd.grpc.v1 version - ok
  37. io.containerd.grpc.v1 cri linux/amd64 ok

顶级配置块下面的子配置块表示该插件的各种配置,比如 cri 插件下面就分为 containerdcniregistry 的配置,而 containerd 下面又可以配置各种 runtime,还可以配置默认的 runtime。
镜像加速的配置就在 cri 插件配置块下面的 registry 配置块,所以需要修改的部分如下:

  1. [plugins."io.containerd.grpc.v1.cri".registry]
  2. [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
  3. [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
  4. endpoint = ["https://dockerhub.mirrors.nwafu.edu.cn"]
  5. [plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]
  6. endpoint = ["https://registry.aliyuncs.com/k8sxio"]
  7. [plugins."io.containerd.grpc.v1.cri".registry.mirrors."gcr.io"]
  8. endpoint = ["xxx"]
  • registry.mirrors.”xxx” : 表示需要配置 mirror 的镜像仓库。例如,registry.mirrors."docker.io" 表示配置 docker.io 的 mirror。
  • endpoint : 表示提供 mirror 的镜像加速服务。例如,这里推荐使用西北农林科技大学提供的镜像加速服务作为 docker.io 的 mirror。

至于 gcr.io,目前还没有公共的加速服务。我自己掏钱搭了个加速服务,拉取速度大概是 3M/s 左右,有加速需求的同学可以通过微信号:cloud-native-yang 加我为好友再详细咨询。

存储配置

Containerd 有两个不同的存储路径,一个用来保存持久化数据,一个用来保存运行时状态。

  1. root = "/var/lib/containerd"
  2. state = "/run/containerd"

root用来保存持久化数据,包括 Snapshots, Content, Metadata 以及各种插件的数据。每一个插件都有自己单独的目录,Containerd 本身不存储任何数据,它的所有功能都来自于已加载的插件,真是太机智了。

  1. 🐳 tree -L 2 /var/lib/containerd/
  2. /var/lib/containerd/
  3. ├── io.containerd.content.v1.content
  4. ├── blobs
  5. └── ingest
  6. ├── io.containerd.grpc.v1.cri
  7. ├── containers
  8. └── sandboxes
  9. ├── io.containerd.metadata.v1.bolt
  10. └── meta.db
  11. ├── io.containerd.runtime.v1.linux
  12. └── k8s.io
  13. ├── io.containerd.runtime.v2.task
  14. ├── io.containerd.snapshotter.v1.aufs
  15. └── snapshots
  16. ├── io.containerd.snapshotter.v1.btrfs
  17. ├── io.containerd.snapshotter.v1.native
  18. └── snapshots
  19. ├── io.containerd.snapshotter.v1.overlayfs
  20. ├── metadata.db
  21. └── snapshots
  22. └── tmpmounts
  23. 18 directories, 2 files

state 用来保存临时数据,包括 sockets、pid、挂载点、运行时状态以及不需要持久化保存的插件数据。

  1. 🐳 tree -L 2 /run/containerd/
  2. /run/containerd/
  3. ├── containerd.sock
  4. ├── containerd.sock.ttrpc
  5. ├── io.containerd.grpc.v1.cri
  6. ├── containers
  7. └── sandboxes
  8. ├── io.containerd.runtime.v1.linux
  9. └── k8s.io
  10. ├── io.containerd.runtime.v2.task
  11. └── runc
  12. └── k8s.io
  13. 8 directories, 2 files

OOM

还有一项配置需要留意:

  1. oom_score = 0

Containerd 是容器的守护者,一旦发生内存不足的情况,理想的情况应该是先杀死容器,而不是杀死 Containerd。所以需要调整 Containerd 的 OOM 权重,减少其被 OOM Kill 的几率。最好是将 oom_score 的值调整为比其他守护进程略低的值。这里的 oom_socre 其实对应的是 /proc/<pid>/oom_socre_adj,在早期的 Linux 内核版本里使用 oom_adj 来调整权重, 后来改用 oom_socre_adj 了。该文件描述如下:

The value of /proc/<pid>/oom_score_adj is added to the badness score before it is used to determine which task to kill. Acceptable values range from -1000 (OOM_SCORE_ADJ_MIN) to +1000 (OOM_SCORE_ADJ_MAX). This allows userspace to polarize the preference for oom killing either by always preferring a certain task or completely disabling it. The lowest possible value, -1000, is equivalent to disabling oom killing entirely for that task since it will always report a badness score of 0.

在计算最终的 badness score 时,会在计算结果是中加上 oom_score_adj ,这样用户就可以通过该在值来保护某个进程不被杀死或者每次都杀某个进程。其取值范围为 -10001000
如果将该值设置为 -1000,则进程永远不会被杀死,因为此时 badness score 永远返回0。
建议 Containerd 将该值设置为 -9990 之间。如果作为 Kubernetes 的 Worker 节点,可以考虑设置为 -999

Systemd 配置

建议通过 systemd 配置 Containerd 作为守护进程运行,配置文件在上文已经被解压出来了:

  1. 🐳 cat /etc/systemd/system/containerd.service
  2. # Copyright The containerd Authors.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. [Unit]
  16. Description=containerd container runtime
  17. Documentation=https://containerd.io
  18. After=network.target local-fs.target
  19. [Service]
  20. ExecStartPre=-/sbin/modprobe overlay
  21. ExecStart=/usr/local/bin/containerd
  22. Type=notify
  23. Delegate=yes
  24. KillMode=process
  25. Restart=always
  26. RestartSec=5
  27. # Having non-zero Limit*s causes performance problems due to accounting overhead
  28. # in the kernel. We recommend using cgroups to do container-local accounting.
  29. LimitNPROC=infinity
  30. LimitCORE=infinity
  31. LimitNOFILE=1048576
  32. # Comment TasksMax if your systemd version does not supports it.
  33. # Only systemd 226 and above support this version.
  34. TasksMax=infinity
  35. OOMScoreAdjust=-999
  36. [Install]
  37. WantedBy=multi-user.target

这里有两个重要的参数:

  • Delegate : 这个选项允许 Containerd 以及运行时自己管理自己创建的容器的 cgroups。如果不设置这个选项,systemd 就会将进程移到自己的 cgroups 中,从而导致 Containerd 无法正确获取容器的资源使用情况。
  • KillMode : 这个选项用来处理 Containerd 进程被杀死的方式。默认情况下,systemd 会在进程的 cgroup 中查找并杀死 Containerd 的所有子进程,这肯定不是我们想要的。KillMode字段可以设置的值如下。
    我们需要将 KillMode 的值设置为 process,这样可以确保升级或重启 Containerd 时不杀死现有的容器。
    • control-group(默认值):当前控制组里面的所有子进程,都会被杀掉
    • process:只杀主进程
    • mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号
    • none:没有进程会被杀掉,只是执行服务的 stop 命令。

现在到了最关键的一步:启动 Containerd。执行一条命令就完事:

  1. 🐳 systemctl enable containerd --now

接下来进入本文最后一部分:Containerd 的基本使用方式。本文只会介绍 Containerd 的本地使用方法,即本地客户端 ctr 的使用方法,不会涉及到 crictl,后面有机会再介绍 crictl

4. Containerd 快速安装

如果你想在一分钟内快速装好 Kubernetes 和 Containerd,可以使用 Sealos 来部署。该项目旨在做一个简单干净轻量级稳定的 kubernetes 安装工具,一条命令,离线安装,包含所有依赖,内核负载不依赖 haproxy keepalived,纯 golang 开发,99 年证书。1.12.0 版本的离线包搭载了最新版本的 Containerd,还支持 arm64 架构,简直就是简直了。
部署方法特别简单,首先下载并安装 sealos, sealos 是个 golang 的二进制工具,直接下载拷贝到 bin 目录即可, release 页面也可下载:

  1. 🐳 wget -c https://sealyun.oss-cn-beijing.aliyuncs.com/latest/sealos
  2. 🐳 chmod +x sealos && mv sealos /usr/bin

下载离线资源包:

  1. 🐳 wget -c https://sealyun.oss-cn-beijing.aliyuncs.com/7b6af025d4884fdd5cd51a674994359c-1.18.0/kube1.18.0.tar.gz

安装一个三 master 的高可用 Kubernetes 集群:

  1. 🐳 sealos init --passwd 123456
  2. --master 192.168.0.2 --master 192.168.0.3 --master 192.168.0.4
  3. --node 192.168.0.5
  4. --pkg-url /root/kube1.18.0.tar.gz
  5. --version v1.18.0

然后就完事了。。。
详细使用方式请访问 https://sealyun.com

5. ctr 使用

ctr 目前很多功能做的还没有 docker 那么完善,但基本功能已经具备了。下面将围绕镜像容器这两个方面来介绍其使用方法。

镜像

镜像下载:

  1. 🐳 ctr i pull docker.io/library/nginx:alpine
  2. docker.io/library/nginx:alpine: resolved |++++++++++++++++++++++++++++++++++++++|
  3. index-sha256:efc93af57bd255ffbfb12c89ec0714dd1a55f16290eb26080e3d1e7e82b3ea66: done |++++++++++++++++++++++++++++++++++++++|
  4. manifest-sha256:6ceeeab513f7d15cea38c1f8dfe5455323b5a1bfd568516b3b0ee70406f75247: done |++++++++++++++++++++++++++++++++++++++|
  5. config-sha256:0fde4fb87e476fd1655b3f04f55aa5b4b3ef7de7c701eb46573bb5a5dcf66fd2: done |++++++++++++++++++++++++++++++++++++++|
  6. layer-sha256:abaddf4965e5e9ce9953f2e136b3bf9cc15365adbcf0c68b108b1cc26c12b1be: done |++++++++++++++++++++++++++++++++++++++|
  7. layer-sha256:05e7bc50f07f000e9993ec0d264b9ffcbb9a01a4d69c68f556d25e9811a8f7f4: done |++++++++++++++++++++++++++++++++++++++|
  8. layer-sha256:c78f7f670e47cf98494e7dbe08e463d34c160bf6a5939a2155ff4438cb8b0e80: done |++++++++++++++++++++++++++++++++++++++|
  9. layer-sha256:ce77cf6a2ede66c463dcdd39f1a43cfbac3723a99e94f697bc20faee0f7cce1b: done |++++++++++++++++++++++++++++++++++++++|
  10. layer-sha256:3080fd9f46494247c9298a6a3d9694f03f6a32898a07ffbe1c17a0752bae5c4e: done |++++++++++++++++++++++++++++++++++++++|
  11. elapsed: 17.3s total: 8.7 Mi (513.8 KiB/s)
  12. unpacking linux/amd64 sha256:efc93af57bd255ffbfb12c89ec0714dd1a55f16290eb26080e3d1e7e82b3ea66...
  13. done

本地镜像列表查询:

  1. 🐳 ctr i ls
  2. REF TYPE DIGEST SIZE PLATFORMS LABELS
  3. docker.io/library/nginx:alpine application/vnd.docker.distribution.manifest.list.v2+json sha256:efc93af57bd255ffbfb12c89ec0714dd1a55f16290eb26080e3d1e7e82b3ea66 9.3 MiB linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x -

这里需要注意PLATFORMS,它是镜像的能够运行的平台标识。
将镜像挂载到主机目录:

  1. 🐳 ctr i mount docker.io/library/nginx:alpine /mnt
  2. 🐳 tree -L 1 /mnt
  3. /mnt
  4. ├── bin
  5. ├── dev
  6. ├── docker-entrypoint.d
  7. ├── docker-entrypoint.sh
  8. ├── etc
  9. ├── home
  10. ├── lib
  11. ├── media
  12. ├── mnt
  13. ├── opt
  14. ├── proc
  15. ├── root
  16. ├── run
  17. ├── sbin
  18. ├── srv
  19. ├── sys
  20. ├── tmp
  21. ├── usr
  22. └── var
  23. 18 directories, 1 file

将镜像从主机目录上卸载:

  1. 🐳 ctr i unmount /mnt

将镜像导出为压缩包:

  1. 🐳 ctr i export nginx.tar.gz docker.io/library/nginx:alpine

从压缩包导入镜像:

  1. 🐳 ctr i import nginx.tar.gz

其他操作可以自己查看帮助:

  1. 🐳 ctr i --help
  2. NAME:
  3. ctr images - manage images
  4. USAGE:
  5. ctr images command [command options] [arguments...]
  6. COMMANDS:
  7. check check that an image has all content available locally
  8. export export images
  9. import import images
  10. list, ls list images known to containerd
  11. mount mount an image to a target path
  12. unmount unmount the image from the target
  13. pull pull an image from a remote
  14. push push an image to a remote
  15. remove, rm remove one or more images by reference
  16. tag tag an image
  17. label set and clear labels for an image
  18. OPTIONS:
  19. --help, -h show help

对镜像的更高级操作可以使用子命令 content,例如在线编辑镜像的 blob 并生成一个新的 digest

  1. 🐳 ctr content ls
  2. DIGEST SIZE AGE LABELS
  3. ...
  4. ...
  5. sha256:fdd7fff110870339d34cf071ee90fbbe12bdbf3d1d9a14156995dfbdeccd7923 740B 7 days containerd.io/gc.ref.content.2=sha256:4e537e26e21bf61836f827e773e6e6c3006e3c01c6d59f4b058b09c2753bb929,containerd.io/gc.ref.content.1=sha256:188c0c94c7c576fff0792aca7ec73d67a2f7f4cb3a6e53a84559337260b36964,containerd.io/gc.ref.content.0=sha256:b7199797448c613354489644be1f60aa2d8e9c2278989100c72ede3001334f7b,containerd.io/distribution.source.ghcr.fuckcloudnative.io=yangchuansheng/grafana-backup-tool
  6. 🐳 ctr content edit --editor vim sha256:fdd7fff110870339d34cf071ee90fbbe12bdbf3d1d9a14156995dfbdeccd7923

容器

创建容器:

  1. 🐳 ctr c create docker.io/library/nginx:alpine nginx
  2. 🐳 ctr c ls
  3. CONTAINER IMAGE RUNTIME
  4. nginx docker.io/library/nginx:alpine io.containerd.runc.v2

查看容器的详细配置:

  1. # 和 docker inspect 类似
  2. 🐳 ctr c info nginx

其他操作可以自己查看帮助:

  1. 🐳 ctr c --help
  2. NAME:
  3. ctr containers - manage containers
  4. USAGE:
  5. ctr containers command [command options] [arguments...]
  6. COMMANDS:
  7. create create container
  8. delete, del, rm delete one or more existing containers
  9. info get info about a container
  10. list, ls list containers
  11. label set and clear labels for a container
  12. checkpoint checkpoint a container
  13. restore restore a container from checkpoint
  14. OPTIONS:
  15. --help, -h show help

任务

上面 create 的命令创建了容器后,并没有处于运行状态,只是一个静态的容器。一个 container 对象只是包含了运行一个容器所需的资源及配置的数据结构,这意味着 namespaces、rootfs 和容器的配置都已经初始化成功了,只是用户进程(这里是 nginx)还没有启动。
然而一个容器真正的运行起来是由 task 对象实现的,task 代表任务的意思,可以为容器设置网卡,还可以配置工具来对容器进行监控等。
所以还需要通过 task 启动容器:

  1. 🐳 ctr task start -d nginx
  2. 🐳 ctr task ls
  3. TASK PID STATUS
  4. nginx 131405 RUNNING

当然,也可以一步到位直接创建并运行容器:

  1. 🐳 ctr run -d docker.io/library/nginx:alpine nginx

进入容器:

  1. # 和 docker 的操作类似,但必须要指定 --exec-id,这个 id 可以随便写,只要唯一就行
  2. 🐳 ctr task exec --exec-id 0 -t nginx sh

暂停容器:

  1. # 和 docker pause 类似
  2. 🐳 ctr task pause nginx

容器状态变成了 PAUSED:

  1. 🐳 ctr task ls
  2. TASK PID STATUS
  3. nginx 149857 PAUSED

恢复容器:

  1. 🐳 ctr task resume nginx

ctr 没有 stop 容器的功能,只能暂停或者杀死容器。
杀死容器:

  1. 🐳 ctr task kill nginx

获取容器的 cgroup 信息:

  1. # 这个命令用来获取容器的内存、CPU 和 PID 的限额与使用量。
  2. 🐳 ctr task metrics nginx
  3. ID TIMESTAMP
  4. nginx 2020-12-15 09:15:13.943447167 +0000 UTC
  5. METRIC VALUE
  6. memory.usage_in_bytes 77131776
  7. memory.limit_in_bytes 9223372036854771712
  8. memory.stat.cache 6717440
  9. cpuacct.usage 194187935
  10. cpuacct.usage_percpu [0 335160 0 5395642 3547200 58559242 0 0 0 0 0 0 6534104 5427871 3032481 2158941 8513633 4620692 8261063 3885961 3667830 0 4367411 356280 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1585841 0 7754942 5818102 21430929 0 0 0 0 0 0 1811840 2241260 2673960 6041161 8210604 2991221 10073713 1111020 3139751 0 640080 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
  11. pids.current 97
  12. pids.limit 0

查看容器中所有进程的 PID

  1. 🐳 ctr task ps nginx
  2. PID INFO
  3. 149857 -
  4. 149921 -
  5. 149922 -
  6. 149923 -
  7. 149924 -
  8. 149925 -
  9. 149926 -
  10. 149928 -
  11. 149929 -
  12. 149930 -
  13. 149932 -
  14. 149933 -
  15. 149934 -
  16. ...

注意:这里的 PID 是宿主机看到的 PID,不是容器中看到的 PID。

命名空间

除了 k8s 有命名空间以外,Containerd 也支持命名空间。

  1. 🐳 ctr ns ls
  2. NAME LABELS
  3. default

如果不指定,ctr 默认是 default 空间。
目前 Containerd 的定位还是解决运行时,所以目前他还不能完全替代 dockerd,例如使用 Dockerfile 来构建镜像。其实这不是什么大问题,我再给大家介绍一个大招:Containerd 和 Docker 一起用!

Containerd + Docker

事实上,Docker 和 Containerd 是可以同时使用的,只不过 Docker 默认使用的 Containerd 的命名空间不是 default,而是 moby。下面就是见证奇迹的时刻。
首先从其他装了 Docker 的机器或者 GitHub 上下载 Docker 相关的二进制文件,然后使用下面的命令启动 Docker:

  1. 🐳 dockerd --containerd /run/containerd/containerd.sock --cri-containerd

接着用 Docker 运行一个容器:

  1. 🐳 docker run -d --name nginx nginx:alpine

现在再回过头来查看 Containerd 的命名空间:

  1. 🐳 ctr ns ls
  2. NAME LABELS
  3. default
  4. moby

查看该命名空间下是否有容器:

  1. 🐳 ctr -n moby c ls
  2. CONTAINER IMAGE RUNTIME
  3. b7093d7aaf8e1ae161c8c8ffd4499c14ba635d8e174cd03711f4f8c27818e89a - io.containerd.runtime.v1.linux

我艹,还可以酱紫?看来以后用 Containerd 不耽误我 docker build 了~~
最后提醒一句:Kubernetes 用户不用惊慌,Kubernetes 默认使用的是 Containerd 的 k8s.io 命名空间,所以 ctr -n k8s.io 就能看到 Kubernetes 创建的所有容器啦,也不用担心 crictl 不支持 load 镜像了,因为 ctr -n k8s.io 可以 load 镜像啊,嘻嘻😬

参考资料

[1]
bucketbench: https://github.com/estesp/bucketbench
[2]
release 页面: https://github.com/containerd/containerd/releases
[3]
Sealos: https://github.com/fanux/sealos