作为一个容器实际上是一个在主机上运行的 Linux 进程,我们可以称它为 “容器化的进程”,但它只能看到主机的一部分,并且它只能访问文件系统的部分子树,或许只能访问有限的资源。
大多数容器只运行一个主要进程,也就是容器的主要应用进程,该进程的PID始终为1。用exec命令进入容器后,你就能看到这个情况。
ps
PID 用 时长 进程
1 root 0:00 node ./app.js
13 root 0:00 sh
22 root 0:00 ps
ps 进程一显示输出就立即终止了,而 sh 进程则会在你退出 exec 会话时终止。这意味着唯一一个长运行的进程就是运行 Node 应用的 PID 1。
如果你杀死容器的主进程(PID 1),容器也会被终止。这是因为容器依赖主进程运行——当主进程结束时,容器也停止运行。
从主机角度的容器因为它实际上只是一个进程,它存在于宿主操作系统中,并与宿主共享内核。接下来我们通过 Docker 容器,来看看它是怎么运作的。
启动一个基于 Ubuntu 的容器,并在其内运行一个进程,然后并在其中启动一个 shell,最后在其中执行一个长时间的睡眠。
$ docker run --rm -it ubuntu bash # 运行一个Ubuntu容器并打开bash终端
root@1551d24a $ sleep 1000 # 使容器暂停1000秒
这个示例让睡眠命令运行1,000秒,但请注意睡眠命令是在容器内的一个进程中运行着。当你按下回车键结束睡眠命令时,这会触发Linux创建一个新的进程,拥有一个新的进程ID号,并在该进程中运行睡眠程序。
你可以将处于休眠状态的进程放到后台(按下 Ctrl-Z 来暂停进程,再输入 bg %1 让它进入后台)。现在在容器里运行 ps 命令,从容器的角度看看这个进程:
me@myhost:~$ docker run --rm -it ubuntu bash
root@ab6ea36fce8e:/$ sleep 1000
^Z
[1]+ 停止了 sleep 1000
root@ab6ea36fce8e:/$ bg %1 # 将作业放到后台运行
[1]+ sleep 1000 &
root@ab6ea36fce8e:/$ ps
PID TTY TIME 命令
1 pts/0 00:00:00 bash
10 pts/0 00:00:00 sleep 1000 秒
11 pts/0 00:00:00 ps
打开同一主机上的第二个终端,当sleep命令仍在运行时,从主机的角度来看同样的睡眠进程。
me@myhost:~$ ps -C sleep
PID TTY TIME CMD
30591 pts/0 00:00:00 sleep
容器有自己的进程ID命名空间,因此它的进程拥有低编号是合乎逻辑的,当你在容器里运行ps命令时确实可以看到这一点。但从主机的角度来看,休眠进程却具有较高编号的进程ID。在上述例子中,只有一个进程,在主机上,它的ID是30591,在容器里则是10。
要充分理解隔离,关键是理解到尽管有两个不同的进程ID号,但它们实际上指的是同一个进程。只是从主机的视角来看,它有更高的进程ID号。
安全容器和其主机共享一个内核,这会对容器主机的最佳实践产生一些影响。如果主机被攻破,该主机上的所有容器都有可能受到攻击,尤其是如果攻击者获得了 root 权限(最高权限)或其他提升的权限(例如成为 Docker 用户组成员,在这种情况下,能够管理容器,特别是在当 Docker 作为运行时使用时)。
除非你的容器镜像文件指定了非 root 的用户,或者你在启动容器时指定了非默认的用户,否则容器将默认以 root 用户启动。
$ docker run -it alpine sh
运行 Alpine 容器并启动 shell。
$ whoami
显示当前用户是谁。
root (root用户)
(注:root
表示用户是超级管理员用户。)
即使运行 docker 命令创建容器的用户是非 root 的,容器内的 root 用户身份仍然反映了主机上的 root。
在 Docker 环境下,即使是由非 root 用户启动的容器也以 root 权限运行,这是一种权限提升的表现。容器以 root 权限运行本身并不一定构成问题,但在安全方面考虑时,这确实会引起安全方面的警觉。如果攻击者能够从以 root 权限运行的容器中逃逸出来,他们将获得主机的完全 root 权限,这意味着他们可以自由访问机器上的所有内容。
当然,你可以使用 —user
选项来覆盖用户的配置,例如:
$ docker run -it --user 5000 ubuntu bash
我没有名字!root:b7ca6ec82aa4:/$
如果没有指定用户ID的话,,默认情况下你的容器就会以 root 用户身份运行。
基本要求.有许多常用的容器镜像,这些镜像包含了原本为直接在服务器上运行设计的流行软件。例如,Nginx反向代理。
默认情况下,标准的 Nginx 容器镜像默认是以 root 用户身份运行的。启动一个 nginx 容器后,查看其内部的进程,你会发现主进程是以 root 用户身份运行的:
$ docker run -d --name nginx nginx
16cc31d9f65c3300d27095f736ce11e5b6632559a93f11dc6d2a5879df76904d
$ docker top nginx
PID 用户ID 时间 命令
91413 root 0:00 nginx: 主程序 nginx -g daemon off;
91458 101 0:00 nginx: 工作程序
在这种情形下,当它运行在服务器上时,让nginx以root用户运行是完全合理的。为了打开低编号端口(小于1024),需要CAP_NET_BIND_SERVICE权限,最简单的方法就是让它以root用户运行。构建一个可以以非root用户运行的Nginx镜像相对简单。
另一个将容器镜像配置为以 root 用户运行的原因是,这样可以使用如 yum 或 apt 这样的包管理器来安装软件。这样做在构建容器镜像时是完全合理的,但在软件包安装完毕之后,Dockerfile 中的后续步骤可以轻松地使用 USER 指令,使镜像以后台用户身份运行。
对于你自己的应用代码,尽可能使用非 root 用户账户,或者使用用户命名空间运行容器,以便容器内的 root 权限与主机上的 root 权限是隔离的。如果系统支持,你可以通过运行无根容器来实际使用用户命名空间。
👏 并订阅获取更多文章吧! 🖥️
[Docker 深入讲解由 Nigel Poulton]
[讲容器安全由 Liz Rice]
共同学习,写下你的评论
评论加载中...
作者其他优质文章