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

BPS社内勉強会でのmorimorihogeさんのスライドを3回に分けてお送りします。 #1: OSとサービス、daemon、Linuxのinitソフトウェア(本記事) #2: SysVInit、ハンズオン、SysVInitの問題 #3: Systemd、ハンズオン、Dockerでの問題と対応 概要 話すこと Linuxサーバーにおけるサービス管理 サービス管理ツールとその概要 SysVInit/Upstart Systemd Monit/Supervisor Dockerに絡む話 話さないこと LinuxやDockerの操作など 最近Linuxのサービス管理が以前と変わりつつあるので、インフラ管理者はもちろん、開発者がテストサーバーを構築したりするときにも役立つ話をしてみたいと思います。 1. OSとサービス プロセスの例 Linuxシステムではさまざまなプロセスが動作しますが、「振る舞い」という観点から大きく「通常のプロセス」と「daemonプロセス」の2とおりに分けられます。 通常のプロセス 実行して仕事が終わると終了するコマンドラインから実行するプログラムやバッチがこれに該当する daemonプロセス システムに常駐して仕事を待ち受ける 仕事が終わっても終了せず、次の仕事を待ち受ける このdaemonプロセスが提供する抽象的なものが「サービス」と思っていただければよいでしょう。 morimorihoge注) イメージですが、通常の「終わると終了するプロセス」は起動時に何をどう処理するのか決まっているタスク型の処理、「daemon型のプロセス」は起動時点では決まっていない処理のためにあらかじめ待ち受けさせておくという意味でサービス型の処理を行うものだと考えると良いと思います。 「サービス」という言葉は、プロセスに比べると抽象度が高い言葉でいろいろな解釈の仕方があるかと思いますが、ここではsystemdやSysVInitで呼ばれる用語としての「サービス」として使います。 「サービス」の実体は、実際には1つのdaemonプロセスの場合もあれば、cron等で周期実行されるプログラムのconfig設定として設定されることもあります。この辺り少し紛らわしいですね。 daemonの特徴 daemon(demonではありません)の特徴は、バックグラウンドで常駐することです。daemonは、それを起動したシェルが終了しても止まらずに動き続けます(daemonプロセスは起動時にforkでシェルと別プロセスになります)。 なお通常のプログラムだと、それを起動したシェルが終了するとプロセスも終了します。 sshで時間のかかるプロセスを実行中にネットワークが切断したときにもシェルが終了するため、やはりプロセスが終了してしまいます。これを避けるには、バックグラウンド実行するとか、tmuxやbyobuなどを使うなどします。 そしてそれ以外は、daemonプロセスは通常のプロセスとまったく変わりません。daemonはroot権限で動作しているとは限りませんし、segmentation faultなどで落ちれば死ぬのも通常のプロセスと同じです。死んだプロセスはひとりでには再起動しません。 morimorihoge注) 補足すると、通常のプロセス起動(内部的にはfork())はプログラムを実行したプロセスが親プロセスとなり、親プロセスが死ぬとその子プロセスにも終了シグナルが送られて終了します。ssh作業中のプロセスが死ぬのは、SSHでクライアントがタイムアウトした際に、「起動していたシェルが終了する」->「シェルから起動していたプロセスも終了する」という連鎖が作用するためです。 それに対し、daemonプロセスはプロセスをdaemonizeする際に親プロセスが変更されます(通常はPID 1のinitプロセスの子になる)。そのため、daemon化されたプロセスは元々の親プロセスが終了しても、既に親子が切り離されているため終了シグナルが届かず、動き続けることができるわけですね。 これらの挙動は、pstreeコマンドなどを使うと見ることができますので、興味のある人は眺めてみると良いと思います。 サービスとして実行されるdaemonプロセスの多くは、特定のポートやSocketファイルなどを開き、そこに対してアクセスが発生すると何らかの動作を行います。 BSDオペレーティングシステムのマスコット「デーモン君」 なおdaemonの多くは名前の末尾がdで終わりますが、これは単なる慣習なので、nginxのようにこのとおりでないものもあります。 定番のdaemonのごく一部を以下にリストアップします。 sshd 22番ポートでsshクライアントを受け付ける apache2/httpd それぞれ80番ポートと443番ポートでブラウザからのアクセスを受け付ける mysqld 3306番ポートやsocketファイルでMySQLクライアントからのアクセスを受け付ける syslogd loggerコマンドやsocketファイル経由でログ出力のアクセスを受け付ける デーモンに関する問題 daemonのほとんどは「常時正常動作して欲しい」プロセスで、止まっていては困るものです。しかし、daemonそれ自身には自分自身を監視・起動する仕組みがないため、システム再起動時の初回起動や、不慮の事故によるエラー終了時には、誰かがdaemonを起動してくれないと動き出してくれません(そのための設定が何も行われていない場合)。 sshdやcrondのような充分に安定したdaemonであれば不慮の事故で落ちることはほぼありませんが、たとえばRailsアプリのWebサーバーに使われるUnicornなどは上で動かすアプリのコードによっては時たま落ちることがあります(さすがにmasterプロセスが落ちることはそうありませんが、何も考えず大量のActiveRecordオブジェクトを生成するようなコードを書いていると、workerが死ぬというのはそこそこ見ます)。 morimorihoge注) なお、プログラム側に一切のバグが無くてもOSの都合でプロセスが落とされることが稀にあります。 その最たるものがOOM Killerで、システムのメモリがどうやっても足りなくなったとき(Out Of Memory)に、OSが適当なプロセスを選んで強制停止するという仕組みがあります。 運悪くOOM Killerに選ばれてしまったプロセスは問答無用で終了させられますが、重要なシステムではこういったケースも考慮しておかないと、OOMガチャでハズレ(ある意味当たり)を引いた際にデータ破損などの事故になってしまう可能性があります。 なお、どうしても殺されたくないプロセスをOOM Killerの対象から外す方法もあるようです。先ほど手元のUbuntuやAmazon Linuxを調べてみたところ、sshdの oom_score_adj値には-1000が設定されていたため、少なくともsshdはOOM Killerに殺されるようにはなっていないことが確認できました。 参考: [TIPS: 特定のプロセスをOOM Killerの対象から外す サービス管理に必要な機能 上述の問題に対処するために使われるのがサービス管理ツールです。「起動管理」「サービスの一括管理」「モニタリング」という機能を用いてサービスを管理します。 起動管理 OS起動時に必要なdaemonなどのサービスを自動起動する サービスの一括管理 サービスごとに異なる起動コマンドなどの違いを吸収し、統一的なインターフェースで操作できるようにする モニタリング サービスが動作しているかどうかを監視し、止まっていれば再起動する サービスの一括管理はなくてもどうにかなりますが、ある方が圧倒的に便利です。個別のサービスはさまざまな作者が作ったプログラムであり、起動や終了の方法がまちまちなので、統一的な方法で操作できる方が助かります。 なお本当の最初の起動設定は実際にはinit(PID 1)の仕事ですが、実際にはinitの中で全ての起動処理をやるわけではなく、initが最低限の起動処理を行った後、これらのサービス管理ツールに作業を移譲するのでここに書きました。initは、daemonの起動以外にもドライバの読み込みやカーネルモジュールのセットアップといった作業も行います。 「daemonごとに異なる起動コマンド」について たとえばMySQLは実行時にユーザーをmysql_userに切り替える必要がありますし、chrootしてその中で起動しないといけないdaemonや、起動時にconfigファイルパスの指定が必要なdaemonもあります。終了方法も、SIGTERMなどのシグナルで止めてよいものもあれば、終了のためのコマンドを実行しないといけないものなど、さまざまです。そのため、いちいちそれぞれのプログラムの仕様を知らなくても統一的に操作できるインターフェースが求められてきました。 Linuxの初回起動の流れ Linuxシステムの初回起動について少し説明しておきます。 Linuxカーネルが起動すると、最初に/sbin/initというプログラムを1つだけ実行します。initとは、OSブート時に最も初期の段階で起動されるプログラムです。このinitが最初のプロセス(プロセスID=1)になり、そしてこれが他のプロセスを次々と起動するという流れになります。 /sbin/initの実装は複数あることにご注意ください。次にご紹介するinitソフトウェアもinitプログラムの実装です。 Linuxの主要initソフトウェア Linuxで前述のサービス管理によく使われる主なinitソフトウェアについて、古いものから「SysVInit」「Upstart」「Systemd」の3つを紹介します。 1. SysVInit SysVInitは非常に歴史の長いinitで、「起動管理」機能と「サービスの一括管理」機能を提供しますが、「モニタリング」機能はありません。Linuxの場合、数年前まではほぼ全てのディストリビューションがSysVInitを使っていました。 SysVInitは、古くから使われているUnix System V(SysV)が起源と思われます。 2. Upstart UpstartはSysVInitの改良版なのでSysVInitと互換性があり、機能も同等ですが、やはり「モニタリング」機能はありません。「イベント駆動モデル」を取り入れているのが特徴です。 なお、Ubuntu 12-14はUpstartがデフォルトでした。 Upstartは上のSysVInitと同等の機能を持つものとして扱えると思っていただいて大丈夫です。 3. Systemd Systemdはさらに改良が加わった次世代initで、「起動管理」「サービスの一括管理」「モニタリング」機能をすべて備えています。 最新のLinuxディストリビューションは、ここ数年(2016-2018年)でほぼSystemdに移行が終わりました。 CentOS 7以降 RHEL 7以降 Ubuntu 16.04以降 SystemdはSysVInit系と互換性がないため、Linuxシステム管理者は新しくSystemdのやり方を学ばなければならなくなったわけです。 SysVInitについては次回詳しく扱います。 morimorihoge注) サービス管理周りはこれまで各ディストリビューションによって微妙に(一部大幅に)異なる部分があり、RedHat系に慣れたLinux管理者もDebian/Ubuntu系を触ろうとすると勝手が違うといった辛みがありましたが、今後はsystemdへの一本化で多少はマシになっていくのかなと思います。 ただ、ツールが一本化されてもパッケージ名などは相変わらずディストリビューション依存なので、たまに使い慣れないディストリビューションを触ると戸惑うことは今後もあるのかなあと思います。 ※僕はRedHat6系からLinuxを使い始め、KondaraやFedoraを経てここ7−8年くらいはUbuntu派でした。ただ、ここ最近はDocker化が進みもうホストOSは全部Amazon Linuxでいいんじゃないかなという気持ちもあります (「#2: SysVInit、ハンズオン、SysVInitの問題」に続く) 関連記事 Webアプリの基礎とさまざまな実行環境を理解する#1(社内勉強会)