はじめに
App Service や Azure Functions においてプライベートエンドポイントが有効にし、パブリックアクセスを無効にしている場合、デプロイ時には工夫が必要となります。
この記事では、Azure Container Apps Jobs を利用して、VNet 内で GitHub Actions セルフホステッドランナーを動かすことで、プライベートエンドポイントで保護されたリソース(App Service) にデプロイすることを試してみます。
TL;DR
- Container Apps Environment ではカスタム VNet を利用する(ターゲットとなる App Service の Private Endpoint にアクセスするため)
- GitHub の PAT のスコープはしっかり確認しましょう
- Container Apps Jobs 上で動かすイメージはチュートリアルのものだと物足りないので、自前で用意する必要アリ
GitHub Action Runner on Container Apps jobs
Azure Container Apps jobs is 何?
ざっくりいうとイベントドリブンで動作するコンテナ。その分コンピューティングリソースの使用量を抑えられる。
Container Apps としてデプロイできるので、VNet にアクセスすることが可能となり、GitHub Actions や、Azur DevOps などの、CI/CD パイプラインをホストすることが想定される利用シナリオとしても挙げられている。
Azure Container Apps には、アプリとジョブという 2 種類のコンピューティング リソースがあります。
アプリは継続的に実行されるサービスです。 アプリ内のコンテナーが失敗すると、自動的に再起動されます。 アプリの例としては、HTTP API、Web アプリ、入力を継続的に処理するバックグラウンド サービスなどがあります。
ジョブとは、開始され、一定時間実行され、完了すると終了されるタスクです。 1 つのジョブを実行するたびに、通常、1 つの作業単位が実行されます。 ジョブの実行は、手動、スケジュール、またはイベントに応答して開始されます。 ジョブの例としては、オンデマンドで実行されるバッチ プロセスやスケジュール タスクなどがあります。
従量課金 プランでは、Azure Container Apps ジョブによって消費されるリソースには、アクティブな料金が課金されます。 ジョブが完了すると実行によってリソースの消費が停止されるため、アイドル料金はジョブには適用されません。
手順
基本的には以下のチュートリアルに沿って構成することでOK
本記事では VNet から App Service の Private Endpoint に対してアクセスするため、Container Apps Environment にてカスタム VNet を利用する構成とした。
はまったポイント
Workflow がキューに作成されても Job コンテナが起動されない
原因としては、GitHub の PAT を作成する際に対象のリポジトリを選択する箇所がある。ここで今回ワークフローを実行したいリポジトリの選択を誤っていたことで、ワークフローがいつまでたっても実行されないことになっていた。
これはなぜかというと、今回チュートリアルで作成したジョブコンテナのスケーリングルールでは Github Runner Scaler を用いている、このスケーラーは何をやっているかというと、api.github.com
のリポジトリ系 API をインターバルに従ってキューの有無を監視、キューにアイテムがあればスケーリング(= Self hosted runner 用のコンテナの起動)といったことを実行している。
そのため、PAT が対象のリポジトリに対するアクセス権がないと、APIの応答が適切に得られず、スケーリングが発生しないという状態だった。
ContainerAppSystemLogs_CL
ではスケーリングが発生したときのログは出力されるが、何も起きていない状態ではログが残らないため、解決のためにはスケーラーのコードを見て呼び出してそうな API を確認、PAT を使って API を叩いてみるといった地道なデバッグが必要だった。
$ curl -L \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GHPAT" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/georgeOsdDev/ghaction-on-aca-jobs/actions/runs
{
"message": "Not Found",
"documentation_url": "https://docs.github.com/rest/actions/workflow-runs#list-workflow-runs-for-a-repository"
}
PAT の対象リポジトリを適切に設定すると、その PAT を使った API で応答が得られるようになる。
30秒のインターバルが設定されているので、PAT の設定変更から30秒以内にジョブの開始が確認できるはず。
--min-executions 0 \
--max-executions 10 \
--polling-interval 30 \
--scale-rule-name "github-runner" \
--scale-rule-type "github-runner" \
--scale-rule-metadata "github-runner=https://api.github.com" "owner=$REPO_OWNER" "runnerScope=repo" "repos=$REPO_NAME" "targetWorkflowQueueLength=1" \
--scale-rule-auth "personalAccessToken=personal-access-token" \
なお、--scale-rule-metadata
については、github-runner=https://api.github.com
という指定は間違っている気がするので以下の Issue をたてた。
ContainerAppSystemLogs_CL
では下記のような出力が確認できる。
Job 実行時に必要なツールが入っていない。
GitHub Hosted Runner の場合、開発で利用されることの多い一般的な言語やツール群がすでにインストールされている状態となっている。例えば Ubuntu 22.04
そのため、workflow 上で気軽に zip
とかが使えるが、詳しくは後述するがチュートリアルで用いているジョブの実行イメージは ghcr.io/actions/actions-runner
単体となり、workflow 上で必要なものが足りない場合がある。App Service によって生成された yaml に対してだと、zip
、unzip
、az cli
が足りなかった。詳しくは後述。
どういうしくみで動いているのか
Workflow Job のキュー監視
これは前述のとおり github-runner という KEDA Scaler がポーリングでキューアイテムの有無を確認している。
How does it work?
The scaler will query the GitHub API to get the number of queued jobs in the specified repositories, subject to filters. If the number of queued jobs is equal to or greater than the targetWorkflowQueueLength, the scaler will scale up.
そのため、下記の資料にあるような GitHub 側からの Webhook を必要としているわけではなく、GitHub リポジトリ側に Container Apps Jobs の情報を明示的に登録する必要はない。
起動するコンテナイメージは何?
KEDA によってスケールが発生したときに起動するイメージは何をやっているのかを見てみる。
チュートリアルに沿って利用したコンテナイメージは以下の通り
az acr build \
--registry "$CONTAINER_REGISTRY_NAME" \
--image "$CONTAINER_IMAGE_NAME" \
--file "Dockerfile.github" \
"https://github.com/Azure-Samples/container-apps-ci-cd-runner-tutorial.git"
ここで指定している Dockerfike
は以下のとおり
FROM ghcr.io/actions/actions-runner:2.304.0
# for latest release, see https://github.com/actions/runner/releases
USER root
# install curl and jq
RUN apt-get update && apt-get install -y curl jq && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY github-actions-runner/entrypoint.sh ./entrypoint.sh
RUN chmod +x ./entrypoint.sh
USER runner
ENTRYPOINT ["./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 $REPO_URL --token $REGISTRATION_TOKEN --unattended --ephemeral && ./run.sh
ghcr.io/actions/actions-runner:2.304.0
をベースイメージとして ./entrypoint.sh
を追加している。
ghcr.io/actions/actions-runner
は何かというと、GitHub が提供している ランナーの公式イメージです。
./entrypoint.sh
では、エフェメラル ランナーの登録を実行したのちに、Runner そのものを起動する ./run.sh
が実行されている。
エフェメラル ランナーの確認
通常時は以下の通り、Self-hosted runner は未登録
Container Apps Jobs が動作したタイミングで、以下のように追加されている。
エフェメラルランナーとして、Container Apps Jobs で動作するコンテナが登録されることで、そのタイミングでキューにあるワークフローがそのランナーで実行されることになる。
ghcr.io/actions/actions-runner のバージョン
Dockerfile
で指定されている 2.304.0
はちょっと古い(最新はv2.311.0
) 。latest
指定する必要はないのかなと思ったが、自動更新をよしなにやってくれる模様。
ContainerAppConsoleLogs_CL
からその様子を確認できる。
また、GitHub 上でも同じく最新版で動作したことを確認
通信経路
以下のとおり、GitHub から何かを受信するものではなく、セルフホステッドランナー側からアウトバウンド通信が発生する仕組みとなっている。
セルフホステッド ランナーが GitHub.com への接続を開くので、ユーザーはセルフホステッド ランナーへのインバウンド接続を行うことを GitHub に許可する必要はありません。
セルフホステッドランナーからのアウトバウンド通信は、上記ドキュメントに記載のように、github.com
、api.github.com
、*.actions.githubusercontent.com
などセルフホステッドランナーが通信できる必要がある FQDN が定義されている。すなわち、Container Apps Jobs をデプロイするサブネットの NSG アウトバウンドルールでは、Internet
向けの通信を許可しておく必要がある。
Private Endpoint で保護された App Service AppService のデプロイに使ってみる
前置きが長くなりましたが、実際に Private Endpoint で保護された App Service へのデプロイに利用してみます。
本題とは関係ないですが今ホットな Hono アプリをデプロイしてみました。
前提
以下の通り、パブリックアクセス無効でプライベートエンドポイントが有効。プライベートエンドポイントは先に作った Container Apps Environment の統合 VNet からアクセス可能な状態。
yaml
ファイルは Azure ポータルのデプロイセンター経由で自動生成されたものを利用します。
# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions
name: Build and deploy Node.js app to Azure Web App - webapp-deploy-test2023
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js version
uses: actions/setup-node@v3
with:
node-version: '20.x'
- name: npm install, build, and test
run: |
npm install
npm run build --if-present
npm run test --if-present
- name: Zip artifact for deployment
run: zip release.zip ./* -r
- name: Upload artifact for deployment job
uses: actions/upload-artifact@v3
with:
name: node-app
path: release.zip
deploy:
runs-on: self-hosted
needs: build
environment:
name: 'Production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
permissions:
id-token: write #This is required for requesting the JWT
steps:
- name: Download artifact from build job
uses: actions/download-artifact@v3
with:
name: node-app
- name: Unzip artifact for deployment
run: unzip release.zip
- name: Login to Azure
uses: azure/login@v1
with:
client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_070C1CDA7B1343E4AD320E57FBF78D5C }}
tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_82953456381644C1B354BD1E088C79BB }}
subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_994B5092010F4FC7AE0F13F292786610 }}
- name: 'Deploy to Azure Web App'
id: deploy-to-webapp
uses: azure/webapps-deploy@v2
with:
app-name: 'webapp-deploy-test2023'
slot-name: 'Production'
package: .
まずは、GitHub Hosted Runner で実行してみる。
想定通り 403 エラーでデプロイできない。
Container Apps Jobs 上の Self Hosted Runner から実行されるようにワークフローを変更する。
zip
コマンドがなくてエラー
しょうがないので zip
、unzip
を含むイメージを作成して 1.1 として ACR にプッシュ、ACA 側で参照タグを 1.1 に変更。
az
コマンドがなくてエラー
しょうがないので az
を含むイメージを作成して 1.2 として ACR にプッシュ、ACA 側で参照タグを 1.2 に変更
最終的に
最終的に以下のような Dockerfileとなった。
FROM ghcr.io/actions/actions-runner:2.304.0
# for latest release, see https://github.com/actions/runner/releases
USER root
# install curl and jq zip unzip, az-cli
RUN apt-get update && apt-get install -y curl jq zip unzip && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
curl -sL https://aka.ms/InstallAzureCLIDeb | bash
COPY entrypoint.sh ./entrypoint.sh
RUN chmod +x ./entrypoint.sh
USER runner
ENTRYPOINT ["./entrypoint.sh"]
無事デプロイ成功
ターゲットとなる App Service のデプロイセンターのログは以下の通り。
Private Endpoint につながる別環境からもHonoアプリがデプロイされていること確認
めでたしめでたし。