S6 在 LAIN 集群中的应用实践


近几年,容器技术迅猛发展,并在各大企业得到了广泛应用。它标准化了应用程序的运行环 境,从而简化了程序的部署流程,同时也促进了 PaaS(Platform as a Service)系统的发展,LAIN 即为其中之一。作为一种革命性的新技术,Docker 具有种种优点,比如一处编译、处处运行,易于编排和轻量等等;但是,由于发展时间较短, 在生产环境中使用时,Docker 在一些环节出现了性能问题,另外,Docker 容器与虚拟机之 间也有一些微妙的区别,在生产环境中使用时需要给予特别的注意。下面分享一下 LAIN 生 产集群在运行过程中遇到的一些问题和解决方案。

不断累积的僵尸进程

为了方便管理,我们把 Jenkins 搬进了容器。但是,Jenkins 容器经常停止响应, 需要定期重启。为什么在虚拟机上可以正常运行的程序到了容器里就出现问题了呢? 通过 ps aux 可以发现,Jenkins 容器里有大量的僵尸进程,即 图. 1 中的 [git-remote-http] 进程。
01.png

图 1:zombie processes

进程表是有限的系统资源,当僵尸进程占用了大量的进程表空间时,就会导致无法启动新的 进程。那为什么容器里会产生大量的僵尸进程呢?因为在类 UNIX 系统中,PID 为 1 的 进程是特殊的:它负责收割僵尸进程。在虚拟机中,PID 为 1 的进程通常是 Systemd、 Sys Vinit 或者 upstart,它们都能自动收割僵尸进程;在容器里,情况有很大的不同: PID 为 1的进程常常是用户自己写的程序,很可能不会收割僵尸进程,比如上面的 jenkins-master,这时候就会造成僵尸进程的累积,最终导致容器内无法再启动新的进程。

偶尔卡死的 Docker Daemon

Docker Daemon 可以收集容器的标准输出,然后使用 syslog 或 json-file 等 log-driver 处理。但是,这个架构是中心化的,Docker Daemon 会成为日志收集的瓶颈。 表. 1 是我们测得的 Docker 收集日志的速度。
b1.png

表 1:Docker 的日志收集速度

可以看到,这个速度并不理想,当容器的标准输出较多时,Docker Daemon 不能及时处理, 就会影响这个容器的正常运行;同时,Docker Daemon 对标准输出的处理会阻塞其他操作, 比如 docker ps 和 docker stop 等命令也会卡死。这时,我们只能重启 Docker Daemon,同时禁止容器的标准输出。

一站式解决方案 —— S6

上述 2 个问题严重地影响了应用的正常运行,是否有办法可以解决呢?即,我们希望找到 一个工具,既可以收割僵尸进程,又可以把标准输出重定向到文件并自动 rotate。 表. 2 是我们找到的一些工具。
b2.png

表 2:进程管理工具的比较

从上表可以看出,S6 是最满足我们需求的解决方案。而且,它体积很小,只有 904 KB,启 动时间不超过 100 ms,运行时占用的 CPU 和内存可以忽略不计。

S6 包含 s6-svscan、s6-supervise 和 s6-log 等组件。这些组件遵循 UNIX 设计哲学,相互独立,功能正交,通过适当组合可 以实现强大的功能。那具体怎样组合呢?S6 的作者把类 UNIX 系统的运行时可以分为 3 个 阶段(Bercot,n.d.),如图. 2 所示。 而 s6-overlay 实现了此方案,下面参考 s6-overlay 说明如何在容器中使用 S6 管理进程。
02.png

图 2:类 UNIX 系统的运行时阶段8

阶段 1

在阶段 1,s6-overlay 准备环境变量和创建 s6 的工作目录 /var/run/s6/services 等, 然后启动 PID 为 1 的 s6-svscan 以管理 /var/run/s6/services 下的服务:
s6-svscan -t0 /var/run/s6/services

为了适应 LAIN 的需求,我们还将 Dockerfile 里的 CMD 写入了 /etc/services.d/app/run。整个流程如图. 3 所示。
03.png

图 3:阶段 1

阶段 2

在阶段 2,s6-overlay 首先把 /etc/services.d 里的文件复制到 s6-svscan 的运行时 目录 /var/run/s6/services,然后通过 s6-svscanctl -a /var/run/s6/services 触 发 s6-svcan 对此目录的检索;s6-svcan 发现 /var/run/s6/services/app/run 和 /var/run/s6/services/app/log/run 后,会调用 s6-supervise 分别启动这个两个服 务,如图. 4 所示。
04.png

图 4:阶段 2

首先,容器运行过程中,s6-svcan 会收割僵尸进程,解决了我们的第一个问题。

其次,s6-log 将 CMD 的标准输出重定向到 /lain/logs/default/current,而不是发送到 Docker Daemon,这样就避免了 Docker Daemon 收集日志的性能瓶颈, 表. 3 是我们的测试结果。可以看到,在 日志文件 test.log 体积较小的时候,s6-log 收集日志的速度可以达到 200 MB/s 左 右,几乎与直接写入文件的速度相当;当 test.log 的体积逐渐增大时,因为会触发越来 越频繁的日志轮转,所以 s6-log 的速度逐渐降低,逐渐趋近于 90 MB/s 左右。 s6-log 只有在日志文件达到 256 MB 时才会触发轮转,而这种情况在实际的生成环境中 并不会频繁发生,因此 s6-log 的实际日志收集速度应该在 100 MB/s 以上,足以满足我 们的需求,解决了我们的第二个问题。
b3.png

表 3:s6-log 的日志收集速度

阶段 3

s6-supervise 还会监听 SIGTERM 信号,当 s6-supervise 收到此信号后进入阶段 3: 执行 /var/run/s6/services/app/finish,也就是 s6-svscanctl -t /var/run/s6/services,从而让整个容器优雅地退出,这比 Docker 默认的等待 10 秒后 强制杀死进程的行为友好很多。

综上,在 Docker 中使用 S6 可以获得以下好处:
  • 自动收割僵尸进程
  • 在保留自动 rotate 功能的同时提高日志收集的速度
  • 适当处理 SIGTERM 信号,优化 docker stop 的用户体验


参考文献:

0 个评论

要回复文章请先登录注册