ごった煮

色々な事を書いてます

Azure Container Apps で Azure Pipeline の Self Hosted Agents を構築する - その2

前回は、エージェントのイメージを ACR へ登録するところまで進めました。

今回は、実際に Container Apps へエージェントを展開して、ビルドが出来るところまで進めます。

前回の記事はこちら ↓ papemk2.hateblo.jp

その2 で行う事

  • Azure DevOps の設定をする
  • Container Apps をデプロイする

Azure DevOps の設定をする

Agent Pool を登録する

エージェントを動かすために、最初に Azure DevOps に新しい Agent Pool を作成します。

Agent Pool の登録は、Organization Settings > Agent pools です。

Agent pools の画面から、Add pool を選択し、登録用のペインを表示します。

Pool type は、Self-hosted を選択し、画面の表示に従って登録します。

Create ボタンで Agent pool を作成すると、新しい Agent pool が一覧に表示されるので、それをクリックします。

後程必要になるので、次の2つを控えておきます。

  • Agent pool 名
  • URL (URL に含まれる poolId の値を後程使います)

Personal Access Token を作成する

画面右上の User settings > Personal access tokens を開きます。

New Token を選択すると、Personal access token の作成ペインが表示されます。

必要な権限を選択して、トークンを作成します。(この画面は、Preview 機能有効化の有無などで、若干変わる可能性があります。ソースコード、ビルド関連の権限を割り当てます)

デフォルトでは、30日間になっていますが、実際に運用する際は、長めにしておかないと急にトークンが切れてビルドが回らないといった事があるので、気を付けましょう。

トークンは、作成してすぐにコピーします。(これ以降、見えなくなるので、ここで保存します)

これで、Azure DevOps での設定は完了です。

Container Apps をデプロイする

ここは、bicep でサクッと作成します。

Container Apps を作成するのに最低限必要なリソースは、次になります。

  • Log Analytics Workspace
  • Container Apps Environment
  • Container Apps
  • bicep
param location string = resourceGroup().location

param tag string
param repository string

param devopsUrl string
param devopsPAT string
param devopsPoolName string
param devopsPoolId string

// create log analytics workspace
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = {
  name: '{Log Analytics Workspace 名}'
  location: location
  properties: any({
    retentionInDays: 30
    features: {
      searchVersion: 1
    }
    sku: {
      name: 'PerGB2018'
    }
  })
}

// create container apps environment
resource environment 'Microsoft.App/managedEnvironments@2022-03-01' = {
  name: '{Container Apps Environment 名}'
  location: location
  properties: {
    appLogsConfiguration: {
      destination: 'log-analytics'
      logAnalyticsConfiguration: {
        customerId: reference(logAnalyticsWorkspace.id, '2021-06-01').customerId
        sharedKey: listKeys(logAnalyticsWorkspace.id, '2021-06-01').primarySharedKey
      }
    }
  }
}

// get acr
resource acr 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = {
  name: '{ACR 名}'
}

// create container apps and deploy image
resource containerApp 'Microsoft.App/containerApps@2022-03-01' = {
  name: '{Container Apps 名}'
  location: location
  properties: {
    managedEnvironmentId: environment.id
    configuration: {
      registries: [
        {
          server: acr.properties.loginServer
          username: acr.name
          passwordSecretRef: 'container-registry-password'
        }
      ]
      secrets: [
        {
          name: 'container-registry-password'
          value: acr.listCredentials().passwords[0].value
        }
        {
          name: 'azp-pat'
          value: devopsPAT
        }
        {
          name: 'azp-url'
          value: devopsUrl
        }
      ]
    }
    template: {
      containers: [
        {
          image: '${acr.name}.azurecr.io/${repository}:${tag}'
          name: repository
          resources: {
            cpu: json('0.25')
            memory: '0.5Gi'
          }
          env: [
            {
              name: 'AZP_URL'
              value: devopsUrl
            }
            {
              name: 'AZP_TOKEN'
              value: devopsPAT
            }
            {
              name: 'AZP_POOL'
              value: devopsPoolName
            }
            {
              name: 'AZP_POOL_ID'
              value: devopsPoolId
            }
          ]
        }
      ]
      scale: {
        minReplicas: 1
        maxReplicas: 10
        rules: [
          {
            name: 'azure-pipeline-queue-rule'
            custom: {
              type: 'azure-pipelines'
              metadata: {
                poolID: devopsPoolId
                targetPipelinesQueueLength: '1'
                poolName: devopsPoolName

              }
              auth: [
                {
                  triggerParameter: 'personalAccessToken'
                  secretRef: 'azp-pat'
                }
                {
                  triggerParameter: 'organizationURL'
                  secretRef: 'azp-url'
                }
              ]
            }
          }
        ]
      }
    }
  }
}

パラメータは、それぞれ次の通りです

パラメータ名 用途
tag コンテナイメージのタグ
repository ACR に登録されているパッケージ名
devopsUrl 自身の Azure DevOps の URL
devopsPAT 先ほど作成した Personal Access Token
devopsPoolName 先ほど作成した Agent pool 名
devopsPoolId 先ほど作成した Agent pool Id
  • コマンド
az deployment group create \
--resource-group {リソースグループ名} 
--template-file {bicep ファイルパス} \
--parameters tag={イメージのタグ名} \
--parameters repository={ACR のパッケージ名} \
--parameters devopsUrl={Azure DevOps の URL} \
--parameters devopsPAT={Personal Access Token} \
--parameters devopsPoolName={Agent pool 名} \
--parameters devopsPoolId={Agent pool Id}

PAT や、DevOps の URL は、機密情報なので、コミットしたりしないように気を付けてください

これをデプロイすると、次のように Agent pool にエージェントが表示されるはずです。

  • エージェント数が最小の時

  • スケールアウトしてエージェントが増えたとき

Offline 状態から、徐々に Online 状態に移っていきます。

今回の構成に関するちょっとした解説

  • インスタンスのスペック
    • CPU : 0.25vCPU
    • メモリ : 0.5GiB
    • Container Apps が提供する最小構成です。
  • スケールイン、アウト時のインスタンス
    • 最小 : 1台
    • 最大 : 10台
    • ※ 最小インスタンス数を Container Apps の最小である0台にすると、Azure DevOps 側でエージェントが見つからず、実行時にエラーになります。
  • オートスケールの動き
    • Azure DevOps の pipeline queue に1つ以上キューが入ったらスケールします。
    • KEDA の azure pipelines トリガーを利用しています。

KEDA の設定について

Container Apps は、オートスケーラーとして KEDA が組み込まれているので、KEDA の設定を流し込むことが出来ます。

bicep の rules の部分に、KEDA の設定を記述します。

KEDA は、azure pipelines 向けのトリガーが用意されているので、ドキュメントを参考に落とし込むと、今回のような記載になります。

この設定により、普段使われていないときは最小の1インスタンスで稼働し、pipeline queue が溜まったら、自動でスケールアウトするという動きを実現できます。

これにより、当初の悩みであった、ビルド時にエージェントの取り合いになるといった事がある程度解決できるはずです。

keda.sh

まとめ

これで、Container Apps 上に Self Hosted Agent の構築が出来ました。

簡単にできる反面、構築の度にコマンドを手動で実行したりは、ミスが出たりと辛い部分が多いので、次回は、Azure Pipeline からこのインフラが構築できるようにしていきます。