😺

GitHub ActionsのセルフホステッドランナーをAzure Container Appsで構成する(GitHub Apps認証編)

2024/12/20に公開

0. はじめに

GitHub ActionsはデフォルトでGitHubが提供するホストランナー上でワークフローを実行できますが、カスタム環境や機密情報を独自の環境下で扱いたい場合、自前のセルフホステッドランナーを用意することができます。

本記事では、Azure Container Apps 上にセルフホステッドランナーを構成し、GitHub Actionsと連携する手順をご紹介します。GitHub Apps認証を利用してAzure Container AppsのジョブとGitHubへの接続を確立している部分に注目してください。
2024/12/09時点、明確な手順を示すドキュメントが存在していないため苦労しました…

1. 前提条件

  • Azure アカウントがあること(無料アカウントでOK)
  • Azure CLI がローカル環境にインストールされていること

https://learn.microsoft.com/ja-jp/cli/azure/install-azure-cli

2. 本記事で利用するAzureコンポーネント

コンポーネント 概要
Resource Group Azure上でリソースをまとめるためのグループ
Azure Container Registry (ACR) コンテナイメージを保存・管理するプライベートレジストリ
Azure Container Apps (ACA) サーバーレスなコンテナ実行環境
Log Analytics Azure Monitorに統合されたログ管理・分析サービス
Azure Key Vault 機密情報(シークレット、鍵、証明書など)を安全に保存・管理するサービス
Managed Identity AzureリソースがIDやパスワードなしで安全に他のAzureサービスにアクセスするための仕組み

3. 事前準備

Azure Container Appsを操作するには、最新版のAzure CLIおよびその拡張機能が必要です。
以下のコマンドを実行してCLIと拡張機能を最新版へ更新します。

# Azure CLI を最新バージョンへ
az upgrade

# Azure Container Apps拡張機能を追加または更新
az extension add --name containerapp --upgrade

最新バージョンの拡張機能またはモジュールがインストールされたら、Microsoft.App および Microsoft.OperationalInsights 名前空間を登録します。

az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights

4. セットアップ全体の流れ

GitHub Appsの作成から、Azureリソースの作成まで、ステップバイステップで記載します。

4.1. ワークフローを実行するためのGitHubリポジトリを作成

  • セキュリティ上、パブリックよりプライベートを推奨

ワークフローを実行するには、ワークフローの定義を含むGitHubリポジトリを作成する必要があります。適当にGitHubリポジトリをプライベートで作成してください。

4.2. GitHub Appsの作成(Github App ID、Installation ID、秘密鍵の取得)

前提となる Github App を作成し、後続で作成するコンテナアプリジョブで必要となる各設定値を取得します。

4.2.1. GitHub Appの作成

https://docs.github.com/ja/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation

GitHub Appsは、画面右側のアイコンよりSettingsをクリックし、Developer settingsをクリックします。
https://github.com/settings/profile

GitHub AppsのNew GitHub Appをクリックします。

[Register new GitHub App] (全般) に次の値を入力します。

設定
GitHub App name 任意の値
Homepage URL https://localhost/
Webhook Active チェックOff

[Repository permissions] (リポジトリのアクセス許可) に次の値を入力します。

設定
Actions Read-only
Administraton Read and write
Metadata Read-only

[Organization permissions] (組織のアクセス許可) に次の値を入力します。

設定
Self-hosted runners Read and write

Create GitHub App をクリックします。

4.2.2. App IDの取得

作成された App IDを控えておき、(※後続で利用します)、Install App をクリックします。

4.2.3. Installation IDの取得

Installをクリックします。

GitHub Appが接続するリポジトリを選択し、Installをクリックします。

URL部に表示されている、Installation IDを控えておきます。(※後続で利用します)

4.2.4. 秘密鍵の作成

画面上部のApp Settingsをクリックします。

Private keysより、Generate a private keyをクリックします。

.pemファイルをダウンロードするためのダイアログが表示されるため、任意のフォルダへダウンロードします。
※ここではC:\Workとしています。

4.3. GitHub Appsを利用するセルフホステッド用スクリプトを定義

Microsoftの公式Selfhostedチュートリアル用リポジトリは、PAT認証をベースとしているため、Github App認証を利用するようスクリプトを編集します。

4.3.1. Microsoftの公式チュートリアル用リポジトリをフォーク

下記Githubリポジトリをフォークします。

https://github.com/Azure-Samples/container-apps-ci-cd-runner-tutorial/tree/main/github-actions-runner

ご自身のGitHub環境で、Forkをクリックします。

4.3.2. GitHub Apps認証を利用するようにスクリプトを編集

フォークしたリポジトリにおいて、entrypoint.sh を下記のように編集します。
サンプルコードは以下の各 GitHub Docs を参考として作成したものとなります。念の為、変更前後を記載します。

https://docs.github.com/ja/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app#example-using-bash-to-generate-a-jwt

https://docs.github.com/ja/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation#using-an-installation-access-token-to-authenticate-as-an-app-installation

github-actions-runner/entrypoint.sh 変更前

#!/bin/sh -l

# Retrieve a short lived runner registration token using the PAT
REGISTRATION_TOKEN="$(curl -X POST -fsSL \
  -H 'Accept: application/vnd.github.v3+json' \
  -H "Authorization: Bearer $GITHUB_PAT" \
  -H 'X-GitHub-Api-Version: 2022-11-28' \
  "$REGISTRATION_TOKEN_API_URL" \
  | jq -r '.token')"

./config.sh --url $GH_URL --token $REGISTRATION_TOKEN --unattended --ephemeral && ./run.sh
github-actions-runner/entrypoint.sh 変更後
#!/bin/bash -l

# 以下の環境変数を利用しますが、これらの環境変数は後述の手順で container app ジョブに設定します。
# $PEM_KEY
# $GITHUB_APP_ID
# $GITHUB_OWNER
# $REPOS

# GitHub App の秘密鍵の内容をファイルに書き込み利用していきます。
echo "$PEM_KEY" > github_app_private_key.pem

# 下記 Github Docs に記載の bash スクリプトを参考に jwt を取得します。
# https://docs.github.com/ja/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app#example-using-bash-to-generate-a-jwt

set -o pipefail
client_id=$GITHUB_APP_ID # Client ID as first argument

now=$(date +%s)
iat=$((${now} - 60)) # Issues 60 seconds in the past
exp=$((${now} + 600)) # Expires 10 minutes in the future

b64enc() { openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'; }

header_json='{
    "typ":"JWT",
    "alg":"RS256"
}'
# Header encode
header=$( echo -n "${header_json}" | b64enc )

payload_json='{
    "iat":'"${iat}"',
    "exp":'"${exp}"',
    "iss":'"${client_id}"'
}'
# Payload encode
payload=$( echo -n "${payload_json}" | b64enc )

# Signature
header_payload="${header}"."${payload}"
signature=$(
    openssl dgst -sha256 -sign ./github_app_private_key.pem \
    <(echo -n "${header_payload}") | b64enc
)

# Create JWT
jwt="${header_payload}"."${signature}"
printf '%s\n' "JWT: $jwt"

# 取得した jwt トークンを利用して Github App トークンを取得します。下記 Github Docs に記載の REST API を利用しています。
# https://docs.github.com/ja/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation#using-an-installation-access-token-to-authenticate-as-an-app-installation

installation_id="$(curl --location --silent --request GET \
  --url "https://api.github.com/users/$GITHUB_OWNER/installation" \
  --header "Accept: application/vnd.github+json" \
  --header "X-GitHub-Api-Version: 2022-11-28" \
  --header "Authorization: Bearer $jwt" \
  | jq -r '.id'
)"

echo "installation_id is: $installation_id"

token="$(curl --location --silent --request POST \
  --url "https://api.github.com/app/installations/$installation_id/access_tokens" \
  --header "Accept: application/vnd.github+json" \
  --header "X-GitHub-Api-Version: 2022-11-28" \
  --header "Authorization: Bearer $jwt" \
  | jq -r '.token'
)"

echo "token is: $token"

registration_token="$(curl -X POST -fsSL \
  -H 'Accept: application/vnd.github.v3+json' \
  -H "Authorization: Bearer $token" \
  -H 'X-GitHub-Api-Version: 2022-11-28' \
  "https://api.github.com/repos/$GITHUB_OWNER/$GITHUB_REPO/actions/runners/registration-token" \
  | jq -r '.token')"
echo "registration_token is: $registration_token"
echo "url is: https://github.com/$GITHUB_OWNER/$GITHUB_REPO"

# 取得したトークンでGithubリポジトリへアクセスします
./config.sh --url https://github.com/$GITHUB_OWNER/$GITHUB_REPO --token $registration_token --unattended --ephemeral && ./run.sh

こんな感じです。

4.4. 必要なAzureリソースを作成

ここからは、PowerShellのAzure CLIを利用してコンポーネントを作成していきます。
変数は下記を前提としますが、ご自身の環境の値に差し替えてください。

手順で利用する変数
$SUBSCRIPTION_ID="<SUBSCRIPTION_ID>"
$RESOURCE_GROUP="rg-github-selfhosted-001"
$LOCATION="japaneast"
$ENVIRONMENT="cae-github-selfhosted-001"
$JOB_NAME="ca-job-github-selfhosted-001"
$REPO_OWNER="<REPO_OWNER>"
$REPO_NAME="<REPO_OWNER>>"
$CONTAINER_IMAGE_NAME="github-actions-runner:1.0"
$CONTAINER_REGISTRY_NAME="acrselfhosted01"
$KEY_VAULT_NAME="kvselfhosted01"
$MANAGED_IDENTITY_NAME="id-selfhosted-01"
$MANAGED_IDENTITY_ID="/subscriptions/$SUBSCRIPTION_ID/resourcegroups/$RESOURCE_GROUP/providers/microsoft.managedidentity/userassignedidentities/$MANAGED_IDENTITY_NAME"
$GITHUB_APP_ID="<GITHUB_APP_ID>"
$GITHUB_APP_INSTALL_ID="<GITHUB_APP_INSTALL_ID>"
$KEY_NAME="pemsecret"
$KEY_VAULT_SECRET_URI="https://$KEY_VAULT_NAME.vault.azure.net/secrets/$KEY_NAME"

プレースホルダーを次の値に置き換えます。

プレースホルダー
<SUBSCRIPTION_ID> 対象のサブスクリプションID
<REPO_OWNER> [4.1]で作成したリポジトリの所有者。通常、この値は GitHub のユーザー名です
<REPO_NAME> [4.1]で作成したリポジトリの名前。これは [Repository name] フィールドに入力した名前と同じです
<GITHUB_APP_ID> [4.2.2]で取得したApp ID
<GITHUB_APP_INSTALL_ID> [4.2.3]で取得したInstallation ID

下記でそれぞれAzureコンポーネントを作成します
※Log AnalyticsはACAを作成した際に自動的に作成されます

# リソースグループの作成
az group create --name "$RESOURCE_GROUP" --location "$LOCATION"

# Azure Container Apps 環境の作成
az containerapp env create --name "$ENVIRONMENT" --resource-group "$RESOURCE_GROUP" --location "$LOCATION"

# Azure Container Registryの作成
az acr create --name "$CONTAINER_REGISTRY_NAME" --resource-group "$RESOURCE_GROUP" --location "$LOCATION" --sku Basic --admin-enabled true

# Azure KeyVaultの作成
az keyvault create --name "$KEY_VAULT_NAME" --resource-group "$RESOURCE_GROUP" --location "$LOCATION"

# Managed Identityの作成
az identity create --name $MANAGED_IDENTITY_NAME --resource-group "$RESOURCE_GROUP" --location "$LOCATION"

4.5. Azureリソースの権限設定

Key Vault、Managed ID、Azure Container Appsの権限設定を行います。

4.5.1. Key Valut&Managed Identityの権限設定

[4.4]で作成したKey Vaultを開き、アクセス制御(IAM)より下記を追加します。

職務ロール 対象
キーコンテナ管理者 ご自身のアカウント
キーコンテナ シークレットユーザ [4.4]で作成した、Managed Identity

4.5.2. Azure Container AppsのマネージドID化

[4.4]で作成したContainer Apps環境を開き、ID>ユーザー割り当て済み>追加より、
作成したManaged IDを選択し追加をクリックします。

4.6. Key Vault への Github Apps 秘密鍵のアップロード

[4.3.2]で定義したスクリプトで利用する Github App の秘密鍵をあらかじめ Key Vault にアップロードし、Container App ジョブが参照できるようにします。

az keyvault secret set `
--vault-name "$KEY_VAULT_NAME" `
--name "$KEY_NAME" `
--file "<[4.2.4]でダウンロードした `.pem` ファイル>"

4.7. セルフホステッドランナー用のコンテナイメージをビルド

次のコマンドを実行してリポジトリをクローンし、クラウドで az acr build コマンドを使ってコンテナー イメージを構築します。

az acr build `
--registry "$CONTAINER_REGISTRY_NAME" `
--image "$CONTAINER_IMAGE_NAME" `
--file "Dockerfile.github" "<[4.3.1]でフォークした github リポジトリの URL>"

4.8. Github App を利用する Container App ジョブの作成

コンテナレジストリへプッシュしたイメージをAzure Container Appsに適用し、セルフホステッドランナー用のコンテナを起動します。

az containerapp job create `
-n "$JOB_NAME" `
-g "$RESOURCE_GROUP" `
--environment "$ENVIRONMENT" `
--trigger-type Event `
--replica-timeout 1800 `
--replica-retry-limit 0 `
--replica-completion-count 1 `
--parallelism 1 `
--image "$CONTAINER_REGISTRY_NAME.azurecr.io/$CONTAINER_IMAGE_NAME" `
--min-executions 0 `
--max-executions 10 `
--polling-interval 30 `
--scale-rule-name "github-runner" `
--scale-rule-type "github-runner" `
--scale-rule-metadata "githubAPIURL=https://api.github.com" "owner=$REPO_OWNER" "runnerScope=repo" "repos=$REPO_NAME" "targetWorkflowQueueLength=1" "applicationID=$GITHUB_APP_ID" "installationID=$GITHUB_APP_INSTALL_ID" `
--scale-rule-auth "appKey=$KEY_NAME" `
--cpu "2.0" `
--memory "4Gi" `
--user-assigned "$MANAGED_IDENTITY_ID" `
--secrets "$KEY_NAME=keyvaultref:$KEY_VAULT_SECRET_URI,identityref:$MANAGED_IDENTITY_ID" `
--env-vars "PEM_KEY=secretref:$KEY_NAME" "GITHUB_APP_ID=$GITHUB_APP_ID" "GITHUB_OWNER=$REPO_OWNER" "GITHUB_REPO=$REPO_NAME" `
--registry-server "$CONTAINER_REGISTRY_NAME.azurecr.io"

上記で、Github Apps を利用したセルフホステッドランナーの Container App ジョブが作成されます。

https://keda.sh/docs/2.13/scalers/github-runner/#trigger-specification

4.9. ワークフローを編集し動作確認

GitHubリポジトリの .github/workflows/ 配下に以下のようなワークフローを作成します。runs-on: self-hosted を指定することで、先ほど構築したAzure Container Apps上のセルフホステッドランナーでジョブが実行されます。

name: CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

  workflow_dispatch:

jobs:
  build:
    # >>> ここを変更
    #runs-on: ubuntu-latest
    runs-on: self-hosted
    # <<< ここを変更

    steps:
      - uses: actions/checkout@v4

      - name: Run a one-line script
        run: echo Hello, world!

      - name: Run a multi-line script
        run: |
          echo Add other actions to build,
          echo test, and deploy your project.

これでmainブランチへのpushやPR作成時に、Azure上のセルフホステッドランナーがトリガーされてジョブを実行するようになります。

4.7. Azure Container Appsでのステータス確認

以下のコマンドで、Container Apps上で稼働しているコンテナの状態を確認できます。

az containerapp job execution list --name "$JOB_NAME" --resource-group "$RESOURCE_GROUP" --output table --query "[].{Status: properties.status, Name: name, StartTime: properties.startTime}"

ログアナリティクスを用いて実行ログやエラー情報を確認することができます。

まとめ

本記事では、GitHub Apps認証を用いたセルフホステッドランナー環境をAzure Container Apps上に構築する流れを解説しました。主なポイントを纏めてみました。

  • セルフホステッドランナーのメリット:標準のGitHub提供ホストランナーでは難しい、独自環境での実行や機密情報のセキュアな取り扱いが可能となります。
  • GitHub Apps認証の利点
    • 設定・実装面ではPAT(Personal Access Token)と比較して手順がやや複雑です。
    • 一方で、PATのように定期的なトークン更新や有効期限切れを気にする必要が無く、長期的な運用時の手間を削減できます。
  • Azure Container AppsとKEDAによるスケールアウト:KEDAスケーラーを利用することで、ワークフローのキュー長に応じてセルフホステッドランナーを自動的にスケールアウトでき、効率的なCI/CD環境を構築可能です。
  • Key Vault・Managed Identityによるセキュリティ強化
    • Key Vaultで秘密鍵やシークレットを安全に管理
    • Managed Identityを用いたAzureリソース間の安全なアクセス
      これらにより、機密情報管理の負荷を軽減し、よりセキュアな運用が可能となります。

これらの要素を組み合わせることで、Azure上でより安定的かつセキュアなセルフホステッドランナー環境を実現できます。公式ドキュメントがまだ充実していない部分もありますが、本記事の手順やポイントを参考に、GitHub Appsを活用した運用コスト削減とスケーラビリティを両立したCI/CD基盤を目指してみてください。

リファレンス

https://azure.github.io/jpazpaas/2024/07/30/How-to-use-GithubApp-for-self-hosted-runner-on-ContainerApps-Job.html

GitHubで編集を提案

Discussion