TerraformでAzure サンプル 2018/1版
サンプルのアップデート
年末にリポジトリの大掃除をしていて、2年前に書いたTerraform & Azureの記事に目が止まりました。原則はいいとして、サンプルは2年物で腐りかけです。ということでアップデートします。
インパクトの大きな変更点
Terraformの、ここ2年の重要なアップデートは以下でしょうか。Azure視点で。
- BackendにAzure Blobを使えるようになった
- Workspaceで同一コード・複数環境管理ができるようになった
- 対応リソースが増えた
- Terraform Module Registryが公開された
更新版サンプルの方針
重要アップデートをふまえ、以下の方針で新サンプルを作りました。
チーム、複数端末での運用
BackendにAzure Blobがサポートされたので、チーム、複数端末でstateの共有がしやすくなりました。ひとつのプロジェクトや環境を、チームメンバーがどこからでも、だけでなく、複数プロジェクトでのstate共有もできます。
Workspaceの導入
従来は /dev /stage /prodなど、環境別にコードを分けて管理していました。ゆえに環境間のコード同期が課題でしたが、TerraformのWorkspace機能で解決しやすくなりました。リソース定義で ${terraform.workspace} 変数を参照するように書けば、ひとつのコードで複数環境を扱えます。
要件によっては、従来通り環境別にコードを分けた方がいいこともあるでしょう。環境間の差分が大きい、開発とデプロイのタイミングやライフサイクルが異なるなど、Workspaceが使いづらいケースもあるでしょう。その場合は無理せず従来のやり方で。今回のサンプルは「Workspaceを使ったら何ができるか?」を考えるネタにしてください。
Module、Terraform Module Registryの活用
TerraformのModuleはとても強力な機能なのですが、あーでもないこーでもないと、こだわり過ぎるとキリがありません。「うまいやり方」を見てから使いたいのが人情です。そこでTerraform Module Registryを活かします。お墨付きのVerifiedモジュールが公開されていますので、そのまま使うもよし、ライセンスを確認の上フォークするのもよし、です。
リソースグループは環境ごとに準備し、管理をTerraformから分離
AzureのリソースをプロビジョニングするTerraformコードの多くは、Azureのリソースグループを管理下に入れている印象です。すなわちdestroyするとリソースグループごとバッサリ消える。わかりやすいけど破壊的。
TerraformはApp ServiceやACIなどPaaS、アプリ寄りのリソースも作成できるようになってきたので、アプリ開発者にTerraformを開放したいケースが増えてきています。dev環境をアプリ開発者とインフラ技術者がコラボして育て、そのコードをstageやprodにデプロイする、など。
ところで。TerraformのWorkspaceは、こんな感じで簡単に切り替えられます。
terraform workspace select prod
みなまで言わなくても分かりますね。悲劇はプラットフォーム側で回避しましょう。今回のサンプルではリソースグループをTerraform管理下に置かず、別途作成します。Terraformからはdata resourcesとしてRead Onlyで参照する実装です。環境別のリソースグループを作成し、dev環境のみアプリ開発者へ権限を付与します。
サンプル解説
サンプルはGitHubに置きました。合わせてご確認ください。
このコードをapplyすると、以下のリソースが出来上がります。
- NGINX on Ubuntu Webサーバー VMスケールセット
- VMスケールセット向けロードバランサー
- 踏み台サーバー
- 上記を配置するネットワーク (仮想ネットワーク、サブネット、NSG)
リポジトリ構造
サンプルのリポジトリ構造です。
├── modules
│ ├── computegroup
│ │ ├── main.tf
│ │ ├── os
│ │ │ ├── outputs.tf
│ │ │ └── variables.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ ├── loadbalancer
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ └── network
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── projects
├── project_a
│ ├── backend.tf
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── shared
├── backend.tf
├── main.tf
├── outputs.tf
└── variables.tf
/modulesにはTerraform Module RegistryでVerifiedされているモジュールをフォークしたコードを入れました。フォークした理由は、リソースグループをdata resource化して参照のみにしたかったためです。
そして、/projectsに2つのプロジェクトを作りました。プロジェクトでリソースとTerraformの実行単位、stateを分割します。sharedで土台となる仮想ネットワークと踏み台サーバー関連リソース、project_aでVMスケールセットとロードバランサーを管理します。
このボリュームだとプロジェクトを分割する必然性は低いのですが、以下のケースにも対応できるように分けました。
- アプリ開発者がproject_a下でアプリ関連リソースに集中したい
- 性能観点で分割したい (Terraformはリソース量につれて重くなりがち)
- 有事を考慮し影響範囲を分割したい
プロジェクト間では、stateをremote_stateを使って共有します。サンプルではsharedで作成した仮想ネットワークのサブネットIDをoutputし、project_aで参照できるよう定義しています。
使い方
前提
- Linux、WSL、macOSなどbash環境の実行例です
- SSHの公開鍵をTerraform実行環境の ~/.ssh/id_rsa.pub として準備してください
管理者向けのサービスプリンシパルを用意する
インフラのプロビジョニングの主体者、管理者向けのサービスプリンシパルを用意します。リソースグループを作成できる権限が必要です。
もしなければ作成します。組み込みロールでは、サブスクリプションに対するContributorが妥当でしょう。Terraformのドキュメントも参考に。
az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/SUBSCRIPTION_ID"
出力されるappId、password、tenantを控えます。既存のサービスプリンシパルを使うのであれば、同情報を確認してください。
なお参考までに。Azure Cloud ShellなどAzure CLIが導入されている環境では、特に認証情報の指定なしでterraform planやapply時にAzureのリソースにアクセスできます。TerraformがCLIの認証トークンを使うからです。
そしてBackendをAzure Blobとする場合、Blobにアクセスするためのキーが別途必要です。ですが、残念ながらBackendロジックでキーを得る際に、このトークンが使われません。キーを明示することもできますが、Blobのアクセスキーは漏洩時のリカバリーが大変です。できれば直に扱いたくありません。
サービスプリンシパル認証であれば、Azureリソースへのプロビジョニング、Backendアクセスどちらも対応できます。これがこのサンプルでサービスプリンシパル認証を選んだ理由です。
管理者の環境変数を設定する
Terraformが認証関連で必要な情報を環境変数で設定します。先ほど控えた情報を使います。
export ARM_SUBSCRIPTION_ID="<your subscription id>"
export ARM_CLIENT_ID="<your servicce principal appid>"
export ARM_CLIENT_SECRET="<your service principal password>"
export ARM_TENANT_ID="<your service principal tenant>"
Workspaceを作る
開発(dev)/ステージング(stage)/本番(prod)、3つのWorkspaceを作る例です。
terraform workspace new dev
terraform workspace new stage
terraform workspace new prod
リソースグループを作る
まずWorkspace別にリソースグループを作ります。
az group create -n tf-sample-dev-rg -l japaneast
az group create -n tf-sample-stage-rg -l japaneast
az group create -n tf-sample-prod-rg -l japaneast
リソースグループ名にはルールがあります。Workspace別にリソースグループを分離するため、Terraformのコードで ${terraform.workspace} 変数を使っているためです。この変数は実行時に評価されます。
data "azurerm_resource_group" "resource_group" {
name = "${var.resource_group_name}-${terraform.workspace}-rg"
}
${var.resource_group_name} は接頭辞です。サンプルではvariables.tfで"tf-sample"と指定しています。
次にBackend、state共有向けリソースグループを作ります。
az group create -n tf-sample-state-rg -l japaneast
このリソースグループは、各projectのbackend.tfで指定しています。
terraform {
backend "azurerm" {
resource_group_name = "tf-sample-state-rg"
storage_account_name = "<your storage account name>"
container_name = "tfstate-project-a"
key = "terraform.tfstate"
}
}
最後にアプリ開発者がリソースグループtf-sample-dev-rg、tf-sample-state-rgへアクセスできるよう、アプリ開発者向けサービスプリンシパルを作成します。
az ad sp create-for-rbac --role="Contributor" --scopes "/subscriptions/<your subscription id>/resourceGroups/tf-sample-dev-rg" "/subscriptions/<your subscription id>/resourceGroups/tf-sample-state-rg"
出力されるappId、password、tenantは、アプリ開発者向けに控えておきます。
Backendを準備する
project別にストレージアカウントとコンテナーを作ります。tf-sample-state-rgに
- ストレージアカウント (名前は任意)
- コンテナー *2 (tfstate-project-a, tfstate-shared)
を作ってください。GUIでもCLIでも、お好きなやり方で。
その後、project_a/backend.tf.sample、shared/backend.tf.sampleをそれぞれbackend.tfにリネームし、先ほど作ったストレージアカウント名を指定します。以下はproject_a/backend.tf.sampleの例。
terraform {
backend "azurerm" {
resource_group_name = "tf-sample-state-rg"
storage_account_name = "<your storage account name>"
container_name = "tfstate-project-a"
key = "terraform.tfstate"
}
}
data "terraform_remote_state" "shared" {
backend = "azurerm"
config {
resource_group_name = "tf-sample-state-rg"
storage_account_name = "<your storage account name>"
container_name = "tfstate-shared"
key = "terraform.tfstateenv:${terraform.workspace}"
}
}
これで準備完了です。
実行
Workspaceをdevに切り替えます。
terraform workspace select dev
まずは土台となるリソースを作成するsharedから。
cd shared
terraform init
terraform plan
terraform apply
土台となるリソースが作成されたら、次はproject_aを。
cd ../project_a
terraform init
terraform plan
terraform apply
ここでは割愛しますが、dev向けサービスプリンシパルで認証しても、dev Workspaceではplan、apply可能です。
dev Workspaceでコードが育ったら、stage/prod Workspaceに切り替えて実行します。
terraform workspace select stage
[以下devと同様の操作]
当然、dev向けサービスプリンシパルで認証している場合は、stage/prodでのplan、apply、もちろんdestroyも失敗します。stage/prod リソースグループにアクセスする権限がないからです。