5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

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スクリプト:

  1. /usr/local/etc/rc.d/に置く
  2. PROVIDE/REQUIREで依存関係定義
  3. daemon(8)でデーモン化
  4. rc.confで有効化

シェルスクリプトだから何でもできる。systemdより柔軟。

この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?