💻

Terraform × GitHub で始める Microsoft Azure リソース管理

に公開

Enterprise-Grade Azure Infrastructure with Terraform

Azure OpenAI の台頭により、多くの企業内で Microsoft Azure 基盤を管理したり、発行された Microsoft Azure サブスクリプションを使ってサービスを展開する部門/組織が増えてきている印象があります。
そこで感じるのは、IaC (Infrastructure as a Code) をもっと標準搭載して欲しい、です。
コードやアプリの CI/CD はかなり認知度も高かったり意識的なユーザーは多いものの、Microsoft Azure 基盤そのものの IaC はまだまだ後回しにされる事例を多く見かけてきました。

なので、今回は GitHub 環境を使って Terraform で簡単に Azure サービスを管理する方法について触れたいと思います。
Terraform と聞くだけでちょっと抵抗を感じる人も多いと思いますが、基本的なことを覚えればあっという間に実装できると思うので、この記事では Terraform 初心者を意識した構成をメインとします。

Terraform

知らない人は少ないとは思いますが、念の為解説します。
Terraform とは、HashiCorp 社が開発した、OSS の Infrastructure as Code (IaC) ツールで、インフラの構築と管理を自動化する用途で利用されるものです。
宣言型アプローチ、マルチクラウド対応、状態管理などのメリットがあり、Microsoft Azure においては 2017 年から連携強化、2025/05 には (パブリックプレビューで) Azure ポータルから ARM や Bicep にならんで Terraform テンプレートの出力が可能になっています。

https://techcommunity.microsoft.com/blog/azuretoolsblog/announcing-public-preview-of-terraform-export-from-the-azure-portal/4409889

IaC 用のコードは HCL (HashiCorp Configuration Language) という独自の記法で記述するものになっており、シンプルかつ直感的という点を推しています。

https://developer.hashicorp.com/terraform/language/syntax/configuration

Azure における Terraform

Microsoft Azure において Terraform を利用するには、Terraform の基本内容に加えて、以下の 3 点を押さえておけば良いと思います。

  1. AzureRM と AzAPI どちらを使用するかを決める
  2. Terraform 側が利用するサービスプリンシパルに対して Azure RBAC ロールやフェデレーション設定を行う
  3. tfstate をどこに保存するか決める

ちなみに、リソースプロバイダーで Microsoft.AzureTerraform を有効化しておくのは必須ではないですが、既存リソースの Terraform テンプレートをエクスポートしたい際は必須なので、実施しておくと良いです。

Terraform vs Bicep

Azure には Bicep があるので、Terraform とどちらを選択すればいいか、という観点があります。
この部分は、正直な話、好きな方を選べばいいと言うか、社内の人材状況だったり、マルチクラウド前提かどうかなどを鑑みて選択すればいいかと思います。

  • Microsoft Azure しか使わず、他のクラウドを利用することがない
  • IaC は Bicep も Terraform も触ったことがない

こういった方の場合は、Microsoft の公式サポートも受けやすい点、Bicep にせよ Terraform にせよ最初の学習コストがかかる点、などを考慮し、まずは Bicep を選択することをおすすめします。

将来的にマルチクラウド環境を想定していたり、業務上 Azure 以外の AWS や GCP などのクラウドを扱う可能性がある、Bicep の仕様に限界を感じてしまった場合などにおいては、Terraform を選択すればいいと思います。
(正直な話、Bicep も Terraform も中身は似ているので、片方を覚えればもう片方を覚えるのもそんなに難しくはない...とも思います)

Official references

Terraform × Azure としては、下記の公式ドキュメントを参照しておけばいいです。

AzureRM

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs

AzAPI

https://registry.terraform.io/providers/Azure/azapi/latest/docs

ざっくりとした認識で言えば、安定稼働の AzureRM、最新機能や PubPrev/PrivPrev 対応の AzAPI という感じです。
AzureRM と AzAPI はそれぞれメンテナーが別という点もあるので、その辺りを考慮して適切なものを利用すれば良いと思います。

私が採用したディレクトリ構成

今回、私は最終的に下記のような思想でディレクトリ構成を採用することにしました。

  • GitHub リポジトリ
    • リソースだけのデプロイ (Azure Policy や NW 系など) とコードデプロイを含むもの (Azure Functions や Azure App Service など) は分割し、一緒にしない
    • サブスクリプション境界に関係なく、プロジェクトや管理組織単位で分ける (Organization 分割できるならする)
    • 本番環境と開発環境の切り分けや、v1 / v2 などの環境差分は、リポジトリ/ブランチ戦略を適切に定義して対応する
  • Terraform ディレクトリ
    • envs 配下は、環境変数的なものしか配置しない (main.tf とかは置かない)
    • modules 配下は、モジュール単位で作成し、複数のリソースデプロイに対応はしない

実のところ、自分は今の組織だと、社内の基盤管理をしつつ基盤管理のために必要なアプリケーションを Azure 上でさまざまなサービスを使って実装しているので、あれもこれも同じリポジトリで管理したくなる自我も生まれていました。ですが、結論それはやめました。(いろいろ考えている間にアンチパターンに陥っていると気がついた)
リポジトリ内のファイル更新によって GitHub Actions が動くことを考えるとリポジトリは操作内容の単位で分割した方が望ましく、GitHub Actions の実行時間をいかに最小化するか、本番/開発環境で差分を生まず無駄なソース管理負荷を生まないようにする、という考えのもと、この構成に至った感じです。

アプリケーションコードのデプロイを含まない場合

下記は Azure Policy の場合の例ですが、このような形を取りました。

azure-policies
|-- modules
|   |-- management_group_policy_assignment
|   |   |-- main.tf
|   |   |-- variables.tf
|   |-- management_group_policy_set_definition
|   |   |-- main.tf
|   |   |-- outputs.tf
|   |   |-- variables.tf
|   |-- policy_assignment
|   |   |-- main.tf
|   |   |-- variables.tf
|   |-- policy_definition
|   |   |-- main.tf
|   |   |-- outputs.tf
|   |   |-- variables.tf
|   |-- policy_set_definition
|       |-- main.tf
|       |-- outputs.tf
|       |-- variables.tf
|
|-- envs
|   |-- development
|   |   |-- backend.config
|   |   |-- terraform.tfvars
|   |-- production
|       |-- backend.config
|       |-- terraform.tfvars
|
|-- backend.tf
|-- main.tf
|-- provider.tf
|-- variables.tf
|-- version.tf

main.tf などの実装例については、下記を参照してください。

https://github.com/ymasaoka/terraform-azure-samples/blob/main/policies/main.tf

アプリケーションコードのデプロイを含む場合

下記は、Azure Functions の場合の例ですが、このような構成となりました。

azure-function
|--infra
|   |-- modules
|   |-- envs
|   |   |-- development
|   |   |   |-- backend.config
|   |   |   |-- terraform.tfvars
|   |   |-- production
|   |       |-- backend.config
|   |       |-- terraform.tfvars
|   |
|   |-- backend.tf
|   |-- main.tf
|   |-- provider.tf
|   |-- variables.tf
|   |-- version.tf
|
|-- src
|   |-- function-python
|   |   |-- {functionApp}
|   |
|   |-- function-powershell
|       |-- {functionApp}
|
|--azure.yaml

CI/CD はリソースを含めて同じリポジトリで管理し、アプリケーションコードの CD と一緒にリソースの状態チェックもできるようにすることをメインにしています。
Azure Developer CLI (azd) に対応したディレクトリ構造にもしています。

main.tf などの実装例は下記を参照してください。

https://github.com/ymasaoka/terraform-azure-samples/blob/main/resources/infra/main.tf

なぜこのようなディレクトリ構造としたか

正直な話、Terraform のディレクトリ構成は宗教論争的な側面があると思っていますので、これはあくまで 私はこういう構成を採用した というレベルに留めてください。別に正解はないと思うし、どの形が優れている/劣っているなどはそこまでないと思うからです。
有名どころと言うか、よく見る構成例だと、

  1. 本番環境 (production) と開発環境 (development) を分けている envs の下にそれぞれ main.tf などを配置する
  2. 本番環境 (production) と開発環境 (development) を分けている envs の下は変数的なものしか配置せず、main.tf などは共通化する ←今回私が採用した例

の 2 パターンが挙げられると思います。
どちらのパターンでも Pros/Cons があると思うので、一概にどっちが良いとは言えない部分があることから、結論は自分たちの流儀やスタイル、プロジェクトなどでの方針に合わせて採用すればいいと思っています。

ですので、私自身の思想として

  • envs 配下で環境ごとに main.tf が乱立する意義をあまり感じられない (環境変数的な差分以外に同じものが重複する事例をあまり知らない)
    • 本番環境も開発環境も同じ main.tf を使用し、変数的な値である環境固有の差分以外で、デプロイコードの差分が起こることがないように努めるべき
  • 開発が進むにあたって新しいリソースを追加したいなどによる環境差分が起こる場合は、main/release/develop ブランチの戦略に則って対応する
    • API エンドポイントなどで v1 -> v2 などで既存環境に破壊的変更が入る場合、(一定期間は移行稼働などもありうるので) リポジトリ自体を分けリソースも別で管理することを想定
    • アプリケーションそのものの場合でも main/release/develop ブランチ戦略が適切に取れていれば問題ない

といったような部分があるので、上記に落ち着いた感じです。

Azure Developer CLI に対応する構成を意識する

Azure リソースを使用したアプリケーションを構築しているのであれば、Azure Developer CLI に対応する Terraform ディレクトリ構成であることが望ましいです。

https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/azd-templates#explore-azure-developer-cli-template-structure

https://github.com/Azure-Samples/azd-starter-terraform/tree/main

テンプレートを見ればわかりますが、リソース定義部分の Azure リソースと、アプリケーション本体のプログラムコードを同じ場所で管理し、CI/CD をインフラもコードも合わせて回す、という前提でのディレクトリ構造が取られています。
これは企業やプロジェクトによって Azure リソースの管理方法などが異なる可能性があるので絶対に、というわけではないですが、下記の観点から個人としては一緒に管理し、一緒に CI/CD を回すべきと考えます。

  • アプリケーションをデプロイする際、基盤である Azure リソースも適切な設定値で維持されていることを確認するべきと思うから (分けてしまうと、リソース自体の確認タイミングがヘタをするとなくなる)
  • アプリケーションの管理/デプロイするチームが Azure リソースも一緒に管理していることが通例であると思うから (コード開発は別チームだったとしても、デプロイされたアプリの管理とアプリ基盤 (Azure リソース) を管理するチームは同じ組織のはず)

よって、Azure リソースを Terraform で管理する際、アプリケーションデプロイが含まれるリソースの場合は、Azure Developer CLI に対応する形で /infra/src のディレクトリ構造に対応できる形の Terraform コードにすることを前提にすることが望ましいと思います。

参考情報

Discussion