精华内容
下载资源
问答
  • Containerd
    千次阅读
    2022-04-02 12:31:24

    containerd自身并不具备为容器提供网络的能力。需要结合CNI为容器配置网络。

    CNI简介

    CNI (Container Network Interface)是CNCF的一个开源项目,其包含一些用于配置linux容器网络接口的规范、库,以及一些支持插件。CNI只关心容器创建时的网络分配,以及当容器被删除时已经分配网络资源的释放。 CNI作为容器网络的标准,使得各个容器管理平台可以通过相同的接口调用各种各样的网络插件来为容器配置网络。
    我们在安装cri-containerd-cni的时候,已经安装好了cni插件以及相关工具,可以在/opt/cni/bin目录下查看相关可执行文件。这些执行文件从功能角度可以分为如下三类:

    • 主插件: 用于创建网络设备
      • bridge: 创建一个网桥设备,并添加宿主机和容器到该网桥
      • ipvlan: 为容器添加ipvlan网络接口
      • loopback: 设置lo网络接口的状态为up
      • macvlan: 创建一个新的MAC地址,并将所有流量转发到容器
      • ptp: 创建Veth对
      • vlan: 分配一个vlan设备
      • host-device: 将已存在的设备移入容器内
    • IPAM插件: 用于IP地址的分配
      • dhcp: 在宿主机上运行dhcp守护程序,代表容器发出dhcp请求
      • host-local: 维护一个分配ip的本地数据库
      • static: 为容器分配一个静态IPv4/IPv6地址,主要用于调试
    • Meta插件: 其他插件,非单独使用插件
      • flannel: flannel网络方案的CNI插件,根据flannel的配置文件创建网络接口
      • tuning: 调整现有网络接口的sysctl参数
      • portmap: 一个基于iptables的portmapping插件。将端口从主机的地址空间映射到容器
      • bandwidth: 允许使用TBF进行限流的插件
      • sbr: 一个为网络接口配置基于源路由的插件
      • firewall: 过iptables给容器网络的进出流量进行一系列限制的插件

    containerd网络配置

    默认情况下,当安装好cni插件和工具后,使用nerdctl启动了一个容器之后,宿主机上会出现一个nerdctl0网桥:

    [root@ecs-350c-0001 ~]# ip addr show nerdctl0 
    3: nerdctl0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
        link/ether 96:97:3c:08:11:cf brd ff:ff:ff:ff:ff:ff
        inet 10.4.0.1/24 brd 10.4.0.255 scope global nerdctl0
           valid_lft forever preferred_lft forever
        inet6 fe80::9497:3cff:fe08:11cf/64 scope link 
           valid_lft forever preferred_lft forever
    

    此时创建出来的容器将获得与网桥同网段的ip,并将网关指向网桥,连接示意图如下:

    除了默认的bridge网桥模式,containerd还支持host、none以及自定义网络,可以通过如下方式查看containerd支持的容器网络:

    [root@ecs-350c-0001 ~]# nerdctl  network ls 
    NETWORK ID    NAME              FILE
    0             bridge            
                  containerd-net    /etc/cni/net.d/10-containerd-net.conflist
                  host              
                  none
    

    bridge网络

    bridge: 这是默认的网络模式

    • 在bridge网络模式中,只要宿主机可以上网,则所有容器都可以上网
    • 如果外部网络想要访问容器,需要为容器配置端口映射
      bridge是containerd的默认网络模式,可以通过如下指令查看bridge的详细配置信息:
    [root@ecs-350c-0001 ~]# nerdctl network inspect bridge
    [
        {
            "Name": "bridge",
            "Id": "0",
            "IPAM": {
                "Config": [
                    {
                        "Subnet": "10.4.0.0/24",
                        "Gateway": "10.4.0.1"
                    }
                ]
            },
            "Labels": {}
        }
    ]
    

    创建使用bridge网络容器的示例:

    nerdctl run -d --name web1 --net bridge nginx
    

    基于bridge网络的容器访问外部网络

    默认情况下,基于bridge网络容器即可访问外部网络,这是因为默认情况下,bridge使用了iptables的snat转发来实现容器对外部的访问。

    外部网络访问基于bridge网络的容器

    如果想让外界可以访问到基于bridge网络创建的容器提供的服务,需要为容器配置端口映射:

    nerdctl run -d -p 80:80 --name web nginx:1.21
    

    这种端口映射基于iptables的dnat实现

    查看容器的端口情况:

    nerdctl port web
    

    none网络

    故名思议,none网络就是什么都没有的网络。使用none网络的容器除了lo,没有其他任何网卡,完全隔离。用于既不需要访问外部服务,也不允许外部服务访问自己的应用场景。

    • 查看none网络信息:
    nerdctl  network inspect none
    

    创建使用none网络容器的示例:

    nerdctl run -d --name web_none --net none nginx
    

    none网络应用场景:病毒分析,病毒分析是不需要网络

    host网络

    host: 在host模式下,不为容器分配IP,而是直接使用宿主机ip

    • host模式将会使容器获得最佳网络性
    • 同一个镜像启动的多个容器,只有第一个能正常启动
    • 使用host网络的主机,与宿主机共享网络地址,可以获得最好的数据转发性能。
    • 缺点是,同一个宿主机上的多个容器共享同一个ip地址,如果多容器使用相同的端口,需要自行解决端口冲突问题。

    同样的,可以通过如下方式查看host网络信息:

    nerdctl network inspect host
    

    创建一个使用host网络容器的示例:

    # 可以看到该容器没有自己的IP地址,因为它直接使用宿主机IP地址
    nerdctl run -d --name web_host -net host nginx
    

    host网络应用场景:做负载均衡时可将负载均衡容器设置为host网络

    自定义网络

    我们可以通过cni的插件和工具轻松的为containerd自定义网络。

    自定义bridge网络

    我们在执行nerdctl network ls时,可以看到有一个自定义的网络名为containerd-net,其配置文件路径为/etc/cni/net.d/10-containerd-mynet.conflist内容如下:

    {
      "cniVersion": "0.4.0",
      "name": "containerd-net",
      "plugins": [
        {
          "type": "bridge",
          "bridge": "cni0",
          "isGateway": true,
          "ipMasq": true,
          "promiscMode": true,
          "ipam": {
            "type": "host-local",
            "ranges": [
              [{
                "subnet": "10.88.0.0/16"
              }],
              [{
                "subnet": "2001:4860:4860::/64"
              }]
            ],
            "routes": [
              { "dst": "0.0.0.0/0" },
              { "dst": "::/0" }
            ]
          }
        },
        {
          "type": "portmap",
          "capabilities": {"portMappings": true}
        }
      ]
    }
    

    这是一个自定义的bridge网络, 我们可以通过如下指令创建一个容器使用该网络:

    nerdctl run -d --name web2 --net containerd-net nginx
    

    自定义flannel网络

    从上面的例子中,我们知道,对于containerd的自定义网络。只要安装对应的网络插件,然后定义cni配置文件即可。而对于像flannel这种可以配置跨主机网络互联的网络插件,cni只为我们提供了配置网络的工具,而并没有安装flannel,需要我们自行安装。

    部署etcd

    flannel网络依赖etcd, 所以需要先安装etcd:

    nerdctl run -d --net host --name etcd -v /var/lib/etcd:/var/lib/etcd registry.cn-zhangjiakou.aliyuncs.com/breezey/etcd:v3.5.0 \
    etcd --name etcd \
    --data-dir /var/lib/etcd \
    --listen-client-urls http://192.168.0.180:2379,http://127.0.0.1:2379 \
    --advertise-client-urls http://192.168.0.180:2379,http://127.0.0.1:2379 \
    --listen-peer-urls http://localhost:2380,http://192.168.0.180:2380 \
    --enable-v2  # flannel目前仍然只支持etcd v2,所以需要将v2 api开启
    

    安装flannel

    往etcd当中写入flannel的网段:

    # 安装etcd客户端
    yum install -y etcd 
    # 使用etcd v2 api向etcd中添加flannel的网段
    export ETCDCTL_API=2;etcdctl --endpoints http://127.0.0.1:2379 set /coreos.com/network/config \
    '{"Network": "10.0.0.0/16", "SubnetLen": 24, "SubnetMin": "10.0.1.0","SubnetMax": "10.0.20.0", "Backend": {"Type": "vxlan"}}'
    

    安装flannel:

    nerdctl run -d --privileged -v /run/flannel:/run/flannel --net host --name flannel registry.cn-zhangjiakou.aliyuncs.com/breezey/flannel:v0.14.0 \
    -etcd-prefix /coreos.com/network -iface eth0 -etcd-endpoints http://192.168.0.180:2379 -ip-masq
    

    为flannel配置cni:

    # vim /etc/cni/net.d/10-flannel.conflist
    {
      "name": "flannel",
      "cniVersion": "0.3.1",
      "plugins": [
        {
          "type": "flannel",
          "delegate": {
            "isDefaultGateway": true
          }
        },
        {
          "type": "portmap",
          "capabilities": {
            "portMappings": true
          }
        }
      ]
    }
    

    关于flannel的cni配置,可参考: flannel-cni/flannel.conflist.default at master · coreos/flannel-cni (github.com)

    此时查看containerd网络:

    [root@ecs-350c-0001 net.d]# nerdctl network ls 
    NETWORK ID    NAME              FILE
    0             bridge            
                  containerd-net    /etc/cni/net.d/10-containerd-net.conflist
                  flannel           /etc/cni/net.d/10-flannel.conflist
                  host              
                  none
    

    由于containerd-net网络在创建时会使用cni0,而flannel网络在创建时也会使用cni0,会出现冲突,所以这里先把containerd-net网络干掉:

    # 清掉所有容器
    nerdctl ps -aq |xargs nerdctl rm 
    # 停掉cni0接口
    ifconfig cni0 down 
    # 删除cni0接口
    yum install -y bridge-utils
    brctl delbr cni0
    # 移除containerd-net配置文件
    mv /etc/cni/net.d/10-containerd-net.conflist /tmp/
    

    此时创建一个使用flannel网络的容器即可:

    [root@ecs-350c-0001 net.d]# nerdctl run -d --net flannel --name flannel busybox:1.28  sleep 3600 
    07a5035f7836b5331c849912df71861fd22ad8c3bf257be81a8c9f36132d5034
    

    再次查看cni0的宿主机网络:

    # ip a 
    ...
    39: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
        link/ether 76:16:d1:84:98:60 brd ff:ff:ff:ff:ff:ff
        inet 10.0.7.0/32 brd 10.0.7.0 scope global flannel.1
           valid_lft forever preferred_lft forever
        inet6 fe80::7416:d1ff:fe84:9860/64 scope link 
           valid_lft forever preferred_lft forever
    43: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
        link/ether 66:d4:88:0c:eb:7d brd ff:ff:ff:ff:ff:ff
        inet 10.0.7.1/24 brd 10.0.7.255 scope global cni0
           valid_lft forever preferred_lft forever
        inet6 fe80::64d4:88ff:fe0c:eb7d/64 scope link 
           valid_lft forever preferred_lft forever
    ...
    

    查看busybox的网络:

    [root@ecs-350c-0001 net.d]# nerdctl exec -it busybox ip a 
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
        inet6 ::1/128 scope host 
           valid_lft forever preferred_lft forever
    3: eth0@if44: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue 
        link/ether ea:2e:f7:96:b8:19 brd ff:ff:ff:ff:ff:ff
        inet 10.0.7.3/24 brd 10.0.7.255 scope global eth0
           valid_lft forever preferred_lft forever
        inet6 fe80::e82e:f7ff:fe96:b819/64 scope link 
           valid_lft forever preferred_lft forever
    

    至此,flannel网络配置完成。将其他宿主机的网络也接入该flannel网络,则可实现跨宿主机的容器网络互联。

    更多相关内容
  • containerd

    2022-05-25 17:01:04
    文章目录 容器运行时 Docker CRI Containerd 架构 安装 配置 使用(contianerd的CLI:ctr) 镜像操作 拉取镜像 列出本地镜像 检测本地镜像 重新打标签 删除镜像 将镜像挂载到主机目录 将镜像从主机目录上卸载 将镜像...


    涉及到容器引擎,有必要了解下容器运行时

    容器运行时

    在学习 Containerd 之前有必要对 Docker 的发展历史做一个简单的回顾,因为这里面牵涉到的组件实战是有点多,比如 libcontainer、runc、containerd、CRI、OCI 等等。

    Docker

    从 Docker 1.11 版本开始,Docker 容器运行就不是简单通过 Docker Daemon 来启动了(C/S架构),而是通过集成 containerd、runc 等多个组件来完成的。虽然 Docker Daemon 守护进程模块在不停的重构,但是基本功能和定位没有太大的变化,一直都是 CS 架构,守护进程负责和 Docker Client 端交互,并管理 Docker 镜像和容器。现在的架构中组件 containerd 就会负责集群节点上容器的生命周期管理,并向上为 Docker Daemon 提供 gRPC 接口。
    在这里插入图片描述
    当我们要创建一个容器的时候,现在 Docker Daemon 并不能直接帮我们创建了,而是请求 containerd 来创建一个容器(containerd管理容器的生命周期),containerd 收到请求后,也并不会直接去操作容器,而是创建一个叫做 containerd-shim 的进程,让这个进程去操作容器,我们指定容器进程是需要一个父进程来做状态收集、维持 stdin 等 fd 打开等工作的,假如这个父进程就是 containerd,那如果 containerd 挂掉的话,整个宿主机上所有的容器都得退出了,而引入 containerd-shim 这个垫片就可以来规避这个问题了。(container-shim作为容器进程父进程,由这个容器去操作runc创建容器进程,并对容器状态收集等)

    然后创建容器需要做一些 namespaces 和 cgroups 的配置,以及挂载 root 文件系统等操作,这些操作其实已经有了标准的规范,那就是 OCI(开放容器标准),runc 就是它的一个参考实现,这个标准其实就是一个文档,主要规定了容器镜像的结构、以及容器需要接收哪些操作指令,比如 create、start、stop、delete 等这些命令。runc 就可以按照这个 OCI 文档来创建一个符合规范的容器,既然是标准肯定就有其他 OCI 实现,比如 Kata、gVisor 这些容器运行时都是符合 OCI 标准的。

    所以真正启动容器是通过 containerd-shim 去调用 runc 来启动容器的,runc 启动完容器后本身会直接退出,containerd-shim 则会成为容器进程的父进程, 负责收集容器进程的状态, 上报给 containerd, 并在容器中 pid 为 1 的进程退出后接管容器中的子进程进行清理, 确保不会出现僵尸进程。

    而 Docker 将容器操作都迁移到 containerd 中去是因为当前做 Swarm,想要进军 PaaS 市场,做了这个架构切分,让Docker Daemon 专门去负责上层的封装编排,当然后面的结果我们知道 Swarm 在 Kubernetes 面前是惨败,然后Docker 公司就把 containerd 项目捐献给了 CNCF 基金会,这个也是现在的 Docker 架构。

    CRI

    我们知道 Kubernetes 提供了一个 CRI 的容器运行时接口,那么这个 CRI 到底是什么呢?这个其实也和 Docker 的发展密切相关的。

    在 Kubernetes 早期的时候,当时 Docker 实在是太火了,Kubernetes 当然会先选择支持Docker,而且是通过硬编码的方式直接调用 Docker API,后面随着 Docker 的不断发展以及 Google的主导,出现了更多容器运行时,Kubernetes 为了支持更多更精简的容器运行时,Google 就和红帽主导推出了 CRI 标准,用于将Kubernetes 平台和特定的容器运行时(当然主要是为了干掉 Docker)解耦。

    CRI(Container Runtime Interface 容器运行时接口)本质上就是 Kubernetes 定义的一组与容器运行时进行交互的接口,所以只要实现了这套接口的容器运行时都可以对接到 Kubernetes 平台上来。不过 Kubernetes 推出 CRI 这套标准的时候还没有现在的统治地位,所以有一些容器运行时可能不会自身就去实现 CRI 接口,于是就有了 shim(垫片), 一个 shim 的职责就是作为适配器将各种容器运行时本身的接口适配到 Kubernetes 的 CRI 接口上,其中 dockershim 就是 Kubernetes 对接 Docker 到 CRI 接口上的一个垫片实现。

    (kubernetes开启了CRI接口,可以将容器运行时比如dockerd接入到kubernetes中,但dockerd本身的接口不是CRI,CRI是google等基于kubernetes考虑的退出的标准,于是此时有shim比如dockershim来将kubelet的CRI接口和dockerd这个容器运行时本身接口进行对接)
    在这里插入图片描述
    Kubelet 通过 gRPC 框架与容器运行时或 shim 进行通信,其中 kubelet 作为客户端,CRI shim(也可能是容器运行时本身)作为服务器。(通过grpc框架进行通信)

    CRI 定义的 API 主要包括两个 gRPC 服务,ImageServiceRuntimeService:
    1.ImageService 服务主要是拉取镜像、查看和删除镜像等操作
    2.RuntimeService 则是用来管理 Pod 和容器的生命周期,以及与容器交互的调用(exec/attach/port-forward)等操作
    可以通过 kubelet 中的标志 --container-runtime-endpoint 和 --image-service-endpoint 来配置这两个服务的套接字。

    不过这里同样也有一个例外,那就是 Docker,由于 Docker 当时的江湖地位很高,Kubernetes 是直接内置了 dockershim 在 kubelet 中的(旧版本kubernetes),所以如果你使用的是 Docker 这种容器运行时的话是不需要单独去安装配置适配器(shim)之类的
    在这里插入图片描述
    现在如果我们使用的是 Docker 的话,当我们在 Kubernetes 中创建一个 Pod 的时候,首先就是 kubelet 通过 CRI 接口调用 dockershim,请求创建一个容器,kubelet 可以视作一个简单的 CRI Client, 而 dockershim 就是接收请求的 Server,不过他们都是在 kubelet 内置的。
    dockershim 收到请求后, 转化成 Docker Daemon 能识别的请求, 发到 Docker Daemon 上请求创建一个容器,请求到了 Docker Daemon 后续就是 Docker 创建容器的流程了,去调用 containerd,然后创建 containerd-shim 进程,通过该进程去调用 runc 去真正创建容器。

    (kubectl或其他RESTAPI发送请求到apiserver创建pod,写入etcd,调度到适合的节点,然后到绑定过程,就需要kubelet,通过CRI接口和使用grpc框架与dockershim通信,其中有两个grpc服务分别一个是负责镜像的一个负责pod和容器的生命周期,调用dockershim,进一步到dockerd这个容器运行时,在到containerd,实际创建container-shim父进程,调用runc进程按照OCI标准创建容器进程,然后runc进程关闭,container-shim成为容器进程附进程,负责容器进程后续的一切)

    其实我们仔细观察也不难发现使用 Docker 的话其实是调用链比较长的,真正容器相关的操作其实 containerd就完全足够了,Docker 太过于复杂笨重了,当然 Docker 深受欢迎的很大一个原因就是提供了很多对用户操作比较友好的功能,但是对于Kubernetes 来说压根不需要这些功能,因为都是通过接口去操作容器的,所以自然也就可以将容器运行时切换到 containerd 来。

    在这里插入图片描述
    切换到 containerd 可以消除掉中间环节,操作体验也和以前一样,但是由于直接用容器运行时调度容器,所以它们对 Docker 来说是不可见的。 因此,你以前用来检查这些容器的 Docker 工具就不能使用了
    你不能再使用 docker ps 或 docker inspect 命令来获取容器信息。由于不能列出容器,因此也不能获取日志、停止容器,甚至不能通过 docker exec 在容器中执行命令。
    当然我们仍然可以下载镜像,或者用 docker build 命令构建镜像,但用 Docker 构建、下载的镜像,对于容器运行时和 Kubernetes,均不可见。为了在 Kubernetes 中使用,需要把镜像推送到镜像仓库中去。

    从上图可以看出在 containerd 1.0 中,对 CRI 的适配是通过一个单独的 CRI-Containerd 进程来完成的,这是因为最开始 containerd 还会去适配其他的系统(比如 swarm),所以没有直接实现 CRI,所以这个对接工作就交给 CRI-Containerd 这个 shim 了。

    然后到了 containerd 1.1 版本后就去掉了 CRI-Containerd 这个 shim,直接把适配逻辑作为插件的方式集成到了 containerd 主进程中,现在这样的调用就更加简洁了。
    在这里插入图片描述
    kubelet的CRI接口和containerd的CRI plugin实现的CRI接口进行对接,同样还有那两个grpc服务

    与此同时 Kubernetes 社区也做了一个专门用于 Kubernetes 的 CRI 运行时 CRI-O,直接兼容 CRI 和 OCI 规范。
    在这里插入图片描述

    这个方案和 containerd 的方案显然比默认的 dockershim 简洁很多,不过由于大部分用户都比较习惯使用
    Docker,所以大家还是更喜欢使用 dockershim 方案。

    但是随着 CRI 方案的发展,以及其他容器运行时对 CRI 的支持越来越完善,Kubernetes 社区在2020年7月份就开始着手移除dockershim
    方案了:https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2221-remove-dockershim,现在的移除计划是在1.20 版本中将 kubelet 中内置的 dockershim 代码分离,将内置的 dockershim 标记为维护模式,当然这个时候仍然还可以使用 dockershim,目标是在 1.23/1.24 版本发布没有 dockershim的版本(代码还在,但是要默认支持开箱即用的 docker 需要自己构建 kubelet,会在某个宽限期过后从 kubelet 中删除内置的dockershim 代码)。

    那么这是否就意味这 Kubernetes 不再支持 Docker 了呢?当然不是的,这只是废弃了内置的 dockershim 功能而已,Docker 和其他容器运行时将一视同仁,不会单独对待内置支持,如果我们还想直接使用 Docker 这种容器运行时应该怎么办呢?可以将 dockershim 的功能单独提取出来独立维护一个 cri-dockerd 即可,就类似于 containerd 1.0 版本中提供的 CRI-Containerd,当然还有一种办法就是 Docker 官方社区将 CRI 接口内置到 Dockerd 中去实现。

    但是我们也清楚 Dockerd 也是去直接调用的 Containerd,而 containerd 1.1 版本后就内置实现了 CRI,所以 Docker 也没必要再去单独实现 CRI 了,当 Kubernetes 不再内置支持开箱即用的 Docker 的以后,最好的方式当然也就是直接使用 Containerd 这种容器运行时,而且该容器运行时也已经经过了生产环境实践的,接下来我们就来学习下 Containerd 的使用。

    Containerd

    我们知道很早之前的 Docker Engine 中就有了 containerd,只不过现在是将 containerd 从 Docker Engine 里分离出来,作为一个独立的开源项目,目标是提供一个更加开放、稳定的容器运行基础设施。分离出来的 containerd 将具有更多的功能,涵盖整个容器运行时管理的所有需求,提供更强大的支持。

    containerd 是一个工业级标准的容器运行时,它强调简单性、健壮性和可移植性,containerd 可以负责干下面这些事情:

    1.管理容器的生命周期(从创建容器到销毁容器)
    2.拉取/推送容器镜像
    3.存储管理(管理镜像及容器数据的存储)
    4.调用 runc 运行容器(与 runc 等容器运行时交互)
    5.管理容器网络接口及网络

    架构

    containerd 可用作 Linux 和 Windows 的守护程序,它管理其主机系统完整的容器生命周期,从镜像传输和存储到容器执行和监测,再到底层存储到网络附件等等。
    在这里插入图片描述
    上图是 containerd 官方提供的架构图,可以看出 containerd 采用的也是 C/S 架构,服务端通过 unix domain socket 暴露低层的 gRPC API 接口出去,客户端通过这些 API 管理节点上的容器,每个 containerd 只负责一台机器,Pull 镜像,对容器的操作(启动、停止等),网络,存储都是由 containerd 完成。具体运行容器由 runc 负责,实际上只要是符合 OCI 规范的容器都可以支持。

    为了解耦,containerd 将系统划分成了不同的组件,每个组件都由一个或多个模块协作完成(Core 部分),每一种类型的模块都以插件的形式集成到 Containerd 中,而且插件之间是相互依赖的,例如,上图中的每一个长虚线的方框都表示一种类型的插件,包括 Service Plugin、Metadata Plugin、GC Plugin、Runtime Plugin 等,其中 Service Plugin 又会依赖 Metadata Plugin、GC Plugin 和 Runtime Plugin。每一个小方框都表示一个细分的插件,例如 Metadata Plugin 依赖 Containers Plugin、Content Plugin 等。比如:

    Content Plugin: 提供对镜像中可寻址内容的访问,所有不可变的内容都被存储在这里。
    Snapshot Plugin: 用来管理容器镜像的文件系统快照,镜像中的每一层都会被解压成文件系统快照,类似于 Docker 中的 graphdriver。
    总体来看 containerd 可以分为三个大块:Storage、Metadata 和 Runtime。

    安装

    我这边采用的是centos8

    yum update
    yum install -y libeseccomp libseccomp-devel
    rpm -qa | grep libeseccomp
    注意版本得是2.4以上,有的系统用yum最新也是2.3,你可以考虑用源码安装的方式,centos8可以用2.5
    如果版本是2.3,低了,后面使用task会报错,runc会用到libseccomp
    
    比如centos7最新的就是2.3,没辙,你可以考虑源码安装高版本的libseccomp,也可以硬是安装centos8下的2.5版本,不过这样多少有点版本不对应的问题,恐怕有bug,不是个完美的方法(还是建议你升级到centos8或者源码安装):
    #卸载原来的
    [i4t@web01 ~]# rpm -qa | grep libseccomp
    libseccomp-devel-2.3.1-4.el7.x86_64
    libseccomp-2.3.1-4.el7.x86_64
    [i4t@web01 ~]# rpm -e libseccomp-devel-2.3.1-4.el7.x86_64 --nodeps
    [i4t@web01 ~]# rpm -e libseccomp-2.3.1-4.el7.x86_64 --nodeps
    #下载高于2.4以上的包
    [i4t@web01 ~]# wget http://rpmfind.net/linux/centos/8-stream/BaseOS/x86_64/os/Packages/libseccomp-2.5.1-1.el8.x86_64.rpm
    #安装
    [i4t@web01 ~]# rpm -ivh libseccomp-2.5.1-1.el8.x86_64.rpm 
    warning: libseccomp-2.5.1-1.el8.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID 8483c65d: NOKEY
    Preparing...                          ################################# [100%]
    Updating / installing...
       1:libseccomp-2.5.1-1.el8           ################################# [100%]
    #查看当前版本
    [root@web01 ~]# rpm -qa | grep libseccomp
    libseccomp-2.5.1-1.el8.x86_64
    
    完成
    [root@www ~]# runc
    NAME:
       runc - Open Container Initiative runtime
    
    runc is a command line client for running applications packaged according to
    the Open Container Initiative (OCI) format and is a compliant implementation of the
    Open Container Initiative specification.
    
    runc integrates well with existing process supervisors to provide a production
    container runtime environment for applications. It can be used with your
    existing process monitoring tools and the container will be spawned as a
    direct child of the process supervisor.
    
    Containers are configured using bundles. A bundle for a container is a directory
    that includes a specification file named "config.json" and a root filesystem.
    The root filesystem contains the contents of the container.
    
    To start a new instance of a container:
    
        # runc run [ -b bundle ] <container-id>
    
    Where "<container-id>" is your name for the instance of the container that you
    are starting. The name you provide for the container instance must be unique on
    your host. Providing the bundle directory using "-b" is optional. The default
    value for "bundle" is the current directory.
    
    USAGE:
       runc [global options] command [command options] [arguments...]
    
    VERSION:
       1.0.3
    commit: v1.0.3-0-gf46b6ba2
    spec: 1.0.2-dev
    go: go1.16.14
    libseccomp: 2.5.1
    
    COMMANDS:
       checkpoint  checkpoint a running container
       create      create a container
       delete      delete any resources held by the container often used with detached container
       events      display container events such as OOM notifications, cpu, memory, and IO usage statistics
       exec        execute new process inside the container
       init        initialize the namespaces and launch the process (do not call it outside of runc)
       kill        kill sends the specified signal (default: SIGTERM) to the container's init process
       list        lists containers started by runc with the given root
       pause       pause suspends all processes inside the container
       ps          ps displays the processes running inside a container
       restore     restore a container from a previous checkpoint
       resume      resumes all processes that have been previously paused
       run         create and run a container
       spec        create a new specification file
       start       executes the user defined process in a created container
       state       output the state of a container
       update      update container resource constraints
       help, h     Shows a list of commands or help for one command
    
    GLOBAL OPTIONS:
       --debug             enable debug output for logging
       --log value         set the log file path where internal debug information is written
       --log-format value  set the format used by logs ('text' (default), or 'json') (default: "text")
       --root value        root directory for storage of container state (this should be located in tmpfs) (default: "/run/runc")
       --criu value        path to the criu binary used for checkpoint and restore (default: "criu")
       --systemd-cgroup    enable systemd cgroup support, expects cgroupsPath to be of form "slice:prefix:name" for e.g. "system.slice:runc:434234"
       --rootless value    ignore cgroup permission errors ('true', 'false', or 'auto') (default: "auto")
       --help, -h          show help
       --version, -v       print the version
    

    在这里插入图片描述

    由于 containerd 需要调用 runc,所以我们也需要先安装 runc,不过 containerd 提供了一个包含相关依赖的压缩包 cri-containerd-cni- V E R S I O N . {VERSION}. VERSION.{OS}-${ARCH}.tar.gz,可以直接使用这个包来进行安装。首先从 release 页面下载对应版本的压缩包,当前为 1.5.10 版本:

    wget https://github.com/containerd/containerd/releases/download/v1.5.10/cri-containerd-cni-1.5.10-linux-amd64.tar.gz
    # 如果有限制,也可以替换成下面的 URL 加速下载
    # wget https://download.fastgit.org/containerd/containerd/releases/download/v1.5.10/cri-containerd-cni-1.5.10-linux-amd64.tar.gz
    

    download.fastgit.org
    在这里插入图片描述
    在这里插入图片描述

    解压前预览或者拿一个测试目录来解压是一个好习惯

    -t:列出压缩包中的文件内容
    -f:指定压缩包
    这里并不是真正解压
    [root@master1 ~]# tar -t -f cri-containerd-cni-1.5.10-linux-amd64.tar.gz
    etc/
    etc/cni/
    etc/cni/net.d/
    etc/cni/net.d/10-containerd-net.conflist
    etc/systemd/
    etc/systemd/system/
    etc/systemd/system/containerd.service
    etc/crictl.yaml
    usr/
    usr/local/
    usr/local/sbin/
    usr/local/sbin/runc
    usr/local/bin/
    usr/local/bin/containerd-shim-runc-v2
    usr/local/bin/containerd-shim
    usr/local/bin/containerd-shim-runc-v1
    usr/local/bin/containerd
    usr/local/bin/containerd-stress
    usr/local/bin/critest
    usr/local/bin/ctr
    usr/local/bin/ctd-decoder
    usr/local/bin/crictl
    opt/
    opt/cni/
    opt/cni/bin/
    opt/cni/bin/firewall
    opt/cni/bin/static
    opt/cni/bin/flannel
    opt/cni/bin/vrf
    opt/cni/bin/host-device
    opt/cni/bin/host-local
    opt/cni/bin/dhcp
    opt/cni/bin/tuning
    opt/cni/bin/ptp
    opt/cni/bin/sbr
    opt/cni/bin/ipvlan
    opt/cni/bin/bandwidth
    opt/cni/bin/macvlan
    opt/cni/bin/bridge
    opt/cni/bin/portmap
    opt/cni/bin/loopback
    opt/cni/bin/vlan
    opt/containerd/
    opt/containerd/cluster/
    opt/containerd/cluster/version
    opt/containerd/cluster/gce/
    opt/containerd/cluster/gce/configure.sh
    opt/containerd/cluster/gce/cni.template
    opt/containerd/cluster/gce/env
    opt/containerd/cluster/gce/cloud-init/
    opt/containerd/cluster/gce/cloud-init/node.yaml
    opt/containerd/cluster/gce/cloud-init/master.yaml
    

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

    -C:改变解压的目标目录
    [root@master1 ~]# tar -C / -xf cri-containerd-cni-1.5.10-linux-amd64.tar.gz
    

    当然要记得将 /usr/local/bin 和 /usr/local/sbin 追加到 ~/.bashrc 文件的 PATH 环境变量中(或者/etc/profile):

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

    然后执行下面的命令使其立即生效:

    [root@master1 ~]# source ~/.bashrc
    

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

    [root@master1 ~]# mkdir -p /etc/containerd
    [root@master1 ~]# containerd config default > /etc/containerd/config.toml
    

    由于上面我们下载的 containerd 压缩包中包含一个 etc/systemd/system/containerd.service 的文件,这样我们就可以通过 systemd 来配置 containerd 作为守护进程运行了,内容如下所示:

    ➜  ~ cat /etc/systemd/system/containerd.service
    [Unit]
    Description=containerd container runtime
    Documentation=https://containerd.io
    After=network.target local-fs.target
    
    [Service]
    ExecStartPre=-/sbin/modprobe overlay
    ExecStart=/usr/local/bin/containerd
    
    Type=notify
    Delegate=yes
    KillMode=process
    Restart=always
    RestartSec=5
    # Having non-zero Limit*s causes performance problems due to accounting overhead
    # in the kernel. We recommend using cgroups to do container-local accounting.
    LimitNPROC=infinity
    LimitCORE=infinity
    LimitNOFILE=1048576
    # Comment TasksMax if your systemd version does not supports it.
    # Only systemd 226 and above support this version.
    TasksMax=infinity
    OOMScoreAdjust=-999
    
    [Install]
    WantedBy=multi-user.target
    

    这里有两个重要的参数:

    Delegate: (授权,代表)这个选项允许 containerd 以及运行时自己管理自己创建容器的 cgroups。如果不设置这个选项,systemd 就会将进程移到自己的 cgroups 中,从而导致 containerd 无法正确获取容器的资源使用情况。
    KillMode: 这个选项用来处理 containerd 进程被杀死的方式。默认情况下,systemd 会在进程的 cgroup 中查找并杀死 containerd 的所有子进程。KillMode 字段可以设置的值如下。
    1.control-group(默认值):当前控制组里面的所有子进程,都会被杀掉
    2.process:只杀主进程
    3.mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号
    4.none:没有进程会被杀掉,只是执行服务的 stop 命令

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

    现在我们就可以启动 containerd 了,直接执行下面的命令即可:

    [root@master1 ~]# systemctl enable containerd --now
    Created symlink from /etc/systemd/system/multi-user.target.wants/containerd.service to /etc/systemd/system/containerd.service.
    

    启动完成后就可以使用 containerd 的本地 CLI 工具 ctr 了,比如查看版本:

    [root@master1 ~]# ctr version
    Client:
      Version:  v1.5.10
      Revision: 2a1d4dbdb2a1030dc5b01e96fb110a9d9f150ecc
      Go version: go1.16.14
    
    Server:
      Version:  v1.5.10
      Revision: 2a1d4dbdb2a1030dc5b01e96fb110a9d9f150ecc
      UUID: f5ec6760-fdf4-4ab4-b85a-50ad011693c9
    

    配置

    查看下上面默认生成的配置文件 /etc/containerd/config.toml:

    disabled_plugins = []
    imports = []
    oom_score = 0
    plugin_dir = ""
    required_plugins = []
    root = "/var/lib/containerd"
    state = "/run/containerd"
    version = 2
    
    [cgroup]
      path = ""
    
    [debug]
      address = ""
      format = ""
      gid = 0
      level = ""
      uid = 0
    
    [grpc]
      address = "/run/containerd/containerd.sock"
      gid = 0
      max_recv_message_size = 16777216
      max_send_message_size = 16777216
      tcp_address = ""
      tcp_tls_cert = ""
      tcp_tls_key = ""
      uid = 0
    
    [metrics]
      address = ""
      grpc_histogram = false
    
    [plugins]
    
      [plugins."io.containerd.gc.v1.scheduler"]
        deletion_threshold = 0
        mutation_threshold = 100
        pause_threshold = 0.02
        schedule_delay = "0s"
        startup_delay = "100ms"
    
      [plugins."io.containerd.grpc.v1.cri"]
        disable_apparmor = false
        disable_cgroup = false
        disable_hugetlb_controller = true
        disable_proc_mount = false
        disable_tcp_service = true
        enable_selinux = false
        enable_tls_streaming = false
        ignore_image_defined_volumes = false
        max_concurrent_downloads = 3
        max_container_log_line_size = 16384
        netns_mounts_under_state_dir = false
        restrict_oom_score_adj = false
        sandbox_image = "k8s.gcr.io/pause:3.5"
        selinux_category_range = 1024
        stats_collect_period = 10
        stream_idle_timeout = "4h0m0s"
        stream_server_address = "127.0.0.1"
        stream_server_port = "0"
        systemd_cgroup = false
        tolerate_missing_hugetlb_controller = true
        unset_seccomp_profile = ""
    
        [plugins."io.containerd.grpc.v1.cri".cni]
          bin_dir = "/opt/cni/bin"
          conf_dir = "/etc/cni/net.d"
          conf_template = ""
          max_conf_num = 1
    
        [plugins."io.containerd.grpc.v1.cri".containerd]
          default_runtime_name = "runc"
          disable_snapshot_annotations = true
          discard_unpacked_layers = false
          no_pivot = false
          snapshotter = "overlayfs"
    
          [plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]
            base_runtime_spec = ""
            container_annotations = []
            pod_annotations = []
            privileged_without_host_devices = false
            runtime_engine = ""
            runtime_root = ""
            runtime_type = ""
    
            [plugins."io.containerd.grpc.v1.cri".containerd.default_runtime.options]
    
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
    
            [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
              base_runtime_spec = ""
              container_annotations = []
              pod_annotations = []
              privileged_without_host_devices = false
              runtime_engine = ""
              runtime_root = ""
              runtime_type = "io.containerd.runc.v2"
    
              [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
                BinaryName = ""
                CriuImagePath = ""
                CriuPath = ""
                CriuWorkPath = ""
                IoGid = 0
                IoUid = 0
                NoNewKeyring = false
                NoPivotRoot = false
                Root = ""
                ShimCgroup = ""
                SystemdCgroup = false
    
          [plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime]
            base_runtime_spec = ""
            container_annotations = []
            pod_annotations = []
            privileged_without_host_devices = false
            runtime_engine = ""
            runtime_root = ""
            runtime_type = ""
    
            [plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime.options]
    
        [plugins."io.containerd.grpc.v1.cri".image_decryption]
          key_model = "node"
    
        [plugins."io.containerd.grpc.v1.cri".registry]
          config_path = ""
    
          [plugins."io.containerd.grpc.v1.cri".registry.auths]
    
          [plugins."io.containerd.grpc.v1.cri".registry.configs]
    
          [plugins."io.containerd.grpc.v1.cri".registry.headers]
    
          [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
    
        [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"]
        no_shim = false
        runtime = "runc"
        runtime_root = ""
        shim = "containerd-shim"
        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.aufs"]
        root_path = ""
    
      [plugins."io.containerd.snapshotter.v1.btrfs"]
        root_path = ""
    
      [plugins."io.containerd.snapshotter.v1.devmapper"]
        async_remove = false
        base_image_size = ""
        pool_name = ""
        root_path = ""
    
      [plugins."io.containerd.snapshotter.v1.native"]
        root_path = ""
    
      [plugins."io.containerd.snapshotter.v1.overlayfs"]
        root_path = ""
    
      [plugins."io.containerd.snapshotter.v1.zfs"]
        root_path = ""
    
    [proxy_plugins]
    
    [stream_processors]
    
      [stream_processors."io.containerd.ocicrypt.decoder.v1.tar"]
        accepts = ["application/vnd.oci.image.layer.v1.tar+encrypted"]
        args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"]
        env = ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"]
        path = "ctd-decoder"
        returns = "application/vnd.oci.image.layer.v1.tar"
    
      [stream_processors."io.containerd.ocicrypt.decoder.v1.tar.gzip"]
        accepts = ["application/vnd.oci.image.layer.v1.tar+gzip+encrypted"]
        args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"]
        env = ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"]
        path = "ctd-decoder"
        returns = "application/vnd.oci.image.layer.v1.tar+gzip"
    
    [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"
    
    [ttrpc]
      address = ""
      gid = 0
      uid = 0
    

    这个配置文件比较复杂,我们可以将重点放在其中的 plugins 配置上面,仔细观察我们可以发现每一个顶级配置块的命名都是 plugins.“io.containerd.xxx.vx.xxx” 这种形式,每一个顶级配置块都表示一个插件,其中 io.containerd.xxx.vx 表示插件的类型,vx 后面的 xxx 表示插件的 ID,我们可以通过 ctr 查看插件列表:

    [root@master1 ~]# ctr plugin ls
    TYPE                            ID                       PLATFORMS      STATUS
    io.containerd.content.v1        content                  -              ok
    io.containerd.snapshotter.v1    aufs                     linux/amd64    skip
    io.containerd.snapshotter.v1    btrfs                    linux/amd64    skip
    io.containerd.snapshotter.v1    devmapper                linux/amd64    error
    io.containerd.snapshotter.v1    native                   linux/amd64    ok
    io.containerd.snapshotter.v1    overlayfs                linux/amd64    ok
    io.containerd.snapshotter.v1    zfs                      linux/amd64    skip
    io.containerd.metadata.v1       bolt                     -              ok
    io.containerd.differ.v1         walking                  linux/amd64    ok
    io.containerd.gc.v1             scheduler                -              ok
    io.containerd.service.v1        introspection-service    -              ok
    io.containerd.service.v1        containers-service       -              ok
    io.containerd.service.v1        content-service          -              ok
    io.containerd.service.v1        diff-service             -              ok
    io.containerd.service.v1        images-service           -              ok
    io.containerd.service.v1        leases-service           -              ok
    io.containerd.service.v1        namespaces-service       -              ok
    io.containerd.service.v1        snapshots-service        -              ok
    io.containerd.runtime.v1        linux                    linux/amd64    ok
    io.containerd.runtime.v2        task                     linux/amd64    ok
    io.containerd.monitor.v1        cgroups                  linux/amd64    ok
    io.containerd.service.v1        tasks-service            -              ok
    io.containerd.internal.v1       restart                  -              ok
    io.containerd.grpc.v1           containers               -              ok
    io.containerd.grpc.v1           content                  -              ok
    io.containerd.grpc.v1           diff                     -              ok
    io.containerd.grpc.v1           events                   -              ok
    io.containerd.grpc.v1           healthcheck              -              ok
    io.containerd.grpc.v1           images                   -              ok
    io.containerd.grpc.v1           leases                   -              ok
    io.containerd.grpc.v1           namespaces               -              ok
    io.containerd.internal.v1       opt                      -              ok
    io.containerd.grpc.v1           snapshots                -              ok
    io.containerd.grpc.v1           tasks                    -              ok
    io.containerd.grpc.v1           version                  -              ok
    io.containerd.grpc.v1           cri                      linux/amd64    ok
    

    顶级配置块下面的子配置块表示该插件的各种配置,比如 cri 插件下面就分为 containerd、cni 和 registry 的配置,而 containerd 下面又可以配置各种 runtime,还可以配置默认的 runtime。比如现在我们要为镜像配置一个加速器,那么就需要在 cri 配置块下面的 registry 配置块下面进行配置 registry.mirrors:

    [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://xxxxxxxxxx.mirror.aliyuncs.com"]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]
          endpoint = ["https://registry.aliyuncs.com/k8sxio"]
    

    https://registry.aliyuncs.com/k8sxio

    registry.mirrors.“xxx”: 表示需要配置 mirror 的镜像仓库,例如 registry.mirrors.“docker.io” 表示配置 docker.io 的 mirror。
    endpoint: 表示提供 mirror 的镜像加速服务,比如我们可以注册一个阿里云的镜像服务来作为 docker.io 的 mirror。
    (就是设置了docker.io和k8s.gcr.io的网址,当使用到这两个仓库,会去这两个网址,这里的网址我们设置为阿里云的加速镜像,注意endpoint是个列表或者说数组,你可以写多个)

    systemctl restart containerd
    

    另外在默认配置中还有两个关于存储的配置路径:

    root = "/var/lib/containerd"
    state = "/run/containerd"
    

    其中 root 是用来保存持久化数据(比如docker的/var/lib/docker,不过这些存放数据的目录,建议那一块大的裸盘或者分区的目录来做,,或者使用ceph等存储集群来做存储,毕竟容器很容易产生大量数据,同一塞满这个/var/lib/containerd的目录所对应的文件系统甚至导致系统宕机),包括 Snapshots, Content, Metadata 以及各种插件的数据,每一个插件都有自己单独的目录,Containerd 本身不存储任何数据,它的所有功能都来自于已加载的插件。

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

    使用(contianerd的CLI:ctr)

    我们知道 Docker CLI 工具提供了需要增强用户体验的功能,containerd 同样也提供一个对应的 CLI 工具:ctr,不过 ctr 的功能没有 docker 完善,但是关于镜像和容器的基本功能都是有的。接下来我们就先简单介绍下 ctr 的使用。

    直接输入 ctr 命令即可获得所有相关的操作命令使用方式:

    [root@master1 ~]# ctr
    NAME:
       ctr -
            __
      _____/ /______
     / ___/ __/ ___/
    / /__/ /_/ /
    \___/\__/_/
    
    containerd CLI
    
    
    USAGE:
       ctr [global options] command [command options] [arguments...]
    
    VERSION:
       v1.5.10
    
    DESCRIPTION:
    
    ctr is an unsupported debug and administrative client for interacting
    with the containerd daemon. Because it is unsupported, the commands,
    options, and operations are not guaranteed to be backward compatible or
    stable from release to release of the containerd project.
    
    COMMANDS:
       plugins, plugin            provides information about containerd plugins
       version                    print the client and server versions
       containers, c, container   manage containers
       content                    manage content
       events, event              display containerd events
       images, image, i           manage images
       leases                     manage leases
       namespaces, namespace, ns  manage namespaces
       pprof                      provide golang pprof outputs for containerd
       run                        run a container
       snapshots, snapshot        manage snapshots
       tasks, t, task             manage tasks
       install                    install a new package
       oci                        OCI tools
       shim                       interact with a shim directly
       help, h                    Shows a list of commands or help for one command
    
    GLOBAL OPTIONS:
       --debug                      enable debug output in logs
       --address value, -a value    address for containerd's GRPC server (default: "/run/containerd/containerd.so                                                                                               ck") [$CONTAINERD_ADDRESS]
       --timeout value              total timeout for ctr commands (default: 0s)
       --connect-timeout value      timeout for connecting to containerd (default: 0s)
       --namespace value, -n value  namespace to use with commands (default: "default") [$CONTAINERD_NAMESPACE]
       --help, -h                   show help
       --version, -v                print the version
    

    镜像操作

    拉取镜像

    拉取镜像可以使用 ctr image pull 来完成,比如拉取 Docker Hub 官方镜像 nginx:alpine,需要注意的是镜像地址需要加上 docker.io Host 地址:

    (镜像是通用的,runc根据OCI来生成容器,镜像生成自然也有标准)

    (docker.io是仓库的地址或者说域名,library是docker hub等公开的代码仓库,其实docker使用时也有这两个写法,只是作为默认选项缺省了)

    (library除了看做仓库,其实看做一个在docker hub上注册的用户更准确,是一个公开用户,大多数镜像仓库网站都一样,创建用户会给你分配一个用户同名的仓库,nginx是软件名,其实就是library下创建的一个名叫nginx的仓库,library/nginx即用户名/软件名,才是dockerhub的一个完整的仓库名,后面的tag则用来标记处仓库中的某一个或类的镜像)

    [root@master1 ~]# ctr image pull docker.io/library/nginx:alpine
    docker.io/library/nginx:alpine:                                                   resolved       |++++                                  ++++++++++++++++++++++++++++++++++|
    index-sha256:a74534e76ee1121d418fa7394ca930eb67440deda413848bc67c68138535b989:    done           |++++                                  ++++++++++++++++++++++++++++++++++|
    manifest-sha256:529db430e042ecef071f2e88267cee6da18f8ab44d66a0c44348886fdc2e60fc: done           |++++                                  ++++++++++++++++++++++++++++++++++|
    layer-sha256:a285f0f83eed13cf71ccb560c31dd31b5eb7be0cadb4f43319d6de59aa4e3c70:    done           |++++                                  ++++++++++++++++++++++++++++++++++|
    layer-sha256:9da77f8e409edbb2c42db3d6a70f31754ac6e35c9ae981555b9f42ea42008a80:    done           |++++                                  ++++++++++++++++++++++++++++++++++|
    layer-sha256:06f5cb628050fa03f0928769c767bba57656e84312961cc39fbff63ae48c2f3e:    done           |++++                                  ++++++++++++++++++++++++++++++++++|
    config-sha256:b1c3acb28882519cf6d3a4d7fe2b21d0ae20bde9cfd2c08a7de057f8cfccff15:   done           |++++                                  ++++++++++++++++++++++++++++++++++|
    layer-sha256:df9b9388f04ad6279a7410b85cedfdcb2208c0a003da7ab5613af71079148139:    done           |++++                                  ++++++++++++++++++++++++++++++++++|
    layer-sha256:32261d4e220f3a41084ad35886169f9d753ffca4f8824ad934a43b1cddbad86c:    done           |++++                                  ++++++++++++++++++++++++++++++++++|
    layer-sha256:e00351ea626cd356c69e58d33181233b47a904d3b6ee508948d6cc221d7b9cfa:    done           |++++                                  ++++++++++++++++++++++++++++++++++|
    elapsed: 15.9s                                                                    total:  8.9 Mi (573.                                  5 KiB/s)
    unpacking linux/amd64 sha256:a74534e76ee1121d418fa7394ca930eb67440deda413848bc67c68138535b989...
    done: 2.107271737s
    

    也可以使用 --platform 选项指定对应平台的镜像。当然对应的也有推送镜像的命令 ctr image push,如果是私有镜像则在推送的时候可以通过 --user 来自定义仓库的用户名和密码。

    列出本地镜像

    [root@master1 ~]# ctr image ls
    REF                            TYPE                                                      DIGEST                                                                  SIZE    PLATFORMS                                                                                LABELS
    docker.io/library/nginx:alpine application/vnd.docker.distribution.manifest.list.v2+json sha256:a74534e76ee1121d418fa7394ca930eb67440deda413848bc67c68138535b989 9.7 MiB linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x -
    
    [root@master1 ~]# ctr image ls -q
    docker.io/library/nginx:alpine
    

    使用 -q(–quiet) 选项可以只打印镜像名称。

    检测本地镜像

    ➜  ~ ctr image check
    [root@master1 ~]# ctr image check
    REF                            TYPE                                                      DIGEST                                                                  STATUS         SIZE            UNPACKED
    docker.io/library/nginx:alpine application/vnd.docker.distribution.manifest.list.v2+json sha256:a74534e76ee1121d418fa7394ca930eb67440deda413848bc67c68138535b989 complete (7/7) 9.7 MiB/9.7 MiB true
    

    主要查看其中的 STATUS,complete 表示镜像是完整可用的状态。

    重新打标签

    同样的我们也可以重新给指定的镜像打一个 Tag:

    [root@master1 ~]# ctr image tag docker.io/library/nginx:alpine harbor.k8s.local/course/nginx:alpine
    harbor.k8s.local/course/nginx:alpine
    [root@master1 ~]# ctr image ls -q
    docker.io/library/nginx:alpine
    harbor.k8s.local/course/nginx:alpine
    

    删除镜像

    不需要使用的镜像也可以使用 ctr image rm 进行删除:

    [root@master1 ~]# ctr image rm harbor.k8s.local/course/nginx:alpine
    harbor.k8s.local/course/nginx:alpine
    [root@master1 ~]# ctr image ls -q
    docker.io/library/nginx:alpine
    

    加上 --sync 选项可以同步删除镜像和所有相关的资源(容器)。

    将镜像挂载到主机目录

    [root@master1 ~]#  ctr image mount docker.io/library/nginx:alpine /mnt
    sha256:1e82c6d6bb97ec37fdaf16a3578db9f79efc2a7fa987875e259148857265b410
    /mnt
    [root@master1 ~]# yum provides tree
    已加载插件:fastestmirror, langpacks
    Loading mirror speeds from cached hostfile
     * base: mirrors.ustc.edu.cn
     * extras: mirrors.ustc.edu.cn
     * updates: mirrors.ustc.edu.cn
    tree-1.6.0-10.el7.x86_64 : File system tree viewer
    源    :base
    [root@master1 ~]# yum install tree-1.6.0-10.el7.x86_64 -y
    
    -L:level,只列出一级
    [root@master1 ~]# tree -L 1 /mnt
    /mnt
    ├── bin
    ├── dev
    ├── docker-entrypoint.d
    ├── docker-entrypoint.sh
    ├── etc
    ├── home
    ├── lib
    ├── media
    ├── mnt
    ├── opt
    ├── proc
    ├── root
    ├── run
    ├── sbin
    ├── srv
    ├── sys
    ├── tmp
    ├── usr
    └── var
    
    18 directories, 1 file
    

    将镜像从主机目录上卸载

    [root@master1 ~]# ctr image unmount /mnt
    /mnt
    

    将镜像导出为压缩包

    # 导出 linux/amd64 平台的镜像
    一般我们用的架构都是liunx下的amd架构
    [root@master1 ~]# ctr image export --platform linux/amd64 nginx.tar.gz docker.io/library/nginx:alpine
    [root@master1 ~]# ls
    anaconda-ks.cfg                               initial-setup-ks.cfg  公共  视频  文档  音乐
    cri-containerd-cni-1.5.10-linux-amd64.tar.gz  nginx.tar.gz          模板  图片  下载  桌面
    

    从压缩包导入镜像

    [root@master1 ~]# ctr image import nginx.tar.gz
    ctr: content digest sha256:7d003b925548b52a107073c359f69ed1f2113109891dd63cd007f1a4365c9d51: not found
    

    容器操作

    容器相关操作可以通过 ctr container 获取。

    创建容器

    [root@master1 ~]# ctr container create docker.io/library/nginx:alpine nginx
    

    列出容器

    [root@master1 ~]# ctr container ls
    CONTAINER    IMAGE                             RUNTIME
    nginx        docker.io/library/nginx:alpine    io.containerd.runc.v2
    [root@master1 ~]# ctr container ls -q
    nginx
    
    

    查看容器详细配置

    类似于 docker inspect 功能。

    [root@master1 ~]# ctr container info nginx
    {
        "ID": "nginx",
        "Labels": {
            "io.containerd.image.config.stop-signal": "SIGQUIT",
            "maintainer": "NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e"
        },
        "Image": "docker.io/library/nginx:alpine",
        "Runtime": {
            "Name": "io.containerd.runc.v2",
            "Options": {
                "type_url": "containerd.runc.v1.Options"
            }
        },
        "SnapshotKey": "nginx",
        "Snapshotter": "overlayfs",
        "CreatedAt": "2022-05-26T11:10:38.544713306Z",
        "UpdatedAt": "2022-05-26T11:10:38.544713306Z",
        "Extensions": null,
        "Spec": {
            "ociVersion": "1.0.2-dev",
            "process": {
                "user": {
                    "uid": 0,
                    "gid": 0,
                    "additionalGids": [
                        1,
                        2,
                        3,
                        4,
                        6,
                        10,
                        11,
                        20,
                        26,
                        27
                    ]
                },
                "args": [
                    "/docker-entrypoint.sh",
                    "nginx",
                    "-g",
                    "daemon off;"
                ],
                "env": [
                    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                    "NGINX_VERSION=1.21.6",
                    "NJS_VERSION=0.7.3",
                    "PKG_RELEASE=1"
                ],
                "cwd": "/",
                "capabilities": {
                    "bounding": [
                        "CAP_CHOWN",
                        "CAP_DAC_OVERRIDE",
                        "CAP_FSETID",
                        "CAP_FOWNER",
                        "CAP_MKNOD",
                        "CAP_NET_RAW",
                        "CAP_SETGID",
                        "CAP_SETUID",
                        "CAP_SETFCAP",
                        "CAP_SETPCAP",
                        "CAP_NET_BIND_SERVICE",
                        "CAP_SYS_CHROOT",
                        "CAP_KILL",
                        "CAP_AUDIT_WRITE"
                    ],
                    "effective": [
                        "CAP_CHOWN",
                        "CAP_DAC_OVERRIDE",
                        "CAP_FSETID",
                        "CAP_FOWNER",
                        "CAP_MKNOD",
                        "CAP_NET_RAW",
                        "CAP_SETGID",
                        "CAP_SETUID",
                        "CAP_SETFCAP",
                        "CAP_SETPCAP",
                        "CAP_NET_BIND_SERVICE",
                        "CAP_SYS_CHROOT",
                        "CAP_KILL",
                        "CAP_AUDIT_WRITE"
                    ],
                    "inheritable": [
                        "CAP_CHOWN",
                        "CAP_DAC_OVERRIDE",
                        "CAP_FSETID",
                        "CAP_FOWNER",
                        "CAP_MKNOD",
                        "CAP_NET_RAW",
                        "CAP_SETGID",
                        "CAP_SETUID",
                        "CAP_SETFCAP",
                        "CAP_SETPCAP",
                        "CAP_NET_BIND_SERVICE",
                        "CAP_SYS_CHROOT",
                        "CAP_KILL",
                        "CAP_AUDIT_WRITE"
                    ],
                    "permitted": [
                        "CAP_CHOWN",
                        "CAP_DAC_OVERRIDE",
                        "CAP_FSETID",
                        "CAP_FOWNER",
                        "CAP_MKNOD",
                        "CAP_NET_RAW",
                        "CAP_SETGID",
                        "CAP_SETUID",
                        "CAP_SETFCAP",
                        "CAP_SETPCAP",
                        "CAP_NET_BIND_SERVICE",
                        "CAP_SYS_CHROOT",
                        "CAP_KILL",
                        "CAP_AUDIT_WRITE"
                    ]
                },
                "rlimits": [
                    {
                        "type": "RLIMIT_NOFILE",
                        "hard": 1024,
                        "soft": 1024
                    }
                ],
                "noNewPrivileges": true
            },
            "root": {
                "path": "rootfs"
            },
            "mounts": [
                {
                    "destination": "/proc",
                    "type": "proc",
                    "source": "proc",
                    "options": [
                        "nosuid",
                        "noexec",
                        "nodev"
                    ]
                },
                {
                    "destination": "/dev",
                    "type": "tmpfs",
                    "source": "tmpfs",
                    "options": [
                        "nosuid",
                        "strictatime",
                        "mode=755",
                        "size=65536k"
                    ]
                },
                {
                    "destination": "/dev/pts",
                    "type": "devpts",
                    "source": "devpts",
                    "options": [
                        "nosuid",
                        "noexec",
                        "newinstance",
                        "ptmxmode=0666",
                        "mode=0620",
                        "gid=5"
                    ]
                },
                {
                    "destination": "/dev/shm",
                    "type": "tmpfs",
                    "source": "shm",
                    "options": [
                        "nosuid",
                        "noexec",
                        "nodev",
                        "mode=1777",
                        "size=65536k"
                    ]
                },
                {
                    "destination": "/dev/mqueue",
                    "type": "mqueue",
                    "source": "mqueue",
                    "options": [
                        "nosuid",
                        "noexec",
                        "nodev"
                    ]
                },
                {
                    "destination": "/sys",
                    "type": "sysfs",
                    "source": "sysfs",
                    "options": [
                        "nosuid",
                        "noexec",
                        "nodev",
                        "ro"
                    ]
                },
                {
                    "destination": "/run",
                    "type": "tmpfs",
                    "source": "tmpfs",
                    "options": [
                        "nosuid",
                        "strictatime",
                        "mode=755",
                        "size=65536k"
                    ]
                }
            ],
            "linux": {
                "resources": {
                    "devices": [
                        {
                            "allow": false,
                            "access": "rwm"
                        },
                        {
                            "allow": true,
                            "type": "c",
                            "major": 1,
                            "minor": 3,
                            "access": "rwm"
                        },
                        {
                            "allow": true,
                            "type": "c",
                            "major": 1,
                            "minor": 8,
                            "access": "rwm"
                        },
                        {
                            "allow": true,
                            "type": "c",
                            "major": 1,
                            "minor": 7,
                            "access": "rwm"
                        },
                        {
                            "allow": true,
                            "type": "c",
                            "major": 5,
                            "minor": 0,
                            "access": "rwm"
                        },
                        {
                            "allow": true,
                            "type": "c",
                            "major": 1,
                            "minor": 5,
                            "access": "rwm"
                        },
                        {
                            "allow": true,
                            "type": "c",
                            "major": 1,
                            "minor": 9,
                            "access": "rwm"
                        },
                        {
                            "allow": true,
                            "type": "c",
                            "major": 5,
                            "minor": 1,
                            "access": "rwm"
                        },
                        {
                            "allow": true,
                            "type": "c",
                            "major": 136,
                            "access": "rwm"
                        },
                        {
                            "allow": true,
                            "type": "c",
                            "major": 5,
                            "minor": 2,
                            "access": "rwm"
                        },
                        {
                            "allow": true,
                            "type": "c",
                            "major": 10,
                            "minor": 200,
                            "access": "rwm"
                        }
                    ]
                },
                "cgroupsPath": "/default/nginx",
                "namespaces": [
                    {
                        "type": "pid"
                    },
                    {
                        "type": "ipc"
                    },
                    {
                        "type": "uts"
                    },
                    {
                        "type": "mount"
                    },
                    {
                        "type": "network"
                    }
                ],
                "maskedPaths": [
                    "/proc/acpi",
                    "/proc/asound",
                    "/proc/kcore",
                    "/proc/keys",
                    "/proc/latency_stats",
                    "/proc/timer_list",
                    "/proc/timer_stats",
                    "/proc/sched_debug",
                    "/sys/firmware",
                    "/proc/scsi"
                ],
                "readonlyPaths": [
                    "/proc/bus",
                    "/proc/fs",
                    "/proc/irq",
                    "/proc/sys",
                    "/proc/sysrq-trigger"
                ]
            }
        }
    }
    

    删除容器

    [root@master1 ~]# ctr container rm nginx
    [root@master1 ~]# ctr container ls
    CONTAINER    IMAGE    RUNTIME
    

    除了使用 rm 子命令之外也可以使用 delete 或者 del 删除容器(习惯用rm)。

    任务

    上面我们通过 container create 命令创建的容器,并没有处于运行状态,只是一个静态的容器。一个 container 对象只是包含了运行一个容器所需的资源及相关配置数据,表示 namespaces、rootfs 和容器的配置都已经初始化成功了,只是用户进程还没有启动。

    一个容器真正运行起来是由 Task 任务实现的,Task 可以为容器设置网卡,还可以配置工具来对容器进行监控等

    Task 相关操作可以通过 ctr task 获取,如下我们通过 Task 来启动容器:

    $ ctr task start -d nginx
    /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
    /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
    

    启动容器后可以通过 task ls 查看正在运行的容器:

    $ ctr task ls
    TASK     PID     STATUS
    nginx    3630    RUNNING
    

    同样也可以使用 exec 命令进入容器进行操作:

    $ ctr task exec --exec-id 0 -t nginx sh
    / #
    

    不过这里需要注意必须要指定 –exec-id 参数,这个 id 可以随便写,只要唯一就行。

    暂停容器,和 docker pause 类似的功能:

    [root@www ~]# ctr task pause nginx
    

    暂停后容器状态变成了 PAUSED:

    [root@www ~]# ctr task ls
    TASK     PID      STATUS
    nginx    10162    PAUSED
    

    同样也可以使用 resume 命令来恢复容器:

    [root@www ~]# ctr task resume nginx
    [root@www ~]# ctr task ls
    TASK     PID      STATUS
    nginx    10162    RUNNING
    

    不过需要注意 ctr 没有 stop 容器的功能,只能暂停或者杀死容器。杀死容器可以使用 task kill 命令:

    [root@www ~]# ctr task kill nginx
    [root@www ~]# ctr task ls
    TASK     PID      STATUS
    nginx    10162    STOPPED
    

    杀掉容器后可以看到容器的状态变成了 STOPPED。同样也可以通过 task rm 命令删除 Task:

    [root@www ~]# ctr task rm nginx
    [root@www ~]# ctr task ls
    TASK    PID    STATUS
    

    除此之外我们还可以获取容器的 cgroup 相关信息,可以使用 task metrics 命令用来获取容器的内存、CPU 和 PID 的限额与使用量。

    [root@www ~]# ctr task start -d nginx
    /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
    /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
    /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
    10-listen-on-ipv6-by-default.sh: info: IPv6 listen already enabled
    /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
    /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
    [root@www ~]# ctr task metrics nginx
    ID       TIMESTAMP
    nginx    2022-05-26 21:47:36.280710086 +0000 UTC
    
    METRIC                   VALUE
    memory.usage_in_bytes    1396736
    memory.limit_in_bytes    9223372036854771712
    memory.stat.cache        12288
    cpuacct.usage            52919888
    cpuacct.usage_percpu     [52919888]
    pids.current             2
    pids.limit               0
    
    

    还可以使用 task ps 命令查看容器中所有进程在宿主机中的 PID:

    [root@www ~]# ctr task ps nginx
    PID      INFO
    10757    -
    10788    -
    [root@www ~]# ctr task ls
    TASK     PID      STATUS
    nginx    10757    RUNNING
    

    其中第一个 PID 3984 就是我们容器中的1号进程。

    命名空间

    另外 Containerd 中也支持命名空间的概念,比如查看命名空间:

    [root@www ~]# ctr ns ls
    NAME    LABELS
    default
    

    如果不指定,ctr 默认使用的是 default 空间。同样也可以使用 ns create 命令创建一个命名空间:

    [root@www ~]# ctr ns create test
    [root@www ~]# ctr ns ls
    NAME    LABELS
    default
    test
    

    使用 remove 或者 rm 可以删除 namespace:

    [root@www ~]# ctr ns ls
    NAME    LABELS
    default
    

    有了命名空间后就可以在操作资源的时候指定 namespace,比如查看 test 命名空间的镜像,可以在操作命令后面加上 -n test 选项:

    [root@www ~]# ctr -n default image ls
    REF                            TYPE                                                      DIGEST                                                                  SIZE    PLATFORMS                                                                                LABELS
    docker.io/library/nginx:alpine application/vnd.docker.distribution.manifest.list.v2+json sha256:a74534e76ee1121d418fa7394ca930eb67440deda413848bc67c68138535b989 9.7 MiB linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x -
    
    [root@www ~]# ctr -n test image ls
    REF TYPE DIGEST SIZE PLATFORMS LABELS
    

    我们知道 Docker 其实也是默认调用的 containerd,事实上 Docker 使用的 containerd 下面的命名空间默认是 moby,而不是 default,所以假如我们有用 docker 启动容器,那么我们也可以通过 ctr -n moby 来定位下面的容器:

    (ctr指定不存在的ns并不会报错)

    [root@www ~]# ctr -n moby container ls
    CONTAINER    IMAGE    RUNTIME
    

    同样 Kubernetes 下使用的 containerd 默认命名空间是 k8s.io,所以我们可以使用 ctr -n k8s.io 来查看 Kubernetes 下面创建的容器。

    切换容器运行时

    前面我们了解了 containerd 的发展历史和基本使用方式,本节我们就来尝试下使用 containerd 来作为 Kubernetes 集群的容器运行时。

    前面我们安装的集群默认使用的是 Docker 作为容器运行时,那么应该如何将容器运行时从 Docker 切换到 containerd 呢?

    维护节点

    首先标记需要切换的节点为维护模式,强制驱逐节点上正在运行的 Pod,这样可以最大程度降低切换过程中影响应用的正常运行,比如我们先将 node1 节点切换到 containerd。

    首先使用 kubectl cordon 命令将 node1 节点标记为 unschedulable 不可调度状态:
    (cordon警戒线,drain排水,使流走)

    # 将 node1 标记为 unschedulable
    $ kubectl cordon node1
    node/node1 cordoned
    $ kubectl get nodes
    NAME     STATUS                     ROLES    AGE   VERSION
    master   Ready                      master   85d   v1.19.11
    node1    Ready,SchedulingDisabled   <none>   85d   v1.19.11
    node2    Ready                      <none>   85d   v1.19.11
    

    执行完上面的命令后,node1 节点变成了一个 SchedulingDisabled 状态,表示不可调度,这样新创建的 Pod 就不会调度到当前节点上来了。

    接下来维护 node1 节点,使用 kubectl drain 命令来维护节点并驱逐节点上的 Pod:

    # 维护 node1 节点,驱逐 Pod
    $ kubectl drain node1 --ignore-daemonsets
    

    上面的命令会强制将 node1 节点上的 Pod 进行驱逐,我们加了一个 --ignore-daemonsets 的参数可以用来忽略 DaemonSet 控制器管理的 Pods,因为这些 Pods 不用驱逐到其他节点去,当节点驱逐完成后接下来我们就可以来对节点进行维护操作了,除了切换容器运行时可以这样操作,比如我们需要变更节点配置、升级内核等操作的时候都可以先将节点进行驱逐,然后再进行维护。

    切换 containerd

    接下来停掉 docker、containerd 和 kubelet:

    $ systemctl stop kubelet docker containerd
    

    因为我们安装的 Docker 默认安装使用了 containerd 作为后端的容器运行时,所以不需要单独安装 containerd 了,当然你也可以将 Docker 和 containerd 完全卸载掉,然后重新安装,这里我们选择直接使用之前安装的 containerd。

    因为 containerd 中默认已经实现了 CRI,但是是以 plugin 的形式配置的,以前 Docker 中自带的 containerd 默认是将 CRI 这个插件禁用掉了的(使用配置 disabled_plugins = [“cri”]),所以这里我们重新生成默认的配置文件来覆盖掉:

    containerd config default > /etc/containerd/config.toml
    

    前面我们已经介绍过上面的配置文件了,首先我们修改默认的 pause 镜像为国内的地址,替换 [plugins.“io.containerd.grpc.v1.cri”] 下面的 sandbox_image(生成infra容器,或者说pause容器):

    [plugins."io.containerd.grpc.v1.cri"]
      sandbox_image = "registry.aliyuncs.com/k8sxio/pause:3.2"
      ......
    

    同样再配置下镜像仓库的加速器地址:

    [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://xxxxxxxxxx.mirror.aliyuncs.com"]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]
          endpoint = ["https://registry.aliyuncs.com/k8sxio"]
    

    接下来修改 kubelet 配置,将容器运行时配置为 containerd,打开 /etc/sysconfig/kubelet 文件,在该文件中可以添加一些额外的 kubelet 启动参数,配置如下所示:

    KUBELET_EXTRA_ARGS="--container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock"
    

    上面的配置中我们增加了两个参数,–container-runtime 参数是用来指定使用的容器运行时的,可选值为 docker 或者 remote,默认是 docker,由于我们这里使用的是 containerd 这种容器运行时,所以配置为 remote 值(也就是除 docker 之外的容器运行时都应该指定为 remote),然后第二个参数 --container-runtime-endpoint 是用来指定远程的运行时服务的 endpiont 地址的,在 Linux 系统中一般都是使用 unix 套接字的形式,比如这里我们就是指定连接 containerd 的套接字地址 unix:///run/containerd/containerd.sock。

    其实还应该配置一个 --image-service-endpoint 参数用来指定远程 CRI 的镜像服务地址,如果没有指定则默认使用 --container-runtime-endpoint 的值了,因为 CRI 都会实容器和镜像服务的。

    配置完成后重启 containerd 和 kubelet 即可:

    $ systemctl daemon-reload
    $ systemctl restart containerd kubelet
    
    

    重启完成后查看节点状态是否正常:

    $ kubectl get nodes -o wide
    

    获取节点的时候加上 -o wide 可以查看节点的更多信息,从上面对比可以看到 node1 节点的容器运行时已经切换到 containerd://1.4.4 了。

    最后把 node1 节点重新加回到集群中来允许调度 Pod 资源:

    $ kubectl uncordon node1
    
    $ kubectl get nodes
    
    

    用同样的方法再去处理其他节点即可将整个集群切换成容器运行时 containerd 了。

    crictl

    现在我们可以 node1 节点上使用 ctr 命令来管理 containerd,查看多了一个名为 k8s.io 的命名空间:
    (k8s.io针对的kubernetes集群的,moby是docker的)

    $ ctr ns ls
    NAME   LABELS
    k8s.io
    moby
    

    上文我们已经介绍 kubernetes 集群对接的 containerd 所有资源都在 k8s.io 的命名空间下面,而 docker 的则默认在 moby 下面,当然现在 moby 下面没有任何的数据了,但是在 k8s.io 命名空间下面就有很多镜像和容器资源了:

    $ ctr -n moby c ls
    CONTAINER    IMAGE    RUNTIME
    $ ctr -n moby i ls
    REF TYPE DIGEST SIZE PLATFORMS LABELS
    $ ctr -n moby t ls
    TASK    PID    STATUS
    ctr -n k8s.io i ls -q
    docker.io/library/busybox:latest
    docker.io/library/busybox@sha256:0f354ec1728d9ff32edcd7d1b8bbdfc798277ad36120dc3dc683be44524c8b60
    quay.io/coreos/flannel:v0.14.0
    quay.io/coreos/flannel@sha256:4a330b2f2e74046e493b2edc30d61fdebbdddaaedcb32d62736f25be8d3c64d5
    registry.aliyuncs.com/k8sxio/pause:3.2
    ......
    

    我们当然可以直接使用 ctr 命令来直接管理镜像或容器资源,但是我们在使用过程中明显可以感觉到该工具没有 docker CLI 方便,从使用便捷性和功能性上考虑,我们更推荐使用 crictl 作为管理工具,crictl 为 CRI 兼容的容器运行时提供 CLI,这允许 CRI 运行时开发人员在无需设置 Kubernetes 组件的情况下调试他们的运行时。

    接下来我们就先简单介绍下如何使用 crictl 工具来提升管理容器运行时的效率。

    安装

    首先我们需要先安装 crictl 工具,直接从 cri-tools 的 release 页面下载对应的二进制包,解压放入 PATH 路径下即可:

    $ VERSION="v1.22.0"
    $ wget https://github.com/kubernetes-sigs/cri-tools/releases/download/$VERSION/crictl-$VERSION-linux-amd64.tar.gz
    # 如果有限制,也可以替换成下面的 URL 加速下载
    # wget https://download.fastgit.org/kubernetes-sigs/cri-tools/releases/download/$VERSION/crictl-$VERSION-linux-amd64.tar.gz
    $ tar zxvf crictl-$VERSION-linux-amd64.tar.gz -C /usr/local/bin
    $ rm -f crictl-$VERSION-linux-amd64.tar.gz
    $ crictl -v
    crictl version v1.22.0
    

    到这里证明 crictl 工具安装成功了。

    用法

    crictl 安装完成后,接下来我们来了解下该工具的一些常见使用方法。

    首先需要修改下默认的配置文件,默认为 /etc/crictl.yaml,在文件中指定容器运行时和镜像的 endpoint 地址,内容如下所示:

    runtime-endpoint: unix:///var/run/containerd/containerd.sock
    image-endpoint: unix:///var/run/containerd/containerd.sock
    debug: false
    pull-image-on-create: false
    disable-pull-on-run: false
    

    (之前是dockerd.sock)
    配置完成后就可以使用 crictl 命令了。

    获取 Pod 列表

    通过 crictl pods 命令可以获取当前节点上运行的 Pods 列表,如下所示:

    crictl pods
    

    还可以使用 --name 参数获取指定的 Pod:

    $ crictl pods --name kube-flannel-ds-mzdgl
    

    同样也可以根据标签来筛选 Pod 列表:

    $ crictl pods --label app=flannel
    

    获取镜像列表

    使用 crictl images 命令可以获取所有的镜像:

    $ crictl images
    

    同样在命令后面可以加上 -v 参数来显示镜像的详细信息:

    $ crictl images -v
    

    获取容器列表

    使用 crictl ps 命令可以获取正在运行的容器列表:

    $ crictl ps
    

    还有更多其他可选参数,可以通过 crictl ps -h 获取,比如显示最近创建的两个容器:

    $ crictl ps -n 2
    

    使用 -s 选项按照状态进行过滤:

    $ crictl ps -s Running
    

    在容器中执行命令

    crictl 也有类似 exec 的命令支持,比如在容器 ID 为 c8474738e4587 的容器中执行一个 date 命令:

    $ crictl exec -it c8474738e4587 date
    

    输出容器日志

    还可以获取容器日志信息:

    $ crictl logs c8474738e4587
    

    和 kubectl logs 类似于,还可以使用 -f 选项来 Follow 日志输出,–tail N 也可以指定输出最近的 N 行日志。

    资源统计

    使用 crictl stats 命令可以列举容器资源的使用情况:

    crictl status
    

    此外镜像和容器相关的一些操作也都支持,比如:

    拉取镜像:crictl pull
    运行 Pod:crictl runp
    运行容器:crictl run
    启动容器:crictl start
    删除容器:crictl rm
    删除镜像:crictl rmi
    删除 Pod:crictl rmp
    停止容器:crictl stop
    停止 Pod:crictl stopp

    CLI 对比

    前面我们了解了围绕镜像、容器和 Pod 可以使用 docker、ctr、crictl 这些命令行工具进行管理,接下来我们就来比较小这几个常用命令的使用区别。

    镜像相关

    在这里插入图片描述

    容器相关

    在这里插入图片描述
    需要注意的是通过 ctr containers create 命令创建的容器只是一个静态的容器,所以还需要通过 ctr task start 来启动容器进程。当然,也可以直接使用 ctr run 命令来创建并运行容器。在进入容器操作时,与 docker 不同的是,必须在 ctr task exec 命令后指定 --exec-id 参数,这个 id 可以随便写,只要唯一就行。另外,ctr 没有 stop 容器的功能,只能暂停(ctr task pause)或者杀死(ctr task kill)容器。

    Pod 相关

    在这里插入图片描述
    这里要说明的是 crictl pods 列出的是 Pod 的信息,包括 Pod 所在的命名空间以及状态。crictl ps 列出的是应用容器的信息,而 docker ps 列出的是初始化容器(pause 容器)和应用容器的信息,初始化容器在每个 Pod 启动时都会创建,通常不会关注,所以 crictl 使用起来更简洁明了一些。

    日志配置

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

    当使用 Docker 作为 Kubernetes 容器运行时的时候,容器日志的落盘是由 Docker 来完成的,日志被保存在类似 /var/lib/docker/containers/<CONTAINER> 的目录下面,kubelet 会在 /var/log/pods 和 /var/log/containers 下面创建软链接,指向容器日志目录下的容器日志文件。对应的日志相关配置可以通过配置文件进行指定,如下所示:

    {
        "log-driver": "json-file",
        "log-opts": {
            "max-size": "100m",
            "max-file: "10"
        }
    }
    

    而当使用 containerd 作为 Kubernetes 容器运行时的时候,容器日志的落盘则由 kubelet 来完成了,被直接保存在 /var/log/pods/ 目录下面,同时在 /var/log/containers 目录下创建软链接指向日志文件。同样日志配置则是通过 kubelet 参数中进行指定的,如下所示:

    --container-log-max-files=10 --container-log-max-size="100Mi"
    

    所以如果我们有进行日志收集理论上来说两种方案都是兼容的,基本上不用改动。

    当然除了这些差异之外,可能对于我们来说镜像构建这个环节是我们最需要关注的了。切换到 containerd 之后,需要注意 docker.sock 不再可用,也就意味着不能再在容器里面执行 docker 命令来构建镜像了。所以接下来需要和大家介绍几种不需要使用 docker.sock 也可以构建镜像的方法。

    nerdctl

    前面我们介绍了可以使用 ctr 操作管理 containerd 镜像容器,但是大家都习惯了使用 docker cli,ctr 使用起来可能还是不太顺手,为了能够让大家更好的转到 containerd 上面来,社区提供了一个新的命令行工具:nerdctl。nerdctl 是一个与 docker cli 风格兼容的 containerd 客户端工具,而且直接兼容 docker compose 的语法的,这就大大提高了直接将 containerd 作为本地开发、测试或者单机容器部署使用的效率。

    安装

    同样直接在 GitHub Release 页面下载对应的压缩包解压到 PATH 路径下即可:

    # 如果没有安装 containerd,则可以下载 nerdctl-full-<VERSION>-linux-amd64.tar.gz 包进行安装
    $ wget https://github.com/containerd/nerdctl/releases/download/v0.11.0/nerdctl-0.11.0-linux-amd64.tar.gz
    # 如果有限制,也可以替换成下面的 URL 加速下载
    # wget https://download.fastgit.org/containerd/nerdctl/releases/download/v0.11.0/nerdctl-0.11.0-linux-amd64.tar.gz
    $ mkdir -p /usr/local/containerd/bin/ && tar -zxvf nerdctl-0.11.0-linux-amd64.tar.gz nerdctl && mv nerdctl /usr/local/containerd/bin/
    $ ln -s /usr/local/containerd/bin/nerdctl /usr/local/bin/nerdctl
    $ nerdctl version
    Client:
     Version:       v0.11.0
     Git commit:    c802f934791f83dacf20a041cd1c865f8fac954e
    
    Server:
     containerd:
      Version:      v1.5.5
      Revision:     72cec4be58a9eb6b2910f5d10f1c01ca47d231c0
    

    命令

    Run&Exec

    nerdctl run

    和 docker run 类似可以使用 nerdctl run 命令运行容器,例如:

    $ nerdctl run -d -p 80:80 --name=nginx --restart=always nginx:alpine
    

    可选的参数使用和 docker run 基本一直,比如 -i、-t、–cpus、–memory 等选项,可以使用 nerdctl run --help 获取可使用的命令选项:

    $ nerdctl run --help
    

    nerdctl exec

    同样也可以使用 exec 命令执行容器相关命令,例如:

    $ nerdctl exec -it nginx /bin/sh
    

    容器管理

    nerdctl ps

    列出容器

    使用 nerdctl ps 命令可以列出所有容器。

    $ nerdctl ps
    

    同样可以使用 -a 选项显示所有的容器列表,默认只显示正在运行的容器,不过需要注意的是 nerdctl ps 命令并没有实现 docker ps 下面的 --filter、–format、–last、–size 等选项。

    nerdctl inspect

    获取容器的详细信息。

    $ nerdctl inspect nginx
    

    显示结果和 docker inspect 也基本一致的。

    nerdctl logs

    获取容器日志

    查看容器日志是我们平时经常会使用到的一个功能,同样我们可以使用 nerdctl logs 来获取日志数据:

    $ nerdctl logs -f nginx
    

    同样支持 -f、-t、-n、–since、–until 这些选项。

    nerdctl stop

    停止容器

    $ nerdctl stop nginx
    nginx
    $ nerdctl ps
    CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES
    $ nerdctl ps -a
    CONTAINER ID    IMAGE                             COMMAND                   CREATED           STATUS    PORTS                 NAMES
    6e489777d2f7    docker.io/library/nginx:alpine    "/docker-entrypoint.…"    20 minutes ago    Up        0.0.0.0:80->80/tcp    nginx
    

    nerdctl rm

    删除容器

    $ nerdctl rm nginx
    You cannot remove a running container f4ac170235595f28bf962bad68aa81b20fc83b741751e7f3355bd77d8016462d. Stop the container before attempting removal or force remove
    $ nerdctl rm -f ginx
    nginx
    $ nerdctl ps
    CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES
    

    要强制删除同样可以使用 -f 或 --force 选项来操作。

    镜像管理

    nerdctl images

    镜像列表

    $ nerdctl images
    

    也需要注意的是没有实现 docker images 的一些选项,比如 --all、–digests、–filter、–format。

    nerdctl pull

    拉取镜像

    $ docker.io/library/busybox:latest:
    

    nerdctl push

    推送镜像

    当然在推送镜像之前也可以使用 nerdctl login 命令登录到镜像仓库,然后再执行 push 操作。

    可以使用 nerdctl login --username xxx --password xxx 进行登录,使用 nerdctl logout 可以注销退出登录。

    nerdctl tag

    镜像标签

    使用 tag 命令可以为一个镜像创建一个别名镜像:

    $ nerdctl images
    REPOSITORY    TAG                  IMAGE ID        CREATED           SIZE
    busybox       latest               0f354ec1728d    6 minutes ago     1.3 MiB
    nginx         alpine               bead42240255    41 minutes ago    16.0 KiB
    $ nerdctl tag nginx:alpine harbor.k8s.local/course/nginx:alpine
    $ nerdctl images
    REPOSITORY                       TAG                  IMAGE ID        CREATED           SIZE
    busybox                          latest               0f354ec1728d    7 minutes ago     1.3 MiB
    nginx                            alpine               bead42240255    41 minutes ago    16.0 KiB
    harbor.k8s.local/course/nginx    alpine               bead42240255    2 seconds ago     16.0 KiB
    

    nerdctl save

    导出镜像

    使用 save 命令可以导出镜像为一个 tar 压缩包。

    $ nerdctl save -o busybox.tar.gz busybox:latest
    $ ls -lh busybox.tar.gz
    -rw-r--r-- 1 root root 761K Aug 19 15:19 busybox.tar.gz
    

    nerdctl rmi

    删除镜像

    nerdctl rmi busybox
    

    nerdctl load

    导入镜像

    使用 load 命令可以将上面导出的镜像再次导入:

    $ nerdctl load -i busybox.tar.gz
    

    使用 -i 或 --input 选项指定需要导入的压缩包。

    镜像构建

    镜像构建是平时我们非常重要的一个需求,我们知道 ctr 并没有构建镜像的命令,而现在我们又不使用 Docker 了,那么如何进行镜像构建了,幸运的是 nerdctl 就提供了 nerdctl build 这样的镜像构建命令。
    nerdctl build:从 Dockerfile 构建镜像

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

    FROM nginx
    RUN echo 'Hello Nerdctl From Containerd' > /usr/share/nginx/html/index.html
    

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

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

    可以看到有一个错误提示,需要我们安装 buildctl 并运行 buildkitd,这是因为 nerdctl build 需要依赖 buildkit 工具。

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

    1.服务端 buildkitd:当前支持 runc 和 containerd 作为 worker,默认是 runc,我们这里使用 containerd
    2.客户端 buildctl:负责解析 Dockerfile,并向服务端 buildkitd 发出构建请求
    buildkit 是典型的 C/S 架构,客户端和服务端是可以不在一台服务器上,而 nerdctl 在构建镜像的时候也作为 buildkitd 的客户端,所以需要我们安装并运行 buildkitd。

    所以接下来我们先来安装 buildkit:

    $ wget https://github.com/moby/buildkit/releases/download/v0.9.0/buildkit-v0.9.0.linux-amd64.tar.gz
    # 如果有限制,也可以替换成下面的 URL 加速下载
    # wget https://download.fastgit.org/moby/buildkit/releases/download/v0.9.0/buildkit-v0.9.0.linux-amd64.tar.gz
    $ tar -zxvf buildkit-v0.9.0.linux-amd64.tar.gz -C /usr/local/containerd/
    bin/
    bin/buildctl
    bin/buildkit-qemu-aarch64
    bin/buildkit-qemu-arm
    bin/buildkit-qemu-i386
    bin/buildkit-qemu-mips64
    bin/buildkit-qemu-mips64el
    bin/buildkit-qemu-ppc64le
    bin/buildkit-qemu-riscv64
    bin/buildkit-qemu-s390x
    bin/buildkit-runc
    bin/buildkitd
    $ ln -s /usr/local/containerd/bin/buildkitd /usr/local/bin/buildkitd
    $ ln -s /usr/local/containerd/bin/buildctl /usr/local/bin/buildctl
    

    这里我们使用 Systemd 来管理 buildkitd,创建如下所示的 systemd unit 文件:

    $ cat /etc/systemd/system/buildkit.service
    [Unit]
    Description=BuildKit
    Documentation=https://github.com/moby/buildkit
    
    [Service]
    ExecStart=/usr/local/bin/buildkitd --oci-worker=false --containerd-worker=true
    
    [Install]
    WantedBy=multi-user.target
    

    然后启动 buildkitd:

    $ systemctl daemon-reload
    $ systemctl enable buildkit --now
    

    现在我们再来重新构建镜像:

    nerdctl build --no-cache -t nginx:nerdctl -f Dockerfile .
    

    构建完成后查看镜像是否构建成功:

    $ nerdctl images
    

    我们可以看到已经有我们构建的 nginx:nerdctl 镜像了,不过出现了一个 WARN[0000] unparsable image name “xxx” 的 Warning 信息,在镜像列表里面也可以看到有一个镜像 tag 为空的镜像,和我们构建的镜像 ID 一样,在 nerdctl 的 github issue 上也有提到这个问题:https://github.com/containerd/nerdctl/issues/177,不过到现在为止还没有 FIX,幸运的是这只是一个警告,不会影响我们的使用。

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

    $ nerdctl run -d -p 80:80 --name=nginx --restart=always nginx:nerdctl
    f8f639cb667926023231b13584226b2c7b856847e0a25bd5f686b9a6e7e3cacd
    $ nerdctl ps
    CONTAINER ID    IMAGE                              COMMAND                   CREATED         STATUS    PORTS                 NAMES
    f8f639cb6679    docker.io/library/nginx:nerdctl    "/docker-entrypoint.…"    1 second ago    Up        0.0.0.0:80->80/tcp    nginx
    $ curl localhost
    This is a nerdctl build's nginx image base on containerd
    

    这样我们就使用 nerdctl + buildkitd 轻松完成了容器镜像的构建。

    当然如果你还想在单机环境下使用 Docker Compose,在 containerd 模式下,我们也可以使用 nerdctl 来兼容该功能。同样我们可以使用 nerdctl compose、nerdctl compose up、nerdctl compose logs、nerdctl compose build、nerdctl compose down 等命令来管理 Compose 服务。这样使用 containerd、nerdctl 结合 buildkit 等工具就完全可以替代 docker 在镜像构建、镜像容器方面的管理功能了。

    镜像构建

    在使用 Docker 的时候一半情况下我们都会直接使用 docker build 来构建镜像,切换到 Containerd 的时候,上节我们也介绍了可以使用 nerdctl + buildkit 来构建容器镜像,除了这些方式之外,还有其他常见的镜像构建工具吗?

    接下来我们就来介绍下在 Containerd 容器运行时下面镜像构建的主要工具和方案。

    使用 Docker 做镜像构建服务

    在 Kubernetes 集群中,部分 CI/CD 流水线业务可能需要使用 Docker 来提供镜像打包服务。可通过宿主机的 Docker 实现,将 Docker 的 UNIX Socket(/var/run/docker.sock) 通过 hostPath 挂载到 CI/CD 的业务 Pod 中,之后在容器里通过 UNIX Socket 来调用宿主机上的 Docker 进行构建,这个就是之前我们使用较多的 Docker outside of Docker 方案。该方式操作简单,比真正意义上的 Docker in Docker 更节省资源,但该方式可能会遇到以下问题:

    1.无法运行在 Runtime 是 containerd 的集群中。
    2.如果不加以控制,可能会覆盖掉节点上已有的镜像。
    3.在需要修改 Docker Daemon 配置文件的情况下,可能会影响到其他业务。
    4.在多租户的场景下并不安全,当拥有特权的 Pod 获取到 Docker 的 UNIX Socket 之后,Pod 中的容器不仅可以调用宿主机的 Docker 构建镜像、删除已有镜像或容器,甚至可以通过 docker exec 接口操作其他容器。

    对于部分需要 containerd 集群,而不改变 CI/CD 业务流程仍使用 Docker 构建镜像一部分的场景,我们可以通过在原有 Pod 上添加 DinD 容器作为 Sidecar 或者使用 DaemonSet 在节点上部署专门用于构建镜像的 Docker 服务。

    使用 DinD 作为 Pod 的 Sidecar

    如下所示,我们有一个名为 clean-ci 的容器,会该容器添加一个 Sidecar 容器,配合 emptyDir,让 clean-ci 容器可以通过 UNIX Socket 访问 DinD 容器:

    apiVersion: v1
    kind: Pod
    metadata:
      name: clean-ci
    spec:
      containers:
      - name: dind
        image: 'docker:stable-dind'
        command:
        - dockerd
        - --host=unix:///var/run/docker.sock
        - --host=tcp://0.0.0.0:8000
        securityContext:
          privileged: true
        volumeMounts:
        - mountPath: /var/run
          name: cache-dir
      - name: clean-ci
        image: 'docker:stable'
        command: ["/bin/sh"]
        args: ["-c", "docker info >/dev/null 2>&1; while [ $? -ne 0 ] ; do sleep 3; docker info >/dev/null 2>&1; done; docker pull library/busybox:latest; docker save -o busybox-latest.tar library/busybox:latest; docker rmi library/busybox:latest; while true; do sleep 86400; done"]
        volumeMounts:
        - mountPath: /var/run
          name: cache-dir
      volumes:
      - name: cache-dir
        emptyDir: {}
    

    通过上面添加的 dind 容器来提供 dockerd 服务,然后在业务构建容器中通过 emptyDir{} 来共享 /var/run 目录,业务容器中的 docker 客户端就可以通过 unix:///var/run/docker.sock 来与 dockerd 进行通信。

    使用 DaemonSet 在每个 containerd 节点上部署 Docker

    除了上面的 Sidecar 模式之外,还可以直接在 containerd 集群中通过 DaemonSet 来部署 Docker,然后业务构建的容器就和之前使用的模式一样,直接通过 hostPath 挂载宿主机的 unix:///var/run/docker.sock 文件即可。

    使用以下 YAML 部署 DaemonSet。示例如下:

    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      name: docker-ci
    spec:
      selector:
        matchLabels:
          app: docker-ci
      template:
        metadata:
          labels:
            app: docker-ci
        spec:
          containers:
          - name: docker-ci
            image: 'docker:stable-dind'
            command:
            - dockerd
            - --host=unix:///var/run/docker.sock
            - --host=tcp://0.0.0.0:8000
            securityContext:
              privileged: true
            volumeMounts:
            - mountPath: /var/run
              name: host
          volumes:
          - name: host
            hostPath:
              path: /var/run
    

    上面的 DaemonSet 会在每个节点上运行一个 dockerd 服务,这其实就类似于将以前的 docker 服务放入到了 Kubernetes 集群中进行管理,然后其他的地方和之前没什么区别,甚至都不需要更改以前方式的任何东西。将业务构建 Pod 与 DaemonSet 共享同一个 hostPath,如下所示:

    apiVersion: v1
    kind: Pod
    metadata:
      name: clean-ci
    spec:
      containers:
      - name: clean-ci
        image: 'docker:stable'
        command: ["/bin/sh"]
        args: ["-c", "docker info >/dev/null 2>&1; while [ $? -ne 0 ] ; do sleep 3; docker info >/dev/null 2>&1; done; docker pull library/busybox:latest; docker save -o busybox-latest.tar library/busybox:latest; docker rmi library/busybox:latest; while true; do sleep 86400; done"]
        volumeMounts:
        - mountPath: /var/run
          name: host
      volumes:
      - name: host
        hostPath:
          path: /var/run
    

    Kaniko

    在这里插入图片描述

    Kaniko 是 Google 开源的一款容器镜像构建工具,可以在容器或 Kubernetes 集群内从 Dockerfile 构建容器镜像,Kaniko 构建容器镜像时并不依赖于 docker daemon,也不需要特权模式,而是完全在用户空间中执行 Dockerfile 中的每条命令,这使得在无法轻松或安全地运行 docker daemon 的环境下构建容器镜像成为了可能。

    Kaniko 构建容器镜像时,需要使用 Dockerfile、构建上下文、以及构建成功后镜像在仓库中的存放地址。此外 Kaniko 支持多种方式将构建上下文挂载到容器中,比如可以使用本地文件夹、GCS bucket、S3 bucket 等方式,使用 GCS 或者 S3 时需要把上下文压缩为 tar.gz,kaniko 会自行在构建时解压上下文。

    Kaniko executor 读取 Dockerfile 后会逐条解析 Dockerfile 内容,一条条执行命令,每一条命令执行完以后会在用户空间下面创建一个 snapshot,并与存储与内存中的上一个状态进行比对,如果有变化,就将新的修改生成一个镜像层添加在基础镜像上,并且将相关的修改信息写入镜像元数据中,等所有命令执行完,kaniko 会将最终镜像推送到指定的远端镜像仓库。。整个过程中,完全不依赖于 docker daemon。

    如下所示我们有一个简单的 Dokerfile 示例:

    FROM alpine:latest
    RUN apk add busybox-extras curl
    CMD ["echo","Hello Kaniko"]
    

    然后我们可以启动一个 kaniko 容器去完成上面的镜像构建,当然也可以直接在 Kubernetes 集群中去运行,如下所示新建一个 kaniko 的 Pod 来构建上面的镜像:

    apiVersion: v1
    kind: Pod
    metadata:
      name: kaniko
    spec:
      containers:
      - name: kaniko
        image: gcr.io/kaniko-project/executor:latest
        args: ["--dockerfile=/workspace/Dockerfile",
              "--context=/workspace/",
              "--destination=cnych/kaniko-test:v0.0.1"]
        volumeMounts:
          - name: kaniko-secret
            mountPath: /kaniko/.docker
          - name: dockerfile
            mountPath: /workspace/Dockerfile
            subPath: Dockerfile
      volumes:
        - name: dockerfile
          configMap:
            name: dockerfile
        - name: kaniko-secret
            projected:
             sources:
             - secret:
                name: regcred
                items:
                - key: .dockerconfigjson
                  path: config.json
    

    上面的 Pod 执行的 args 参数中,主要就是指定 kaniko 运行时需要的三个参数: Dockerfile、构建上下文以及远端镜像仓库。

    推送至指定远端镜像仓库需要 credential 的支持,所以需要将 credential 以 secret 的方式挂载到 /kaniko/.docker/ 这个目录下,文件名称为 config.json,内容如下:

    {
        "auths": {
            "https://index.docker.io/v1/": {
                "auth": "AbcdEdfgEdggds="
           }
        }
    
    }
    

    其中 auth 的值为: docker_registry_username:docker_registry_password base64 编码过后的值。然后 Dockerfile 通过 Configmap 的形式挂载进去,如果构建上下文中还有其他内容也需要一同挂载进去。
    更多使用方式可以参考官网https://github.com/GoogleContainerTools/kaniko

    Jib

    如果你是在 Java 环境下面,还可以使用 Jib 来构建镜像,Jib 也是 Google 开源的,只是是针对 Java 容器镜像构建的工具。
    通过使用 Jib,Java 开发人员可以使用他们熟悉的 Java 工具来构建镜像。Jib 是一个快速而简单的容器镜像构建工具,它负责处理将应用程序打包到容器镜像中所需的所有步骤,它不需要你编写 Dockerfile 或安装 Docker,而且可以直接集成到 Maven 和 Gradle 中,只需要将插件添加到构建中,就可以立即将 Java 应用程序容器化。

    Jib 利用了 Docker 镜像的分层机制,将其与构建系统集成,并通过以下方式优化 Java 容器镜像的构建:

    简单:Jib 使用 Java 开发,并作为 Maven 或 Gradle 的一部分运行。你不需要编写 Dockerfile 或运行 Docker 守护进程,甚至无需创建包含所有依赖的大 JAR 包。因为 Jib 与 Java 构建过程紧密集成,所以它可以访问到打包应用程序所需的所有信息。
    快速:Jib 利用镜像分层和缓存来实现快速、增量的构建。它读取你的构建配置,将你的应用程序组织到不同的层(依赖项、资源、类)中,并只重新构建和推送发生变更的层。在项目进行快速迭代时,Jib 只将发生变更的层(而不是整个应用程序)推送到镜像仓库来节省宝贵的构建时间。
    可重现:Jib 支持根据 Maven 和 Gradle 的构建元数据进行声明式的容器镜像构建,因此,只要输入保持不变,就可以通过配置重复创建相同的镜像。
    以下示例将使用 Jib 提供的 gradle 插件集成到一个 spring boot 项目的构建中,并展示 Jib 如何简单快速的构建镜像。

    首先,在项目的 build.gradle 构建文件中引入 jib 插件:

    buildscript{
        ...
        dependencies {
            ...
            classpath "gradle.plugin.com.google.cloud.tools:jib-gradle-plugin:1.1.2"
        }
    }
    
    apply plugin: 'com.google.cloud.tools.jib'
    

    如果需要配置相关参数,可以使用下面的 gradle 配置:

    jib {
        from {
            image = 'harbor.k8s.local/library/base:1.0'
            auth {
                username = '********'
                password = '********'
            }
        }
        to {
            image = 'harbor.k8s.local/library/xxapp:1.0'
            auth {
                username = '********'
                password = '********'
            }
        }
        container {
            jvmFlags = ['-Djava.security.egd=file:/dev/./urandom']
            ports = ['8080']
            useCurrentTimestamp = false
            workingDirectory = "/app"
        }
    }
    

    然后执行以下命令就可以直接触发构建生成容器镜像了:

    # 构建 jib.to.image 指定的镜像,并且推送至镜像仓库
    $ gradle jib
    

    如果你还想将构建的镜像保存到本地 dockerd,则可以使用下面的命令构建:

    gradle jibDockerBuild
    

    当然还有前文我们介绍的 buildkit 可以用于镜像构建,还有一个经常和 Podman 搭配使用的 Buildah,是一个可以用于构建符合 OCI 标准容器镜像的命令行工具,有了这些工具,在构建容器镜像时已经完全可以脱离 docker daemon 了,而且这些工具都能很好的与 Kubernetes 集成,支持在容器环境下完成构建。

    展开全文
  • docker containerd rpm package
  • 带有容器,CRI-O和JSON的流利位随着dockerd弃用作为Kubernetes容器运行时,我们就搬到containerd 。 更改后,我们的fluentbit日志记录无法正确解析JSON日志。 containerd和CRI-O使用CRI Log格式,该格式略有不同,...
  • cri-containerd-cni-1.5.5-linux-amd64.tar.gz
  • yum -y install docker-ce,安装高版本docke-ce,报错Problem: package docker-ce-3:19.03.8-3.el7.x86_64 requires containerd.io >= 1.2.2-3, but none of the providers can be installed,意思就是 containerd....
  • containerd.io-1.5.11-3.1.el8.x86_64.rpm
  • ZFS文件系统名称是任意的,但是当容器化的根目录设置为/var/lib/containerd时,挂载点必须为/var/lib/containerd/io.containerd.snapshotter.v1.zfs 。 $ zfs create -o mountpoint=/var/lib/containerd/io....
  • Centos8 安装 Docker 遇到问题的问题: Problem: package docker-ce-3:19.03.13-3.el7.x86_64 requires containerd.io >= 1.2.2-3, but none of the providers can be installed
  • ubuntu使用deb包离线安装docker依赖程序。sudo dpkg -i containerd.io_1.2.6-3_amd64.deb。
  • containerd是行业标准的容器运行时,重点是简单性,健壮性和可移植性。 它可以作为Linux和Windows的守护程序使用,可以管理其主机系统的完整容器生命周期:图像传输和存储,容器执行和监督,低级存储和网络附件等。...
  • Containerd 是一个控制 runC 的守护进程,主要是为了性能和密度。Containerd 提供一个命令行客户端和 API,在一个机器上管理容器。Containerd 使用 runC 来根据 OCI 规范运行容器 。Containerd 利用 runC 的高级特性...
  • containerd.io-1.2.6-3.3.el7.x86_64.rpm离线安装包。
  • 基于containerd部署Kubernetes资源
  • firecracker-containerd此存储库支持使用容器化的容器运行时来管理Firecracker microVM。 与传统容器一样,Firecracker microVM提供快速启动和关闭以及最小的开销。 firecracker-containerd自动化状态测试Lint该存储...
  • 文章目录环境准备安装 Containerd使用 kubeadm 部署 Kubernetes添加节点Dashboard清理 使用 kubeadm 从头搭建一个使用 containerd 作为容器运行时的 Kubernetes 集群,这里我们安装 v1.22.1 版本。 环境准备 节点...


    使用 kubeadm 从头搭建一个使用 containerd 作为容器运行时的 Kubernetes 集群,这里我们安装 v1.22.1 版本。

    环境准备

    在这里插入图片描述
    在这里插入图片描述

    节点的 hostname 必须使用标准的 DNS 命名,另外千万不用什么默认的 localhost 的 hostname,会导致各种错误出现的。在 Kubernetes 项目里,机器的名字以及一切存储在 Etcd 中的 API 对象,都必须使用标准的 DNS 命名(RFC 1123)。可以使用命令 hostnamectl set-hostname node1 来修改 hostname。

    每个节点都做:

    [root@master1 ~]# systemctl disable firewalld --now
    Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.service.
    Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
    [root@master1 ~]# setenforce 0
    [root@master1 ~]# sed -ir "/^SELINUX=/c SELINUX=disabled" /etc/selinux/config
    [root@master1 ~]# modprobe br_netfilter
    
    [root@master1 ~]# vim /etc/sysctl.d/k8s.conf
    net.bridge.bridge-nf-call-ip6tables = 1
    net.bridge.bridge-nf-call-iptables = 1
    net.ipv4.ip_forward = 1
    
    [root@master1 ~]# sysctl -p /etc/sysctl.d/k8s.conf
    net.bridge.bridge-nf-call-ip6tables = 1
    net.bridge.bridge-nf-call-iptables = 1
    net.ipv4.ip_forward = 1
    
    [root@master1 ~]# cat > /etc/sysconfig/modules/ipvs.modules <<EOF
    > #!/bin/bash
    > modprobe -- ip_vs
    > modprobe -- ip_vs_rr
    > modprobe -- ip_vs_wrr
    > modprobe -- ip_vs_sh
    > modprobe -- nf_conntrack_ipv4
    > EOF
    
    
    [root@master1 ~]# chmod 755 /etc/sysconfig/modules/ipvs.modules && bash /etc/sysconfig/modules/ipvs.modules && lsmod | grep -e ip_vs -e nf_conntrack_ipv4
    nf_conntrack_ipv4      15053  0
    nf_defrag_ipv4         12729  1 nf_conntrack_ipv4
    ip_vs_sh               12688  0
    ip_vs_wrr              12697  0
    ip_vs_rr               12600  0
    ip_vs                 145458  6 ip_vs_rr,ip_vs_sh,ip_vs_wrr
    nf_conntrack          139264  2 ip_vs,nf_conntrack_ipv4
    libcrc32c              12644  3 xfs,ip_vs,nf_conntrack
    
    [root@master1 ~]# yum install -y ipset ipvsadm
    [root@master1 ~]# yum install -y chrony
    [root@master1 ~]# systemctl enable chronyd --now
    [root@master1 ~]# chronyc sources
    210 Number of sources = 4
    MS Name/IP address         Stratum Poll Reach LastRx Last sample
    ===============================================================================
    ^- a.chl.la                      2   8   335    99    +37ms[  +37ms] +/-  163ms
    ^* 139.199.214.202               2   9   377   487  +1531us[+1924us] +/-   34ms
    ^+ time.neu.edu.cn               1   9   377   373   +463us[ +463us] +/-   35ms
    ^+ time.neu.edu.cn               1   8   377   102   -710us[ -710us] +/-   36ms
    
    [root@master1 ~]# swapoff -a
    [root@master1 ~]# sed -ir '/ swap / s/^\(.*\)$/#\1/' /etc/fstab
    
    [root@master1 ~]# cat /etc/sysctl.d/k8s.conf
    net.bridge.bridge-nf-call-ip6tables = 1
    net.bridge.bridge-nf-call-iptables = 1
    net.ipv4.ip_forward = 1
    vm.swappiness=0
    
    [root@master1 ~]# sysctl -p /etc/sysctl.d/k8s.conf
    net.bridge.bridge-nf-call-ip6tables = 1
    net.bridge.bridge-nf-call-iptables = 1
    net.ipv4.ip_forward = 1
    vm.swappiness = 0
    

    bridge-nf 使得 netfilter 可以对 Linux 网桥上的 IPv4/ARP/IPv6 包过滤。比如,设置net.bridge.bridge-nf-call-iptables=1后,二层的网桥在转发包时也会被 iptables的 FORWARD 规则所过滤。常用的选项包括:
    net.bridge.bridge-nf-call-arptables:是否在 arptables 的 FORWARD 中过滤网桥的 ARP 包
    net.bridge.bridge-nf-call-ip6tables:是否在 ip6tables 链中过滤 IPv6 包
    net.bridge.bridge-nf-call-iptables:是否在 iptables 链中过滤 IPv4 包
    net.bridge.bridge-nf-filter-vlan-tagged:是否在 iptables/arptables 中过滤打了 vlan 标签的包。

    安装 Containerd

    [root@master1 ~]# rpm -qa | grep libseccomp
    libseccomp-2.3.1-4.el7.x86_64
    [root@master1 ~]# rpm -e libseccomp-2.3.1-4.el7.x86_64 --nodeps
    [root@master1 ~]# wget http://rpmfind.net/linux/centos/8-stream/BaseOS/x86_64/os/Packages/libseccomp-2.5.1-1.el8.x86_64.rpm
    [root@master1 ~]# rpm -ivh libseccomp-2.5.1-1.el8.x86_64.rpm
    
    [root@master1 ~]# wget https://github.com/containerd/containerd/releases/download/v1.5.10/cri-containerd-cni-1.5.10-linux-amd64.tar.gz
    # 如果有限制,也可以替换成下面的 URL 加速下载
    # wget https://download.fastgit.org/containerd/containerd/releases/download/v1.5.10/cri-containerd-cni-1.5.10-linux-amd64.tar.gz
    
    [root@master1 ~]# tar -C / -xzf cri-containerd-cni-1.5.10-linux-amd64.tar.gz
    
    然后要将 /usr/local/bin 和 /usr/local/sbin 追加到 ~/.bashrc 文件的 PATH 环境变量中:
    export PATH=$PATH:/usr/local/bin:/usr/local/sbin
    [root@master1 ~]#source ~/.bashrc
    
    [root@master1 ~]# mkdir -p /etc/containerd
    [root@master1 ~]# containerd config default > /etc/containerd/config.toml
    

    对于使用 systemd 作为 init system 的 Linux 的发行版,使用 systemd 作为容器的 cgroup driver 可以确保节点在资源紧张的情况更加稳定,所以推荐将 containerd 的 cgroup driver 配置为 systemd。

    修改前面生成的配置文件 /etc/containerd/config.toml,在 plugins.“io.containerd.grpc.v1.cri”.containerd.runtimes.runc.options 配置块下面将 SystemdCgroup 设置为 true:

    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
      ...
      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
        SystemdCgroup = true
        ....
    

    然后再为镜像仓库配置一个加速器,需要在 cri 配置块下面的 registry 配置块下面进行配置 registry.mirrors:

    [plugins."io.containerd.grpc.v1.cri"]
      ...
      # sandbox_image = "k8s.gcr.io/pause:3.5"
      sandbox_image = "registry.aliyuncs.com/k8sxio/pause:3.5"
      ...
      [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://xxxxxxxxxx.mirror.aliyuncs.com"]
          [plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]
            endpoint = ["https://registry.aliyuncs.com/k8sxio"]
    

    由于上面我们下载的 containerd 压缩包中包含一个 etc/systemd/system/containerd.service 的文件,这样我们就可以通过 systemd 来配置 containerd 作为守护进程运行了,现在我们就可以启动 containerd 了,直接执行下面的命令即可:

    [root@master1 ~]# systemctl daemon-reload
    [root@master1 ~]# systemctl enable containerd --now
    Created symlink from /etc/systemd/system/multi-user.target.wants/containerd.service to /etc/systemd/system/containerd.service.
    

    启动完成后就可以使用 containerd 的本地 CLI 工具 ctr 和 crictl 了,比如查看版本:

    [root@master1 ~]# ctr version
    Client:
      Version:  v1.5.10
      Revision: 2a1d4dbdb2a1030dc5b01e96fb110a9d9f150ecc
      Go version: go1.16.14
    
    Server:
      Version:  v1.5.10
      Revision: 2a1d4dbdb2a1030dc5b01e96fb110a9d9f150ecc
      UUID: dca19e1e-fbef-4b03-ba22-a51bdaab8740
    [root@master1 ~]# crictl version
    Version:  0.1.0
    RuntimeName:  containerd
    RuntimeVersion:  v1.5.10
    RuntimeApiVersion:  v1alpha2
    

    使用 kubeadm 部署 Kubernetes

    上面的相关环境配置也完成了,现在我们就可以来安装 Kubeadm 了,我们这里是通过指定yum 源的方式来进行安装的:

    ➜  ~ cat <<EOF > /etc/yum.repos.d/kubernetes.repo
    [kubernetes]
    name=Kubernetes
    baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
    enabled=1
    gpgcheck=1
    repo_gpgcheck=1
    gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg
            https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
    EOF
    

    上面的 yum 源是需要科学上网的,如果不能科学上网的话,我们可以使用阿里云的源进行安装:

    cat <<EOF > /etc/yum.repos.d/kubernetes.repo
    [kubernetes]
    name=Kubernetes
    baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
    enabled=1
    gpgcheck=0
    repo_gpgcheck=0
    gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
            http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
    EOF
    

    然后安装 kubeadm、kubelet、kubectl:

    # --disableexcludes 禁掉除了kubernetes之外的别的仓库
    yum makecache fast
    yum install -y kubelet-1.22.1 kubeadm-1.22.1 kubectl-1.22.1 --disableexcludes=kubernetes
    kubeadm version
    kubeadm version: &version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.1", GitCommit:"632ed300f2c34f6d6d15ca4cef3d3c7073412212", GitTreeState:"clean", BuildDate:"2021-08-19T15:44:22Z", GoVersion:"go1.16.7", Compiler:"gc", Platform:"linux/amd64"}
    
    systemctl enable --now kubelet
    

    接下来的操作是master节点进行的操作
    初始化集群¶
    当我们执行 kubelet --help 命令的时候可以看到原来大部分命令行参数都被 DEPRECATED了,这是因为官方推荐我们使用 --config 来指定配置文件,在配置文件中指定原来这些参数的配置,可以通过官方文档 Set Kubelet parameters via a config file 了解更多相关信息,这样 Kubernetes 就可以支持动态 Kubelet 配置(Dynamic Kubelet Configuration)了,参考 Reconfigure a Node’s Kubelet in a Live Cluster。

    然后我们可以通过下面的命令在 master 节点上输出集群初始化默认使用的配置:

    kubeadm config print init-defaults --component-configs KubeletConfiguration > kubeadm.yaml
    

    然后根据我们自己的需求修改配置,比如修改 imageRepository 指定集群初始化时拉取 Kubernetes 所需镜像的地址,kube-proxy 的模式为 ipvs,另外需要注意的是我们这里是准备安装 flannel 网络插件的,需要将 networking.podSubnet 设置为10.244.0.0/16:

    # kubeadm.yaml
    apiVersion: kubeadm.k8s.io/v1beta3
    bootstrapTokens:
    - groups:
      - system:bootstrappers:kubeadm:default-node-token
      token: abcdef.0123456789abcdef
      ttl: 24h0m0s
      usages:
      - signing
      - authentication
    kind: InitConfiguration
    localAPIEndpoint:
      advertiseAddress: 192.168.23.205  # 指定master节点内网IP
      bindPort: 6443
    nodeRegistration:
      criSocket: /run/containerd/containerd.sock  # 使用 containerd的Unix socket 地址
      imagePullPolicy: IfNotPresent
      name: master1   #master节点的主机名
      taints:  # 给master添加污点,master节点不能调度应用
      - effect: "NoSchedule"
        key: "node-role.kubernetes.io/master"
    
    ---
    apiVersion: kubeproxy.config.k8s.io/v1alpha1
    kind: KubeProxyConfiguration
    mode: ipvs  # kube-proxy 模式
    
    ---
    apiServer:
      timeoutForControlPlane: 4m0s
    apiVersion: kubeadm.k8s.io/v1beta3
    certificatesDir: /etc/kubernetes/pki
    clusterName: kubernetes
    controllerManager: {}
    dns: {}
    etcd:
      local:
        dataDir: /var/lib/etcd
    imageRepository: registry.aliyuncs.com/k8sxio
    kind: ClusterConfiguration
    kubernetesVersion: 1.22.1
    networking:
      dnsDomain: cluster.local
      serviceSubnet: 10.96.0.0/12
      podSubnet: 10.244.0.0/16  # 指定 pod 子网
    scheduler: {}
    
    ---
    apiVersion: kubelet.config.k8s.io/v1beta1
    authentication:
      anonymous:
        enabled: false
      webhook:
        cacheTTL: 0s
        enabled: true
      x509:
        clientCAFile: /etc/kubernetes/pki/ca.crt
    authorization:
      mode: Webhook
      webhook:
        cacheAuthorizedTTL: 0s
        cacheUnauthorizedTTL: 0s
    clusterDNS:
    - 10.96.0.10
    clusterDomain: cluster.local
    cpuManagerReconcilePeriod: 0s
    evictionPressureTransitionPeriod: 0s
    fileCheckFrequency: 0s
    healthzBindAddress: 127.0.0.1
    healthzPort: 10248
    httpCheckFrequency: 0s
    imageMinimumGCAge: 0s
    kind: KubeletConfiguration
    cgroupDriver: systemd  # 配置 cgroup driver
    logging: {}
    memorySwap: {}
    nodeStatusReportFrequency: 0s
    nodeStatusUpdateFrequency: 0s
    rotateCertificates: true
    runtimeRequestTimeout: 0s
    shutdownGracePeriod: 0s
    shutdownGracePeriodCriticalPods: 0s
    staticPodPath: /etc/kubernetes/manifests
    streamingConnectionIdleTimeout: 0s
    syncFrequency: 0s
    volumeStatsAggPeriod: 0s
    

    在开始初始化集群之前可以使用kubeadm config images pull --config kubeadm.yaml预先在各个服务器节点上拉取所k8s需要的容器镜像。

    配置文件准备好过后,可以使用如下命令先将相关镜像 pull 下面:

    [root@master1 ~]# kubeadm config images pull --config kubeadm.yaml
    [config/images] Pulled registry.aliyuncs.com/k8sxio/kube-apiserver:v1.22.1
    [config/images] Pulled registry.aliyuncs.com/k8sxio/kube-controller-manager:v1.22.1
    [config/images] Pulled registry.aliyuncs.com/k8sxio/kube-scheduler:v1.22.1
    [config/images] Pulled registry.aliyuncs.com/k8sxio/kube-proxy:v1.22.1
    [config/images] Pulled registry.aliyuncs.com/k8sxio/pause:3.5
    [config/images] Pulled registry.aliyuncs.com/k8sxio/etcd:3.5.0-0
    failed to pull image "registry.aliyuncs.com/k8sxio/coredns:v1.8.4": output: time="2022-05-27T01:21:15-04:00" level=found desc = failed to pull and unpack image \"registry.aliyuncs.com/k8sxio/coredns:v1.8.4\": failed to resolve refer8.4\": registry.aliyuncs.com/k8sxio/coredns:v1.8.4: not found"
    , error: exit status 1
    To see the stack trace of this error execute with --v=5 or higher
    

    拉取 coredns 镜像的时候出错了(其实就是用加速器名称就不对应了),没有找到这个镜像,我们可以手动 pull 该镜像,然后重新 tag 下镜像地址即可:

    [root@master1 ~]# ctr -n k8s.io i pull docker.io/coredns/coredns:1.8.4
    docker.io/coredns/coredns:1.8.4:                                                  resolved       |++++++++++++++++++++++++++++++++++++++|
    index-sha256:6e5a02c21641597998b4be7cb5eb1e7b02c0d8d23cce4dd09f4682d463798890:    done           |++++++++++++++++++++++++++++++++++++++|
    manifest-sha256:10683d82b024a58cc248c468c2632f9d1b260500f7cd9bb8e73f751048d7d6d4: done           |++++++++++++++++++++++++++++++++++++++|
    layer-sha256:bc38a22c706b427217bcbd1a7ac7c8873e75efdd0e59d6b9f069b4b243db4b4b:    done           |++++++++++++++++++++++++++++++++++++++|
    config-sha256:8d147537fb7d1ac8895da4d55a5e53621949981e2e6460976dae812f83d84a44:   done           |++++++++++++++++++++++++++++++++++++++|
    layer-sha256:c6568d217a0023041ef9f729e8836b19f863bcdb612bb3a329ebc165539f5a80:    done           |++++++++++++++++++++++++++++++++++++++|
    elapsed: 17.3s                                                                    total:  13.1 M (773.7 KiB/s)      
    unpacking linux/amd64 sha256:6e5a02c21641597998b4be7cb5eb1e7b02c0d8d23cce4dd09f4682d463798890...
    done: 974.228881ms
    
    [root@master1 ~]# ctr -n k8s.io i tag docker.io/coredns/coredns:1.8.4 registry.aliyuncs.com/k8sxio/coredns:v1.8.4
    registry.aliyuncs.com/k8sxio/coredns:v1.8.4
    

    然后就可以使用上面的配置文件在 master 节点上进行初始化:

    [root@master1 ~]# kubeadm init --config kubeadm.yaml
    [init] Using Kubernetes version: v1.22.1
    [preflight] Running pre-flight checks
    [preflight] Pulling images required for setting up a Kubernetes cluster
    [preflight] This might take a minute or two, depending on the speed of your internet connection
    [preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
    [certs] Using certificateDir folder "/etc/kubernetes/pki"
    [certs] Generating "ca" certificate and key
    [certs] Generating "apiserver" certificate and key
    [certs] apiserver serving cert is signed for DNS names [kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local master1] and IPs [10.96.0.1 192.168.23.205]
    [certs] Generating "apiserver-kubelet-client" certificate and key
    [certs] Generating "front-proxy-ca" certificate and key
    [certs] Generating "front-proxy-client" certificate and key
    [certs] Generating "etcd/ca" certificate and key
    [certs] Generating "etcd/server" certificate and key
    [certs] etcd/server serving cert is signed for DNS names [localhost master1] and IPs [192.168.23.205 127.0.0.1 ::1]
    [certs] Generating "etcd/peer" certificate and key
    [certs] etcd/peer serving cert is signed for DNS names [localhost master1] and IPs [192.168.23.205 127.0.0.1 ::1]
    [certs] Generating "etcd/healthcheck-client" certificate and key
    [certs] Generating "apiserver-etcd-client" certificate and key
    [certs] Generating "sa" key and public key
    [kubeconfig] Using kubeconfig folder "/etc/kubernetes"
    [kubeconfig] Writing "admin.conf" kubeconfig file
    [kubeconfig] Writing "kubelet.conf" kubeconfig file
    [kubeconfig] Writing "controller-manager.conf" kubeconfig file
    [kubeconfig] Writing "scheduler.conf" kubeconfig file
    [kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
    [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
    [kubelet-start] Starting the kubelet
    [control-plane] Using manifest folder "/etc/kubernetes/manifests"
    [control-plane] Creating static Pod manifest for "kube-apiserver"
    [control-plane] Creating static Pod manifest for "kube-controller-manager"
    [control-plane] Creating static Pod manifest for "kube-scheduler"
    [etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
    [wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
    [apiclient] All control plane components are healthy after 21.011508 seconds
    [upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
    [kubelet] Creating a ConfigMap "kubelet-config-1.22" in namespace kube-system with the configuration for the kubelets in the cluster
    [upload-certs] Skipping phase. Please see --upload-certs
    [mark-control-plane] Marking the node master1 as control-plane by adding the labels: [node-role.kubernetes.io/master(deprecated) node-role.kubernetes.io/control-plane node.kubernetes.io/exclude-from-external-load-balancers]
    [mark-control-plane] Marking the node master1 as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
    [bootstrap-token] Using token: abcdef.0123456789abcdef
    [bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
    [bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to get nodes
    [bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
    [bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
    [bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
    [bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
    [kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
    [addons] Applied essential addon: CoreDNS
    [addons] Applied essential addon: kube-proxy
    
    Your Kubernetes control-plane has initialized successfully!
    
    To start using your cluster, you need to run the following as a regular user:
    
      mkdir -p $HOME/.kube
      sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
      sudo chown $(id -u):$(id -g) $HOME/.kube/config
    
    Alternatively, if you are the root user, you can run:
    
      export KUBECONFIG=/etc/kubernetes/admin.conf
    
    You should now deploy a pod network to the cluster.
    Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
      https://kubernetes.io/docs/concepts/cluster-administration/addons/
    
    Then you can join any number of worker nodes by running the following on each as root:
    
    kubeadm join 192.168.23.205:6443 --token abcdef.0123456789abcdef \
            --discovery-token-ca-cert-hash sha256:e35247f442d800b821d8dcaf041c6095f528824b2a5b3a4115ce6598b36f7d09
    
    

    根据安装提示拷贝 kubeconfig 文件:

    mkdir -p $HOME/.kube
    sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
    sudo chown $(id -u):$(id -g) $HOME/.kube/config
    

    然后可以使用 kubectl 命令查看 master 节点已经初始化成功了:

    [root@master1 ~]# kubectl get node
    NAME      STATUS   ROLES                  AGE    VERSION
    master1   Ready    control-plane,master   6m2s   v1.22.1
    

    添加节点

    记住初始化集群上面的配置和操作要提前做好,将 master 节点上面的 $HOME/.kube/config 文件拷贝到 node 节点对应的文件中,安装 kubeadm、kubelet、kubectl(可选),然后执行上面初始化完成后提示的 join 命令即可:

    [root@node1 containerd]# kubeadm join 192.168.23.205:6443 --token abcdef.0123456789abcdef \
    >         --discovery-token-ca-cert-hash sha256:e35247f442d800b821d8dcaf041c6095f528824b2a5b3a4115ce6598b36f7d09
    [preflight] Running pre-flight checks
    [preflight] Reading configuration from the cluster...
    [preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
    [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
    [kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
    [kubelet-start] Starting the kubelet
    [kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...
    
    This node has joined the cluster:
    * Certificate signing request was sent to apiserver and a response was received.
    * The Kubelet was informed of the new secure connection details.
    
    Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
    

    如果忘记了上面的 join 命令可以使用命令 kubeadm token create --print-join-command 重新获取。

    安装网络插件,接下来安装网络插件,可以在文档 https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/ 中选择我们自己的网络插件,这里我们安装 flannel:

    ➜  ~ wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
    # 如果有节点是多网卡,则需要在资源清单文件中指定内网网卡
    # 搜索到名为 kube-flannel-ds 的 DaemonSet,在kube-flannel容器下面
    ➜  ~ vi kube-flannel.yml
    ......
    containers:
    - name: kube-flannel
      image: quay.io/coreos/flannel:v0.14.0   #相关的image换成这个,cni-plugin那个的插件可以不用换,rancher的镜像在这里使用有点问题,如果rancher的cni-plugin也有问题,可以到github或docker上找替代的镜像
      command:
      - /opt/bin/flanneld
      args:
      - --ip-masq
      - --kube-subnet-mgr
      - --iface=ens33  # 如果是多网卡的话,指定内网网卡的名称
    ......
    ➜  ~ kubectl apply -f kube-flannel.yml  # 安装 flannel 网络插件
    

    如果有多网卡网络,flannel最好是指定通信网卡,越精确越好,否则不指定它则使用默认路由的网卡通信。
    (可以指定多张网卡名称,比如新节点不是ens33,还有就是进去该节点的pod容器中手动修改网卡名称)

    当我们部署完网络插件后执行 ifconfig 命令,正常会看到新增的cni0与flannel1这两个虚拟设备,但是如果没有看到cni0这个设备也不用太担心,我们可以观察/var/lib/cni目录是否存在,如果不存在并不是说部署有问题,而是该节点上暂时还没有应用运行,我们只需要在该节点上运行一个 Pod 就可以看到该目录会被创建,并且cni0设备也会被创建出来。

    [root@master1 ~]# kubectl get pod -n kube-system
    NAME                              READY   STATUS     RESTARTS   AGE
    coredns-7568f67dbd-dd2dp          1/1     Running    0          15m
    coredns-7568f67dbd-rzfs7          1/1     Running    0          15m
    etcd-master1                      1/1     Running    0          15m
    kube-apiserver-master1            1/1     Running    0          15m
    kube-controller-manager-master1   1/1     Running    0          15m
    kube-flannel-ds-259dw             0/1     Init:1/2   0          2m55s
    kube-flannel-ds-bmctc             0/1     Init:1/2   0          2m55s
    kube-flannel-ds-r4nvw             0/1     Init:1/2   0          2m55s
    kube-proxy-cj7zq                  1/1     Running    0          8m6s
    kube-proxy-cv9tx                  1/1     Running    0          8m14s
    kube-proxy-kn6dk                  1/1     Running    0          15m
    kube-scheduler-master1            1/1     Running    0          15m
    
    
    

    Dashboard

    v1.22.1 版本的集群需要安装最新的 2.0+ 版本的 Dashboard:

    # 推荐使用下面这种方式
    ➜  ~ wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.3.1/aio/deploy/recommended.yaml
    ➜  ~ vi recommended.yaml
    # 修改Service为NodePort类型
    ......
    kind: Service
    apiVersion: v1
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
      name: kubernetes-dashboard
      namespace: kubernetes-dashboard
    spec:
      ports:
        - port: 443
          targetPort: 8443
      selector:
        k8s-app: kubernetes-dashboard
      type: NodePort  # 加上type=NodePort变成NodePort类型的服务
    ......
    

    在 YAML 文件中可以看到新版本 Dashboard 集成了一个 metrics-scraper 的组件,可以通过 Kubernetes 的 Metrics API 收集一些基础资源的监控信息,并在 web 页面上展示,所以要想在页面上展示监控信息就需要提供 Metrics API,比如安装 Metrics Server。

    kubectl apply -f recommended.yaml
    
    kubectl get pods -n kubernetes-dashboard -o wide
    NAME                                         READY   STATUS    RESTARTS   AGE   IP          NODE     NOMINATED NODE   READINESS GATES
    dashboard-metrics-scraper-856586f554-pllvt   1/1     Running   0          24m   10.88.0.9   master1   <none>           <none>
    kubernetes-dashboard-76597d7        1/1     Running   0          21m   10.88.0.2   node2    <none>           <none>
    

    上面的 Pod 分配的 IP 段是 10.88.xx.xx,包括前面自动安装的 CoreDNS 也是如此,我们前面配置的 podSubnet 为 10.244.0.0/16

    ls -la /etc/cni/net.d/
    total 8
    drwxr-xr-x  2 1001 docker  67 Aug 31 16:45 .
    drwxr-xr-x. 3 1001 docker  19 Jul 30 01:13 ..
    -rw-r--r--  1 1001 docker 604 Jul 30 01:13 10-containerd-net.conflist
    -rw-r--r--  1 root root   292 Aug 31 16:45 10-flannel.conflist
    

    一个是 10-containerd-net.conflist,另外一个是我们上面创建的 Flannel 网络插件生成的配置,我们的需求肯定是想使用 Flannel 的这个配置,我们可以查看下 containerd 这个自带的 cni 插件配置:

    ➜  ~ cat /etc/cni/net.d/10-containerd-net.conflist
    {
      "cniVersion": "0.4.0",
      "name": "containerd-net",
      "plugins": [
        {
          "type": "bridge",
          "bridge": "cni0",
          "isGateway": true,
          "ipMasq": true,
          "promiscMode": true,
          "ipam": {
            "type": "host-local",
            "ranges": [
              [{
                "subnet": "10.88.0.0/16"
              }],
              [{
                "subnet": "2001:4860:4860::/64"
              }]
            ],
            "routes": [
              { "dst": "0.0.0.0/0" },
              { "dst": "::/0" }
            ]
          }
        },
        {
          "type": "portmap",
          "capabilities": {"portMappings": true}
        }
      ]
    }
    

    可以看到上面的 IP 段恰好就是 10.88.0.0/16,但是这个 cni 插件类型是 bridge 网络,网桥的名称为 cni0:

    ip a
    ...
    6: cni0: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
        link/ether 9a:e7:eb:40:e8:66 brd ff:ff:ff:ff:ff:ff
        inet 10.88.0.1/16 brd 10.88.255.255 scope global cni0
           valid_lft forever preferred_lft forever
        inet6 2001:4860:4860::1/64 scope global
           valid_lft forever preferred_lft forever
        inet6 fe80::98e7:ebff:fe40:e866/64 scope link
           valid_lft forever preferred_lft forever
    ...
    

    但是使用 bridge 网络的容器无法跨多个宿主机进行通信,跨主机通信需要借助其他的 cni 插件,比如上面我们安装的 Flannel,或者 Calico 等等,由于我们这里有两个 cni 配置,所以我们需要将 10-containerd-net.conflist 这个配置删除,因为如果这个目录中有多个 cni 配置文件,kubelet 将会使用按文件名的字典顺序排列的第一个作为配置文件,所以前面默认选择使用的是 containerd-net 这个插件。

    mv /etc/cni/net.d/10-containerd-net.conflist /etc/cni/net.d/10-containerd-net.conflist.bak
    ifconfig cni0 down && ip link delete cni0
    systemctl daemon-reload
    systemctl restart containerd kubelet
    

    重建 coredns 和 dashboard 的 Pod,重建后 Pod 的 IP 地址就正常了,变回我们设置的10.244.0.0网段

    ➜  ~ kubectl get svc -n kubernetes-dashboard
    NAME                        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)         AGE
    dashboard-metrics-scraper   ClusterIP   10.99.37.172    <none>        8000/TCP        25m
    kubernetes-dashboard        NodePort    10.103.102.27   <none>        443:31050/TCP   25m
    

    然后可以通过上面的 31050 端口去访问 Dashboard,要记住使用 https,Chrome 不生效可以使用Firefox 测试,点击页面中的信任证书即可:

    然后创建一个具有全局所有权限的用户来登录 Dashboard:(admin.yaml)

    直接使用cluster-admin

    kind: ClusterRoleBinding
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: admin
    roleRef:
      kind: ClusterRole
      name: cluster-admin
      apiGroup: rbac.authorization.k8s.io
    subjects:
    - kind: ServiceAccount
      name: admin
      namespace: kubernetes-dashboard
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: admin
      namespace: kubernetes-dashboard
    
    kubectl apply -f admin.yaml
    kubectl get secret -n kubernetes-dashboard|grep admin-token
    admin-token-lwmmx                  kubernetes.io/service-account-token   3         1d
    kubectl get secret admin-token-lwmmx -o jsonpath={.data.token} -n kubernetes-dashboard |base64 -d
    # 会生成一串很长的base64后的字符串
    

    然后用上面的 base64 解码后的字符串作为 token 登录 Dashboard 即可
    在这里插入图片描述

    清理

    如果你的集群安装过程中遇到了其他问题,我们可以使用下面的命令来进行重置:

    kubeadm reset
    ifconfig cni0 down && ip link delete cni0
    ifconfig flannel.1 down && ip link delete flannel.1
    rm -rf /var/lib/cni/
    
    展开全文
  • containerd1.5.5的安装

    2022-06-22 17:38:17
    containerd 1.5.5的安装

    在学习 Containerd 之前我们有必要对 Docker 的发展历史做一个简单的回顾,因为这里面牵涉到的组件实战是有点多,有很多我们会经常听到,但是不清楚这些组件到底是干什么用的,比如 libcontainer
    runc
    containerd
    CRI
    OCI
     等等。

    Docker

    从 Docker 1.11 版本开始,Docker 容器运行就不是简单通过 Docker Daemon 来启动了,而是通过集成 containerd、runc 等多个组件来完成的。虽然 Docker Daemon 守护进程模块在不停的重构,但是基本功能和定位没有太大的变化,一直都是 CS 架构,守护进程负责和 Docker Client 端交互,并管理 Docker 镜像和容器。现在的架构中组件 containerd 就会负责集群节点上容器的生命周期管理,并向上为 Docker Daemon 提供 gRPC 接口。

    docker 架构

    当我们要创建一个容器的时候,现在 Docker Daemon 并不能直接帮我们创建了,而是请求 containerd
     来创建一个容器,containerd 收到请求后,也并不会直接去操作容器,而是创建一个叫做 containerd-shim
     的进程,让这个进程去操作容器,我们指定容器进程是需要一个父进程来做状态收集、维持 stdin 等 fd 打开等工作的,假如这个父进程就是 containerd,那如果 containerd 挂掉的话,整个宿主机上所有的容器都得退出了,而引入 containerd-shim
     这个垫片就可以来规避这个问题了。

    然后创建容器需要做一些 namespaces 和 cgroups 的配置,以及挂载 root 文件系统等操作,这些操作其实已经有了标准的规范,那就是 OCI(开放容器标准),runc
     就是它的一个参考实现(Docker 被逼无耐将 libcontainer
     捐献出来改名为 runc
     的),这个标准其实就是一个文档,主要规定了容器镜像的结构、以及容器需要接收哪些操作指令,比如 create、start、stop、delete 等这些命令。runc
     就可以按照这个 OCI 文档来创建一个符合规范的容器,既然是标准肯定就有其他 OCI 实现,比如 Kata、gVisor 这些容器运行时都是符合 OCI 标准的。

    所以真正启动容器是通过 containerd-shim
     去调用 runc
     来启动容器的,runc
     启动完容器后本身会直接退出,containerd-shim
     则会成为容器进程的父进程, 负责收集容器进程的状态, 上报给 containerd, 并在容器中 pid 为 1 的进程退出后接管容器中的子进程进行清理, 确保不会出现僵尸进程。

    而 Docker 将容器操作都迁移到 containerd
     中去是因为当前做 Swarm,想要进军 PaaS 市场,做了这个架构切分,让 Docker Daemon 专门去负责上层的封装编排,当然后面的结果我们知道 Swarm 在 Kubernetes 面前是惨败,然后 Docker 公司就把 containerd
     项目捐献给了 CNCF 基金会,这个也是现在的 Docker 架构。

    CRI

    我们知道 Kubernetes 提供了一个 CRI 的容器运行时接口,那么这个 CRI 到底是什么呢?这个其实也和 Docker 的发展密切相关的。

    在 Kubernetes 早期的时候,当时 Docker 实在是太火了,Kubernetes 当然会先选择支持 Docker,而且是通过硬编码的方式直接调用 Docker API,后面随着 Docker 的不断发展以及 Google 的主导,出现了更多容器运行时,Kubernetes 为了支持更多更精简的容器运行时,Google 就和红帽主导推出了 CRI 标准,用于将 Kubernetes 平台和特定的容器运行时(当然主要是为了干掉 Docker)解耦。

    CRI
    (Container Runtime Interface 容器运行时接口)本质上就是 Kubernetes 定义的一组与容器运行时进行交互的接口,所以只要实现了这套接口的容器运行时都可以对接到 Kubernetes 平台上来。不过 Kubernetes 推出 CRI 这套标准的时候还没有现在的统治地位,所以有一些容器运行时可能不会自身就去实现 CRI 接口,于是就有了 shim(垫片)
    , 一个 shim 的职责就是作为适配器将各种容器运行时本身的接口适配到 Kubernetes 的 CRI 接口上,其中 dockershim
     就是 Kubernetes 对接 Docker 到 CRI 接口上的一个垫片实现。

    cri shim

    Kubelet 通过 gRPC 框架与容器运行时或 shim 进行通信,其中 kubelet 作为客户端,CRI shim(也可能是容器运行时本身)作为服务器。

    CRI 定义的 API(https://github.com/kubernetes/kubernetes/blob/release-1.5/pkg/kubelet/api/v1alpha1/runtime/api.proto) 主要包括两个 gRPC 服务,ImageService
     和 RuntimeService
    ImageService
     服务主要是拉取镜像、查看和删除镜像等操作,RuntimeService
     则是用来管理 Pod 和容器的生命周期,以及与容器交互的调用(exec/attach/port-forward)等操作,可以通过 kubelet 中的标志 --container-runtime-endpoint
     和 --image-service-endpoint
     来配置这两个服务的套接字。

    kubelet cri

    不过这里同样也有一个例外,那就是 Docker,由于 Docker 当时的江湖地位很高,Kubernetes 是直接内置了 dockershim
     在 kubelet 中的,所以如果你使用的是 Docker 这种容器运行时的话是不需要单独去安装配置适配器之类的,当然这个举动似乎也麻痹了 Docker 公司。

    dockershim

    现在如果我们使用的是 Docker 的话,当我们在 Kubernetes 中创建一个 Pod 的时候,首先就是 kubelet 通过 CRI 接口调用 dockershim
    ,请求创建一个容器,kubelet 可以视作一个简单的 CRI Client, 而 dockershim 就是接收请求的 Server,不过他们都是在 kubelet 内置的。

    dockershim
     收到请求后, 转化成 Docker Daemon 能识别的请求, 发到 Docker Daemon 上请求创建一个容器,请求到了 Docker Daemon 后续就是 Docker 创建容器的流程了,去调用 containerd
    ,然后创建 containerd-shim
     进程,通过该进程去调用 runc
     去真正创建容器。

    其实我们仔细观察也不难发现使用 Docker 的话其实是调用链比较长的,真正容器相关的操作其实 containerd 就完全足够了,Docker 太过于复杂笨重了,当然 Docker 深受欢迎的很大一个原因就是提供了很多对用户操作比较友好的功能,但是对于 Kubernetes 来说压根不需要这些功能,因为都是通过接口去操作容器的,所以自然也就可以将容器运行时切换到 containerd 来。

    切换到containerd

    切换到 containerd 可以消除掉中间环节,操作体验也和以前一样,但是由于直接用容器运行时调度容器,所以它们对 Docker 来说是不可见的。因此,你以前用来检查这些容器的 Docker 工具就不能使用了。

    你不能再使用 docker ps
     或 docker inspect
     命令来获取容器信息。由于不能列出容器,因此也不能获取日志、停止容器,甚至不能通过 docker exec
     在容器中执行命令。

    当然我们仍然可以下载镜像,或者用 docker build
     命令构建镜像,但用 Docker 构建、下载的镜像,对于容器运行时和 Kubernetes,均不可见。为了在 Kubernetes 中使用,需要把镜像推送到镜像仓库中去。

    从上图可以看出在 containerd 1.0 中,对 CRI 的适配是通过一个单独的 CRI-Containerd
     进程来完成的,这是因为最开始 containerd 还会去适配其他的系统(比如 swarm),所以没有直接实现 CRI,所以这个对接工作就交给 CRI-Containerd
     这个 shim 了。

    然后到了 containerd 1.1 版本后就去掉了 CRI-Containerd
     这个 shim,直接把适配逻辑作为插件的方式集成到了 containerd 主进程中,现在这样的调用就更加简洁了。

    containerd cri

    与此同时 Kubernetes 社区也做了一个专门用于 Kubernetes 的 CRI 运行时 CRI-O,直接兼容 CRI 和 OCI 规范。

    cri-o

    这个方案和 containerd 的方案显然比默认的 dockershim 简洁很多,不过由于大部分用户都比较习惯使用 Docker,所以大家还是更喜欢使用 dockershim
     方案。

    但是随着 CRI 方案的发展,以及其他容器运行时对 CRI 的支持越来越完善,Kubernetes 社区在2020年7月份就开始着手移除 dockershim 方案了:https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2221-remove-dockershim,现在的移除计划是在 1.20 版本中将 kubelet 中内置的 dockershim 代码分离,将内置的 dockershim 标记为维护模式
    ,当然这个时候仍然还可以使用 dockershim,目标是在 1.23/1.24 版本发布没有 dockershim 的版本(代码还在,但是要默认支持开箱即用的 docker 需要自己构建 kubelet,会在某个宽限期过后从 kubelet 中删除内置的 dockershim 代码)。

    那么这是否就意味着 Kubernetes 不再支持 Docker 了呢?当然不是的,这只是废弃了内置的 dockershim
     功能而已,Docker 和其他容器运行时将一视同仁,不会单独对待内置支持,如果我们还想直接使用 Docker 这种容器运行时应该怎么办呢?可以将 dockershim 的功能单独提取出来独立维护一个 cri-dockerd
     即可,就类似于 containerd 1.0 版本中提供的 CRI-Containerd
    ,当然还有一种办法就是 Docker 官方社区将 CRI 接口内置到 Dockerd 中去实现。

    但是我们也清楚 Dockerd 也是去直接调用的 Containerd,而 containerd 1.1 版本后就内置实现了 CRI,所以 Docker 也没必要再去单独实现 CRI 了,当 Kubernetes 不再内置支持开箱即用的 Docker 的以后,最好的方式当然也就是直接使用 Containerd 这种容器运行时,而且该容器运行时也已经经过了生产环境实践的,接下来我们就来学习下 Containerd 的使用。

    Containerd

    我们知道很早之前的 Docker Engine 中就有了 containerd,只不过现在是将 containerd 从 Docker Engine 里分离出来,作为一个独立的开源项目,目标是提供一个更加开放、稳定的容器运行基础设施。分离出来的 containerd 将具有更多的功能,涵盖整个容器运行时管理的所有需求,提供更强大的支持。

    containerd 是一个工业级标准的容器运行时,它强调简单性健壮性可移植性,containerd 可以负责干下面这些事情:

    • 管理容器的生命周期(从创建容器到销毁容器)

    • 拉取/推送容器镜像

    • 存储管理(管理镜像及容器数据的存储)

    • 调用 runc 运行容器(与 runc 等容器运行时交互)

    • 管理容器网络接口及网络

    架构

    containerd 可用作 Linux 和 Windows 的守护程序,它管理其主机系统完整的容器生命周期,从镜像传输和存储到容器执行和监测,再到底层存储到网络附件等等。

    containerd 架构

    上图是 containerd 官方提供的架构图,可以看出 containerd 采用的也是 C/S 架构,服务端通过 unix domain socket 暴露低层的 gRPC API 接口出去,客户端通过这些 API 管理节点上的容器,每个 containerd 只负责一台机器,Pull 镜像,对容器的操作(启动、停止等),网络,存储都是由 containerd 完成。具体运行容器由 runc 负责,实际上只要是符合 OCI 规范的容器都可以支持。

    点击上方图片,打开小程序,『美团外卖』红包天天免费领!

    为了解耦,containerd 将系统划分成了不同的组件,每个组件都由一个或多个模块协作完成(Core 部分),每一种类型的模块都以插件的形式集成到 Containerd 中,而且插件之间是相互依赖的,例如,上图中的每一个长虚线的方框都表示一种类型的插件,包括 Service Plugin、Metadata Plugin、GC Plugin、Runtime Plugin 等,其中 Service Plugin 又会依赖 Metadata Plugin、GC Plugin 和 Runtime Plugin。每一个小方框都表示一个细分的插件,例如 Metadata Plugin 依赖 Containers Plugin、Content Plugin 等。比如:

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

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

    总体来看 containerd 可以分为三个大块:Storage、Metadata 和 Runtime。

    containerd 架构2

    安装

    这里我使用的系统是 Linux Mint 20.2
    ,首先需要安装 seccomp
     依赖:

    ➜  ~ apt-get update
    ➜  ~ apt-get install libseccomp2 -y
    
    

    由于 containerd 需要调用 runc,所以我们也需要先安装 runc,不过 containerd 提供了一个包含相关依赖的压缩包 cri-containerd-cni-${VERSION}.${OS}-${ARCH}.tar.gz
    ,可以直接使用这个包来进行安装。首先从 release 页面下载最新版本的压缩包,当前为 1.5.5 版本:

    ➜  ~ wget https://github.com/containerd/containerd/releases/download/v1.5.5/cri-containerd-cni-1.5.5-linux-amd64.tar.gz
    # 如果有限制,也可以替换成下面的 URL 加速下载
    # wget https://download.fastgit.org/containerd/containerd/releases/download/v1.5.5/cri-containerd-cni-1.5.5-linux-amd64.tar.gz
    
    

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

    ➜  ~ tar -tf cri-containerd-cni-1.4.3-linux-amd64.tar.gz
    etc/
    etc/cni/
    etc/cni/net.d/
    etc/cni/net.d/10-containerd-net.conflist
    etc/crictl.yaml
    etc/systemd/
    etc/systemd/system/
    etc/systemd/system/containerd.service
    usr/
    usr/local/
    usr/local/bin/
    usr/local/bin/containerd-shim-runc-v2
    usr/local/bin/ctr
    usr/local/bin/containerd-shim
    usr/local/bin/containerd-shim-runc-v1
    usr/local/bin/crictl
    usr/local/bin/critest
    usr/local/bin/containerd
    usr/local/sbin/
    usr/local/sbin/runc
    opt/
    opt/cni/
    opt/cni/bin/
    opt/cni/bin/vlan
    opt/cni/bin/host-local
    opt/cni/bin/flannel
    opt/cni/bin/bridge
    opt/cni/bin/host-device
    opt/cni/bin/tuning
    opt/cni/bin/firewall
    opt/cni/bin/bandwidth
    opt/cni/bin/ipvlan
    opt/cni/bin/sbr
    opt/cni/bin/dhcp
    opt/cni/bin/portmap
    opt/cni/bin/ptp
    opt/cni/bin/static
    opt/cni/bin/macvlan
    opt/cni/bin/loopback
    opt/containerd/
    opt/containerd/cluster/
    opt/containerd/cluster/version
    opt/containerd/cluster/gce/
    opt/containerd/cluster/gce/cni.template
    opt/containerd/cluster/gce/configure.sh
    opt/containerd/cluster/gce/cloud-init/
    opt/containerd/cluster/gce/cloud-init/master.yaml
    opt/containerd/cluster/gce/cloud-init/node.yaml
    opt/containerd/cluster/gce/env
    
    

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

    ➜  ~ tar -C / -xzf cri-containerd-cni-1.5.5-linux-amd64.tar.gz
    
    

    当然要记得将 /usr/local/bin
     和 /usr/local/sbin
     追加到 ~/.bashrc
     文件的 PATH
     环境变量中:

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

    然后执行下面的命令使其立即生效:

    ➜  ~ source ~/.bashrc
    
    

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

    ➜  ~ mkdir /etc/containerd
    ➜  ~ containerd config default > /etc/containerd/config.toml
    
    

    由于上面我们下载的 containerd 压缩包中包含一个 etc/systemd/system/containerd.service
     的文件,这样我们就可以通过 systemd 来配置 containerd 作为守护进程运行了,内容如下所示:

    ➜  ~ cat /etc/systemd/system/containerd.service
    [Unit]
    Description=containerd container runtime
    Documentation=https://containerd.io
    After=network.target local-fs.target
    
    [Service]
    ExecStartPre=-/sbin/modprobe overlay
    ExecStart=/usr/local/bin/containerd
    
    Type=notify
    Delegate=yes
    KillMode=process
    Restart=always
    RestartSec=5
    # Having non-zero Limit*s causes performance problems due to accounting overhead
    # in the kernel. We recommend using cgroups to do container-local accounting.
    LimitNPROC=infinity
    LimitCORE=infinity
    LimitNOFILE=1048576
    # Comment TasksMax if your systemd version does not supports it.
    # Only systemd 226 and above support this version.
    TasksMax=infinity
    OOMScoreAdjust=-999
    
    [Install]
    WantedBy=multi-user.target
    
    

    这里有两个重要的参数:

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

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

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

      • process
        :只杀主进程

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

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

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

    现在我们就可以启动 containerd 了,直接执行下面的命令即可:

    ➜  ~ systemctl enable containerd --now
    
    

    启动完成后就可以使用 containerd 的本地 CLI 工具 ctr
     了,比如查看版本:

    配置

    我们首先来查看下上面默认生成的配置文件 /etc/containerd/config.toml

    disabled_plugins = []
    imports = []
    oom_score = 0
    plugin_dir = ""
    required_plugins = []
    root = "/var/lib/containerd"
    state = "/run/containerd"
    version = 2
    
    [cgroup]
      path = ""
    
    [debug]
      address = ""
      format = ""
      gid = 0
      level = ""
      uid = 0
    
    [grpc]
      address = "/run/containerd/containerd.sock"
      gid = 0
      max_recv_message_size = 16777216
      max_send_message_size = 16777216
      tcp_address = ""
      tcp_tls_cert = ""
      tcp_tls_key = ""
      uid = 0
    
    [metrics]
      address = ""
      grpc_histogram = false
    
    [plugins]
    
      [plugins."io.containerd.gc.v1.scheduler"]
        deletion_threshold = 0
        mutation_threshold = 100
        pause_threshold = 0.02
        schedule_delay = "0s"
        startup_delay = "100ms"
    
      [plugins."io.containerd.grpc.v1.cri"]
        disable_apparmor = false
        disable_cgroup = false
        disable_hugetlb_controller = true
        disable_proc_mount = false
        disable_tcp_service = true
        enable_selinux = false
        enable_tls_streaming = false
        ignore_image_defined_volumes = false
        max_concurrent_downloads = 3
        max_container_log_line_size = 16384
        netns_mounts_under_state_dir = false
        restrict_oom_score_adj = false
        sandbox_image = "k8s.gcr.io/pause:3.5"
        selinux_category_range = 1024
        stats_collect_period = 10
        stream_idle_timeout = "4h0m0s"
        stream_server_address = "127.0.0.1"
        stream_server_port = "0"
        systemd_cgroup = false
        tolerate_missing_hugetlb_controller = true
        unset_seccomp_profile = ""
    
        [plugins."io.containerd.grpc.v1.cri".cni]
          bin_dir = "/opt/cni/bin"
          conf_dir = "/etc/cni/net.d"
          conf_template = ""
          max_conf_num = 1
    
        [plugins."io.containerd.grpc.v1.cri".containerd]
          default_runtime_name = "runc"
          disable_snapshot_annotations = true
          discard_unpacked_layers = false
          no_pivot = false
          snapshotter = "overlayfs"
    
          [plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]
            base_runtime_spec = ""
            container_annotations = []
            pod_annotations = []
            privileged_without_host_devices = false
            runtime_engine = ""
            runtime_root = ""
            runtime_type = ""
    
            [plugins."io.containerd.grpc.v1.cri".containerd.default_runtime.options]
    
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
    
            [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
              base_runtime_spec = ""
              container_annotations = []
              pod_annotations = []
              privileged_without_host_devices = false
              runtime_engine = ""
              runtime_root = ""
              runtime_type = "io.containerd.runc.v2"
    
              [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
                BinaryName = ""
                CriuImagePath = ""
                CriuPath = ""
                CriuWorkPath = ""
                IoGid = 0
                IoUid = 0
                NoNewKeyring = false
                NoPivotRoot = false
                Root = ""
                ShimCgroup = ""
                SystemdCgroup = false
    
          [plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime]
            base_runtime_spec = ""
            container_annotations = []
            pod_annotations = []
            privileged_without_host_devices = false
            runtime_engine = ""
            runtime_root = ""
            runtime_type = ""
    
            [plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime.options]
    
        [plugins."io.containerd.grpc.v1.cri".image_decryption]
          key_model = "node"
    
        [plugins."io.containerd.grpc.v1.cri".registry]
          config_path = ""
    
          [plugins."io.containerd.grpc.v1.cri".registry.auths]
    
          [plugins."io.containerd.grpc.v1.cri".registry.configs]
    
          [plugins."io.containerd.grpc.v1.cri".registry.headers]
    
          [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
    
        [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"]
        no_shim = false
        runtime = "runc"
        runtime_root = ""
        shim = "containerd-shim"
        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.aufs"]
        root_path = ""
    
      [plugins."io.containerd.snapshotter.v1.btrfs"]
        root_path = ""
    
      [plugins."io.containerd.snapshotter.v1.devmapper"]
        async_remove = false
        base_image_size = ""
        pool_name = ""
        root_path = ""
    
      [plugins."io.containerd.snapshotter.v1.native"]
        root_path = ""
    
      [plugins."io.containerd.snapshotter.v1.overlayfs"]
        root_path = ""
    
      [plugins."io.containerd.snapshotter.v1.zfs"]
        root_path = ""
    
    [proxy_plugins]
    
    [stream_processors]
    
      [stream_processors."io.containerd.ocicrypt.decoder.v1.tar"]
        accepts = ["application/vnd.oci.image.layer.v1.tar+encrypted"]
        args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"]
        env = ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"]
        path = "ctd-decoder"
        returns = "application/vnd.oci.image.layer.v1.tar"
    
      [stream_processors."io.containerd.ocicrypt.decoder.v1.tar.gzip"]
        accepts = ["application/vnd.oci.image.layer.v1.tar+gzip+encrypted"]
        args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"]
        env = ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"]
        path = "ctd-decoder"
        returns = "application/vnd.oci.image.layer.v1.tar+gzip"
    
    [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"
    
    [ttrpc]
      address = ""
      gid = 0
      uid = 0
    
    

    这个配置文件比较复杂,我们可以将重点放在其中的 plugins
     配置上面,仔细观察我们可以发现每一个顶级配置块的命名都是 plugins."io.containerd.xxx.vx.xxx"
     这种形式,每一个顶级配置块都表示一个插件,其中 io.containerd.xxx.vx
     表示插件的类型,vx
     后面的 xxx
     表示插件的 ID,我们可以通过 ctr
     查看插件列表:

    ➜  ~ ctr plugin ls
    ctr plugin ls
    TYPE                            ID                       PLATFORMS      STATUS
    io.containerd.content.v1        content                  -              ok
    io.containerd.snapshotter.v1    aufs                     linux/amd64    ok
    io.containerd.snapshotter.v1    btrfs                    linux/amd64    skip
    io.containerd.snapshotter.v1    devmapper                linux/amd64    error
    io.containerd.snapshotter.v1    native                   linux/amd64    ok
    io.containerd.snapshotter.v1    overlayfs                linux/amd64    ok
    io.containerd.snapshotter.v1    zfs                      linux/amd64    skip
    io.containerd.metadata.v1       bolt                     -              ok
    io.containerd.differ.v1         walking                  linux/amd64    ok
    io.containerd.gc.v1             scheduler                -              ok
    io.containerd.service.v1        introspection-service    -              ok
    io.containerd.service.v1        containers-service       -              ok
    io.containerd.service.v1        content-service          -              ok
    io.containerd.service.v1        diff-service             -              ok
    io.containerd.service.v1        images-service           -              ok
    io.containerd.service.v1        leases-service           -              ok
    io.containerd.service.v1        namespaces-service       -              ok
    io.containerd.service.v1        snapshots-service        -              ok
    io.containerd.runtime.v1        linux                    linux/amd64    ok
    io.containerd.runtime.v2        task                     linux/amd64    ok
    io.containerd.monitor.v1        cgroups                  linux/amd64    ok
    io.containerd.service.v1        tasks-service            -              ok
    io.containerd.internal.v1       restart                  -              ok
    io.containerd.grpc.v1           containers               -              ok
    io.containerd.grpc.v1           content                  -              ok
    io.containerd.grpc.v1           diff                     -              ok
    io.containerd.grpc.v1           events                   -              ok
    io.containerd.grpc.v1           healthcheck              -              ok
    io.containerd.grpc.v1           images                   -              ok
    io.containerd.grpc.v1           leases                   -              ok
    io.containerd.grpc.v1           namespaces               -              ok
    io.containerd.internal.v1       opt                      -              ok
    io.containerd.grpc.v1           snapshots                -              ok
    io.containerd.grpc.v1           tasks                    -              ok
    io.containerd.grpc.v1           version                  -              ok
    io.containerd.grpc.v1           cri                      linux/amd64    ok
    
    

    顶级配置块下面的子配置块表示该插件的各种配置,比如 cri 插件下面就分为 containerd、cni 和 registry 的配置,而 containerd 下面又可以配置各种 runtime,还可以配置默认的 runtime。比如现在我们要为镜像配置一个加速器,那么就需要在 cri 配置块下面的 registry
     配置块下面进行配置 registry.mirrors

    [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://bqr1dr1n.mirror.aliyuncs.com"]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]
          endpoint = ["https://registry.aliyuncs.com/k8sxio"]
    
    

    或者参考如下配置

    [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
            [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
              endpoint = [
                "https://registry-1.docker.io",
                "https://xlx9erfu.mirror.aliyuncs.com",
                "https://kfwkfulq.mirror.aliyuncs.com",
                "https://2lqq34jg.mirror.aliyuncs.com",
                "https://pee6w651.mirror.aliyuncs.com",
                "https://docker.mirrors.ustc.edu.cn",
                "http://hub-mirror.c.163.com"
              ]
            [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"."gcr.io"]
              endpoint = [
                "https://gcr.mirrors.ustc.edu.cn"
              ]
            [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"."k8s.gcr.io"]
              endpoint = [
                "https://gcr.mirrors.ustc.edu.cn/google-containers/"
              ]
            [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"."quay.io"]
              endpoint = [
                "https://quay.mirrors.ustc.edu.cn"
              ]
    • registry.mirrors."xxx"
      : 表示需要配置 mirror 的镜像仓库,例如 registry.mirrors."docker.io"
       表示配置 docker.io 的 mirror。

    • endpoint
      : 表示提供 mirror 的镜像加速服务,比如我们可以注册一个阿里云的镜像服务来作为 docker.io 的 mirror。

    另外在默认配置中还有两个关于存储的配置路径:

    root = "/var/lib/containerd"
    state = "/run/containerd"
    
    

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

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

    使用

    我们知道 Docker CLI 工具提供了需要增强用户体验的功能,containerd 同样也提供一个对应的 CLI 工具:ctr
    ,不过 ctr 的功能没有 docker 完善,但是关于镜像和容器的基本功能都是有的。接下来我们就先简单介绍下 ctr
     的使用。

    帮助

    直接输入 ctr
     命令即可获得所有相关的操作命令使用方式:

    ➜  ~ ctr
    NAME:
       ctr -
            __
      _____/ /______
     / ___/ __/ ___/
    / /__/ /_/ /
    \___/\__/_/
    
    containerd CLI
    
    
    USAGE:
       ctr [global options] command [command options] [arguments...]
    
    VERSION:
       v1.5.5
    
    DESCRIPTION:
    
    ctr is an unsupported debug and administrative client for interacting
    with the containerd daemon. Because it is unsupported, the commands,
    options, and operations are not guaranteed to be backward compatible or
    stable from release to release of the containerd project.
    
    COMMANDS:
       plugins, plugin            provides information about containerd plugins
       version                    print the client and server versions
       containers, c, container   manage containers
       content                    manage content
       events, event              display containerd events
       images, image, i           manage images
       leases                     manage leases
       namespaces, namespace, ns  manage namespaces
       pprof                      provide golang pprof outputs for containerd
       run                        run a container
       snapshots, snapshot        manage snapshots
       tasks, t, task             manage tasks
       install                    install a new package
       oci                        OCI tools
       shim                       interact with a shim directly
       help, h                    Shows a list of commands or help for one command
    
    GLOBAL OPTIONS:
       --debug                      enable debug output in logs
       --address value, -a value    address for containerd's GRPC server (default: "/run/containerd/containerd.sock") [$CONTAINERD_ADDRESS]
       --timeout value              total timeout for ctr commands (default: 0s)
       --connect-timeout value      timeout for connecting to containerd (default: 0s)
       --namespace value, -n value  namespace to use with commands (default: "default") [$CONTAINERD_NAMESPACE]
       --help, -h                   show help
       --version, -v                print the version
    
    

    镜像操作

    拉取镜像

    拉取镜像可以使用 ctr image pull
     来完成,比如拉取 Docker Hub 官方镜像 nginx:alpine
    ,需要注意的是镜像地址需要加上 docker.io
     Host 地址:

    ➜  ~ ctr image pull docker.io/library/nginx:alpine
    docker.io/library/nginx:alpine:                                                   resolved       |++++++++++++++++++++++++++++++++++++++|
    index-sha256:bead42240255ae1485653a956ef41c9e458eb077fcb6dc664cbc3aa9701a05ce:    exists         |++++++++++++++++++++++++++++++++++++++|
    manifest-sha256:ce6ca11a3fa7e0e6b44813901e3289212fc2f327ee8b1366176666e8fb470f24: done           |++++++++++++++++++++++++++++++++++++++|
    layer-sha256:9a6ac07b84eb50935293bb185d0a8696d03247f74fd7d43ea6161dc0f293f81f:    done           |++++++++++++++++++++++++++++++++++++++|
    layer-sha256:e82f830de071ebcda58148003698f32205b7970b01c58a197ac60d6bb79241b0:    done           |++++++++++++++++++++++++++++++++++++++|
    layer-sha256:d7c9fa7589ae28cd3306b204d5dd9a539612593e35df70f7a1d69ff7548e74cf:    done           |++++++++++++++++++++++++++++++++++++++|
    layer-sha256:bf2b3ee132db5b4c65432e53aca69da4e609c6cb154e0d0e14b2b02259e9c1e3:    done           |++++++++++++++++++++++++++++++++++++++|
    config-sha256:7ce0143dee376bfd2937b499a46fb110bda3c629c195b84b1cf6e19be1a9e23b:   done           |++++++++++++++++++++++++++++++++++++++|
    layer-sha256:3c1eaf69ff492177c34bdbf1735b6f2e5400e417f8f11b98b0da878f4ecad5fb:    done           |++++++++++++++++++++++++++++++++++++++|
    layer-sha256:29291e31a76a7e560b9b7ad3cada56e8c18d50a96cca8a2573e4f4689d7aca77:    done           |++++++++++++++++++++++++++++++++++++++|
    elapsed: 11.9s                                                                    total:  8.7 Mi (748.1 KiB/s)
    unpacking linux/amd64 sha256:bead42240255ae1485653a956ef41c9e458eb077fcb6dc664cbc3aa9701a05ce...
    done: 410.86624ms
    
    

    也可以使用 --platform
     选项指定对应平台的镜像。当然对应的也有推送镜像的命令 ctr image push
    ,如果是私有镜像则在推送的时候可以通过 --user
     来自定义仓库的用户名和密码。

    列出本地镜像

    ➜  ~ ctr image ls
    REF                            TYPE                                                      DIGEST                                                                  SIZE    PLATFORMS                                                                                LABELS
    docker.io/library/nginx:alpine application/vnd.docker.distribution.manifest.list.v2+json sha256:bead42240255ae1485653a956ef41c9e458eb077fcb6dc664cbc3aa9701a05ce 9.5 MiB linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x -
    ➜  ~ ctr image ls -q
    docker.io/library/nginx:alpine
    
    

    使用 -q(--quiet)
     选项可以只打印镜像名称。

    检测本地镜像

    ➜  ~ ctr image check
    REF                            TYPE                                                      DIGEST                                                                  STATUS         SIZE            UNPACKED
    docker.io/library/nginx:alpine application/vnd.docker.distribution.manifest.list.v2+json sha256:bead42240255ae1485653a956ef41c9e458eb077fcb6dc664cbc3aa9701a05ce complete (7/7) 9.5 MiB/9.5 MiB true
    
    

    主要查看其中的 STATUS
    complete
     表示镜像是完整可用的状态。

    重新打标签

    同样的我们也可以重新给指定的镜像打一个 Tag:

    ➜  ~ ctr image tag docker.io/library/nginx:alpine harbor.k8s.local/course/nginx:alpine
    harbor.k8s.local/course/nginx:alpine
    ➜  ~ ctr image ls -q
    docker.io/library/nginx:alpine
    harbor.k8s.local/course/nginx:alpine
    
    

    删除镜像

    不需要使用的镜像也可以使用 ctr image rm
     进行删除:

    ➜  ~ ctr image rm harbor.k8s.local/course/nginx:alpine
    harbor.k8s.local/course/nginx:alpine
    ➜  ~ ctr image ls -q
    docker.io/library/nginx:alpine
    
    

    加上 --sync
     选项可以同步删除镜像和所有相关的资源。

    将镜像挂载到主机目录

    ➜  ~ ctr image mount docker.io/library/nginx:alpine /mnt
    sha256:c3554b2d61e3c1cffcaba4b4fa7651c644a3354efaafa2f22cb53542f6c600dc
    /mnt
    ➜  ~ tree -L 1 /mnt
    /mnt
    ├── bin
    ├── dev
    ├── docker-entrypoint.d
    ├── docker-entrypoint.sh
    ├── etc
    ├── home
    ├── lib
    ├── media
    ├── mnt
    ├── opt
    ├── proc
    ├── root
    ├── run
    ├── sbin
    ├── srv
    ├── sys
    ├── tmp
    ├── usr
    └── var
    
    18 directories, 1 file
    
    

    将镜像从主机目录上卸载

    ➜  ~ ctr image unmount /mnt
    /mnt
    
    

    将镜像导出为压缩包

    ➜  ~ ctr image export nginx.tar.gz docker.io/library/nginx:alpine
    
    

    从压缩包导入镜像

    ➜  ~ ctr image import nginx.tar.gz
    
    

    容器操作

    容器相关操作可以通过 ctr container
     获取。

    创建容器

    ➜  ~ ctr container create docker.io/library/nginx:alpine nginx
    
    

    列出容器

    ➜  ~ ctr container ls
    CONTAINER    IMAGE                             RUNTIME
    nginx        docker.io/library/nginx:alpine    io.containerd.runc.v2
    
    

    同样可以加上 -q
     选项精简列表内容:

    ➜  ~ ctr container ls -q
    nginx
    
    

    查看容器详细配置

    类似于 docker inspect
     功能。

    ➜  ~ ctr container info nginx
    {
        "ID": "nginx",
        "Labels": {
            "io.containerd.image.config.stop-signal": "SIGQUIT"
        },
        "Image": "docker.io/library/nginx:alpine",
        "Runtime": {
            "Name": "io.containerd.runc.v2",
            "Options": {
                "type_url": "containerd.runc.v1.Options"
            }
        },
        "SnapshotKey": "nginx",
        "Snapshotter": "overlayfs",
        "CreatedAt": "2021-08-12T08:23:13.792871558Z",
        "UpdatedAt": "2021-08-12T08:23:13.792871558Z",
        "Extensions": null,
        "Spec": {
    ......
    
    

    删除容器

    ➜  ~ ctr container rm nginx
    ➜  ~ ctr container ls
    CONTAINER    IMAGE    RUNTIME
    
    

    除了使用 rm
     子命令之外也可以使用 delete
     或者 del
     删除容器。

    任务

    上面我们通过 container create
     命令创建的容器,并没有处于运行状态,只是一个静态的容器。一个 container 对象只是包含了运行一个容器所需的资源及相关配置数据,表示 namespaces、rootfs 和容器的配置都已经初始化成功了,只是用户进程还没有启动。

    一个容器真正运行起来是由 Task 任务实现的,Task 可以为容器设置网卡,还可以配置工具来对容器进行监控等。

    Task 相关操作可以通过 ctr task
     获取,如下我们通过 Task 来启动容器:

    ➜  ~ ctr task start -d nginx
    /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
    /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
    
    

    启动容器后可以通过 task ls
     查看正在运行的容器:

    ➜  ~ ctr task ls
    TASK     PID     STATUS
    nginx    3630    RUNNING
    
    

    同样也可以使用 exec
     命令进入容器进行操作:

    ➜  ~ ctr task exec --exec-id 0 -t nginx sh
    / #
    
    

    不过这里需要注意必须要指定 --exec-id
     参数,这个 id 可以随便写,只要唯一就行。

    暂停容器,和 docker pause
     类似的功能:

    ➜  ~ ctr task pause nginx
    
    

    暂停后容器状态变成了 PAUSED

    ➜  ~ ctr task ls
    TASK     PID     STATUS
    nginx    3630    PAUSED
    
    

    同样也可以使用 resume
     命令来恢复容器:

    ➜  ~ ctr task resume nginx
    ➜  ~ ctr task ls
    TASK     PID     STATUS
    nginx    3630    RUNNING
    
    

    不过需要注意 ctr 没有 stop 容器的功能,只能暂停或者杀死容器。杀死容器可以使用 task kill
     命令:

    ➜  ~ ctr task kill nginx
    ➜  ~ ctr task ls
    TASK     PID     STATUS
    nginx    3630    STOPPED
    
    

    杀掉容器后可以看到容器的状态变成了 STOPPED
    。同样也可以通过 task rm
     命令删除 Task:

    ➜  ~ ctr task rm nginx
    ➜  ~ ctr task ls
    TASK    PID    STATUS
    
    

    除此之外我们还可以获取容器的 cgroup 相关信息,可以使用 task metrics
     命令用来获取容器的内存、CPU 和 PID 的限额与使用量。

    # 重新启动容器
    ➜  ~ ctr task metrics nginx
    ID       TIMESTAMP
    nginx    2021-08-12 08:50:46.952769941 +0000 UTC
    
    METRIC                   VALUE
    memory.usage_in_bytes    8855552
    memory.limit_in_bytes    9223372036854771712
    memory.stat.cache        0
    cpuacct.usage            22467106
    cpuacct.usage_percpu     [2962708 860891 1163413 1915748 1058868 2888139 6159277 5458062]
    pids.current             9
    pids.limit               0
    
    

    还可以使用 task ps
     命令查看容器中所有进程在宿主机中的 PID:

    ➜  ~ ctr task ps nginx
    PID     INFO
    3984    -
    4029    -
    4030    -
    4031    -
    4032    -
    4033    -
    4034    -
    4035    -
    4036    -
    ➜  ~ ctr task ls
    TASK     PID     STATUS
    nginx    3984    RUNNING
    
    

    其中第一个 PID 3984
     就是我们容器中的1号进程。

    命名空间

    另外 Containerd 中也支持命名空间的概念,比如查看命名空间:

    ➜  ~ ctr ns ls
    NAME    LABELS
    default
    
    

    如果不指定,ctr 默认使用的是 default
     空间。同样也可以使用 ns create
     命令创建一个命名空间:

    ➜  ~ ctr ns create test
    ➜  ~ ctr ns ls
    NAME    LABELS
    default
    test
    
    

    使用 remove
     或者 rm
     可以删除 namespace:

    ➜  ~ ctr ns rm test
    test
    ➜  ~ ctr ns ls
    NAME    LABELS
    default
    
    

    有了命名空间后就可以在操作资源的时候指定 namespace,比如查看 test 命名空间的镜像,可以在操作命令后面加上 -n test
     选项:

    ➜  ~ ctr -n test image ls
    REF TYPE DIGEST SIZE PLATFORMS LABELS
    
    

    我们知道 Docker 其实也是默认调用的 containerd,事实上 Docker 使用的 containerd 下面的命名空间默认是 moby
    ,而不是 default
    ,所以假如我们有用 docker 启动容器,那么我们也可以通过 ctr -n moby
     来定位下面的容器:

    ➜  ~ ctr -n moby container ls
    
    

    同样 Kubernetes 下使用的 containerd 默认命名空间是 k8s.io
    ,所以我们可以使用 ctr -n k8s.io
     来查看 Kubernetes 下面创建的容器。

    展开全文
  • windows安装containerd

    2022-04-06 00:05:05
    windows上containerd的安装
  • containerd使用

    2022-04-28 11:42:58
    Docker CLI 工具提供了需要增强用户体验的功能,containerd 同样也提供一个对应的 CLI 工具:ctr,不过 ctr 的功能没有 docker 完善,但是关于镜像和容器的基本功能都是有的。具体看这篇文章:...
  • 与传统容器一样,Firecracker microVM提供快速启动和关闭以及最小的开销。 但是,与传统容器不同,它们可以通过KVM管理程序提供额外的隔离层。
  • 在学习 Containerd 之前我们有必要对 Docker 的发展历史做一个简单的回顾,因为这里面牵涉到的组件实战是有点多,有很多我们会经常听到,但是不清楚这些组件到底是干什么用的,比如libcontainer、runc、containerd、...
  • 本篇文章是containerd系列的第一篇文章,对containerd进行了系统的介绍,包括其含义,历史以及基础架构(1.4.x)
  • Containerd 被 Docker、Kubernetes CRI 和其他一些项目使用Containerd 旨在轻松嵌入到更大的系统中。Docker 在后台使用 containerd来...
  • 1.安装依赖 yum install -y yum-utils device-mapper-persistent-data lvm2 2.添加yum源 yum-config-manager --add-repo ... 3.安装containerd yum install containerd -y ...containerd config default &g.
  • containerd常用命令

    千次阅读 2021-11-07 23:29:20
    ctr -n k8s.io images ls #查看镜像 ctr -n k8s.io images pull -k #拉取镜像,拉取的时候忽略校验 ctr -n k8s.io images pull --tlscacert /etc/…/tls.crt #拉取镜像...ctr -n k8s.io i import containerd-build-aar
  • 在学习 Containerd 之前我们有必要对 Docker 的发展历史做一个简单的回顾,因为这里面牵涉到的组件实战是有点多,有很多我们会经常听到,但是不清楚这些组件到底是干什么用的,比如...
  • 实战:centos7上containerd的安装-20211023

    千次阅读 2021-10-24 10:46:12
    文章目录目录实验环境实验软件1、安装libseccomp依赖包2、下载containerd软件包并解压3、生成containerd 的默认配置文件config.toml4、启动containerd服务5、验证6、配置镜像加速器地址需要注意的问题关于我总结 ...
  • containerd 全面攻略

    千次阅读 2021-04-11 13:26:26
    containerd简介 Containerd是一个工业标准的容器运行时,重点是它简洁,健壮,便携,在Linux和window上可以作为一个守护进程运行,它可以管理主机系统上容器的完整的生命周期:镜像传输和存储,容器的执行和监控,低...
  • Containerd容器管理

    千次阅读 2022-04-01 09:49:53
    containerd的默认命令行工具(crictl)也不是很好用,和docker也不兼容。 nttlabs贡献了一个名为nerdctl的containerd客户端,可以兼容docker命令行工具。于是我们就可以使用nerdctl来作为docker的替代品了。 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 34,294
精华内容 13,717
关键字:

Containerd