一、k8s中容器运行时的选择问题
Kubelet 的职责在于通过 RPC 管理容器的生命周期,实现容器生命周期的钩子,以及存活和健康监测,执行 Pod 的重启策略等。
Pod是k8s调度的最小单位,pod由一组应用容器组成,其中包含了共有的环境和资源约束。在 CRI 里,这个环境被称为 PodSandbox。
在启动 Pod 之前,Kubelet 调用 RuntimeService.RunPodSandbox 来创建环境。
这一过程包括为 Pod 设置网络(分配 IP);
PodSandbox 激活之后,就可以独立的创建、启动、停止和删除不同的容器了;
Sandbox(沙箱)即容器本身,是容器而非pod的API,之所以叫PodSandbox是因为创建的沙箱(容器)用于部署pod;
Kubelet 会在停止和删除 PodSandbox 之前首先停止和删除其中的容器。
1、 主流容器运行时:dockershim、Containerd与CRI-O对比
Docker
对Docker来说,负责响应这个请求的就是一个叫作 dockershim 的组件,它把 CRI 请求里的内容拿出来,然后组装成 Docker API 请求发给 Docker Daemon。对于别的容器项目来说,扮演shim角色的组件统称为CRI-shim,可见,CRI-shim就是实现CRI中定义的每一个接口,然后把具体的 CRI 请求“翻译”成对后端容器项目的请求或者操作。
docker-shim
注意一点的是,我们在部署docker时并没有部署这个docker-shim组件,docker-shim代码是包含在kubelet中的,但是其它的容器项目,就需要在hosts中部署自己的CRI-shim了。
Docker项目是C/S架构,Docker client端发送请求给docker daemon采用root用户运行在host上,通过socket进行通信,docker daemon接收创建容器的请求再发送给containerd,容器进程是docker daemon的子进程,而不是dockerCLI的子进程。
可以通过查看进程树
# pstree -p
systemd(1)-+-agetty(3596)
|-crond(25481)
|-dbus-daemon(3498)
|-dhclient(3744)
|-dockerd(8050)-+-containerd(8062)-+-containerd-shim(7104)-+-pause(7123)
| | | |-{containerd-shim}(7105)
| | | |-{containerd-shim}(7106)
| | | |-{containerd-shim}(7107)
| | | |-{containerd-shim}(7108)
| | | |-{containerd-shim}(7109)
| | | |-{containerd-shim}(7110)
| | | |-{containerd-shim}(7111)
| | | |-{containerd-shim}(7112)
| | | |-{containerd-shim}(7152)
| | | `-{containerd-shim}(8816)
| | |-containerd-shim(7327)-+-containerd(17056)-+-{containerd}(17061)
| | | | |-{containerd}(17062)
| | | | |-{containerd}(17063)
| | | | |-{containerd}(17064)
| | | | |-{containerd}(17067)
| | | | `-{containerd}(17068)
CRI-containerd
CNCF的containerd就是充当了CRI-shim的角色,kubelet调用CRI,containerd响应CRI请求,进而调用runc创建容器,runc才是真正通过设置namesapce,cgroup,chroot的创建容易的幕后英雄。
CRI-O的范围
CRI-O的目的是提供一种在OCI一致的运行时和kubelet之间的集成方式,它实现了kubelet容器运行时的接口,CRI-O的范围与CRI的范围相关。
从更高的角度来看,CRI-O能够在一下功能发展的更加严格规范:
支持更多image的格式包括目前使用的docker image的格式;
支持更多的方式来下载image包括对image的可信和验证;
容器镜像管理(管理image的层,文件系统);
容器进程的生命周期管理;
CRI所需求的monitoring和logging;
CRI需求的资源隔离
CRI-O 目前支持的功能
- Pod和container的lifecycle
- Image lifecycle
- CNI网络集成
- Logging
- Exec(sync/streaming)
- Attach/Detach
- Port forwarding
- OOM 检测和汇报
- daemon 重启
- 支持多种存储插件(overlay,devicemapper,aufs,btrfs)
- Selinux
- Seccomp
- 清除容器
- 支持runc
- Gpg check on image pull
- Mixed runtimes (runc and Clear Containers)
2、为什么 CRI 是围绕容器进行的?
Kubernetes 有一个 Pod 资源的接口。CRI开发团队曾经可能采用的一个 CRI 的设计就是抽象复用 Pod 对象,容器运行时就可以自行实现自己的控制逻辑和状态转换,这样一来,就能极大地简化 API,让 CRI 能够更广泛的适用于多种容器运行时。但是CRI开发团队经过深入讨论之后,放弃了这一想法。
首先,Kubelet 有很多的 Pod 级功能和机制(例如循环崩溃的处理),交给容器运行时实现的话,会造成很重的负担;第二,更重要的是,Pod 标准还在高速前进。很多的新功能(例如容器初始化)是由 Kubelet 直接管理容器的,而无需容器运行时进行变更。
CRI 选择了围绕容器进行实现,这样容器运行时能够共享这些通用特性,获得更好的开发进度
二、Pod 的生命周期
Pod 在其生命周期中只会被调度一次。 一旦 Pod 被调度(分派)到某个节点,Pod 会一直在该节点运行,直到 Pod 停止或者被终止。
Pod 自身不具有自愈能力。如果 Pod 被调度到某节点而该节点之后失效, Pod 会被删除;类似地,Pod 无法在因节点资源耗尽或者节点维护而被驱逐期间继续存活。
1、Pod的唯一标识UID
任何给定的 Pod (由 UID 定义)从不会被“重新调度(rescheduled)”到不同的节点; 相反,这一 Pod 可以被一个新的、几乎完全相同的 Pod 替换掉。 如果需要,新 Pod 的名字可以不变,但是其 UID 会不同。
openshift环境下可以通过下面命令查询出pod的uid
oc get po gange3-system-67bd9b5884-822fc -oyaml|grep uid
uid: 4bf83ef1-0992-4782-a934-174e5b81c00a
2、Pod 就绪态的状态
命令 kubectl patch 不支持修改对象的状态。 如果需要设置 Pod 的 status.conditions,应用或者 Operators 需要使用 PATCH 操作。你可以使用 Kubernetes 客户端库之一来编写代码, 针对 Pod 就绪态设置定制的 Pod 状况。
对于使用定制状况的 Pod 而言,只有当下面的陈述都适用时,该 Pod 才会被评估为就绪:
- Pod 中所有容器都已就绪;
readinessGates
中的所有状况都为True
值。
当 Pod 的容器都已就绪,但至少一个定制状况没有取值或者取值为 False, kubelet 将 Pod 的状况设置为 ContainersReady。
3、Pod内容器探针探测类型
针对运行中的容器,kubelet
可以选择是否执行以下三种探针,以及如何针对探测结果作出反应:
livenessProbe
指示容器是否正在运行。如果存活态探测失败,则 kubelet 会杀死容器, 并且容器将根据其重启策略决定未来。如果容器不提供存活探针, 则默认状态为 Success
。
readinessProbe
指示容器是否准备好为请求提供服务。如果就绪态探测失败, 端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址。 初始延迟之前的就绪态的状态值默认为 Failure
。 如果容器不提供就绪态探针,则默认状态为 Success
。
startupProbe
指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被 禁用,直到此探针成功为止。如果启动探测失败,kubelet
将杀死容器, 而容器依其重启策略进行重启。 如果容器没有提供启动探测,则默认状态为 Success
。
4、常见pod的状态转换
Pod的容器数 | Pod当前状态 | 发生的事件 | Pod结果状态 |
|
|
|
|
| RestartPolicy=Always | RestartPolicy=OnFailure | RestartPolicy=Never |
包含一个容器 | Running | 容器成功退出 | Running | Succeeded | Succeeded |
包含一个容器 | Running | 容器失败退出 | Running | Running | Failure |
包含两个容器 | Running | 1个容器失败退出 | Running | Running | Running |
包含两个容器 | Running | 容器被OOM杀掉 | Running | Running | Failure |
5、容器运行时内存超出限制
- 容器以失败状态终止。
- 记录 OOM 事件。
- 如果
restartPolicy
为:- Always:重启容器;Pod
phase
仍为 Running。 - OnFailure:重启容器;Pod
phase
仍为 Running。 - Never: 记录失败事件;Pod
phase
仍为 Failed。
- Always:重启容器;Pod
三、实战篇-OpenShift4节点运行容器过程
OpenShift 4的集群节点使用了基于CRI-O的容器运行环境。每个节点的kubelet通过gRPC调用CRI-O,而CRI-O运行符合OCI规范的容器。
1、Master给节点上的kubelet服务发送来一个启动一个新pod的请求
systemctl status kubelet|grep kubelet.conf
└─2303 kubelet --config=/etc/kubernetes/kubelet.conf --bootstrap-kubeconfig=/etc/kubernetes/kubeconfig --kubeconfig=/var/lib/kubelet/kubeconfig --container-runtime=remote --container-runtime-endpoint=/var/run/crio/crio.sock --runtime-cgroups=/system.slice/crio.service --node-labels=node-role.kubernetes.io/worker,node.openshift.io/os_id=rhcos --node-ip=10.19.59.97 --minimum-container-ttl-duration=6m0s --volume-plugin-dir=/etc/kubernetes/kubelet-plugins/volume/exec --cloud-provider= --hostname-override= --pod-infra-container-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:a978dee2cb368c8d5ec2682d0bc8b23f1b1282b15c3abf03b087006f97aad6f8 --system-reserved=cpu=500m,memory=5.5Gi --v=2
2、kubelet将请求通过CRI转发给CRI-O守护进程来启动新的POD
查看crictl的配置,缺省crictl连接的是该节点本地的“unix:///var/run/crio/crio.sock”
cat /etc/crictl.yaml
runtime-endpoint: "unix:///var/run/crio/crio.sock"
timeout: 0
debug: false
查看crio运行环境的配置文件中的“相关”参数
cat /etc/crio/crio.conf | grep -v "#" | sed '/^$/d'
[crio]
[crio.runtime]
selinux = true
[crio.network]
plugin_dirs = [
"/usr/libexec/cni",
]
[crio.metrics]
enable_metrics = true
metrics_port = 9537
查看crio系统服务的配置文件
more /usr/lib/systemd/system/crio.service
[Unit]
Description=Container Runtime Interface for OCI (CRI-O)
……………
[Service]
Type=notify
EnvironmentFile=-/etc/sysconfig/crio
Environment=GOTRACEBACK=crash
ExecStart=/usr/bin/crio \
$CRIO_CONFIG_OPTIONS \
$CRIO_RUNTIME_OPTIONS \
$CRIO_STORAGE_OPTIONS \
$CRIO_NETWORK_OPTIONS \
$CRIO_METRICS_OPTIONS
ExecReload=/bin/kill -s HUP $MAINPID
TasksMax=infinity
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
OOMScoreAdjust=-999
TimeoutStartSec=0
Restart=on-abnormal
用户可以设置的 oom_score_adj,范围是 -1000到 1000
对于oom_score_adj参数是-999的表示kubernetes永远不会因为OOM而被kill掉(最后被删掉,配置-1000则linux永远不会回收)。
3、查看crio服务的运行状态,ciro是一个守护进程
从“MCO environment configuration”可以看出crio的配置是由OpenShift的MCO控制的。
systemctl status crio
4、CRI-O使用从容器镜像仓库中拉取Image
5、下载的Image会被解压到容器的根文件系统中
6、OCI Runtime 之 runc相关查询命令
在为容器创建了根文件系统后,CRI-O会生成一个OCI运行时规范json文件,描述如何使用OCI生成工具运行容器。CRI-O 使用该规范启动 OCI 兼容运行时以运行容器进程。默认的 OCI Runtime 是 runc
a、使用runc list可以列出当前环境中所有的container,ID表示containerID PID表示容器的进程ID,其中stopped的容器进程ID为0。
runc list|head -5
b、通过containerID我们可以查看container的监控进程:
ps -ef | grep 0095730803c5c0368a69e18a8167a85d091b705d8c8d5733c05e1a6a3075ceed
root 5728 1 0 Feb18 ? 00:00:00 /usr/bin/conmon -b /run/containers/storage/overlay-containers/0095730803c5c0368a69e18a8167a85d091b705d8c8d5733c05e1a6a3075ceed/userdata
7、conmon(container monitor)进程
每个容器由一个单独的conmon(container monitor)进程监控。
它处理容器的日志,并记录容器进程的退出代码。
conmon会负责为每一个container起一个进程来监控容器额定运行,从它的启动参数我们可以看出,它会监控容器的log,socket信息,退出信息,还可以通过pidfile查看监控进程所监控的容器的服务进程ID:
a、执行以下命令查找test-jcjg-gange-2-dev-oracle项目中system相关的Pod相关信息
crictl pods --namespace test-jcjg-gange-2-dev-oracle
MCD_POD_NAME=gange3-system-6f85d4b6c8-h2mgc
MCD_POD_ID=fd68b752333a6
MCD_FULL_POD_ID=$(crictl inspectp $MCD_POD_ID | jq .status.id | cut -d "\"" -f 2)
b、根据MCD_FULL_POD_ID查找该Pod包含的Container。可以看到该Pod中有1个容器,名称是
crictl ps -p $MCD_FULL_POD_ID
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID
16c23fd258aec registry-dev.hisense.com/test-jcjg-gange-2-dev-oracle/gange3-system@sha256:a9302fc3dd79ddd5146c32d00826426f5ead70706e9346d2f2489d81d5144257 7 minutes ago Running gange3-system 0 fd68b752333a6
c、查询系统中同时包括conmon和machine-config-daemon的进程。可以看到有1个“/usr/bin/conmon”进程,它们使用的参数对应了上一步的1个容器和Pod本身。
ps -ef | grep $MCD_POD_NAME | grep conmon
root 3893409 1 0 06:41 ? 00:00:02 /usr/bin/conmon -b /run/containers/storage/overlay-containers/16c23fd258aec82ac1feb662849a10d2ffdf17e027021d562b1e04f32343ad50/userdata -c 16c23fd258aec82ac1feb662849a10d2ffdf17e027021d562b1e04f32343ad50 --exit-dir /var/run/crio/exits -l /var/log/pods/test-jcjg-gange-2-dev-oracle_gange3-system-6f85d4b6c8-h2mgc_4bf83ef1-0992-4782-a934-174e5b81c00a/gange3-system/0.log --log-level info -n k8s_gange3-system_gange3-system-6f85d4b6c8-h2mgc_test-jcjg-gange-2-dev-oracle_4bf83ef1-0992-4782-a934-174e5b81c00a_0 -P /run/containers/storage/overlay-containers/16c23fd258aec82ac1feb662849a10d2ffdf17e027021d562b1e04f32343ad50/userdata/conmon-pidfile -p /run/containers/storage/overlay-containers/16c23fd258aec82ac1feb662849a10d2ffdf17e027021d562b1e04f32343ad50/userdata/pidfile --persist-dir /var/lib/containers/storage/overlay-containers/16c23fd258aec82ac1feb662849a10d2ffdf17e027021d562b1e04f32343ad50/userdata -r /usr/bin/runc --runtime-arg --root=/run/runc --socket-dir-path /var/run/crio -u 16c23fd258aec82ac1feb662849a10d2ffdf17e027021d562b1e04f32343ad50 -s
格式:namespace + podname + uid