本节摘要:交付前的安检三层次——镜像层管攻击面起点、运行层管爆炸半径、宿主层管码头本身。本节给出一份可直接落地的基线清单,并兑现第 1 章埋下的承诺:共享内核的隔离上限,工程上怎么补。
第一关:安检。容器安全的常见误区是把它当成单点动作("装个扫描器就行")。码头安保的逻辑其实更诚实:**验货(镜像)、锁箱(运行)、管港(宿主)**三层各司其职,任何一层单干都会漏。本节按这三层展开,每层给出可执行的检查动作。

镜像是你交付物的本体,安检从这里开始。四条基线,逐条可查:
来源可信。 底座镜像只用官方源或企业私有堆场的白名单货——来源不明的镜像等于来历不明的集装箱,里面装什么全凭运气。私有堆场的访问控制(3.5 节)是这道关的硬件。
漏洞扫描。 对镜像做已知漏洞扫描,交付流水线里设为强制关卡:
# 用开源扫描器对镜像体检(以 trivy 为例,结果按严重度分级) trivy image myapp:1.0.0 # 输出(节选): # myapp:1.0.0 (alpine 3.20.1) # Total: 3 (UNKNOWN: 0, LOW: 1, MEDIUM: 1, HIGH: 1, CRITICAL: 0)
流水线的纪律通常是:高危及以上漏洞阻断交付,中危限期整改。扫描要进流水线而不是攒到上线前——发现越早,换底座越从容。
最小化。 3.4 节的瘦身在这里升级成安全命题:终箱里没有 shell、没有包管理器、没有调试工具,攻击者即使得手也难以横移。瘦身清单就是安检清单。
无敏感残留。 密钥、证书、内网地址一旦进了层就永远在层里(3.4 讲过"删除不减体积"的推论:删过的密钥照样能从旧层里翻出来)。基线动作:密钥只走环境注入或密钥服务;构建日志与 .dockerignore 定期审计。
锁箱的目的是把"万一被攻破"的爆炸半径压到最小。核心动作是非 root 运行:
# 装箱单里声明专用低权限用户(容器安全的第一优先项) FROM node:20-alpine WORKDIR /app COPY . . RUN chown -R node:node /app # 目录属主交给专用用户 USER node # 之后一切进程以 node 身份运行 CMD ["node", "server.js"]
运行参数侧的锁箱组合拳:
# 只读根文件系统 + 可写区单独挂仓 + 裁剪能力 + 禁止提权 docker run -d --name hardened-app \ --read-only \ --tmpfs /tmp \ -v app-data:/var/lib/app \ --cap-drop ALL \ --security-opt no-new-privileges \ --memory 512m --cpus 1 \ myapp:1.0.0 # --read-only 根文件系统只读:写操作只能落在指定卷 # --cap-drop ALL 摘掉内核默认赋予的全部特权能力 # --security-opt no-new-privileges 禁止进程提权 # 配额照旧:资源维度也是锁箱的一部分(4.5 节)
这组参数的含义是"假设应用被攻破,攻击者拿到的也只是:无 root、只读根、无特权能力、写不进系统目录、吃不满宿主机资源"。安全的目标从来不是不可能被攻破,而是被攻破时损失可控——与第 1 章讲的隔离上限话题正好接上:共享内核的上限补不过虚拟机,但锁箱能把上限大幅推高。
码头本身的安全,三件事:引擎及时更新(容器引擎的漏洞公告值得订阅);守护接口不裸奔(引擎的远程接口默认不该暴露公网,历史上"无鉴权引擎接口被扫到"是真实的大规模安全事件来源);宿主机最小化(宿主机上跑的东西越少,容器逃逸后的落脚点越小)。再配一套基础审计——谁在什么时候起吊了什么箱子,要有账可查:
# 审计视角:全机容器清单与启动参数快照(定时留存) docker ps -a --no-trunc --format "{{.Names}}\t{{.Image}}\t{{.Status}}" > audit-$(date +%F).txt docker inspect --format "{{.Name}}: {{.HostConfig.Privileged}}" \ $(docker ps -q) >> audit-$(date +%F).txt # Privileged 为 false 是底线:特权容器等半个 root,使用必须专项审批
在宿主与运行的交界处,还有两项进阶防御值得知道其存在:用户命名空间重映射(把容器内的 root 映射为宿主机上的普通用户,箱子里的 root 不再等于船上的 root)与无根引擎(整个引擎以非特权用户运行,连守护进程都不碰 root)。两者都有配置与兼容成本,属于"高风险业务按需启用"的选项;多数团队做好前述三层基线,已经能挡住绝大多数现实攻击路径。安全是分层加固的持续工程,不是一次性达标的清单——把安检动作固化进流水线(下一节),基线才不会随时间腐化。
基线写完还要能查。对任何一只在航箱子,三查当场点验:查身份(谁在跑)、查锁(根文件系统)、查特权(能力与提权):
# 一查身份:运行用户是不是 root docker inspect hardened-app --format "用户:{{.Config.User}}" # 用户:node <- 输出为空即默认 root,红灯 # 二查锁:根文件系统是否只读 docker inspect hardened-app --format "只读根:{{.HostConfig.ReadonlyRootfs}}" # 只读根:true # 三查特权:特权模式与能力裁剪 docker inspect hardened-app --format "特权:{{.HostConfig.Privileged}} 裁剪:{{.HostConfig.CapDrop}}" # 特权:false 裁剪:[ALL]
把三查拼进定时任务,每天输出偏离基线的箱子名单,就是最朴素的容器合规年检。上线那天合规不等于永远合规——镜像被替换、参数被"临时"放宽,基线都会悄悄腐化,年检制度正是对人为漂移的兜底。三查全绿,才谈得上把交付交给下一节的流水线。
安检合格,箱子可以进流水线了。下一节让交付自动化起来。