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

SysVInit/Upstartの詳細

SysVInitとUpstartは比較的近いので、まとめて説明します。

/etc/inittabに記述されているランレベルに従って起動を進めるシンプルな設計です。以下は典型的なランレベルの一例ですが、LinuxのディストリビューションやOSの種類によって異なりますのでご注意ください(特にランレベル2〜5)。
※Upstartの場合、runlevelによる設定はSysVInitとの互換のため実装されているという扱いのようです

ID 名称 説明
0 停止 システムの停止、またはシャットダウン
1 シングルユーザモード ネットワーク機能(デーモン)を起動しない
2 (マルチユーザーモード) (ネットワークを起動しない)
3 (ネットワーク有のマルチユーザーモード) (通常のモード)
5 (ネットワーク有のマルチユーザーモード、GUIを起動する) (デスクトップ環境などで利用)
6 リブート システムのリブート

設定ファイルは以下を使います。サービスを起動するには/etc/init.d/サービス名 startを実行します。実体はただのシェルスクリプトです。

/etc/inittab
ランレベル定義ファイル
/etc/init.d/
サービススクリプト群がこの下に置かれる
/etc/rcN.d
ランレベルごとに起動するスクリプト群(Nはランレベルを表す数値)
上の/etc/init.d/以下のファイルに対するsymlinkになっている
起動/停止は頭文字がSかどうかで判断する
morimorihoge注)

主要ディストリビューションではserviceコマンドが提供されているので、通常は直接/etc/init.d以下のスクリプトを実行するのではなくserviceコマンドを利用するのが良いでしょう。

参考: デーモンの起動・終了にはserviceコマンドを利用しよう

/etc/init.d/以下の様子
morimorihoge注)

伝統のSysVInitは当該ランレベルの/etc/rcN.d/S*を順番に起動する仕様のため、起動の優先順位があるサービスを使う場合のために、慣例的にS10hogedS99piyodなどの数値2桁の名前が付けられてきました。

また、SysVInitの起動スクリプトは直列に一つずつ実行されるため、例えば、mysqldが起動した後にunicornを起動して欲しい場合には、mysqldをS90mysqld、unicornをS99unicornなどとすることで、mysqlの後にunicornが起動するよう保証することも行われていました。

SysVInit系の管理コマンドはSysVInit共通というものがなく、Linuxディストリビューションごとに実装が異なっています。

  • chkconfig: RedHat系
  • update-rc.d: Debian系

chkconfigupdate-rc.dも、/etc/init.d/及び/etc/rcN.d以下にあるファイル名を変更するだけのスクリプトなので、手動で変更するのと変わりません。

ハンズオン: SysVInit/Upstartを実際に見てみる

以下はCentOS 6.10で確認しました。VirtualBoxなどで自分で動かして試してみましょう。

sbin/init

まずはsbin/initがどのinitかを確認してみましょう。

/sbin/init --version

/etc/inittab

続いて/etc/inittabの中身です。表示してみれば1画面で収まる程度です。

cat /etc/inittab

画面の下の方にランレベルが記載されています。この場合通常のランレベルはここにある3に該当します。なお、/etc/inittabは編集に失敗すると起動しなくなることもあるのでご注意ください。その場合は起動時にブートローダーでランレベルを直接指定するなどの方法で回避することになります。

ついでですが、rootユーザーが/sbin/initに以下↓のようにランレベルを与えて実行するとその場でランレベルを変更できます。0を与えればシャットダウンと同じ動作です(実際にはまず使いませんが)。ここからわかるように、/sbin/initの動作は意外に素朴です。

/sbin/init 0

/etc/init.d/

続いて/etc/init.d/の下を見てみましょう。

ここに置かれているのは個別のdaemonの起動や終了を制御するスクリプトで、中身は単なるシェルスクリプトです。その中からsshdのスクリプトを見てみましょう。

~£ cat /etc/init.d/sshd
#!/bin/bash
#
# sshd      Start up the OpenSSH server daemon

【中略】

start()
{
    [ -x $SSHD ] || exit 5
    [ -f /etc/ssh/sshd_config ] || exit 6
    # Create keys if necessary
    if [ "x${AUTOCREATE_SERVER_KEYS}" != xNO ]; then
        do_rsa_keygen
        if [ "x${AUTOCREATE_SERVER_KEYS}" != xRSAONLY ]; then
            do_rsa1_keygen
            do_dsa_keygen
        fi
    fi

    echo -n $"Starting $prog: "
    $SSHD $OPTIONS && success || failure
    RETVAL=$?
    [ $RETVAL -eq 0 ] && touch $lockfile
    echo
    return $RETVAL
}

stop()
{
    echo -n $"Stopping $prog: "
    killproc -p $PID_FILE $SSHD
    RETVAL=$?
    # if we are in halt or reboot runlevel kill all running sessions
    # so the TCP connections are closed cleanly
    if [ "x$runlevel" = x0 -o "x$runlevel" = x6 ] ; then
        trap '' TERM
        killall $prog 2>/dev/null
        trap TERM
    fi
    [ $RETVAL -eq 0 ] && rm -f $lockfile
    echo
}

reload()
{
    echo -n $"Reloading $prog: "
    killproc -p $PID_FILE $SSHD -HUP
    RETVAL=$?
    echo
}

【中略】

case "$1" in
    start)
        rh_status_q && exit 0
        start
        ;;
    stop)
        if ! rh_status_q; then
            rm -f $lockfile
            exit 0
        fi
        stop
        ;;
    restart)
        restart
        ;;
    reload)
        rh_status_q || exit 7
        reload
        ;;
【中略】
    *)
        echo $"Usage: $0 {start|stop|restart|reload|force-reload|condrestart|try-restart|status}"
        RETVAL=2
esac
exit $RETVAL
~£

sshスクリプトの中にはstartstopreloadなどがシェルスクリプトとして定義されています。実は他のスクリプトも、startstop以外は割と自由に書かれていたりします。

/etc/rc.d/

次は/etc/rc.d/を見てみましょう。

その中から、rc3.d/ディレクトリの下を見てみます。

rc3.d/ディレクトリの下には、Kと数字で始まるスクリプトと、Sと数字で始まるスクリプトがあります。

  • Sxx: 起動するスクリプト
  • Kxx: 起動しないスクリプト(実際にはなくても同じだが、変更時renameだけすれば良いように配置されている)

そして、スクリプトはxxの数字の順(注↓)に起動されます。非常に単純なつくりであることがわかります。つまりこの数値を変更すれば起動の順序を変えられるわけです。

morimorihoge注)

「数字の順」は、実際にはls /etc/rcN.d/S*した順と思って良いです。そのため、もし同じ数字のスクリプトがある場合にはアルファベット順になります。

chkconfig

CentOSのSysVInitにはchkconfigというコマンドがあります。chkconfigだけを実行すると、rc*.d/のランレベル(ここでは1から6)ごとにサービスのオンオフ状態が表示されます。

たとえばchkconfig --del iptablesと実行するとrc*.d/の下にあるiptablesスクリプトへのsymlinkが削除されます(スクリプトそのものはこのコマンドでは削除しません)。chkconfig --add iptablesとすればsymlinkが作成されます。

morimorihoge注)

chkconfigコマンドで設定及び閲覧できる情報は、あくまで起動時(ランレベル変更時)の設定です。

chkconfigしただけでは当該サービスは起動しませんので、Linuxを再起動せずに当該サービスを起動したい場合には別途service hoged startする必要があることに注意しましょう。


以上のようにSysVInitは驚くほどシンプルなつくりです。そして今でも必要になることが多々ありますので、やはり知っておくのがよいでしょう。

たとえば自分で何かサービス(daemon)を追加するなら、/etc/init.d/の下にある他の起動スクリプトを真似て、start/stop/restartオプションぐらいは取れるように書いておけばだいたい間に合うでしょう。

SysVInit系の問題

SysVInitは上から順に1つずつ直列に起動する、素朴とも言えるシンプルなつくりなので、比較的わかりやすい代わりに非効率です。具体的には、起動するスクリプト群の中に起動に時間のかかるものがあると、それに引きずられてシステム全体の起動時間が長くなってしまいます。

たとえばsshdはたいてい起動順の後ろの方にあるので、Linuxシステムを再起動するとsshdが起動するまでなかなかログインできないなんてこともあります。「LANケーブルが挿さっていないとIPアドレスが取れないので起動が終わらない」などというトラブルもだいたいSysVInitです(手前のdhclient(DHCPでIPを取得する)がタイムアウトするまで次の処理に進まないあるある)

Upstartはイベント実行をサポートすることでその部分が改良されていて、指定のサービスを並列起動できます。

SysVInitの/etc/init.d/の下に記載するのは単なるシェルスクリプトなので、サービスごとにstartスクリプトやstopスクリプトを書くのに手間がかかります(ある程度の雛形はありますが)。依存関係を指定して起動するしくみもサポートされないので、先ほどのS*ファイルの数字を自力で変更して起動順を制御するなどしなければなりません。

morimorihoge注)

実際にはRedHat系のchkconfigには/etc/init.dのスクリプトに以下の形式の「コメント」を書くことで、起動順序の数字についてはそれを見てくれます。コメントである点にご注意。

# chkconfig: - 起動の番号 終了の番号

参考: Writing System V init scripts for Red Hat Linux
参考: IT技術の処方箋:起動スクリプトの順番

SysVInitやUpstartには「モニタリング機能がない」のも問題です。このため、monitsupervisorなどを用いて別途監視体制を構築しないと、エラーで止まったdaemonは自動起動されません。


(次回「#3: Systemd、ハンズオン、Dockerでの問題と対応」に続く)

関連記事

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

デザインも頼めるシステム開発会社をお探しなら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探訪シリーズ