Linuxのサービス起動周りとDockerとの関連を理解する#3(社内勉強会)

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 $MAINPIDkill -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コマンドで操作できないサービスは、たいていこれが原因です。

morimorihoge注)

daemon化を前提とするサービスの多くがこの影響を受けます。PID 1のinitプロセスを前提としてdaemon化するプログラムは動作しません。

詳しくは別の勉強会で取り上げますが、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 ドキュメント

morimorihoge注)

そもそもDockerを使うならプロセス単位できちんとコンテナを分けろというコンテナ至上主義的な方からツッコミが来るかなとも思うのですが、現実的な実用レベルで考えるとある程度のまとまったサービス群で一つのコンテナにしたいというニーズも強くあります。

この辺りはサービスをどのレベルで抽象化してコンテナに落とし込むべきかという設計思想の話になると思うのですが、マイクロコンテナに分割する設計を強要してしまうとコンテナ自体のメンテの難易度や手間が増え、ちょっとした構成変更の度にコンテナ職人が呼び出されることになるのはあまり健全ではないように僕は思います。

#チーム内にコンテナのメンテができる人が多数いるのであれば問題ありませんが、大抵の場合1人か2人程度がインフラの一環としてコンテナをメンテしているのではないでしょうか

サービス管理ツールの使いどころ

SysVInit/Upstart
古めのLinuxではこれを使うことになる(古いLinux書籍の多くもこれが前提になっている)
シェルだと思って扱うのがよい
Systemd
最近のLinuxではこれを使う
新しい情報をぐぐる必要がある
ドキュメントはきちんと読むこと(シェルスクリプトではないので)
Monit/Supervisor
initプロセスとは別にプロセス監視したい場合に使う
Dockerではsupervisorを使っておけば大丈夫

まとめ

SystemdやSupervisordが登場したことで、Linuxのサービス起動周りが以前よりも複雑になってきている面があります。

簡単でよいので、Linuxのサービス起動周りの歴史を押さえておくと参考になります。

Dockerを使う人は、既に動いている他人のSupervisor設定を参考にするとたぶん幸せになれます。

morimorihoge注)

他人のSupervisor設定を参考にと書きましたが、実は適当にぐぐると結構怪しい設定を見かけます。

特に、通常のLinuxネイティブ環境で動作させる前提のパッケージはデフォルトだと起動スクリプトがdetachしてバックグラウンド実行になってしまうことがほとんどなので、きちんとsupervisor管理させたいなら注意が必要です。

参考: Dockerでsupervisorを使う時によくハマる点まとめ

ただ、本番系ではなく開発環境などのある程度ラフな環境で良ければ「もうコンテナごとrestartしちゃってええやん」ということで、docker CONTAINER restartしてしまうのもありなのではないかと思います。

そもそも起動が軽いのがコンテナの利点なので、よほど重い環境でもなければカジュアルに再起動してしまうという思い切りは悪くないのではないかと考えています。
#もちろんきれいに作らないといけない時はちゃんと作ります。

お便り発掘

関連記事

Webアプリの基礎とさまざまな実行環境を理解する#1(社内勉強会)

Dockerでsupervisorを使う時によくハマる点まとめ

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ