13进阶:容器安全

1296 字
6 分钟
13进阶:容器安全

第 13 关 容器安全#

13.1 容器不是沙箱#

第 0 关说过一句要展开的话:容器不是安全沙箱。它共享宿主机内核,namespace 管「看得见什么」、cgroups 管「用多少」——这些是行为边界,不是安全边界。内核一个漏洞,隔离就可能被绕过。

所以容器安全不靠「容器」本身,靠你在外面加的那几道限制。这一关就是那几道。

13.2 密码别写进环境变量#

第 2 关起 mysql 时,root 密码是这么给的:

docker run -d --name db -e MYSQL_ROOT_PASSWORD=hecloud mysql

当时说「正经的密码管理留到第 13 关」。现在给完整解释。

为什么 -e 不行:那个密码会在三个地方明文躺着——

  1. 终端历史;
  2. docker inspect <容器> 的 Config.Env(第 12 关)——它会一直跟着这条容器;
  3. 容器里所有进程的环境变量,docker exec db env 就能看见。

而且环境变量是在容器创建那一刻抄进配置的:想改,只能删掉重建。

换成挂一个文件。 mysql 支持让密码从文件里读:

echo 'hecloud' > ./db_password
chmod 600 ./db_password
docker run -d --name db \
-e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db_password \
-v ./db_password:/run/secrets/db_password:ro \
mysql

-e 里现在只有路径,没有密码——inspect、env、终端历史里都看不到它。

更一般的界线:

放环境变量挂文件
适合不敏感、启动时读一次的配置(端口、模式、日志级别)密码、证书、密钥
改的代价重建容器改文件内容即可,挂载没变
会不会被 inspect 看见会不会

像第 2 关那个 nginx.conf 这样的配置文件,改了只要重启,容器不用重建——因为挂载没变,变的只是文件里的字。

mysql 的 root 密码是个特例:它只在第一次初始化时读,之后就不看了。所以改它还是得改数据库或重建;换成文件解决的是泄露面,不是「随时可改」。

compose 里有专门的 secrets:(第 18 关的字段表里),效果一样:以文件形式挂在 /run/secrets/<名字>,不进 Env。

13.3 别用 root 跑#

镜像不写 USER,容器里默认就是 root。容器里的 root 不等于宿主机 root(有 namespace 隔开),但一旦逃逸、或者挂了宿主机目录,破坏力会放大。

两个地方改:

RUN useradd -r app
USER app
Terminal window
docker run --user 1000:1000 <镜像>

易踩的坑:换了用户之后,写挂载目录的权限可能不对(容器里的 uid 和宿主机上的对不上)——第 4 关的绑定挂载会立刻现形,表现为「权限被拒」。

13.4 收走用不着的权力#

Linux 把 root 的权力拆成了几十份,叫 capabilities(NET_ADMIN、SYS_ADMIN、CHOWN……)。Docker 默认只给其中一部分,不是全部——但还能再收回一部分:

docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx

只留真正需要的。反过来最该避免的是 --privileged:它把能给的都给了,等于亲手把笼子拆了。

禁止提权:

docker run --security-opt no-new-privileges nginx

它挡住容器里的进程通过 setuid 程序升权。

系统调用与 seccomp

程序想用内核的能力——读文件、开网络、创建进程——都得经过一组固定的入口向内核「打申请」。这些入口叫系统调用(syscall)。

seccomp 是 Linux 给进程装的一张白名单:名单上有的才放行,不在名单上的直接拒绝(通常连进程一起杀掉)。

Docker 默认就给每个容器装了一张,挡掉一批容器用不着、却很适合搞破坏的调用——加载内核模块、重启系统、改系统时间之类。所以你什么都不做,这层已经在挡了。

一般不用动。真要收得更窄,才需要自己写一份:

docker run --security-opt seccomp=<配置文件> <镜像>

看它有没有生效:docker info 的 Security Options 里有一行 seccomp,下面写着 Profile: builtin——那就是默认白名单。

它和 capabilities 的分界:capabilities 管「你手里有哪几把钥匙」,seccomp 管「允许你敲哪几扇门」。有钥匙,门也可能被 seccomp 封着。

13.5 少开一扇门#

第 5 关讲过:一个容器可以挂多张网。安全上就是三条。

  1. 不该露的端口,不写 -p。数据库没有理由让宿主机访问。
  2. 要露的,绑到本机回环:
docker run -p 127.0.0.1:8080:80 nginx

不写 127.0.0.1 时,端口绑在 0.0.0.0 上——同一网段的人都能连。

  1. 给数据库一张内部网:
docker network create --internal db-net

--internal 建的网没有出口,外面也进不来,只有接在上面的容器互相可见。

13.6 一份最小清单#

  1. 密码、证书、密钥 → 挂文件,别放 -e;
  2. 能不用 root 就不用(USER / --user);
  3. --cap-drop ALL 再按需 --cap-add,别用 --privileged;
  4. --security-opt no-new-privileges;
  5. 不发布的端口不写 -p,发布时绑 127.0.0.1;
  6. 数据库放 --internal 网。

一句话:容器给的隔离是底线,不是保险。 上面每一条,都是在笼子上再加一道锁。

下一关处理另一类边界——不是「谁能不能进来」,而是「它能用掉多少 CPU 和内存」。

本关命令速查#

安全相关的运行参数,见 命令速查表 · 安全。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
13进阶:容器安全
https://hecloud.top/posts/tech/docker/13容器安全/
作者
贺小云
发布于
2026-10-29
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
贺小云
热爱技术与折腾的博客 Dancing with uncertainty. 与不确定性共舞
公告
出于成本考虑,贺云已全面转型Serverless架构
分类
标签
碎碎念
站点统计
文章
95
分类
5
标签
44
总字数
110,484
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Netlify CI
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0