导言

最近在集群中遇到了很多containerd的问题,所以不禁思考我真的懂containerd吗???

前提

本文假设你熟悉了

  1. CRI的概念 && gRPC && socket 参考:https://blog.kalyan.life/2024/07/04/CRI/
  2. shim垫片概念

containerd是什么就不说啦

架构(理解就好,主要在于如何使用,想要研究可以去看源码)

image-20240808100725408

1. API 层

  • gRPC API: 提供给客户端的 gRPC API,支持包括容器管理、镜像管理、存储管理等操作。
  • CRI: 容器运行时接口,允许 Kubernetes 通过 gRPC API 与 containerd 交互。
  • Prometheus: 用于监控的指标接口,收集和暴露 containerd 的运行时数据。

2. 核心层 (Core)

  • Services

    : 包含多种服务,每种服务负责不同的功能模块。

    • Containers Service: 管理容器的生命周期。
    • Content Service: 负责内容管理,处理镜像层的数据。
    • Diff Service: 负责镜像层之间的差异计算。
    • Images Service: 负责镜像的管理。
    • Leases Service: 管理临时资源。
    • Namespaces Service: 提供命名空间支持,隔离不同的容器组。
    • Snapshots Service: 管理快照。
    • Tasks Service: 管理任务的运行。
  • Metadata

    : 提供命名空间支持和元数据管理。

    • Containers, Content, Images, Leases, Namespaces, Snapshots: 各种元数据的管理模块。

3. 后端层 (Backend)

  • Content Store: 负责存储内容,可以通过插件和本地存储实现。

  • Snapshotter

    : 管理文件系统的快照。

    • overlay, btrfs, devmapper, native, windows, plugin: 支持多种文件系统和快照技术。
  • Runtime

    : 支持容器运行时。

    • runc, runhcs, kata, Firecracker, gVisor, shim: 支持多种运行时,包括 runc、runhcs、kata containers、Firecracker 和 gVisor。
    • v2 shim client: 每个容器都有一个 shim 进程,隔离容器的生命周期管理,确保容器的独立运行。

4. 系统层 (System)

  • 支持多种硬件架构和操作系统,包括 ARM、Intel、Windows、Linux。

简化版

  • 分为三⼤块:Storage 管理镜像⽂件的存储;Metadata 管理镜像和容器的元数据;另外由 Task
    触发运⾏时。对外提供 GRPC ⽅式的 API 以及 Metrics 接⼝。

image-20240808102354598

然后我们讲一下重要的配置文件

config.toml

image-20240808103459459

version = 2
# 配置文件版本

root = "/var/lib/containerd"
# containerd 数据存储的根目录

state = "/run/containerd"
# containerd 状态信息存储目录

oom_score = 0
# OOM(Out of Memory)分数调整值,用于内存不足时的优先级

[grpc]
# gRPC 配置部分

max_recv_message_size = 16777216
# gRPC 最大接收消息大小

max_send_message_size = 16777216
# gRPC 最大发送消息大小

[debug]
# 调试配置部分

level = "info"
# 日志级别,info 表示信息级别日志

[metrics]
# 指标配置部分

address = ""
# 指标服务监听地址,空表示不启用

grpc_histogram = false
# 是否启用 gRPC 直方图

[plugins]
# 插件配置部分

[plugins."io.containerd.grpc.v1.cri"]
# CRI 插件配置部分

sandbox_image = "192.168.154.2/registry.k8s.io/pause:3.9"
# 沙箱镜像,用于 Pod 的基础镜像

max_container_log_line_size = -1
# 容器日志行的最大长度,-1 表示不限制

enable_unprivileged_ports = false
# 是否启用非特权端口

enable_unprivileged_icmp = false
# 是否启用非特权 ICMP

[plugins."io.containerd.grpc.v1.cri".containerd]
# containerd 相关配置

default_runtime_name = "runc"
# 默认运行时名称

snapshotter = "overlayfs"
# 默认使用的快照器类型

discard_unpacked_layers = true
# 是否丢弃解包的层

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
# 容器运行时配置部分

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
# runc 运行时配置部分

runtime_type = "io.containerd.runc.v2"
# 运行时类型

runtime_engine = ""
# 运行时引擎

runtime_root = ""
# 运行时根目录

base_runtime_spec = "/etc/containerd/cri-base.json"
# 基础运行时规格文件

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
# runc 运行时选项配置部分

SystemdCgroup = true
# 是否启用 systemd cgroup

BinaryName = "/usr/local/bin/runc"
# runc 二进制文件路径

[plugins."io.containerd.grpc.v1.cri".registry]
# 镜像仓库配置部分

[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
# 镜像仓库镜像配置部分

[plugins."io.containerd.grpc.v1.cri".registry.mirrors."192.168.154.2"]
# 指定的镜像仓库
endpoint = ["https://192.168.154.2"]
# 镜像仓库的端点 URL

[plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.154.2".tls]
# 镜像仓库 TLS 配置部分

insecure_skip_verify = true
# 是否跳过 TLS 证书验证

值得注意的是这里的镜像仓库有两种加载方式,到了2.0版本就可以用

[plugins."io.containerd.grpc.v1.cri".registry]
config_path= "/etc/containerd/certs.d"

# 然后再certs.d目录下面添加你的镜像仓库就好了.

/run/containerd/里面的是什么

image-20240808111628400

  1. containerd.sock:
  • 这是 containerd 的主 Unix socket 文件,用于与 containerd 守护进程进行通信。客户端(例如 Docker 或 Kubernetes)通过这个 socket 文件发送管理指令。
  1. containerd.sock.ttrpc:
  • 这是 containerd 用于 ttrpc(Tiny Transport RPC)通信的 socket 文件。ttrpc 是一种轻量级的 RPC 框架,用于在性能敏感的环境中提供高效的进程间通信。
  1. io.containerd.grpc.v1.cri:
  • 这个目录包含与 Kubernetes CRI(Container Runtime Interface)集成相关的 socket 文件和配置。containerd 支持 CRI,使得 Kubernetes 可以直接通过 containerd 来管理容器。
  1. io.containerd.runtime.v1.linux:
  • 这个目录包含与 v1 版本的 containerd 运行时相关的文件和配置,主要用于 Linux 系统上的容器管理。
  1. io.containerd.runtime.v2.task:
  • 这个目录包含与 v2 版本的 containerd 任务管理相关的文件和配置。v2 版本引入了一些新的特性和改进,用于更高效地管理容器任务。
  1. runc:
  • 这是 runc 运行时的目录。runc 是一个 CLI 工具,用于根据 OCI(Open Container Initiative)规范创建和运行容器。containerd 使用 runc 作为默认的容器运行时。
  1. s:
  • 这个目录的具体用途可能需要根据系统的实际配置来确定。它可能是用于存储临时文件、状态文件或特定插件的目录。

/var/lib/containerd 里面的是什么

image-20240808113725285

  1. io.containerd.content.v1.content:
  • 存储镜像和容器层的内容,包括所有下载的镜像数据。这里的文件通常以内容地址(例如 SHA256 哈希)进行命名和存储。
  1. io.containerd.snapshotter.v1.overlayfs:
  • 这是一个与文件系统快照相关的目录,特别是使用 OverlayFS 作为存储后端时。这个目录包含快照和层的数据,用于构建和管理容器的文件系统层。
  1. io.containerd.snapshotter.v1.btrfs:
  • 类似于 overlayfs 目录,但用于 Btrfs 文件系统。它存储使用 Btrfs 作为存储后端时的快照和层的数据。
  1. io.containerd.snapshotter.v1.devmapper:
  • 用于 Device Mapper 存储后端的快照数据,存储容器层的信息。
  1. io.containerd.grpc.v1.cri:
  • 包含与 Kubernetes CRI 集成相关的数据和配置。Kubernetes 通过这个目录与 containerd 进行通信和数据交换。
  1. tmpmounts:
  • 临时挂载点目录,用于存储容器运行时的临时文件和挂载点信息。

命令工具

常用命令

命令 描述 docker ctr crictl nerdctl
显示镜像列表 显示本地主机上的镜像列表 docker images ctr images list crictl images nerdctl images list
下载镜像 从 registry 中下载指定的镜像 docker pull ctr images pull crictl pull nerdctl pull
上传镜像 将本地的镜像上传到 registry docker push ctr images push 不支持 nerdctl images push

其他命令

命令 描述 docker ctr crictl nerdctl
删除镜像 删除指定的镜像 docker rmi ctr images remove/delete crictl rmi nerdctl rmi
启动容器 创建并启动容器 docker run ctr run crictl run nerdctl run
显示容器列表 显示本地主机上的容器列表 docker ps ctr tasks ls crictl ps nerdctl ps
显示容器详情 显示容器的详细信息 docker inspect ctr task info crictl inspect nerdctl inspect
停止容器 停止容器的运行 docker stop ctr task kill crictl stop nerdctl stop
删除容器 删除指定的容器 docker rm ctr task delete crictl rm nerdctl delete
进入容器 进入正在运行的容器 docker exec ctr task exec crictl exec nerdctl exec
查看容器日志 显示容器的日志输出 docker logs ctr task logs crictl logs nerdctl logs
导出容器 导出容器文件系统为 tar 包 docker export ctr task export 不支持 nerdctl export
导入容器 从导出的 tar 包创建一个新的容器 docker import ctr image import 不支持 nerdctl import
构建镜像 从 Dockerfile 构建镜像 docker build 不支持 不支持 nerdctl build
显示网络列表 显示本地主机上的网络列表 docker network ls ctr network list 不支持 nerdctl network ls
创建网络 创建一个新的网络 docker network create ctr network create 不支持 nerdctl network create
删除网络 删除指定的网络 docker network rm ctr network remove 不支持 nerdctl network rm

Containerd 如何存储镜像和容器。⽬录结构是什么样的。是否⽀持 容量限制

Containerd 是⼀个容器运⾏时管理程序,它使⽤ OCI(Open Container Initiative)标准来定义容器和镜像。镜像通常被存储在⼀个容器镜像存储库(Container Image Store)中,例如 Docker Hub,或者本地的 OCI 镜像存储库。Containerd 会从存储库中下载镜像,并在本地存储。容器则是使⽤镜像创建的运⾏实例。Containerd 将容器的元数据存储在称为 “snapshots” 的⽬录中。每个容器都有⼀个独⽴的快照⽬录,其中包含容器的根⽂件系统和元数据。Containerd 还⽀持将容器存储在可移动的磁盘上,这种⽅式称为 “offline snapshots”。Containerd ⽀持容器资源限制,可以设置 CPU 和内存的限制,也可以设置 I/O 和⽹络带宽等限制。这些限制是通过使⽤ Linux 内核的 cgroup 和 namespace 功能来实现的。

Containerd 如何处理⽇志,是否⽀持轮滚

Containerd 本身并不负责处理容器的⽇志,⽽是将⽇志处理交给容器运⾏时,如 runc 或 CRI- O。这些容器运⾏时⽀持使⽤各种不同的⽅式来处理容器的⽇志,如直接输出到控制台、将⽇志写⼊⽂件或使⽤⽇志聚合⼯具,如 Fluentd 或ELK Stack。对于容器的⽇志轮滚,⼀般是由容器运⾏时负责实现。例如,runc 可以通过指定 –log-opt max-size 和 –log-opt max-file 参数来控制容器⽇志的⼤⼩和轮滚。当容器⽇志⽂件⼤⼩达到指定的⼤⼩时,runc 会⾃动创建⼀个新的⽇志⽂件,并将旧的⽇志⽂件压缩和删除。类似地,CRI- O 也⽀持使⽤ –log-max-file 和 –log-max-size 参数来控制容器⽇志的轮滚。

Containerd 重启,是否会导致容器重启

当 Containerd 重启时,已经在运⾏的容器不会⽴即停⽌或重启。这是因为容器本身是由容器运⾏时(例如 runc 或CRI- O)管理的,⽽不是由 Containerd 直接控制。因此,Containerd 重启不会直接影响容器的运⾏状态。当 Containerd 重启后,容器运⾏时会重新连接到新的 Containerd 进程,以便继续管理容器。在此过程中,容器运⾏时可能会暂停⼀段时间,导致容器内的应⽤程序暂时⽆法访问,但通常这个时间⾮常短暂,只会影响到容器内正在进⾏的临时操作。需要注意的是,如果 Containerd 重启导致容器存储被破坏或不可⽤,那么容器本身可能会受到影响,因为容器的根⽂
件系统和元数据存储在 Containerd 的快照⽬录中。在这种情况下,容器运⾏时可能会⽆法连接到 Containerd,导致容器⽆法正常运⾏。因此,在重启 Containerd 之前,需要确保容器存储的完整性和可⽤性。

Containerd 的⽹络命名空间是什么样的,⽆ CNI 下的容器,是否⽀持⽹络

Containerd 使⽤ Linux 内核的⽹络命名空间(network namespace)来隔离容器的⽹络栈和⽹络配置,以便容器可以拥有⾃⼰独⽴的⽹络栈和⽹络环境。每个容器都有⾃⼰的⽹络命名空间,并且容器之间默认是隔离的,不能相互访问。在没有 CNI(Container Networking Interface)插件的情况下,Containerd 本身并不提供⽹络功能,需要⼿动配置容器的⽹络。可以使⽤ Linux 的⽹络⼯具,如 ip 和 iptables,来⼿动配置容器的⽹络,例如分配 IP 地址、设置路由和防⽕墙规则等。这种⽅式需要⼿动配置和管理,⽐较繁琐。不过,Containerd 可以与 CNI 插件配合使⽤,以便⾃动化配置容器的⽹络。CNI 插件可以在容器创建时⾃动配置⽹络,例如分配 IP 地址、设置⽹络接⼝和路由等。常⻅的 CNI 插件包括 Flannel、Calico、Weave Net 等,它们可以与Containerd 集成,以提供⾃动化的⽹络配置和管理。

Containerd 有没有 KMEM 泄露 的问题

在早期版本的 Containerd 中曾经存在⼀些 KMEM 泄漏的问题。具体来说,这些问题通常与 Containerd 在处理⾼负载情况下使⽤了⼤量的内核内存(KMEM),导致内存泄漏和系统不稳定。不过,Containerd 的开发团队已经在后续版本中修复了这些问题,并采取了⼀些措施来避免 KMEM 泄漏。例如,Containerd 1.4.0 版本中引⼊了 KMEM 限制和监控功能,以便在 Containerd 使⽤⼤量 KMEM 时⾃动降低容器的资源配额,从⽽避免 KMEM 泄漏和系统不稳定。总的来说,如果您使⽤的是较新版本的 Containerd,并且在运⾏期间遇到了 KMEM 泄漏的问题,建议升级到最新版本并检查您的系统配置,以确保已经正确配置了 KMEM 限制和监控。同时,如果您的系统遇到了 KMEM 泄漏等其他问题,也可以向 Containerd 的开发团队报告问题并寻求技术⽀持。

Containerd-shim-runc-v1与v2的区别

containerd-shim-runc-v1 和 containerd-shim-runc-v2 是 containerd 中使⽤的两个 shim 实现。这两个 shim 实现都是使⽤ runc 来启动和管理容器的。containerd-shim-runc-v1 是 containerd 中旧的 shim 实现,它使⽤进程间通信 (IPC) 来与 containerd 守护进程进⾏通信,通常会使⽤ UNIX 域套接字或 FIFO 进⾏通信。它是在 containerd 1.0 中引⼊的,主要⽤于运⾏ Docker 容器,但现在已经被 containerd-shim-runc-v2 替代。containerd-shim-runc-v2 是 containerd 中新的 shim 实现,它使⽤ gRPC 来与 containerd 守护进程进⾏通信。它是在 containerd 1.1 中引⼊的,其⽬标是提供更好的性能和可靠性,并为后续的扩展提供更好的基础设施。与containerd-shim-runc-v1 相⽐,它更加轻量级,并且可以通过 API 配置各种容器和执⾏参数。总的来说,containerd-shim-runc-v2 是 containerd 中更加现代和⾼效的 shim 实现,它⽐ containerd-shimrunc-v1 更具扩展性和可维护性,因此在使⽤ containerd 时应该尽可能地使⽤ containerd-shim-runc-v2。

Containerd的grpc⽅法是如何注册的?

containerd 的 gRPC ⽅法是通过⽣成的 protobuf ⽂件和相应的代码实现的。protobuf ⽂件描述了 containerd ⽀持的API ⽅法和数据结构,然后使⽤这个⽂件⽣成对应的代码(包括客户端和服务器端代码)。这些⾃动⽣成的代码提供了实现⽅法的框架,开发⼈员可以在其中添加⾃⼰的代码以实现具体功能。在 containerd 中,服务器端的 gRPC ⽅法是在 services ⽬录下实现的。每个服务都实现了⼀个接⼝(在 protobuf⽂件中定义),并提供了⼀些⽅法来处理请求。这些⽅法通常采⽤ context 参数来获取请求上下⽂和取消信号,然后使⽤⾃动⽣成的代码处理 protobuf 消息。在这些⽅法中,使⽤的核⼼实现代码通常在 containers、images 或content 等核⼼ package 中实现。服务注册是通过 init 函数实现的,每个服务都在其对应的包中实现了⼀个名为 Register 的函数,该函数在包被导⼊时⾃动运⾏,将⾃⼰注册到 containerd 的 gRPC 服务器上。在 Register 函数中,使⽤
server.RegisterService 函数将⾃⼰的服务实现注册到 containerd 的 gRPC 服务器上,以便客户端可以调⽤。

Containerd与containerd-shim是如何交互的?

在 Containerd 中,containerd-shim 是负责启动容器进程并与 Containerd API 通信的组件。Containerd 通过 gRPC接⼝与 containerd-shim 通信,以便进⾏容器的创建、启动、停⽌、删除等操作。具体⽽⾔,Containerd 与containerd-shim 之间的交互过程如下:

  1. Containerd 使⽤ gRPC 接⼝向 containerd-shim 发送创建容器的请求,包括容器的 ID、镜像、⽂件系统等参数。
  2. containerd-shim 接收到创建容器的请求后,会根据请求中的参数启动⼀个新的容器进程,并创建⼀个对应的Linux namespace。
  3. containerd-shim 将容器的 PID、Stdin、Stdout 和 Stderr 等信息发送回给 Containerd,以便后者能够管理和监控容器的运⾏状态。
  4. 在容器运⾏过程中,Containerd 可以通过 gRPC 接⼝向 containerd-shim 发送信号以启动、停⽌、暂停或恢复容器的运⾏。
  5. 当容器进程退出后,containerd-shim 会向 Containerd 发送容器的退出状态和退出码,Containerd 在接收到这些信息后,会更新容器的状态并删除对应的容器对象。需要注意的是,Containerd 和 containerd-shim 都是独⽴的进程,它们之间通过 gRPC 接⼝通信,因此 Containerd和 containerd-shim 可以在不同的计算机上运⾏,这也为 Containerd 的分布式部署提供了便利。

Containerd-shim与runc是如何交互的?

在容器技术中,runc 是⼀个⽤于启动和管理容器的轻量级⼯具,⽽ containerd-shim 是 containerd ⽤于管理容器⽣命周期的代理。在容器启动的过程中,runc 和 containerd-shim 之间会进⾏以下交互:

  1. Containerd-shim 通过 Containerd API 向 Containerd 发送创建容器的请求。
  2. Containerd 接收到请求后,将请求转发给 runc 来创建容器。runc 将通过 system call 创建新的容器进程,并设置容器的隔离环境(⽐如 namespace、cgroups、rootfs)等参数。
  3. runc 启动新的容器进程并返回 PID 给 Containerd-shim。
  4. Containerd-shim 接收到容器进程的 PID 后,会将 PID 发送回给 Containerd。Containerd 会继续管理和监控容器的⽣命周期。
  5. 在容器运⾏期间,Containerd 可以通过 Containerd API 向 Containerd-shim 发送命令,⽐如停⽌、暂停、恢复、删除容器等操作。
  6. 当容器进程退出后,runc 将容器的退出状态和退出码发送给 Containerd-shim。Containerd-shim 将这些信息发送回给 Containerd,Containerd 将更新容器的状态并删除对应的容器对象。需要注意的是,runc 是⼀个独⽴的⼯具,它与 containerd-shim 的交互是在容器启动时发⽣的。在容器启动成功后,runc 将直接与容器进程交互,⽽ containerd-shim 的作⽤将逐渐变⼩。