3行まとめ
- AzureのContainer InstanceをVirtual Networkの中にTerraformで作成したいけどなぜかうまくいかない。
- Container InstanceとVirtual Networkの依存関係がTerraformに認識されていなかった。
- Terraformの
depends_on
を使えば解決!
背景
Azure上でウェブアプリを走らせたいときには
- App service ( + Blob)
- Functions + Blob
などの技術スタックを用いると、かなりあっさりアプリケーションをデプロイすることができる。まさしく王道。
一方で、containerizeされたアプリケーションをデプロイしたいけど、App serviceでは要件を満たせない場合などがあると思う。実際に僕の場合だと、ネットワークの設定やコンテナの構成で少しこだわりたいところが出てきて、その要件を満たすためにはApp serviceでは間に合わないことがわかった。
そこで次に検討されるのが、以下のサービスだと思う
- Azure Kubernetes Service
- Azure Container Instance
Azure Kubernetes Serviceはご存じKubernetesのマネージドサービス。Azure Container InstanceはAWSのECSに該当するサービスで、作成したContainerを渡すとあとはいい感じに運用してくれるサービス。
ここで、個人開発ではAKSが割に合わないなあとなると、必然的にAzure Container Instanceを選ぶことになる。
この記事では、この構成をTerraformで作ろうとするとなぜかうまくいかないことを解消する。
発生する問題
Azure Container InstanceをVirtual Networkの中に建てたい、しかもそれをTerraformで構成管理したい!というときに、どうもうまくいかない。
実際のmain.tf
は長いので、必要なところを抜粋。ざっくり説明すると
- subnet
- Container Group(Container Instanceだと思ってもらえればOK)
- Network Profile
- Container Groupのネットワーク設定
を作成している。
resource "azurerm_subnet" "subnet" {
name = "application-subnet"
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.1.0/24"]
resource_group_name = azurerm_resource_group.rg.name
delegation {
name = "delegation"
service_delegation {
name = "Microsoft.ContainerInstance/containerGroups"
actions = ["Microsoft.Network/networkinterfaces/*", "Microsoft.Network/virtualNetworks/subnets/action", "Microsoft.Network/virtualNetworks/subnets/join/action", "Microsoft.Network/virtualNetworks/subnets/prepareNetworkPolicies/action", "Microsoft.Network/virtualNetworks/subnets/unprepareNetworkPolicies/action"]
}
}
}
resource "azurerm_container_group" "acg" {
name = "container-group"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
ip_address_type = "Private"
network_profile_id = azurerm_network_profile.app-netprofile.id
os_type = "Linux"
container {
name = "hello-world"
image = "mcr.microsoft.com/azuredocs/aci-helloworld:latest"
cpu = "0.5"
memory = "1.5"
ports {
port = 80
protocol = "TCP"
}
}
}
resource "azurerm_network_profile" "app-netprofile" {
name = "app-netprofile"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
container_network_interface {
name = "app-enic"
ip_configuration {
name = "app-ipconfig"
subnet_id = azurerm_subnet.subnet.id
}
}
}
で、これを走らせると、以下のエラーが出る。
# TODO: Error logをここに貼る。
エラーログを素直に読めば、Virtual Network SubnetからContainerInstancesへのdelegation(日本語訳は「委譲」)がうまくいっていないということになる。
しかし、subnetの設定を抜粋すると、
resource "azurerm_subnet" "subnet" {
name = "application-subnet"
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.1.0/24"]
resource_group_name = azurerm_resource_group.rg.name
delegation {
name = "delegation"
service_delegation {
name = "Microsoft.ContainerInstance/containerGroups"
actions = ["Microsoft.Network/networkinterfaces/*", "Microsoft.Network/virtualNetworks/subnets/action", "Microsoft.Network/virtualNetworks/subnets/join/action", "Microsoft.Network/virtualNetworks/subnets/prepareNetworkPolicies/action", "Microsoft.Network/virtualNetworks/subnets/unprepareNetworkPolicies/action"]
}
}
}
最後の部分、service_delegation
で問題なく"Microsoft.ContainerInstance/containerGroups"
へ委譲できているように見える。
付与する権限が足りてないのかなと思って、Terraformのドキュメントに乗っている、actionsの配列をすべて突っ込んでみたりしたが、うまく動かなかった。
解決の糸口とその方法
ふとした拍子に、
Terraformはリソースを並列に生成すること
を思い出して(公式ドキュメント)、「もしかしてリソース作成の順番が影響するのでは?subnetがない状態でそのsubnetへの権限をリクエストしてしまっているのではないか」と思って検証したのが功を奏した。
- はじめにVirtual NetworkとSubnetを生成する
main.tf
でterraform apply
する。- 成功
- 次に、Subnetに
delegation
blockを追加して、再度terraform apply
する。- 成功
- 最後に、Container GroupとNetwork Profileを追加して、
terraform apply
する。- 成功!!
一件落着?
Terraformは状態を記述すれば、何度やっても同じ結果が得られることが売りのはず。こんな原始的な方法でしか生成できないだなんてありえない!なにか公式がいい手立てを用意しているはず!
とおもって調べたら、depends_on
というまさしくな設定があることを初めて知った。
これは
resource "azurerm_subnet" "subnet" {
name = "application-subnet"
virtual_network_name = azurerm_virtual_network.vnet.name
... # 省略
}
resource "azurerm_container_group" "acg" {
name = "container-group"
ip_address_type = "Private"
network_profile_id = azurerm_network_profile.app-netprofile.id
... # 省略
depends_on = [
azurerm_subnet.subnet # resourceのtypeとlocal nameで指定
]
}
のように記述すると、Terraformが自動的に認識することができない、隠れた(implicit)依存性を明記することができ、Terraformが上手にリソース作成することができるようになる。(Resourceの依存性解決の話はここ、depends_on
の話はここのそれぞれ公式ドキュメントが詳しい)
今回のリソース作成で自動的な依存性解決が行われなかったのは、おそらくAzure Container Groupのネットワーク設定を直接リソースに記述するのではなく、Network Profileという別リソースに記述しなければならなかったことが絡んでいると思われる。
まとめ
- Terraformにとってimplicitなdependencyが存在するとき、そのリソース作成がうまくいかない場合がある
- 今回はこれを、Subnet + ACG + Network Profileの組み合わせで踏んでしまった
- 段階的に生成すればOK
- 原始的に、手作業で段階的に生成することでも回避できる。
- 実際の開発現場では、段階的にコードが継ぎ足されるはず。
- 「本番環境では動くのに、0から開発環境を作成しようとするとなぜか失敗する」悲劇、頻発してそう。
depends_on
を書けば、いつでもちゃんと回避できる。
- 原始的に、手作業で段階的に生成することでも回避できる。
depends_on
で、よきTerraformライフを!
後日談
この記事に記載した問題は、Ubuntu 22.04の上で実行していた際に遭遇したが、なぜかWindows 10の上でまったく同じコードを使用すると問題なくリソース作成できてしまった。Terraform CLIのバージョン違いとかかな。悲しい。