为了账号安全,请及时绑定邮箱和手机立即绑定

容器里的进程到底是怎么回事?| Docker解释通俗了

作为一个容器实际上是一个在主机上运行的 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 用户运行的原因是,这样可以使用如 yumapt 这样的包管理器来安装软件。这样做在构建容器镜像时是完全合理的,但在软件包安装完毕之后,Dockerfile 中的后续步骤可以轻松地使用 USER 指令,使镜像以后台用户身份运行。

对于你自己的应用代码,尽可能使用非 root 用户账户,或者使用用户命名空间运行容器,以便容器内的 root 权限与主机上的 root 权限是隔离的。如果系统支持,你可以通过运行无根容器来实际使用用户命名空间。

👏 并订阅获取更多文章吧! 🖥️

[Docker 深入讲解由 Nigel Poulton]
[讲容器安全由 Liz Rice]

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消