Skip to content

1. Kubernetes容器运行时演进

早期的kubernetes runtime架构,远没这么复杂,kubelet创建容器,直接调用docker daemon,docker daemon自己调用libcontainer就把容器运行起来。

国际大厂们认为运行时标准不能被 Docker 一家公司控制,于是就串通搞了开放容器标准 OCI。忽悠Docker 把 libcontainer 封装了一下,变成 runC 捐献出来作为 OCI 的参考实现。

OCI(开放容器标准),规定了2点:

  • 容器镜像要长啥样,即 ImageSpec。里面的大致规定就是你这个东西需要是一个压缩了的文件夹,文件夹里以 xxx 结构放 xxx 文件;
  • 容器要需要能接收哪些指令,这些指令的行为是什么,即 RuntimeSpec。这里面的大致内容就是“容器”要能够执行 “create”,“start”,“stop”,“delete” 这些命令,并且行为要规范。

runC 参考实现,就是它能按照标准将符合标准的容器镜像运行起来,标准的好处就是方便搞创新,只要符合标准,生态圈里的其它工具都能和我一起工作(……当然 OCI 这个标准本身制定得不怎么样,真正工程上还是要做一些 adapter 的),那我的镜像就可以用任意的工具去构建,我的“容器”就不一定非要用 namespace 和 cgroups 来做隔离。这就让各种虚拟化容器可以更好地参与到容器实现当中。

再接下来 rkt(coreos推出的,类似docker) 想从 Docker 那边分一杯羹,希望 Kubernetes 原生支持 rkt 作为运行时,而且 PR 还真的合进去了。但是,整合出现的很多坑让Kubernetes疲于奔命。

然后,在Kubernetes 1.5 推出了 CRI 机制,即容器运行时接口(Container Runtime Interface),Kubernetes 告诉大家,你们想做 Runtime 可以啊,实现这个接口就成,成功反客为主。

不过 ,当时的 Kubernetes 尚未达到如今这般武林盟主的地位,容器运行时当然不能说我跟 Kubernetes 绑死了只提供 CRI 接口,于是就有了 shim(垫片)这个说法,一个 shim 的职责就是作为 Adapter 将各种容器运行时本身的接口适配到 Kubernetes 的 CRI 接口上,如下图中dockershim。

Untitled

这时,Docker 要搞 Swarm 进军 PaaS 市场,于是做了个架构切分,把容器操作都移动到一个单独的 Daemon 进程 containerd 中去,让 Docker Daemon 专门负责上层的封装编排。可惜 Swarm 在 Kubernetes 面前惨败。

之后,Docker 公司就把 containerd 项目捐给 CNCF 缩回去安心搞 Docker 企业版了。

Docker+containerd的runtime 实在是有点复杂了,于是Kubernetes就有了直接拿 containerd 做 oci-runtime 的方案。当然,除了 Kubernetes 之外,containerd 还要接诸如 Swarm 等调度系统,因此它不会去直接实现 CRI,这个适配工作当然就要交给一个 shim 了。

containerd 1.0 中,对 CRI 的适配通过一个单独的进程 CRI-containerd 来完成;

containerd 1.1 中做的又更漂亮一点,砍掉了 CRI-containerd 这个进程,直接把适配逻辑作为插件放进了 containerd 主进程中。

但在 containerd 做这些事情前,社区就已经有了一个更为专注的 cri-runtime:CRI-O,它非常纯粹,就是兼容 CRI 和 OCI,做一个 Kubernetes 专用的运行时:

Untitled

其中 conmon 就对应 containerd-shim,大体意图是一样的。

CRI-O 和(直接调用)containerd 的方案比起默认的 dockershim 确实简洁很多,但没啥生产环境的验证案例。直到不久前的1.24版本,Kubernetes终于不再原生支持Docker,以后的生产环境想必越来越多的containerd 的方案了。

2. 部署runtime方式

  • 集群创建方式1:Containerd 默认情况下,Kubernetes在创建集群的时候,使用的就是 Containerd方式。
  • 集群创建方式2:Docker Docker使用的普及率较高,虽然Kubernetes 1.24默认情况下废弃了kubelet对于Docker的支持,但是我们还可以借助于Mirantis维护的cri-dockerd插件方式来实现Kubernetes集群的创建。
  • 集群创建方式3:CRI-O CRI-O的方式是Kubernetes创建容器最直接的一种方式,在创建集群的时候,需要借助于cri-o插件的方式来实现Kubernetes集群的创建。

1.方式1

看下章

2.方式2

2.1 部署containerd

#下载
wget https://github.com/containerd/containerd/releases/download/v1.7.22/cri-containerd-cni-1.7.22-linux-amd64.tar.gz

#解压
tar zxvf cri-containerd-cni-1.7.22-linux-amd64.tar.gz -C /
#下载
wget https://github.com/containerd/containerd/releases/download/v1.7.22/cri-containerd-cni-1.7.22-linux-amd64.tar.gz

#解压
tar zxvf cri-containerd-cni-1.7.22-linux-amd64.tar.gz -C /
  • 配置
bash
mkdir /etc/containerd/

containerd config default | tee /etc/containerd/config.toml

vim config.toml
...
SystemdCgroup = false #修改为true
...


再修改/etc/containerd/config.toml中的
[plugins."io.containerd.grpc.v1.cri"]
  ...
  # sandbox_image = "k8s.gcr.io/pause:3.6"
  sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"	#这里一定要注意,要根据下载到本地 pause镜像的版本来进行修改,否则初始化会过不去。

再修改,默认是io.containerd.runc.v2,否则crictl 无法连接containerd
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
  runtime_type = "io.containerd.runtime.v1.linux"
mkdir /etc/containerd/

containerd config default | tee /etc/containerd/config.toml

vim config.toml
...
SystemdCgroup = false #修改为true
...


再修改/etc/containerd/config.toml中的
[plugins."io.containerd.grpc.v1.cri"]
  ...
  # sandbox_image = "k8s.gcr.io/pause:3.6"
  sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"	#这里一定要注意,要根据下载到本地 pause镜像的版本来进行修改,否则初始化会过不去。

再修改,默认是io.containerd.runc.v2,否则crictl 无法连接containerd
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
  runtime_type = "io.containerd.runtime.v1.linux"
  • 启动
bash
# 重新加载Unit file
systemctl daemon-reload

#开机启动,并启动服务
systemctl enable --now containerd
# 重新加载Unit file
systemctl daemon-reload

#开机启动,并启动服务
systemctl enable --now containerd

2.2 部署 Docker

bash
# 二进制包下载地址:https://download.docker.com/linux/static/stable/x86_64/
wget https://mirrors.ustc.edu.cn/docker-ce/linux/static/stable/x86_64/docker-26.1.4.tgz
# 二进制包下载地址:https://download.docker.com/linux/static/stable/x86_64/
wget https://mirrors.ustc.edu.cn/docker-ce/linux/static/stable/x86_64/docker-26.1.4.tgz

1.解压

bash
tar xf docker-*.tgz 

cp docker/* /usr/bin/
tar xf docker-*.tgz 

cp docker/* /usr/bin/

2.配置unit文件

bash
# 准备 docker 的 service 文件
cat > /etc/systemd/system/docker.service <<EOF
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target firewalld.service cri-docker.service docker.socket containerd.service
Wants=network-online.target
Requires=docker.socket containerd.service

[Service]
Type=notify
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
StartLimitBurst=3
StartLimitInterval=60s
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
Delegate=yes
KillMode=process
OOMScoreAdjust=-500

[Install]
WantedBy=multi-user.target
EOF
# 准备 docker 的 service 文件
cat > /etc/systemd/system/docker.service <<EOF
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target firewalld.service cri-docker.service docker.socket containerd.service
Wants=network-online.target
Requires=docker.socket containerd.service

[Service]
Type=notify
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
StartLimitBurst=3
StartLimitInterval=60s
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
Delegate=yes
KillMode=process
OOMScoreAdjust=-500

[Install]
WantedBy=multi-user.target
EOF

[Unit]

Description: 描述服务的作用,这里是Docker Application Container Engine,即Docker应用容器引擎。

Documentation: 提供关于此服务的文档链接,这里是Docker官方文档链接。

After: 说明该服务在哪些其他服务之后启动,这里是在网络在线、firewalld服务和containerd服务后启动。

Wants: 说明该服务想要的其他服务,这里是网络在线服务。

Requires: 说明该服务需要的其他服务,这里是docker.socket和containerd.service。

[Service]

Type: 服务类型,这里是notify,表示服务在启动完成时发送通知。

ExecStart: 命令,启动该服务时会执行的命令,这里是/usr/bin/dockerd -H fd:// --

containerd=/run/containerd/containerd.sock,即启动dockerd并指定一些参数,其中-H指定

dockerd的监听地址为fd://,--containerd指定containerd的sock文件位置。

ExecReload: 重载命令,当接收到HUP信号时执行的命令,这里是/bin/kill -s HUP $MAINPID,即发送HUP信号给主进程ID。

TimeoutSec: 服务超时时间,这里是0,表示没有超时限制。

RestartSec: 重启间隔时间,这里是2秒,表示重启失败后等待2秒再重启。

Restart: 重启策略,这里是always,表示总是重启。

StartLimitBurst: 启动限制次数,这里是3,表示在启动失败后最多重试3次。

StartLimitInterval: 启动限制时间间隔,这里是60秒,表示两次启动之间最少间隔60秒。

LimitNOFILE: 文件描述符限制,这里是infinity,表示没有限制。

LimitNPROC: 进程数限制,这里是infinity,表示没有限制。

LimitCORE: 核心转储限制,这里是infinity,表示没有限制。

TasksMax: 最大任务数,这里是infinity,表示没有限制。

Delegate: 修改权限,这里是yes,表示启用权限修改。

KillMode: 杀死模式,这里是process,表示杀死整个进程组。

OOMScoreAdjust: 用于调整进程在系统内存紧张时的优先级调整,这里是-500,表示将OOM分数降低500。 [Install]

WantedBy: 安装目标,这里是multi-user.target,表示在多用户模式下安装。

在WantedBy参数中,我们可以使用以下参数:

multi-user.target:指定该服务应该在多用户模式下启动。

graphical.target:指定该服务应该在图形化界面模式下启动。

default.target:指定该服务应该在系统的默认目标(runlevel)下启动。

rescue.target:指定该服务应该在系统救援模式下启动。

poweroff.target:指定该服务应该在关机时启动。

reboot.target:指定该服务应该在重启时启动。

halt.target:指定该服务应该在停止时启动。

shutdown.target:指定该服务应该在系统关闭时启动。

这些参数可以根据需要选择一个或多个,以告知系统在何时启动该服务。

3. 部署docker socket 文件

bash
#准备 docker 的 socket 文件
cat > /etc/systemd/system/docker.socket <<EOF
[Unit]
Description=Docker Socket for the API

[Socket]
ListenStream=/var/run/docker.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target
EOF
#准备 docker 的 socket 文件
cat > /etc/systemd/system/docker.socket <<EOF
[Unit]
Description=Docker Socket for the API

[Socket]
ListenStream=/var/run/docker.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target
EOF
这是一个用于Docker API的socket配置文件,包含了以下参数:

[Unit]

Description:描述了该socket的作用,即为Docker API的socket。
[Socket]

ListenStream:指定了 socket 的监听地址,该 socket 会监听在 /var/run/docker.sock 上,即 Docker 守护程序使用的默认 sock 文件。
SocketMode:指定了socket文件的权限模式,此处为0660,即用户和用户组有读写权限,其他用户无权限。
SocketUser:指定了socket文件的所有者,此处为root用户。
SocketGroup:指定了socket文件的所属用户组,此处为docker用户组。
[Install]

WantedBy:指定了该socket被启用时的目标,此处为sockets.target,表示当sockets.target启动时启用该socket。
该配置文件的作用是为 Docker 提供 API 访问的通道,它监听在 /var/run/docker.sock 上,具有 root 用户权限,但只接受 docker 用户组的成员的连接,并且其他用户无法访问。这样,只有 docker 用户组的成员可以通过该 socket 与 Docker 守护进程进行通信。
这是一个用于Docker API的socket配置文件,包含了以下参数:

[Unit]

Description:描述了该socket的作用,即为Docker API的socket。
[Socket]

ListenStream:指定了 socket 的监听地址,该 socket 会监听在 /var/run/docker.sock 上,即 Docker 守护程序使用的默认 sock 文件。
SocketMode:指定了socket文件的权限模式,此处为0660,即用户和用户组有读写权限,其他用户无权限。
SocketUser:指定了socket文件的所有者,此处为root用户。
SocketGroup:指定了socket文件的所属用户组,此处为docker用户组。
[Install]

WantedBy:指定了该socket被启用时的目标,此处为sockets.target,表示当sockets.target启动时启用该socket。
该配置文件的作用是为 Docker 提供 API 访问的通道,它监听在 /var/run/docker.sock 上,具有 root 用户权限,但只接受 docker 用户组的成员的连接,并且其他用户无法访问。这样,只有 docker 用户组的成员可以通过该 socket 与 Docker 守护进程进行通信。

4.配置docker加速

# 配置加速器
mkdir /etc/docker/ -pv

cat >/etc/docker/daemon.json <<EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "registry-mirrors": [
  ],
  "max-concurrent-downloads": 10,
  "log-driver": "json-file",
  "log-level": "warn",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
    },
  "data-root": "/var/lib/docker",
  "storage-driver": "overlay2"
}
EOF
# 配置加速器
mkdir /etc/docker/ -pv

cat >/etc/docker/daemon.json <<EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "registry-mirrors": [
  ],
  "max-concurrent-downloads": 10,
  "log-driver": "json-file",
  "log-level": "warn",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
    },
  "data-root": "/var/lib/docker",
  "storage-driver": "overlay2"
}
EOF
5.启动
# 启动 Docker
groupadd docker

systemctl daemon-reload

systemctl enable --now docker.socket

systemctl enable --now docker.service

systemctl status docker.service

docker info
# 启动 Docker
groupadd docker

systemctl daemon-reload

systemctl enable --now docker.socket

systemctl enable --now docker.service

systemctl status docker.service

docker info

5.部署 cri-docker

bash
# 解压 cri-docker
# https://github.com/Mirantis/cri-dockerd/releases/
wget  https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.15/cri-dockerd-0.3.15.amd64.tgz

tar xvf cri-dockerd-*.amd64.tgz 

cp -r cri-dockerd  /usr/bin/

chmod +x /usr/bin/cri-dockerd
# 解压 cri-docker
# https://github.com/Mirantis/cri-dockerd/releases/
wget  https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.15/cri-dockerd-0.3.15.amd64.tgz

tar xvf cri-dockerd-*.amd64.tgz 

cp -r cri-dockerd  /usr/bin/

chmod +x /usr/bin/cri-dockerd
1.配置unit文件
bash
# 写入启动  cri-docker 配置文件

cat >  /usr/lib/systemd/system/cri-docker.service <<EOF
[Unit]
Description=CRI Interface for Docker Application Container Engine
Documentation=https://docs.mirantis.com
After=network-online.target firewalld.service docker.service
Wants=network-online.target
Requires=cri-docker.socket

[Service]
Type=notify
ExecStart=/usr/bin/cri-dockerd --network-plugin=cni --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.9
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
StartLimitBurst=3
StartLimitInterval=60s
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
Delegate=yes
KillMode=process

[Install]
WantedBy=multi-user.target
EOF
# 写入启动  cri-docker 配置文件

cat >  /usr/lib/systemd/system/cri-docker.service <<EOF
[Unit]
Description=CRI Interface for Docker Application Container Engine
Documentation=https://docs.mirantis.com
After=network-online.target firewalld.service docker.service
Wants=network-online.target
Requires=cri-docker.socket

[Service]
Type=notify
ExecStart=/usr/bin/cri-dockerd --network-plugin=cni --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.9
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
StartLimitBurst=3
StartLimitInterval=60s
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
Delegate=yes
KillMode=process

[Install]
WantedBy=multi-user.target
EOF
2.配置socket文件
bash
# 写入 cri-docker 的 socket 配置文件

cat > /usr/lib/systemd/system/cri-docker.socket <<EOF
[Unit]
Description=CRI Docker Socket for the API
PartOf=cri-docker.service

[Socket]
ListenStream=%t/cri-dockerd.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target
EOF
# 写入 cri-docker 的 socket 配置文件

cat > /usr/lib/systemd/system/cri-docker.socket <<EOF
[Unit]
Description=CRI Docker Socket for the API
PartOf=cri-docker.service

[Socket]
ListenStream=%t/cri-dockerd.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target
EOF
  • 配置
bash
cat /etc/crictl.yaml
runtime-endpoint: "unix:///var/run/cri-dockerd.sock"
image-endpoint: "unix:///var/run/cri-dockerd.sock"
timeout: 10
debug: false
pull-image-on-create: true
disable-pull-on-run: false
cat /etc/crictl.yaml
runtime-endpoint: "unix:///var/run/cri-dockerd.sock"
image-endpoint: "unix:///var/run/cri-dockerd.sock"
timeout: 10
debug: false
pull-image-on-create: true
disable-pull-on-run: false
bash
crictl ps
crictl ps
3.启动
bash
# 启动 cri-docker

systemctl daemon-reload

systemctl enable --now cri-docker.service

systemctl status docker.service
# 启动 cri-docker

systemctl daemon-reload

systemctl enable --now cri-docker.service

systemctl status docker.service

2.3 配置kubelet-所有节点

bash
# kubelet 配置
# 当使用 docker 作为 Runtime
cat > /usr/lib/systemd/system/kubelet.service << EOF

[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/kubernetes/kubernetes
After=network-online.target firewalld.service cri-docker.service docker.socket containerd.service
Wants=network-online.target
Requires=docker.socket containerd.service

[Service]
ExecStart=/usr/local/bin/kubelet \\
    --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.kubeconfig  \\
    --kubeconfig=/etc/kubernetes/kubelet.kubeconfig \\
    --config=/etc/kubernetes/kubelet-conf.yml \\
    --container-runtime-endpoint=unix:///run/cri-dockerd.sock  \\
    --node-labels=node.kubernetes.io/node= 


[Install]
WantedBy=multi-user.target
EOF
# kubelet 配置
# 当使用 docker 作为 Runtime
cat > /usr/lib/systemd/system/kubelet.service << EOF

[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/kubernetes/kubernetes
After=network-online.target firewalld.service cri-docker.service docker.socket containerd.service
Wants=network-online.target
Requires=docker.socket containerd.service

[Service]
ExecStart=/usr/local/bin/kubelet \\
    --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.kubeconfig  \\
    --kubeconfig=/etc/kubernetes/kubelet.kubeconfig \\
    --config=/etc/kubernetes/kubelet-conf.yml \\
    --container-runtime-endpoint=unix:///run/cri-dockerd.sock  \\
    --node-labels=node.kubernetes.io/node= 


[Install]
WantedBy=multi-user.target
EOF

# IPv6示例
# 若不使用IPv6那么忽略此项即可
# 下方 --node-ip 更换为每个节点的IP即可
# cat > /usr/lib/systemd/system/kubelet.service << EOF
# [Unit]
# Description=Kubernetes Kubelet
# Documentation=https://github.com/kubernetes/kubernetes
# After=network-online.target firewalld.service cri-docker.service docker.socket # containerd.service
# Wants=network-online.target
# Requires=docker.socket containerd.service

# [Service]
# ExecStart=/usr/local/bin/kubelet \\
#     --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.kubeconfig  \\
#     --kubeconfig=/etc/kubernetes/kubelet.kubeconfig \\
#     --config=/etc/kubernetes/kubelet-conf.yml \\
#     --container-runtime-endpoint=unix:///run/cri-dockerd.sock  \\
#     --node-labels=node.kubernetes.io/node=   \\
#     --node-ip=192.168.1.31,2408:822a:245:8c01::fab
# [Install]
# WantedBy=multi-user.target
# EOF

# IPv6示例
# 若不使用IPv6那么忽略此项即可
# 下方 --node-ip 更换为每个节点的IP即可
# cat > /usr/lib/systemd/system/kubelet.service << EOF
# [Unit]
# Description=Kubernetes Kubelet
# Documentation=https://github.com/kubernetes/kubernetes
# After=network-online.target firewalld.service cri-docker.service docker.socket # containerd.service
# Wants=network-online.target
# Requires=docker.socket containerd.service

# [Service]
# ExecStart=/usr/local/bin/kubelet \\
#     --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.kubeconfig  \\
#     --kubeconfig=/etc/kubernetes/kubelet.kubeconfig \\
#     --config=/etc/kubernetes/kubelet-conf.yml \\
#     --container-runtime-endpoint=unix:///run/cri-dockerd.sock  \\
#     --node-labels=node.kubernetes.io/node=   \\
#     --node-ip=192.168.1.31,2408:822a:245:8c01::fab
# [Install]
# WantedBy=multi-user.target
# EOF