mixi engineer blog

*** 引っ越しました。最新の情報はこちら → https://medium.com/mixi-developers *** ミクシィ・グループで、実際に開発に携わっているエンジニア達が執筆している公式ブログです。様々なサービスの開発や運用を行っていく際に得た技術情報から採用情報まで、有益な情報を幅広く取り扱っています。

systemdを本番運用してわかったこと

こんにちは、運用部 アプリ運用グループの清水です。モンスト仲間募集中です。

以前、Fedora 8からFedora 17への移行のお話を書きました。Fedora 17ではsystemdがデフォルトで使われています。そのsystemdを本番環境で運用して1年以上が経ち、様々な経験をしてきました。systemdの環境で知っておくと役に立つと思われることについていくつか紹介したいと思います。

まずは、systemdの概要について簡単に紹介します。

systemdの概要と歴史

systemdは、従来のSysVinit/Upstartに代わるもので、Linuxサーバの起動時に初期設定やサービス起動をおこなうことにとどまらず、プロセスやリソースなど様々な管理をおこなうデーモンです。
Fedora 14の頃(2010年11月リリース)にTechnology Previewとして提供され、Fedora 15でUpstartに代わってsystemdがデフォルトとなりました。Red Hat Enterprise Linux(以下RHEL)では、2014年中にリリースされると言われているVersion 7でsystemdがデフォルトになる予定とのことです。あくまで推測ですが、CentOSもVersion 7で、systemdがデフォルトになるのではないかと思っています。

systemd起動までの流れ

簡単に図と流れを書いてみます。細かなところは端折っています。

1. BIOSからディスクのMBRにあるブートローダー(GRUB)起動

2. 別パーティションにあるGRUBのcore.imgをロード

3. ディスク上の/boot/grub2をロード

4. vmlinuzとinitramfsをメモリにロード

5. initramfsをRAMディスクに展開、マウント

6. Kernelが/initを起動。実体は、/usr/lib/systemd/systemd

7. udev起動、/sysrootにディスクをマウント

8. systemctl switch-rootで/sysrootにchroot [1]

9. ディスク上の/sbin/init(/usr/lib/systemd/systemdのsymlink)を起動

10. 依存関係にある各サービスの起動

以上の流れで起動しています。(もし間違いがあれば、やさしくご指摘ください:p)

サービスの制御方法の変更点

SysVinit/Upstartの場合

service SERVICE_NAME start/stop/restart/status
もしくは
/etc/init.d/SERVICE_NAME start/stop/restart/status

systemdの場合

service SERVICE_NAME start/stop/restart/status

もしくは

systemctl start/stop/restart/status SERVICE_NAME

systemdに変わっても、共通してserviceコマンドで制御ができます。

serviceコマンドの実体はシェルスクリプトで、/etc/init.d 以下に指定されたサービスのスクリプトがあればそれを実行、無ければsystemctlコマンドにサービス名を渡して実行、という処理がされます。

systemdに対応したソフトウェアは、パッケージインストール時に /usr/lib/systemd/system に .service ファイルが用意され、systemctlコマンド実行時に使われます。

systemd非対応の場合は、/etc/init.d 以下のスクリプトしか用意されていません。しかし、この場合でも systemdがサービスをコントロールできるように工夫がされています。

スクリプトの最初には、

# Source function library.
. /etc/init.d/functions

という記述があるはずなので、このfunctionsの中では、

if [ $PPID -ne 1 -a -z "$SYSTEMCTL_SKIP_REDIRECT" ] && \
                ( /bin/mountpoint -q /cgroup/systemd || /bin/mountpoint -q /sys/fs/cgroup/systemd ) ; then
        case "$0" in
        /etc/init.d/*|/etc/rc.d/init.d/*)
                _use_systemctl=1
                ;;
        esac
fi

というように、PIDが1以外(systemd自身ではない)かつ、$SYSTEMCTL_SKIP_REDIRECTが空の場合、_use_systemctl=1というフラグが設定され、systemctl_redirectという関数がコールされ、結果としてsystemctlコマンドに渡されるようになっています。詳しくは /etc/init.d/functionsを眺めるとよくわかると思います。

systemctlに渡された後は、.serviceファイルが存在しないため、systemdが /etc/init.d 以下のスクリプトをパースして、.serviceファイルに代わる情報(pidファイルの位置やサービス名など)をLSBヘッダーから集めた後、/etc/init.d以下のスクリプトにstart/stop/reload引数を付けて実行します。こうすることで、/etc/init.d以下のスクリプトにおいても、systemdがプロセスをコントロールできるようになっています。

PAMにsystemdが使われるようになった

PAM(Pluggable Authentication Modules)にもsystemdが使われるようになりました。sshの認証にPAMを使っている場合(UsePAM yes)、/etc/pam.d/sshdの設定が使われますが、その中で、

session    include      password-auth

という箇所があります。さらに、includeされた/etc/pam.d/password-authの設定の中に

-session     optional      pam_systemd.so

という箇所があります。これにより、sshでログインした際にpam_systemd.soがロードされることになります。PAMにsystemdが使われるようになったことでどのような影響があるのか、これにはcgroupsとの密接な関係があります。詳しくは次の節で触れてみます。

cgroups(Control Groups)との関係

cgroupsは、Kernelが持つ機能の1つで、CPUやメモリなどのリソースをプロセスグループごとに割り当てることができるものです。各グループの階層については、systemd-cglsコマンドで確認できます。

Fedora 19のマシンで systemctl start httpd.service を実行した後の systemd-cgls 実行結果を以下に示します。(表示が長いので一部割愛)

|-system
| |-1 /usr/lib/systemd/systemd --switched-root --system --deserialize 19
| |-httpd.service
| | |-32595 /usr/sbin/httpd -DFOREGROUND
| | |-32596 /usr/sbin/httpd -DFOREGROUND
| | |-32597 /usr/sbin/httpd -DFOREGROUND
| | |-32598 /usr/sbin/httpd -DFOREGROUND
| | |-32599 /usr/sbin/httpd -DFOREGROUND
| | `-32600 /usr/sbin/httpd -DFOREGROUND
| |-crond.service
| | `-2802 /usr/sbin/crond -n
| |-rpcbind.service
| | `-800 /sbin/rpcbind -w
| |-sshd.service
| | `-30427 /usr/sbin/sshd -D
| |-getty@.service
| | `-getty@tty1.service
| |   `-17744 /sbin/agetty --noclear tty1 38400 linux
| |-dbus.service
| | `-457 /bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation
| |-rsyslog.service
| | `-454 /sbin/rsyslogd -n
| |-systemd-logind.service
| | `-446 /usr/lib/systemd/systemd-logind
| |-systemd-udevd.service
| | `-316 /usr/lib/systemd/systemd-udevd
| `-systemd-journald.service
|   `-313 /usr/lib/systemd/systemd-journald
`-user
  `-1000.user
    `-1020.session
      |-32564 sshd: shimizu [priv]
      |-32566 sshd: shimizu@pts/0
      |-32567 -zsh
      |-32602 systemd-cgls
      `-32603 less

systemとuserという大きなグループで構成され、system以下にはsystemdがコントロールするサービスがあり、user以下には、ログインしたユーザーが起動したプロセスがあることがわかります。

今度は、systemctlを使わずに、直接apachectlコマンドを使ってhttpdを起動してみます。

sudo apachectl -k start

実行後、systemd-cglsコマンドでcgroupsの状態を見てみます。

|-system
| |-1 /usr/lib/systemd/systemd --switched-root --system --deserialize 19
中略
`-user
  `-1000.user
    `-1020.session
      |-32564 sshd: shimizu [priv]
      |-32566 sshd: shimizu@pts/0
      |-32567 -zsh
      |-32614 /usr/sbin/httpd -k start
      |-32615 /usr/sbin/httpd -k start
      |-32616 /usr/sbin/httpd -k start
      |-32617 /usr/sbin/httpd -k start
      |-32618 /usr/sbin/httpd -k start
      |-32619 /usr/sbin/httpd -k start
      |-32620 systemd-cgls
      `-32621 less

すると、user以下に起動したhttpdが属していることがわかります。pam_systemdがログイン時にuser以下のグループを自動的に作成し、起動したプロセスは配下にぶら下がる形になります。

一旦ログアウトして、ログインし直すと以下のようになっています。

|-system
| |-1 /usr/lib/systemd/systemd --switched-root --system --deserialize 19
中略
`-user
  `-1000.user
    |-1021.session
    | |-32623 sshd: shimizu [priv]
    | |-32625 sshd: shimizu@pts/0
    | |-32626 -zsh
    | |-32643 systemd-cgls
    | `-32644 less
    `-1020.session
      |-32614 /usr/sbin/httpd -k start
      |-32615 /usr/sbin/httpd -k start
      |-32616 /usr/sbin/httpd -k start
      |-32617 /usr/sbin/httpd -k start
      |-32618 /usr/sbin/httpd -k start
      `-32619 /usr/sbin/httpd -k start

当然ながらログアウトしてもこのグループは消えず、httpdは起動したままになっています。

次に、PAMの設定からpam_systemd.soを使わないようにしたらどうなるか試してみます。
/etc/pam.d/password-authの設定にあるpam_systemd.soの行をコメントアウトします。

#-session     optional      pam_systemd.so

先ほど起動したhttpdをkillしておいて、一旦ログアウト、再度ログインしなおします。そして再度httpdを起動します。

sudo apachectl -k start

実行後、systemd-cglsコマンドでcgroupsの状態を見てみます。

`-system
  |-1 /usr/lib/systemd/systemd --switched-root --system --deserialize 19
 中略
  |-sshd.service
  | |-30427 /usr/sbin/sshd -D
  | |-32657 sshd: shimizu [priv]
  | |-32659 sshd: shimizu@pts/0
  | |-32660 -zsh
  | |-32681 /usr/sbin/httpd -k start
  | |-32682 /usr/sbin/httpd -k start
  | |-32683 /usr/sbin/httpd -k start
  | |-32684 /usr/sbin/httpd -k start
  | |-32685 /usr/sbin/httpd -k start
  | |-32686 /usr/sbin/httpd -k start
  | |-32687 systemd-cgls
  | `-32688 less
 中略
  `-systemd-journald.service
    `-313 /usr/lib/systemd/systemd-journald

今度は、ログイン時にグループが作られないため、userグループがありません。httpdはsshd.service配下にぶら下がる形になっています。/etc/ssh/sshd_configでUsePAM noとした場合もこの状態になります。この場合、何らかの作業でsshdをrestartした場合、httpdはどうなるでしょうか。

その挙動について知るべく、sshdのサービスファイル /usr/lib/systemd/system/sshd.serviceファイルを見てみます。(Fedora 19の標準のOpenSSH 6.2p2の場合)

[Unit]
Description=OpenSSH server daemon
After=syslog.target network.target auditd.service

[Service]
EnvironmentFile=/etc/sysconfig/sshd
ExecStartPre=/usr/sbin/sshd-keygen
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process

[Install]
WantedBy=multi-user.target

[Service]以下に、KillModeという設定があります。これは、サービスを停止する際にどの単位でプロセスをkillするかという設定です。設定値としては、control-group、process、noneという値が設定できますが、このsshd.serviceの場合は、最初からprocessという値が設定されています。この場合、サービスの停止時(or 再起動時)には、グループに属するプロセスが影響を受けず、sshdプロセスのみがkillされます。逆に設定がない場合は、control-groupがデフォルトの設定として使われ、サービスの停止時(or 再起動時)はグループに属するプロセスすべてがkillされることになります。noneの場合は何もkillしません。

Fedora 19から搭載されているOpenSSHでは、最初からKillModeの設定が.serviceファイルに書かれるようになりましたが、Fedora 17、18のreleasesパッケージのOpenSSHではこの記述がありません。

しかし、

Use KillMode=process for the sshd.service
https://bugzilla.redhat.com/show_bug.cgi?id=890376

このBugzillaのレポートがきっかけで、Fedora 18のupdatesパッケージ、openssh-6.1p1-8からKillModeの記述がデフォルトでされるようになりました。sshdのプロセスだけが影響を受けるほうが安全ですね。

通常、PAMの設定を変更することは少ないかもしれませんが、何らかの都合で設定の変更が必要になった場合、上に書いたようなcgroupsとの関係や、.serviceファイルのKillMode設定を覚えておくと幸せかもしれません。

systemd関連で抑えておくと便利なコマンド

systemdに関わるコマンドは多数あるのですが、代表的なものをいくつか紹介します。

サービスの有効・無効化

systemctl enable/disable SERVICE_NAME

いわゆるchkconfig on/offに相当します。別に以下のコマンドもあります。

systemctl mask/unmask SERVICE_NAME

enable/disableよりも強力なコマンド。serviceファイルが/dev/nullのsymlinkになります。

systemdのリロード

systemctl daemon-reload

systemd管理下のファイルをリロードして、依存関係の階層を再構築します。.serviceファイルなどを更新した場合に必要なコマンドになります。

各フェーズにおける起動時間を知る

systemd-analyze

起動にかかった時間の詳細がわかります。

$ systemd-analyze
Startup finished in 1.781s (kernel) + 12.580s (initrd) + 15.895s (userspace) = 30.257s

plotオプションをつけるとSVGファイルを出力します。

$ systemd-analyze plot

仮想環境の種類の判定

systemd-detect-virt

仮想環境の種類の判定ができます。サーバの管理上、仮想環境の種類によってConfigurationを変えたりしたい場合には使えるかもしれません。

KVMのゲストOSがで実行する

$ sudo systemd-detect-virt
kvm

LXCのインスタンスで実行する

$ sudo systemd-detect-virt
lxc

何を元に判定しているのか、systemd-detect-virtのコードを読んだところ、KVMの場合はCPUIDを元に、LXCの場合は/proc/1/environを元に判定していました。仮想環境ではない場合の出力は none になります。

まとめ

本番稼働している大半のサーバでsystemdを運用して1年以上が経過しましたが、幸い大きなトラブルには遭遇していません。些細なバグに遭遇したことはありましたが、systemdのバージョンアップによって解消しました。問題に遭遇した場合は、Bugzillaや最新のsystemdのChangelogなどを見ると該当する情報が見つかるかもしれません。

systemdは非常に多くの機能があり、1回のエントリではすべてを解説しきれないので、今回は個人的にひとまず抑えておいたほうがいいと思っている点に絞って書いてみました。systemdを扱うことは、慣れるまでに相当なストレスを感じるかもしれませんが、挙動を正しく理解することで徐々に解消していくと思います。また、systemdだけでは物足りず、cgroupsなどの関連技術についても学ぶ必要が出てくるため、決して学習コストは少ないなぁという印象です。RHEL 7がリリースされた後には、systemdを使って運用するケースが増えてくると予想されるので、systemdの運用ノウハウについて共有できる場が増えてくるといいなぁと思っています。

補足 - systemdについて理解を深めるために

systemdは、Red Hat社のLennart Poetteringが開発したことで有名ですが、彼のブログではsystemdに関する記事が多く書かれています。記事に目を通すと非常に勉強になりますし、systemdの理解がより深まります。また、systemdのmanページも非常に充実しているので併せて読むといいかもしれません。
(今年のLinuxCon JapanでLennart自身によるsystemdの講演を聞きましたが、私の英語力と理解力の低さにより半分も理解できませんでした…)

英語が苦手な方、まとまった内容を読みたい方は、以下のスライドやページがとても参考になります。

Linux女子部 systemd徹底入門
http://www.slideshare.net/enakai/linux-27872553

11月のLinux女子部に参加して、この資料に沿った講演を拝聴したのですが、ものすごく充実しています。オススメです!

systemd
http://www.slideshare.net/moriwaka/systemd

systemdを触り始めた時に大変お世話になりました。

systemd (日本語)
https://wiki.archlinux.org/index.php/Systemd_(日本語)

Arch Linuxのドキュメントですが、日本語で丁寧に解説されています。

(注)

[1] dracutの起動シーケンス
https://www.kernel.org/pub/linux/utils/boot/dracut/dracut.html#dracutbootup7