IaCの治安を守るために
はじめに
先月ぶりです、バックエンドエンジニアの満江です。
今回は IaC(Infrastructure as Code)化を進める上で悩ましい「リポジトリ構成」や「ディレクトリ構成」、「命名規則」などの整備を進めたので紹介したいと思います。
Terraform などのツールを使用している方であれば、 ベストプラクティスはサービスの規模や複雑さ によって異なるということはご存知かと思います。
そのため今回の記事は弊社サービスでの一例をご紹介するものであり、参考程度にしていただければ幸いです。
状況
弊社ではクラウドリソースを 手動作成, Terraform, Serverless Framework の3種類の方法で管理しています。
1. ツール選定
まず IaC 化のツールとして Terraform と Serverless Framework の2つを採用しています。
基本的には Terraform で管理し、Lambda とそれに紐付く EventBridge や IAM Role などのみ Serverless Framework で管理しています。
Serverless Framework を使用している理由としては Serverless Framework と Lambda の相性が良い点と、Serverless Framework Compose を積極採用し複数 Lambda をまとめて管理しているためです。
2. リポジトリ構成
最初にぶつかる壁は「どうGitリポジトリを分けるか」です。
考え出すと下記のような候補が出てくると思います。
Terraform/ServerlessFramework で分ける
サービス単位で分ける
リソース単位で分ける
etc...
この中から弊社では「サービス単位で分ける」を採用しました。
ただしネットワーク周りや IAM User など、 "サービスに依存しないリソース" は共通リポジトリを用意し管理しています。
- サービスAリポジトリ
- サービスBリポジトリ
:
- 共通リポジトリ
こうすることで tfstate の管理を行いやすく、またサービス間での依存関係を明確にすることができます。
つまり tfstate の分割粒度は大きくてもサービス単位になりますね。
3. ディレクトリ構成
リポジトリの分け方が決まると次に悩むのが「ディレクトリ構成」です。
上記のリポジトリ構成を見直すと2種類のディレクトリ構成を考える必要がありそうです。
サービスに依存したリポジトリ
サービスに依存しないリポジトリ
3-1. サービスに依存したリポジトリ
まず考えたいのは「どの順で階層にするか」です。
例えばクラウドサービスプロバイダやツール、環境などがこれにあたります。
これらを踏まえて、弊社では下記のようなディレクトリ構成を採用しました。
.
├── aws/
│ ├── terraform/
│ │ ├── environments/
│ │ │ ├── development/
│ │ │ │ └── main.tf
│ │ │ ├── production/
│ │ │ │ └── main.tf
│ │ │ └── staging/
│ │ │ └── main.tf
│ │ └── modules/
│ └── serverless/
│ └── serverless.ts
└── gcp/
└── terraform/
├── environments/
│ ├── development/
│ │ └── main.tf
│ ├── production/
│ │ └── main.tf
│ └── staging/
│ └── main.tf
└── modules/
まずクラウドサービスプロバイダごとにディレクトリを分けています。
次にツールごとにディレクトリを分け、最後に環境ごとにディレクトリを分けています。
Serverless Framework では stage で環境を分けることができるため、terraform/ ディレクトリのみ環境ディレクトリを用意しています。
また下記のようにプロバイダとツールを逆にしても良いと思います。
.
├── terraform/
│ ├── aws/
│ │ ├── environments/
│ │ │ ├── development/
│ │ │ │ └── main.tf
│ │ │ ├── production/
│ │ │ │ └── main.tf
│ │ │ └── staging/
│ │ │ └── main.tf
│ │ └── modules/
│ └── gcp/
│ ├── environments/
│ │ ├── development/
│ │ │ └── main.tf
│ │ ├── production/
│ │ │ └── main.tf
│ │ └── staging/
│ │ └── main.tf
│ └── modules/
└── serverless/
└── aws/
└── serverless.ts
この辺は CI/CD の設定ファイルを置く位置や、使用しているプロバイダの数、もっと言うと好みによって変わってくると思います。
極端な話、AWS にベンダーロックインしている場合は前者の方がスッキリします。
逆に GCP や Azure など複数のプロバイダを使用している場合は複数プロバイダのリソースを同時にデプロイするケースが存在し、後者は Makefile を terraform/ 直下に配置すると CI/CD の設定ファイルを管理しやすそうです。
実際、運用してみないとメリットデメリットがわからないと思うのでそこまで深く考えずに進めていく方が良いかもしれません。
Terraform に関しては tfstate の分割粒度や管理をしっかりしていれば、ディレクトリ構成を変更しても問題ないですし。
3-2. サービスに依存しないリポジトリ
次に「サービスに依存しない」リポジトリのディレクトリ構成を考えてみます。
サービスに依存しないリソースをどこに置くかがポイントになりそうです。
こちらも上記3-1の「サービスに依存する」リポジトリの構成と同じようなディレクトリ構成を採用しました。
.
├── aws/
│ ├── terraform/
│ │ ├── environments/
│ │ │ ├── development/
│ │ │ │ ├── iam/
│ │ │ │ │ └── main.tf
│ │ │ │ └── network/
│ │ │ │ └── main.tf
│ │ │ ├── production/
│ │ │ │ ├── iam/
│ │ │ │ │ └── main.tf
│ │ │ │ └── network/
│ │ │ │ └── main.tf
│ │ │ └── staging/
│ │ │ ├── iam/
│ │ │ │ └── main.tf
│ │ │ └── network/
│ │ │ └── main.tf
│ │ └── modules/
│ └── serverless/
│ ├── function-A/
│ │ └── serverless.yml
│ └── function-B/
│ └── serverless.yml
└── gcp/
└── terraform/
├── environments/
│ ├── development/
│ │ └── big_query/
│ │ └── main.tf
│ ├── production/
│ │ └── big_query/
│ │ └── main.tf
│ └── staging/
│ └── big_query/
│ └── main.tf
└── modules/
ポイントは各環境配下に共通リソース用のディレクトリを追加している点です。
ここには IAM やネットワークなど、サービスに依存しないリソースを置いています。
terraform/ 配下に置かれる共通リソースは特段理由がなければ 1ディレクトリ=1tfstate となると思っています。
また serverless/ 配下に置かれるリソースは、サービスに依存しないような Lambda を置きます。
例えば社内で使用している bot などがこれにあたります。
4. 命名規則
外枠が固まったのでいよいよ実装に入りたい所ですが、このまま複数人が実装するとカオス化しそうです。
その要因の一つに「命名規則」があります。
もちろんコーディングルールや tfstate,module の分割粒度も要因ですが、それはまた別の機会に。
今回制定した命名規則は以下の通りです。
tfstate 管理用の S3 バケット名
リソースへ付与するタグ名
4-1. tfstate 管理用の S3 バケット名
バケット名の命名規則では「一貫性があり検索しやすい」を焦点に置き、 下記の命名規則を採用しました。
fukurou-tfstate-<environment>-<service>-<resource>
バケット名は全世界でユニークにする必要があるため、先頭に `fukurou` を付与しています。
次に、バケット名から tfstate 管理用バケットであることがわかるように `tfstate` を付与しています。
不変な値を前半へ寄せ、後半に可変な値を寄せることで検索しやすく、AWSコンソール上でのバケット一覧も綺麗に並びます。
以下は可変値の説明です。
<environment> は環境名を指定します。
<service> はサービス名を指定します。
サービスに依存しないリポジトリの場合は、サービス名を指定出来ないため `shared` を指定します。
fukurou-tfstate-production-shared-iam
<resource> はリソース名を指定します。
サービスに依存しないリポジトリの場合は、そのリソース名を指定します。
サービスに依存するリポジトリの場合は、tfstate の分割粒度がリソースとならない場合もあります。
例えば小さなサービスの場合、1サービス=1tfstate となるケースです。
その場合は、`all` を指定するようにしています。
fukurou-tfstate-production-mini-service-all
4-2. リソースへ付与するタグ名
冒頭でも述べましたが、弊社ではクラウドリソースを 手動作成, Terraform, Serverless Framework の3種類の方法で管理しています。
運用していると「このリソースってどうやって作ったっけ?」となることが予想されるため、リソースには必ずタグを付与することをルール化しています。
サービスに依存するリポジトリでは下記のタグを付与しています。
service = <service_name>
repo = <repo_name>
managed_by = <terraform|serverless>
サービスに依存しないリポジトリでは下記のタグを付与しています。
repo = <repo_name>
managed_by = <terraform|serverless>
またタグの付け忘れを防ぐために Terraform では default_tags でタグの管理を行なっています。
さいごに
今回は弊社での IaC の運用方法について紹介しました。
長くなりそうだったので全てを紹介できたわけではありませんが、是非参考にして頂ければ幸いです。
また、まだまだ改善点はあると思うので今後も積極的に改善していきたいと思っています。
直近は IaC の管理を属人化させないために、どうチームに浸透させるかが課題になりそうです。
「俺に任せろ!!」と思った方や、「一緒に開発してみたい!!」と思った方は是非採用ページを覗いて行ってください。
カジュアル面談もやっているのでお気軽にご連絡ください!
この記事が気に入ったらサポートをしてみませんか?