2015 年 6 月,Docker 公司将 libcontainer(runc 的前身)捐赠给 Linux 基金会,成立了 OCI(Open Container Initiative)。这个决定改变了容器生态的走向,从此,容器镜像格式和运行时接口有了开放标准,任何人都可实现兼容的运行时,不再被 Docker 的私有格式锁定。
OCI 定义了三大规范:Image Spec(镜像长什么样)、Runtime Spec(怎么运行容器)、Distribution Spec(怎么分发镜像)。这三个规范串联起来,定义了从镜像构建到容器运行的完整链路。后来 OCI 又增加了 Artifact Spec,把镜像格式推广到 Helm Chart、SBOM 等非容器制品,但它不是最初的三大核心规范,定位和成熟度与前三个不同。
前置知识
Ch02 Linux Namespace 深入:OCI Runtime Spec 定义了如何配置 Namespace,理解 Namespace 是理解 Runtime Spec 的前提
Ch03 Cgroup v2 深入:OCI Runtime Spec 定义了如何配置 Cgroup 资源限制
Ch04 OverlayFS:容器文件系统:OCI Image Spec 定义了镜像层的格式,OverlayFS 是镜像层复用的底层机制
OCI 规范是 Ch02-04 三大内核机制的”标准化包装”,掌握了 Namespace/Cgroup/OverlayFS,再看 OCI 规范会发现它只是把这些内核能力用 JSON 描述清楚
OCI 规范是容器生态的”宪法”,runc、containerd、CRI-O、Kubernetes 都遵循它。
下面逐一拆解三大规范的细节。
一、OCI 组织与规范体系
1.1 OCI 的组织结构
1.2 三大规范的关系
| 规范 | 定义内容 | 输入 | 输出 |
|---|---|---|---|
| Image Spec | 镜像格式 | Dockerfile/BuildKit | OCI Image(manifest+config+layers) |
| Runtime Spec | 运行时接口 | OCI Bundle(rootfs+config.json) | 运行中的容器 |
| Distribution Spec | 分发协议 | OCI Image | Registry API(push/pull) |
二、OCI Image Spec
2.1 镜像的组成
OCI 镜像由以下部分组成:
| 组件 | 格式 | 说明 |
|---|---|---|
| Manifest | JSON | 镜像的入口,指向 config 和 layers |
| Config | JSON | 镜像的配置(环境变量、入口命令、层描述) |
| Layer | tar+gzip | 文件系统层(增量变更) |
| Index | JSON | 多架构镜像的索引(可选) |
{ "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 7023, "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 32654, "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0" }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 16724, "digest": "sha256:3c3a4f68c8e1e3e2b5c26a0e7e1f6a8c9b2d1e4f5a6b7c8d9e0f1a2b3c4d5e6" } ], "annotations": { "org.opencontainers.image.source": "https://github.com/example/app" }}2.2 Image Config
Image Config 定义了镜像的运行时配置:
{ "architecture": "amd64", "os": "linux", "config": { "User": "nginx", "ExposedPorts": {"80/tcp": {}}, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "NGINX_VERSION=1.21.3" ], "Entrypoint": ["/docker-entrypoint.sh"], "Cmd": ["nginx", "-g", "daemon off;"], "Volumes": {"/var/cache/nginx": {}}, "WorkingDir": "/", "Labels": {"maintainer": "NGINX Docker Maintainers"} }, "rootfs": { "type": "layers", "diff_ids": [ "sha256:a1b2c3d4e5f6...", "sha256:b2c3d4e5f6a7..." ] }, "history": [ {"created": "2021-01-01T00:00:00Z", "comment": "ADD file:..."}, {"created": "2021-01-01T00:01:00Z", "comment": "CMD [\"/bin/sh\"]"} ]}2.3 镜像层(Layer)
每个 Layer 是一个 tar 归档文件,包含相对于上一层的文件系统变更:
- 新增文件:直接包含在 tar 中
- 删除文件:使用 whiteout 标记(
.wh.<filename>) - 删除目录:使用 opaque whiteout(
.wh..wh..opq)
# 查看镜像层的内容docker save nginx -o nginx.tartar -xf nginx.tar
# 查看某一层的文件列表layer_id=$(cat manifest.json | python3 -c "import json, sysm = json.load(sys.stdin)print(m[0]['Layers'][0])")tar -tzf "$layer_id" | head -20
# 查看 whiteout 文件tar -tzf "$layer_id" | grep ".wh."2.4 多架构镜像(Image Index)
OCI Image Index 支持多架构镜像,一个 manifest 列表指向不同平台的镜像:
{ "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:abc123...", "size": 7143, "platform": {"architecture": "amd64", "os": "linux"} }, { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:def456...", "size": 7143, "platform": {"architecture": "arm64", "os": "linux"} } ]}2.5 Image Spec 的媒体类型
| 媒体类型 | 用途 |
|---|---|
application/vnd.oci.image.manifest.v1+json | 镜像 Manifest |
application/vnd.oci.image.config.v1+json | 镜像 Config |
application/vnd.oci.image.layer.v1.tar+gzip | 压缩层 |
application/vnd.oci.image.layer.v1.tar | 未压缩层 |
application/vnd.oci.image.layer.nondistributable.v1.tar+gzip | 不可分发层(Image Spec v1.1 已弃用,改用普通 layer 加 annotation 表达) |
application/vnd.oci.image.index.v1+json | 多架构索引 |
三、OCI Runtime Spec
3.1 OCI Bundle
OCI Bundle 是 Runtime Spec 的中心概念,一个包含 rootfs 和 config.json 的目录:
config.json容器运行时配置
rootfs容器的根文件系统
bin
dev
etc
lib
proc
sys
usr
# 创建 OCI Bundlemkdir -p /tmp/bundle/rootfscd /tmp/bundledocker export $(docker create busybox) | tar -C rootfs -xvf -runc spec # 生成默认 config.json3.2 config.json 详解
config.json 是 OCI Runtime Spec 的主配置文件,定义了容器的所有运行时参数:
{ "ociVersion": "1.0.2", "process": { "terminal": true, "user": {"uid": 0, "gid": 0}, "args": ["sh"], "env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"], "cwd": "/", "capabilities": { "bounding": ["CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE"], "effective": ["CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE"], "permitted": ["CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE"] }, "rlimits": [{"type": "RLIMIT_NOFILE", "hard": 1024, "soft": 1024}], "noNewPrivileges": true }, "root": {"path": "rootfs", "readonly": true}, "hostname": "mycontainer", "mounts": [ {"destination": "/proc", "type": "proc", "source": "proc"}, {"destination": "/dev", "type": "tmpfs", "source": "tmpfs", "options": ["nosuid","noexec","mode=755"]}, {"destination": "/sys", "type": "sysfs", "source": "sysfs", "options": ["nosuid","noexec","ro"]} ], "linux": { "namespaces": [ {"type": "pid"}, {"type": "mount"}, {"type": "ipc"}, {"type": "uts"}, {"type": "network"} ], "resources": { "memory": {"limit": 536870912}, "cpu": {"quota": 200000, "period": 100000} }, "seccomp": { "defaultAction": "SCMP_ACT_ERRNO", "architectures": ["SCMP_ARCH_X86_64"], "syscalls": [{"names": ["accept","access","arch_prctl"], "action": "SCMP_ACT_ALLOW"}] } }}这段 config.json 里有几个安全相关的字段值得展开说明。
capabilities 的选择逻辑
runc spec 生成的默认 capabilities 只有三个:CAP_AUDIT_WRITE、CAP_KILL、CAP_NET_BIND_SERVICE。这不是随意挑选的,而是 OCI 规范在”最小权限”和”容器可用性”之间权衡的结果:
CAP_AUDIT_WRITE:允许向内核审计日志写入记录。这个 capability 看起来不起眼,但某些基础库(如 pam、systemd 组件)在初始化时会调用audit_write()系统调用。缺少它不会导致进程崩溃,但会产生大量 “operation not permitted” 审计拒绝日志,干扰宿主机审计系统的正常工作。保留它是为了”不打扰宿主机”,而非容器自身需要CAP_KILL:允许向非子进程发送信号。容器内常见的场景是 init 进程(如 tini、dumb-init)需要向子进程转发 SIGTERM 以实现优雅退出。没有这个 capability,init 进程只能等待子进程自行退出,可能导致容器无法正常停止CAP_NET_BIND_SERVICE:允许绑定 1024 以下的特权端口。大量容器化 Web 服务(nginx、envoy 等)需要监听 80/443,缺少此 capability 会导致bind()系统调用返回EACCES,服务启动失败。如果容器内服务只监听高位端口(如 8080),则可以安全地移除这个 capability
对比 Docker 的默认配置,Docker 会赋予更多 capabilities(约 14 个),而 runc spec 只保留最核心的三个,体现了更严格的最小权限原则。生产环境中应该根据实际需求裁剪,不需要绑定特权端口的容器就不该保留 CAP_NET_BIND_SERVICE。
noNewPrivileges 的安全含义
noNewPrivileges: true 是一个容易被忽略但影响深远的设置。它对应 Linux 内核的 prctl(PR_SET_NO_NEW_PRIVS, 1) 调用,效果是:此后该进程及其所有子进程,无论是否执行了 setuid 程序(如 sudo、ping),都不会获得比当前更多的权限。
具体来说,设置了 noNewPrivileges 之后:
- 执行 setuid 二进制文件(如
/usr/bin/sudo)不会提升权限 - setgid 机制同样失效
- 文件的 capability 位(如
/bin/ping自带的CAP_NET_RAW)不会被激活
这是容器安全的重要防线。考虑一个场景:容器以 uid 0 运行但只分配了少量 capabilities,如果容器内存在 setuid 二进制文件(如某些基础镜像自带的 su、newgrp),没有 noNewPrivileges 的话,执行这些文件会获得完整的 root 权限,绕过 capability 限制。runc spec 默认开启它,说明 OCI 规范倾向于”安全优先”的默认值。
noNewPrivileges 和 capabilities 配合使用时有一个容易混淆的点:noNewPrivileges 阻止的是通过 exec 获得新权限,但不影响进程启动时已经分配的 capabilities。也就是说,bounding 集合中的 capability 在 noNewPrivileges 开启时仍然有效,只是后续 exec 执行的程序无法获得超出 bounding 集合的权限。
seccomp defaultAction: SCMP_ACT_ERRNO 的设计
seccomp 配置中 defaultAction: SCMP_ACT_ERRNO 采用了”默认拒绝”策略:所有未在 syscalls 白名单中明确允许的系统调用,都会返回 ERRNO(默认返回 EPERM,即 “Operation not permitted”),而不是直接杀死进程。
选择 SCMP_ACT_ERRNO 而非 SCMP_ACT_KILL 有实际考量:
SCMP_ACT_KILL会直接发送SIGSYS信号杀死进程,进程没有机会清理资源,且调试时只能看到 “killed” 而不知道哪个系统调用被拦截SCMP_ACT_ERRNO返回错误码,应用可以优雅地处理权限拒绝(降级功能或输出错误信息),而不是直接崩溃- 对于被误拦截的系统调用,
ERRNO模式下应用通常能继续运行(功能降级),而KILL模式下进程直接终止 - 调试时可以通过
strace看到被拒绝的系统调用及其返回的EPERM,比排查 “killed” 容易得多
白名单中只列出了 accept、access、arch_prctl 等少量系统调用,这是 runc spec 的示例,实际生产中 containerd/Docker 会加载更完整的 seccomp profile(通常允许 300+ 个系统调用)。
3.3 config.json 的关键配置项
| 配置项 | 说明 | 对应 Docker 参数 |
|---|---|---|
| process.args | 容器启动命令 | CMD / ENTRYPOINT |
| process.env | 环境变量 | -e |
| process.user | 运行用户 | -u |
| process.capabilities | Linux Capabilities | —cap-add / —cap-drop |
| root.path | rootfs 路径 | — |
| root.readonly | 只读根文件系统 | —read-only |
| hostname | 主机名 | -h |
| mounts | 挂载点列表 | -v |
| linux.namespaces | Namespace 配置 | — |
| linux.resources | Cgroup 资源限制 | —memory, —cpus |
| linux.seccomp | seccomp 过滤规则 | —security-opt |
| linux.uidMappings | User Namespace UID 映射 | —userns |
| linux.rootPropagation | Mount 传播类型 | — |
3.4 容器生命周期
OCI Runtime Spec 定义了容器的标准生命周期:
3.5 运行时命令
OCI Runtime Spec 定义了运行时必须实现的命令:
| 命令 | 功能 | 状态变化 |
|---|---|---|
create | 创建容器(不启动进程) | → Created |
start | 启动容器进程 | Created → Running |
kill | 发送信号 | Running → Stopped |
delete | 删除容器和资源 | Stopped → (删除) |
state | 查询容器状态 | — |
features | 查询运行时特性 | — |
# OCI 运行时的标准操作流程runc create mycontainer # 创建容器(状态:Created)runc start mycontainer # 启动容器(状态:Running)runc state mycontainer # 查看状态runc kill mycontainer SIGTERM # 发送信号runc delete mycontainer # 删除容器四、OCI Distribution Spec
4.1 Registry API
Distribution Spec 定义了镜像仓库的标准 API:
| API | 方法 | 功能 |
|---|---|---|
/v2/ | GET | API 版本检查 |
/v2/<name>/manifests/<reference> | GET/PUT/DELETE | Manifest 操作 |
/v2/<name>/blobs/<digest> | GET/PUT/DELETE | Blob(层/配置)操作 |
/v2/<name>/tags/list | GET | 标签列表 |
/v2/<name>/blobs/uploads/ | POST | 开始上传 Blob |
# 使用 OCI Distribution API 拉取镜像# 1. 获取 Manifestcurl -s -H "Accept: application/vnd.oci.image.manifest.v1+json" \ https://registry.example.com/v2/myapp/manifests/latest
# 2. 下载 Configcurl -s \ https://registry.example.com/v2/myapp/blobs/sha256:b5b2b2c507a...
# 3. 下载 Layercurl -s \ https://registry.example.com/v2/myapp/blobs/sha256:9834876dcfb...4.2 推送流程
4.3 镜像拉取流程
# containerd 的镜像拉取流程(简化)ctr image pull docker.io/library/nginx:latest
# 底层操作:# 1. GET /v2/library/nginx/manifests/latest → 获取 Manifest# 2. 解析 Manifest,获取 Config 和 Layers 的 digest# 3. 检查本地是否已有对应 blob(跳过已下载的层)# 4. GET /v2/library/nginx/blobs/sha256:xxx → 下载缺失的 blob# 5. 解压 layers 到 OverlayFS# 6. 验证 digest 一致性五、OCI 规范与容器运行时的关系
5.1 从 Image Spec 到 Runtime Spec
containerd 负责将 Image Spec 转换为 Runtime Spec:
// containerd 将 OCI Image 转换为 OCI Bundle(简化)func imageToBundle(image ocispec.Image, rootfs string) (*specs.Spec, error) { spec := &specs.Spec{ Version: specs.Version, Process: &specs.Process{ Args: image.Config.Cmd, Env: image.Config.Env, Cwd: image.Config.WorkingDir, }, Root: &specs.Root{ Path: rootfs, Readonly: false, }, Mounts: defaultMounts(), Linux: &specs.Linux{ Namespaces: defaultNamespaces(), }, }
// 处理 Entrypoint + Cmd if len(image.Config.Entrypoint) > 0 { spec.Process.Args = append(image.Config.Entrypoint, image.Config.Cmd...) }
// 处理 User if image.Config.User != "" { uid, gid := parseUser(image.Config.User) spec.Process.User = specs.User{UID: uid, GID: gid} }
return spec, nil}5.2 规范兼容性矩阵
| 运行时 | Image Spec | Runtime Spec | Distribution Spec |
|---|---|---|---|
| runc | 否 | 是 | 否 |
| containerd | 是 | 是 | 是 |
| CRI-O | 是 | 是 | 是 |
| gVisor (runsc) | 否 | 是 | 否 |
| Kata Containers | 否 | 是 | 否 |
| WasmEdge | (扩展) | (扩展) | 否 |
低层运行时(runc/gVisor/Kata)只实现 Runtime Spec,不关心镜像格式和分发协议。高层运行时(containerd/CRI-O)实现全部三个规范。
六、动手实践
6.1 手动构建 OCI 镜像
#!/bin/bash# 手动构建 OCI 镜像(不使用 Docker)
set -eIMAGE_DIR="/tmp/oci-image"mkdir -p $IMAGE_DIR/blobs/sha256
# 1. 创建 rootfs 层ROOTFS_DIR="/tmp/rootfs"mkdir -p $ROOTFS_DIR/{bin,etc,dev,proc,sys}echo '#!/bin/sh' > $ROOTFS_DIR/bin/helloecho 'echo "Hello OCI!"' >> $ROOTFS_DIR/bin/hellochmod +x $ROOTFS_DIR/bin/helloecho "root:x:0:0::/root:/bin/sh" > $ROOTFS_DIR/etc/passwd
# 2. 打包层cd $ROOTFS_DIRtar -cf layer.tar .LAYER_DIGEST=$(sha256sum layer.tar | cut -d' ' -f1)mv layer.tar $IMAGE_DIR/blobs/sha256/$LAYER_DIGESTLAYER_SIZE=$(stat -c%s $IMAGE_DIR/blobs/sha256/$LAYER_DIGEST)
# 3. 创建 ConfigCONFIG=$(cat <<EOF{ "architecture": "amd64", "os": "linux", "config": {"Cmd": ["/bin/hello"]}, "rootfs": {"type": "layers", "diff_ids": ["sha256:$LAYER_DIGEST"]}}EOF)echo -n "$CONFIG" > config.jsonCONFIG_DIGEST=$(sha256sum config.json | cut -d' ' -f1)CONFIG_SIZE=$(stat -c%s config.json)mv config.json $IMAGE_DIR/blobs/sha256/$CONFIG_DIGEST
# 4. 创建 ManifestMANIFEST=$(cat <<EOF{ "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "config": {"mediaType": "application/vnd.oci.image.config.v1+json", "size": $CONFIG_SIZE, "digest": "sha256:$CONFIG_DIGEST"}, "layers": [{"mediaType": "application/vnd.oci.image.layer.v1.tar", "size": $LAYER_SIZE, "digest": "sha256:$LAYER_DIGEST"}]}EOF)echo -n "$MANIFEST" > manifest.jsonMANIFEST_DIGEST=$(sha256sum manifest.json | cut -d' ' -f1)mv manifest.json $IMAGE_DIR/blobs/sha256/$MANIFEST_DIGEST
# 5. 创建 Image Layoutecho '{"imageLayoutVersion":"1.0.0"}' > $IMAGE_DIR/oci-layoutecho "{\"schemaVersion\":2,\"manifests\":[{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"digest\":\"sha256:$MANIFEST_DIGEST\",\"size\":$(stat -c%s $IMAGE_DIR/blobs/sha256/$MANIFEST_DIGEST)}]}" > $IMAGE_DIR/index.json
echo "OCI Image created at $IMAGE_DIR"6.2 用 runc 运行自定义 Bundle
#!/bin/bash# 从 OCI 镜像创建 Bundle 并运行
# 1. 从上面的 OCI 镜像解压 rootfsBUNDLE_DIR="/tmp/my-bundle"mkdir -p $BUNDLE_DIR/rootfscd $BUNDLE_DIR
# 解压层到 rootfs# 注意:不能用 tar -xf /tmp/oci-image/blobs/sha256/* 这样的通配符,# 因为 blobs/sha256/ 目录下不仅有 layer,还有 config 和 manifest,# 它们都是 JSON 文件而非 tar 归档,通配符解压会报错。# 正确做法:先解析 index.json → 找到 manifest → 从 manifest 中提取 layer digestMANIFEST_DIGEST=$(python3 -c "import jsonwith open('/tmp/oci-image/index.json') as f: idx = json.load(f)print(idx['manifests'][0]['digest'].split(':')[1])")
# 从 manifest 中提取所有 layer 的 digestpython3 -c "import json, subprocess, oswith open('/tmp/oci-image/blobs/sha256/$MANIFEST_DIGEST') as f: manifest = json.load(f)for layer in manifest['layers']: digest = layer['digest'].split(':')[1] blob_path = f'/tmp/oci-image/blobs/sha256/{digest}' print(f'Extracting layer: {digest[:12]}...') subprocess.run(['tar', '-xf', blob_path, '-C', '/tmp/my-bundle/rootfs/'], check=True)print('All layers extracted.')"
# 2. 生成 config.jsonrunc spec
# 3. 修改 process.argspython3 -c "import jsonwith open('config.json') as f: spec = json.load(f)spec['process']['args'] = ['/bin/hello']with open('config.json', 'w') as f: json.dump(spec, f, indent=2)"
# 4. 运行sudo runc run my-oci-container# Hello OCI!附、实践:手工构建 OCI Bundle 并运行
本节从零构建一个符合 OCI Runtime Spec 的 Bundle,用 runc 运行容器。所有命令需要 root 权限。
附.1 创建 OCI Bundle 目录
OCI Bundle 的最小结构:一个 rootfs/ 目录和一个 config.json 文件。
mkdir -p /tmp/oci-bundle/rootfscd /tmp/oci-bundle附.2 准备 rootfs
从 Docker 导出 Alpine 的文件系统作为 rootfs:
docker export $(docker create alpine:latest) | tar -xf - -C /tmp/oci-bundle/rootfs/附.3 生成 config.json
runc spec 命令生成符合 OCI Runtime Spec 的默认配置:
runc spec查看生成的配置摘要:
python3 -c "import jsonwith open('/tmp/oci-bundle/config.json') as f: spec = json.load(f)print('ociVersion:', spec.get('ociVersion'))print('process.args:', spec.get('process', {}).get('args'))print('namespaces:', [ns['type'] for ns in spec.get('linux', {}).get('namespaces', [])])"ociVersion: 1.0.2process.args: ['sh']namespaces: ['pid', 'network', 'ipc', 'uts', 'mount', 'cgroup']默认配置会创建 6 种 Namespace(PID/Network/IPC/UTS/Mount/Cgroup),运行 sh 命令。
附.4 修改 config.json
将默认的 sh 改为一条有输出的命令,方便验证:
python3 -c "import jsonwith open('/tmp/oci-bundle/config.json') as f: spec = json.load(f)spec['process']['args'] = ['/bin/sh', '-c', 'echo \"Hello from OCI container!\"; hostname; ps aux']spec['process']['terminal'] = Falsewith open('/tmp/oci-bundle/config.json', 'w') as f: json.dump(spec, f, indent=2)"process.terminal 必须设为 false,否则 runc 会尝试打开 /dev/tty 导致报错。
附.5 用 runc 运行
cd /tmp/oci-bundle && runc run my-oci-demoHello from OCI container!runcPID USER TIME COMMAND 1 root 0:00 ps aux关键观察:
hostname返回runc,UTS Namespace 生效,容器有独立的主机名ps aux只显示 1 个进程,PID Namespace 生效,容器有独立的进程树- 整个过程没有 Docker 参与,runc 直接读取 config.json + rootfs 创建容器
这就是 OCI Runtime Spec 的价值:一个 JSON 文件 + 一个 rootfs 目录 = 一个容器。Docker、containerd、CRI-O 都是在这个基础上添加更高级的管理能力。
实验结束后清理:runc delete my-oci-demo && rm -rf /tmp/oci-bundle
七、本章小结
上一章探讨了 OverlayFS 分层文件系统。
| 规范 | 核心概念 | 关键文件/格式 |
|---|---|---|
| Image Spec | 镜像格式 | manifest.json, config.json, layer.tar |
| Runtime Spec | 运行时接口 | config.json, rootfs/ |
| Distribution Spec | 分发协议 | /v2/ API |
OCI 规范的核心价值在于标准化,任何符合规范的镜像可以在任何符合规范的运行时上运行。但标准化是有代价的:规范只能定义所有实现的最小公约数,runc 的安全隔离能力、gVisor 的系统调用拦截、Kata 的硬件虚拟化,这些差异无法在规范中表达。规范保证了互操作,却无法保证安全等价;它加速了生态的兼容性,也限制了创新的速度,任何新特性要进入规范,必须经过漫长的多方协商,而运行时往往先以扩展方式实现,再等待标准化追上。
OCI 规范只定义了接口标准,不保证实现的安全性。runc、gVisor、Kata 都符合 Runtime Spec,但隔离强度差异巨大,runc 共享宿主内核,gVisor 用用户态内核拦截系统调用,Kata 用硬件虚拟化隔离。选型时要根据威胁模型决定,不能只看”OCI 兼容”就认为安全。
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






