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

描述

一、概述

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 systemctl enable containerd

# 验证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 systemctl enable crio

# 验证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    # 查看容器日志
crictl exec -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 run test-nginx --image=nginx:1.24-alpine --restart=Never
kubectl get pod test-nginx -o wide

# 验证Pod正常运行
kubectl exec test-nginx -- nginx -v
kubectl logs test-nginx

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

# 清理
kubectl delete pod test-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"
if systemctl is-active --quiet containerd 2>/dev/null; then
    RUNTIME="containerd"
    SOCKET="/run/containerd/containerd.sock"
elif systemctl 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

for i in $(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
    kubectl wait --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"

# 清理
for i in $(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

for IMAGE in "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}

# 检测运行时类型
if systemctl is-active --quiet containerd; then
    RUNTIME="containerd"
elif systemctl is-active --quiet crio; then
    RUNTIME="crio"
else
    echo "未检测到运行中的容器运行时"
    exit 1
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行日志
crictl exec -it  /bin/sh   # 进入容器
crictl inspect             # 查看容器详情
crictl stats                             # 查看容器资源使用
crictl stop                # 停止容器
crictl rm                  # 删除容器

# Pod沙箱操作
crictl pods                              # 查看所有Pod沙箱
crictl pods --name test-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 images export nginx.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 systemctl enable containerd         # 开机自启
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 systemctl enable crio               # 开机自启
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毕业项目

 

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

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分