はじめに
Linuxだとsystemdのユニットファイルを書くけど、FreeBSDはrcスクリプト。
「古臭い」って思うかもしれないけど、シェルスクリプトだから何でもできるという利点がある。
今回は自作サービスを起動時に自動実行する方法を解説。
rcスクリプトの基本構造
#!/bin/sh
# PROVIDE: myservice
# REQUIRE: NETWORKING
# KEYWORD: shutdown
. /etc/rc.subr
name="myservice"
rcvar="myservice_enable"
command="/usr/local/bin/myservice"
load_rc_config $name
run_rc_command "$1"
これだけでOK。
各パーツの解説
メタデータ
# PROVIDE: myservice # このスクリプトが提供するサービス名
# REQUIRE: NETWORKING # 依存するサービス(これらの後に起動)
# BEFORE: LOGIN # これより前に起動
# KEYWORD: shutdown # シャットダウン時にも実行
REQUIRE/BEFOREで起動順序を制御。
基本変数
name="myservice" # サービス名
rcvar="myservice_enable" # rc.confの有効化変数
command="/usr/local/bin/myservice" # 実行ファイル
読み込みと実行
. /etc/rc.subr # rcフレームワーク読み込み
load_rc_config $name # rc.confから設定読み込み
run_rc_command "$1" # start/stop/restart等を処理
最小の例:シンプルなデーモン
デーモン本体
# /usr/local/bin/myservice
#!/bin/sh
while true; do
echo "$(date): Running..." >> /var/log/myservice.log
sleep 60
done
chmod +x /usr/local/bin/myservice
rcスクリプト
# /usr/local/etc/rc.d/myservice
#!/bin/sh
# PROVIDE: myservice
# REQUIRE: DAEMON
# KEYWORD: shutdown
. /etc/rc.subr
name="myservice"
rcvar="myservice_enable"
command="/usr/local/bin/myservice"
command_interpreter="/bin/sh"
pidfile="/var/run/${name}.pid"
start_cmd="${name}_start"
stop_cmd="${name}_stop"
myservice_start()
{
echo "Starting ${name}."
/usr/sbin/daemon -p ${pidfile} ${command}
}
myservice_stop()
{
if [ -f ${pidfile} ]; then
echo "Stopping ${name}."
kill $(cat ${pidfile})
rm -f ${pidfile}
fi
}
load_rc_config $name
run_rc_command "$1"
chmod +x /usr/local/etc/rc.d/myservice
有効化
# rc.confに追加
sysrc myservice_enable="YES"
# 起動
service myservice start
# 確認
service myservice status
より実用的な例
Pythonアプリケーション
#!/bin/sh
# PROVIDE: myflask
# REQUIRE: NETWORKING
# KEYWORD: shutdown
. /etc/rc.subr
name="myflask"
rcvar="myflask_enable"
# 設定変数(rc.confで上書き可能)
: ${myflask_enable:="NO"}
: ${myflask_user:="www"}
: ${myflask_group:="www"}
: ${myflask_dir:="/var/www/myflask"}
: ${myflask_env:="/var/www/myflask/venv"}
: ${myflask_host:="0.0.0.0"}
: ${myflask_port:="5000"}
pidfile="/var/run/${name}.pid"
command="${myflask_env}/bin/python"
command_args="${myflask_dir}/app.py"
start_precmd="${name}_prestart"
start_cmd="${name}_start"
stop_cmd="${name}_stop"
status_cmd="${name}_status"
myflask_prestart()
{
# 必要なディレクトリを確認
if [ ! -d "${myflask_dir}" ]; then
echo "Error: ${myflask_dir} does not exist"
return 1
fi
if [ ! -f "${myflask_env}/bin/python" ]; then
echo "Error: Python venv not found"
return 1
fi
}
myflask_start()
{
echo "Starting ${name}."
cd ${myflask_dir}
/usr/sbin/daemon -u ${myflask_user} -p ${pidfile} \
${command} ${command_args} \
--host=${myflask_host} --port=${myflask_port}
}
myflask_stop()
{
if [ -f ${pidfile} ]; then
echo "Stopping ${name}."
kill $(cat ${pidfile}) 2>/dev/null
rm -f ${pidfile}
else
echo "${name} is not running."
fi
}
myflask_status()
{
if [ -f ${pidfile} ]; then
pid=$(cat ${pidfile})
if kill -0 ${pid} 2>/dev/null; then
echo "${name} is running as pid ${pid}."
else
echo "${name} is not running (stale pidfile)."
fi
else
echo "${name} is not running."
fi
}
load_rc_config $name
run_rc_command "$1"
rc.confでの設定
# /etc/rc.conf または /etc/rc.conf.local
myflask_enable="YES"
myflask_user="www"
myflask_port="8080"
myflask_dir="/var/www/myapp"
組み込みコマンド
rc.subrが提供するコマンド:
service myservice start # 起動
service myservice stop # 停止
service myservice restart # 再起動
service myservice reload # 設定再読み込み(対応していれば)
service myservice status # 状態確認
service myservice rcvar # 有効化変数を表示
service myservice poll # 起動完了を待つ
service myservice enabled # 有効かどうか確認
高度なオプション
環境変数の設定
myservice_env="PATH=/usr/local/bin:/usr/bin LANG=ja_JP.UTF-8"
chroot
myservice_chroot="/var/chroot/myservice"
CPU/メモリ制限
myservice_limits="-v 1073741824" # 1GBメモリ制限
複数インスタンス
#!/bin/sh
# PROVIDE: myworker
# REQUIRE: DAEMON
. /etc/rc.subr
name="myworker"
rcvar="myworker_enable"
# インスタンス数
: ${myworker_instances:="4"}
start_cmd="${name}_start"
stop_cmd="${name}_stop"
myworker_start()
{
echo "Starting ${myworker_instances} ${name} workers."
for i in $(seq 1 ${myworker_instances}); do
/usr/sbin/daemon -p /var/run/${name}.${i}.pid \
/usr/local/bin/myworker -n ${i}
done
}
myworker_stop()
{
echo "Stopping ${name} workers."
for pidfile in /var/run/${name}.*.pid; do
if [ -f "${pidfile}" ]; then
kill $(cat ${pidfile}) 2>/dev/null
rm -f ${pidfile}
fi
done
}
load_rc_config $name
run_rc_command "$1"
デバッグ
詳細出力
# スクリプトのデバッグ
sh -x /usr/local/etc/rc.d/myservice start
# rc.subrのデバッグ
rc_debug=YES service myservice start
よくあるエラー
Permission denied
# 実行権限を確認
ls -la /usr/local/etc/rc.d/myservice
# -rwxr-xr-x 1 root wheel 1234 ...
chmod +x /usr/local/etc/rc.d/myservice
cannot run
# rcvarが設定されてるか確認
service myservice rcvar
# myservice_enable="YES"
# rc.confを確認
grep myservice /etc/rc.conf
依存関係エラー
# 依存サービスが起動しているか確認
service networking status
# 依存関係を確認
rcorder /etc/rc.d/* /usr/local/etc/rc.d/* | grep myservice
ベストプラクティス
1. daemon(8)を使う
# フォアグラウンドプロセスをデーモン化
/usr/sbin/daemon -p ${pidfile} ${command}
# ユーザー指定
/usr/sbin/daemon -u ${myservice_user} -p ${pidfile} ${command}
# 出力をログに
/usr/sbin/daemon -o /var/log/myservice.log -p ${pidfile} ${command}
2. デフォルト値を設定
: ${myservice_enable:="NO"}
: ${myservice_user:="nobody"}
: ${myservice_flags:=""}
3. 前処理/後処理を活用
start_precmd="${name}_prestart" # 起動前
start_postcmd="${name}_poststart" # 起動後
stop_precmd="${name}_prestop" # 停止前
stop_postcmd="${name}_poststop" # 停止後
4. ログを残す
myservice_start()
{
echo "Starting ${name} at $(date)" >> /var/log/myservice.log
# ...
}
systemdとの比較
| 項目 | systemd | rc.d |
|---|---|---|
| 設定ファイル | .service | シェルスクリプト |
| 依存関係 | After=, Requires= | REQUIRE, BEFORE |
| 起動方法 | systemctl start | service ... start |
| 並列起動 | デフォルト | rcorder |
| 柔軟性 | 設定ファイルの範囲内 | シェルスクリプト=何でもあり |
| 学習コスト | 低〜中 | 低(シェルスクリプトがわかれば) |
rc.dの利点:シェルスクリプトだから、systemdでは難しい複雑なロジックも書ける。
まとめ
FreeBSDのrcスクリプト:
-
/usr/local/etc/rc.d/に置く -
PROVIDE/REQUIREで依存関係定義 -
daemon(8)でデーモン化 -
rc.confで有効化
シェルスクリプトだから何でもできる。systemdより柔軟。
この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!