記事

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](https://unsplash.com/@theoneandonlydantaylor?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash){:target="_blank"}

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 → Settings → Secrets and variables → Actions → Secrets。

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

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

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

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

GitHub Repo — Actions Variables

Repo → Settings → 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 マージイベントがない場合は、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 を持つことができます

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

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

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

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

  • 特にワークフローの条件を設定していない場合、複数のジョブのうちどれかのジョブでエラーが発生しても、他のジョブは引き続き実行されます

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

  • Workflow 定義の on: workflow_call により、他の Workflow で Job として再利用できるようにパッケージ化できます。

  • 同じ組織内のリポジトリ間で共有可能です。

  • そのため、複数のリポジトリで使う CI/CD の作業を共通のリポジトリにまとめて利用できます。

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

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

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

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

  • 複数のステップは順番に実行されます

  • Step が完了したら、後続の Steps が参照できるように文字列を Output できます。

  • Step は直接シェルスクリプトを記述可能
    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 の処理ロジックを記述します。同様に環境変数も渡されて参照可能です。
    e.g. pozil/auto-assign-issue

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

文字数の関係で、本記事では GitHub Actions の Action のパッケージ化方法は紹介しません。興味がある方は公式ドキュメントをご参照ください: tutorials/creating-a-composite-action

GitHub Runner

  • GitHub は Runner のラベルに応じて対応するジョブを 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 リスト](https://github.com/actions/runner-images){:target="_blank"}

2025/06 の Images 一覧

  • Runner にあらかじめインストールされているものは、こちらから確認できます:
    e.g. macos-14-arm64

[macos-14-arm64](https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md){:target="_blank"}

macos-14-arm64

  • iOS 開発では -arm64 (Mシリーズ) プロセッサの Runner を優先して使用すると、実行速度が速くなります

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

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

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

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

[about-billing-for-github-actions](https://docs.github.com/en/billing/managing-billing-for-your-products/about-billing-for-github-actions){:target="_blank"}

about-billing-for-github-actions

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

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

[usage-limits-billing-and-administration](https://docs.github.com/en/actions/concepts/overview/usage-limits-billing-and-administration#usage-limits){:target="_blank"}

usage-limits-billing-and-administration

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

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

  • 自分のマシンを Runner として使う

  • 1台の物理マシンで複数のランナーを起動し、並行してタスクを処理できる

  • 無料で無制限に使用可能
    マシンの購入費用のみで、一度買えば使い放題!
    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つのWorkflowと2つのRunnerがある場合のワークフローと関係を図でまとめます。

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

  • CD — 4つのジョブがあり、各ジョブにはいくつかのステップがあります。Runnerラベル(run-on) — self-hosted-app

  • Runner — 2つあり、Runnerラベルも両方とも self-hosted-app です

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

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

⚠️ だからこそ、各 Job が独立していることが非常に重要です(特に Self-hosted Runner 環境では、Job 終了後に完全にクリーンアップされない場合があり、この Job の成果物を次の Job がそのまま使えるとは限りません)。

Job が出力を持ち、後続の Job に渡す場合:

  • Job Output String : 純粋なテキストを変数に出力して他のジョブで参照する

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

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

  • [Self-hosted Only] 共有ディスク、ディレクトリ: Self-hosted Runner が共用ディレクトリをマウントしている場合、このディレクトリ内に UUID ごとにフォルダを作成して成果物を保存できます。そして、後続の Job は前の Job の Output UUID を使って対応する保存場所を見つけて読み込むことが可能です。
    注意点として、異なるホストの Runner も同じ共用ディレクトリをマウントしている必要があります。

  • 同じ Job の Steps で全ての作業を完了させる。

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

「言うより行う」以上の名詞説明とプロセス構造の紹介は皆さんも少し曖昧に感じたかもしれません。これからは実際の3つの機能例を挙げて、実際に手を動かしながら説明し、作業を通して学び、GitHub Actionsとは何かを理解していきます。

ケース — 1

Pull Request 作成後にファイル変更サイズのラベルを自動で付けて、レビュアーがレビュー作業を効率的に計画できるようにします。

成果図

[Demo PR](https://github.com/ZhgChgLi/github-actions-ci-cd-demo/pull/11){:target="_blank"}

Demo PR

動作の流れ

  • ユーザーがPRを作成、再オープン、PRに新しいコミットをプッシュする

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

  • shellスクリプトで変更ファイル数を取得する方法

  • 変更数に基づいてPRラベルを付与する

  • 完了

実践する

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

ファイル名: Automation-PullRequest.yml

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

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

Automation-PullRequest.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# 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コミット時
    types: [opened, synchronize, reopened]


# 同じ Concurrency Group に新しい 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 Hosted Runner ubuntu-latest を使用して作業を実行
    # Privateリポジトリの場合は使用量に応じて課金される可能性あり
    runs-on: ubuntu-latest

    # 作業ステップ
    # ステップは順番に実行される
    steps:
      # ステップ名
      - name: Get changed file count and apply label
        # ステップID(省略可、出力を参照しない場合は不要)
        id: get-changed-files-count-by-gh
        # 外部環境パラメータを実行時に注入
        env:
          # secrets.GITHUB_TOKEN はGitHub Actions実行時に自動生成されるトークンで、Secretsに設定不要。GitHubリポジトリ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を環境変数に注入しないと操作権限がない
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        # Shellスクリプト
        # GitHub Hosted Runner には gh CLI が標準インストールされており、追加インストール不要で使用可能
        run: \\|
          #   ${{ github.xxx }} はGitHub Actionsのコンテキスト式
          #   シェル変数ではなく、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](https://github.com/ZhgChgLi/github-actions-ci-cd-demo/pull/11){:target="_blank"}

Demo PR

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

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

他人が作成した Action を直接使用するステップ: pascalgn/size-label-action

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# Workflow(Action) 名称
name: Pull Reqeust Automation

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


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

# ジョブ
# ジョブは並行して実行される
jobs:
  # ジョブID
  label-pr-by-file-count:
    # ジョブ名(省略可、ログ表示でわかりやすいように設定)
    name: Label PR by changes file count

    # このジョブが失敗してもワークフロー全体には影響せず、他のジョブは継続する
    continue-on-error: true
    
    # 最大タイムアウト時間を設定し、異常時の無限待機を防止
    timeout-minutes: 10

    # ランナーラベル - GitHub Hosted Runner の ubuntu-latest を使用してジョブを実行
    # プライベートリポジトリの場合は使用量が計算され、超過すると料金が発生する可能性あり
    runs-on: ubuntu-latest

    # ステップ
    # ステップは順番に実行される
    steps:
      # ステップ名
      - name: Get changed file count and apply label
        # ステップID(省略可、後続のステップで出力を参照しない場合は不要)
        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 の権限)、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 が設定されていない場合は自動的に作成者を割り当て、コメントで通知します。(初回作成時のみ実行)

成果図

[Demo PR](https://github.com/ZhgChgLi/github-actions-ci-cd-demo/pull/11){:target="_blank"}

Demo PR

動作の流れ

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

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

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

  • もしアサインされていなければ、PRを作成した作者にアサインし、コメントを投稿する

  • 完了

実践してみよう

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

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

Automation-PullRequest.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 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 ラベル - 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ワークフロー(Action) 名称
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](https://github.com/ZhgChgLi/github-actions-ci-cd-demo/pull/11){:target="_blank"}

Demo PR

実行が完了し成功した後、PR に Asignees がいなければ自動的に PR 作成者をアサインし、コメントを投稿します。(すべて 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 ワークフローのトリガー

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

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

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

  • 完了

ハンズオン

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

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

Automation-PullRequest-Daily.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# Workflow(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:

# Job 作業項目
# Jobは並行実行される
jobs:
  # Job ID
  caculate-pr-status:
    # Job 名称(省略可能、ログ表示が読みやすくなる)
    name: Caculate PR Status
    # Runnerラベル - GitHub Hosted Runner ubuntu-latest を使用して作業を実行
    # Private Repoの場合は使用量が計算され、超過すると費用が発生する可能性あり
    runs-on: ubuntu-latest

    # Job 出力
    outputs:
      pr_list: ${{ steps.pr-info.outputs.pr_list }}

    # 作業ステップ
    # ステップは順番に実行される
    steps:
      # ステップ名
      - name: Fetch open PRs and caculate
        # Step外部で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
    # Jobはデフォルトで並行実行、needsを使うと指定Job完了後に実行可能
    needs: [caculate-pr-status]
    runs-on: ubuntu-latest
    
    steps:
      - name: Generate Message
        # Step外部で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 クロンタブによる自動トリガーと 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 で既存のアクションを探し、巨人の肩に乗って活用することもお忘れなく。

本記事は入門基礎のみ(コードの Checkout すら行いません)、次回の記事「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/>{:target="_blank"}

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 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 Hosted Runnerのubuntu-latestで実行
    # Privateリポジトリの場合は使用量が計算され、超過すると費用が発生する可能性あり
    # ただしこのような小規模自動化は過剰使用になりにくい
    runs-on: ubuntu-latest
    steps:
      - name: PR説明に作成者を追加
        env:
          # secrets.GITHUB_TOKENはGitHub Actions実行時に自動生成されるTokenで、Secretsに設定不要。GitHubリポジトリAPIの一部スコープ権限を持つ
          # gh(GitHub) CLIはGH_TOKENを環境変数に注入する必要があり、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のコンテキスト式
          # Shell変数ではなく、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 をピン留め(Pin)できます。また、Disable を使って特定の Action を一時停止することも可能です。

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

Reuse Workflow 補足

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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が対象とする$LABELブランチ" --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 を追加します:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
name: Automation-label-pr-base-branch

on:
  pull_request:
    types: [opened]

jobs:
  call-label-pr-workflow:
    name: Call Base Branch Label Workflow
    # ✅ 呼び出される workflow が同じリポジトリ内の場合:
    #    uses: ./.github/workflows/Automation-label-pr-base-branch.yml
    #    注意:この書き方はブランチを指定できず(呼び出し元 workflow のブランチを固定使用)
    #    参考例:
    #    - CD-Deploy-Form.yml は本リポジトリの workflow を呼び出し
    #    - CD-Deploy.yml は別リポジトリの workflow を呼び出し
    #
    # ✅ 呼び出される workflow が別リポジトリの場合:
    #    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 を呼び出し元 workflow から継承する場合は `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 に移動してください:

チェックを変更:

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

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

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

1
2
3
4
5
無効なワークフローファイル: .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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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: \\|
          # Shell スクリプト:
          # カスタム注入された 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 がシェル実行時に自動注入
          # ✅ 他環境では 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 のシェルスクリプトで 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 を例にしています。

  • Settings → Actions → Runners → New self-hosted runner。

  • Runner image: macOS

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

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

Download 手順に従ってローカルコンピュータで完了する:

1
2
3
4
5
6
7
8
9
10
# 任意のパスに 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:

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

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

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

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

  • このランナーには次のラベルが付与されます: ‘self-hosted’, ‘macOS’, ‘X64’
    追加のラベルを入力してください(例: label-1,label-2): [Enterキーでスキップ]
    設定したいランナーのラベルを入力してください。複数のカスタムラベルを設定すると後で便利です。
    前述の通り、GitHub Actions/Runnerは対応するラベルに基づいてジョブを取得します。デフォルトラベルのみを使うと、組織内の他のランナーがジョブを実行する可能性があるため、カスタムラベルを設定するのが安全です。
    ここでは自分で適当に self-hosted-zhgchgli というラベルを設定しました。

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

√ Settings Saved. が表示されたら、設定は完了です。

ランナーの起動:

1
./run.sh

√ Connected to GitHub、Listening for Jobs が表示されている場合は、すでに Actions のジョブを監視しています:

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

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

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

ステータス:

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

  • Active: タスクが実行中です

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

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

Automation-PullRequest.yml を例に:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 前述を参照してください、省略....
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 を実機にインストールした後、再度実行をトリガーする:

成功しました!これでこのジョブは完全に自分のPCで実行され、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 パラメータを使用できます:

1
2
3
4
5
6
7
8
9
10
11
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 ラベルの Runner 上で並行してそれぞれ1回ずつ実行されます:

  • self-hosted、linux、高メモリ

  • self-hosted、macos、xcode-15

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

- または Runner の選択

- Runner の重み付け設定

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

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

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

iOS こちらで調査が必要な問題があります。バックグラウンドサービスに切り替えた後、Archive 時にエラーが発生します(キーチェーン権限に関連している疑いがあります)。当時は時間がなかったため、先にフォアグラウンドの Terminal Runner を使いました。

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

actions.runner.REPO.RUNNER_NAME.plist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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 beer on PayPal

🍺 Buy me a beer on PayPal

ご質問やご意見がありましたら、ぜひこちらからご連絡ください

Post MediumからZMediumToMarkdownで変換。


🍺 Buy me a beer on PayPal

👉👉👉 Follow Me On Medium! (1,053+ Followers) 👈👈👈

本記事は Medium にて初公開されました(こちらからオリジナル版を確認)。ZMediumToMarkdown による自動変換・同期技術を使用しています。

Improve this page on Github.

本記事は著者により CC BY 4.0 に基づき公開されています。