crun 配置参数详解
详解 crun(C 语言轻量 OCI 运行时)的配置参数:config.json 全字段(crun spec 生成、process/root/mounts/linux/resources/namespaces)、cgroup resources 实测落地、命令行全局 flags 真实默认值、Podman containers.conf 运行时配置与安全项,已在 Ubuntu 22.04(crun 1.20)实测。
crun 配置参数详解
目标:详解 crun 低层 OCI 运行时(runtime)的全部配置入口——它没有独立的长驻配置文件,实体配置全部放在 OCI bundle 的
config.json(由crun spec生成、可按字段改动)里,另有少量行为由命令行全局 flags 控制默认值。本文逐字段拆解 config.json,并用真实命令回显验证 cpu/memory/pids 限制、maskedPaths/readonlyPaths、capabilities 等关键字段是否真正生效。已在 Ubuntu 22.04.5 LTS(x86_64),crun 1.20 实测通过。
💡 定位:crun 是低层 OCI 运行时,只负责「按 config.json 把 OCI bundle 拉起来成进程」,它本身不管理镜像、无守护进程。日常由 Podman / Buildah / CRI 栈按 OCI 接口调用;理解 crun 配置,本质就是理解 config.json。
一、概述
1.1 crun 没有独立配置文件
crun 与 MySQL、Redis 这类「读 ini/conf 配置文件」的守护式服务完全不同:
- crun 不扫描任何系统级配置文件(如
/etc/crun/*.conf),也不维护长驻进程; - 它的一切运行参数由 OCI bundle 中的
config.json描述,bundle 是在被调用的那一刻由上层工具(Podman 等)现场生成并传入的; config.json必须放在 bundle 根目录,并与rootfs目录平级(root.path指向它);- 少量全局行为(日志、cgroup 管理器、root 目录等)由命令行 flags 控制,见四、节。
因此在 crun 语境里,「配置参数」= config.json 的字段 + 命令行全局 flags。
1.2 实测环境与版本
在 Ubuntu 22.04.5 LTS(x86_64,内核 Linux cgroups v2)上:
root@ubuntu2204:~# crun --version
crun version 1.20
commit: 1.20
rundir: /run/crun
spec: 1.0.0
+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +YAJL
rundir /run/crun:crun 管理运行态容器状态的默认 root 目录(对应全局 flag--root的默认值);spec: 1.0.0:当前编译时对应的 OCI runtime-spec 版本;- 编译特性
+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +YAJL:开启了对 systemd、SELinux、AppArmor、capabilities、seccomp、eBPF、libyajl(JSON) 的支持。
二、crun spec 详解
在 bundle 目录执行 crun spec 会生成一份可直接运行的最小 config.json(红色默认即最小可用集):
root@ubuntu2204:/opt/bundle# cd /opt/bundle && rm -f config.json && crun spec
root@ubuntu2204:/opt/bundle# cat config.json
{
"ociVersion": "1.0.0",
"process": {
"terminal": true,
"user": { "uid": 0, "gid": 0 },
"args": [ "sh" ],
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm"
],
"cwd": "/",
"capabilities": { "bounding": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ],
"effective": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ],
"inheritable": [],
"permitted": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ],
"ambient": [ "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": "crun",
"mounts": [ { "destination": "/proc", "type": "proc", "source": "proc" },
{ "destination": "/dev", "type": "tmpfs", "source": "tmpfs", "options": ["nosuid","strictatime","mode=755","size=65536k"] },
{ "destination": "/dev/pts", "type": "devpts", "source": "devpts", "options": ["nosuid","noexec","newinstance","ptmxmode=0666","mode=0620","gid=5"] },
{ "destination": "/dev/shm", "type": "tmpfs", "source": "shm", "options": ["nosuid","noexec","nodev","mode=1777","size=65536k"] },
{ "destination": "/dev/mqueue", "type": "mqueue", "source": "mqueue", "options": ["nosuid","noexec","nodev"] },
{ "destination": "/sys", "type": "sysfs", "source": "sysfs", "options": ["nosuid","noexec","nodev","ro"] },
{ "destination": "/sys/fs/cgroup", "type": "cgroup", "source": "cgroup", "options": ["nosuid","noexec","nodev","relatime","ro"] } ],
"linux": {
"resources": { "devices": [ { "allow": false, "access": "rwm" } ] },
"namespaces": [ { "type": "pid" }, { "type": "network" }, { "type": "ipc" },
{ "type": "uts" }, { "type": "cgroup" }, { "type": "mount" } ],
"maskedPaths": [ "/proc/acpi", "/proc/asound", "/proc/kcore", "/proc/keys", "/proc/latency_stats",
"/proc/timer_list", "/proc/timer_stats", "/proc/sched_debug", "/sys/firmware", "/proc/scsi" ],
"readonlyPaths": [ "/proc/bus", "/proc/fs", "/proc/irq", "/proc/sys", "/proc/sysrq-trigger" ]
}
}
各顶层字段含义:
| 字段 | 顶层作用域 | 作用 |
|---|---|---|
ociVersion | 顶层 | 声明的 OCI runtime-spec 版本号(crun 支持 1.0.0 到 1.1.0+dev,见 crun features 的 ociVersionMin/Max) |
process | 顶层 | 定义容器内第 1 号进程(init)从何而来、以什么身份/环境跑 |
root | 顶层 | 指明 rootfs 目录与是否只读挂载 |
hostname | 顶层 | 容器内 UTS 命名空间的主机名 |
mounts | 顶层 | 容器内额外挂载点列表(/proc、/dev、/sys 等) |
linux | 顶层 | Linux 平台特有的资源、命名空间、安全与用户映射配置 |
三、关键字段示例与实测
3.1 process:进程与身份
process 决定 init 进程的启动方式,常见关键子字段:
"process": {
"terminal": false,
"user": { "uid": 0, "gid": 0 },
"args": [ "sh", "-c", "echo run-ok; hostname" ],
"env": [ "PATH=/usr/sbin:/usr/bin", "TERM=xterm" ],
"cwd": "/",
"rlimits": [ { "type": "RLIMIT_NOFILE", "hard": 1024, "soft": 1024 } ]
}
terminal:true分配 ptys(交互式),false直接复用 stdout/stderr(非交互)。实测非交互跑crun run,容器 stdout 直接透传到终端;user.uid/gid:init 进程的宿主视角 uid/gid,默认0/0;args:argv[0] 及参数,即容器内 PID 1 执行的命令;env:注入的环境变量,注意 PATH 需自给自足(不以宿主机继承);cwd:初始工作目录,默认/;rlimits:资源限制,默认会给RLIMIT_NOFILE软硬限 1024。
实测回显(terminal=false,args 执行打印后退出):
root@ubuntu2204:/opt/bundle# crun run cors-demo
run-ok
crun-demo
3.2 root:rootfs 与只读
"root": { "path": "rootfs", "readonly": true }
path:rootfs 目录(相对 bundle 目录),容器的新根目录即挂载它;readonly:true将整个 rootfs 以只读方式挂载,配合可写挂载点实现“只读根 + 局部可写”。默认是只读。
⚠️ 若用
readonly: true又要写数据,需在mounts里单独挂一个可写 tmpfs(如/tmp),否则容器内任何写根的操作都会报Read-only file system。
3.3 hostname(UTS)与 namespaces
"hostname": "crun-demo",
"linux": { "namespaces": [
{ "type": "pid" }, { "type": "network" }, { "type": "ipc" },
{ "type": "uts" }, { "type": "cgroup" }, { "type": "mount" } ] }
hostname只在使用 uts 命名空间时才有隔离意义,实测进入容器执行hostname回显为crun-demo;linux.namespaces:要创建的命名空间类型。默认 pid / network / ipc / uts / cgroup / mount 全套创建;- 若省略
network命名空间,则为「host 网络」模式(容器与宿主机共享网络栈)。
实测回显(UTS 隔离生效):
root@ubuntu2204:/opt/bundle# crun exec demo-sleep hostname
crun-demo
3.4 linux.resources:cpu / memory / pids 限制
资源限制在 linux.resources,crun 会将它们写入 cgroups v2 的对应属性文件。给容器配置如下:
"linux": {
"cgroupsPath": "/crun-res",
"resources": {
"cpu": { "shares": 256, "quota": 50000, "period": 100000 },
"memory":{ "limit": 67108864, "swap": 67108864 },
"pids": { "limit": 32 },
"devices": [ { "allow": false, "access": "rwm" } ]
}
}
参数含义:
| 子字段 | 单位/含义 | 说明 |
|---|---|---|
| resources.cpu.shares | 权重 | CPU 时间权重(默认 1024,值越高得越多) |
| resources.cpu.quota / period | 微秒 | 每 period 周期内最多可用 quota 微秒的 CPU,50000/100000=最多 50% 单核 |
| resources.memory.limit | 字节 | 内存上限(67108864=64MB) |
| resources.memory.swap | 字节 | 内存+swap 总和上限(与 limit 相等即禁 swap) |
| resources.pids.limit | 个 | 该 cgroup 内最多允许的进程/线程数 |
| resources.devices | 布尔 | 设备访问白/黑名单,默认 allow=false 拒绝所有 |
实测回显(容器运行中查看对应 cgroups v2 文件):容器运行后,在对应 cgroup 目录内:
# 内存上限 memory.max(字节) / swap
67108864
# pids 上限 pids.max
32
# cpu 配额/周期 cpu.max(quota period)
50000 100000
即 crun 把 resources 如实落到 cgroups v2:memory.max、pids.max、cpu.max 与配置一一对应,资源限制真实生效。
3.5 linux.cgroupsPath:cgroup 路径
"linux": { "cgroupsPath": "/crun-demo" }
- 该值指定容器 cgroup 在 cgroups v2 unified hierarchy 中的挂载路径;
- 实测容器进程
/proc/1/cgroup首行即0::/crun-demo,证明 init 落入该 cgroup; - 留空时 crun 自动生成路径;生产建议显式指定唯一路径,便于用
systemd-cgtop、podman ps对账。
实测回显:
root@ubuntu2204:~# crun exec demo-sleep cat /proc/1/cgroup | head -1
0::/crun-demo
3.6 linux.uidMappings / gidMappings:用户命名空间映射
rootful 运行(root 用户直接跑 crun)时,默认不配置 uidMappings/gidMappings,容器内 uid/gid 与宿主机一致(process.user.uid=0 即在宿主为 root)。rootless 运行需要把容器内 uid 映射到宿主非特权用户,示例:
"linux": {
"uidMappings": [ { "containerID": 0, "hostID": 1000, "size": 65536 } ],
"gidMappings": [ { "containerID": 0, "hostID": 1000, "size": 65536 } ]
}
containerID:容器命名空间内的起始 uid;hostID:对应映射到的宿主 uid;size:连续映射的 uid 个数。
⚠️ 启用上述字段需 crun 编译开启 user 命名空间支持,并在 rootless 场景配合
newuidmap/newgidmap(详见crun --help=params的--rootless)。rootful 场景不填即映射 1:1。
3.7 linux.maskedPaths / readonlyPaths:屏蔽与只读路径
对内核暴露的敏感 proc/sys 路径做「屏蔽(mask)」或「只读(read-only)」,是防御内核对容器开放的加固手段:
"linux": {
"maskedPaths": [ "/proc/kcore", "/proc/keys", "/proc/sched_debug", "/sys/firmware", "/proc/scsi" ],
"readonlyPaths": [ "/proc/bus", "/proc/fs", "/proc/irq", "/proc/sys", "/proc/sysrq-trigger" ]
}
maskedPaths:路径被替换成空的目标(看似可写但写入被丢弃),防止读到内核内存等敏感内容;readonlyPaths:路径被只读重挂载,写操作直接失败。
实测回显(同一进程内分别写两类路径):
sh: 1: cannot create /proc/sys/kernel/hostname: Read-only file system
RC2=2
(写 /proc/kcore 的 masked 路径返回 0、不报错但内容被丢弃;写 /proc/sys/kernel/hostname 的 readonly 路径报 Read-only file system、返回码 2。)
3.8 mounts 二次说明
mounts 数组按顺序挂载。除 root 的 rootfs 外,minimum mount 必备 /proc(proc)、/dev(tmpfs)、/sys(sysfs)等。追加自定义目录时同理加一项,例如给容器提供可写临时目录:
"mounts": [
{ "destination": "/tmp", "type": "tmpfs", "source": "tmpfs",
"options": [ "nosuid", "size=512m", "mode=1777" ] }
]
四、crun 命令行 flags
crun 无配置文件,部分全局行为需要在命令行上控制。用 crun --help 查看全部全局 flags 与默认值:
root@ubuntu2204:~# crun --help
Usage: crun [OPTION...] COMMAND [OPTION...]
COMMANDS:
create - create a container
delete - remove definition for a container
exec - exec a command in a running container
features - show the enabled features
list - list known containers
kill - send a signal to the container init process
ps - show the processes in the container
run - run a container
spec - generate a configuration file
start - start a container
state - output the state of a container
pause - pause all the processes in the container
resume - unpause the processes in the container
update - update container resource constraints
--cgroup-manager=MANAGER cgroup manager
--debug produce verbose output, similar to --log-level=debug
--log=FILE log destination: '[file:]PATH', 'journald:ID' or 'syslog:ID' (defaults to stderr)
--log-format=FORMAT log format: 'text' (default) or 'json'
--log-level=LEVEL log level to use: 'error' (default), 'warning' or 'debug'
--root=DIR
--rootless=VALUE
--systemd-cgroup use systemd cgroups
-?, --help Give this help list
| 全局 flag | 默认值 / 取值 | 说明 |
|---|---|---|
--root=DIR | /run/crun(rootful) | crun 存放运行态容器状态文件的目录(即 --version 的 rundir) |
--cgroup-manager=MANAGER | 自动探测(本环境 cgroupfs / cgroups v2) | cgroup 管理器,可选 cgroupfs 或 systemd;与 --systemd-cgroup 等效表达 |
--systemd-cgroup | 关 | 用 systemd 管理 cgroup(等价 --cgroup-manager=systemd) |
--log=FILE | stderr | 日志输出目的地,支持 [file:]PATH、journald:ID、syslog:ID |
--log-level=LEVEL | error | 日志级别:error(默认)、warning、debug |
--log-format=FORMAT | text | 日志格式:text(默认) 或 json |
--debug | 关 | 打开冗长调试输出,等价 --log-level=debug |
--rootless=VALUE | 自动探测 | 是否按 rootless 运行(auto/true/false) |
用法示例(指定状态目录、cgroup 管理器为 systemd、日志打到文件并以 debug 级别):
root@ubuntu2204:/opt/bundle# crun --root /var/run/crun --cgroup-manager systemd \
--log /var/log/crun.log --log-level debug run myapp
💡
--log-level=debug时 crun 会逐条打印 config.json 解析、挂载、cgroup 创建等过程,是排查“为何限制没生效”的首选工具。
五、在 Podman 中配置 crun
crun 是 Podman 的默认低层运行时。Podman 的运行时由 /etc/containers/containers.conf(或每用户 ~/.config/containers/containers.conf)定义与选择。
5.1 运行时定义(engine.runtimes)
在 containers.conf 的 [engine.runtimes] 段登记可用运行时与可执行文件搜索路径(示例为 crun,找到任一即用):
[engine.runtimes]
crun = [
"/usr/bin/crun",
"/usr/sbin/crun",
"/usr/local/bin/crun",
"/usr/local/sbin/crun",
"/bin/crun",
"/sbin/crun",
]
5.2 选择默认运行时(runtime = "crun")
在 [engine] 段设置 runtime 指定默认使用的运行时名:
[engine]
runtime = "crun"
5.3 实测确认当前默认运行时
在已配置好 Podman + crun 的 Ubuntu 22.04 上,podman info 会显示当前生效的 OCI 运行时名字与路径:
root@ubuntu2204:~# podman --version
podman version 3.4.4
root@ubuntu2204:~# podman info | grep -A3 ociRuntime
ociRuntime:
name: crun
package: 'crun: /usr/bin/crun'
path: /usr/bin/crun
💡 确认运行中的容器确实跑在 crun 上:
podman info --format '{{.Host.OCIRuntime.Name}}'应输出crun;containers.conf示例修正为完整候选路径即可(登记可从注释复制)。
5.4 单次指定运行时
不改全局配置,可在命令行临时指定本次容器用哪个运行时:
# 按 crun(默认)
podman run --rm ubuntu:22.04 echo hello
# 显式指定 runc 跑一次
podman run --rm --runtime /usr/bin/runc ubuntu:22.04 echo hello
⚠️ Podman 3.x 的
containers.conf落在/usr/share/containers/containers.conf(只读模板)与/etc/containers/containers.conf(可覆盖)。前者[engine.runtimes]中 crun 各路径默认注释为示例,建议在/etc或用户级文件里按5.1覆盖启用。
六、安全配置
6.1 capabilities(能力集)
config.json 中 process.capabilities 有 5 个子集,默认生成的容器只保留最小能力 CAP_AUDIT_WRITE、CAP_KILL、CAP_NET_BIND_SERVICE(能写审计、发信号、绑定特权端口,但无提权/网络管理能力):
"process": { "capabilities": {
"bounding": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ],
"effective": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ],
"inheritable":[],
"permitted": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ],
"ambient": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ] } }
实测回显(容器内读 /proc/self/status 的 Cap 位,CapBnd/Prm/Eff 一致、Ambient/Inheritable 为空):
CapInh: 0000000000000000
CapPrm: 0000000020000420
CapEff: 0000000020000420
CapBnd: 0000000020000420
CapAmb: 0000000000000000
0x20000420 = CAP_NET_BIND_SERVICE + CAP_KILL + CAP_AUDIT_WRITE,与实际配置的 bounding 能力完全吻合。生产要降权就把对应能力从 bounding/effective 里删掉。
6.2 noNewPrivileges(禁止提权)
"process": { "noNewPrivileges": true }
- 默认
true:容器内即使有 setuid 二进制或拉起setuid,也不会获得比 init 进程更多权限,是用CAP_SYS_ADMIN提权类攻击的兜底开关;建议保持true。
6.3 seccomp(系统调用过滤)
⚠️ crun 默认不注入任何 seccomp 过滤,实测容器内 Seccomp 值为 0/Seccomp_filters: 0(无过滤)。需要系统调用过滤时必须在 linux.seccomp 显式配置。最小白名单示例:
"linux": { "seccomp": {
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [ "SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32" ],
"syscalls": [
{ "names": [ "read", "write", "openat", "close", "mmap", "munmap",
"brk", "exit", "exit_group", "wait4" ],
"action": "SCMP_ACT_ALLOW" } ] } }
即 defaultAction=SCMP_ACT_ERRNO(默认拒绝所有未列系统调用),仅放行列出的基础 syscall(read/write/openat/mmap/exit 等),按业务需要再往里加。若改用 defaultAction=SCMP_ACT_ALLOW(默认允许)则反过来只写禁止清单(如列 reboot、kexec_load 为 SCMP_ACT_ERRNO 封锁)。
⚠️ seccomp 白名单写错会让容器内进程无法启动(如
exec format error/operation not permitted),排查时用--log-level debug定位。
6.4 结合短板补齐
- maskedPaths / readonlyPaths 已默认覆盖高危 proc/sys(见 3.7),无需另配;
- 若容器需要写
/etc或日志,记得配可写 tmpfs mount 或关掉root.readonly。
七、与 runc 配置差异
crun 与 runc 都是符合 OCI runtime-spec 的低层运行时,config.json 格式高度一致(都吃同一份 OCI bundle),差异主要在实现与个别字段支持上:
| 维度 | crun 1.20 | runc 1.3.4 |
|---|---|---|
| 语言 | C(依赖 libc/libyajl/libcap) | Go |
| 启动延迟 / 内存占用 | 更低,毫秒级拉起 / 数 MB | 略高 |
| config.json 结构 | OCI 1.0.0–1.1.0+dev | 基本一致 |
| 命令行子命令 | run/create/start/exec/kill/delete/list/state/ps/pause/resume/update/spec/features | run/create/start/exec/kill/delete/list/state/ps/pause/resume/update/spec/features |
| features 输出 | crun features 输出 JSON | runc features 输出 JSON |
| cgroup | cgroupfs / systemd | cgroupfs / systemd |
| 状态目录 | /run/crun | /run/runc |
| rootless / user ns | 支持 | 支持 |
实测两种运行时共存同一 bundle 的用法(同一份 config.json,两个运行时都能拉起):
root@ubuntu2204:/opt/bundle# crun run app1 # crun 拉起
root@ubuntu2204:/opt/bundle# runc --root /run/runc run app2 # 同一 bundle 用 runc 拉起
- 由上可见:字段级并无“crun 专属”配置,
config.json是 OCI 通吃的;真正的差异体现在旗标、性能与状态目录上; - 少数 runc 支持、crun 支持或有差异的扩展(如 systemd-cgroup、AppArmor/SELinux 配置路径)以
crun features/crun --help输出为准。
八、常见问题
8.1 改了 cpu/memory 却没生效
确认三点:linux.resources 确实放在 linux 下而非 process 下;cgroup 文件落在对应 cgroupsPath 目录;以及是否启用了 systemd cgroup 管理器(--cgroup-manager systemd 时得上报 systemd 才能正确建 scope)。用 crun --log-level debug 查看是否成功写 cpu/memory 文件。
8.2 容器内写根目录报 Read-only file system
默认 root.readonly: true 且 rootfs 只读。挂一个可写 tmpfs 或把 root.readonly 置为 false:
"mounts": [ { "destination": "/tmp", "type": "tmpfs", "source": "tmpfs", "options": ["mode=1777"] } ]
8.3 容器内读 /proc/kcore 拿到敏感内核数据
现象
容器内 /proc/kcore 显示为真实内核内存文件,存在信息泄露 / 根因分析误读风险(默认 maskedPaths 已屏蔽,若自定义注掉会暴露)。
定位
检查 config.json 是否保留默认 linux.maskedPaths 中的 /proc/kcore;实测写入 masked 路径返回 0 但内容被丢弃(见 3.7)。
解决
保持 maskedPaths 默认值,若嫌列表冗余可只保留最关键几项:
"linux": { "maskedPaths": [ "/proc/kcore", "/proc/keys", "/sys/firmware" ] }
8.4 想给容器减权限但提权还是能成功
确认 noNewPrivileges 是否为 true(默认 true),并把 process.capabilities.bounding/effective 里的能力删到只剩必要项;同时对风险 syscall 用 linux.seccomp 白名单(见 6.3)。
8.5 rootless 跑不了(uid/gid 不对)
rootless 需配置 linux.uidMappings/gidMappings 并配合 newuidmap/newgidmap(见 3.6);同时用 --rootless true 或让 crun 自动探测。rootful 场景不建议配 user namespace 映射,保持默认 1:1。
九、参考链接
- crun 官方源码与文档:https://github.com/containers/crun
- OCI runtime-spec(config.json 字段权威规范):https://github.com/opencontainers/runtime-spec
- crun features / capabilities / seccomp 相关 man 手册:
man crun、man crun.1 - Podman containers.conf(5) 手册:
man containers.conf - 同专栏关联文档:《Ubuntu 22.04 安装 crun 指南》《crun 常用 CLI 命令与工具详解》