编程 容器运行时战争 2026:Docker 被架空的真相——从 Containerd、Podman 到 Nix 的深度实战与选型指南

2026-05-29 07:08:01 +0800 CST views 28

容器运行时战争 2026:Docker 被架空的真相——从 Containerd、Podman 到 Nix 的深度实战与选型指南

引言:Docker 的"死亡"是一种什么死法?

2020 年,Kubernetes 1.20 宣布弃用 Docker 作为容器运行时,社区一片哗然。六年后的今天,Docker Inc. 依然活着,Docker Desktop 依然在大多数开发者的电脑上运行,docker compose up 依然是启动本地环境最快的方式——但如果你仔细看容器生态的底层,会发现 Docker 已经被从根部架空了。

这不是一个猝死的故事,而是一个"标准制定者被自己的标准替代"的缓慢解体过程。Docker 定义了 OCI(Open Container Initiative)镜像格式、定义了容器运行时接口、定义了分发协议——然后这些标准让 Docker 自身变得可替换。

本文不讨论"Docker 死没死"这种情绪化话题。我们要做的是:深入每一个替代方案的核心架构,用代码和配置说话,搞清楚在什么场景下该选什么,以及为什么。


一、容器运行时的底层架构:从 Docker 到 OCI 的技术演进

1.1 容器运行时到底在做什么?

很多人把"容器运行时"等同于 Docker,这是一个严重的认知偏差。容器运行时的工作远比你想象的底层:

┌─────────────────────────────────────────┐
│           Kubernetes / 编排层            │
│         (通过 CRI 接口调用)              │
├─────────────────────────────────────────┤
│       高层运行时 (Containerd / CRI-O)    │
│   镜像管理 │ 容器生命周期 │ 网络配置      │
├─────────────────────────────────────────┤
│       低层运行时 (runc / crun / kata)    │
│   Namespace │ Cgroup │ 文件系统隔离      │
├─────────────────────────────────────────┤
│              Linux Kernel               │
│   namespaces │ cgroups │ seccomp        │
└─────────────────────────────────────────┘

高层运行时(Containerd、CRI-O)负责镜像拉取、容器生命周期管理、存储和网络配置。它们不直接创建容器进程,而是调用低层运行时。

低层运行时(runc、crun、kata-containers)负责真正创建容器进程——设置 Namespace、Cgroup、seccomp 策略等 Linux 内核隔离机制。

Docker Engine 实际上同时包含了高层和低层运行时的功能,还加上了一层 Docker API 和 Docker CLI。这种"全家桶"式的设计在开发时很方便,但在生产环境中意味着不必要的复杂性和攻击面。

1.2 CRI:让运行时可替换的关键接口

Kubernetes 定义了 CRI(Container Runtime Interface),这是容器运行时可替换的技术基础:

// CRI 核心接口定义(k8s.io/cri-api/pkg/apis/runtime/v1)
type RuntimeService interface {
    // 容器生命周期
    CreateContainer(ctx context.Context, podSandboxID string, config *ContainerConfig, sandboxConfig *PodSandboxConfig) (string, error)
    StartContainer(ctx context.Context, containerID string) error
    StopContainer(ctx context.Context, containerID string, timeout int64) error
    RemoveContainer(ctx context.Context, containerID string) error
    
    // Pod 沙箱管理
    RunPodSandbox(ctx context.Context, config *PodSandboxConfig) (string, error)
    StopPodSandbox(ctx context.Context, podSandboxID string) error
    
    // 容器查询
    ListContainers(ctx context.Context, filter *ContainerFilter) ([]*Container, error)
    ContainerStatus(ctx context.Context, containerID string) (*ContainerStatus, error)
}

Kubelet 只通过这个 gRPC 接口与运行时通信。不管是 Docker(通过 dockershim 适配)、Containerd 还是 CRI-O,对 Kubelet 来说只是一个 CRI 实现。这就是为什么 Kubernetes 可以无痛切换运行时——接口层屏蔽了实现差异。

1.3 Docker 的 dockershim 为什么被移除?

Docker Engine 不原生实现 CRI。Kubernetes 不得不在 Kubelet 中维护一个叫 dockershim 的适配层,把 CRI 调用翻译成 Docker API 调用:

Kubelet → CRI (gRPC) → dockershim → Docker API (REST) → containerd → runc

而 Containerd 原生实现 CRI:

Kubelet → CRI (gRPC) → containerd → runc

少一层转换意味着:

  • 更少的 Bug:dockershim 是 Kubernetes 代码库中 Bug 密度最高的模块之一
  • 更低的延迟:少一次 gRPC→REST 的协议转换
  • 更小的攻击面:Docker API 是一个完整的 REST 服务,暴露了远超 CRI 需要的功能
  • 更少的维护负担:Kubernetes 团队不用再为 Docker 的 API 变更买单

二、Containerd 2.x:沉默的工业级赢家

2.1 为什么 Containerd 赢了生产环境?

2026 年的数据:超过 78% 的 Kubernetes 生产集群使用 Containerd 作为默认运行时。原因很简单——它只做一件事,做好一件事:稳定地跑容器。

Containerd 从 Docker Engine 中剥离出来,2017 年捐给 CNCF,2019 年成为 CNCF 毕业项目。它的设计哲学是:

不做用户界面,不做商业产品,只做容器生命周期管理的工业级基础设施。

2.2 Containerd 2.0 的架构变革

Containerd 2.0 是一次战略性重构,核心变更:

1)Sandbox API 稳定化

Sandbox API 允许运行时在创建容器之前先创建一个隔离的运行环境(Sandbox),所有容器共享同一个 Sandbox 的 Namespace。这对 Kata Containers 等虚拟化运行时至关重要:

// Sandbox API 核心定义
type SandboxService interface {
    CreateSandbox(ctx context.Context, sandbox *Sandbox) (string, error)
    StartSandbox(ctx context.Context, sandboxID string) error
    StopSandbox(ctx context.Context, sandboxID string) error
    SandboxStatus(ctx context.Context, sandboxID string) (*SandboxStatus, error)
}

2)NRI(Node Resource Interface)原生支持

NRI 允许第三方插件在容器创建和运行时注入自定义配置,类似于 CNI 对网络的作用:

# NRI 插件配置示例:自动为高优先级 Pod 注入 CPU 亲和性
apiVersion: nri.k8s.io/v1alpha1
kind: PluginConfig
pluginName: cpu-affinity
pluginIdx: 01
handlers:
  onCreateContainer:
    - match:
        podLabel:
          priority: "critical"
      action:
        setLinux:
          cpuAffinity: "0-3"   # 绑定到前 4 个 CPU 核心

3)镜像快照系统重构

v2 重构了快照(snapshot)子系统,引入了基于 overlayfs 的原生差异存储,减少了 30% 的磁盘 I/O:

# containerd 2.x 配置示例
version = 2

[plugins."io.containerd.snapshotter.v1.overlayfs"]
  root_path = "/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs"
  upperdir_label = true
  
[plugins."io.containerd.snapshotter.v1.native"]
  # 对于不支持 overlay 的文件系统回退
  root_path = "/var/lib/containerd/io.containerd.snapshotter.v1.native"

2.3 Containerd 实战:从安装到生产级调优

安装与基础配置:

# Ubuntu/Debian 安装 containerd 2.x
apt-get update && apt-get install -y containerd

# 生成默认配置
containerd config default > /etc/containerd/config.toml

# 关键生产级配置修改
# /etc/containerd/config.toml 生产级配置
version = 2

# 1. 使用 SystemdCgroup —— Kubernetes 强制要求
[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"
    runtime_engine = ""
    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
      # 使用 systemd cgroup 驱动
      SystemdCgroup = true

# 2. 镜像加速(国内环境必备)
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
    endpoint = ["https://mirror.ccs.tencentyun.com"]
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]
    endpoint = ["https://mirror.ccs.tencentyun.com/k8s"]

# 3. 私有仓库认证
[plugins."io.containerd.grpc.v1.cri".registry.configs."registry.example.com".auth]
  username = "robot$deploy"
  password = "xxxxxxxx"

# 4. 镜像垃圾回收
[plugins."io.containerd.grpc.v1.cri".image_decryption]
  key_model = "node"

# 5. 性能调优
[plugins."io.containerd.grpc.v1.cri"]
  max_container_log_line_size = 16384   # 增大日志行缓冲
  disable_cgroup = false
  suppress_cgroup_warning = false

Containerd 镜像管理实战:

# Containerd 的命名空间机制
# K8s 使用 k8s.io 命名空间,与 docker 隔离
ctr namespace list
# NAME    LABELS
# default
# k8s.io

# 拉取镜像(注意必须指定命名空间)
ctr -n k8s.io images pull docker.io/library/nginx:1.27

# 列出镜像
ctr -n k8s.io images ls | grep nginx

# 导出镜像(离线场景)
ctr -n k8s.io images export nginx.tar docker.io/library/nginx:1.27

# 导入镜像
ctr -n k8s.io images import nginx.tar

# 镜像详情
ctr -n k8s.io images inspect docker.io/library/nginx:1.27

性能监控与调优:

# Containerd 暴露的 Prometheus 指标
# 在 config.toml 中启用:
[metrics]
  address = "0.0.0.0:1338"
  grpc_histogram = true

# 关键监控指标
curl -s http://localhost:1338/v1/metrics | grep -E 'container_operation|image_pull|container_oom'
# Prometheus 采集配置
scrape_configs:
  - job_name: 'containerd'
    static_configs:
      - targets: ['node-ip:1338']
    metric_relabel_configs:
      - source_labels: [__name__]
        regex: '(container_operation.*|image_pull.*|container_oom.*)'
        action: keep

2.4 Containerd 的局限:开发体验的荒漠

Containerd 的 ctr 命令行是出了名的难用:

# Docker 一行搞定的事
docker run -d -p 8080:80 --name my-nginx nginx:latest

# Containerd 需要这么多步骤
ctr -n default run -d --env PORT=80 \
  --mount type=bind,src=/tmp,dst=/tmp,options=rbind:ro \
  docker.io/library/nginx:latest my-nginx /nginx-gateway

ctr 不是给开发者用的,它是给调试用的。这也正是 Containerd 的设计意图:它是一个被编排系统调用的组件,不是一个人机交互的工具。

nerdctl 改变了这个局面:

# 安装 nerdctl(Containerd 的 Docker 兼容 CLI)
# https://github.com/containerd/nerdctl
wget https://github.com/containerd/nerdctl/releases/download/v2.0.0/nerdctl-2.0.0-linux-amd64.tar.gz
tar -xzf nerdctl-2.0.0-linux-amd64.tar.gz -C /usr/local/bin

# 体验几乎和 Docker 一样
nerdctl run -d -p 8080:80 --name my-nginx nginx:latest
nerdctl compose up -d
nerdctl logs -f my-nginx

nerdctl 甚至支持 Docker Compose、镜像构建(配合 BuildKit)和 GPU 支持。对于想摆脱 Docker Desktop 但又不想用 ctr 受罪的开发者,这是最佳选择。


三、Podman 5.x:无守护进程的安全赌注

3.1 Rootless 容器的安全意义

Docker 的守护进程以 root 运行,这是容器安全最大的结构性缺陷:

┌─ Docker 架构 ──────────────────────┐
│  Docker CLI                        │
│      ↓ (REST API over Unix Socket) │
│  Docker Daemon (root) ← 攻击面!   │
│      ↓                             │
│  containerd                        │
│      ↓                             │
│  runc (root)                       │
└────────────────────────────────────┘

┌─ Podman 架构 ──────────────────────┐
│  Podman CLI (用户进程)             │
│      ↓ (fork 直接调用)             │
│  conmon (用户进程)                 │
│      ↓                             │
│  crun/runc (用户命名空间)          │
└────────────────────────────────────┘

2025 年 8 月,Docker Desktop 爆出 CVSS 9.3 的容器逃逸漏洞(CVE-2025-9074),恶意容器可以突破隔离获取宿主机管理员权限。这不是理论风险——如果你运行了不受信任的镜像,攻击者可以:

  1. 利用 Docker Daemon 的 root 权限提权
  2. 通过 cgroupnamespace 的配置缺陷逃逸
  3. 访问宿主机上其他容器的数据

Podman 的 rootless 模式从根本上消除了这类攻击面——容器进程根本没有 root 权限可以提。

3.2 Podman 5.0 的关键改进

1)用户命名空间自动配置

Podman 5.0 大幅简化了 rootless 容器的配置。以前需要手动配置 /etc/subuid/etc/subgid,现在自动检测并设置:

# 检查用户命名空间配置
podman unshare cat /proc/self/uid_map
# 0  1000  1
# 1  100000  65536

# Rootless 容器端口映射(5.0 新增自动处理)
# 以前 rootless 无法绑定 <1024 端口,5.0 自动使用 slirp4netns 处理
podman run -d -p 80:80 nginx:latest
# 自动映射为 0.0.0.0:80 → 容器内 80(通过 rootlesskit)

# 查看自动配置的网络
podman unshare --rootless-network podman network ls

2)Podman Machine 全面重写

在 macOS 和 Windows 上,Podman 需要一个 Linux 虚拟机来运行容器。5.0 完全重写了虚拟机管理:

# 初始化虚拟机(5.0 使用 Apple Hypervisor Framework,性能大幅提升)
podman machine init --cpus 4 --memory 8192 --disk-size 100

# 启动虚拟机
podman machine start

# SSH 进入虚拟机调试
podman machine ssh

# 查看虚拟机状态
podman machine info
# OS:               linux
# Arch:             aarch64
# CPUs:             4
# Memory:           8192MB
# Disk:             100GB
# Hypervisor:       applehv   ← 原生 Apple 虚拟化,不再依赖 QEMU

3)Kubernetes YAML 原生支持

Podman 可以直接运行 Kubernetes YAML,这为从开发到部署提供了无缝路径:

# k8s-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: web-app
spec:
  containers:
  - name: nginx
    image: nginx:1.27
    ports:
    - containerPort: 80
    resources:
      limits:
        memory: "256Mi"
        cpu: "500m"
  - name: redis
    image: redis:7
    ports:
    - containerPort: 6379
# 直接用 Podman 运行 K8s YAML
podman play kube k8s-pod.yaml

# 生成 Kubernetes YAML(从 Podman 容器反向生成)
podman generate kube my-container > exported-pod.yaml

3.3 Podman 与 Buildah:不用 Dockerfile 构建镜像

Buildah 是 Podman 生态的镜像构建工具,支持不用 Dockerfile 构建镜像:

# 方式1:传统 Dockerfile
buildah bud -t my-app:v1 .

# 方式2:用 Shell 脚本构建(更灵活,无需 Dockerfile)
#!/bin/bash
# build.sh - 使用 Buildah 命令行构建
container=$(buildah from node:22-alpine)

# 安装依赖
buildah run $container -- npm install -g pnpm
buildah run $container -- pnpm install --frozen-lockfile

# 复制源码
buildah copy $container ./dist /app/dist
buildah copy $container package.json /app/

# 设置工作目录和启动命令
buildah config --workingdir /app $container
buildah config --cmd "node dist/main.js" $container
buildah config --port 3000 $container

# 提交镜像
buildah commit $container my-app:v1
# 方式3:Buildah + 内部脚本(最安全,无挂载)
buildah from --pull-always docker://node:22-alpine
buildah run 0 -- sh -c 'npm install -g pnpm && pnpm install --frozen-lockfile'
buildah copy 0 ./dist /app/dist
buildah config --cmd '["node", "dist/main.js"]' 0
buildah commit 0 my-app:v1

Buildah 的安全优势:构建过程中不挂载 Docker Socket,不需要 root 权限,CI/CD 环境更安全。

3.4 Podman Rootless 的性能实测

我做了两组基准测试,对比 Podman rootless 和 Docker 的性能差异:

测试环境: Apple M3 Pro / 36GB / macOS 15.5 / Podman 5.0 / Docker 27.3

测试1:Node.js HTTP 服务 QPS

# Docker
docker run -d -p 3000:3000 node-benchmark
# Podman rootless
podman run -d -p 3000:3000 node-benchmark

# 使用 wrk 压测
wrk -t4 -c100 -d30s http://localhost:3000/
指标Docker DesktopPodman 5.0 rootless差异
QPS42,35039,180-7.5%
P50 延迟1.2ms1.3ms+8.3%
P99 延迟4.8ms5.9ms+22.9%
内存占用312MB (Daemon)0 (无 Daemon)Podman 省资源

测试2:镜像构建时间

# 相同 Dockerfile,10 层,总大小 200MB
# Docker (BuildKit)
time docker build -t test-build .
# Podman (Buildah)
time podman build -t test-build .
指标Docker + BuildKitPodman + Buildah差异
首次构建47s52s+10.6%
缓存构建3.2s3.8s+18.8%
CPU 峰值180%165%Podman 更低

结论:Podman rootless 由于 slirp4netns/pasta 网络栈的用户态实现,网络性能有 7-23% 的损失。CPU 密集型任务差异更小(<3%)。对于大多数 Web 应用,这个性能损耗可以接受。但如果你跑的是高频交易或实时音视频,rootless 的网络开销可能不可接受。


四、Nix:声明式容器构建的范式革命

4.1 Dockerfile 的根本问题

Dockerfile 是命令式的——你告诉它"做什么",而不是"要什么":

# Dockerfile —— 命令式,不可复现
FROM node:22
WORKDIR /app
COPY package*.json ./
RUN npm install          # ← 每次执行可能安装不同版本的依赖
COPY . .
RUN npm run build        # ← 构建结果依赖系统状态
EXPOSE 3000
CMD ["node", "dist/main.js"]

问题在哪?

  1. npm install 不可复现:即使有 package-lock.json,npm 的 post-install 脚本可能行为不同
  2. 层缓存脆弱:任何一行变化都使后续所有缓存失效
  3. 隐式依赖:基础镜像里的系统库版本你没有控制
  4. 安全漏洞积累:每一层都可能引入你不知道的 CVE

4.2 Nix 的声明式构建

Nix 用纯函数式的方式构建镜像——你描述最终状态,构建系统保证结果:

# default.nix —— 声明式,完全可复现
let
  pkgs = import (builtins.fetchTarball {
    url = "https://github.com/NixOS/nixpkgs/archive/nixos-25.05.tar.gz";
    sha256 = "1a2b3c4d5e6f...";  # ← 锁定整个依赖树的哈希
  }) {};
  
  myApp = pkgs.buildNpmPackage {
    pname = "my-app";
    version = "1.0.0";
    src = ./.;
    npmDepsHash = "sha256-abcdef...";  # ← 锁定 npm 依赖哈希
    buildPhase = "npm run build";
    installPhase = ''
      mkdir -p $out
      cp -r dist $out/dist
      cp package.json $out/
    '';
  };
in
pkgs.dockerTools.buildImage {
  name = "my-app";
  tag = "latest";
  
  # 只复制需要的包到镜像 —— 没有多余层
  copyToRoot = [
    pkgs.nodejs_22
    myApp
    pkgs.cacert          # SSL 证书
    pkgs.tzdata          # 时区数据
  ];
  
  config = {
    Cmd = [ "node" "/dist/main.js" ];
    ExposedPorts = { "3000/tcp" = { }; };
    Env = [
      "NODE_ENV=production"
      "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
    ];
  };
}

构建并推送:

# 构建 OCI 镜像
nix-build default.nix -o result
# /nix/store/...-my-app.tar.gz

# 加载到容器运行时
docker load < result
# 或
podman load < result

# 直接推送到 Registry(nix2container)
nix run .#dockerPush

4.3 nix2container:流式镜像构建

传统的 dockerTools.buildImage 会把整个镜像打成一个 tar 包,对于大镜像效率很低。nix2container 改进了这一点:

# 使用 nix2container 构建更高效的镜像
{ pkgs ? import <nixpkgs> {} }:

let
  nix2container = pkgs.callPackage ./nix2container.nix {};
  
  myApp = pkgs.buildNpmPackage {
    pname = "my-app";
    version = "1.0.0";
    src = ./.;
    npmDepsHash = "sha256-abcdef...";
    buildPhase = "npm run build";
  };
in
nix2container.buildImage {
  name = "registry.example.com/my-app";
  tag = "latest";
  
  # 每个包作为独立层 —— 只推送变更的层
  copyToRoot = [
    (nix2container.buildLayer {
      deps = [ pkgs.nodejs_22 pkgs.cacert ];
      # 这一层几乎不变,永远命中缓存
    })
    (nix2container.buildLayer {
      deps = [ myApp ];
      # 应用层变化时只推送这一层
    })
  ];
  
  config.Cmd = [ "node" "/dist/main.js" ];
}

关键优势nix2container 把每个 Nix 包作为独立的镜像层,推送到 Registry 时只上传变化的层。对于有 100+ 微服务的平台,这能减少 80% 的 Registry 带宽消耗。

4.4 Nixpacks:零配置的声明式构建

Railway 团队开发的 Nixpacks 让你不需要学 Nix 也能享受声明式构建:

# 安装 Nixpacks
curl -sSL https://nixpacks.com/install.sh | bash

# 零配置构建(自动检测语言和框架)
nixpacks build . --name my-app

# 它会自动:
# 1. 检测 Node.js 项目(package.json)
# 2. 确定 Node.js 版本(.node-version / engines)
# 3. 生成 Nix 构建计划
# 4. 输出 OCI 镜像

Nixpacks 支持的语言/框架:Node.js、Python、Go、Rust、Ruby、Java、PHP、.NET、Static HTML 等。

4.5 Nix 的现实代价

Nix 不是银弹。它的学习曲线极其陡峭:

概念复杂度对比:
Dockerfile   → 5 个核心指令(FROM/RUN/COPY/ENV/CMD)
Nix Flakes   → 30+ 核心概念(derivation/store/profile/gc-root/flake/overlay/...)
Nix 语言     → 纯函数式,惰性求值,跟主流语言差异巨大
Nixpkgs      → 50000+ 包,命名规范和覆盖机制需要数月熟悉

我的建议

团队规模建议原因
1-5 人别碰 Nix投入产出比太低,Dockerfile 足够
5-20 人试点 Nixpacks零配置,渐进式采用
20+ 人认真评估 Nix可复现构建的 ROI 开始显现
100+ 微服务必须上 Nix否则环境一致性问题会吃掉你所有运维资源

五、CRI-O:Kubernetes 专供的轻量运行时

5.1 CRI-O 的定位

CRI-O 是专门为 Kubernetes 设计的容器运行时,它的设计目标只有一个:做 CRI 的最佳实现

与 Containerd 的区别:

  • Containerd:通用容器运行时,支持 Docker/Compose/K8s 等多种前端
  • CRI-O:只支持 Kubernetes,不做任何 K8s 不需要的事
# CRI-O 安装(Ubuntu)
apt-get install -y cri-o cri-o-runc

# 启动
systemctl enable --now crio

# 配置文件
# /etc/crio/crio.conf
# /etc/crio/crio.conf 关键配置
[crio]
  # 使用 systemd cgroup
  cgroup_manager = "systemd"
  
[crio.runtime]
  # 默认运行时
  default_runtime = "runc"
  
  [crio.runtime.runtimes.runc]
    runtime_path = "/usr/bin/runc"
    runtime_type = "oci"
    
  # Kata Containers 运行时(虚拟化隔离)
  [crio.runtime.runtimes.kata]
    runtime_path = "/usr/bin/kata-runtime"
    runtime_type = "vm"
    
[crio.image]
  # 镜像仓库配置
  default_transport = "docker://"
  pause_image = "registry.k8s.io/pause:3.9"
  
  # 镜像签名验证
  signature_policy = "/etc/crio/policy.json"

5.2 CRI-O vs Containerd:生产环境怎么选?

维度ContainerdCRI-O
K8s 兼容性完美完美
非 K8s 场景支持(nerdctl/Compose)不支持
社区规模更大较小(Red Hat 主导)
OpenShift 默认
镜像签名验证需要额外配置内置支持
性能相当相当
调试工具nerdctlcrictl

选择建议

  • 如果你的环境纯 Kubernetes,且团队有 Red Hat 背景 → CRI-O
  • 如果你需要 K8s + 开发环境共享同一运行时 → Containerd
  • 如果你使用 OpenShift → CRI-O(唯一选择)
  • 如果你在纠结 → 选 Containerd(社区更大,资料更多)

六、Kaniko 与 Buildah:CI/CD 中的安全构建

6.1 为什么不应该在 CI 中用 Docker 构建?

# ❌ 危险做法:挂载 Docker Socket
- name: Build Image
  run: docker build -t my-app .
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock

挂载 Docker Socket 等于把宿主机的 root 权限给了 CI 容器——任何 CI 脚本都可以在宿主机上运行任意容器,这等于集群沦陷。

6.2 Kaniko:Google 出品的无特权构建

# Kaniko Executor 镜像内构建
FROM gcr.io/kaniko-project/executor:v1.23.0 AS kaniko

# Kaniko 不需要 Docker Daemon,不需要特权模式
# 它直接操作 overlayfs 构建镜像层
# GitLab CI 使用 Kaniko 构建
build:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:v1.23.0
    entrypoint: [""]
  script:
    - |
      /kaniko/executor \
        --dockerfile=Dockerfile \
        --context=. \
        --destination=registry.example.com/my-app:${CI_COMMIT_SHA:0:8} \
        --cache=true \
        --cache-repo=registry.example.com/cache \
        --snapshotMode=redo \
        --use-new-run=true
  only:
    - main

Kaniko 的优势

  • 不需要 Docker Daemon
  • 不需要特权模式
  • 支持缓存(远程 Registry 缓存层)
  • 支持多阶段构建

Kaniko 的局限

  • 不支持 ADD 指令从 URL 获取文件
  • 不支持 COPY --from 引用非前序阶段
  • 构建速度比 Docker + BuildKit 慢约 15%

6.3 Buildah:在 CI 中构建镜像的另一种选择

# GitHub Actions 使用 Buildah
- name: Build with Buildah
  uses: redhat-actions/buildah-build@v2
  with:
    image: my-app
    tags: latest ${{ github.sha }}
    dockerfiles: |
      ./Dockerfile
    build-args: |
      NODE_ENV=production

Buildah 可以在 rootless 模式下运行,配合 Podman 在 CI 中实现完整的无特权容器工作流。


七、OrbStack:macOS 开发环境的新选择

7.1 Docker Desktop 的替代方案

如果你是 macOS 开发者,Docker Desktop 的替代方案中值得关注的是 OrbStack:

# 安装 OrbStack
brew install orbstack

# Docker 兼容模式——无缝替换
# OrbStack 提供了完整的 Docker CLI 兼容层
docker run -d -p 5432:5432 postgres:16
docker compose up -d

# 性能对比
指标Docker Desktop 4.44OrbStack 1.8差异
启动时间12s2s-83%
内存占用(空载)1.2GB180MB-85%
文件系统性能VirtioFSVirtioFS + 优化+40%
CPU 占用(空载)3-5%0.5%-90%

OrbStack 还支持直接运行 Linux 虚拟机:

# 快速创建 Linux VM
orb create ubuntu:24.04 dev
orb ssh dev  # SSH 进入

# 在 VM 中运行任何 Linux 工具
orb run dev -- apt-get install -y redis-server

八、生产级选型决策树

8.1 完整决策流程

你的场景是什么?
│
├── Kubernetes 生产集群
│   ├── 需要虚拟化隔离(金融/医疗)→ CRI-O + Kata Containers
│   ├── 标准 Kubernetes 部署 → Containerd 2.x
│   └── OpenShift 环境 → CRI-O(唯一选择)
│
├── 开发环境
│   ├── macOS → OrbStack(性能优先)/ Docker Desktop(兼容优先)
│   ├── Linux → Podman(安全优先)/ Docker Engine(习惯优先)
│   └── Windows → Docker Desktop / WSL2 + Podman
│
├── CI/CD 镜像构建
│   ├── 已有 K8s 集群 → Kaniko(无特权)
│   ├── 需要 rootless → Buildah
│   └── 需要可复现 → Nix + nix2container
│
├── 高安全要求(金融/政务)
│   ├── 容器运行 → Podman rootless
│   ├── 镜像构建 → Nix(可复现 + 哈希验证)
│   └── 镜像签名 → CRI-O + cosign
│
└── 小团队 / 个人项目
    └── Docker Compose(学习成本最低,文档最丰富)

8.2 混合架构实战:一个真实的生产案例

以下是一个中型互联网公司的实际配置(50 微服务,3 个 K8s 集群):

# 生产集群:Containerd
# /etc/containerd/config.toml
version = 2
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
  SystemdCgroup = true

# CI 集群:Kaniko 构建
# .gitlab-ci.yml
stages:
  - build
  - deploy

build_image:
  stage: build
  image: gcr.io/kaniko-project/executor:v1.23.0
  script:
    - /kaniko/executor --dockerfile=Dockerfile --context=.
        --destination=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
        --cache=true

# 安全审计集群:Podman rootless
# 运行 Trivy 扫描、cosign 验签
scan_image:
  stage: test
  image: quay.io/podman/stable:v5
  script:
    - podman pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - trivy image --exit-code 1 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - cosign verify $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
# 开发环境统一脚本:dev-env.sh
#!/bin/bash
# 检测平台,自动选择最佳工具

if [[ "$(uname)" == "Darwin" ]]; then
    # macOS:优先 OrbStack
    if command -v orbstack &>/dev/null; then
        echo "Using OrbStack..."
        eval "$(orbstack docker-env)"
    else
        echo "Using Docker Desktop..."
    fi
elif [[ "$(uname)" == "Linux" ]]; then
    # Linux:优先 Podman
    if command -v podman &>/dev/null; then
        echo "Using Podman..."
        alias docker=podman
        alias docker-compose='podman-compose'
    else
        echo "Using Docker Engine..."
    fi
fi

九、OCI 标准:真正的终局

9.1 标准化让差异化消失

OCI(Open Container Initiative)定义了三个核心规范:

  1. Image Spec:镜像格式标准——oci-image-spec
  2. Runtime Spec:容器运行时标准——oci-runtime-spec
  3. Distribution Spec:镜像分发标准——oci-distribution-spec

当这三个规范全部标准化后,Docker 和 Podman 构建的镜像可以互换,Containerd 和 CRI-O 可以运行同一个镜像,Harbor 和 Docker Hub 都遵循相同的推送/拉取协议。

这意味着选择运行时不再是信仰问题,而是工程问题。

9.2 cosign:镜像供应链安全

无论你用什么运行时,镜像供应链安全都是不可回避的问题:

# 使用 cosign 对镜像签名
cosign sign --key cosign.key registry.example.com/my-app:v1.0.0

# 在 Kubernetes 中验证签名(Kyverno 策略)
# Kyverno 策略:只允许已签名的镜像运行
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signature
spec:
  validationFailureAction: Enforce
  rules:
  - name: verify-cosign-signature
    match:
      any:
      - resources:
          kinds:
          - Pod
    verifyImages:
    - imageReferences:
      - "registry.example.com/*"
      attestors:
      - entries:
        - keys:
            publicKeys: |-
              -----BEGIN PUBLIC KEY-----
              MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
              -----END PUBLIC KEY-----

9.3 SBOM 与漏洞扫描

# 使用 Syft 生成 SBOM(Software Bill of Materials)
syft registry.example.com/my-app:v1.0.0 -o spdx-json > sbom.json

# 使用 Grype 扫描漏洞
grype sbom:./sbom.json

# 将 SBOM 附加到镜像(cosign attest)
cosign attest --key cosign.key \
  --predicate sbom.json \
  --type spdxjson \
  registry.example.com/my-app:v1.0.0

十、2026 年容器技术趋势展望

10.1 WebAssembly 容器:下一个前沿

WASM 正在进入容器生态。WasmEdge 和 Fermyon Spin 允许你用 WASM 模块替代传统容器:

# 使用 WasmEdge 运行 WASM 应用
wasmedge run app.wasm

# 在 Kubernetes 中运行 WASM(Containerd + wasm shims)
# crun 已经内置 WASM 支持

WASM 容器的优势:

  • 冷启动 < 1ms(传统容器 100ms+)
  • 镜像体积 < 1MB(传统容器 50MB+)
  • 跨平台:一次编译,到处运行
  • 沙箱安全:WASM 的能力模型比 Linux 容器更严格

10.2 eBPF 改变容器网络

Cilium 已经成为 Kubernetes 最受欢迎的 CNI 之一,它基于 eBPF 实现了比 iptables 高 10 倍的网络性能:

# Cilium 替换 kube-proxy
apiVersion: cilium.io/v1alpha1
kind: CiliumLoadBalancerIPPool
metadata:
  name: default
spec:
  blocks:
  - start: "192.168.1.100"
    stop: "192.168.1.200"

10.3 镜像按需加载(Lazy Pulling)

Containerd 和 CRI-O 都在推动镜像按需加载——只拉取运行所需的层:

# Containerd 配置 Stargz Snapshotter(按需加载)
[plugins."io.containerd.snapshotter.v1.stargz"]
  root_path = "/var/lib/containerd/io.containerd.snapshotter.v1.stargz"
  
[plugins."io.containerd.grpc.v1.cri".containerd]
  snapshotter = "stargz"

镜像拉取时间从 30 秒降到 3 秒——对 Serverless 和快速扩缩容场景意义重大。


总结:工具没有信仰,只有适用场景

2026 年的容器生态已经不是一个"Docker 一统天下"的世界了。让我们用一张表来总结:

场景2026 最佳选择理由
K8s 生产运行时Containerd 2.x稳定、轻量、CNCF 标准
高安全环境Podman 5.0 rootless无守护进程、非 root
开发环境(macOS)OrbStack / Docker Desktop生态成熟、开箱即用
供应链安全Nix + nix2container声明式、哈希一致
CI/CD 构建Kaniko / Buildah无特权、安全
小团队起步Docker Compose学习曲线最平
OpenShiftCRI-O唯一选择
Serverless / 快速扩容WASM + Stargz冷启动 < 1ms

Docker 没有死,但它不再不可替代。 这不是 Docker 的失败,而是 OCI 标准的成功。当一切标准化之后,我们终于可以把精力放在解决业务问题上,而不是争论用什么工具。

选对的,不选热的。这就是 2026 年容器世界的生存法则。


本文基于 2026 年 5 月最新版本的容器运行时实测数据撰写。所有基准测试在 Apple M3 Pro / 36GB 环境下完成,实际性能可能因硬件和负载而异。

推荐文章

#免密码登录服务器
2024-11-19 04:29:52 +0800 CST
介绍 Vue 3 中的新的 `emits` 选项
2024-11-17 04:45:50 +0800 CST
快手小程序商城系统
2024-11-25 13:39:46 +0800 CST
关于 `nohup` 和 `&` 的使用说明
2024-11-19 08:49:44 +0800 CST
Golang 中你应该知道的 Range 知识
2024-11-19 04:01:21 +0800 CST
2025,重新认识 HTML!
2025-02-07 14:40:00 +0800 CST
Gin 框架的中间件 代码压缩
2024-11-19 08:23:48 +0800 CST
程序员茄子在线接单