前回(第8回) で常駐化できましたが、更新は毎回手動でpublish&再起動でした。今回は GitHub Actions で、pushしたら自動でデプロイされるようにします。
Table of contents
Open Table of contents
難所:Piは自宅NATの内側にいる
GitHub Actions のランナーはクラウド、Piは自宅ルーターの内側(公開IPなし)。クラウドから直接Piへ入るのは面倒です。そこで、Pi自身にランナーを置く「セルフホストランナー」方式が最も手軽です。ランナーは外向き通信でGitHubを見に行くので、ポート開放もグローバルIPも不要です。
セルフホストランナーとは
GitHub Actions のジョブを実際に動かす「作業員」が ランナー です。通常はGitHubが用意するクラウド上のランナーが使われますが、自分のマシン(今回はRaspberry Pi)をランナーにするのが セルフホストランナー です。
Piにランナーの常駐プログラムを入れておくと、それが GitHubへ外向きに「仕事はある?」と問い合わせ続け(ポーリング)、pushでジョブが来たら Pi自身がその場で実行します。
flowchart LR
Runner["Piのランナー(常駐)"]
GH["GitHub(クラウド)"]
App["Blazorアプリ publish→再起動"]
Runner -->|① 外向きに問い合わせ| GH
GH -->|② ジョブを渡す| Runner
Runner -->|③ Piの中で実行| App
- 外向き通信だけでやり取りするので、ポート開放もグローバルIPも不要(NATの内側でOK)
- ジョブはPiの中で動くため、
~/.dotnetやsystemctlなどPiのローカル資源をそのまま使える
STEP 1:Piにセルフホストランナーを入れる
登録手順とコマンドは、GitHubが自動生成してくれます。
- リポジトリの Settings → Actions → Runners → New self-hosted runner を開く
- Linux / ARM64 を選ぶ
- 画面に出るコマンドをPi上で上から順に実行する(
<URL>や<TOKEN>は画面の値をそのまま使う)
手順は、大きく3ステップです。
(1)と(2)はコマンド例であり、GitHubの画面上に表示されたコマンドを正としてください。
(1) Download:Pi上にランナー本体を取得
mkdir actions-runner && cd actions-runner
curl -o runner.tar.gz -L <画面に表示されるダウンロードURL>
tar xzf runner.tar.gz
(2) Configure:このPiをリポジトリのランナーとして登録
./config.sh --url https://github.com/<user>/<repo> --token <画面のTOKEN>
config.sh は (1)で展開したフォルダーに最初から入っているセットアップ用スクリプトです(自分で作るものではありません)。これでPiとリポジトリが紐付きます。
(3) svc.sh:常駐サービスとして自動起動させる
sudo ./svc.sh install
sudo ./svc.sh start
svc.sh も同梱のサービス管理スクリプト。これで、SSHを切っても・Piを再起動してもランナーが動き続け、pushを待ち受けます。
これで、pushをPi上のランナーが受けて処理できます。
STEP 2:サービス再起動を許可する(sudoers)
ワークフローの最後で sudo systemctl restart tempmonitor を実行しますが、sudo は通常パスワード入力を求めます。自動実行ではパスワードを打てないため、そこで止まってしまいます。
そこで使うのが sudoers です。sudoersは「誰が・どのコマンドを・パスワード無しで sudo 実行してよいか」を定義するLinuxの設定ファイル。ここで、ランナーのユーザー(pi)に、対象サービスの再起動コマンドだけをパスワード無しで許可します。
編集は visudo(文法チェック付きで安全に編集できる専用コマンド)で行います。
sudo visudo -f /etc/sudoers.d/tempmonitor
開いたら次の1行を書いて保存します。
pi ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart tempmonitor
- 意味:
piユーザーがsystemctl restart tempmonitorだけをパスワード無しでsudo実行できる - 許可はこの1コマンドだけに絞ります(最小権限)。
pi以外でランナーを動かす場合はそのユーザー名に変更 systemctlのパスは環境で異なることがあるのでwhich systemctlで確認(多くは/usr/bin/systemctl)
STEP 3:ワークフローを書く
リポジトリに .github/workflows/deploy.yml を追加します。
name: deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: self-hosted # ← Pi上のランナーで実行
steps:
- uses: actions/checkout@v4
- name: Publish
run: ~/.dotnet/dotnet publish -c Release -o ~/tempmonitor
- name: Restart service
run: sudo systemctl restart tempmonitor
main にpushすると、Piがチェックアウト→publish→サービス再起動まで自動で行います。第8回のsystemdサービスがそのまま更新されます。
動作確認
コードを少し直して git push すると、GitHubの Actions タブでジョブが走り、完了後に http://<PiのIP>:5000 が更新されていれば成功です。
⚠️ セキュリティ(重要)
- セルフホストランナーは原則プライベートリポジトリで。公開リポジトリだと、フォークからのPRであなたのPi上で任意コードが実行される危険があります
sudoersの許可は必要な1コマンドに限定する- ランナーのトークン・秘密情報はリポジトリの Secrets で管理
別方式:クラウドでビルドしてSSHで配る
Piでビルドさせたくない場合は、GitHubのランナーでビルドし、成果物をPiへ送る方式もあります。
dotnet publish -c Release -r linux-arm64 --self-containedで成果物を作成- Tailscale+SSH(
appleboy/ssh-action等)でNATを越えてPiにコピー&再起動 - SSH秘密鍵は GitHub Secrets に格納
Piの負荷を下げられる一方、SSH到達性(Tailscale等)の準備が要ります。まずはセルフホストランナー方式が手軽です。
つまずきポイント
dotnetが見つからない:ランナーの実行環境にPATHが無い場合がある。フルパス(~/.dotnet/dotnet)で呼ぶ- 再起動でパスワードを要求される:
sudoersの記述とパス(which systemctlで確認) - publishでファイルが使用中:通常Linuxは実行中でも差し替え可能。失敗する場合は一時ディレクトリへ出して入れ替える
まとめ
- 自宅NAT内なら セルフホストランナーが手軽(外向き通信のみでポート開放不要)
- ワークフローは checkout → publish →
systemctl restart - 再起動は 最小権限のsudoersで許可
- セキュリティ:セルフホストランナーはプライベートリポジトリで
次回(第10回・最終回)は、安全な外部公開。Cloudflare Tunnelでポート開放なしにHTTPS公開し、運用の仕上げをします。
