【Azure】App Service にTerraform を使ってコンテナイメージをデプロイする
はじめに
みなさん、こんにちは。Azure App Service をTerraform で構築 + コンテナイメージをデプロイというのをやってみたので、備忘的に書いていきたいと思います。Azure は調べていても情報が少なかったり古かったりするため、少しでも役に立てたらな、というモチベーションです。
今回は以下の流れで実践してます。
- Terraform でApp Service を作成する。
- Terrafrom でAzure Container Registry(ACR) を作成する。
- GitHub Actions からアプリのコンテナイメージを作成し、ACR にPUSHすると共に、App Service にデプロイする。
Azure でのコンテナサービス
2022/12/10現在、Azure でコンテナイメージを利用してアプリを構築する際は、以下のサービスが選択肢として挙がります。
- Azure Kubernetes Service (AKS)
- Azure Red Hat OpenShift
- Azure Container Apps
- Asure Functions
- Web App for Containers (App Service)👈今回はコチラ
- Azure Container Instance
👇こちらにAzure のコンテナサービスがまとまっています。
今回はGraphQL + NestJS のアプリをコンテナイメージとし、単一のBFF として動かすことが目的だったため、シンプルで社内にナレッジも溜まっているであろうWeb App for Containers(App Service) を採用しました。初めて見たときは「こんなサービスがあるんだな」と思ったのですが、どうやらApp Service にコンテナイメージを載せることを指しているだけのようです。分かりづらい…実際、ページに行くと「App Service -> Web App for Containers」とあります。
混乱するだけなので、サービスの多さで競おうとせずに、名前変えて欲しいところですが…ということで、Terraform で構築するのもApp Service になるわけです。
App Service の作成
まずはApp Service を作っていきます。App Serivice 構築にあたって、最初はAzure のページを見ていたのですが、Terraform サンプルはだいたい古くなっていました。そのため、HashiCorp のドキュメントを頼るのですが、これまたHashiCorp のサンプルも古いことが多かったので、Arguments を参考にしつつ、GitHub を見に行く、という動き方が最善だと思います。
なお、App Serivice の今回の要件としては以下のとおりです。
- App Service のOS タイプはLinux を選択
- App Service をVNet 統合する
- コンテナイメージの指定はマイクロソフトのサンプルを利用し、2回目以降は差分を無視する
最後の要件は、GitHub Actions からアプリのコンテナイメージをデプロイしたあとに、Terraform の再実行でイメージを書き換えないようにするためです。今回はlifecycle
内にてignore_changes
を利用していますが、もっと良い方法がある気がしています…詳しい方がいたらコメントくださると嬉しいです。
実際にはモジュール化をしていたり、変数用のvariabels.tf
やterraform.tfvars
を利用していますが、ここでは記事用に1ファイルにまとめてみました。(そのため、このまま利用しても動かないかもです…
また、サンプルで見かけるazurerm_app_service_plan
とazurerm_app_service
は廃止が予定されているため注意が必要です。
# Resource Group
resource "azurerm_resource_group" "rg" {
name = "appservice-rg"
location = "japaneast"
}
# VNet
resource "azurerm_virtual_network" "vnet" {
name = "vnet"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
address_space = ["10.0.0.0/16"]
}
# VNet統合用サブネット(integration_subnet)
resource "azurerm_subnet" "integrationsubnet" {
name = "integrationsubnet"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.1.0/24"]
delegation {
name = "delegation"
service_delegation {
name = "Microsoft.Web/serverFarms"
}
}
}
# App Service Plan
resource "azurerm_service_plan" "app_service_plan" {
name = "appserviceplan"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
os_type = "Linux"
sku_name = "B1"
}
# App Serivice
resource "azurerm_linux_web_app" "example" {
name = "web-app-for-containers-example"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
service_plan_id = azurerm_service_plan.app_service_plan.id
site_config {
application_stack {
# Docker Image は初期値のみ指定
docker_image = "jackofallops/azure-containerapps-python-acctest"
docker_image_tag = "v0.0.1"
}
}
# Docer Image 初期登録後は変更されていても無視する
lifecycle {
ignore_changes = [
site_config[0].application_stack[0].docker_image,
site_config[0].application_stack[0].docker_image_tag
]
}
# VNet統合のサブネットを指定
virtual_network_subnet_id = azurerm_subnet.integrationsubnet.id
# その他省略...
}
Azure Container Registry(ACR) 作成
続いて、GitHub Actions からコンテナイメージをPUSHするためのAzure Container Registry を作成します。ここはHashiCorp のドキュメント通りで問題ないですが、GitHub Actions から管理者ユーザーでのアクセスを行うため、admin_enabled
にtrue
を設定しています。anonymous_pull_enabled
は指定しなければfalse
になりますが、明示的に指定しています。
# Azure Container Registy
resource "azurerm_container_registry" "example" {
name = "acr-example"
resource_group_name = "rg-example"
location = "japaneast"
sku = "Standard"
# 管理者ユーザーからのアクセス可否
admin_enabled = true
# パブリックアクセスはtrue
public_network_access_enabled = true
# 匿名(未認証)プルアクセス許可設定:デフォルトfalse(trueでアクセス許可されるため注意)
anonymous_pull_enabled = false
}
GitHub Actions 作成
作成前の下準備
GitHub Actions を利用したAzure へのアクセスには、サービスプリンシパルとOIDCを利用しています。サービスプリンシパルというのがAzure 初心者の私には中々理解できなかったのですが、AWS でいうIAM ロール的なものだろうと一旦判断しました(タブン違う…ので、ADをしっかり理解しないといけないな、と思いながら年を越してしまいそうです…
以下のページにGitHub Actions からAzure への接続方法についてまとまっていますので、こちらを確認するのが良いと思います。これはドキュメント通りで問題なかったです。
また、GitHub Actions からAzure Container Registy(ACR) へのアクセスは管理者ユーザーを使っているため、Azure ポータルからユーザー名とパスワードを取得し、GitHub のシークレットに登録します。より安全にACR を利用する場合は、こちらもサービスプリンシパルを利用するのが良いのだと思うのですが、サポートの方とやり取りしても中々上手くいかなかったため、暫定でこのようにしています。
GitHub Actions Workflow 作成
以下のように作成しました。デプロイは手動で行うためworkflow_dispatch
が入っていることと、入力値によって環境シークレットを使い分けるようにしています。こうすることで、GitHub Actions 実行時に選択した環境ごとに異なる値が設定されるため、実行ファイルを一つにできます。
name: Build and deploy docker image to Azure App Service
on:
# Allow manually trigger
workflow_dispatch:
inputs:
environment:
type: environment
description: deploy environment
# OIDC
permissions:
id-token: write
contents: read
# Login Azure container registry and build and deploy docker image.
jobs:
build-and-deploy:
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment }}
steps:
- name: Checkout to the branch
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Azure Login
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Log in to container registry
uses: azure/docker-login@v1
with:
login-server: ${{ secrets.REGISTRY_URL }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build and push container image to registry
uses: docker/build-push-action@v3
with:
push: true
tags: ${{ secrets.REGISTRY_URL }}/${{ secrets.CONTAINER_REPOSITORY_NAME }}:${{ github.sha }}
file: ./hoge/Dockerfile
context: ./hoge
- name: Deploy to Azure Web App
id: deploy-to-webapp
uses: azure/webapps-deploy@v2
with:
app-name: ${{ secrets.AGGREGATE_WEB_APP_FOR_CONTAINER }}
images: "${{ secrets.REGISTRY_URL }}/${{ secrets.CONTAINER_REPOSITORY_NAME }}:${{ github.sha }}"
まとめ
今回はAzure App Service へGitHub Actions からコンテナイメージをデプロイしてみました。Azure の知識が全然無い中、必死に格闘していましたが、Terraform を介して対話することで知識がついたような気がします(タブン...
その他にも、プライベートエンドポイントというものを作成したりもしていますが、GUIで作成するとサービス側が自動で作成して意識する必要がないものも、Terraform だと自分で定義しなくてはならなかったりするので、IaC での構築はサービスに対しての理解を深めるためにも非常に重要だと再認識できました。
これからも自分が詳しいサービスばかり携われるわけではないですし、知見のないサービスを素早くキャッチアップする力を養うためにも、頑張っていきたいと思います。
それでは、この記事がどなたかのお役に立てば幸いです。
最後までお読みいただき、ありがとうございました!
Discussion