Ubuntu Weekly Recipe

第358回Upstart Jobをsystemd Unitに変換する

先日発売されたSoftware Design 2015年2月号の第1特集は「Linux systemd入門」でした。その特集の中の後ろのほうにちょこっとだけ、主に14.04と14.10におけるsystemdの状況と今後の予定について書かせていただいています。そこで今回のRecipeでは、Upstart初心者向けのsystemdとの違いについて紹介します。

もちろんSoftware Design 2015年2月号を読んでいなくてもわかるように書いています。ただ同号にはsystemdそのものの基本も載っていますので、併せて読んでいただけると嬉しいです。

Ubuntuにおけるsystemdの進捗

多くの方がご存知のとおり、Ubuntuで現在採用されているinitデーモンは「Upstart」です。Ubuntu 6.10から採用されたUpstartは、イベントベースの非同期的な起動システムによって、起動時間の大幅な高速化に貢献してくれました。そんなUbuntuのinitデーモンですが、Debianがデフォルトのinitとしてsystemdの採用を決定したことをうけて、Ubuntuでも将来的にsystemdに移行することが議論されています

少なくともUbuntu 14.04 LTSは従来どおりUpstartを利用しています。このため、少なくとも5年間はUpstartのサポートが続きます。これを踏まえてSoftware Design 2015年2月号では14.04以降の流れや、今のUbuntu内部でsystemdがどのように使われているのか[1]⁠、今後どういう予定になっているかについて紹介しました。そこで今回のRecipeでは、移行に向けての具体的な作業内容について紹介したいと思います。

2015年4月にリリース予定の15.04では、かなり多くのパッケージがsystemd「も」サポートする予定です。これはJessieのリリースに向けたDebianの成果を取り込んでいることに加えて、先日のSprintではUpstartのみサポートしていた100近くのパッケージにsystemdのUnitも追加しました。現時点で残っているのはUbuntu Touch関連か、UpstartのSession Job[2]で利用するものばかりなので、15.04でPID=1をsystemdに移行するための「パッケージ側の準備」はほぼ整ったと言えます。

15.04をインストールしたマシンがあるのであれば、以下のように必要なパッケージをインストールして、GRUBからカーネルの起動オプションを設定することで、かんたんにPID=1をsystemdに変更することが可能です。

$ sudo apt-get install systemd libpam-systemd systemd-ui

/etc/default/grubのGRUB_CMDLINE_LINUX_DEFAULTにinitオプションを追加してください。

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash init=/lib/systemd/systemd"

最後にGRUBの内容をアップデートします。

$ sudo update-grub

再起動すればPID=1がsystemdになっているはずです。

今後はアップグレード時の移行シナリオや移行の是非・タイミングを議論することになります[3]⁠。また、systemdの多種多様な機能をフルに活用するという状態にはなっていません。hostnamectlやtimedatectlなど、既存の仕組みとかぶっているsystemdの機能はどうするのかということも考えなくてはなりません。その結果についてはいずれアナウンスされる事になりますので、Ubuntu Weekly Topicsやリリースノートにご期待ください。

Upstartとsystemdの比較

公式リポジトリから提供しているパッケージについてはUbuntuコミュニティで修正するとして、各自で作成しているUpstart Jobをどうすれば良いのでしょうか。これについては作成者が各自で手作業で対応していただくほかありません。Upstartとsystemdの設定ファイルの書式や「できること」が大幅に違うため、自動的な変換ツールを提供することは難しいでしょう。その代わり移植の際に蓄積したノウハウが、UbuntuのWikiにまとまっていますのでそちらを参考にするのが良いでしょう。

Upstartとsystemdの用語は必ずしも1:1の対応になっているわけではありませんが、基本的な部分において、似たような意味や役割を持つ用語はいくつか存在します。たとえばUpstartは特定のサービスやタスクを実行する処理単位をJobと呼びます。systemdで対応するのは「Unit」です。

Jobは「/etc/init/」以下に「Job名.conf」という名前で保存されます。システム/パッケージが提供するJobは、⁠/etc/init/Job名.override」というファイルを作ることでその挙動を上書きできます。systemdの場合はシステムが提供するUnitは「/lib/systemd/system/」に配置され、ユーザーがUnitの挙動を変更する場合は/etc/systemd/system/に同名のファイルを置くことになります。

UpstartのJobファイルの中ではstanzaを使って設定を行います。systemdのUnitファイルではdirectiveが近い存在でしょう。Upstartはイベントベースですので、特定のJobが起動する契機となるイベントを「start on」stanzaで指定します。既存のイベントや呼び出される契機については「man upstart-events」に詳細が載っていますし、Jobファイルの中で独自のイベントを生成することも可能です。systemdの場合はUnit同士の「依存関係」「起動順序」「Wants/Requires/After/Before」directiveなどを利用して、より細かく設定できます。

UpstartのJob操作コマンドは「initctl」「start/stop/restart」を利用します。systemdの場合は「systemctl」コマンドが中心となります。詳しいことはWikiの比較表を参照してください。

OpenSSH Serverの場合

では、実際にOpenSSH Serverを例に2つの設定ファイルを比較してみましょう。以下は、Ubuntu 14.04 LTSにおけるopenssh-serverパッケージのUpstert Jobです(/etc/init/ssh.conf⁠⁠。

# ssh - OpenBSD Secure Shell server
#
# The OpenSSH server provides secure shell access to the system.

description     "OpenSSH server"

start on runlevel [2345]
stop on runlevel [!2345]

respawn
respawn limit 10 5
umask 022

env SSH_SIGSTOP=1
expect stop

# 'sshd -D' leaks stderr and confuses things in conjunction with 'console log'
console none

pre-start script
    test -x /usr/sbin/sshd || { stop; exit 0; }
    test -e /etc/ssh/sshd_not_to_be_run && { stop; exit 0; }

    mkdir -p -m0755 /var/run/sshd
end script

# if you used to set SSHD_OPTS in /etc/default/ssh, you can change the
# 'exec' line here instead
exec /usr/sbin/sshd -D

以下は同じくUbuntu 14.04 LTSにおけるopenssh-serverパッケージに含まれているsystemd Unitです(/lib/systemd/system/ssh.service⁠⁠。

[Unit]
Description=OpenBSD Secure Shell server
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

[Service]
EnvironmentFile=-/etc/default/ssh
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure

[Install]
WantedBy=multi-user.target
Alias=sshd.service

UpstartのJobファイルは「#」で始まるコメントを除くと、大抵はdescription stanzaでJobの説明を記述したあとに、まずは「start on」⁠stop on」で起動や終了の契機となるイベントを設定します。ssh.confで設定されている「runlevel」イベントはrc-sysinit.conf[4]が最後にtelinitコマンドで発行しますので、rc-sysinit.confのJobの完了タイミングもしくは外部でtelinitコマンドを使った場合に評価されます。stop onの「[!2345]」「runlevelが0(halt)か1(single user)もしくは6(reboot)のときに停止する」という意味になります。

systemdの場合はAfterで起動順序を、WantedByで依存関係を設定しています。multi-user.targetは、Ubuntuでいうところの「runlevel [2345]」に近いので、Afterによってnetwork.targetよりあとに起動することをUnitファイル内部で明示していること以外はUpstartと同じ内容だと思って良いでしょう。ちなみにUpstartの場合は、rc-sysinit.conf自体がネットワーク設定が完了したあとに起動されるようになっています。

Upstartの「respawn」「respawn limit 10 5」はexec stanzaで指定したプロセスが終了したときに自動再起動することとその処理を「5秒間隔で最大10回」と制限しています。systemdでは「Restart」directiveで、エラー終了時に再起動することを設定しています。

expect stopはUpstartがexecされたプロセスの起動完了の判定方法を指定します。execされたプロセスは、サービスの種類によって1回ないし2回forkすることがあります。Ubuntuのopenssh serverは環境変数SSH_SIGSTOPが設定されていたら起動完了後にSIGSTOPを送るパッチが適用されているので、このような設定になっています。systemdにおける同じような機能をもったdirectiveは「Type」です。

pre-start stanzaではサービス起動前に必要な準備を行います。場合によってはここやexec stanzaで、他のJobのために独自のイベントを発行することもあります。最後にexec stanzaでコマンドを実行したら起動完了です。systemdの場合はConditionPathExistsなどいくつかのdirectiveを組み合わせて同等のことを実現しているようです。

pre-start stanzaのように、UpstartのJobの中ではscript stanzaを使うことで、シェルスクリプトを記述することが可能です。script stanzaやexec stanzaで実行されるコマンドは「/bin/sh -e」経由で実行されることになっています。systemdにはそのような仕組みはなく、ExecStartなどは直接exec()されるので、別途外部スクリプトを用意するか、⁠/bin/sh -e」から書いたうえで「-c」で実行するスクリプトを文字列で渡してください。

まとめ

Ubuntu 14.04 LTSから16.04 LTSにアップグレードする際には、Upstartからsystemdへの移行と言う壁が立ちふさがる可能性があります。今からでも、systemdについての情報を集めておいても遅くはないでしょう。また、現時点でUbuntu 14.04 LTS上のサービス向けに起動ファイルを作成する必要がある場合は、systemdを念頭に起きながらUpstartで記述するのが無難です。もしイベント駆動である必要がないサービスであれば、従来のSysVInit向けの起動スクリプトを「/etc/init.d/」以下に置いてupdate-rc.dコマンドで有効・無効を管理すると言うのも1つの手かもしれません。

おすすめ記事

記事・ニュース一覧