Alpine Linux Dockerコンテナ ー initスクリプト(OpenRC)によるプロセス管理

Computer, Development, Linux 6月 9, 2022
(Last Updated On: 2022年9月11日)

Alpine Linuxは軽量Linuxディストリビューションの一つでカスタムDockerコンテナのベースイメージに使っている人も多いと思います。Dockerコンテナで複数サービスを起動したい場合、プロセススーパーバイザーを使うのが常道です。DJBのdaemontoolsなら小さく軽量で良いのですが、Pythonで実装されたSupervisordは軽量コンテナにする意味を台無しにするくらいに色々インストールする必要があります。

インストールするパッケージ/プログラム数と管理項目を最小限にしたいミニマリストなら、Alpine Linuxの起動プロセス管理ツールのOpenRC(昔ながらのSysV Initスタイル)を使いたいと思うハズです。私もその一人でしたが、検索してもどう構成すれば良いのかズバリ説明しているページを見つけられませんでした。

ということで、Alpine Linuxなどに付属するOpenRCを使ったDockerの起動プロセス管理の基本を紹介します。

OpenRCをDockerで使うのはアリなのか?

アリです。何故なら

  • OpenRCはプロセススーパーバイザーとして利用可能
  • Docker環境であることを自動認識し余計な事を行わない
  • 元々OpenRCはコンパクトである上に他のパッケージへの依存性もほぼ無い(わざわざ他のスーパーバイザーを入れる方が大きな無駄)

だからです。OpenRCが入っているディストリビューションなら使わない理由がないです。

OpenRC 0.21からプロセスのスーパーバイズ機能(プロセスが停止したら自動的に再起動する機能)が追加されています。プロセススーパーバイザーとしても使えます。そもそもOpenRCは軽量な組込みシステムやコンテナ/仮想環境で動作させることを想定しているのでコンパクトなので適しています。特に元々OpenRCが前提のAlpine Linuxの場合、パッケージを幾つかインストールするだけで使える様になります。Alpine Linuxを使うならOpenRCを使わない方が損です。

DockerでOpenRCを使う方法の英語ページを検索すると「DockerでOpenRCを使う方法」聞いたユーザーに対して「スーパーバイザーを使え」「ここはDockerコンテナでsshdを起動する方法を聞く場所ではない」といった的外れな回答ばかりが散見されました。

そもそも軽量を目指しているOpenRCはコンテナやハイパーバイザーで使うことを考慮して作られています。それはOpenRCパッケージに含まれている/etc/rc.confで分かります。

# This is the subsystem type.
# It is used to match against keywords set by the keyword call in the
# depend function of service scripts.
#
# It should be set to the value representing the environment this file is
# PRESENTLY in, not the virtualization the environment is capable of.
# If it is commented out, automatic detection will be used.
#
# The list below shows all possible settings as well as the host
# operating systems where they can be used and autodetected.
#
# ""               - nothing special
# "docker"         - Docker container manager (Linux)
# "jail"           - Jail (DragonflyBSD or FreeBSD)
# "lxc"            - Linux Containers
# "openvz"         - Linux OpenVZ
# "prefix"         - Prefix
# "rkt"            - CoreOS container management system (Linux)
# "subhurd"        - Hurd subhurds (to be checked)
# "systemd-nspawn" - Container created by systemd-nspawn (Linux)
# "uml"            - Usermode Linux
# "vserver"        - Linux vserver
# "xen0"           - Xen0 Domain (Linux and NetBSD)
# "xenU"           - XenU Domain (Linux and NetBSD)
#rc_sys=""

Dockerのみでなく、JailやUser Mode Linuxまでサポートしていて自動検出するようになっています。実際に起動すると以下の様に

OpenRC 0.44.10 is starting up Linux 5.17.7-200.fc35.x86_64 (x86_64) [DOCKER]

Dockerコンテナ内であることを自動検出してくれます。

OpenRCのSupervise機能

以下はデフォルトのstart/stopでなくsupervise-damonを使ってAvahiデーモン(自動構成を実現するmDNSデーモン)をスーパーバイズする例です。

#!/sbin/openrc-run
# Copyright 1999-2016 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

extra_started_commands="reload"
command="/usr/sbin/avahi-daemon"
supervisor="supervise-daemon"

depend() {
        before netmount nfsmount
        use net
        need dbus
}

#start() {
#       ebegin "Starting avahi-daemon"
#       /usr/sbin/avahi-daemon -D
#       eend $?
#}
#
#stop() {
#       ebegin "Stopping avahi-daemon"
#       /usr/sbin/avahi-daemon -k
#       eend $?
#}

reload() {
       ebegin "Reloading avahi-daemon"
#      /usr/sbin/avahi-daemon -r
       ${command} -r
       eend $?
}

OpenRCが使えるシステムで別途スーパーバイザーを導入するのは無駄が多いです。OpenRCはほぼ依存性なく

bash-5.1# apk -R info openrc 
openrc-0.44.10-r7 depends on:
ifupdown-any
/bin/sh
so:libc.musl-x86_64.so.1

サイズも2MB程度しかありません。

bash-5.1# apk -s info openrc 
openrc-0.44.10-r7 installed size:
2364 KiB

Pythonで実装されているSupervisordをインストールするにはPythonだけで47MB

bash-5.1# apk -s info python3
python3-3.10.4-r0 installed size:
47 MiB

更にSupervisordが依存するPythonライブラリとバイナリライブラリを多数インストールする必要がありあす。

RCスクリプトがあるパッケージをインストールすると動的にRCスクリプトも自動インストールされる、微妙な無駄がありますが、上記のAvahiデーモンのスクリプトのように1KBもありません。

SSHDをスーパーバイズする場合はこうするだけです。一般的なスーパーバイザー設定に必要な設定項目とそう変わりません。

pidfile="${SSHD_PIDFILE}"
command_args_background="-o PidFile=${pidfile}"
command_args="${SSHD_OPTS} -f ${SSHD_CONFIG}"
command_args_foreground="-D"
supervisor="supervise-daemon"

# Wait one second (length chosen arbitrarily) to see if sshd actually
# creates a PID file, or if it crashes for some reason like not being
# able to bind to the address in ListenAddress (bug 617596).
#: ${SSHD_SSD_OPTS:=--wait 1000}
start_stop_daemon_args="${SSHD_SSD_OPTS}"

単純なモノなら/etc/init..d/のスクリプトを修正せずに、/etc/conf.d/の設定ファイルを修正するだけです。例えば、/etc/conf.d/rngdを

supervisor="supervise-daemon"
command_args_foreground="--foreground"

とするとrngdがスーパーバイズされます。

OpenRCをDockerで使う

OpenRCをDockerで使うのはとても簡単です。/sbin/initをPID=1(最初に起動するプロセスにする)だけです。先ずはOpenRCパッケージをインストールする必要があります。以下のようなDockerfileを作ります。例としてApache2とPostgreSQLとSSHdもインストールすることにします。

FROM alpine:3.16
MAINTAINER Yasuo Ohgaki version: 0.1

# ログを管理しないと溢れるのでlogrotateも入れる。crondはデフォルトで入っているが、crondのinitスクリプトはbusybox-initscriptsに入っている。
RUN \
 apk --no-cache update && \
 apk add openrc busybox-initscripts logrotate && \
 apk add openssh-client openssh-server postgresql apache2 


# 自動起動するサービスを指定する。
RUN \
 rc-update add local && \
 rc-update add crond && \
 rc-update add apache2 && \
 rc-update add postgresql && \
 rc-update add sshd

# DockerコンテナにTTYは必要ない。使おうとするとエラーが大量に記録されるのでコメントアウトする。
RUN \
 sed -e 's/^tty/#tty/g' < /etc/inittab > /tmp/inittab && \
 mv /tmp/inittab /etc/inittab

# /etc/init.d/crond は busybox-initscriptsに入っている。
# rcスクリプトを使わない場合はlocalでスタートも可能
#COPY crond /etc/local.d/crond.start

# PID=1 で /sbin/init を起動する。
CMD /sbin/init

Dockerfileを作ったらビルドして起動します。

$ docker build -t alpine-openrc-test .
$ docker run  --rm --name alpine-openrc-test alpine-openrc-test

起動すると自動的にPostgreSQLのデータベース、sshdの鍵を作ってくれたりすることを確認できます。

 * Creating a new PostgreSQL 14 database cluster ...The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locales
  COLLATE:  C
  CTYPE:    C.UTF-8
  MESSAGES: C
  MONETARY: C
  NUMERIC:  C
  TIME:     C
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".

Data page checksums are disabled.

fixing permissions on existing directory /var/lib/postgresql/14/data ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default time zone ... UTC
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... sh: locale: not found
2022-06-09 07:32:15.450 UTC [147] WARNING:  no usable system locales were found
ok
syncing data to disk ... initdb: warning: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the option -A, or
--auth-local and --auth-host, the next time you run initdb.
ok


Success.

 [ ok ]
 * /run/postgresql: creating directory
 * /run/postgresql: correcting mode
 * /run/postgresql: correcting owner
 * Starting PostgreSQL 14 ... [ ok ]

ssh-keygen: generating new host keys: RSA DSA ECDSA ED25519 
 * Starting sshd ... [ ok ]

当たり前ですがPostgreSQLデータベースの初期化やsshdの鍵生成はファイルやディレクトリが無い場合にのみ初期化/生成されます。これは普通のOpenRCスクリプトの動作と同じです。

sysfsがリードオンリーだったり/runをtmpfsでマウントできなかったりしてエラーメッセージが表示されますが、それら以外は直接ハードウェアにインストールしたLinuxのように動作します。これらのエラーは無視できるので放っておいても大丈夫です。

OpenRCスクリプト以外のプロセスを起動する

Dockerfileで

rc-update add local

を実行しています。これを実行すると起動時に/etc/local.dに拡張子.startで保存された実行可能なファイルを実行します。.stop拡張子を持つファイルはシステムがシャットダウンされる時(localサービスが停止する時)に呼ばれます。

/etc/local.dの起動/停止スクリプトだとプロセスのスーパーバイズ(クラッシュしたら自動起動)ができないので/etc/init.d/の中にOpenRCの流儀に従った起動/停止スクリプトを書く方が良いです。

Busybox

OpenRCを採用しているAlpine Linuxを使えば、Dockerコンテナでも通常のLinuxディストリビューションのような感じで起動プロセスを管理できます。

Alpine LinuxはBusybox(よく利用されるUNIXツールをまとめて使えるようにしたコンパクトなパッケージ。組込みシステムでよく利用される)を採用しています。crondもBusyboxの機能です。

/ # ls -l `which crond`
lrwxrwxrwx    1 root     root            12 May 23 16:51 /usr/sbin/crond -> /bin/busybox

Alpine LinuxのデフォルトシェルはashでこれもBusyboxの機能です。bashが使いたい場合はapk add bashとしてインストールする必要があります。

基本的なservice(rc-service)コマンドの使い方

インストールされたサービス一覧

service -l

サービスの起動/停止/有効化/無効化

service サービス名 start (/etc/init.d/<file> start でもOK)
service サービス名 stop (/etc/init.d/<file> stop でもOK)
rc-update add サービス名 
rc-update del サービス名

詳しくは

https://wiki.gentoo.org/wiki/OpenRC_to_systemd_Cheatsheet/ja

https://wiki.archlinux.jp/index.php/OpenRC

起動スクリプト(というよりは設定ファイル)は/etc/init.d/にあります。

OpenRCの使い方は以下のページなどを参考にしてください。

https://wiki.gentoo.org/wiki/OpenRC_to_systemd_Cheatsheet

https://github.com/OpenRC/openrc/blob/master/user-guide.md

https://github.com/OpenRC/openrc/blob/master/supervise-daemon-guide.md

https://wiki.gentoo.org/wiki/OpenRC/supervise-daemon

https://github.com/OpenRC/openrc/blob/master/service-script-guide.md

複数コンテナ vs 単一コンテナ

Webサーバーとデータベースサーバーは別のコンテナにし、docker-composeなどのオーケストレーションツール使って分離した方がセキュリティ的には良いです。Webサーバーとデータベースサーバーしかないようなコンテナの場合、複数のコンテナに分離して使うことをお薦めします。

しかし、メールサーバーのようにメール(SMTP)、アンチスパム、アンチウイルス、POP、IMAP、Webなど複数のサービスが一つのコンテナに同居しなければならないシステムもあります。このようなコンテナを作る場合、OpenRCを使ったプロセス管理の方が圧倒的に作りやすいです。Alpine Linuxのサーバー起動スクリプトや設定がそのまま使えるのでコンテナ構築と管理の手間を大幅に削減できます。

OpenRCはSysV Initスタイルで管理できるので、SysV Initに慣れている人なら学習時間がほぼゼロで使える点も良いです。

投稿者: yohgaki