- #1: OSとサービス、daemon、Linuxのinitソフトウェア
- #2: SysVInit、ハンズオン、SysVInitの問題
- #3: Systemd、ハンズオン、Dockerでの問題と対応(本記事)
Systemdはどこが違うか
SystemdはSysVInitやUpstartの問題を改善した最も新しいinitですが、SysVInitとの互換性はありません。
その代わり、SysVInitではstart
/ stop
などの処理やPIDファイルの扱いを手動で書く必要があったのが、良くある処理なら設定ファイルを記述するだけで書けるようになりました。
さらに複雑な管理も可能で、サービス同士の依存関係を定義したり、お互いに依存しないサービスを並列起動したりできます。
また、SysVInit系にはないモニタリング機能があるので、不慮の終了時といった条件を指定した起動を「システム起動時以外にも」行えます。
なお、管理コマンドはsystemctl start サービス名.service
といった形式で実行します。
ハンズオン: Systemd
以下はUbuntu 16.04 LTSを使用しています。
/etc/systemd/system/
Systemdのサービスは、/etc/systemd/system/
や/lib/systemd/system/
の下に置かれます。
/etc/systemd/system/sshd.service
を見てみましょう。
[Service]
で起動や再起動の方法を指定します。
ExecReload=/bin/kill -HUP $MAINPID
のkill -HUP
シグナルは多くのプログラムで再起動相当の処理を行うようになっています(設定ファイルの再読み込みなどがされる)Restart=on-failure
は「起動に失敗したら再起動」
Systemdでは、SysVInit系の「ランレベル」という概念がなくなりました。それに近いのは「Wants」という概念です(上のWantedBy=
の部分)。
ならSystemdならいいんじゃね?
BPS社内でもCentOSやUbuntuはこのSystemdに移行していますので、もうSystemdさえ使いこなせれば基本的には問題ない、と思いますよね。
もちろんCentOS 6以下やUbuntu 14.04以下は未だにSysVInitやUpstartなので、現実にはレガシー対応としてSysVInit系を知っておく必要はありますが。
・・・めでたしめでたし?
問題は「Docker」
Systemdですべてこなせればよいのですが、実はDockerではデフォルトでSystemdが使えないという壁が立ちはだかっています。
理由1: cgroupsの壁
まず、Systemdは、LinuxのcgroupsというKernel提供の機能に依存しているのですが、通常のDocker containerはそもそもcgroupsの機能を使って隔離された環境で動作するため、cgroups自体を操作する権限が限定されてしまいます。
これを回避するために--privileged
オプションを付けてDockerを起動するという方法もあるといえばあるのですが、このオプションはcontainer側にcgroupの書き込み権限を許すものなので、本来containerを利用する利点の一つである隔離性(サンドボックス化)を手放すことになってしまいます。
※なお、細かくcgroupsの権限設定を割り当てることも可能ですが、正直かなり面倒です
理由2: initの壁
そもそもDockerコンテナはinitから起動しない(initがない)ので、initプロセスに依存するスクリプトは軒並み動きません。service
コマンドで操作できないサービスは、たいていこれが原因です。
詳しくは別の勉強会で取り上げますが、Dockerは「コンテナ型仮想化」と呼ばれるプロセス単位での仮想化を行うものなので、VirtualBoxのような「仮想マシン型仮想化」などのように、VMがまるごと提供されるものではありません。
ではDockerではどうすればよいのか?
Dockerでプロセスを管理するツールとしては、「Monit」と「Supervisor」が代表的です。
- Monit
- 歴史の長いプロセス管理ツール
「プロセスが動いてなければ起動する」などの操作が可能
configの書き方にクセがあるが、枯れているのは利点 - Supervisor
- 比較的新しい(2004〜)プロセス監視ツール
シンプルな設定で書けるので、Dockerとペアでよく使われる
Supervisorを使ってみる
Dockerコンテナ内で以下を実行します(Dockerの細かな操作法については省略します)。
apt-get update # コンテナで初めてapt-getを使う場合
apt-get install supervisor
後は/etc/supervisor/conf.d/
の下に設定を書けば完了です。
たとえばsshdであれば、/etc/supervisor/conf.d/sshd.conf
などのファイルに以下の2行を書くだけでSupervisor向けに設定できます。
※ -D
オプションでdetachしないようにする(フォアグラウンド実行させる)のが重要です。フォアグラウンド実行させないとsupervisorが起動状態を監視できません
[program:sshd]
command=/user/sbin/sshd -D
DockerからSupervisor管理のコンテナを起動するには、たとえば以下のようにdocker run
を実行します。ポイントは--restart=always
オプションで、これを付けておくことでDocker Hostを再起動しても自動で起動するようになります。
docker run --restart=always -d -p 14022:22 --name ubuntu_test -it ubuntu /usr/bin/supervisord
この場合、コンテナ内でまずsupervisordが起動し、そのsupervisordが各種サービスを起動するという流れになります。
※なお、DockerfileのCMDで設定してあるのであれば、最後の /usr/bin/supervisord
は不要です
重要なのは、サービス(ここではsshd)を直接起動していないので、サービスを再起動してもコンテナが死なないという点です。上述のように、Docker Host(サーバ)自体を再起動してもsupervisordが起動されるので、必要なサービスが自動的に起動するようになります。
なお、supervisordを再起動してしまうと当然コンテナが死んでしまうので、supervisor自体の設定を変更した場合にはsupervisorctl reread
及びsupervisorctl update
で更新しましょう。
参考: Supervisor と Docker を使う — Docker-docs-ja 1.9.0b ドキュメント
そもそもDockerを使うならプロセス単位できちんとコンテナを分けろというコンテナ至上主義的な方からツッコミが来るかなとも思うのですが、現実的な実用レベルで考えるとある程度のまとまったサービス群で一つのコンテナにしたいというニーズも強くあります。
この辺りはサービスをどのレベルで抽象化してコンテナに落とし込むべきかという設計思想の話になると思うのですが、マイクロコンテナに分割する設計を強要してしまうとコンテナ自体のメンテの難易度や手間が増え、ちょっとした構成変更の度にコンテナ職人が呼び出されることになるのはあまり健全ではないように僕は思います。
#チーム内にコンテナのメンテができる人が多数いるのであれば問題ありませんが、大抵の場合1人か2人程度がインフラの一環としてコンテナをメンテしているのではないでしょうか
サービス管理ツールの使いどころ
- SysVInit/Upstart
- 古めのLinuxではこれを使うことになる(古いLinux書籍の多くもこれが前提になっている)
シェルだと思って扱うのがよい - Systemd
- 最近のLinuxではこれを使う
新しい情報をぐぐる必要がある
ドキュメントはきちんと読むこと(シェルスクリプトではないので) - Monit/Supervisor
- initプロセスとは別にプロセス監視したい場合に使う
Dockerではsupervisorを使っておけば大丈夫
まとめ
SystemdやSupervisordが登場したことで、Linuxのサービス起動周りが以前よりも複雑になってきている面があります。
簡単でよいので、Linuxのサービス起動周りの歴史を押さえておくと参考になります。
Dockerを使う人は、既に動いている他人のSupervisor設定を参考にするとたぶん幸せになれます。
他人のSupervisor設定を参考にと書きましたが、実は適当にぐぐると結構怪しい設定を見かけます。
特に、通常のLinuxネイティブ環境で動作させる前提のパッケージはデフォルトだと起動スクリプトがdetachしてバックグラウンド実行になってしまうことがほとんどなので、きちんとsupervisor管理させたいなら注意が必要です。
参考: Dockerでsupervisorを使う時によくハマる点まとめ
ただ、本番系ではなく開発環境などのある程度ラフな環境で良ければ「もうコンテナごとrestartしちゃってええやん」ということで、docker CONTAINER restart
してしまうのもありなのではないかと思います。
そもそも起動が軽いのがコンテナの利点なので、よほど重い環境でもなければカジュアルに再起動してしまうという思い切りは悪くないのではないかと考えています。
#もちろんきれいに作らないといけない時はちゃんと作ります。
お便り発掘
きたー!
Linuxのサービス起動周りとDockerとの関連を理解する#3(社内勉強会) https://t.co/qT8HKnUjGS
— d-tasaki (@devchick99) September 4, 2018
daemon化を前提とするサービスの多くがこの影響を受けます。PID 1のinitプロセスを前提としてdaemon化するプログラムは動作しません。