1.我在B站学云原生之Containerd容器运行时‎基础介绍与Ubuntu系统中安装配置实践
全栈工程师修炼指南
2022年04月24日 15:58
收录于文集
共39篇

帅哥(靓仔)、美女,点个关注后续不迷路!

本章目录


  • 0x00 前言简述

    • 1.基础介绍

    • 2.专业术语

    • 3.架构简述

      • CRI 的架构

      • Containerd 的架构

  • 0x01 安装配置

    • 1.Ubuntu安装Containerd.io流程


作者: WeiyiGeek

原文地址:https://blog.weiyigeek.top/2021/6-27-570.html


0x00 前言简述

描述: 目前Docker是Kubernetes默认的容器运行时(Container Runtime), 由于k8s在2020年宣布1.20版本之后将弃用 dockershim (其中也有kubernetes与Docker爱恨情仇)时,才把containerd拉回大众的视野之中,所以本章主要讲解containerd基础入门。

1.基础介绍

Q: 什么是Containerd?

答: Containerd是从Docker中分类出的容器运行时与runc一样被分解为Docke的高级运行时部分,它支持 OCI 的镜像标准、可以实现拉取和推送镜像、管理操作镜像负责容器的整个生命周期。 例如当它需要运行一个容器时,它会将映像解压到一个OCI运行时包中,并将其发送给runc来运行它,Containerd还提供了一个API和客户端应用程序可以用来与之交互,containerd命令行客户端是ctr命令。 官方介绍: An industry-standard container runtime with an emphasis on simplicity, robustness and portability - 业界标准的容器运行时,强调简单性、健壮性和可移植性。

Q: Containerd 有何特点?

  • OCI Image Spec support - OCI图像规范支持

  • OCI Runtime Spec support - OCI运行时规范支持

  • Image push and pull support - 图像推拉支持

  • Container runtime and lifecycle support - 容器运行时和生命周期支持

  • Network primitives for creation, modification, and deletion of interfaces - 用于创建、修改和删除接口的网络原语

  • Management of network namespaces containers to join existing namespaces - 管理连接现有名称空间的网络名称空间容器

  • Multi-tenant supported with CAS storage for global images - CAS存储支持用于全局映像的多租户

发展历史: ( Containerd 和 Docker 的前世今生以及爱恨情仇) 在几年之前 Docker 公司在容器技术领域强势崛起一家独大,Google、RedHat 这样的巨头们都产生了很大的危机感,因此他们想与 Docker 公司一起联合研发推进一个开源的容器运行时作为 Docker 技术的核心依赖。然鹅 Docker 公司很高冷的表示:我不干!巨头们听到这个反馈就很不爽啊,因此通过一些手段对 Docker 公司软硬兼施,使其将 libcontainer 捐给了开源社区,也就是现在的 runc,一个 low level 的容器运行时。此外巨头们又成立了 CNCF 去对抗 Docker 公司的一家独大,CNCF 成立的思路很明确:在容器领域干不过 Docker,那就搞容器上层的建设——容器编排,从此 K8s 诞生了。虽然 Docker 公司也尝试使用 Swarm 去对抗 K8s但最终也失败了。

自此K8s慢慢成为云原生领域的标配,其生态也越做越大、越做越完善。Docker 公司为了融入生态,开源了 Docker 的核心依赖 containerd 。2019年2月28日containerd正式成为云原生计算基金会(Cloud Native Computing Foundation -CNCF)的一个毕业项目,紧随之后,在从containerd1.5开始,Kubernetes容器运行时接口(CRI)的containerd插件已经合并到containerd中。。

此外 K8s 为了对接下一层的容器,也因为其中立性搞了一个运行时接口,也就是 ,runc、containerd 等运行时都去支持此接口。由于当时确实没有啥 high level 的 runtime,oci-o 虽然支持 CRI 接口,但其功能太弱;containerd 也尚未成熟,而且其当时的定位是嵌入到系统中,并非给终端用户使用;rkt 有自己的一套体系后来这个项目也失败了。只能暂时为了适配 Docker 搞了个 shim,将 CRI 转换为 Docker API。K8s 和 Docker 进入了冷静期,双方都在各自优化自己,互不干扰。然而平静之下仍是暗潮汹涌,CNCF 社区一直在不断完善 containerd,其定位也发生了改变,由原来的系统嵌入组件,变成了今天的,并提供给终端用户使用。直到2020年12月 K8s 宣布废弃使用 Docker,而改用 Containerd。

总结: 其实kubernetes宣布废弃dockershim,一方面是因为商业因素,而另一方面 K8s 已经提供了标准接口对接底层容器运行时,不再想单独维护一个类似于的适配层去迎合不同的运行时。

Q: 那什么是dockershim? 描述: dockershim 是 Kubernetes 的一个组件,其作用是为了操作Docker。Docker是在2013年面世的而Kubernetes是在2016年,所以Docker刚开始并没有想到编排,也不会知道会出现Kubernetes这个庞然大物(它要是知道,也不会败的那么快)。但是Kubernetes在创建的时候就是以Docker作为容器运行时,很多操作逻辑都是针对的Docker,随着社区越来越健壮,为了兼容更多的容器运行时,才将Docker的相关逻辑独立出来组成了dockershim。

正因为这样,只要Kubernetes的任何变动或者Docker的任何变动,都必须维护dockershim,这样才能保证足够的支持,但是通过dockershim操作Docker,其本质还是操作Docker的底层运行时Containerd,而且Containerd自身也是支持CRI(Container Runtime Interface)交互,所以正如前面所说只是kubernetes不想单独维护一个类似于的适配层去迎合不同的运行时。

相关参考 Containerd 官网: https://containerd.io/ Containerd 帮助文档: https://containerd.io/docs/ CNI 网络项目: https://github.com/containernetworking

Tips: 在github存储库中的 containerd 基本项目级信息:

  • containerd/containerd :containerd的主要项目repo,包括container运行时以及从containerd 1.5开始,用于Kubernetes容器运行时接口(CRI)已并入containerd。

  • containerd/containerd.io : 用于构建containerd网站和文档的资产(即您当前正在阅读的内容)

  • containerd/project : 跨containerd存储库使用的实用程序,如脚本、公共文件和核心文档

  • containerd/ttrpc : containerd使用的gRPC版本(为低内存环境设计)

2.专业术语

描述: 在介绍容器运行时相关概念及组件原理,梳理下我们常听到的 OCI、runc、containerd 等名词之间的关系。

OCI Docker 公司与 CoreOS 和 Google 共同创建了  并提供了两种规范:

  • 1.镜像规范 (https://github.com/opencontainers/image-spec) 镜像规范定义了OCI镜像的标准,high level 运行时将会下载一个OCI 镜像,并把它解压成OCI 运行时文件系统包(filesystem bundle),例如 制定镜像格式、操作等

  • 2.运行时规范 (https://github.com/opencontainers/runtime-spec): 描述了如何从OCI 运行时文件系统包运行容器程序,并且定义它的配置、运行环境和生命周期,如何为新容器设置命名空间(namepsaces)和控制组(cgroups),以及挂载根文件系统等等操作,都是在这里定义的。它的一个参考实现是runC(低层级运行时- Low-level Runtime)除它以外也有很多其他的运行时遵循OCI标准,例如kata-runtime。

Tips: : 定义了一种将容器编码为文件系统束的格式,即以某种方式组织的一组文件,并包含所有符合要求的运行时对其执行所有标准操作的必要数据和元数据,即 config.json 与 根文件系统。

Tips:  : 它是为了解决这些容器运行时和Kubernetes的集成问题在Kubernetes 1.5版本中推出。

Docker、Google 等开源了用于运行容器的工具和库 runC 作为 OCI 的一种实现参考, 随后各种运行时和库也慢慢出现例如 ,然而这些工具所拥有的功能却不尽相同,有的只有运行容器(runc、lxc),而有的除此之外也可以对镜像进行管理(containerd、cri-o), 按照前面容器运行时进行分为两类, 其不同容器运行时工具分类关系图如下。

Runtime

  • 1.容器运行时(Container Runtime): 运行于Docker或者Kubernetes集群的每个节点中, 负责容器的整个生命周期,包括构建、创建、运行、管理、删除等对容器的操作。其中Docker是目前应用最广的,随着容器云的发展,越来越多的容器运行时涌现。

  • 2.容器运行时分成了 low-level 和 high-level 两类。

    • (1) low-level : 指的是仅关注运行容器的容器运行时,调用操作系统,使用 namespace 和 cgroup 实现资源隔离和限制

    • (2) high-level :指包含了更多上层功能,例如 grpc调用,镜像存储管理等。

(1)  : 主要关注如何与操作系统资源交互和创建并运行容器。目前常见的 low-level runtime有:

(2)  : 主要负责传输和管理容器镜像,解压镜像,并传递给low-level runtimes来运行容器。目前主流的 high-level runtime 有:

CRI 描述: CRI是Kubernetes定义的一组gRPC服务。Kubelet作为客户端,基于gRPC框架,通过Socket和容器运行时通信。它包括两类服务:镜像服务(Image Service)和运行时服务(Runtime Service)。镜像服务提供下载、检查和删除镜像的远程程序调用。运行时服务包含用于管理容器生命周期,以及与容器交互的调用  的远程程序调用。

CRI接口客户端工具

  • ctr: 是containerd本身的 CLI (https://github.com/containerd/containerd/tree/master/cmd/ctr)

  • crictl: 是Kubernetes社区定义的专门 CLI 工具 (https://github.com/kubernetes-sigs/cri-tools)

3.架构简述

CRI 的架构

描述: CRI 插件是 Kubernetes 容器运行时接口 (CRI) 的实现, Containerd 与 Kubelet 在同一节点上运行。

containerd 中的插件处理来自 Kubelet 的所有 CRI 服务请求,并使用容器内部来管理容器和容器映像,该插件使用 containerd 来管理整个容器生命周期和所有容器映像,并通过CNI(另一个CNCF项目)管理pod网络,如下图所示。

CRI 插件使用

让我们用一个例子来演示当 Kubelet 创建一个单容器 pod 时,插件是如何工作的

  • Kubelet 通过 CRI 运行时服务 API 调用插件来创建 pod;

  • cri 创建 Pod 的网络命名空间,然后使用 CNI 对其进行配置;

  • cri 使用 containerd internal 创建和启动一个特殊的暂停容器(沙盒容器),并将该容器放在 pod 的 cgroup 和命名空间中(为简洁起见省略了步骤);

  • Kubelet随后通过CRI镜像服务API调用插件,拉取应用容器镜像;

  • cri 进一步使用容器来拉取图像,如果图像不存在于节点上;

  • 然后 Kubelet 通过 CRI 运行时服务 API 调用,使用拉取的容器映像在 pod 内创建和启动应用程序容器;cri

  • cri 最后使用 containerd internal 创建应用程序容器,将其放在 pod 的 cgroup 和命名空间中,然后启动 pod 的新应用程序容器。完成这些步骤后,将创建并运行 Pod 及其相应的应用程序容器。

Containerd 的架构

描述: containerd是Linux和Windows的守护程序。它管理其主机系统的完整容器生命周期,从图像传输和存储到容器执行和监督,再到低级存储到网络附件等等。

Containerd 的设计是一个大的插件系统,下图中每一个虚线框其实就对应一个插件。

  • 1.底层系统支持 Linux 、Windows操作系统 和 支持 arm 和 x86 架构

  • 2.中间 containerd 包含 Backend、core、API 三层

    • Backend 层: Runtime plugin 提供了容器运行时的具体操作,为了支持不同的容器运行时 containerd 也提供了一系列的 containerd-shim

    • Core 层: 则是核心部分,提供了各种功能的服务,其中比较常用的是 Content service ,提供对镜像中可寻址内容的访问,所有不可变的内容都被存储在这里;Images Service 提供镜像相关的操作;Snapshot Plugin : 用来管理容器镜像的文件系统快照。镜像中的每一个 layer 都会被解压成文件系统快照,类似于 Docker 中的 graphdriver

    • API 层: 通过 GRPC 与客户端连接,例如提供了给 Prometheus 使用 API 来进行监控,给kubernetes提供了CRI接口,给containerd提供了服务处理程序。

  • 3.高层提供各种客户端,包括 K8s 的 kubelet,containerd 自己的命令行 ctr 等。

先来看看 Containerd 的架构及其生态:

Containerd 的架构及其生态

可以看到 Containerd 仍然采用标准的 C/S 架构,服务端通过 GRPC 协议提供稳定的 API,客户端通过调用服务端的 API 进行高级的操作。

为了解耦,Containerd 将不同的职责划分给不同的组件,每个组件就相当于一个子系统(subsystem)。连接不同子系统的组件被称为模块。

总体上 Containerd 被划分为两个子系统:

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

  • Runtime : Runtime 子系统用来执行 Bundles,比如创建容器。

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

介绍几个常用的插件:

  • Content Plugin : 提供对镜像中可寻址内容的访问,所有不可变的内容都被存储在这里。

  • Snapshot Plugin : 用来管理容器镜像的文件系统快照。镜像中的每一个 layer 都会被解压成文件系统快照,类似于 Docker 中的 graphdriver。

  • Metrics : 暴露各个组件的监控指标。

我们可以将上面的架构图简化如下, 简化后的Containerd 分为三大块, 并对外提供 GRPC 方式的 API 以及 Metrics 接口。

containerd-shim 描述: 主要是用于剥离 containerd 守护进程与容器进程。目前已有 shim v1 和 shim v2 两个版本;它是containerd中的一个组件,其通过 shim 调用 runc 的包函数来启动容器。直白的说引入shim是允许runc在创建和运行容器之后退出, 并将shim作为容器的父进程, 而不是containerd作为父进程。

代码块
JavaScript
自动换行
复制代码
# - 当我们执行 pstree 命令时可以看到如下的进程关系containerd-shim是独立于containerd进程运行的。
pstree
systemd─┬─VGAuthService
        ├─containerd───15*[{containerd}]
        ├─containerd-shim─┬─sh
        │                 └─13*[{containerd-shim}]
        ├─2*[containerd-shim─┬─sh]
        │                    └─12*[{containerd-shim}]]
复制成功

此种方式的目的是当containerd进程挂掉时保证容器不受影响, 此外 shim 也可以收集和报告容器的退出状态,不需要 containerd 来 wait 容器进程。

Tips:我们有需求去替换 runc 运行时工具库时,例如 替换为安全容器 kata container 或 Google 研发的 gViser,则需要增加对应的  以上两者均有自己实现的 shim。

CRI & OCI 如下图所示 dockershim,containerd 和 cri-o 都是遵循CRI的容器运行时,我们称他们为高层级运行时(High-level Runtime)。

Containerd vs Cri-o 描述: 前面我们说到过kubernetes为啥会替换掉Docker呢?主要原因就是其复杂性,由于Docker的多层封装和调用,导致其在可维护性上略逊一筹,增加了线上问题的定位难度(貌似除了重启Docker,我们就毫无他法了);

  • 如下图所示,我们总结了Docker,containerd以及cri-o的详细调用层级, Containerd和cri-o的方案比起 Docker 简洁很多, 因此我们更偏向于选用更加简单和纯粹的 containerd 和 cri-o 作为我们的容器运行时,kubernetes 1.20.x 之上的版本建议使用containerd作为容器运行时。

  • 如下图所示,我们对containerd和cri-o进行了一组性能测试,包括创建、启动、停止和删除容器,得出它们所耗的时间。 Tips : containerd在各个方面都表现良好,除了启动容器这项。从总用时来看 containerd的用时还是要比cri-o要短的。

  • 如下图所示, 从功能性来讲containerd和cri-o都符合CRI和OCI的标准。从稳定性来说,单独使用containerd和cri-o都没有足够的生产环境经验。但庆幸的是,containerd一直在Docker里使用,而Docker的生产环境经验可以说比较充足。可见在稳定性上containerd略胜一筹。所以我们最终选用了containerd。

Device Mapper vs. Overlayfs 描述: 容器运行时使用存储驱动程序(storage driver)来管理镜像和容器的数据。目前我们生产环境选用的是Device Mapper。然而目前Device Mapper在新版本的Docker中已经被弃用,containerd也放弃对Device Mapper的支持。

当初选用 Device Mapper,也是有历史原因的。我们大概是在2014年开始Kubernetes这个项目的。那时候Overlayfs都还没合进kernel。当时我们评估了Docker支持的存储驱动程序,发现Device Mapper是最稳定的。所以我们选用了Device Mapper。但是实际使用下来,由Device Mapper引起的Docker问题也不少。所以我们也借这个契机把Device Mapper给换掉,换成现在containerd和Docker都默认的Overlayfs。

从下图的测试结果来看,Overlayfs 的 IO性能比 Device Mapper 好很多。Overlayfs的IOPS大体上能比Device Mapper高20%,和直接操作主机路径差不多。最终,我们选用了containerd,并以Overlayfs作为存储后端的文件系统,替换了原有的Docker加Device Mapper的搭配。

我们在同一个节点上同时起10,30,50和80的Pod,然后再同时删除,去比较迁移前后创建和删除的用时。从下图可知,containerd用时明显优于Docker。

Docker和containerd除了上述常用命令有些区别外,在容器日志及相关参数配置方面也存在一些差异

0x01 安装配置

1.Ubuntu安装Containerd.io流程

安装环境: 已进行Ubuntu安全基线加固(SecOpsDev/Ubuntu-InitializeSecurity.sh at master · WeiyiGeek/SecOpsDev (github.com))以及系统优化。

加固脚本: https://github.com/WeiyiGeek/SecOpsDev/blob/master/OS-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/Linux/Ubuntu/Ubuntu-InitializeSecurity.sh

代码块
JavaScript
自动换行
复制代码
# - OS
$ lsb_release -a
  # Description:    Ubuntu 20.04.2 LTS focal
$ uname -a
  # Linux containerd 5.4.0-78-generic #87-Ubuntu SMP Fri Jun 18 16:29:09 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

# - Container : 1.4.6-1
$ apt-cache madison containerd.io | cut -d '|' -f 2 |  sed 's/^[ \t]*//g'
  # 1.4.6-1
  # 1.4.4-1
  # 1.4.3-2
  # 1.4.3-1
  # 1.3.9-1
  # 1.3.7-1
  # 1.2.13-2
复制成功

安装流程

  • Step 1.参考Kubernetes容器运行时接口进行安装前的基础环境准备.

代码块
Shell
自动换行
复制代码
# 安装和配置先决条件:
# 模块加载
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter

# Setup required sysctl params, these persist across reboots.
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.ipv4.ip_forward                 = 1
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

# Apply sysctl params without reboot
sudo sysctl --system
复制成功
  • Step 2.在Ubuntu中安装containerd.io,它来自官方Docker存储库的软件包我们可以参考安装Install Docker Engine(https://docs.docker.com/engine/install/ubuntu/)中的安装命令。

代码块
Shell
自动换行
复制代码
# - 卸载原有 Docker 以及 containerd
sudo apt-get remove docker docker-engine docker.io containerd runc
# - 更新apt程序包索引并安装程序包,以允许apt通过HTTPS使用存储库
sudo apt-get update
sudo apt-get install \
  apt-transport-https \
  ca-certificates \
  curl \
  gnupg \
  lsb-release

# - 添加Docker的官方GPG密钥:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# - 使用以下命令设置稳定存储库。要添加nightly或test存储库,请在下面的命令中的单词stable后面添加单词nightly或test(或两者)。
echo \
  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# - 更新apt包索引,安装最新版本的containerd或进入下一步安装特定版本:
sudo apt-get update
# 安装前可查看containerd.io可用的版本: apt-cache madison containerd.io && apt install containerd.io=1.4.6-1
apt install containerd.io=1.4.6-1

# - 查看安装Containerd的版本
$ ctr --version
  # ctr containerd.io 1.4.6
复制成功

  • Step 3.初始化Containerd.io基础配置

代码块
Shell
自动换行
复制代码
# - 初始化配置
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
# - 替换默认的 sandbox_image = "k8s.gcr.io/pause:3.2" 镜像因为GFW的原因导致无法访问我们可以替换为阿里云镜像源。
sed -ir 's#sandbox_image = \S\w+.*$#sandbox_image = "registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0"#g' /etc/containerd/config.toml
sed -ir "s#https://registry-1.docker.io#https://xlx9erfu.mirror.aliyuncs.com#g"  /etc/containerd/config.toml

# - 应用配置并重新运行containerd服务并查看服务状态
systemctl daemon-reload && systemctl restart containerd
systemctl status containerd.service
  # ● containerd.service - containerd container runtime
  #     Loaded: loaded (/lib/systemd/system/containerd.service; enabled; vendor preset: enabled)
  #     Active: active (running) since Sun 2021-06-27 19:05:48 CST; 8s ago
  #       Docs: https://containerd.io
  #     Process: 3088 ExecStartPre=/sbin/modprobe overlay (code=exited, status=0/SUCCESS)
  #   Main PID: 3097 (containerd)
  #       Tasks: 15
  #     Memory: 22.6M
  #     CGroup: /system.slice/containerd.service
  #             └─3097 /usr/bin/containerd
复制成功

Tips : 查看默认的配置文件,其中root与state 分别表示 Containerd 有两个不同的存储路径,一个用来保存持久化数据,一个用来保存运行时状态。

代码块
Shell
自动换行
复制代码
$ cat /etc/containerd/config.toml
version = 2
root = "/var/lib/containerd"
state = "/run/containerd"
plugin_dir = ""
disabled_plugins = []
required_plugins = []
oom_score = 0

[grpc]
  address = "/run/containerd/containerd.sock"
  tcp_address = ""
  tcp_tls_cert = ""
  tcp_tls_key = ""
  uid = 0
  gid = 0
  max_recv_message_size = 16777216
  max_send_message_size = 16777216

[ttrpc]
  address = ""
  uid = 0
  gid = 0

[debug]
  address = ""
  uid = 0
  gid = 0
  level = ""

[metrics]
  address = ""
  grpc_histogram = false

[cgroup]
  path = ""

[timeouts]
  "io.containerd.timeout.shim.cleanup" = "5s"
  "io.containerd.timeout.shim.load" = "5s"
  "io.containerd.timeout.shim.shutdown" = "3s"
  "io.containerd.timeout.task.state" = "2s"

[plugins]
  [plugins."io.containerd.gc.v1.scheduler"]
    pause_threshold = 0.02
    deletion_threshold = 0
    mutation_threshold = 100
    schedule_delay = "0s"
    startup_delay = "100ms"
  [plugins."io.containerd.grpc.v1.cri"]
    disable_tcp_service = true
    stream_server_address = "127.0.0.1"
    stream_server_port = "0"
    stream_idle_timeout = "4h0m0s"
    enable_selinux = false
    selinux_category_range = 1024
    sandbox_image = "registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0"
    stats_collect_period = 10
    systemd_cgroup = false
    enable_tls_streaming = false
    max_container_log_line_size = 16384
    disable_cgroup = false
    disable_apparmor = false
    restrict_oom_score_adj = false
    max_concurrent_downloads = 3
    disable_proc_mount = false
    unset_seccomp_profile = ""
    tolerate_missing_hugetlb_controller = true
    disable_hugetlb_controller = true
    ignore_image_defined_volumes = false
    [plugins."io.containerd.grpc.v1.cri".containerd]
      snapshotter = "overlayfs"
      default_runtime_name = "runc"
      no_pivot = false
      disable_snapshot_annotations = true
      discard_unpacked_layers = false
      [plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]
        runtime_type = ""
        runtime_engine = ""
        runtime_root = ""
        privileged_without_host_devices = false
        base_runtime_spec = ""
      [plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime]
        runtime_type = ""
        runtime_engine = ""
        runtime_root = ""
        privileged_without_host_devices = false
        base_runtime_spec = ""
      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
          runtime_type = "io.containerd.runc.v2"
          runtime_engine = ""
          runtime_root = ""
          privileged_without_host_devices = false
          base_runtime_spec = ""
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
    [plugins."io.containerd.grpc.v1.cri".cni]
      bin_dir = "/opt/cni/bin"
      conf_dir = "/etc/cni/net.d"
      max_conf_num = 1
      conf_template = ""
    [plugins."io.containerd.grpc.v1.cri".registry]
      [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
          endpoint = ["https://registry-1.docker.io"]
    [plugins."io.containerd.grpc.v1.cri".image_decryption]
      key_model = ""
    [plugins."io.containerd.grpc.v1.cri".x509_key_pair_streaming]
      tls_cert_file = ""
      tls_key_file = ""
  [plugins."io.containerd.internal.v1.opt"]
    path = "/opt/containerd"
  [plugins."io.containerd.internal.v1.restart"]
    interval = "10s"
  [plugins."io.containerd.metadata.v1.bolt"]
    content_sharing_policy = "shared"
  [plugins."io.containerd.monitor.v1.cgroups"]
    no_prometheus = false
  [plugins."io.containerd.runtime.v1.linux"]
    shim = "containerd-shim"
    runtime = "runc"
    runtime_root = ""
    no_shim = false
    shim_debug = false
  [plugins."io.containerd.runtime.v2.task"]
    platforms = ["linux/amd64"]
  [plugins."io.containerd.service.v1.diff-service"]
    default = ["walking"]
  [plugins."io.containerd.snapshotter.v1.devmapper"]
    root_path = ""
    pool_name = ""
    base_image_size = ""
    async_remove = false
复制成功

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

代码块
Shell
自动换行
复制代码
$ tree -L 2 /var/lib/containerd/
/var/lib/containerd/
├── io.containerd.content.v1.content
│   ├── blobs
│   └── ingest
├── io.containerd.grpc.v1.cri
│   ├── containers
│   └── sandboxes
├── io.containerd.metadata.v1.bolt
│   └── meta.db
├── io.containerd.runtime.v1.linux
│   └── k8s.io
├── io.containerd.runtime.v2.task
├── io.containerd.snapshotter.v1.aufs
│   └── snapshots
├── io.containerd.snapshotter.v1.btrfs
├── io.containerd.snapshotter.v1.native
│   └── snapshots
├── io.containerd.snapshotter.v1.overlayfs
│   ├── metadata.db
│   └── snapshots
└── tmpmounts

18 directories, 2 files
复制成功

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

代码块
Shell
自动换行
复制代码
🐳  → tree -L 2 /run/containerd/
/run/containerd/
├── containerd.sock
├── containerd.sock.ttrpc
├── io.containerd.grpc.v1.cri
│   ├── containers
│   └── sandboxes
├── io.containerd.runtime.v1.linux
│   └── k8s.io
├── io.containerd.runtime.v2.task
└── runc
    └── k8s.io

8 directories, 2 files
复制成功

温馨提示: 值得注意的是 OOM 这一配置项  Containerd 是容器的守护者,一旦发生内存不足的情况,理想的情况应该是先杀死容器,而不是杀死 Containerd。所以需要调整 Containerd 的 OOM 权重,减少其被 OOM Kill 的几率,最好是将 oom_score 的值调整为比其他守护进程略低的值。这里的 oom_socre 其实对应的是 ,在早期的 Linux 内核版本里使用 oom_adj 来调整权重, 后来改用  了。在计算最终的  时,会在计算结果是中加上 oom_score_adj ,这样用户就可以通过该在值来保护某个进程不被杀死或者每次都杀某个进程。其取值范围为 -1000 到 1000。

如果将该值设置为 -1000,则进程永远不会被杀死,因为此时 badness score 永远返回0, 建议 Containerd 将该值设置为 -999 到 0 之间。如果作为 Kubernetes 的 Worker 节点,可以考虑设置为 -999。

  • Step 4.查看containerd Client与Server端的信息。

代码块
Shell
自动换行
复制代码
$ ctr version
Client:
  Version:  1.4.6
  Revision: d71fcd7d8303cbf684402823e425e9dd2e99285d
  Go version: go1.13.15

Server:
  Version:  1.4.6
  Revision: d71fcd7d8303cbf684402823e425e9dd2e99285d
  UUID: 71a28bbb-6ed6-408d-a873-e394d48b35d8
复制成功
  • Step 5.查看 Containerd 的 systemd 配置与自启设置。

代码块
Shell
自动换行
复制代码
$ cat /lib/systemd/system/containerd.service | grep -v "#"

[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target local-fs.target

[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/bin/containerd

Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5
LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=1048576
TasksMax=infinity
OOMScoreAdjust=-999

[Install]
WantedBy=multi-user.target

# 现在到了最关键的一步,启用 Containerd 自启动。
systemctl enable containerd --now
复制成功

上述配置中有两个重要参数即 Delegate 和 KillMode。

  • Delegate : 这个选项允许 Containerd 以及运行时自己管理自己创建的容器的 cgroups。如果不设置这个选项,systemd 就会将进程移到自己的 cgroups 中,从而导致 Containerd 无法正确获取容器的资源使用情况。

  • KillMode : 这个选项用来处理 Containerd 进程被杀死的方式。默认情况下,systemd 会在进程的 cgroup 中查找并杀死 Containerd 的所有子进程,这肯定不是我们想要的。KillMode字段可以设置的值如下。

    • control-group(默认值):当前控制组里面的所有子进程,都会被杀掉

    • process:只杀主进程

    • mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号

    • none:没有进程会被杀掉,只是执行服务的 stop 命令。

我们需要将 KillMode 的值设置为 process,这样可以确保升级或重启 Containerd 时不杀死现有的容器。


原文地址:https://blog.weiyigeek.top/2021/6-27-570.html


本文至此完毕,更多技术文章,尽情期待下一章节!


已发布的相关历史文章(点击即可进入)

1.我在B站学云原生之快速拥抱下一代容器引擎Podman来替代Docker容器​

2.我在B站学云原生之快速拥抱下一代容器引擎Podman常用命令浅析与简单配置​


1.我在B站学云原生之Kubernetes基础入门学习概述系统架构及组件浅析​

2.我在B站学云原生之Kubernetes基础概念名词浅析与简单实践指南​

3.我在B站学云原生之Kubernetes入门实践CentOS系统上安装部署K8S单控制平面环境​

4.我在B站学云原生之Kubernetes入门实践CentOS系统上手动安装部署K8S集群环境​

网页链接​5.我在B站学云原生之Kubernetes入门实践Ubuntu系统上安装部署K8S单控制平面环境​

6.我在B站学云原生之Kubernetes入门实践Ubuntu系统上安装部署高可用的K8S集群环境​


欢迎各位志同道合的朋友一起学习交流,如文章有误请在下方留下您宝贵的经验知识,个人邮箱地址【master#weiyigeek.top】或者 个人公众号【WeiyiGeek】联系我。


更多文章来源于【WeiyiGeek Blog - 为了能到远方,脚下的每一步都不能少】 个人博客。

个人主页: 【 https://weiyigeek.top 】

博客地址: 【 https://blog.weiyigeek.top 】

https://weiyigeek.top - Always keep a beginner's mind, don&#​39;t forget the beginner's mind

专栏书写不易,如果您觉得这个专栏还不错的,请给这篇专栏【点个赞、投个币、收个藏、关个注,转个发、留个言】,这将对我的肯定,谢谢!。

谢谢,各位帅哥、美女四连支持!!这就是我的动力!

 更多网络安全、系统运维、应用开发、全栈文章,尽在【个人博客 - https://blog.weiyigeek.top】站点,谢谢支持!