1コンテナ上で、nginxとfluentdを動かした話。
どうして複数プロセスを起動してはいけないの
Dockerコンテナではプロセスを1つだけ起動させる、とは有名なContainerベストプラクティスですが、これはどうしてでしょう。いろいろ理由があると思いますが、明確な技術的理由として、DockerではCMDオプションやENTRYPOINTオプションで起動したプロセスがコンテナ上のPID1になるからだ、と思っています。
試しに、nginxのコンテナを起動してプロセスを確認してみると、PID1は、nginxのmaster processとなっています。
$ docker run --rm -d --name nginx nginx $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1d4d0754f57a nginx "nginx -g 'daemon of…" 6 seconds ago Up 4 seconds 80/tcp nginx $ docker exec -it 1d4d0754f57a bash root@1d4d0754f57a:/# apt update && apt install -y procps root@1d4d0754f57a:/# ps -aef UID PID PPID C STIME TTY TIME CMD root 1 0 0 06:28 ? 00:00:00 nginx: master process nginx -g daemon off; nginx 6 1 0 06:28 ? 00:00:00 nginx: worker process root 427 0 0 06:35 pts/0 00:00:00 bash root 676 427 0 06:37 pts/0 00:00:00 ps -aef
つまりinitプログラムがおらず、Supervisorの機能もないので、複数プロセスを起動させる場合にはUNIXの世界でのルール違反になってしまう、と。以下のブログで詳しく説明してくれています。
Docker and the PID 1 zombie reaping problem
このあたりが分かりやすい記述でしょうか。
Let's look at a concrete example. Suppose that your container contains a web server that runs a CGI script that's written in bash. The CGI script calls grep. Then the web server decides that the CGI script is taking too long and kills the script, but grep is not affected and keeps running. When grep finishes, it becomes a zombie and is adopted by the PID 1 (the web server). The web server doesn't know about grep, so it doesn't reap it, and the grep zombie stays in the system.
つまり、PID1がinitじゃないから、親プロセスを殺された孫プロセスはゾンビプロセスになってしまいます。まあ、initいないのに、プロセスをforkやspawnしまくるのは危険ですよね。
runitについて
じゃあ複数プロセスを起動させたい場合にはどうすればいいのさ、という話ですが、コンテナ上でinit的なものをPID1で動かしてあげれば良い訳です。それにあたるものとして runit
というものがあります。
各説明サイトたち。
runit - a UNIX init scheme with service supervision
phusion/baseimage
について
phusion/baseimage
とは
で、この runit
を同梱してくれたコンテナイメージがあり、それが phusion/baseimage
です。
phusion/baseimage
は、 runit
以外にも以下のようなサービスをコンテナ上で起動してくれます。
- Ubuntu 16.04 LTS as base system
- A correct init process
- Fixes APT incompatibilities with Docker
- syslog-ng
- The cron daemon
- An optional SSH server (disabled by default)
- Runit for service supervision and management
コンテナというより、仮想サーバって感あり。
Dockerfile構成
この phusion/baseimage
を利用して、1コンテナ上でnginxとfluentdを動かしてみます。下記のようなコンテナを作成します。
- nginxプロセスを起動
- 一般的なwebサーバーとして動作
- flutendプロセスを起動
- nginxアクセスログをs3に転送
folder/file structure
. ├── Dockerfile ├── service │ ├── nginx │ │ └── run │ └── td-agent │ └── run └── td-agent └── td-agent.conf
Dockerfile
FROM phusion/baseimage:0.11 ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn ENV DEBIAN_FRONTEND=noninteractive # install nginx # https://nginx.org/en/linux_packages.html#Ubuntu RUN apt-get update && \ apt-get install -y --no-install-recommends curl=7.58.0-2ubuntu3.7 gnupg2=2.2.4-1ubuntu1.2 ca-certificates=20180409 lsb-release=9.20170808ubuntu1 && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* RUN echo "deb http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" > /etc/apt/sources.list.d/nginx.list RUN curl -o nginx_signing.key -fsSL https://nginx.org/keys/nginx_signing.key && \ apt-key add nginx_signing.key RUN apt-get update && \ apt-get install -y --no-install-recommends nginx=1.14.0-0ubuntu1.2 && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* RUN rm /etc/init.d/nginx EXPOSE 80 # install td-agent # https://toolbelt.treasuredata.com/sh/install-ubuntu-bionic-td-agent3.sh RUN curl -o GPG-KEY-td-agent -fsSL https://packages.treasuredata.com/GPG-KEY-td-agent && \ apt-key add GPG-KEY-td-agent RUN echo "deb http://packages.treasuredata.com/3/ubuntu/bionic/ bionic contrib" > /etc/apt/sources.list.d/treasure-data.list RUN apt-get update && \ apt-get install -y --no-install-recommends td-agent=3.4.1-0 && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* COPY td-agent /etc/td-agent/ RUN rm /etc/init.d/td-agent # upload runit service script COPY service /etc/service RUN chmod 755 /etc/service/*/run
以下はrunitのユーザーレベルサービスを定義しているファイル。initにおける、init.dみたいなもの。ここにおいた処理を、起動時に runsvdir
というプログラムが起動してくれます。
service/nginx/run
#!/bin/sh exec /usr/sbin/nginx -c /etc/nginx/nginx.conf -g "daemon off;"
service/td-agent/run
#!/bin/sh exec /usr/sbin/td-agent
以下はfluentdのコンフィグ。
td-agent/td-agent.conf
<source> @type tail path /var/log/nginx/access.log pos_file /var/log/nginx/access.log.pos format nginx tag nginx </source> <match *> @type s3 aws_key_id XXXXXXXXXXXXXXXXXXXXXX aws_sec_key XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX s3_bucket fluentd-sample-kiritan path sample/ s3_region ap-northeast-1 time_slice_format %Y%m%d%H%M </match>
コンテナ起動
ビルドして起動。
$ docker build -t baseimage:latest . $ docker run --rm --name baseimage -p 8000:80 baseimage:latest /sbin/my_init
プロセスを確認すると、/sbin/my_init(runitのsvコマンドを読んでる処理)がPID1となっており、 runsvdir
により各サービスを起動しています。
$ docker exec -it bb2d2028160b ps -ef awxf UID PID PPID C STIME TTY STAT TIME CMD root 55 0 0 13:12 pts/0 Rs+ 0:00 ps -ef awxf root 1 0 0 12:30 ? Ss 0:00 /usr/bin/python3 -u /sbin/my_init root 13 1 0 12:30 ? S 0:00 /usr/sbin/syslog-ng --pidfile /var/run/syslog-ng.pid -F --no-caps root 21 1 0 12:30 ? S 0:00 /usr/bin/runsvdir -P /etc/service root 22 21 0 12:30 ? Ss 0:00 \_ runsv sshd root 23 21 0 12:30 ? Ss 0:00 \_ runsv cron root 26 23 0 12:30 ? S 0:00 | \_ /usr/sbin/cron -f root 24 21 0 12:30 ? Ss 0:00 \_ runsv nginx root 28 24 0 12:30 ? S 0:00 | \_ nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf -g daemon www-data 31 28 0 12:30 ? S 0:00 | \_ nginx: worker process root 25 21 0 12:30 ? Ss 0:00 \_ runsv td-agent root 27 25 0 12:30 ? S 0:00 \_ /bin/sh ./run root 29 27 0 12:30 ? Sl 0:01 \_ /opt/td-agent/embedded/bin/ruby /usr/sbin/td-agent root 34 29 0 12:30 ? Sl 0:02 \_ /opt/td-agent/embedded/bin/ruby -Eascii-8bit:ascii-8bit /usr/sbin
dockerホストのIPを確認してWEBサイトにアクセスすると、
$ docker-machine ip 192.168.99.100 $ curl http://192.168.99.100:8000
しばらくした後、s3上にnginxのアクセスログが転送されています。
$ aws s3 ls s3://fluentd-sample-kiritan/sample/ 2019-05-25 10:16:01 172 201905250105_0.gz