田舎人の書斎作ったり、試したり、書き留めたり。

田舎人、停電に負けない。

停電でラズパイのcronが止まる:毎日の通知スクリプトをGitHub Actionsの定期実行へ移す

公開

試した環境

実行環境
GitHub Actions(ubuntu-latest、Python 3.12)
移行前
Raspberry Pi の cron
通知先
Discord(Webhook)

毎日決まった時刻にデータを取ってきて、Discordに通知する小さなPythonスクリプトを動かしています。これまでは自宅のRaspberry Piに置き、cronで毎日実行していました。

ところが、田舎では雷による停電がそれほど珍しくありません。停電すればラズパイも止まり、その日の通知は届きません。

幸い、私の家では大きな被害を受けたことはありません。それでも最近は、豪雨による水害もたびたび起きています。停電が長引いたり、機器そのものが水に浸かったりすれば、自宅に置いた機械は動かせなくなります。

ラズパイ側で無停電電源装置を用意するなどの手もありますが、そもそも「毎日1回、数十秒動けばいい」スクリプトなら、自宅の機械で動かす必要はありません。そこで、GitHub Actionsの定期実行に移しました。

GitHub Actionsの定期実行を選んだ理由

ワークフローの設定

リポジトリに .github/workflows/daily.yml を置きます。実際に使っているものから、スクリプト固有の部分を省いた形です。

name: Daily notify

on:
  schedule:
    # UTC 8:30 = 日本時間 17:30(火〜土)
    - cron: "30 8 * * 2-6"
  workflow_dispatch: # Actions の画面から手動で実行できるボタン

jobs:
  run:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: pip

      - run: pip install -r requirements.txt

      - name: Run
        env:
          API_KEY: ${{ secrets.API_KEY }}
          DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
        run: python main.py

いくつか、つまずきやすい点があります。

cronの時刻はUTC

schedule の cron は、日本時間ではなく**UTC(協定世界時)**で書きます。日本時間はUTCより9時間進んでいるので、日本時間の17:30に動かしたいなら、UTCの8:30を指定します。

曜日も同じくUTC基準です。日本時間の早朝(UTCでは前日)に動かす場合は、曜日が1日ずれるので注意が必要です。

毎時0分を避ける

GitHub Actionsの定期実行は、混み合う時間帯には開始が遅れることがあり、特に毎時0分は混みやすいとされています。数分の遅れが困らない用途でも、30 のように0分を外しておくと安心です。

手動実行のボタンを付けておく

workflow_dispatch を書いておくと、Actionsの画面に「Run workflow」ボタンが出ます。設定を変えたあと、次の定期実行を待たずに動作を確かめられるので便利です。

時間の上限を決めておく

timeout-minutes を指定しておくと、通信が固まったときなどに、実行時間を使い切るまで待ち続けるのを防げます。

APIキーはSecretsに入れる

ラズパイでは、APIキーやDiscordのWebhook URLを config.json に書いていました。このファイルはGitに入れていないので、GitHub Actionsからは読めません。

代わりに、リポジトリの「Settings」→「Secrets and variables」→「Actions」で、Secretsとして登録します。登録した値はワークフローの env: で環境変数として渡せ、ログにも伏せ字で表示されます。

設定ファイルと環境変数を両立させる

スクリプトの側は、「config.json があれば読み込み、同じ名前の環境変数があればそちらを優先する」形にしました。こうしておくと、手元では今までどおり config.json で、GitHub Actionsでは環境変数で、同じスクリプトが動きます。

import json
import os

def load_config(config_path="config.json"):
    """config.json があれば読み込み、同じ名前の環境変数があればそちらを優先する。"""
    base_dir = os.path.dirname(os.path.abspath(__file__))
    path = os.path.join(base_dir, config_path)
    cfg = {}
    if os.path.exists(path):
        with open(path, "r", encoding="utf-8") as f:
            cfg = json.load(f)
    for key in ("API_KEY", "DISCORD_WEBHOOK_URL"):
        if os.environ.get(key):
            cfg[key] = os.environ[key]
    return cfg

移してみて

GitHub Actionsで動くことを確かめてから、ラズパイのcronは止めました。二重に通知が届くこともなく、これで自宅の電気の具合とは関係なく、毎日の通知が届くようになりました。

ラズパイは、センサーの値を読むなど自宅に置く意味があるものに使い、時刻どおりに動けばよいだけの処理はクラウドに任せる。役割を分けたことで、停電の心配をひとつ減らせました。

雷の多い地域の方や、豪雨や水害への備えを考えている方には、自宅で動かしている定期処理を一度見直してみることをおすすめします。

まとめ