💪

Bicep初心者が考えたフォルダ階層パターン

2024/10/25に公開

はじめに

今回、はじめて Azure を本格的に利用する案件を担当することになり、さらに IaC ツールである Bicep を使用して構築を進めることが決まり、試行錯誤しながら学びを重ねています。
本投稿では、Bicep を使う中で得た知見や試行錯誤したことを自分なりに整理して記載しようと思います。

Bicep の概要

Bicep とは

Bicep は、宣言型の構文を使用して Azure リソースをデプロイするドメイン固有言語 (DSL) 。
Bicep ファイル内で Azure にデプロイするインフラストラクチャを定義し、そのファイルを開発ライフサイクル全体にわたって使用してインフラストラクチャを繰り返しデプロイします。
https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/overview?tabs=bicep

Bicep の特徴

すべてのリソースの種類と API バージョンのサポート

Bicep は Azure サービスのすべてのプレビュー版と GA バージョンがサポートされます。
リソースプロバイダーが新しいリソースの種類と API バージョンを導入するとすぐに Bicep で利用することが出来ます。
「このサービスのこの機能を待ってたんだよ!あーでもコード側での実装はまだか...」というようなことを考慮することなく利用することが出来るのは良いなと思いました。

視認性と学習コストの低さ

今までの IaC である ARM Template は JSON 形式でちょっと残念でしたが、Bicep は Microsoft 独自の言語を用いてより簡潔な構文を提供しています。
構文は宣言型であり、デプロイするリソースとリソース プロパティを指定するだけでよいことから、プログラミング言語に関する複雑な知識を必要とし無いのも特徴です。
ただ簡単である反面、他の言語と比較すると「こういうことはできないのか...」というのも目立ってきます。

フォルダ階層を検討する目的

なぜフォルダ階層を検討するのか

Bicep に限定した話ではなく、IaC 利用時はフォルダ階層をかなり悩んで設計すると思います。
コードを管理するフォルダ構成は後から簡単にひっくり返すことが難しく、また IaC の運用はシステム維持・保守のフェーズにおいても利用されるため、メンテナンスやチーム内の作業効率に大きく関わります。
(自分の知らないところで「こんなフォルダ構成にしたの誰だよ...」と言われないようにする必要があります。)

どんないいことがあるのかな

大きくは以下の点だと考えています。結構多い。

  1. 可読性と保守性の向上
    IaC は、他のチームメンバや運用メンバが共同でコーディングすることを前提に書かれるため、フォルダ構造を工夫することでリソースや設定の場所が明確になり保守性が向上します。
    例えば階層を定義することで新規参画者でもコードの場所が分かりやすかったり、モジュールを別フォルダに切り出すことで再利用を促進したりできます。
  2. スケーラビリティの確保
    適当なフォルダ構成でも規模が小さければ何とかなります。気合で。
    コードに触れる人数・チームが多かったり、作成するリソースが多くなってくるとそうはいきません。
    プロジェクトが進んでいくにつれて管理すべきリソースや環境が増えていくため、初期に適切なフォルダ階層を設計しておくことで拡張に耐えられる構造を用意できます。
  3. 環境ごとの設定の分離
    IaC では開発環境(dev)、本番環境(prod)、ステージング環境(staging)などで異なる設定を持つことが一般的で、初期にフォルダ階層を設計することで、環境ごとの設定および権限等を明確に分離できます。
    これにより、意図しない環境へのデプロイを防止したり、環境ごとの差異を管理しやすくなります。(オペミスもなくなるね)
  4. CI/CD パイプラインの効率化
    フォルダ階層を明確にすることで CI/CD パイプラインの自動化が簡単になります。
    具体的には環境ごとのフォルダを利用して、特定のフォルダ内の変更にのみをトリガーとするパイプラインを構築できたりします。
  5. バージョン管理の効率化
    Git などのバージョン管理システムでは、フォルダ構造が管理単位になります。変更の規模を把握したり、コンフリクトを減らしたりするために、リソース単位でフォルダを分けておくと便利です。

フォルダ構造を検討で注意したポイント

思いつくまま挙げていきます。

  1. リソースの粒度と分離のバランス
    まずはどの単位でリソースを分割するかです。
    分割が細かすぎると管理が煩雑になりますが、適切な粒度でモジュール化することで再利用や変更が容易になります。
    そのため、高頻度で変更されるリソースは分割したり、依存関係が強いリソースは同じファイル・フォルダにまとめるなどの考慮が必要です。

  2. モジュールの再利用性
    モジュールをどのように構成するのかを設計してフォルダ構造に落とし込むことは IaC において非常に重要だと考えています。
    使用頻度の高いリソースを共通モジュールとして切り出すことで、コードの重複を削減し、変更にも強くなります。
    モジュール自体は汎用的に作成し、パラメータなどの変数で柔軟性を持たせるのは当たり前ですが、そのほかにも各モジュールのフォルダで README を用意したりすることで、可動性向上やモジュールの注意点なども共有することが出来ます。

  3. チームの分業管理
    大規模なプロジェクトでは、複数のチームが関わることが多いため、フォルダ構造をチームの役割や権限に合わせて設計することが重要です。
    例えば下記のように各サービスによって設計・構築担当のチームが異なる場合でも、管理しているチームが明確にできたりします。

    └── Modules/
     ├── VNet/     # ネットワーク設計担当が管理
     ├── Storatge/ # ストレージ設計担当が管理
     └── Monitor/  # 監視設計担当が管理
    
  4. バージョン管理と運用
    分散管理することでより効率に開発を行うことが出来ると考えておりますが、そのためにはやはり Git での管理が必須となってきます。
    ただし Git などのバージョン管理を利用する場合、ブランチ戦略の設計も必要となるため、負荷は少し高いかもしれませんが運用は効率よくなります。

おすすめのフォルダ階層パターン

パターン ①:シンプルな環境ベースのフォルダ構造

├── dev/
│   ├── main.bicep
│   └── dev.parameters.json
├── staging/
│   ├── main.bicep
│   └── staging.parameters.json
└── prod/
    ├── main.bicep
    └── prod.parameters.json

見た通りシンプルに分割しており、小規模プロジェクトや単純な構成で各環境が独立している場合に適用できます。
構成するリソースを分割せずに「main.bicep」1 つにまとめているため、デプロイ対象の数が少ない小規模チームでないと適用できないと考えています。
また各環境で同じリソース構成を重複して記載する必要があるため、変更時に管理が煩雑になる可能性があります。

パターン ②:モジュールベースのフォルダ構造

├── modules/
│   ├── network.bicep
│   ├── storage.bicep
│   └── app_service.bicep
├── environments/
│   ├── dev/
│   │   ├── main.bicep
│   │   └── dev.parameters.json
│   ├── staging/
│   │   ├── main.bicep
│   │   └── staging.parameters.json
│   └── prod/
│       ├── main.bicep
│       └── prod.parameters.json

複数の環境で同じリソースを再利用する場合に適用でき、リソースの種類が多くて構成が複雑なプロジェクトにも対応が可能であると考えています。
モジュールの再利用が可能なため、管理効率が良い同じ構成を異なる環境で簡単に展開できるのはメリットではありますが、作成リソースが増えていくことでモジュール間の依存関係も複雑になる可能性があり、テストやデバッグにかかるコスト・リスクも増加することがかんがえられます。

パターン ③:コンポーネントベース+環境ごとの設定

├── components/
│   ├── network/
│   │   ├── virtual-network.bicep
│   │   ├── subnet.bicep
│   │   :
│   ├── storage/
│   │   ├── storageAccount.bicep
│   │   ├── blobService.bicep
│   │   :
│   ├── compute/
│   │   ├── virtual-machine/
│   │   │   ├── virtual-machine.bicep
│   │   │   ├── virtual-machine-extension.bicep
│   │   │   :
│   │   ├── virtual-machine-scale-set/
│   │   │   :
│   :   :
├── environments/
│   ├── dev/
│   │   ├── main.bicep
│   │   └── dev.parameters.json
│   ├── staging/
│   │   ├── main.bicep
│   │   └── staging.parameters.json
│   └── prod/
│       ├── main.bicep
│       └── prod.parameters.json

一番細かく分割したパターン。
大規模プロジェクトでリソースを用途(ネットワーク、アプリケーション、DB など)ごとに分けたい場合のパターンになります。
この構成であればチームが分業して各コンポーネントを管理することもできるので、効率的に開発が可能となります。
ただし、フォルダ階層が深くなって管理が複雑になりやすい面もあったり、各コンポーネント間の依存関係を明確にする必要もあります。

まとめ

Bicep での基盤開発にあたり、がんばってかんがえてみたフォルダ階層構成を記載しました。
ただ、今回紹介したフォルダ構成はあくまで一例であり、正解ではありません。(そもそも正解は無いとも思っています..)
この構成をベースとしてプロジェクトやチームで調整していくことで、「そのプロジェクト」の最適解になるのではないかと思います。
どの構成パターンもメリットデメリットがあるので、まだまだ創意工夫が必要ではありますが、何かの参考になれば幸いです。

また、その他にも Bicep のコード自体の記載方法(ネーミングルールやパラメータの外部化、CI/CD を利用したデプロイ)などについても今後記事にできればと考えています。

最後まで読んでいただきありがとうございました。
またどこかでお会いしましょう。

参考資料

https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/best-practices
https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/compute/virtual-machine/extension

Discussion