APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

Azure Container AppsをTerraformのAzureRM ProviderではなくAzureAPI Providerで様々な機能を使う

はじめに

ACS事業部 安藤です。
以前、Azure Container AppsがTerraformでサポートされたぞ!という記事を書いて1年半以上過ぎたんですが、
皆さんイイ感じに使えているでしょうか?

techblog.ap-com.co.jp

こんなブログを書いておきながら、私も実はあんまり…な感じだったりします。

というのも、現行のContainer Apps周りのリソースでは一部の機能や設定がTerraformでは、正確にはAzureRM Providerでは設定できないものがあり、
そういった部分はTerraformで出来る範囲の使用に留めるか、手動でやる必要が出てしまったりします。

例を出すと、

  • Container Apps EnvironmentのManaged IDの有効化
  • Container Apps Environmentの証明書のKey Vault連携

などです。

個人的にはこの辺りの問題がContainer Appsを選択したり、オススメしにくい要因とも感じていました。

AzureRM ProviderもVersion 4系にメジャーアップデートされ、Container Appsもいい感じにサポートされないか?と期待はしたものの未だ来ず、という状態だったので、
おそらくAzureでリリースされている機能であれば全機能対応できるであろうAzureAPI Providerで書いてみてはどうだろう?と思い至りました。

AzureAPI Providerとは

AzureのTerraform Providerといえば先述のAzureRM Providerのことを指すことが一番多いと思いますが、
Azure関連のTerraform Providerには複数あり、Azure AD(現在ではAzure Entra ID)関連のAzureAD ProviderやAzure Stack関連のProviderがあります。

その中でAzureが直々にサポートしているPartner Provederの1つとしてAzAPI Providerがあります。

learn.microsoft.com

対象とする領域はAzure Resource Manager (ARM) なのでAzureRM Providerと重複しているのですが、それぞれの特長があるので使い分けが効きます。

AzureRMは様々なリソースタイプが用意されており比較的読み書きしやすいですが、Azure側のAPIのリリースに追従してAzureRM Providerの機能を追加することで新サービスや新機能が扱えるようになるのでタイムラグが発生します。
Container Appsを例にすると、GAされたのが2022/5/25で、AzureRM Providerに追加されたのが8ヶ月半後の2023/2/10でした。

AzAPIはazapi_resourceとごく小数のリソースタイプのみしかありませんが、APIタイプやバージョンを指定することで様々なリソースやサブリソースを管理できます。
ARM TemplateやBicepで利用されているAzure ARM REST API の上にある薄いレイヤーなので、公開されているAPI バージョンならプレビューのものでも利用できるのが特長です。
各リソース・サブリソースに対応した書式はAzureのドキュメントにARM TemplateやBicepと同列で書かれており、HashicorpのドキュメントであるTerraform Registryとは書きっぷりが違うので戸惑うところはありますが、比較的書き初めやすいかと思います。

learn.microsoft.com

Microsoft真壁さんもAzureRMとAzAPIを使い分けて使われているとのことなのでご参考ください。

speakerdeck.com

↑のスライドは弊社主催のイベントでご登壇いただきました!(宣伝)

8a1-apc.connpass.com

AzAPI ProviderのドキュメントでContainer Apps環境のパラメータを確認してみる

Container Apps環境は microsoft.app/managedenvironments というAPIタイプで管理できます。↓がそのドキュメントです。

learn.microsoft.com

これをしばらく眺めてみたんですが…Managed IDのパラメータがない!?
Microsoft.App managedEnvironments/certificates の証明書の方も見てみたんですが、こちらもKey Vault連携のパラメータはありませんでした…

ガッカリして1時間ぐらい諦めムードでしたが、ふと他のAPI Versionだとどうなんだろう?とバージョンを弄ってみると…

ありました!!

最新の @2024-03-01 にはなかった identity のパラメータが @2024-02-02-preview にはありました!

Change logを見てみると、
Version 2023-11-02-preview で追加された identityのパラメータは最新の Version 2024-03-01ではRemovedになっていました。
経緯は把握できていませんが、何らかの理由で最新のAPIバージョンではidentity等の一部のパラメータは利用できなくなっていることがわかります。

Microsoft.App/managedEnvironments/certificatesproperties.certificateKeyVaultPropertiesも同様のようです(Change log

ちなみに、AzureRM ProviderのGitHubのコードを確認してみると、直近のバージョンではMicrosoft.App/managedEnvironments@2024-03-01 を利用していることがわかります。
identityなどのパラメータがTerraformではサポートされていない一因を垣間見えたように思えますね。

github.com

ということで思いがけず紆余曲折ありましたが、AzAPI Providerでは任意のAPI Versionを使用することが出来るので、
@2024-02-02-preview のAPIバージョンを使えばidentity等のパラメータを使用することができそうです!

AzAPI ProviderでContainer Apps環境の作成にチャレンジ!

それでは早速Container Apps環境を作って、AzureRMではサポートされていない機能を追加してみましょう。

provider関連

まずはAzAPI Providerを使用することを宣言するところから

terraform {
  required_providers {
    azapi = {
      source = "Azure/azapi"
    }
  }
}

provider "azapi" {}

provider "azurerm" {
  subscription_id = "********-****-****-****-************"
  features {}
}

こんな感じになります。
余談ですが、AzureRM Providerでは最近リリースされたv4系からproviderブロック内でsubscription_idの指定が必要になったのでお忘れなく。

Container Apps 環境 関連

次に本題のContainer Apps 環境を作ります。
Container Apps 環境では現状LogAnaliticsワークスペースを紐づけることが必須なのでその辺りは従来のAzureRM Providerを使用して作成します。

resource "azurerm_resource_group" "main" {
  name     = "ando-demo"
  location = var.location
  tags     = local.tags
}

resource "azurerm_log_analytics_workspace" "main" {
  name                = "ando-demo"
  location            = var.location
  resource_group_name = azurerm_resource_group.main.name
  tags                = local.tags
}

resource "azurerm_user_assigned_identity" "container" {
  name                = "ando-container-id"
  location            = var.location
  resource_group_name = azurerm_resource_group.main.name
}

resource "azapi_resource" "managed_environment" {
  name      = "ando-contaienr-env"
  location  = var.location
  parent_id = azurerm_resource_group.main.id
  type      = "Microsoft.App/managedEnvironments@2024-02-02-preview"
  tags      = local.tags

  body = jsonencode({
    identity = {
      type = "UserAssigned"
      userAssignedIdentities = {
        "${azurerm_user_assigned_identity.container.id}" : {}
      }
    }
    properties = {
      appLogsConfiguration = {
        destination = "log-analytics"
        logAnalyticsConfiguration = {
          customerId = azurerm_log_analytics_workspace.main.workspace_id
          sharedKey  = azurerm_log_analytics_workspace.main.primary_shared_key
        }
      }
    }
  })
}

最後のazapi_resourceがAzAPI Providerのリソースになります。
AzureRMと違って様々なリソースタイプが共通で使える分、ブロック名でどんなリソースなのか見分けがつくようにしたほうが良さそうです。(今回はmanaged_environmentとしました)

typeには先述のMicrosoft.App/managedEnvironments@2024-02-02-previewを指定します。
parent_id には親となるリソースのIDを渡します。基本的にはリソースグループのIDを渡しておけばよいですが、何かしらのサブリソースの場合は適宜親リソースを指定する必要があります。(後述します)

bodyにはJSON形式でパラメータを渡してやる必要がありますが、Terraformの組み込み関数であるjsonencode関数を使うと、HCLの書式で書いたものをJSONに変換してくれるので、
これを利用してHCLらしく書くのが慣例のようです。

そしてbodyの中で早速identityを設定しています。typeはSystemAssignedのほうが個人的には好きなので最初はそうしていたんですが…
いざこのマネージドIDにKeyVaultの権限等を渡そうとしたところ、azapi_resource.managed_environmentのリソースからマネージドIDのオブジェクトIDやプリンシパルIDといった値を取り出せませんでした。
ここは色々なアトリビュート・パラメータを取得できるAzureRMの良さが、翻ってAzAPIの使いにくさになっているように感じます。
ということで、azurerm_user_assigned_identityでマネージドIDを作成してUser Assigned IDとして紐付けました。

その後のuserAssignedIdentitiesではキーに変数を設定していてだいぶ気持ち悪い書き方になっていますが、とりあえずこれで通ったのでOKなはず。。

      userAssignedIdentities = {
        "${azurerm_user_assigned_identity.container.id}" : {}
      }

Container Apps環境の証明書 関連

次にKey Vaultにある証明書をContainer Apps環境に連携しています。
作成したKey VaultにTerraformで操作している私にAdmin権限を、Container Apps環境のマネージドIDに参照権限を付与しています。
azurerm_key_vault_certificateは長いですが、example.comのいわゆるオレオレ証明書を作っているだけです。

data "azurerm_client_config" "current" {}

resource "azurerm_key_vault" "main" {
  name                      = "ando-vault"
  location                  = var.location
  resource_group_name       = azurerm_resource_group.main.name
  tenant_id                 = data.azurerm_client_config.current.tenant_id
  sku_name                  = "standard"
  enable_rbac_authorization = true
  tags                      = local.tags
}

resource "azurerm_role_assignment" "main" {
  role_definition_name = "Key Vault Administrator"
  scope                = azurerm_key_vault.main.id
  principal_id         = data.azurerm_client_config.current.object_id
}

resource "azurerm_role_assignment" "container" {
  role_definition_name = "Key Vault Certificate User"
  scope                = azurerm_key_vault.main.id
  principal_id         = azurerm_user_assigned_identity.container.principal_id
}

resource "azurerm_key_vault_certificate" "main" {
  name         = "ando-cert"
  key_vault_id = azurerm_key_vault.main.id

  certificate_policy {
    issuer_parameters {
      name = "Self"
    }

    key_properties {
      exportable = true
      key_size   = 2048
      key_type   = "RSA"
      reuse_key  = true
    }

    lifetime_action {
      action {
        action_type = "AutoRenew"
      }

      trigger {
        days_before_expiry = 30
      }
    }

    secret_properties {
      content_type = "application/x-pkcs12"
    }

    x509_certificate_properties {
      # Server Authentication = 1.3.6.1.5.5.7.3.1
      # Client Authentication = 1.3.6.1.5.5.7.3.2
      extended_key_usage = ["1.3.6.1.5.5.7.3.1"]

      key_usage = [
        "cRLSign",
        "dataEncipherment",
        "digitalSignature",
        "keyAgreement",
        "keyCertSign",
        "keyEncipherment",
      ]

      subject_alternative_names {
        dns_names = ["example.com"]
      }

      subject            = "CN=example.com"
      validity_in_months = 12
    }
  }

  depends_on = [
    azurerm_role_assignment.main,
  ]
}

resource "azapi_resource" "cert" {
  type      = "Microsoft.App/managedEnvironments/certificates@2024-02-02-preview"
  name      = "ando-container-cert"
  location  = var.location
  parent_id = azapi_resource.managed_environment.id
  tags      = local.tags
  body = jsonencode({
    properties = {
      certificateType = "ServerSSLCertificate"
      certificateKeyVaultProperties = {
        identity    = azurerm_user_assigned_identity.container.id
        keyVaultUrl = azurerm_key_vault_certificate.main.versionless_secret_id
      }
    }
  })
}

最後のazapi_resourceではtypeで Microsoft.App/managedEnvironments/certificates@2024-02-02-previewを使用しているように、Key Vault証明書をContainer Apps環境に連携しています。
こちらはContainer Apps環境のサブリソースなので、parent_idにはContainer Apps環境のIDを指定しています。
certificateKeyVaultPropertiesとしてkeyVaultUrlとアクセスするマネージドIDを指定します。

Container Apps 関連

最後にContainer Appsを作成します。Container AppsではPreviewにしかない機能は使ってないので、Microsoft.App/containerApps@2024-03-01のAPI Versionを使用しています。
なのでAzureRMのほうを使っても問題ないと思いますが、せっかくなのでAzAPIで書いてます。

肝心のconfiguration.customDomainsをコメントアウトしていますが、
example.comのドメインなので諸々NGなのと、正規のドメインであってもドメイン検証のためにDNSのレコード追加などの操作が必要になるので、
完全にTerraformでやるのは結構難しそうなので手作業でやってあとからコードで取り込み、みたいな形のほうが楽だと思ったからです。

resource "azapi_resource" "container_app" {
  name      = "ando-contaienr-apps"
  location  = var.location
  parent_id = azurerm_resource_group.main.id
  type      = "Microsoft.App/containerApps@2024-03-01"
  tags      = local.tags

  body = jsonencode({
    properties : {
      managedEnvironmentId = azapi_resource.managed_environment.id
      template = {
        "containers" : [
          {
            name : "nginx"
            image : "nginx:latest"
            resources = {
              cpu : 0.25
              memory : "0.5Gi"
            }
          }
        ]
      }
      configuration = {
        ingress = {
          transport  = "http"
          targetPort = 80
          external   = true
          # customDomains = [
          #   {
          #     name          = "example.com"
          #     bindingType   = "Disabled"
          #     certificateId = azapi_resource.cert.id
          #   }
          # ]
        }
      }
    }
  })
}

まとめ

Azure Container Appsがリリースされた当初はAzAPI Providerなら作れる!と言われつつも敷居の高さを感じてAzureRM Providerでのリリースを待ちましたが、
いざリリースされたら一部機能が設定できず、結局AzAPI Providerを触ることになってしまいました。

ですが、実際に調べてみると多少のクセはありますがMicrosoftがドキュメントをしっかり整備してくれており、そこまで辛みなく書くことができました。

これで使いたい機能がAzureRMでサポートされていない時に、AzureRMのアップデートを待つ以外にもAzAPIで書く!という選択肢が増えましたね!!

今回は取り上げませんでしたが、AzAPI2AzureRM という移行ツールもあるようなので、
AzureRMでサポートされたらAzureRMに変換する、なんてことも出来るのかもしれません。(未検証)

azapi_resource_actionというあまりTerraformらしからぬリソースタイプもあり、MS真壁さんによるとそちらも有用とのことなので活用方法を調べていきたいと思います!


【PR】
私達 ACS 事業部は Azure・AKS などのクラウドネイティブ技術を活用した内製化のご支援をしております。

www.ap-com.co.jp

一緒に働いていただける仲間も募集中です!
まだまだ組織規模拡大中なので、ご興味持っていただけましたらぜひお声がけください。
我々の事業部の CultureDeck はコチラ。

www.ap-com.co.jp

私達ACS事業部はAzure・AKSなどのクラウドネイティブ技術を活用した内製化のご支援をしております。

www.ap-com.co.jp

本記事の投稿者: 安藤 知樹
AzureとTerraformをメインにインフラ系のご支援を担当しています。