Kubernetes 通过容器运行时(container runtime)来启动和管理容器。官方文档列举了以下几种 runtime:Docker,CRI-O,Containerd,fraki。它们之间有什么区别和联系呢?经常会看到 OCI、CRI 这些缩写,这些和容器、docker 到底是什么关系呢?
这篇文章不会深入到很细节的部分,旨在为初学者提供一个比较初步的概览,对一些基本概念做一些简单介绍。
简单说说什么是容器。容器实际上是 Linux 内核几组功能的组合:cgroup、namespace 和 union file system。cgroup 用来限制进程组所使用的系统资源(CPU、Memory、IO 等);namespace 用来隔离进程对系统资源的访问(IPC、Network、PID 等),让不同 namespace 的进程看不到彼此的存在;union file system 用来支持对文件系统的修改分层。
容器并不是虚拟机。虚拟机一般会虚拟完整的操作系统内核,而容器只是虚拟进程的运行环境。容器用到的技术,本身就是内核提供的。容器与容器是共享一个内核的,而虚拟机与虚拟机有可能跑在同一台物理机器上但是各有一个内核。
容器运行时是管理容器和容器镜像的程序。对于 k8s 而言,runtime 指的是 CRI-runtime,它不关心如何调用内核 API,只规定了 kubelet 与容器相关的接口;对于 docker 而言,runtime 一般指的是 ORI-runtime,封装具体的内核交互和系统调用。
OCI 标准
OCI(Open Container Initiative)标准是由 Docker 公司主导的一个关于容器格式和运行时的标准或规范,包含运行时标准(runtime-spec )和容器镜像标准(image-spec)。运行时标准规定了怎么去运行一个容器,如何去表达容器的状态(state)和生命周期(lifestyle)、如何设置 namespace、cgroup、文件系统等等,可以理解为运行期的动态描述;而容器镜像标准规定了容器镜像的格式、配置、元数据等,可以理解为对镜像的静态描述。
为什么要搞这么一个标准呢?应该是为了防止各家容器各有一套互不兼容的格式导致生态过于碎片化,另外一个目的是尽管目前只有 Linux 系统有容器,但万一我们要在 Windows 或者 Unix 上实现容器,要不要重新搞一套标准呢?OCI 规范也可以在其他操作系统和平台上实现。
runc
OCI 规范在 Linux 上的完整实现是 runC。我们通过 runC 命令可以看到一些基本的说明:
[root@master ~]# runc --help
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 的 help 输出可以看到,这是一个符合 OCI 规范的命令行工具。我们可以通过 runc run [ -b bundle ] <container-id>
来启动一个容器。bundle
是一个包含描述文件 config.json
和 rootfs 的路径。
runsc in gViser
google 出品的 gViser 实现了一个用户空间的 kernel,也就说,在用户空间模拟了系统调用(syscall)。它是通过沙盒(Sandbox)的机制来为进程提供更强的隔离性,容器将跑在用户空间的沙盒里,而不是内核空间了。它包含了一个符合 OCI 标准的 runtime——runsc
。兼容 OCI 标准使得它容易与 docker 和 k8s 集成。
CRI 标准
Docker 应该是最出名的容器引擎或容器运行时了。k8s 早期只支持 docker ,后来为了让 k8s 和 docker 解耦,防止绑定在特定的运行时上面,k8s 开放了容器运行时接口(CRI)。该接口是基于 gRPC 的,容器运行时只要实现了 CRI,就能和 k8s 集成。
除了 CRI,k8s 还开放了容器网络接口(CNI,Container Network Interface)和容器存储接口(CSI,Container Storage Interface)。用户或者管理员可以根据自己的实际需求使用不同的容器运行时、网络插件和存储驱动,只要他们实现了相应的接口,而不需要对 k8s 源码做特殊的改动。
除了 Docker,还有 CRI-O、Containerd。
可以通过修改 kubelet 的参数来配置不同的 CRI 运行时。kubelet 运行在每一个 node 上面,k8s 通过 kubelet 来启动容器。
通过 kubelet --help
查看相关参数的说明:
--container-runtime string The container runtime to use. Possible values: 'docker', 'remote', 'rkt (deprecated)'. (default "docker")
--container-runtime-endpoint string [Experimental] The endpoint of remote runtime service. Currently unix socket endpoint is supported on Linux, while npipe and tcp endpoints are supported on windows. Examples:'unix:///var/run/dockershim.sock', 'npipe:////./pipe/dockershim' (default "unix:///var/run/dockershim.sock")
默认情况下,kubelet 是通过内置的 docker-shim 调用 docker 来创建容器。 --container-runtime=remote
指定了其他的运行时, --container-runtime-endpoint
指定了该运行时的访问端点。
比如我想使用 CRI-O 作为 k8s 的容器进行时,我可以这么设置:
--container-runtime=remote --container-runtime-endpoint=/var/run/crio/crio.sock --cgroup-driver=systemd
同时启动 crio 的 daemon:systemctl start crio
并重启 kubelet:systemctl restart kubelet
通过 CRI 的命令行客户端 crictl
可以查看版本:
[root@master ~]# crictl version
Version: 0.1.0
RuntimeName: cri-o
RuntimeVersion: 1.11.11-1.rhaos3.11.git474f73d.el7
RuntimeApiVersion: v1alpha1
可以看到 RuntimeName 是 cri-o。
假如 kubelet 配置的是 docker,crictl version
的结果是:
[root@node01 ~]# crictl version
Version: 0.1.0
RuntimeName: docker
RuntimeVersion: 18.09.6
RuntimeApiVersion: 1.39.0
docker
现在安装比较新的 docker(18.09.6),会看到实际上至少会有三个组件:runC、containerd、dockerd。
dockerd 是个守护进程,直接面向用户,用户使用的命令 docker 直接调用的后端就是 dockerd;dockerd 不会直接使用 runc,而是去调用 containerd;containerd 会 fork 出一个单独子进程的 containerd-shim,使用 runc 将目标容器跑起来。
Kubelet 则是通过内置的 docker-shim 去调用 dockerd。
+--------------------+
| |
| | CRI gRPC
| kubelet +-----+ +---------------+ +--------------+
| | | | | | |
| | | +---------------+ +--------------+ fork |container-shim +-----> container |
| +-------------+ | | | | +-------> | | |
| | | | | | | | +---------------+ +--------------+
| | A+<----+ | | | | runc(OCI)
| | dockershim | | dockerd | | containerd | +---------------+ +--------------+
| | +--------->B +------->C | | | | |
| | | | | | +------->container-shim +-----> container |
| | | | | | | | | | |
+------+-------------+ +---------------+ +--------------+ +---------------+ +--------------+
|
A:unix:///var/run/dockershim.sock +------> ......
C:/run/containerd/containerd.sock
B:/var/run/docker.sock
通过 ps -ef
可以看到几个进程之间的关系:
root 5904 1 0 Jul28 ? 00:21:59 /usr/bin/containerd
root 7824 5904 0 Jul28 ? 00:00:04 containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/cf8911b66df50d267e7bd6699dc38c2c4e5b7324ce9c9bf2108800b957035813 -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc
root 7892 7824 0 Jul28 ? 00:00:46 /frpc -c /conf/frpc.ini
看起来 kubelet 与 docker 之间的交互还是蛮复杂的,其中有很多历史原因,也牵扯到 k8s 与 docker(Swarm) 之间的竞争。但 docker 在容器领域是还是最出名的,用 docker 的人是最多的。kubelet 默认也是使用 docker 作为容器运行时。所以如果遇到问题比较容易找到相关的资料。
CRI-O
CRI-O 是 RedHat 发布的容器运行时,旨在同时满足 CRI 标准和 OCI 标准。kubelet 通过 CRI 与 CRI-O 交互,CRI-O 通过 OCI 与 runC 交互,追求简单明了。可以看到,在这种方式下,就不需要使用 docker 了。
+----------+ +--------------+
| | | |
| conmon +----------> container |
+-------------+ +--------------+----> | | |
| | | | +----------+ +--------------+
| | | | runc(OCI)
| kubelet | CRI | CRI-O | +----------+ +--------------+
| +------->A | | | | |
| | | +----> conmon +----------> container |
| | | | | | | |
+-------------+ +--------------+ +----------+ +--------------+
|
+---->......
A:/var/run/crio/crio.sock
cri-containerd
Containerd 也是 docker 公司实现的,后来捐献给了 CNCF。contianerd 把 dockerd 与 runc 解耦了,dockerd 不直接创建容器,而是通过 containerd 去调用 runc。从 contianerd 1.1 开始,contianerd 可以以插件的方式集成 CRI。contianerd 也可以使用除 runc 以外的容器引擎。
+--------------------+ +----------------------+ +---------------+ +--------------+
| | | | | | | |
| | | | fork |container-shim +-----> container |
| kubelet | | containerd +----------> | | |
| | | | +---------------+ +--------------+
| | | | runc(OCI)
| | +--------------+ | +---------------+ +--------------+
| | | | | | | | |
| | | CRI-plugin | +---------->container-shim +-----> container |
| | | | | | | | |
| +--------->A | | +---------------+ +--------------+
| | | | |
| | | | +---------->......
+--------------------+ +--------------+-------+
A:/run/containerd/containerd.sock
使用 containerd 的优势是可配置性,可以通过插件的方式更换具体的实现。
强隔离 runtime:Frakti、gVisor…
容器毕竟还是共享内核的,安全性和隔离型对于想要实现多租户是不够。所以又出现了许多基于虚拟机隔离的方案出来。
Frakti 提供了hypervisor级别的隔离性,官网的原话是:
Frakti lefts Kubernetes run pods and containers directly inside hypervisors via runV. It is light weighted and portable, but can provide much stronger isolation with independent kernel than linux-namespace-based container runtimes.
提供的是内核级别的而非Linux命名空间级别的隔离。
gVisor 我的理解是拦截了系统调用,用自己实现用户态的进程而非内核来处理系统调用。
使用相关的命令行工具来查看容器信息
为了更加清晰地理清各种乱七八糟的 daemon,我们可以在一个运行的 k8s 集群里,通过命令行客户端来看一下实际运行的容器是怎样的。
环境
按照默认方式安装了 1 master 2 nodes 的 k8s 集群,三台机器都是 centos 7。IP 如下:
Master 10.10.13.61
Node1 10.10.13.62
Node2 10.10.13.63
k8s 版本:
[root@master runc]# kubectl version
Client Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.3", GitCommit:"5e53fd6bc17c0dec8434817e69b04a25d8ae0ff0", GitTreeState:"clean", BuildDate:"2019-06-06T01:44:30Z", GoVersion:"go1.12.5", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.3", GitCommit:"5e53fd6bc17c0dec8434817e69b04a25d8ae0ff0", GitTreeState:"clean", BuildDate:"2019-06-06T01:36:19Z", GoVersion:"go1.12.5", Compiler:"gc", Platform:"linux/amd64"}
docker 版本:
[root@master runc]# docker version
Client:
Version: 18.09.6
API version: 1.39
Go version: go1.10.8
Git commit: 481bc77156
Built: Sat May 4 02:34:58 2019
OS/Arch: linux/amd64
Experimental: false
Server: Docker Engine - Community
Engine:
Version: 18.09.6
API version: 1.39 (minimum version 1.12)
Go version: go1.10.8
Git commit: 481bc77
Built: Sat May 4 02:02:43 2019
OS/Arch: linux/amd64
Experimental: false
为了比较各种运行时的效果,我们设置 master 上的容器运行时为 containerd,node1 为 docker,node2 为 crio。
master 配置 kubelet 使用 cri-contianerd
生成 containerd 默认配置文件:
containerd config default > /etc/containerd/config.toml
可以看到配置文件中 cri 是作为 plugin 存在的:
......
[plugins]
[plugins.cgroups]
no_prometheus = false
[plugins.cri]
stream_server_address = "127.0.0.1"
stream_server_port = "0"
enable_selinux = false
sandbox_image = "registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0"
stats_collect_period = 10
systemd_cgroup = false
enable_tls_streaming = false
max_container_log_line_size = 16384
[plugins.cri.containerd]
snapshotter = "overlayfs"
no_pivot = false
[plugins.cri.containerd.default_runtime]
runtime_type = "io.containerd.runtime.v1.linux"
runtime_engine = ""
runtime_root = ""
[plugins.cri.containerd.untrusted_workload_runtime]
runtime_type = ""
runtime_engine = ""
runtime_root = ""
[plugins.cri.cni]
bin_dir = "/opt/cni/bin"
conf_dir = "/etc/cni/net.d"
conf_template = ""
[plugins.cri.registry]
.......
配置 kubelet: 在 ExecStart 一行(或修改对应的 EnvironmentFile)添加两个 flag --container-runtime=remote --container-runtime-endpoint=/run/containerd/containerd.sock
[Unit]
Description=Kubernetes Kubelet
After=docker.service
Requires=docker.service
[Service]
EnvironmentFile=/k8s/kubernetes/cfg/kubelet
ExecStart=/k8s/kubernetes/bin/kubelet $KUBELET_OPTS --container-runtime=remote --container-runtime-endpoint=/run/containerd/containerd.sock
WorkingDirectory=/var/lib/kubelet
Restart=on-failure
KillMode=process
[Install]
WantedBy=multi-user.target
重启 containerd 和 kubelet
systemctl restart containerd
systemctl restart kubelet
使用 ctr 可以看到有两个 namespace:
[root@master ~]# ctr namespace ls
NAME LABELS
k8s.io
moby
一个是 k8s.io, 一个是 moby。这里的 namespace 不是 k8s 层面的,而是 containerd 用来隔离不同的 plugin 的。通过 kubelet 启动的容器,ns 就是 k8s.io,通过 docker 启动的就是 moby。docker ps
是看不到 k8s.io 下的容器的。对于 containerd 而言, docker 和 kubelet 是两个不同的客户端。
ctr plugin ls
可以看到启用了哪些插件:
[root@master ~]# ctr plugin ls | grep cri
io.containerd.grpc.v1 cri linux/amd64 ok
使用 crictl 连接 containerd 查看版本信息:
[root@master runc]# crictl -r /run/containerd/containerd.sock version
Version: 0.1.0
RuntimeName: containerd
RuntimeVersion: 1.2.5
RuntimeApiVersion: v1alpha2
Crictl 是 CRI 的客户端,只要通过 --runtime-endpoint
参数传递符合 CRI 标准的unix sock,它就可以与 CRI daemon 交互。
安装方法:
VERSION="v1.15.0"
wget https://github.com/kubernetes-sigs/cri-tools/releases/download/$VERSION/crictl-$VERSION-linux-amd64.tar.gz
sudo tar zxvf crictl-$VERSION-linux-amd64.tar.gz -C /usr/local/bin
rm -f crictl-$VERSION-linux-amd64.tar.gz
crictl --help
NAME:
crictl - client for CRI
USAGE:
crictl [global options] command [command options] [arguments...]
VERSION:
v1.15.0
COMMANDS:
.....
在 node2 上配置 kubelet 使用 cri-o
安装 cri-o:
yum install yum-utils
yum-config-manager --add-repo=https://cbs.centos.org/repos/paas7-crio-311-candidate/x86_64/os/
yum install --nogpgcheck cri-o
修改 kubelet 启动参数(也可以写在EnvironmentFile
指定的文件里):
vim /lib/systemd/system/kubelet.service
[Unit]
......
[Service]
......
ExecStart=/k8s/kubernetes/bin/kubelet $KUBELET_OPTS --container-runtime=remote --container-runtime-endpoint=/var/run/crio/crio.sock --cgroup-driver=systemd
......
[Install]
.......
启动 crio:
systemctl start crio
使用 crictl 查看版本:
[root@node02 ~]# crictl version
Version: 0.1.0
RuntimeName: cri-o
RuntimeVersion: 1.11.11-1.rhaos3.11.git474f73d.el7
RuntimeApiVersion: v1alpha1
重启 kubelet
systemctl restart kubelet
观察容器情况
快速创建一个 pod,k8s 会根据各节点的负载情况进行调度:
kubectl create -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: busybox1
namespace: default
spec:
containers:
- image: busybox
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
name: busybox
restartPolicy: Always
EOF
pod 是 k8s 的概念,可以理解成容器组。一个 pod 里一般会有一个 pause 容器和其他一个或多个容器。
在 master 上
[root@master yaml]# kubectl get po --all-namespaces -o wide | grep 13.61
default busybox1 1/1 Running 0 18m 10.88.42.142 10.10.13.61 <none> <none>
kube-system traefik-ingress-lb-cmkx9 1/1 Running 5 7d5h 10.10.13.61 10.10.13.61 <none> <none>
busybox1 被调度到了 master 上,此时 master 上有两个 pod。
[root@master yaml]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3cc0ec11447c tomcat "catalina.sh run" 3 weeks ago Up 45 hours 0.0.0.0:8088->8080/tcp pilot_compose_tomcat_1
cf8911b66df5 registry.local/frp:20190613 "/frpc -c /conf/frpc…" 6 weeks ago Up 45 hours 80/tcp, 443/tcp, 6000/tcp, 7000/tcp, 7500/tcp frpc
通过 docker 的命令可以看到有两个容器,但不是 k8s 启动的 busybox1 或者 traefik-ingress。
ps
一下:
[root@master yaml]# ps -ef | grep containerd
root 5904 1 0 Jul28 ? 00:23:38 /usr/bin/containerd
root 7824 5904 0 Jul28 ? 00:00:05 containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/cf8911b66df50d267e7bd6699dc38c2c4e5b7324ce9c9bf2108800b957035813 -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc
root 7833 5904 0 Jul28 ? 00:00:05 containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/3cc0ec11447cbe50dde34474e1936fb940a2c7ebb49cd6099cf70f742992f60b -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc
root 9574 5904 0 Jul28 ? 00:00:04 containerd-shim -namespace k8s.io -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/k8s.io/81cdaebde5e69ea08c14e2567b69fb76dcde38023eac7bf24a6993a99b2485ac -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /run/runc
root 14382 5904 0 Jul28 ? 00:00:06 containerd-shim -namespace k8s.io -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/k8s.io/1fc14cf22c0fc99ccf9c1e08709a96dacbce70d2e548712e15c22bd07e64270f -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /run/runc
root 15449 5904 0 15:38 ? 00:00:00 containerd-shim -namespace k8s.io -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/k8s.io/fcf197c1366c21624a0cefe8d0975066956dfae6b1bb0bddbdbaa018354fdca4 -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /run/runc
root 15628 5904 0 15:38 ? 00:00:00 containerd-shim -namespace k8s.io -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/k8s.io/ccc4612d874ef21c9bd36976cd815280e837830c3e01170c6c13f6b0687d3d64 -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /run/runc
一共有6个containerd-shim进程,都是containerd的子进程。2个namespace为moby,是docker启动的,4个是k8s.io,由kubelet启动。因为 k8s 每个 pod 都有一个 pause 容器,所以和我们之前看到的两个pod是能对应上的。
[root@master yaml]# ps -ef | grep 15628
root 15628 5904 0 15:38 ? 00:00:00 containerd-shim -namespace k8s.io -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/k8s.io/ccc4612d874ef21c9bd36976cd815280e837830c3e01170c6c13f6b0687d3d64 -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /run/runc
root 15655 15628 0 15:38 ? 00:00:00 sleep 3600
15655 这个进程就是我们刚刚运行的 busybox1,它是 15628 containerd-shim 的子进程。
也可以使用 containerd 的 cli 工具: ctr 来观察
[root@master yaml]# ctr -n moby t ls
TASK PID STATUS
cf8911b66df50d267e7bd6699dc38c2c4e5b7324ce9c9bf2108800b957035813 7892 RUNNING
3cc0ec11447cbe50dde34474e1936fb940a2c7ebb49cd6099cf70f742992f60b 7915 RUNNING
[root@master yaml]# ctr -n k8s.io t ls
TASK PID STATUS
81cdaebde5e69ea08c14e2567b69fb76dcde38023eac7bf24a6993a99b2485ac 9602 RUNNING
fcf197c1366c21624a0cefe8d0975066956dfae6b1bb0bddbdbaa018354fdca4 15502 RUNNING
ccc4612d874ef21c9bd36976cd815280e837830c3e01170c6c13f6b0687d3d64 15655 RUNNING
1fc14cf22c0fc99ccf9c1e08709a96dacbce70d2e548712e15c22bd07e64270f 14448 RUNNING
15655 只存在 k8s.io ns 下。
使用 crictl
[root@master yaml]# crictl -r /run/containerd/containerd.sock version
Version: 0.1.0
RuntimeName: containerd
RuntimeVersion: 1.2.5
RuntimeApiVersion: v1alpha2
[root@master yaml]# crictl -r /run/containerd/containerd.sock ps --all
CONTAINER ID IMAGE CREATED STATE NAME ATTEMPT POD ID
ccc4612d874ef db8ee88ad75f6 28 minutes ago Running busybox 0 fcf197c1366c2
1fc14cf22c0fc 18471c10e6e4b 45 hours ago Running traefik-ingress-lb 5 81cdaebde5e69
crictl 也是看不到 moby ns 下的容器的。究其原因,是因为 dockerd 与 contianerd 交互未必符合 CRI 标准,kubelet 是在内置的 dockershim 里实现 CRI 。dockershim 通过 restful 接口调用 dockerd。
因为 containerd 使用 oci-runtime 都是 runc,所以我们可以用runc来查看所有的容器:
[root@master runc]# runc -root /run/runc/k8s.io list
ID PID STATUS BUNDLE CREATED OWNER
1fc14cf22c0fc99ccf9c1e08709a96dacbce70d2e548712e15c22bd07e64270f 14448 running /run/containerd/io.containerd.runtime.v1.linux/k8s.io/1fc14cf22c0fc99ccf9c1e08709a96dacbce70d2e548712e15c22bd07e64270f 2019-07-28T11:08:04.234695321Z root
81cdaebde5e69ea08c14e2567b69fb76dcde38023eac7bf24a6993a99b2485ac 9602 running /run/containerd/io.containerd.runtime.v1.linux/k8s.io/81cdaebde5e69ea08c14e2567b69fb76dcde38023eac7bf24a6993a99b2485ac 2019-07-28T11:07:55.379537513Z root
ccc4612d874ef21c9bd36976cd815280e837830c3e01170c6c13f6b0687d3d64 15655 running /run/containerd/io.containerd.runtime.v1.linux/k8s.io/ccc4612d874ef21c9bd36976cd815280e837830c3e01170c6c13f6b0687d3d64 2019-07-30T07:38:25.252221456Z root
fcf197c1366c21624a0cefe8d0975066956dfae6b1bb0bddbdbaa018354fdca4 15502 running /run/containerd/io.containerd.runtime.v1.linux/k8s.io/fcf197c1366c21624a0cefe8d0975066956dfae6b1bb0bddbdbaa018354fdca4 2019-07-30T07:38:24.908954234Z root
[root@master runc]# runc -root /var/run/docker/runtime-runc/moby list
ID PID STATUS BUNDLE CREATED OWNER
3cc0ec11447cbe50dde34474e1936fb940a2c7ebb49cd6099cf70f742992f60b 7915 running /run/containerd/io.containerd.runtime.v1.linux/moby/3cc0ec11447cbe50dde34474e1936fb940a2c7ebb49cd6099cf70f742992f60b 2019-07-28T11:07:48.327338536Z root
cf8911b66df50d267e7bd6699dc38c2c4e5b7324ce9c9bf2108800b957035813 7892 running /run/containerd/io.containerd.runtime.v1.linux/moby/cf8911b66df50d267e7bd6699dc38c2c4e5b7324ce9c9bf2108800b957035813 2019-07-28T11:07:48.167570109Z root
-root
的值可以通过上面 container-shim 的 -runtime-root
获得。
在 node1 上
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
eb6574aa4f0b quay.io/external_storage/nfs-client-provisioner "/nfs-client-provisi…" 45 hours ago Up 45 hours k8s_nfs-client-provisioner_nfs-client-provisioner-6c8c5fb7d4-88bnx_default_14321ec0-ac56-11e9-b88b-00505699ed79_9
a6c75aa56bb5 registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0 "/pause" 45 hours ago Up 45 hours k8s_POD_nfs-client-provisioner-6c8c5fb7d4-88bnx_default_14321ec0-ac56-11e9-b88b-00505699ed79_1
c6200aa9db3c ac22eb1f780e "/tiller" 46 hours ago Up 46 hours k8s_tiller_tiller-deploy-767d9fb945-7rjtr_kube-system_d1dd3461-af57-11e9-b88b-00505699ed79_1
3ba6a0a3bc05 registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0 "/pause" 46 hours ago Up 46 hours k8s_POD_tiller-deploy-767d9fb945-7rjtr_kube-system_d1dd3461-af57-11e9-b88b-00505699ed79_1
cff0cdb06c7f eb516548c180 "/coredns -conf /etc…" 46 hours ago Up 46 hours k8s_coredns_coredns-747b485444-r8c94_kube-system_de1d5186-a857-11e9-94c1-00505699ed79_561
c8ba17a43d5d registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0 "/pause" 46 hours ago Up 46 hours k8s_POD_coredns-747b485444-r8c94_kube-system_de1d5186-a857-11e9-94c1-00505699ed79_1
[root@node01 ~]# crictl version
Version: 0.1.0
RuntimeName: docker
RuntimeVersion: 18.09.6
RuntimeApiVersion: 1.39.0
[root@node01 ~]# crictl ps
CONTAINER ID IMAGE CREATED STATE NAME ATTEMPT POD ID
eb6574aa4f0b4 quay.io/external_storage/nfs-client-provisioner@sha256:022ea0b0d69834b652a4c53655d78642ae23f0324309097be874fb58d09d2919 45 hours ago Running nfs-client-provisioner 9 a6c75aa56bb59
c6200aa9db3cc ac22eb1f780e4 46 hours ago Running tiller 1 3ba6a0a3bc05b
cff0cdb06c7f0 eb516548c180f 46 hours ago Running coredns 561 c8ba17a43d5d2
crictl 默认是连接 dockershim,而 dockershim 实际上是kubelet 在监听。 crictl help 可以看到 -r 的默认值:
--runtime-endpoint value, -r value Endpoint of CRI container runtime service (default: "unix:///var/run/dockershim.sock") [$CONTAINER_RUNTIME_ENDPOINT]
[root@node01 ~]# netstat -nlp | grep dockershim
unix 2 [ ACC ] STREAM LISTENING 29605 1938/kubelet /var/run/dockershim.sock
[root@node01 ~]# crictl -r /var/run/dockershim.sock version
Version: 0.1.0
RuntimeName: docker
RuntimeVersion: 18.09.6
RuntimeApiVersion: 1.39.0
因为 contianerd 没有 启用 CRI 插件,所以无法使用 crictl 连接
[root@node01 ~]# cat /etc/containerd/config.toml | grep cri
disabled_plugins = ["cri"]
[root@node01 ~]# crictl -r /run/containerd/containerd.sock version
FATA[0000] getting the runtime version failed: rpc error: code = Unimplemented desc = unknown service runtime.v1alpha2.RuntimeService
使用 ctr 只能看到 moby ns 的容器:
[root@node01 ~]# ctr namespaces ls
NAME LABELS
moby
在 node2 上
[root@node02 userdata]# ps -ef | grep crio
root 31241 1 0 15:31 ? 00:00:00 /usr/libexec/crio/conmon -s -c f9f42b5ae54c71180ab7eb1706205bba79597d019f1c3e22f5f68e0b0ae055a8 -u f9f42b5ae54c71180ab7eb1706205bba79597d019f1c3e22f5f68e0b0ae055a8 -r /usr/sbin/runc -b /var/run/containers/storage/overlay-containers/f9f42b5ae54c71180ab7eb1706205bba79597d019f1c3e22f5f68e0b0ae055a8/userdata -p /var/run/containers/storage/overlay-containers/f9f42b5ae54c71180ab7eb1706205bba79597d019f1c3e22f5f68e0b0ae055a8/userdata/pidfile -l /var/log/pods/default_busybox-54f48547c7-j9fp9_10a3ff6f-b29b-11e9-8df0-00505699ed79/f9f42b5ae54c71180ab7eb1706205bba79597d019f1c3e22f5f68e0b0ae055a8.log --exit-dir /var/run/crio/exits --socket-dir-path /var/run/crio --log-level error
root 31716 1 0 15:33 ? 00:00:12 /usr/bin/crio --runtime=/usr/sbin/runc --pause-image="registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0"
root 31773 1 0 15:34 ? 00:00:00 /usr/libexec/crio/conmon -s -c fbe5b37ad3c472ea970af75afc4c58481c2dd4d89a93b1a7ca37ddda823b201c -u fbe5b37ad3c472ea970af75afc4c58481c2dd4d89a93b1a7ca37ddda823b201c -r /usr/sbin/runc -b /var/run/containers/storage/overlay-containers/fbe5b37ad3c472ea970af75afc4c58481c2dd4d89a93b1a7ca37ddda823b201c/userdata -p /var/run/containers/storage/overlay-containers/fbe5b37ad3c472ea970af75afc4c58481c2dd4d89a93b1a7ca37ddda823b201c/userdata/pidfile -l /var/log/pods/default_busybox-54f48547c7-j9fp9_10a3ff6f-b29b-11e9-8df0-00505699ed79/busybox/0.log --exit-dir /var/run/crio/exits --socket-dir-path /var/run/crio --log-level error -t
[root@node02 userdata]# ps -ef | grep 31773
root 31785 31773 0 15:34 ? 00:00:00 /bin/sh
可见 conmon 有些类似 containerd 的container-shim,作为容器进程的父进程存在。
crictl -r /var/run/crio/crio.sock version
Version: 0.1.0
RuntimeName: cri-o
RuntimeVersion: 1.11.11-1.rhaos3.11.git474f73d.el7
RuntimeApiVersion: v1alpha1
[root@node02 ~]# crictl -r /var/run/crio/crio.sock ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID
fbe5b37ad3c47 docker.io/library/busybox@sha256:895ab622e92e18d6b461d671081757af7dbaa3b00e3e28e12505af7817f73649 About an hour ago Running busybox 0 f9f42b5ae54c7
[root@node02 userdata]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@node02 runc]# runc list
ID PID STATUS BUNDLE CREATED OWNER
f9f42b5ae54c71180ab7eb1706205bba79597d019f1c3e22f5f68e0b0ae055a8 31252 running /run/containers/storage/overlay-containers/f9f42b5ae54c71180ab7eb1706205bba79597d019f1c3e22f5f68e0b0ae055a8/userdata 2019-07-30T07:31:54.768990081Z root
fbe5b37ad3c472ea970af75afc4c58481c2dd4d89a93b1a7ca37ddda823b201c 31785 running /run/containers/storage/overlay-containers/fbe5b37ad3c472ea970af75afc4c58481c2dd4d89a93b1a7ca37ddda823b201c/userdata 2019-07-30T07:34:15.524324699Z root
docker ps 同样看不到 crio 创建的容器,而 runc 可以看到 pause 容器和主容器。
总结
-
容器运行时是管理容器和容器镜像的程序。有两个标准,一个是 CRI-runtime,抽象了 kubelet 如何启动和管理容器,一个是 OCI-runtime,抽象了怎么调用内核 API 来管理容器。标准实际上是定义了一系列接口,让上层应用与底层实现接耦。
-
实现 CRI 的 runtime 有 CRI-O、CRI-containred 等,CRI 的命令行客户端是 crictl。containerd 的客户端是 ctr。dockerd 的客户端是 docker。它们通过 unix sock 与对应的 daemon 交互。
-
OCI 的默认实现是 runc。runc 是一个命令行工具,而不是一个 daemon。通过 runc 我们可以手动启动一个容器,也可以查看其他进程启动的容器。
-
进一步学习 runtime,可以看 runc/contianrd/cri-o 的源码。熟悉 runtime,要获取容器的相关信息会更方便,比如 metric、log。更进一步可以实现自己的 CRI/OCI runtime。
参考链接
画图工具: asciiflow