0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Kubernetes容器运行时containerd与CRI-O如何选择

马哥Linux运维 来源:马哥Linux运维 2026-02-26 09:54 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

一、概述

1.1 背景介绍

Kubernetes 1.24版本正式移除了dockershim,Docker不再是K8s的默认容器运行时。这个变化直接影响了所有K8s集群的运维方式——升级到1.24+必须切换到containerd或CRI-O。

容器运行时分两层:高级运行时(High-level Runtime)负责镜像管理、容器生命周期管理、API接口;低级运行时(Low-level Runtime)负责实际创建和运行容器。containerd和CRI-O都是高级运行时,底层都调用runc(或其他OCI兼容的低级运行时)来创建容器。

containerd从Docker项目中拆分出来,是Docker架构的核心组件。即使你用Docker,底层也是containerd在管理容器。containerd功能全面,既能独立使用也能作为K8s的运行时。

CRI-O是Red Hat主导开发的,专门为Kubernetes设计的容器运行时。它只实现了CRI(Container Runtime Interface)接口,不提供docker build、docker push这些功能。设计哲学是"够用就好",代码量和攻击面都比containerd小。

两者都是CNCF毕业项目,生产环境都经过大规模验证。选哪个取决于你的技术栈和运维习惯。

1.2 技术特点

containerd特点

功能全面:支持镜像拉取/推送、容器生命周期管理、快照管理、内容存储,可以完全替代Docker

生态成熟:Docker、K8s、AWS EKS、Google GKE都用containerd

插件架构:通过插件扩展功能,支持多种快照驱动(overlayfs、btrfs、zfs等)

命名空间隔离:不同命名空间的容器和镜像互相隔离,K8s用k8s.io命名空间

CRI-O特点

专为K8s设计:只实现CRI接口,不做多余的事,代码精简

版本对齐:CRI-O版本号和K8s版本号一一对应(CRI-O 1.28对应K8s 1.28),兼容性有保障

安全性高:代码量少意味着攻击面小,Red Hat在安全方面投入大

OpenShift默认:Red Hat OpenShift的默认运行时,企业级支持

1.3 适用场景

containerd:通用场景,从Docker迁移的集群,需要独立使用容器运行时(不依赖K8s),AWS EKS/Google GKE环境

CRI-O:纯K8s环境,Red Hat/OpenShift技术栈,追求最小化运行时,安全要求高的场景

两者都适合:标准K8s集群的容器运行时,替代dockershim

1.4 环境要求

组件 containerd CRI-O 说明
操作系统 CentOS 7+/Ubuntu 18.04+ CentOS 8+/Ubuntu 20.04+ CRI-O对旧系统支持较差
内核版本 3.10+(推荐5.4+) 4.18+(推荐5.4+) CRI-O需要较新内核
K8s版本 1.20+ 1.20+(推荐版本对齐) 1.24+必须用CRI兼容运行时
runc 1.1+ 1.1+ 底层OCI运行时
CNI插件 1.0+ 1.0+ 容器网络接口
CPU 2核+ 2核+ 运行时本身开销很小
内存 4GB+ 4GB+ 主要是K8s组件和业务容器的需求

二、详细步骤

2.1 准备工作

2.1.1 系统检查

# 检查系统版本
cat /etc/os-release

# 检查内核版本
uname -r

# 检查当前容器运行时
crictl version 2>/dev/null ||echo"crictl not installed"
docker version 2>/dev/null ||echo"docker not installed"
containerd --version 2>/dev/null ||echo"containerd not installed"
crio --version 2>/dev/null ||echo"cri-o not installed"

# 检查K8s版本(如果已安装)
kubectl version --short 2>/dev/null
kubelet --version 2>/dev/null

# 检查内核模块
lsmod | grep -E"overlay|br_netfilter"

# 检查系统资源
free -h
df -h
nproc

2.1.2 前置配置(containerd和CRI-O通用)

# 加载必要的内核模块
cat <

2.2 核心配置

2.2.1 containerd安装与配置

CentOS/RHEL安装

# 添加Docker仓库(containerd在Docker仓库中)
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# 安装containerd
sudo yum install -y containerd.io

# 生成默认配置文件
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

Ubuntu/Debian安装

# 添加Docker仓库
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

echo"deb [arch=$(dpkg --print-architecture)signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu 
$(. /etc/os-release && echo "$VERSION_CODENAME")stable"| sudo tee /etc/apt/sources.list.d/docker.list

# 安装containerd
sudo apt-get update
sudo apt-get install -y containerd.io

# 生成默认配置文件
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

containerd关键配置

# 文件路径:/etc/containerd/config.toml
# 以下只列出需要修改的关键配置项

version = 2

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

 [plugins."io.containerd.grpc.v1.cri".containerd]
  default_runtime_name = "runc"

  [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
   runtime_type = "io.containerd.runc.v2"

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

 [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://mirror.ccs.tencentyun.com", "https://registry-1.docker.io"]

[metrics]
 address = "0.0.0.0:1338"

注意:SystemdCgroup = true这个配置极其关键。如果不设置,containerd默认用cgroupfs驱动,而kubelet默认用systemd驱动,两者不一致会导致kubelet启动失败或Pod状态异常。我见过因为这个配置不一致导致整个集群节点NotReady的事故。

# 修改配置后重启containerd
sudo systemctl restart containerd
sudo systemctlenablecontainerd

# 验证containerd状态
sudo systemctl status containerd

# 验证CRI接口
sudo crictl --runtime-endpoint unix:///run/containerd/containerd.sock info

2.2.2 CRI-O安装与配置

CentOS 8/RHEL 8安装

# 设置版本变量(CRI-O版本和K8s版本对齐)
OS="CentOS_8_Stream"
VERSION="1.28"

# 添加CRI-O仓库
sudo curl -L -o /etc/yum.repos.d/devellibcontainers:stable.repo 
 https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/${OS}/devellibcontainers:stable.repo

sudo curl -L -o /etc/yum.repos.d/devellibcontainerscri-o:${VERSION}.repo 
 https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/${VERSION}/${OS}/devellibcontainerscri-o:${VERSION}.repo

# 安装CRI-O
sudo yum install -y cri-o cri-tools

Ubuntu 22.04安装

OS="xUbuntu_22.04"
VERSION="1.28"

# 添加仓库密钥和源
sudo mkdir -p /usr/share/keyrings
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/${OS}/Release.key 
 | sudo gpg --dearmor -o /usr/share/keyrings/libcontainers-archive-keyring.gpg

curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/${VERSION}/${OS}/Release.key 
 | sudo gpg --dearmor -o /usr/share/keyrings/libcontainers-crio-archive-keyring.gpg

echo"deb [signed-by=/usr/share/keyrings/libcontainers-archive-keyring.gpg] 
 https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/${OS}/ /"
 | sudo tee /etc/apt/sources.list.d/devellibcontainers:stable.list

echo"deb [signed-by=/usr/share/keyrings/libcontainers-crio-archive-keyring.gpg] 
 https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/${VERSION}/${OS}/ /"
 | sudo tee /etc/apt/sources.list.d/devellibcontainerscri-o:${VERSION}.list

sudo apt-get update
sudo apt-get install -y cri-o cri-o-runc cri-tools

CRI-O关键配置

# 文件路径:/etc/crio/crio.conf
# CRI-O的配置比containerd简洁很多,大部分默认值就能用
# 以下只列出需要修改的关键配置项

[crio]
 log_dir = "/var/log/crio/pods"
 version_file = "/var/run/crio/version"
 clean_shutdown_file = "/var/lib/crio/clean.shutdown"

[crio.api]
 listen = "/var/run/crio/crio.sock"
 grpc_max_send_msg_size = 83886080
 grpc_max_recv_msg_size = 83886080

[crio.runtime]
 default_runtime = "runc"
 conmon = "/usr/bin/conmon"
 conmon_cgroup = "pod"
 # 关键:cgroup管理器必须和kubelet一致
 cgroup_manager = "systemd"
 # pause镜像,国内环境换成阿里云镜像
 pause_image = "registry.aliyuncs.com/google_containers/pause:3.9"
 pause_command = "/pause"
 # 容器退出后日志保留的最大数量
 log_size_max = 104857600
 # PID限制
 pids_limit = 4096

[crio.runtime.runtimes.runc]
 runtime_path = "/usr/bin/runc"
 runtime_type = "oci"

[crio.image]
 # 镜像拉取策略
 pause_image = "registry.aliyuncs.com/google_containers/pause:3.9"
 # 镜像签名验证策略
 global_auth_file = "/etc/crio/auth.json"
 image_volumes = "mkdir"

[crio.network]
 network_dir = "/etc/cni/net.d/"
 plugin_dirs = ["/opt/cni/bin/", "/usr/libexec/cni/"]

[crio.metrics]
 enable_metrics = true
 metrics_port = 9537
 metrics_collectors = ["operations", "operations_latency", "operations_errors", "image_pulls_layer_size", "containers_oom_total", "processes_defunct"]

注意:CRI-O的cgroup_manager = "systemd"和containerd的SystemdCgroup = true是同一个意思——告诉运行时用systemd管理cgroup。这个配置必须和kubelet的--cgroup-driver=systemd保持一致,否则Pod创建会失败。

# 启动CRI-O
sudo systemctl daemon-reload
sudo systemctl start crio
sudo systemctlenablecrio

# 验证CRI-O状态
sudo systemctl status crio

# 验证CRI接口
sudo crictl --runtime-endpoint unix:///var/run/crio/crio.sock info

2.2.3 crictl工具配置(通用)

crictl是CRI兼容运行时的命令行工具,类似docker命令但只支持CRI接口。不管用containerd还是CRI-O,都用crictl来操作。

# 配置crictl默认连接的运行时端点
# containerd环境
cat <

crictl常用命令

# 查看运行时信息
crictl info

# 镜像操作
crictl pull nginx:1.24-alpine
crictl images
crictl rmi nginx:1.24-alpine

# 容器操作(K8s环境下一般不直接操作容器)
crictl ps          # 查看运行中的容器
crictl ps -a        # 查看所有容器(含已停止)
crictl logs  # 查看容器日志
crictlexec-it  /bin/sh # 进入容器
crictl inspect      # 查看容器详情

# Pod操作
crictl pods         # 查看所有Pod沙箱
crictl inspectp   # 查看Pod详情

# 资源统计
crictl stats        # 查看容器资源使用
crictl statsp        # 查看Pod资源使用

# 清理(谨慎使用,会影响K8s)
crictl rmp      # 删除Pod沙箱
crictl rm   # 删除容器

注意:crictl和docker命令的最大区别是crictl操作的是Pod和容器两层结构。K8s中每个Pod先创建一个sandbox容器(pause容器),业务容器运行在sandbox的网络命名空间中。用crictl pods看到的是sandbox,crictl ps看到的是业务容器。

2.2.4 kubelet配置对接运行时

kubelet通过CRI接口和容器运行时通信,需要配置正确的socket路径。

# containerd环境的kubelet配置
# 文件路径:/var/lib/kubelet/config.yaml 或 kubelet启动参数
cat <

kubeadm初始化时指定运行时

# containerd环境
sudo kubeadm init 
 --cri-socket unix:///run/containerd/containerd.sock 
 --pod-network-cidr=10.244.0.0/16 
 --image-repository registry.aliyuncs.com/google_containers

# CRI-O环境
sudo kubeadm init 
 --cri-socket unix:///var/run/crio/crio.sock 
 --pod-network-cidr=10.244.0.0/16 
 --image-repository registry.aliyuncs.com/google_containers

2.3 启动和验证

2.3.1 containerd验证

# 检查containerd服务状态
sudo systemctl status containerd
# 预期:active (running)

# 检查containerd版本和配置
containerd --version
crictl info | grep -E"runtimeVersion|cgroupDriver"

# 拉取测试镜像
crictl pull registry.aliyuncs.com/google_containers/pause:3.9
crictl images

# 运行测试Pod(手动创建Pod沙箱,仅用于验证)
cat < /tmp/test-pod.json
{
 "metadata": {
   "name":"test-sandbox",
   "namespace":"default",
   "uid":"test-uid-001"
  },
 "log_directory":"/tmp/test-logs",
 "linux": {}
}
EOF

mkdir -p /tmp/test-logs
POD_ID=$(crictl runp /tmp/test-pod.json)
echo"Pod sandbox created:${POD_ID}"

# 查看Pod状态
crictl pods
crictl inspectp${POD_ID}

# 清理测试Pod
crictl stopp${POD_ID}
crictl rmp${POD_ID}
rm -f /tmp/test-pod.json

2.3.2 CRI-O验证

# 检查CRI-O服务状态
sudo systemctl status crio
# 预期:active (running)

# 检查CRI-O版本
crio --version
crictl info

# 检查CRI-O配置
crio config --default | grep -E"cgroup_manager|pause_image|log_size_max"

# 拉取测试镜像
crictl pull registry.aliyuncs.com/google_containers/pause:3.9
crictl images

# 检查CRI-O的socket文件
ls -la /var/run/crio/crio.sock

# 检查CNI插件
ls /opt/cni/bin/ 2>/dev/null || ls /usr/libexec/cni/
ls /etc/cni/net.d/

2.3.3 K8s集群验证

# 检查节点状态和运行时信息
kubectl get nodes -o wide
# CONTAINER-RUNTIME列显示containerd://1.7.x 或 cri-o://1.28.x

# 检查kubelet使用的运行时
kubectl describe node $(hostname) | grep -i"container runtime"

# 部署测试Pod
kubectl runtest-nginx --image=nginx:1.24-alpine --restart=Never
kubectl get podtest-nginx -o wide

# 验证Pod正常运行
kubectlexectest-nginx -- nginx -v
kubectl logstest-nginx

# 通过crictl查看对应的容器
crictl pods --nametest-nginx
crictl ps --nametest-nginx

# 清理
kubectl delete podtest-nginx

三、示例代码和配置

3.1 完整配置示例

3.1.1 containerd生产环境完整配置

# 文件路径:/etc/containerd/config.toml
# 适用场景:K8s 1.28+ 生产集群节点

version = 2
root = "/var/lib/containerd"
state = "/run/containerd"
oom_score = -999

[grpc]
 address = "/run/containerd/containerd.sock"
 max_recv_message_size = 16777216
 max_send_message_size = 16777216

[debug]
 address = "/run/containerd/debug.sock"
 level = "info"

[metrics]
 address = "0.0.0.0:1338"
 grpc_histogram = false

[plugins."io.containerd.grpc.v1.cri"]
 sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"
 max_container_log_line_size = 16384
 max_concurrent_downloads = 10
 disable_apparmor = false
 restrict_oom_score_adj = false
 tolerate_missing_hugetlb_controller = true

 [plugins."io.containerd.grpc.v1.cri".containerd]
  default_runtime_name = "runc"
  snapshotter = "overlayfs"
  disable_snapshot_annotations = true

  [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
   runtime_type = "io.containerd.runc.v2"
   privileged_without_host_devices = false

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

 [plugins."io.containerd.grpc.v1.cri".cni]
  bin_dir = "/opt/cni/bin"
  conf_dir = "/etc/cni/net.d"
  max_conf_num = 1

 [plugins."io.containerd.grpc.v1.cri".registry]
  config_path = "/etc/containerd/certs.d"

[plugins."io.containerd.gc.v1.scheduler"]
 pause_threshold = 0.02
 deletion_threshold = 0
 mutation_threshold = 100
 schedule_delay = "0s"
 startup_delay = "100ms"

containerd镜像仓库配置(新版方式)

containerd 1.5+推荐用/etc/containerd/certs.d/目录配置镜像仓库,替代config.toml中的registry.mirrors配置。

# 配置Docker Hub镜像加速
sudo mkdir -p /etc/containerd/certs.d/docker.io
cat <

3.1.2 CRI-O生产环境完整配置

# 文件路径:/etc/crio/crio.conf
# 适用场景:K8s 1.28 生产集群节点(CRI-O 1.28)

[crio]
 root = "/var/lib/containers/storage"
 runroot = "/var/run/containers/storage"
 log_dir = "/var/log/crio/pods"
 version_file = "/var/run/crio/version"
 clean_shutdown_file = "/var/lib/crio/clean.shutdown"

[crio.api]
 listen = "/var/run/crio/crio.sock"
 stream_address = "127.0.0.1"
 stream_port = "0"
 grpc_max_send_msg_size = 83886080
 grpc_max_recv_msg_size = 83886080

[crio.runtime]
 default_runtime = "runc"
 no_pivot = false
 decryption_keys_path = "/etc/crio/keys/"
 conmon = "/usr/bin/conmon"
 conmon_cgroup = "pod"
 conmon_env = [
  "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
 ]
 default_env = []
 selinux = false
 seccomp_profile = "/usr/share/containers/seccomp.json"
 apparmor_profile = "crio-default"
 cgroup_manager = "systemd"
 default_capabilities = [
  "CHOWN",
  "DAC_OVERRIDE",
  "FSETID",
  "FOWNER",
  "SETGID",
  "SETUID",
  "SETPCAP",
  "NET_BIND_SERVICE",
  "KILL",
 ]
 default_sysctls = []
 pids_limit = 4096
 log_size_max = 104857600
 log_to_journald = false
 container_exits_dir = "/var/run/crio/exits"
 container_attach_socket_dir = "/var/run/crio"
 bind_mount_prefix = ""
 read_only = false
 uid_mappings = ""
 gid_mappings = ""
 minimum_mappable_uid = -1
 minimum_mappable_gid = -1

 [crio.runtime.runtimes.runc]
  runtime_path = "/usr/bin/runc"
  runtime_type = "oci"
  runtime_root = "/run/runc"
  allowed_annotations = [
   "io.containers.trace-syscall",
  ]

[crio.image]
 default_transport = "docker://"
 global_auth_file = ""
 pause_image = "registry.aliyuncs.com/google_containers/pause:3.9"
 pause_image_auth_file = ""
 pause_command = "/pause"
 signature_policy = ""
 image_volumes = "mkdir"
 big_files_temporary_dir = ""

[crio.network]
 network_dir = "/etc/cni/net.d/"
 plugin_dirs = [
  "/opt/cni/bin/",
  "/usr/libexec/cni/",
 ]

[crio.metrics]
 enable_metrics = true
 metrics_port = 9537
 metrics_socket = ""
 metrics_cert = ""
 metrics_key = ""
 metrics_collectors = [
  "operations",
  "operations_latency",
  "operations_errors",
  "image_pulls_layer_size",
  "containers_oom_total",
  "processes_defunct",
 ]

CRI-O镜像仓库配置

CRI-O使用/etc/containers/registries.conf配置镜像仓库,这是containers/image库的标准配置格式。

# 文件路径:/etc/containers/registries.conf

unqualified-search-registries = ["docker.io", "quay.io"]

[[registry]]
prefix = "docker.io"
location = "docker.io"

[[registry.mirror]]
location = "mirror.ccs.tencentyun.com"

[[registry]]
prefix = "registry.example.com"
location = "registry.example.com"
insecure = false

[[registry]]
# 测试环境HTTP仓库
prefix = "192.168.1.100:5000"
location = "192.168.1.100:5000"
insecure = true

3.1.3 运行时诊断脚本

#!/bin/bash
# 文件名:runtime-diag.sh
# 容器运行时诊断脚本,自动检测当前运行时并输出关键信息

set-euo pipefail

RED='�33[0;31m'
GREEN='�33[0;32m'
YELLOW='�33[1;33m'
NC='�33[0m'

echo"========== 容器运行时诊断 =========="
echo""

# 检测运行时类型
RUNTIME="unknown"
ifsystemctl is-active --quiet containerd 2>/dev/null;then
  RUNTIME="containerd"
  SOCKET="/run/containerd/containerd.sock"
elifsystemctl is-active --quiet crio 2>/dev/null;then
  RUNTIME="crio"
  SOCKET="/var/run/crio/crio.sock"
fi

echo-e"运行时类型:${GREEN}${RUNTIME}${NC}"
echo-e"Socket路径:${SOCKET}"
echo""

# 版本信息
echo"========== 版本信息 =========="
if["$RUNTIME"="containerd"];then
  containerd --version
  runc --version | head -1
elif["$RUNTIME"="crio"];then
  crio --version
  runc --version | head -1
fi
crictl version 2>/dev/null ||echo"crictl未安装"
echo""

# 服务状态
echo"========== 服务状态 =========="
if["$RUNTIME"="containerd"];then
  systemctl status containerd --no-pager -l | head -15
elif["$RUNTIME"="crio"];then
  systemctl status crio --no-pager -l | head -15
fi
echo""

# CRI信息
echo"========== CRI信息 =========="
crictl --runtime-endpoint unix://${SOCKET}info 2>/dev/null | head -20
echo""

# cgroup驱动
echo"========== Cgroup驱动 =========="
if["$RUNTIME"="containerd"];then
  grep -i"systemdcgroup"/etc/containerd/config.toml 2>/dev/null ||echo"未找到SystemdCgroup配置"
elif["$RUNTIME"="crio"];then
  grep"cgroup_manager"/etc/crio/crio.conf 2>/dev/null ||echo"未找到cgroup_manager配置"
fi
echo""

# 镜像列表
echo"========== 镜像列表 =========="
crictl images 2>/dev/null | head -20
echo""

# 容器和Pod统计
echo"========== 容器/Pod统计 =========="
POD_COUNT=$(crictl pods -q 2>/dev/null | wc -l)
CONTAINER_COUNT=$(crictl ps -q 2>/dev/null | wc -l)
echo"运行中的Pod:${POD_COUNT}"
echo"运行中的容器:${CONTAINER_COUNT}"
echo""

# 资源使用
echo"========== 运行时进程资源 =========="
if["$RUNTIME"="containerd"];then
  ps aux | grep containerd | grep -v grep | awk'{printf "PID: %s CPU: %s%% MEM: %s%% RSS: %s
", $2, $3, $4, $6}'
elif["$RUNTIME"="crio"];then
  ps aux | grep crio | grep -v grep | awk'{printf "PID: %s CPU: %s%% MEM: %s%% RSS: %s
", $2, $3, $4, $6}'
fi
echo""

# 磁盘使用
echo"========== 存储使用 =========="
if["$RUNTIME"="containerd"];then
  du -sh /var/lib/containerd/ 2>/dev/null ||echo"无法读取containerd数据目录"
elif["$RUNTIME"="crio"];then
  du -sh /var/lib/containers/ 2>/dev/null ||echo"无法读取CRI-O数据目录"
fi

# 最近错误日志
echo""
echo"========== 最近错误日志(最近10条) =========="
if["$RUNTIME"="containerd"];then
  journalctl -u containerd --no-pager -p err --since"1 hour ago"| tail -10
elif["$RUNTIME"="crio"];then
  journalctl -u crio --no-pager -p err --since"1 hour ago"| tail -10
fi

echo""
echo"========== 诊断完成 =========="

3.2 实际应用案例

案例一:从Docker迁移到containerd

场景描述:生产环境K8s集群从1.23升级到1.28,需要将所有节点的容器运行时从Docker切换到containerd。集群有50个节点,业务不能中断。

迁移步骤

#!/bin/bash
# 文件名:migrate-to-containerd.sh
# Docker到containerd迁移脚本(单节点执行)
# 前提:集群已经有多个节点,可以逐个迁移

set-euo pipefail

NODE_NAME=$(hostname)
echo"开始迁移节点:${NODE_NAME}"

# 第一步:驱逐节点上的Pod
echo"=== 步骤1: 驱逐Pod ==="
kubectl cordon${NODE_NAME}
kubectl drain${NODE_NAME}
 --ignore-daemonsets 
 --delete-emptydir-data 
 --force 
 --timeout=300s
echo"Pod驱逐完成"

# 第二步:停止kubelet和Docker
echo"=== 步骤2: 停止服务 ==="
sudo systemctl stop kubelet
sudo systemctl stop docker
sudo systemctl stop containerd
# Docker自带的containerd也要停

# 第三步:安装独立的containerd
echo"=== 步骤3: 安装containerd ==="
sudo yum install -y containerd.io
# 或 sudo apt-get install -y containerd.io

# 第四步:生成并修改containerd配置
echo"=== 步骤4: 配置containerd ==="
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml > /dev/null

# 修改关键配置
sudo sed -i's/SystemdCgroup = false/SystemdCgroup = true/'/etc/containerd/config.toml
sudo sed -i's|sandbox_image = "registry.k8s.io/pause:3.8"|sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"|'/etc/containerd/config.toml

# 第五步:配置kubelet使用containerd
echo"=== 步骤5: 配置kubelet ==="
cat </dev/null)
 if["$STATUS"="True"];then
   echo"节点已就绪"
   break
 fi
 echo"等待节点就绪... (${i}/60)"
  sleep 5
done

# 第八步:恢复调度
echo"=== 步骤8: 恢复调度 ==="
kubectl uncordon${NODE_NAME}

# 验证
echo"=== 验证 ==="
kubectl get node${NODE_NAME}-o wide
kubectl describe node${NODE_NAME}| grep"Container Runtime"
# 预期输出:Container Runtime Version: containerd://1.7.x

echo"节点${NODE_NAME}迁移完成"

迁移注意事项

逐个节点迁移,不要同时迁移多个节点,确保集群始终有足够的计算资源

迁移前确认所有Deployment的replicas > 1,单副本服务迁移时会中断

Docker的镜像缓存不会自动迁移到containerd,迁移后第一次拉取镜像会比较慢

如果业务容器使用了Docker特有的功能(如docker.sock挂载),需要提前改造

迁移后docker ps看不到容器了,用crictl ps替代

迁移前后对比

迁移前(Docker):
$ kubectl get node node-01 -o wide
NAME   STATUS  ROLES  VERSION  CONTAINER-RUNTIME
node-01  Ready    v1.28.4  docker://24.0.7

迁移后(containerd):
$ kubectl get node node-01 -o wide
NAME   STATUS  ROLES  VERSION  CONTAINER-RUNTIME
node-01  Ready    v1.28.4  containerd://1.7.11

案例二:containerd与CRI-O性能对比测试

场景描述:在相同硬件环境下(8核16GB,SSD),对比containerd和CRI-O在容器启动速度、镜像拉取速度、内存占用三个维度的性能差异,为运行时选型提供数据支撑。

测试脚本

#!/bin/bash
# 文件名:runtime-benchmark.sh
# 容器运行时性能基准测试

set-euo pipefail

RUNTIME=$(crictl info 2>/dev/null | grep -o'"runtimeName":"[^"]*"'| cut -d'"'-f4)
echo"当前运行时:${RUNTIME}"
echo"测试时间:$(date)"
echo"=========================================="

# 测试1:容器启动速度(创建100个Pod的平均时间)
echo""
echo"=== 测试1: 容器启动速度 ==="
TOTAL_TIME=0
TEST_COUNT=20

foriin$(seq 1${TEST_COUNT});do
  START=$(date +%s%N)
  kubectl run bench-${i}--image=nginx:1.24-alpine --restart=Never 2>/dev/null
 # 等待Pod Running
  kubectlwait--for=condition=Ready pod/bench-${i}--timeout=60s 2>/dev/null
  END=$(date +%s%N)
  ELAPSED=$(( (END - START) / 1000000 ))
  TOTAL_TIME=$((TOTAL_TIME + ELAPSED))
 echo" Pod bench-${i}:${ELAPSED}ms"
done

AVG_TIME=$((TOTAL_TIME / TEST_COUNT))
echo"平均启动时间:${AVG_TIME}ms"

# 清理
foriin$(seq 1${TEST_COUNT});do
  kubectl delete pod bench-${i}--grace-period=0 --force 2>/dev/null &
done
wait

# 测试2:镜像拉取速度
echo""
echo"=== 测试2: 镜像拉取速度 ==="
# 先清理缓存
crictl rmi nginx:1.24-alpine 2>/dev/null ||true
crictl rmi redis:7.2-alpine 2>/dev/null ||true

forIMAGEin"nginx:1.24-alpine""redis:7.2-alpine""python:3.12-slim";do
  START=$(date +%s%N)
  crictl pull${IMAGE}2>/dev/null
  END=$(date +%s%N)
  ELAPSED=$(( (END - START) / 1000000 ))
 echo" ${IMAGE}:${ELAPSED}ms"
done

# 测试3:运行时内存占用
echo""
echo"=== 测试3: 运行时内存占用 ==="
if["${RUNTIME}"="containerd"];then
  RSS=$(ps aux | grep"containerd "| grep -v grep | awk'{sum+=$6} END {print sum}')
 echo"containerd进程RSS:$((RSS/1024))MB"
elif["${RUNTIME}"="cri-o"];then
  RSS=$(ps aux | grep"crio"| grep -v grep | awk'{sum+=$6} END {print sum}')
 echo"crio进程RSS:$((RSS/1024))MB"
fi

# conmon进程内存(CRI-O特有)
CONMON_RSS=$(ps aux | grep conmon | grep -v grep | awk'{sum+=$6} END {print sum}')
echo"conmon进程总RSS:$((CONMON_RSS/1024))MB"

echo""
echo"=========================================="
echo"测试完成"

实测结果对比(8核16GB SSD环境,K8s 1.28,各运行50个Pod):

测试项 containerd 1.7.11 CRI-O 1.28.3 说明
单Pod启动时间(平均) 1.8s 1.6s CRI-O略快,因为代码路径更短
nginx镜像拉取(首次) 3.2s 3.4s 基本持平,瓶颈在网络
运行时进程RSS(空载) 45MB 28MB CRI-O内存占用更小
运行时进程RSS(50 Pod) 120MB 65MB CRI-O优势明显
conmon进程总RSS(50 Pod) N/A 35MB CRI-O每个容器一个conmon进程
100 Pod批量创建总时间 42s 38s CRI-O批量创建略快

结论:两者性能差异不大,都能满足生产需求。CRI-O在内存占用上有明显优势(少约40%),适合节点资源紧张的场景。containerd在生态兼容性上更好,排查问题的资料更多。

四、最佳实践和注意事项

4.1 最佳实践

4.1.1 性能优化

调大并行镜像拉取数:containerd默认max_concurrent_downloads = 3,大规模部署时镜像拉取慢。调到10能明显加速节点初始化:

# containerd: /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri"]
 max_concurrent_downloads = 10

使用overlayfs快照驱动:containerd默认用overlayfs,这是性能最好的选择。不要改成btrfs或zfs,除非你的文件系统就是btrfs/zfs。CRI-O默认也用overlay,不需要额外配置:

# containerd
[plugins."io.containerd.grpc.v1.cri".containerd]
 snapshotter = "overlayfs"

containerd的GC调优:containerd有内置的垃圾回收机制,清理不再使用的镜像层和快照。默认配置比较保守,大量镜像更新的环境可以调积极一些:

[plugins."io.containerd.gc.v1.scheduler"]
 pause_threshold = 0.02
 deletion_threshold = 0
 mutation_threshold = 100
 schedule_delay = "0s"
 startup_delay = "100ms"

CRI-O的conmon优化:CRI-O为每个容器启动一个conmon进程负责日志和退出状态监控。100个容器就是100个conmon进程,虽然每个只占几百KB内存,但进程数多了调度开销不可忽视。确保conmon使用pod级别的cgroup:

[crio.runtime]
 conmon_cgroup = "pod"

4.1.2 安全加固

启用seccomp默认策略:seccomp限制容器内可以调用的系统调用,减少内核攻击面。containerd和CRI-O都支持默认seccomp profile:

# CRI-O: /etc/crio/crio.conf
[crio.runtime]
 seccomp_profile = "/usr/share/containers/seccomp.json"

containerd通过K8s的SecurityContext配置seccomp,不需要在运行时层面单独设置。

限制容器默认capabilities:CRI-O可以在配置文件中全局限制容器的默认capabilities,比K8s的PodSecurityPolicy更底层:

# CRI-O: /etc/crio/crio.conf
[crio.runtime]
 default_capabilities = [
  "CHOWN",
  "DAC_OVERRIDE",
  "FSETID",
  "FOWNER",
  "SETGID",
  "SETUID",
  "SETPCAP",
  "NET_BIND_SERVICE",
  "KILL",
 ]

镜像签名验证:CRI-O原生支持镜像签名验证(基于containers/image库),可以配置只允许拉取经过签名的镜像。containerd需要通过额外的admission webhook实现类似功能:

# CRI-O镜像签名策略
# /etc/containers/policy.json
{
"default": [{"type":"reject"}],
"transports": {
 "docker": {
  "registry.example.com": [{"type":"signedBy","keyType":"GPGKeys","keyPath":"/etc/pki/rpm-gpg/RPM-GPG-KEY-example"}],
  "docker.io": [{"type":"insecureAcceptAnything"}]
  }
 }
}

运行时Socket权限控制:containerd和CRI-O的socket文件默认权限是root:root 0660。确保只有kubelet和授权用户能访问,不要把socket暴露给普通用户:

# 检查socket权限
ls -la /run/containerd/containerd.sock
ls -la /var/run/crio/crio.sock
# 预期:srw-rw---- 1 root root

4.1.3 高可用配置

运行时服务自动恢复:containerd和CRI-O都通过systemd管理,配置自动重启策略。运行时挂了kubelet会检测到并报告节点NotReady,但自动重启能减少人工干预:

# /etc/systemd/system/containerd.service.d/override.conf
[Service]
Restart=always
RestartSec=5
OOMScoreAdj=-999
LimitNOFILE=1048576
LimitNPROC=infinity

containerd的live-restore等价功能:Docker有live-restore(dockerd重启不杀容器),containerd天然支持这个特性——containerd重启后会自动恢复对已有容器的管理。CRI-O也一样,重启crio进程不会影响正在运行的容器。这是因为实际管理容器的是runc创建的shim进程,运行时进程只是管理层。

版本升级策略

containerd:小版本升级(1.7.x → 1.7.y)直接替换二进制文件重启即可,不影响运行中的容器。大版本升级(1.6 → 1.7)建议走节点轮转流程

CRI-O:版本和K8s对齐,升级K8s时同步升级CRI-O。CRI-O 1.28只保证兼容K8s 1.28,不要跨版本使用

4.2 注意事项

4.2.1 配置注意事项

警告:containerd和CRI-O的cgroup驱动必须和kubelet保持一致(都用systemd)。不一致会导致kubelet无法正确管理容器的资源限制,表现为Pod创建失败或节点NotReady。我见过一个集群因为升级containerd后配置文件被覆盖,SystemdCgroup回到了默认的false,导致整个节点上的Pod全部异常。

注意pause镜像配置:containerd和CRI-O都需要配置pause镜像(sandbox镜像)。国内环境必须换成阿里云或其他国内源,否则节点初始化时拉不到pause镜像,所有Pod都无法创建。这是新集群部署最常见的问题之一

注意CNI插件路径:containerd默认CNI插件目录是/opt/cni/bin/,CRI-O同时搜索/opt/cni/bin/和/usr/libexec/cni/。如果CNI插件安装在非默认路径,需要在配置文件中修改

注意containerd配置文件版本:containerd config.toml有version 1和version 2两种格式。containerd 1.6+默认生成version 2格式,旧版本的version 1配置在新版本上可能不兼容。升级containerd后建议重新生成配置文件

4.2.2 常见错误

错误现象 原因分析 解决方案
failed to create containerd task: failed to create shim task runc版本太旧或损坏 升级runc到1.1+,runc --version检查
container runtime is not running 运行时服务未启动或socket不可达 systemctl status containerd/crio 检查服务状态
sandbox image not found pause镜像未配置或拉取失败 检查pause_image配置,手动crictl pull测试
cgroup driver mismatch 运行时和kubelet的cgroup驱动不一致 统一设为systemd,重启运行时和kubelet
CNI plugin not found CNI插件未安装或路径配置错误 检查/opt/cni/bin/目录,安装CNI插件
failed to pull image: context deadline exceeded 镜像仓库网络不通或超时 检查镜像加速配置,crictl pull手动测试
OCI runtime create failed runc执行失败,通常是内核或seccomp问题 检查内核版本,查看journalctl -u containerd/crio日志

4.2.3 兼容性问题

版本兼容:containerd 1.6.x是LTS版本,支持K8s 1.24-1.28。containerd 1.7.x支持K8s 1.26+。CRI-O严格版本对齐,CRI-O 1.28.x只支持K8s 1.28.x,不要混用版本

平台兼容:containerd和CRI-O都支持amd64和arm64。containerd还支持Windows容器(K8s Windows节点),CRI-O只支持Linux

组件依赖:两者都依赖runc 1.1+作为底层OCI运行时。也可以替换为crun(C语言实现,启动更快)或kata-containers(虚拟机级隔离)。替换OCI运行时不需要修改上层K8s配置

五、故障排查和监控

5.1 故障排查

5.1.1 日志查看

# containerd日志
sudo journalctl -u containerd -f --no-pager
sudo journalctl -u containerd --since"1 hour ago"-p err

# CRI-O日志
sudo journalctl -u crio -f --no-pager
sudo journalctl -u crio --since"1 hour ago"-p err

# kubelet日志(运行时相关)
sudo journalctl -u kubelet -f | grep -i -E"runtime|containerd|crio|cri"

# 查看容器级别日志
crictl logs 
crictl logs --tail 100 -f 

# 查看Pod沙箱日志目录
# containerd: /var/log/pods/__/
# CRI-O: /var/log/crio/pods//
ls /var/log/pods/

5.1.2 常见问题排查

问题一:节点NotReady,kubelet报runtime not running

# 诊断
systemctl status containerd # 或 systemctl status crio
crictl info
journalctl -u containerd --since"10 min ago"| tail -30

解决方案

运行时服务挂了:systemctl restart containerd(或crio),检查日志找根因

socket文件不存在:检查配置文件中的socket路径是否正确

权限问题:确认kubelet有权限访问运行时socket

问题二:Pod一直处于ContainerCreating状态

# 诊断
kubectl describe pod 
# 查看Events部分的错误信息

# 常见错误:
# "failed to pull image" - 镜像拉取失败
# "network plugin is not ready" - CNI插件未就绪
# "failed to create sandbox" - pause镜像问题

# 检查CNI插件
ls /opt/cni/bin/
ls /etc/cni/net.d/

# 检查pause镜像是否存在
crictl images | grep pause

解决方案

镜像拉取失败:检查镜像仓库配置和网络连通性

CNI未就绪:确认CNI插件已安装,配置文件在/etc/cni/net.d/下

pause镜像缺失:手动拉取crictl pull registry.aliyuncs.com/google_containers/pause:3.9

问题三:containerd/CRI-O内存持续增长

症状:运行时进程RSS从初始的50MB逐渐增长到500MB+,节点内存告警

排查

# 查看运行时进程内存
ps aux | grep -E"containerd|crio"| grep -v grep

# 查看容器和镜像数量
crictl ps -a | wc -l
crictl images | wc -l

# containerd: 查看快照使用情况
ctr -n k8s.io snapshots ls | wc -l

# 检查是否有大量已停止的容器未清理
crictl ps -a --state exited | wc -l

解决

清理已退出的容器和未使用的镜像:crictl rmi --prune

containerd的GC可能不够积极,调整gc scheduler参数

如果是已知的内存泄漏bug,升级到最新补丁版本

5.1.3 调试模式

# containerd开启debug日志
# 修改 /etc/containerd/config.toml
# [debug]
#  level = "debug"
sudo systemctl restart containerd
sudo journalctl -u containerd -f | grep -i debug

# CRI-O开启debug日志
# 修改 /etc/crio/crio.conf 或启动参数
# crio --log-level debug
sudo systemctl restart crio
sudo journalctl -u crio -f | grep -i debug

# 使用ctr直接操作containerd(绕过CRI层,排查containerd本身的问题)
sudo ctr -n k8s.io containers ls
sudo ctr -n k8s.io images ls
sudo ctr -n k8s.io tasks ls
sudo ctr -n k8s.io snapshots ls

# 查看containerd的shim进程
ps aux | grep containerd-shim

# 查看CRI-O的conmon进程
ps aux | grep conmon

# 使用runc直接查看容器状态(最底层排查)
sudo runc --root /run/containerd/runc/k8s.io list  # containerd环境
sudo runc --root /run/runc list            # CRI-O环境

5.2 性能监控

5.2.1 关键指标监控

# 运行时进程资源使用
ps aux | grep -E"containerd|crio"| grep -v grep

# 容器资源统计
crictl stats
crictl statsp

# containerd指标端点(配置了metrics.address后)
curl -s http://localhost:1338/v1/metrics | head -50

# CRI-O指标端点(配置了enable_metrics后)
curl -s http://localhost:9537/metrics | head -50

# 磁盘使用
du -sh /var/lib/containerd/  # containerd
du -sh /var/lib/containers/  # CRI-O

# 镜像占用空间
crictl images -o json | python3 -c"
import json,sys
data=json.load(sys.stdin)
total=sum(int(i.get('size','0')) for i in data.get('images',[]))
print(f'镜像总大小: {total/1024/1024:.0f}MB')
"

5.2.2 监控指标说明

指标名称 正常范围 告警阈值 说明
运行时进程RSS <200MB >500MB 持续增长可能是内存泄漏
容器创建延迟 <2s >5s 超过5s检查镜像拉取和CNI
容器创建失败率 0% >1% 任何失败都需要排查
镜像拉取延迟 <30s >60s 网络或仓库性能问题
磁盘使用率 <70% >85% 镜像和容器层占用空间
shim/conmon进程数 等于容器数 偏差>10% 进程泄漏需要排查
CRI API延迟(P99) <100ms >500ms 运行时响应慢影响Pod调度

5.2.3 监控告警配置

# Prometheus抓取配置:prometheus.yml
scrape_configs:
# containerd指标
-job_name:'containerd'
 static_configs:
  -targets:['192.168.1.10:1338']
 metrics_path:/v1/metrics

# CRI-O指标
-job_name:'crio'
 static_configs:
  -targets:['192.168.1.10:9537']
 metrics_path:/metrics
# Prometheus告警规则:runtime-alerts.yml
groups:
-name:container_runtime_alerts
 rules:
  -alert:RuntimeProcessMemoryHigh
   expr:process_resident_memory_bytes{job=~"containerd|crio"}>524288000
   for:10m
   labels:
    severity:warning
   annotations:
    summary:"容器运行时内存使用过高"
    description:"{{ $labels.job }}进程RSS{{ $value | humanize }},超过500MB"

  -alert:ContainerCreateLatencyHigh
   expr:histogram_quantile(0.99,rate(container_runtime_operations_duration_seconds_bucket{operation_type="create_container"}[5m]))>5
   for:5m
   labels:
    severity:warning
   annotations:
    summary:"容器创建延迟过高"
    description:"P99延迟{{ $value }}s,超过5秒"

  -alert:ContainerCreateErrors
   expr:rate(container_runtime_operations_errors_total{operation_type="create_container"}[5m])>0
   for:2m
   labels:
    severity:critical
   annotations:
    summary:"容器创建失败"
    description:"错误率{{ $value }}/s"

  -alert:ImagePullErrors
   expr:rate(container_runtime_operations_errors_total{operation_type="pull_image"}[5m])>0
   for:5m
   labels:
    severity:warning
   annotations:
    summary:"镜像拉取失败"
    description:"错误率{{ $value }}/s,检查镜像仓库连通性"

5.3 备份与恢复

5.3.1 备份策略

#!/bin/bash
# 文件名:runtime-backup.sh
# 容器运行时配置备份脚本

BACKUP_DIR="/backup/runtime/$(date +%Y%m%d)"
mkdir -p${BACKUP_DIR}

# 检测运行时类型
ifsystemctl is-active --quiet containerd;then
  RUNTIME="containerd"
elifsystemctl is-active --quiet crio;then
  RUNTIME="crio"
else
 echo"未检测到运行中的容器运行时"
 exit1
fi

echo"备份运行时:${RUNTIME}"

# 备份配置文件
if["$RUNTIME"="containerd"];then
  cp /etc/containerd/config.toml${BACKUP_DIR}/
  cp -r /etc/containerd/certs.d/${BACKUP_DIR}/ 2>/dev/null
elif["$RUNTIME"="crio"];then
  cp /etc/crio/crio.conf${BACKUP_DIR}/
  cp /etc/containers/registries.conf${BACKUP_DIR}/
  cp /etc/containers/policy.json${BACKUP_DIR}/ 2>/dev/null
fi

# 备份crictl配置
cp /etc/crictl.yaml${BACKUP_DIR}/ 2>/dev/null

# 备份CNI配置
cp -r /etc/cni/net.d/${BACKUP_DIR}/cni-conf/ 2>/dev/null

# 备份systemd override
if["$RUNTIME"="containerd"];then
  cp -r /etc/systemd/system/containerd.service.d/${BACKUP_DIR}/ 2>/dev/null
elif["$RUNTIME"="crio"];then
  cp -r /etc/systemd/system/crio.service.d/${BACKUP_DIR}/ 2>/dev/null
fi

# 备份kubelet配置
cp /etc/default/kubelet${BACKUP_DIR}/ 2>/dev/null
cp /var/lib/kubelet/config.yaml${BACKUP_DIR}/ 2>/dev/null

# 导出镜像列表
crictl images -o json >${BACKUP_DIR}/images-list.json

echo"备份完成:${BACKUP_DIR}"
du -sh${BACKUP_DIR}

5.3.2 恢复流程

安装运行时:按照2.2节步骤安装对应版本的containerd或CRI-O

恢复配置文件

# containerd
cp config.toml /etc/containerd/
cp -r certs.d/ /etc/containerd/

# CRI-O
cp crio.conf /etc/crio/
cp registries.conf /etc/containers/

恢复CNI配置:cp -r cni-conf/* /etc/cni/net.d/

恢复kubelet配置:cp kubelet /etc/default/kubelet

启动服务:systemctl daemon-reload && systemctl start containerd && systemctl start kubelet

验证节点状态:kubectl get node $(hostname) -o wide确认节点Ready且运行时版本正确

六、总结

6.1 技术要点回顾

运行时选型:containerd适合通用场景和从Docker迁移的集群,生态成熟,资料丰富;CRI-O适合纯K8s环境和Red Hat技术栈,代码精简,内存占用小

关键配置:cgroup驱动必须设为systemd并和kubelet保持一致,pause镜像国内环境必须换源,这两个配置错了节点直接NotReady

crictl工具:替代docker命令的标准工具,crictl ps看容器,crictl pods看Pod沙箱,crictl images看镜像,日常运维够用

迁移策略:逐节点迁移,先cordon+drain,再切换运行时,最后uncordon。Docker的镜像缓存不会自动迁移,首次拉取会慢

监控要点:运行时进程内存、容器创建延迟、CRI API错误率是核心指标,containerd暴露1338端口,CRI-O暴露9537端口

6.2 进阶学习方向

OCI运行时替换:runc之外还有crun(C语言实现,启动速度快50%)、kata-containers(虚拟机级隔离)、gVisor(用户态内核)。不同安全级别的工作负载可以用不同的OCI运行时

学习资源:kata-containers官方文档、gVisor官方文档

实践建议:在containerd中配置多个runtime class,普通Pod用runc,敏感Pod用kata

镜像构建工具链:脱离Docker后,镜像构建可以用Buildah(Red Hat出品,和CRI-O配合好)、kaniko(在容器内构建,不需要Docker daemon)、BuildKit(containerd生态)

学习资源:Buildah官方文档、kaniko GitHub

实践建议:CI/CD流水线中用kaniko替代docker build,不需要在CI Runner上安装Docker

容器运行时安全:深入理解seccomp、AppArmor、SELinux在容器运行时层面的实现,以及RuntimeClass在K8s中的应用

学习资源:K8s RuntimeClass文档、OCI runtime-spec

实践建议:为不同安全级别的工作负载配置不同的RuntimeClass

6.3 参考资料

containerd官方文档- containerd配置和使用指南

CRI-O官方文档- CRI-O安装和配置

Kubernetes容器运行时- K8s官方的运行时配置指南

OCI Runtime Specification- OCI运行时规范

crictl文档- CRI命令行工具

从Docker迁移到containerd- K8s官方迁移指南

附录

A. 命令速查表

crictl常用命令(containerd和CRI-O通用)

# 运行时信息
crictl info               # 查看运行时信息
crictl version             # 查看crictl和运行时版本

# 镜像操作
crictl pull nginx:1.24-alpine      # 拉取镜像
crictl images              # 查看镜像列表
crictl images -o json          # JSON格式输出镜像列表
crictl inspecti nginx:1.24-alpine    # 查看镜像详情
crictl rmi nginx:1.24-alpine      # 删除镜像
crictl rmi --prune           # 清理未使用的镜像

# 容器操作
crictl ps                # 查看运行中的容器
crictl ps -a              # 查看所有容器(含已停止)
crictl ps --name nginx         # 按名称过滤容器
crictl logs        # 查看容器日志
crictl logs --tail 100 -f # 实时跟踪最近100行日志
crictlexec-it  /bin/sh # 进入容器
crictl inspect       # 查看容器详情
crictl stats              # 查看容器资源使用
crictl stop        # 停止容器
crictl rm         # 删除容器

# Pod沙箱操作
crictl pods               # 查看所有Pod沙箱
crictl pods --nametest-pod       # 按名称过滤Pod
crictl pods --state ready        # 按状态过滤Pod
crictl inspectp         # 查看Pod详情
crictl statsp              # 查看Pod资源使用
crictl stopp           # 停止Pod沙箱
crictl rmp            # 删除Pod沙箱
crictl runp pod-config.json       # 创建Pod沙箱(调试用)

containerd专用命令(ctr工具)

# ctr是containerd的原生CLI,绕过CRI层直接操作containerd
# K8s环境下容器在k8s.io命名空间

# 命名空间
ctr namespaces ls            # 查看所有命名空间

# 镜像操作
ctr -n k8s.io images ls         # 查看K8s命名空间的镜像
ctr -n k8s.io images pull docker.io/library/nginx:1.24-alpine # 拉取镜像
ctr -n k8s.io images rm      # 删除镜像
ctr -n k8s.io imagesexportnginx.tar docker.io/library/nginx:1.24-alpine # 导出镜像
ctr -n k8s.io images import nginx.tar  # 导入镜像

# 容器操作
ctr -n k8s.io containers ls      # 查看容器列表
ctr -n k8s.io containers info    # 查看容器详情

# 任务(运行中的容器进程)
ctr -n k8s.io tasks ls         # 查看运行中的任务
ctr -n k8s.io tasks metrics     # 查看任务资源指标

# 快照
ctr -n k8s.io snapshots ls       # 查看快照列表
ctr -n k8s.io snapshots usage   # 查看快照磁盘使用

# 内容存储
ctr -n k8s.io content ls        # 查看内容存储

服务管理命令

# containerd服务管理
sudo systemctl start containerd     # 启动
sudo systemctl stop containerd     # 停止
sudo systemctl restart containerd    # 重启
sudo systemctl status containerd    # 查看状态
sudo systemctlenablecontainerd    # 开机自启
containerd --version          # 查看版本
containerd config default        # 输出默认配置
containerd config dump         # 输出当前配置

# CRI-O服务管理
sudo systemctl start crio       # 启动
sudo systemctl stop crio        # 停止
sudo systemctl restart crio       # 重启
sudo systemctl status crio       # 查看状态
sudo systemctlenablecrio       # 开机自启
crio --version             # 查看版本
crio config --default          # 输出默认配置

# 日志查看
journalctl -u containerd -f --no-pager # containerd实时日志
journalctl -u crio -f --no-pager    # CRI-O实时日志
journalctl -u containerd -p err     # containerd错误日志
journalctl -u crio -p err       # CRI-O错误日志

# 底层OCI运行时
runc --version             # 查看runc版本
runc --root /run/containerd/runc/k8s.io list # containerd环境查看容器
runc --root /run/runc list       # CRI-O环境查看容器

B. containerd与CRI-O对比表

对比维度 containerd CRI-O
开发主导 Docker/CNCF Red Hat/CNCF
CNCF状态 毕业项目 毕业项目
设计定位 通用容器运行时 专为K8s设计的CRI运行时
代码语言 Go Go
版本策略 独立版本号(1.6.x/1.7.x) 和K8s版本对齐(1.28.x对应K8s 1.28)
CRI支持 通过内置CRI插件 原生CRI实现
独立使用 支持(不依赖K8s) 不支持(只为K8s服务)
镜像构建 支持(通过BuildKit) 不支持(需要Buildah等外部工具)
镜像推送 支持 不支持
Windows容器 支持 不支持
插件架构 支持(快照、运行时等插件) 不支持
命名空间隔离 支持(k8s.io、moby等) 不支持
容器监控进程 containerd-shim(每容器一个) conmon(每容器一个)
默认存储驱动 overlayfs overlay
配置文件 /etc/containerd/config.toml /etc/crio/crio.conf
Socket路径 /run/containerd/containerd.sock /var/run/crio/crio.sock
镜像仓库配置 /etc/containerd/certs.d/ /etc/containers/registries.conf
指标端口 1338 9537
CLI工具 ctr(原生)+ crictl(CRI) crictl(CRI)
空载内存占用 ~45MB ~28MB
50 Pod内存占用 ~120MB ~65MB(+35MB conmon)
单Pod启动时间 ~1.8s ~1.6s
云平台采用 AWS EKS、Google GKE、Azure AKS Red Hat OpenShift
社区活跃度 非常活跃,贡献者多 活跃,Red Hat主导
排查资料 丰富,中英文资料多 较少,英文为主

C. 术语表

术语 英文 解释
高级运行时 High-level Runtime 负责镜像管理、容器生命周期管理、API接口的运行时,containerd和CRI-O都属于此类
低级运行时 Low-level Runtime 负责实际创建和运行容器的运行时,如runc、crun、kata-containers
CRI Container Runtime Interface Kubernetes定义的容器运行时接口标准,kubelet通过CRI和运行时通信
OCI Open Container Initiative 容器镜像格式和运行时的开放标准,runc是OCI运行时的参考实现
runc runc OCI运行时的参考实现,用Go编写,containerd和CRI-O底层都调用runc创建容器
crun crun 用C语言实现的OCI运行时,启动速度比runc快约50%,内存占用更小
containerd-shim containerd-shim containerd为每个容器启动的中间进程,负责管理容器的stdin/stdout和退出状态,containerd重启不影响容器
conmon Container Monitor CRI-O为每个容器启动的监控进程,负责日志记录、退出状态监控、TTY管理
Pod沙箱 Pod Sandbox K8s中每个Pod先创建的基础容器(pause容器),提供网络命名空间,业务容器共享该命名空间
pause容器 Pause Container Pod沙箱的实现,一个极简的容器,只执行pause系统调用,为Pod内其他容器提供共享的网络和IPC命名空间
crictl CRI Control CRI兼容运行时的命令行工具,类似docker命令但只支持CRI接口,containerd和CRI-O通用
ctr containerd CLI containerd的原生命令行工具,绕过CRI层直接操作containerd,排查containerd本身问题时使用
dockershim Docker Shim K8s中将Docker适配为CRI接口的垫片层,K8s 1.24正式移除
cgroup Control Group Linux内核特性,限制和监控进程组的资源使用(CPU、内存等),容器资源限制的底层机制
cgroup驱动 Cgroup Driver 管理cgroup的方式,有systemd和cgroupfs两种,K8s环境必须统一使用systemd
CNI Container Network Interface 容器网络接口标准,定义容器网络的创建和删除接口,K8s网络插件都实现CNI规范
快照 Snapshot containerd的存储概念,类似Docker的镜像层,用于管理容器的文件系统层
RuntimeClass RuntimeClass K8s资源对象,用于指定Pod使用哪种OCI运行时(如runc、kata-containers),实现不同隔离级别
CNCF Cloud Native Computing Foundation 云原生计算基金会,containerd和CRI-O都是CNCF毕业项目

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 容器
    +关注

    关注

    0

    文章

    541

    浏览量

    23057
  • Docker
    +关注

    关注

    0

    文章

    540

    浏览量

    14467
  • kubernetes
    +关注

    关注

    0

    文章

    281

    浏览量

    9546

原文标题:containerd vs CRI-O:Kubernetes 生产环境到底选谁?

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    如何缩短Vivado的运行时

    在Vivado Implementation阶段,有时是有必要分析一下什么原因导致运行时间(runtime)过长,从而找到一些方法来缩短运行时间。
    的头像 发表于 05-29 14:37 1.6w次阅读
    如何缩短Vivado的<b class='flag-5'>运行时</b>间

    Kubernetes架构和核心组件组成 Kubernetes节点“容器运行时”技术分析

    Kubernetes 架构简介 Kubernetes架构如下图所示: 在这张系统架构图中,我们把服务分为运行在工作节点上的服务和组成集群级别控制板的服务。Kubernetes节点有
    的头像 发表于 09-25 15:53 4563次阅读
    <b class='flag-5'>Kubernetes</b>架构和核心组件组成 <b class='flag-5'>Kubernetes</b>节点“<b class='flag-5'>容器</b><b class='flag-5'>运行时</b>”技术分析

    Containerd常见命令操作

    作为接替 Docker 运行时Containerd 在早在 Kubernetes1.7 时就能直接与 Kubelet 集成使用,只是大部分时候我们因熟悉 Docker,在部署集群时采用了默认
    的头像 发表于 08-30 10:08 6553次阅读

    各种容器运行的作用是什么

    容器技术中,容器运行时可以分为三种类型:低级运行时、高级运行时以及沙盒或虚拟化运行时
    发表于 09-20 11:42 1855次阅读
    各种<b class='flag-5'>容器</b><b class='flag-5'>运行</b>的作用是什么

    紫金桥组态软件新的功能_运行时组态

    运行时组态是组态软件新近提出的新的概念。运行时组态是在运行环境下对已有工程进行修改,添加新的功能。它不同于在线组态,在线组态是在工程运行的同时,进入组态环境,在组态环境中对工程进行修改
    发表于 10-13 16:17 2次下载
    紫金桥组态软件新的功能_<b class='flag-5'>运行时</b>组态

    重磅!阿里巴巴工程师获得 containerd 社区席位,与社区共建云时代容器标准

    社区席位,成为 containerd 社区 Reviewer,未来将共同参与云时代容器标准的建设。containerd 是一个工业级别的容器运行时
    发表于 12-11 17:25 572次阅读

    CRI 与 ShimV2:一种 Kubernetes 集成容器运行时的新思路

    年,由 containerd 社区主导的 shimv2 API 的出现,在 CRI 的基础上,为用户集成自己的容器运行时带来了更加成熟和方便的实践方法。本次演讲分享了关于
    发表于 12-20 17:05 355次阅读

    k8s容器运行时演进历史

    运行时接口(Container Runtime Interface),这一步中,Kubelet 可以视作一个简单的 CRI Client,而 dockershim 就是接收请求的 Server。目前 dockershim 的代码其实是内嵌在 Kubele
    的头像 发表于 02-02 13:50 2801次阅读
    k8s<b class='flag-5'>容器</b><b class='flag-5'>运行时</b>演进历史

    如何高效测量ECU的运行时

    ,最终可能会引起运行时间方面的问题。这在项目后期需要大量的时间和金钱来解决。如果不能掌握系统的运行状态,则很难发现系统内缺陷的根源。 解决方案 将TA软件工具套件与VX1000测量标定硬件相结合,可同步分析 ECU内部运行时序和
    的头像 发表于 10-28 11:05 3307次阅读

    Go运行时:4年之后

    自 2018 年以来,Go GC,以及更广泛的 Go 运行时,一直在稳步改进。近日,Go 社区总结了 4 年来 Go 运行时的一些重要变化。
    的头像 发表于 11-30 16:21 1718次阅读

    什么是Kubernetes容器运行时CRI

    起初,Docker是事实上的容器技术标准,Kubernetes v1.5之前的代码中直接调用Docker API,实现容器运行时的相关操作。
    的头像 发表于 02-20 16:22 3490次阅读
    什么是<b class='flag-5'>Kubernetes</b><b class='flag-5'>容器</b><b class='flag-5'>运行时</b><b class='flag-5'>CRI</b>

    怎样避免电力电容器运行时漏油

    电力电容器运行中,会因为各种因素出现故障。在电力电容器运行时遇到的故障中,出现渗油和漏油的概率非常大。那么如何避免电力电容器
    的头像 发表于 04-07 16:01 2048次阅读

    iSulad+Kuasar:管理面资源消耗锐减99%的新一代统一容器运行时解决方案

    容器引擎(Container Engine)主要负责容器运行环境的创建、容器资源的配置和容器生命周期的管理,北向接收来自于
    的头像 发表于 04-27 15:00 2301次阅读
    iSulad+Kuasar:管理面资源消耗锐减99%的新一代统一<b class='flag-5'>容器</b><b class='flag-5'>运行时</b>解决方案

    ch32v307记录程序运行时

    ch32v307记录程序运行时间 在程序开发中,很重要的一项任务就是对程序的运行时间进行评估。对于大型的程序系统来说,它们通常需要处理大量的数据或进行复杂的计算操作。因此,如果程序的运行时间过长
    的头像 发表于 08-22 15:53 1984次阅读

    如何保证它们容器运行时的安全?

    紧密耦合的容器运行时继承了主机操作系统的安全态势和攻击面。运行时或主机内核中的任何漏洞及其利用都会成为攻击者的潜在切入点。
    的头像 发表于 11-03 15:24 1689次阅读