ZhgChg.Li

GitHub Actions|Self-hosted Runner:CI/CD 実践ガイドで自動化効率最大化

GitHub ActionsとSelf-hosted Runnerの基本から応用までを詳解。設定の悩みを解消し、CI/CDパイプラインの自動化をスムーズに実現する具体的手法を紹介。

GitHub Actions|Self-hosted Runner:CI/CD 実践ガイドで自動化効率最大化
本記事は AI による翻訳です。お気づきの点があればお知らせください。

CI/CD 実践ガイド(二):GitHub Actions と Self-hosted Runner の使用と構築大全

GitHub Actions/Self-hosted Runner の仕組みと使い方をゼロから解説します。

Photo by Dan Taylor

Photo by Dan Taylor

はじめに

前回の「CI/CD 実践ガイド(一):CI/CD とは?CI/CD を使って安定かつ効率的な開発チームを作るには?ツールの選び方は?」では、CI/CD とは何か、得られる効果やツールの選び方について紹介しました。今回は GitHub Actions と Self-hosted Runner の構成と使い方に重点を置き、いくつかの面白い自動化ワークフローを一緒に作りながら、丁寧に解説していきます。

GitHub Actions アーキテクチャフローチャート

開始する前に、まず GitHub Actions の動作構造、フローの関係、および役割を確認しましょう。

GitHub リポジトリ

  • GitHub Actions の世界では、すべての Actions(Workflow YAML ファイル)は必ずある Git リポジトリ内の REPO/.github/workflows/ に保存されます。

GitHub リポジトリ — Actions Secrets

Repo → 設定 → Secrets and variables → Actions → Secrets。

  • Actions のステップで使用する Secret Key や Token を保存
    例:Slack Bot Token、Apple Store Connect API の .p8 キー

  • Secrets の内容は Action ログに表示されず、自動的に ** でマスクされます**

  • Secrets の内容は閲覧や編集ができず、上書きのみ可能です。

  • Secrets は現在テキストのみ対応しており、ファイルのアップロードはできません
    - バイナリデータの場合は、公式手順のBase64エンコードを使って保存する方法 を参照してください。

  • iOS 開発証明書の保存方法は公式ガイドを参照してください: Installing an Apple certificate on macOS runners for Xcode development

  • 組織レベルの Secrets を保存でき、リポジトリ間で共有可能

GitHub Repo — Actions 変数

Repo → 設定 → Secrets and variables → Actions → Variables。

  • Actions のステップでよく使う変数を格納
    例:シミュレーターの iOS バージョン、作業ディレクトリ

  • Variables の内容は閲覧および編集可能です

  • Variables の内容は Action Log に出力できます

  • Variables はプレーンテキストのみをサポートしており、JSON文字列を保存して自分で解析して使用することもできます。

  • 組織レベルの Variables を保存でき、リポジトリ間で共有可能です。

GitHub Actions — トリガー

  • Github Action における最も重要な出発点 — トリガーイベント(条件)

  • トリガーイベントに一致する GitHub Actions のみが実行されます

  • 完全なイベント一覧は公式ドキュメントをご参照ください。

  • 基本的にすべての CI/CD や自動化で遭遇するイベントシナリオを網羅しています。
    しかし、特殊なシナリオでイベントがない場合は、他のイベントと組み合わせて Job 内で判定するか、Schedule を使って手動でチェックするしかありません
    例:PR Merged イベントがない場合は、pull_request: closed と Job の if: github.event.pull_request.merged == true を使って実現します。

よく使われるイベント:

  • schedule (cron):スケジュールによる定期実行(crontab と同様)
    自動化に利用可能:定期的な PR チェック、定期ビルド、定期的な自動化スクリプトの実行など
    すべて main / develop(デフォルトブランチ)で実行されます。

  • pull_request: :PR 関連のイベント
    PR がオープンされたとき、PR がアサインされたとき、ラベルが追加されたとき、新しいコミットがプッシュされたとき…など

  • issuesissue_comment :Issue に関連するイベント
    Issue が作成されたとき、新しいコメントが追加されたとき…など

  • workflow_dispatch :手動トリガー;入力が必要なフィールドを設定でき、GitHub Actions はユーザーが情報を入力できる簡単なフォームを提供します。
    e.g.:

  • workflow_call :別の Action(Workflow)を呼び出してタスクを実行します。

  • workflow_run :他の Action(Workflow)がタスクを実行したときに、このタスクをトリガーします。

さらに多くのイベントタイプや設定の詳細は公式ドキュメントをご参照ください。

GitHub Actions — ワークフロー

  • 別名 Action

  • YAML を使って .yaml ファイルを作成し、ファイルはすべて REPO/.github/workflows/ に配置します。

  • メインブランチ内の Workflow YAML ファイルを基準にする
    他のブランチで開発中の Action が見えない場合や実行に問題がある場合は、まずメインブランチにマージして確認してください。

  • GitHub Actions の最も基本的な単位である Workflow は、それぞれ CI/CD や自動化操作を表します。

  • Workflow は他の Workflow を呼び出してタスクを実行できる
    (この特性を利用して、コア Workflow と呼び出し用の Workflow を分けることができます)

  • ここでは、タスク名、実行戦略、トリガーイベント、タスクのジョブなど、すべての Action に関する設定を定義します。

  • 現在のファイル構成はサブディレクトリをサポートしていません。

  • Action は完全無料(パブリックおよびプライベートリポジトリ)

GitHub Actions — ワークフロー — ジョブ

  • GitHub Actions における実行単位

  • Workflow 内のジョブにはどのようなものがあるか定義する

  • 各 Workflow は複数の Jobs を持つことができます

  • 各ジョブは使用する Runner ラベルを指定する必要があり、実行時には対応する Runner マシンでタスクが実行されます。

  • 複数のジョブは並行実行されます(順序が必要な場合は needs で制約可能)

  • 各ジョブは独立した実行単位(それぞれをサンドボックスとして扱う) と考えるべきであり、ジョブ終了後に生成されたリソースファイルを他のジョブやワークフローで使用する場合は、Artifacts をアップロードするか、self-hosted 環境で共有出力ディレクトリに移動する必要があります。

  • Job が完了すると、他の Job が参照できる文字列を Output として渡せます。
    (例:実行結果 true または false)

  • 特にワークフロー条件を設定していない場合、複数の Job のうち一つの Job がエラーになっても、他の Job は引き続き実行されます

GitHub Actions — ワークフロー/ジョブの再利用

  • Workflow 定義で on: workflow_call を指定すると、他の Workflow に Job として再利用可能な形でパッケージ化できます。

  • 同じ組織内でリポジトリを跨いで共有可能です。

  • そのため、複数のリポジトリで共通の CI/CD ワークフローを共有リポジトリにまとめて利用できます。

GitHub Actions — ワークフロー — ジョブ — ステップ

  • GitHub Actions における最小実行単位

  • Job 内で実際にタスクを実行するプログラム

  • 各ジョブは複数のステップを持つことができます

  • 複数の Steps は順番に実行されます

  • Step が完了すると、後続の Steps が参照できる文字列を出力できます。

  • Step は直接 shell script を記述可能
    gh cli や現在の環境変数(例:PR番号の取得)を利用して、やりたいことを直接実行できます。

  • 特にフロー条件を設定していない場合、Stepでエラーが発生すると直ちに中断され、後続のStepsは実行されません。

GitHub Actions — ワークフロー — ジョブ — アクションステップの再利用

  • Marketplace にある多くの優れた既存ワークフローをそのまま再利用できます。
    例えば、PRにコメントを投稿 などがあります。

  • 自分の一連の作業ステップをまとめて、他のワークフローで直接再利用できる Action GitHub リポジトリとしてパッケージ化することも可能です。

  • Public Repo の Action は Marketplace に公開できます。

パッケージ化された Action のサポート:

  • Docker Action — GitHub Actions は環境変数を Docker コンテナに渡し、その後 shell script、Java、PHP などで処理します。

  • JavaScript/TypeScript Action — node.js を使って直接 GitHub Actions の処理ロジックを作成します。環境変数もすべて参照用に渡されます。
    例: pozil/auto-assign-issue

  • Composite (YAML) — 純粋な YAML でタスクのステップを記述します(GitHub Actions の Workflow — Job — Step と同様)。実行するステップを宣言したり、直接シェルスクリプトを書くことができます。
    例: ZhgChgLi/ZReviewTender

ページの都合上、本記事では GitHub Actions の Action 作成方法については紹介しません。興味がある方は公式ドキュメントをご参照ください: tutorials/creating-a-composite-action

GitHub Runner

  • GitHub は Runner Label に基づいて対応するジョブを Runner に割り当てて実行します。

  • Runner はリスナーとしてのみ機能し、GitHub からのタスクをポーリングして監視します。

  • Job のみを重視し、どの Action(Workflow)かは気にしません。
    そのため、Action A の Job-1 が完了したら、次に Action B の Job-1 が実行され、Action A のすべての Jobs が完了してから Action B に移るわけではありません。

  • Runner は GitHub Hosted Runner または Self-hosted Runner を使用できます。

GitHub ホスト型ランナー

  • GitHub が提供する Runner は、公式リポジトリ一覧を参照してください:

2025/06 の Images リスト

2025/06 の Images リスト

  • Runner にあらかじめインストールされているものは以下から確認できます:
    例:macos-14-arm64

macos-14-arm64

macos-14-arm64

  • iOS開発では、-arm64(Mシリーズ)プロセッサのランナーを優先的に使用すると、より高速に動作します。

  • Job の runs-on に表の YAML ラベルを貼り付けるだけで、そのRunnerを使ってジョブを実行できます。

  • Public Repo の料金体系:完全無料で無制限に利用可能

  • Private Repo 無料枠:
    無料枠(アカウントによって異なりますが、GitHub Free の場合を例とします):
    使用量:毎月無料 2,000 分
    ストレージ:500 MB

  • ⚠️️Private Repo の課金方法:
    無料枠を超えると従量課金が始まります(上限設定や通知可能)。Runnerが動作するマシンのOSやコア数によって料金が異なります:

about-billing-for-github-actions

about-billing-for-github-actions

macOS の価格は、デバイスのコストが高いため高価であることがわかります。

  • 最大同時実行ジョブ数の制限:

usage-limits-billing-and-administration

usage-limits-billing-and-administration

ここでは話がそれすぎています。私たちの重点は Self-hosted Runner です。

社内サーバーでのセルフホストランナー

  • 自分のマシンを Runner として使用する

  • 1台の実機で複数のRunnerを起動し、並行してジョブを処理できる

  • 無料で無制限に使用可能
    機器の購入費用のみで、一度購入すれば使い放題!
    32G RAM M4 Mini(=NT$40,900)を例にすると、GitHub Hosted Runner を使うと1ヶ月で500 USDかかりますが、1台購入してセットアップすれば3ヶ月以上で元が取れます

  • Windows、macOS、Linux (x64/ARM/ARM64) をサポートしています

  • 同じ組織内で複数のリポジトリ間で Runner を共有可能

  • ⚠️現在:actions/cache、actions/upload-artifact、actions/download-artifact はすべて GitHub のクラウドサービスのみ対応しており、これらのデータは GitHub サーバーにアップロードされ、ストレージ使用量に応じて課金されます。
    自分のマシン上で共有ディレクトリを作成して代替することが可能です。

  • Self-hosted Runner は Docker, k8s もサポートしていますが、私はまだ調べていません。

Self-hosted Runner のセットアップは数ステップで完了し(10分以内に設定可能)、すぐにタスクの実行を開始できます(本文の後半で詳しく説明します)。

GitHub Workflow x Job x Step x Runner フロー関係図

ここでは図を使ってワークフローと関係をまとめます。ワークフローが2つ、ランナーが2つあると仮定します:

  • CI — Rには3つのジョブがあり、それぞれのジョブに複数のステップがあります。Runner Label(run-on) — self-hosted-app

  • CD — 4つのジョブがあり、それぞれのジョブにいくつかのステップがあります。Runner Label(run-on) — self-hosted-app

  • Runner — 2つあり、Runner Label はどちらも self-hosted-app です

前述の通り、Runner と Workflow は Runner Label に基づいてタスクの割り当てや受け取りを行います。このケースではすべて同じ self-hosted-app Runner Label を使用しています。Workflow の Jobs はデフォルトで並行実行され、Steps は各 Job の実際の処理内容であり、順番に直列実行されます。すべての Steps が完了すると Job も完了となります。

そのため、Workflow の実際の実行タイムラインは、2つの Runner 間を行き来して Job を実行し、並行して実行されることもあり、現在の Job が終了しても次に実行されるのが必ずしも同じ Workflow の次の Job とは限りません。

⚠️ だからこそ、各ジョブが独立していることを確認することが重要です(特に Self-hosted Runner 環境では、ジョブ終了後にクリーンアップが十分に行われない場合があり、そのジョブの成果物を次のジョブでそのまま使えると思い込んでしまうことがあります)が、そのように使うべきではありません。

Job が後続の Job に成果物を渡す場合:

  • Job Output String : 他のジョブで参照するために変数へプレーンテキストを出力する

  • Artifact-upload/download: ジョブの最後のステップで実行結果を GitHub Artifact にアップロードし、別のジョブの最初のステップでダウンロードして処理を続けます。
    例:ジョブ — パッケージ → パッケージ結果の .ipa を Artifact → ジョブ — デプロイ → .ipa をダウンロード → App Store にアップロードしてデプロイ
    注意点:現在、Self-hosted でも GitHub Artifact はクラウドストレージに保存されます。

  • AWS/GCP…クラウドストレージ : 同様ですが、自分のクラウドストレージサービスを使用します。

  • [Self-hosted Only] 共有ドライブ、ディレクトリ: Self-hosted Runner が共通のディレクトリをマウントしている場合、このディレクトリ内に UUID ごとにフォルダを作成して成果物を保存できます。その後のジョブは前のジョブの Output UUID を使って対応する保存場所を参照し、データを取得して利用します。
    注意点として、異なるホストの Runner も同じ共有ディレクトリをマウントしている必要があります。

  • 同じジョブのステップで全ての作業を完了させる。

実践で学ぶ GitHub Actions — ケーススタディ

「言うより行う」この言葉の意味やプロセス構成の説明は皆さんも少し曖昧に感じていると思います。次に、実際に3つの機能例を挙げて、一緒に手を動かしながら解説していきます。実践しながら学ぶことで、GitHub Actionsが何かを理解していきましょう。

ケース — 1

Pull Request 作成後にファイル変更のサイズラベルを自動で付けて、レビュアーがレビュー作業をスムーズに行えるようにします。

成果図

Demo PR

Demo PR

動作の流れ

  • ユーザーが PR を作成、再オープン、または PR に新しいコミットをプッシュしたとき

  • GitHub Actions のワークフローをトリガーする

  • shell script でファイルの変更数を取得する方法

  • PR のラベルを変更数に応じて付ける

  • 完了

実践してみよう

Repo → Actions → New workflow → 自分でワークフローを設定する。

ファイル名: Automation-PullRequest.yml

Action Workflow はタスクごとにファイルを分けることも、トリガーイベントや目的に応じて同じ目的のものを1つのファイルにまとめることもできます。いずれにせよ複数の Job は並行して実行されます。なお、GitHub Actions は現時点でディレクトリ構造をサポートしていないため、ファイル数を少なくし、階層的な命名を使った方が管理しやすいです

ここでは PR 関連のイベントの Actions をすべて同じ Workflow にまとめています。

Automation-PullRequest.yml

# Workflow(Action) 名称
name: Pull Reqeust Automation

# Actions ログのタイトル名
run-name: "[Pull Reqeust Automation] ${{ github.event.pull_request.title \\|\\| github.ref }}"

# トリガーイベント
on:
  # PR イベント
  pull_request:
    # PR - オープン、再オープン、新しい Push Commit 時
    types: [opened, synchronize, reopened]


# 同じ Concurrency Group に新しい Job がある場合、実行中のものをキャンセル
# 例えば Push Commit でトリガーされたジョブがまだ実行中に再度 Push Commit があった場合、前のジョブをキャンセル
concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}
  cancel-in-progress: true

# Job 作業項目
# Job は並行実行される
jobs:
  # Job ID
  label-pr-by-file-count:
    # Job 名称(省略可、ログに表示され読みやすくなる)
    name: Label PR by changes file count

    # この Job が失敗しても Workflow 全体には影響せず、他の Job は続行
    continue-on-error: true
    
    # 最大タイムアウト時間を設定し、異常時の無限待機を防止
    timeout-minutes: 10

    # Runner ラベル - GitHub Hosted Runner の ubuntu-latest を使用して実行
    # Private Repo の場合は使用量が計算され、超過すると費用が発生する可能性あり
    runs-on: ubuntu-latest

    # 作業ステップ
    # ステップは順番に実行される
    steps:
      # ステップ名
      - name: Get changed file count and apply label
        # ステップ ID(省略可、後で Step の Output を参照しない場合は不要)
        id: get-changed-files-count-by-gh
        # 外部環境変数を実行時に注入
        env:
          # secrets.GITHUB_TOKEN は GitHub Actions 実行時に自動生成されるトークンで、Secrets に設定不要
          # 一部の GitHub Repo API スコープの権限を持つ
          # https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-guides/use-github_token-in-workflows
          # gh(GitHub) cli は GH_TOKEN を ENV に注入する必要があり、gh が操作権限を持つ
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        # シェルスクリプト
        # GitHub Hosted Runner には gh cli があらかじめインストール済みで、インストール不要で使用可能
        run: \\|
          #   ${{ github.xxx }} は GitHub Actions の Context 式
          #   シェル変数ではなく、YAML 解析時に GitHub Actions によって対応する値に置換される
          #   その他のパラメータ:https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
          
          # PR 番号を取得:
          PR_NUMBER=${{ github.event.pull_request.number }}

          # リポジトリを取得:
          REPO=${{ github.repository }}

          # GitHub API (gh cli) で変更ファイル数を取得
          FILE_COUNT=$(gh pr view $PR_NUMBER --repo $REPO --json files --jq '.files \\| length')
          
          # ログ出力
          echo "Changed file count: $FILE_COUNT"

          # ラベル付けロジック
          if [ "$FILE_COUNT" -lt 5 ]; then
            LABEL="XS"
          elif [ "$FILE_COUNT" -lt 10 ]; then
            LABEL="S"
          elif [ "$FILE_COUNT" -lt 30 ]; then
            LABEL="M"
          elif [ "$FILE_COUNT" -lt 80 ]; then
            LABEL="L"
          elif [ "$FILE_COUNT" -lt 200 ]; then
            LABEL="XL"
          else
            LABEL="XXL"
          fi

          # GitHub API (gh cli) で現在のサイズラベルを削除
          EXISTING_LABELS=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json labels --jq '.labels[].name')
          for EXISTING in $EXISTING_LABELS; do
            case "$EXISTING" in
              XS\\|S\\|M\\|L\\|XL\\|XXL)
                echo "🧹 既存ラベルを削除中: $EXISTING"
                gh pr edit "$PR_NUMBER" --repo "$REPO" --remove-label "$EXISTING"
                ;;
            esac
          done

          # (任意)ラベルが存在しなければ作成
          if ! gh label list --repo "$REPO" \\| grep -q "^$LABEL"; then
            echo "🆕 ラベルを作成中: $LABEL"
            gh label create "$LABEL" --repo "$REPO" --description "Size label: $LABEL" --color "ededed"
          else
            echo "✅ ラベル '$LABEL' は既に存在します"
          fi
          
          # GitHub API (gh cli) でラベルを付与
          gh pr edit $PR_NUMBER --repo $REPO --add-label "$LABEL"

Commit ファイルをリポジトリのメインブランチにプッシュした後、新しい PR を作成すると自動的に GitHub Actions がトリガーされます:

Action の実行状態が Queued となっている場合、タスクが Runner に引き継がれて実行されるのを待っていることを意味します。

実行結果

Demo PR

Demo PR

実行が完了し成功すると、PR に対応するラベルが自動で付けられます!記録には github-actions がラベル付けしたことが表示されます。

完全なコード: Automation-PullRequest.yml

他人が作成した Action を直接使用する手順: pascalgn/size-label-action

前述で他人が作成したActionを直接使えると説明しましたが、PRのサイズラベルを付けるタスクにはすでに使えるツールがあります。上記はあくまで教育目的であり、実際には自分で作り直す必要はありません。

Action Workflow Job Step 内で直接使用するだけでタスクを完了できます:

# Workflow(Action) 名称
name: Pull Reqeust Automation

# トリガーイベント
on:
  # PRイベント
  pull_request:
    # PR - オープン、再オープン、新しいPushコミットがある時
    types: [opened, synchronize, reopened]


# 同じConcurrency Group内で新しいJobが開始されると、実行中のJobはキャンセルされる
# 例えばPushコミットでトリガーされたジョブがまだ実行されていないのに、さらにPushコミットがある場合、前のジョブをキャンセルする
concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}
  cancel-in-progress: true

# Jobの作業項目
# Jobは並行して実行される
jobs:
  # Job ID
  label-pr-by-file-count:
    # Job名(省略可能。ログ表示のために設定推奨)
    name: Label PR by changes file count

    # このJobが失敗してもWorkflow全体には影響せず、他のJobを続行する
    continue-on-error: true
    
    # 最大タイムアウト時間を設定し、異常時の無限待機を防ぐ
    timeout-minutes: 10

    # Runnerラベル - GitHubホストランナーのubuntu-latestを使用して作業を実行
    # Privateリポジトリの場合は使用量がカウントされ、超過すると費用が発生する可能性あり
    runs-on: ubuntu-latest

    # 作業ステップ
    # ステップは順番に実行される
    steps:
      # ステップ名
      - name: Get changed file count and apply label
        # ステップID(省略可能。後続のステップでOutputを参照しない場合は不要)
        id: get-changed-files-count-by-gh
        # 他者が作成したアクションを直接利用
        uses: "pascalgn/[email protected]"
        # 外部環境パラメータを実行時に注入
        # パラメータ名や使用可能なパラメータは説明を参照:https://github.com/pascalgn/size-label-action/tree/main
        env:
          # secrets.GITHUB_TOKENはGitHub Actions実行時に自動生成されるトークン(github-actionsのID)、Secretsに自分で設定不要で、GitHubリポジトリのAPIスコープ権限を持つ
          # https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-guides/use-github_token-in-workflows
          GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

このパッケージ化された Action は JavaScript Action であり、実際の実行コードは以下のファイルを参照してください: dist/index.js

name, run-name の補足:

  • name: Action Workflow の名前

  • run-name: 実行ログのタイトル名(PRタイトルやブランチ、作成者などを指定可能)
    on: pull_request イベントの場合、デフォルトは PR タイトルです。

ケース — 2

Pull Request を作成後、Assignee が設定されていなければ自動的に作成者を Assign し、コメントで通知します。(初回作成時のみ実行)

成果図

Demo PR

Demo PR

動作の流れ

  • ユーザーがPRを作成する

  • GitHub Actions Workflowをトリガーする

  • github scriptでassigneeを取得する方法

  • アサインがない場合、PRの作成者をアサインしてコメントを追加する

  • 完了

実践してみよう

Repo → Actions → New workflow → 自分でワークフローを設定する。

ファイル名: Automation-PullRequest.yml (同上)

Automation-PullRequest.yml

# Workflow(Action) 名称
name: Pull Reqeust Automation

# Actions ログのタイトル名
run-name: "Pull Reqeust Automation - Daily Checker"

# トリガーイベント
on:
  # PR イベント
  pull_request:
    # PR - オープン、再オープン、新しい Push Commit 時
    types: [opened, synchronize, reopened]


# 同じ Concurrency Group に新しい Job がある場合、実行中のものをキャンセル
# 例えば Push Commit トリガーのジョブがまだ実行されていないのに再度 Push Commit された場合、前のジョブをキャンセル
concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}
  cancel-in-progress: true

# Job 作業項目
# Job は並行実行される
jobs:
  # Job ID
  label-pr-by-file-count:
    # 前文を参照、略....
  # ---------
  assign-self-if-no-assignee:
    name: Automatically assign to self if no assignee is specified
    # 共通トリガーのため、Job 内で自分で判定。Pull Request Opened(初回作成)のみ実行し、それ以外はスキップ
    if: github.event_name == 'pull_request' && github.event.action == 'opened'

    # この Job が失敗しても Workflow 全体に影響せず、他の Job を継続
    continue-on-error: true
    
    # 最長タイムアウト時間を設定し、異常時の無限待機を防止
    timeout-minutes: 10
    
    # Runner Label - GitHub Hosted Runner の ubuntu-latest を使用して作業を実行
    # Private Repo の場合は使用量が計算され、超過すると料金が発生する可能性あり
    runs-on: ubuntu-latest

    steps:
      - name: Assign self if No Assignee
        # GitHub Script (JavaScript) でスクリプト作成 (Node.js 環境)
        # 直接 Shell Script より便利で見やすい
        # 環境変数や GITHUB_TOKEN の注入も不要
        uses: actions/github-script@v7
        with:
          script: \\|
            // github-script では context 変数が自動的に注入され、JavaScript で直接参照可能
            // https://docs.github.com/en/actions/learn-github-actions/contexts#github-context

            const issue = context.payload.pull_request; // Issue も対応する場合は context.payload.issue \\|\\| context.payload.pull_request と記述可能
            const assignees = issue.assignees \\|\\| [];
            const me = context.actor;

            if (assignees.length === 0) {
              // Assignee を自分に設定
              await github.rest.issues.addAssignees({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: issue.number,
                assignees: [me]
              });

              // コメントで通知
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: issue.number,
                body: `🔧 No assignee was set, so I have assigned this to myself (@${me}).`
              });
            }

今回は GitHub Script(JavaScript)でスクリプトを作成する方法を示します。コードの文法がより柔軟で書きやすくなります。

もちろん、もし前に言ったように各タスクを一つのファイルにしたい場合は、Job の If 条件を外して、直接 Action Workflow のトリガー条件を設定すれば良いです:

Automation-PullRequest-Auto-Assign.yml

# ワークフロー(アクション)名
name: Pull Request Automation - Auto Assignee Self

# トリガーイベント
on:
  # PRイベント
  pull_request:
    # PRがオープンされたとき
    types: [opened]

jobs:
  assign-self-if-no-assignee:
    name: 担当者が指定されていない場合に自動で自分を割り当てる
    runs-on: ubuntu-latest
    steps:
      # 前述を参照、省略....

Commit ファイルをリポジトリのメインブランチにプッシュした後、新しい PR を作成すると自動的に GitHub Actions がトリガーされます:

現在、2つのジョブを実行します!

実行結果

Demo PR

Demo PR

実行完了かつ成功後、PRにAssigneesがいない場合、自動的にPRの作成者をAssignし、コメントを投稿します。(すべて github-actions の権限で操作)

完全なコード: Automation-PullRequest.yml

テスト再オープン(Reopened) PR

Size Label Job のみが実行され、Auto Assignee Job はスキップされました。

このタスクもすでにActionとしてパッケージ化されており、そのまま再利用可能です。参考:pozil/auto-assign-issue

ケース — 3

毎朝9時に現在のPR数と開いてからの経過時間を自動集計し、Slackの作業チームに通知メッセージを送信、さらに3ヶ月以上開いているPRを自動でクローズします。

成果図

  • Slack ワークグループに毎朝自動でレポートを送信する

  • 90日を超えたPRを自動でクローズする

動作の流れ

  • GitHub Actions は毎朝9時に自動でトリガーされる

  • GitHub Actions Workflow のトリガー

  • github script で開いている PR の一覧を取得し、開いてからの日数を集計する方法

  • Slack に統計レポートメッセージを送信する

  • 90日以上経過したPRをクローズする

  • 完了

実践してみよう

Repo → Actions → New workflow → 自分でワークフローを設定する。

ファイル名: Automation-PullRequest-Daily.yml

Automation-PullRequest-Daily.yml

# ワークフロー(Action)名
name: Pull Reqeust Automation - Daily Checker

# トリガーイベント
on:
  # スケジュールによる定期実行
  # https://crontab.guru/
  # UTC時間
  schedule:
    # UTCの01:00 = 毎日UTC+8の09:00
    - cron: '0 1 * * *'
  # 手動トリガー
  workflow_dispatch:

# ジョブ
# ジョブは並行実行される
jobs:
  # ジョブID
  caculate-pr-status:
    # ジョブ名(省略可、ログ表示で読みやすくなる)
    name: Caculate PR Status
    # ランナーラベル - GitHubホステッドランナー ubuntu-latestで実行
    # プライベートリポジトリの場合は使用量が計算され、超過すると料金が発生する可能性あり
    runs-on: ubuntu-latest

    # ジョブ出力
    outputs:
      pr_list: ${{ steps.pr-info.outputs.pr_list }}

    # ステップ
    # ステップは順番に実行される
    steps:
      # ステップ名
      - name: Fetch open PRs and caculate
        # ステップの外部からOutputを参照するために設定が必要
        id: pr-info
        uses: actions/github-script@v7
        with:
          script: \\|
            const now = new Date();
            const per_page = 100;
            let page = 1;
            let allPRs = [];
      
            while (true) {
              const { data: prs } = await github.rest.pulls.list({
                owner: context.repo.owner,
                repo: context.repo.repo,
                state: 'open',
                per_page,
                page,
              });
              if (prs.length === 0) break;
              allPRs = allPRs.concat(prs);
              if (prs.length < per_page) break;
              page++;
            }
      
            const result = allPRs.map(pr => {
              const created = new Date(pr.created_at);
              const daysOpen = Math.floor((now - created) / (1000 * 60 * 60 * 24));
              return {
                pr: pr.number.toString(),
                title: pr.title,
                idle: daysOpen
              };
            });

            // Outputに設定、文字列のみ受け付ける
            core.setOutput('pr_list', JSON.stringify(result));
  # ----
  send-pr-summary-message-to-slack:
    name: Send PR Summary Messag to Slack
    # ジョブはデフォルトで並行実行、needsを使うと指定したジョブ完了まで待機する
    needs: [caculate-pr-status]
    runs-on: ubuntu-latest
    
    steps:
      - name: Generate Message
        # ステップの外部からOutputを参照するために設定が必要
        id: gen-msg
        uses: actions/github-script@v7
        with:
          script: \\|
            const prList = JSON.parse(`${{ needs.caculate-pr-status.outputs.pr_list }}`);
            const blocks = [];
      
            // タイトル
            blocks.push({
              type: "section",
              text: {
                type: "mrkdwn",
                text: `📬 *Open PR Report*\nTotal: *${prList.length}* PR(s)`
              }
            });
      
            // 各PRを1行ずつ
            for (const pr of prList) {
              blocks.push({
                type: "section",
                text: {
                  type: "mrkdwn",
                  text: `• <https://github.com/${context.repo.owner}/${context.repo.repo}/pull/${pr.pr}\\|PR #${pr.pr}> *${pr.title}* - 🕒 ${pr.idle} day(s)`
                }
              });
            }

            // Outputに設定、文字列のみ受け付ける
            core.setOutput('blocks', JSON.stringify(blocks));

            
      # Slack公式のSlack API GitHub Actionsを使用
      # https://tools.slack.dev/slack-github-action/sending-techniques/sending-data-slack-api-method/
      # メッセージ送信
      - name: Post text to a Slack channel
        uses: slackapi/[email protected]
        with:
          method: chat.postMessage
          token: ${{ secrets.SLACK_BOT_TOKEN }}
          payload: \\|
            channel: ${{ vars.SLACK_TEAM_CHANNEL_ID }}
            blocks: ${{ steps.gen-msg.outputs.blocks }}
  # ----
  auto-close-old-prs:
    name: Auto Close Old PRs
    needs: [caculate-pr-status]
    runs-on: ubuntu-latest

    steps:
      - name: Auto close PRs opened more than 90 days
        uses: actions/github-script@v7
        with:
          script: \\|
            const prList = JSON.parse(`${{ needs.caculate-pr-status.outputs.pr_list }}`);
            const oldPRs = prList.filter(pr => pr.idle > 90);

            for (const pr of oldPRs) {
              await github.rest.pulls.update({
                owner: context.repo.owner,
                repo: context.repo.repo,
                pull_number: parseInt(pr.pr),
                state: 'closed'
              });

              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: parseInt(pr.pr),
                body: `⚠️ This pull request has been automatically closed because it has been open for more than 90 days. Please reopen if needed.`
              });
            }
            console.log(`Closed ${oldPRs.length} PR(s)`);

この例では以下を使用します:

  • on: schedule
    Crontab スケジュールによる自動トリガーと workflow_dispatch による手動トリガーのサポート

  • Job output/Step output(どちらも文字列のみ)

  • 複数のジョブはデフォルトで並行実行されますが、needs を使って依存関係を設定し、順番待ちが可能です。

  • Repo Secrets/Variables から設定を取得する

  • Slack API の連携

Repo Secrets — 新規追加 SLACK_BOT_TOKEN

  • Slack App の作成およびメッセージ送信権限の設定については、私の以前の記事を参照してください。

Repo Variables — 新規追加 SLACK_TEAM_CHANNEL_ID

Commit をファイルをリポジトリのメインブランチにプッシュした後、Actions に戻って手動でトリガーしてみましょう:

今後は毎日自動的にトリガーされます。

Actions → プルリクエスト自動化 — デイリーチェッカー → ワークフローを実行 → ブランチ: main → ワークフローを実行。

実行後にクリックして実行状況を確認できます:

needs の制約があるため、ジョブの流れはまず Calculate PR Status が完了し、その後に Auto Close Old PRsSend PR Summary Message to Slack が並行して実行されます。

実行結果

タスクがすべて成功した後に Slack メッセージを確認できます:

成功 🚀🚀🚀

完全なコード: Automation-PullRequest-Daily.yml

小結

上記の3つの事例が、GitHub Actions の基本的な理解に役立ち、自動化ワークフローのアイデアを刺激できれば幸いです。まずは トリガーイベント を必ず参照し、自分でワークフローを考案してスクリプトを作成してください。また、Marketplace で既存のアクションを探し、既存の資源を活用することも忘れないでください。

本記事は入門基礎(コードのチェックアウトすら行いません)に過ぎません。次回の記事「CI/CD 実践ガイド(三):GitHub Actions を使ったアプリプロジェクトの CI と CD ワークフローの実装」では、より複雑で深い GitHub Actions Workflow を紹介します。

GitHub 自動化の拡張トピック

GitHub は Slack と連携でき、リポジトリの PR 更新通知やデフォルトブランチへのプッシュ通知などを購読できます。

問題 1. GitHub メッセージで人をタグ付けできず、GitHub アカウントのみがタグ付けされ、Slack アカウントに通知が届かない:

Slack App または Apps で GitHub を検索 → メッセージウィンドウを開く → Connect GitHub Account の手順を完了してください。これにより、GitHub が対応するあなたの Slack UID を認識し、メンションできるようになります。

問題 2. プルリクエストリマインダー

これはもともとサードパーティ製の機能だったのですが、後に直接 GitHub に統合されました。わざわざ自分で GitHub Actions を使ってスクリプトを書く必要はありません!

GitHub の組み込み機能はチーム単位の設定です。まず組織にチームを追加し、リポジトリをチームに割り当ててからプルリクエストリマインダーを設定する必要があります。

  • Slack チャンネルとルールを入力して保存すると、毎日自動でリマインドメッセージが送信され、Reviewer にタグ付けされます。設定方法はこちらを参照してください。結果は以下の通りです:

<https://michaelheap.com/github-scheduled-reminders/>

https://michaelheap.com/github-scheduled-reminders/

問題 4. GitHub PR メッセージ、作者が通知を受け取れない:

これは長年の問題であり、PR の Slack メッセージで 実は作者が誰かをスレッドで返信していることを知らない という点です。GitHub が送るこの Slack メッセージは Reviewers のみをタグ付けし、Assignee や PR 作者はタグ付けされません。

この時、GitHub Actions を使った自動化ツールが役立ちます。PR Description の最後に作者を追記する Job を追加すれば、そのメッセージが Slack に送られる際にも作者がタグ付けされます:

# Workflow(Action) 名称
name: Pull Reqeust Automation
# 前述を参照、省略....

  # ---------
  append-author-to-pr-description:
    name: PR説明に作成者を追記
    # 共有トリガーイベントのため、Job内で判定し、Pull RequestがOpened(初回作成)の場合のみ実行。そうでなければスキップ
    if: github.event_name == 'pull_request' && github.event.action == 'opened'
    
    # このJobが失敗してもWorkflow全体には影響せず、他のJobを継続
    continue-on-error: true
    
    # 最大タイムアウト時間を設定し、異常時の無限待機を防止
    timeout-minutes: 10
    
    # Runnerラベル - GitHubホストランナー ubuntu-latest を使用して実行
    # Privateリポジトリの場合は使用量が計算され、超過すると費用が発生する可能性あり
    # ただしこのような小規模な自動化作業で過剰使用は稀
    runs-on: ubuntu-latest
    steps:
      - name: PR説明に作成者を追記
        env:
          # secrets.GITHUB_TOKEN はGitHub Actions実行時に自動生成されるトークンで、Secretsに設定不要。GitHubリポジトリAPIの一部スコープ権限を持つ
          # gh(GitHub) CLIはGH_TOKENをENVに注入する必要があり、ghに操作権限を付与
          # https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-guides/use-github_token-in-workflows
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

          #   ${{ github.xxx }} はGitHub ActionsのContext式
          #   シェル変数ではなく、YAML解析時にGitHub Actionsが対応する値に置換
          #   その他パラメータ:https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
          PR_NUMBER: ${{ github.event.pull_request.number }}
          AUTHOR_TAG: '@${{ github.event.pull_request.user.login }}'
        run: \\|
          PR_BODY=$(gh pr view $PR_NUMBER --repo ${{ github.repository }} --json body -q ".body")
          NEW_BODY=$(printf "%s\n\nCreated by %s" "$PR_BODY" "$AUTHOR_TAG")
          gh pr edit $PR_NUMBER --repo ${{ github.repository }} --body "$NEW_BODY"

完全なコード: Automation-PullRequest.yml

PR を作成すると、自動的に説明の後に作成者情報が追加され、Slack メッセージにも正しく作成者がタグ付けされます:

問題 5. GitHub が大量に通知メールを送ってきて非常に迷惑:

GitHub リポジトリの通知と Slack の設定が完了したら、通知はすべて Slack に集約されます。GitHub の個人設定でメール通知をオフにしましょう:

その他

Actions は現在ディレクトリ構造を持っていませんが、Actions ページで最大5つの Actions をピン留めできます。また、Disable を使って特定の Action を一時停止することも可能です。

Insights で GitHub Actions の使用量と実行結果を確認できます:

Reuse Workflow 補足

次の記事では同じリポジトリ内でWorkflowを分割して再利用していますが、ここでは異なるリポジトリ間でWorkflowを分割して再利用する例を補足します。

先に Reuse Workflow を定義します: Automation-label-pr-base-branch.yml: ZhgChgLi/github-actions-ci-cd-demo-share-actions リポジトリ内

name: Automation-label-pr-base-branch
run-name: "[Automation-label-pr-base-branch] ${{ github.event.inputs.PR_NUMBER \\|\\| github.ref }}"

concurrency:
  group: ${{ github.workflow }}-${{ github.event.inputs.PR_NUMBER \\|\\| github.ref }}
  cancel-in-progress: true

# トリガーイベント
on:
  # 他のWorkflowからこのWorkflowを呼び出しトリガー
  workflow_call:
    # 入力データ
    inputs:
      # PR番号
      PR_NUMBER:
        required: true
        type: string
    # シークレット入力
    secrets:
      GH_TOKEN:
        description: "APIアクセス用のGitHubトークン"
        required: true
jobs:
  label-pr-base-branch:
    name: PRのベースブランチにラベル付け
    continue-on-error: true
    timeout-minutes: 10
    runs-on: ubuntu-latest

    steps:
      - name: ベースブランチでPRにラベル付け
        id: label-pr-base-branch
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: \\|
          PR_NUMBER="${{ inputs.PR_NUMBER }}"
          REPO="${{ github.repository }}"

          echo "📦 リポジトリ $REPO のPR #$PR_NUMBER を処理中"

          # PRのベースブランチを取得
          BASE_BRANCH=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json baseRefName --jq '.baseRefName')
          echo "🔖 PRのベースブランチ: $BASE_BRANCH"

          # 許可されたベースブランチのラベル
          BRANCH_LABELS=("develop" "main" "master")

          # ラベルが許可リストにあるか確認
          if [[ " ${BRANCH_LABELS[@]} " =~ " ${BASE_BRANCH} " ]]; then
            LABEL="$BASE_BRANCH"
          else
            echo "⚠️ ベースブランチ '$BASE_BRANCH' は許可リストにありません。ラベル付けをスキップします。"
            exit 0
          fi

          # 既存のベースブランチラベルを削除
          EXISTING_LABELS=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json labels --jq '.labels[].name')
          for EXISTING in $EXISTING_LABELS; do
            if [[ " ${BRANCH_LABELS[@]} " =~ " ${EXISTING} " ]]; then
              echo "🧹 既存のベースブランチラベルを削除中: $EXISTING"
              gh pr edit "$PR_NUMBER" --repo "$REPO" --remove-label "$EXISTING"
            fi
          done

          # ラベルが存在しなければ作成
          if ! gh label list --repo "$REPO" \\| grep -q "^$LABEL$"; then
            echo "🆕 ラベルを新規作成: $LABEL"
            gh label create "$LABEL" --repo "$REPO" --description "PR targeting $LABEL branch" --color "ededed"
          else
            echo "✅ ラベル '$LABEL' は既に存在します"
          fi

          # ベースブランチラベルを追加
          echo "🏷️ PR #$PR_NUMBER にラベル '$LABEL' を追加"
          gh pr edit "$PR_NUMBER" --repo "$REPO" --add-label "$LABEL"

元の メイン ZhgChgLi/github-actions-ci-cd-demo リポジトリに戻り、 新たにメインの Workflow Demo-Reuse-Action-From-Another-Repo.yml を追加します:

name: Automation-label-pr-base-branch

on:
  pull_request:
    types: [opened]

jobs:
  call-label-pr-workflow:
    name: ベースブランチラベルワークフローを呼び出す
    # ✅ 呼び出されるワークフローが同じリポジトリ内の場合:
    #    uses: ./.github/workflows/Automation-label-pr-base-branch.yml
    #    注意:この書き方ではブランチを指定できません(呼び出し元ワークフローのブランチが固定で使用されます)
    #    参考例:
    #    - CD-Deploy-Form.yml が同リポジトリのワークフローを呼び出す
    #    - CD-Deploy.yml が別リポジトリのワークフローを呼び出す
    #
    # ✅ 呼び出されるワークフローが別リポジトリの場合:
    #    uses: {owner}/{repo}/.github/workflows/{file}.yml@{branch_or_tag}
    #    ブランチまたはタグを指定可能
    #    ref: https://github.com/ZhgChgLi/github-actions-ci-cd-demo-share-actions/blob/main/.github/workflows/Automation-label-pr-base-branch.yml
    uses: ZhgChgLi/github-actions-ci-cd-demo-share-actions/.github/workflows/Automation-label-pr-base-branch.yml@main
    with:
      PR_NUMBER: ${{ github.event.number }}
      
    # すべての secrets を呼び出し元ワークフローから継承する場合は、直接 `inherit` を使う
    # secrets: inherit
    #
    # 特定の secrets のみ渡す場合は個別に指定
    secrets:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

コミットしたファイルでPRを試してみてください。

ワークフロー:メインリポジトリの Demo-Reuse-Action-From-Another-Repo.yml → 別のリポジトリの Automation-label-pr-base-branch.yml を呼び出し、パラメータを渡してタスクを実行 → メインリポジトリに戻って結果を報告。

成功!

Private Repo アクセス問題

ここで注意すべきセキュリティの問題があります。Publicリポジトリなら全く問題ありませんが、Privateリポジトリ内でReuse Workflowを使う場合は、同じ組織内の他のリポジトリであること + そのリポジトリもPrivateであること + 設定で許可されていることが揃って初めてPrivateリポジトリのReuse Workflowにアクセスして使用できます。

  • Public — ZhgChgLi/ReuseRepo + Private or Public-ZhgChgli or harry/myRepo = ✅

  • Private — ZhgChgLi/ReuseRepo + Private or Public— harry/myRepo = ❌

  • Private — ZhgChgLi/ReuseRepo + Public — ZhgChgLi/anotherRepo = ❌

  • Private — ZhgChgLi/ReuseRepo + Private — ZhgChgLi/anotherRepo = ✅

許可されている方法の設定は、Reuse Workflow Repo → Settings → Actions → General → Access で行います:

チェックを変更:

'ZhgChgLi' 組織内のリポジトリからアクセス可能
'ZhgChgLi' 組織に属する他のリポジトリのワークフローは、このリポジトリ内のアクションや再利用可能なワークフローにアクセスできます。アクセスはプライベートリポジトリからのみ許可されます。

「保存」をクリックして、完了です。

権限がない場合、以下のエラーメッセージが表示されます:

無効なワークフローファイル: .github/workflows/Demo-reuse-action-from-another-repo.yml#L21
呼び出されたワークフローの解析エラー
".github/workflows/Demo-reuse-action-from-another-repo.yml"
-> "ZhgChgLi/github-actions-ci-cd-demo-share-actions/.github/workflows/Automation-label-pr-base-branch.yml@main"
: ワークフローが見つかりません。

GitHub Actions Script 文法補足

補足として、皆さんが疑問に感じるかもしれない ${{ XXX }}${XXX}$XXX の変数構文について。

Demo-Env-Vars-.yml:

name: Demo-Env-Vars

on:
  workflow_dispatch:

jobs:
  print-env-vars:
    runs-on: ubuntu-latest

    steps:
      - name: print-env
        run: env
      - name: context-vars-vs-vars
        env:
          SAY_MY_NAME: "Heisenberg"
          # GitHub Actions Context 式、下記説明と同じ
          FROM_REF: "${{ github.ref }}"
        run: \\|
          # シェルスクリプト:
          # カスタム注入された ENV 変数の参照
          # ${SAY_MY_NAME} または $SAY_MY_NAME どちらも可
          # ${XX} は文字列結合時に便利:
          echo "HI: ${SAY_MY_NAME}"
          
          # 💡 GitHub Actions Context 式
          # これはシェル変数ではなく、YAML解析時に GitHub Actions が対応する値に置換
          # ⚠ ローカルや GitHub Actions 以外の環境では失敗する
          # 🔗 https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
          BRANCH_NAME_FROM_CONTEXT="${{ github.ref }}"
          
          # 💡 GitHub Actions 実行時の環境変数
          # これらは runner がシェル実行時に GitHub Actions から自動注入される
          # ✅ 他環境では export や ENV で同じ変数を事前定義すれば利用可
          # 🔗 https://docs.github.com/en/actions/learn-github-actions/environment-variables
          BRANCH_NAME_FROM_ENV_VARS="${GITHUB_REF}"
          
          echo "FROM_REF: ${FROM_REF}"
          echo "BRANCH_NAME_FROM_CONTEXT: ${BRANCH_NAME_FROM_CONTEXT}"
          echo "BRANCH_NAME_FROM_ENV_VARS: ${BRANCH_NAME_FROM_ENV_VARS}"
      - name: print-github-script-env
        uses: actions/github-script@v7
        env:
          SAY_MY_NAME: "Heisenberg"
          # GitHub Actions Context 式、上記説明と同じ
          FROM_REF: "${{ github.ref }}"
        with:
          script: \\|
            // GitHub Script: (JavaScript (Node.js)):
            // process.env から ENV 値を取得
            console.log(`HI: ${process.env.SAY_MY_NAME}`);
            console.log(`FROM_REF: ${process.env.FROM_REF}`);
            // github-script では context 変数に自動注入されて JS から直接参照可能
            // https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
            const branch_name_from_context_vars = context.ref;
            console.log(`branch_name_from_context_vars: ${branch_name_from_context_vars}`);
            // 同様に GitHub Actions Context 式も使用可(意味はあまりない):
            const branch_name_from_context = "${{ github.ref }}";
            console.log(`branch_name_from_context: ${branch_name_from_context}`);
            
            for (const [key, value] of Object.entries(process.env)) {
              console.log(`${key}=${value}`);
            }
            // github-script 内の github オブジェクトは Octokit REST API のインスタンス
            // GitHub API 操作用
            // 例:
            // await github.rest.pulls.list({
            //   owner: context.repo.owner,
            //   repo: context.repo.repo,
            //   state: "open"
            //  });
      # gh CLI はデフォルトで GITHUB_TOKEN を使用しない;GH_TOKEN が必要
      - name: gh CLI without GH_TOKEN (expected to fail)
        continue-on-error: true
        run: \\|
          PR_COUNT=$(gh pr list --repo $GITHUB_REPOSITORY --json number --jq 'length')
          echo "Found $PR_COUNT open pull requests"
      
      - name: gh CLI with GH_TOKEN (expected to succeed)
        env:
          # GH_TOKEN を割り当てて gh CLI の認証を可能にする
          # https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-guides/use-github_token-in-workflows
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: \\|
          PR_COUNT=$(gh pr list --repo $GITHUB_REPOSITORY --json number --jq 'length')
          echo "Found $PR_COUNT open pull requests"
      
      - name: github-script auto-authentication (no GH_TOKEN needed)
        uses: actions/github-script@v7
        with:
          script: \\|
            // github = Octokit REST クライアント(GITHUB_TOKEN で自動認証済み)
            const pulls = await github.rest.pulls.list({
              owner: context.repo.owner,
              repo: context.repo.repo,
              state: "open"
            });
            console.log(`Found ${pulls.data.length} open pull requests`);

${{ XXX }} GitHub Actions コンテキスト式

  • ${{ XXX }} は GitHub Actions のコンテキスト式で、YAML の解析時に GitHub Actions によって対応する値に置き換えられます(上図の BRANCH_NAME_FROM_CONTEXT のように)

  • 利用可能な式の全リストは公式ドキュメントを参照してください。

  • ${XXX}$XXX は実際の環境変数で、${XXX} は文字列連結時に便利です。

  • GitHub Actions はデフォルトでいくつかの環境変数を注入します。詳細は公式ドキュメントをご覧ください。

actions/github-script@v7

  • github-script でも ${{ XXX }} は GitHub Actions のコンテキスト式として使えますが、あまり意味はありません。なぜなら github-script 環境ではデフォルトで context 変数が注入されており、JavaScript 内で直接参照できるからです

  • github-script では、github オブジェクトは Octokit REST API のインスタンスです。

  • github-scriptenv: から変数を注入できますが、${process.env.xxx} を使ってアクセスする必要があります。

  • 同様にいくつかの環境変数がデフォルトで注入されます。詳細は公式ドキュメントを参照してください。${process.env.xxx}でアクセス可能ですが、すでに context 変数があるため、ここからアクセスする意味はあまりありません。

  • github-script は自動的に github-token を渡すため、指定する必要はありません。

gh cli GH_TOKEN

  • GitHub Actions の Shell Script で gh cli を使用する場合、GH_TOKEN パラメータを指定する必要があります

  • デフォルトのトークン — secrets.GITHUB_TOKEN を直接使用可能(GitHub Actions 実行時に自動生成)

  • デフォルトのトークンには基本的な権限がありますが、操作するコマンドの権限が不足している場合は Personal Access Token を使用してください。チームでの利用は共有アカウントでPATを作成することを推奨します。

完全なコード: Demo-Env-Vars-.yml

GitHub Actions はすでに開発が完了しているので、次のステップとして GitHub Hosted Runner を自分の Self-hosted Runner に置き換えることができます。

GitHub Hosted Runner には月間2,000分の無料枠があります。小さな自動化タスクならそれほど時間がかからず、Linuxマシン上で動作するためコストも低く、無料枠を使い切らないことも多いです。必ずしも Self-hosted Runner に変更する必要はありません。Runner を変更する場合は環境を正しく整える必要があります(例えば、GitHub Hosted Runner には gh cli が標準搭載されていますが、Self-hosted Runner では自分でインストールする必要があります)。この記事ではあくまで教育目的で切り替えを行っています

CI/CD タスクを実行する場合にのみ、Self-hosted Runner を使用する必要があります。

Self-hosted Runner の追加

本文は macOS M1 を例にしています。

  • 設定 → Actions → Runners → 新しいセルフホストランナー。

  • Runner image: macOS

  • Architecture : M1 は ARM64 を選択すると実行が速くなります

実機のコンピュータでターミナルを開く。

Download 手順に従ってローカルPCで完了させる:

# 任意のパスに Runner ディレクトリを作成
mkdir actions-runner && cd actions-runner
# Runner イメージをダウンロード
curl -o actions-runner-osx-x64-2.325.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.325.0/actions-runner-osx-x64-2.325.0.tar.gz

# 任意: ハッシュの検証
echo "0562bd934b27ca0c6d8a357df00809fbc7b4d5524d4aeb6ec152e14fd520a4c3  actions-runner-osx-x64-2.325.0.tar.gz" \\| shasum -a 256 -c

# 解凍
tar xzf ./actions-runner-osx-x64-2.325.0.tar.gz

以上はあくまで参考です;最新の Runner イメージを使用するには、設定ページの手順に従うことをお勧めします。

設定 Configure:

# 設定ページのコマンドを参照してください。Tokenは時間とともに変わります
./config.sh --url https://github.com/ORG/REPO --token XXX

順番に入力してください:

  • ランナーを追加するランナーグループの名前を入力してください:[EnterキーでDefault] そのままEnterを押す
    *ランナーグループの機能は、Organizationレベルで登録されたランナーのみ利用可能です

  • Enter the name of runner: [press Enter for ZhgChgLideMacBook-Pro] 設定したいランナー名を入力してください。例: app-runner-1 またはそのまま Enter を押してください

  • This runner will have the following labels: ‘self-hosted’, ‘macOS’, ‘X64’
    Enter any additional labels (ex. label-1,label-2): [press Enter to skip]
    設定したい Runner のラベルを入力してください。複数のカスタムラベルを入力して後で使いやすくできます
    前述の通り、GitHub Actions/Runner は対応するラベルに基づいてジョブを取得します。デフォルトのラベルだけを使うと、組織内の他の Runner がジョブを実行する場合があるため、カスタムラベルを設定するのが安全です
    ここでは自分で適当に self-hosted-zhgchgli というラベルを設定しました。

  • 作業フォルダ名を入力してください: [Enterで_work] そのままEnterを押す

√ Settings Saved. が表示されたら、設定が完了したことを意味します。

Runnerの起動:

./run.sh

出現 √ Connected to GitHub、Listening for Jobs と表示されていれば、すでに Actions のジョブを監視しています:

このターミナルウィンドウを閉じない限り、タスクの受信を続けます。

🚀🚀🚀同じパソコンで複数のターミナルを開き、異なるディレクトリで複数のランナーを起動できます。

リポジトリ設定ページに戻ると、Runnerがタスクを待機しているのが確認できます:

ステータス:

  • Idle: アイドル状態、タスク待機中

  • Active: タスク実行中

  • Offline: ランナーがオフラインです

Workflow(GitHub Actions) Runner を Self-hosted Runner に変更する

Automation-PullRequest.yml を例にすると:

# 前述を参照してください、省略....
jobs:
  label-pr-by-file-count:
    # 前述を参照してください、省略....
    runs-on: [self-hosted-zhgchgli]
    # 前述を参照してください、省略....
  # ---------
  assign-self-if-no-assignee:
    # 前述を参照してください、省略....
    runs-on: [self-hosted-zhgchgli]

    steps:
      # 前述を参照してください、省略....

Commit ファイルをリポジトリのメインブランチにプッシュした後、PR を再度作成して Actions をトリガーし、検証します。

Runner のターミナルに戻ると、新しいジョブが来ているのが確認でき、実行中および実行結果が表示されます:

失敗しました。なぜなら、私のローカルPCに gh cli 環境がインストールされていないからです:

brew install gh を使って実機に gh をインストールした後、再度実行をトリガーする:

成功しました!これでこのジョブは完全に自分たちのコンピュータ上で実行され、GitHub Hosted Runner は使わず、料金も発生しません。

Action Log でジョブが実行された Runner やマシンを確認できます:

runs-on: [ Runner Label] 設定

ここは AND です。GitHub Runner は現時点で OR による Runner 選択をサポートしていません。

例えば、[self-hosted, macOS, app] → Runner が 同時に self-hosted, macOS, app の3つのラベルを持っている場合にのみ、ジョブを受け取って実行します。

もし一つの Job で異なる Runner 環境の結果を同時にテストしたい場合は、matrix パラメータを使用できます:

jobs:
  test:
    runs-on: ${{ matrix.runner }}
    strategy:
      matrix:
        runner:
          - [self-hosted, linux, high-memory]
          - [self-hosted, macos, xcode-15]

    steps:
      - run: echo "Running on ${{ matrix.runner }}"

こうすると、このジョブは以下の2つの Runner Labels の Runner 上で並行してそれぞれ1回ずつ実行されます:

  • self-hosted、linux、高メモリ

  • self-hosted、macos、xcode-15

Runner はまだサポートしていません:

- または Runner の選択

- Runner の重み付け設定

Runner をサービスとして登録する

公式ドキュメント「 Configuring the self-hosted runner application as a service 」を参照して、Runner を直接システムサービスとして登録できます。これにより、バックグラウンドで実行され(Terminal を開かずに前景での操作不要)、起動時に自動的に開始されます。

複数の Runner がある場合は、「 Customizing the self-hosted runner service 」の設定で異なる名前を登録するようにしてください。

iOSで現在調査中の問題があります。バックグラウンドサービスに変更してからアーカイブ時にエラーが発生しており(キーチェーンの権限が関係している疑いがあります)、当時は時間がなく、先にフォアグラウンドのターミナルランナーを使用しました。

もし従来のフォアグラウンドアプリを起動時に自動起動させたい場合は、~/Library/LaunchAgents に自動起動設定ファイルを追加する必要があります:

actions.runner.REPO.RUNNER_NAME.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
 <dict>
  <key>Label</key>
  <string>actions.runner.REPO.RUNNER_NAME</string>
  <!-- Terminal.app を指定して起動 -->
  <key>ProgramArguments</key>
  <array>
   <string>/usr/bin/open</string>
   <string>-a</string>
   <string>Terminal</string>
   <string>/Users/zhgchgli/Documents/actions-runner/run.sh</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>WorkingDirectory</key>
  <string>/Users/zhgchgli/Documents/actions-runner</string>
 </dict>
</plist>

DevOps に興味がある方は、公式の k8s Runner ドキュメントをご覧ください。

完全なプロジェクトリポジトリ

公式ドキュメント

より詳細な設定方法については、必ず公式マニュアルを参照してください。

AIが助けになる!

実際にChatGPTに完全な手順とタイミングを伝えるだけで、GitHub Actionsをすぐに作成してくれます!

まとめ

今、GitHub Actions と Self-hosted Runner についてある程度理解できたと思います。次回は App(iOS)CI/CD をケースとして、一連のプロセスを手取り足取り構築していきます。

シリーズ記事:

🍺 Buy me a beer on PayPal

本シリーズの記事は多くの時間と労力をかけて執筆しています。内容がお役に立ち、チームの作業効率や製品品質の向上に貢献できた場合は、ぜひコーヒーをご馳走ください。ご支援ありがとうございます!

Buy me a coffee

🍺 Buy me a beer on PayPal

Post MediumからZMediumToMarkdownを使って変換。

GitHub で編集
この記事を改善
本記事は Medium で初公開
オリジナルを読む
この記事をシェア
リンクをコピー · SNS でシェア
ZhgChgLi
著者

ZhgChgLi

An iOS, web, and automation developer from Taiwan 🇹🇼 who also loves sharing, traveling, and writing.

コメント