浅解析Containerd
导言
最近在集群中遇到了很多containerd的问题,所以不禁思考我真的懂containerd吗???
前提
本文假设你熟悉了
- CRI的概念 && gRPC && socket 参考:https://blog.kalyan.life/2024/07/04/CRI/
- shim垫片概念
containerd是什么就不说啦
架构(理解就好,主要在于如何使用,想要研究可以去看源码)
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 接⼝。
然后我们讲一下重要的配置文件
config.toml
version = 2 |
值得注意的是这里的镜像仓库有两种加载方式,到了2.0版本就可以用
[plugins."io.containerd.grpc.v1.cri".registry] |
/run/containerd/里面的是什么
- containerd.sock:
- 这是 containerd 的主 Unix socket 文件,用于与 containerd 守护进程进行通信。客户端(例如 Docker 或 Kubernetes)通过这个 socket 文件发送管理指令。
- containerd.sock.ttrpc:
- 这是 containerd 用于 ttrpc(Tiny Transport RPC)通信的 socket 文件。ttrpc 是一种轻量级的 RPC 框架,用于在性能敏感的环境中提供高效的进程间通信。
- io.containerd.grpc.v1.cri:
- 这个目录包含与 Kubernetes CRI(Container Runtime Interface)集成相关的 socket 文件和配置。containerd 支持 CRI,使得 Kubernetes 可以直接通过 containerd 来管理容器。
- io.containerd.runtime.v1.linux:
- 这个目录包含与 v1 版本的 containerd 运行时相关的文件和配置,主要用于 Linux 系统上的容器管理。
- io.containerd.runtime.v2.task:
- 这个目录包含与 v2 版本的 containerd 任务管理相关的文件和配置。v2 版本引入了一些新的特性和改进,用于更高效地管理容器任务。
- runc:
- 这是 runc 运行时的目录。runc 是一个 CLI 工具,用于根据 OCI(Open Container Initiative)规范创建和运行容器。containerd 使用 runc 作为默认的容器运行时。
- s:
- 这个目录的具体用途可能需要根据系统的实际配置来确定。它可能是用于存储临时文件、状态文件或特定插件的目录。
/var/lib/containerd 里面的是什么
- io.containerd.content.v1.content:
- 存储镜像和容器层的内容,包括所有下载的镜像数据。这里的文件通常以内容地址(例如 SHA256 哈希)进行命名和存储。
- io.containerd.snapshotter.v1.overlayfs:
- 这是一个与文件系统快照相关的目录,特别是使用 OverlayFS 作为存储后端时。这个目录包含快照和层的数据,用于构建和管理容器的文件系统层。
- io.containerd.snapshotter.v1.btrfs:
- 类似于 overlayfs 目录,但用于 Btrfs 文件系统。它存储使用 Btrfs 作为存储后端时的快照和层的数据。
- io.containerd.snapshotter.v1.devmapper:
- 用于 Device Mapper 存储后端的快照数据,存储容器层的信息。
- io.containerd.grpc.v1.cri:
- 包含与 Kubernetes CRI 集成相关的数据和配置。Kubernetes 通过这个目录与 containerd 进行通信和数据交换。
- 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 之间的交互过程如下:
- Containerd 使⽤ gRPC 接⼝向 containerd-shim 发送创建容器的请求,包括容器的 ID、镜像、⽂件系统等参数。
- containerd-shim 接收到创建容器的请求后,会根据请求中的参数启动⼀个新的容器进程,并创建⼀个对应的Linux namespace。
- containerd-shim 将容器的 PID、Stdin、Stdout 和 Stderr 等信息发送回给 Containerd,以便后者能够管理和监控容器的运⾏状态。
- 在容器运⾏过程中,Containerd 可以通过 gRPC 接⼝向 containerd-shim 发送信号以启动、停⽌、暂停或恢复容器的运⾏。
- 当容器进程退出后,containerd-shim 会向 Containerd 发送容器的退出状态和退出码,Containerd 在接收到这些信息后,会更新容器的状态并删除对应的容器对象。需要注意的是,Containerd 和 containerd-shim 都是独⽴的进程,它们之间通过 gRPC 接⼝通信,因此 Containerd和 containerd-shim 可以在不同的计算机上运⾏,这也为 Containerd 的分布式部署提供了便利。
Containerd-shim与runc是如何交互的?
在容器技术中,runc 是⼀个⽤于启动和管理容器的轻量级⼯具,⽽ containerd-shim 是 containerd ⽤于管理容器⽣命周期的代理。在容器启动的过程中,runc 和 containerd-shim 之间会进⾏以下交互:
- Containerd-shim 通过 Containerd API 向 Containerd 发送创建容器的请求。
- Containerd 接收到请求后,将请求转发给 runc 来创建容器。runc 将通过 system call 创建新的容器进程,并设置容器的隔离环境(⽐如 namespace、cgroups、rootfs)等参数。
- runc 启动新的容器进程并返回 PID 给 Containerd-shim。
- Containerd-shim 接收到容器进程的 PID 后,会将 PID 发送回给 Containerd。Containerd 会继续管理和监控容器的⽣命周期。
- 在容器运⾏期间,Containerd 可以通过 Containerd API 向 Containerd-shim 发送命令,⽐如停⽌、暂停、恢复、删除容器等操作。
- 当容器进程退出后,runc 将容器的退出状态和退出码发送给 Containerd-shim。Containerd-shim 将这些信息发送回给 Containerd,Containerd 将更新容器的状态并删除对应的容器对象。需要注意的是,runc 是⼀个独⽴的⼯具,它与 containerd-shim 的交互是在容器启动时发⽣的。在容器启动成功后,runc 将直接与容器进程交互,⽽ containerd-shim 的作⽤将逐渐变⼩。