【Atlantis】複数のTerraformデプロイ環境の一元管理
はじめに
当たり前ですが、受注案件が増えてくると管理するインフラの数も増えます。
TerraformのようなIaCツールを使用している場合、同じようなインフラ構成であれば1つのコードでデプロイ環境を管理するニーズがあります。(DRY原則)
ただし、同じmoduleを使い回す場合、1つの変更でも環境の数だけplan/applyの実行が必要となるため、各アプリケーションの連携や責任を考えると複数人のローカルでデプロイを管理することは組織として避けるべきです。
この記事では、リモートベースでTerraformのデプロイを行うツールであるAtlantisを使用した複数のデプロイ環境の一元管理方法を紹介します。
(私たちはこの管理方法の導入によりプロトタイプ型の開発の効率化・高速化を実現し、受託案件の受注増加に貢献しました☺️)
Atlantis
簡単に説明すると、AtlantisとはPull RequestのコメントでTerraformコマンドの実行が可能なTerraform管理ツールです。
Atlantisはセルフホストで使用するOSSであり、去年CNCF Sandboxに採択されているなどホットなツールです!
Atlantisの利用には次のようなメリットがあります。
1. Terraformをリモートで管理
複数人のローカル環境でTerraformコマンドを実行する場合、下のような問題が起きる可能性があります。
例えば任意のインフラを管理するTerraformにおいて複数人が並行してタスクを行う場合、誰かがapplyすると他の人のコードと実際のインフラ構成に差分が発生するため、予期せぬplan結果となります。(Drift Detection)
もちろん、リモートのmainブランチをSSOT(Single Source of Truth)として、常にmainは最新の状態であるような完全な開発体制が可能であれば問題はありません。
しかし、modulesなどで複数環境を1つのTerraformで管理している場合、開発ブランチの数だけ複雑になります。
上のようにAtlantisを導入すれば次のような恩恵を受けることができます!
・Pull RequestのコメントでTerraformのコマンドを実行
具体的には、コメントでatlantis plan
やatlantis apply
を実行することでwebhookを経由してAtlantisサーバでTerraformコマンドを実行します。
また、-p
や-d
のオプションを使用すればプロジェクトやディレクトリごとにTerraformコマンドの実行環境を分割できるため、簡単に複数環境でデプロイを実行できます。
・WEB画面でstate lockを管理
Atlantisのstate lock機能は非常に優秀であり、各環境ごとにplan/applyが可能なPull Requestを唯一つに制限できます。
環境ごとのstate状態を管理するWEB画面が存在するため、どの環境のどのPRでstate lockがかかっているかが一目でわかります。
ちなみに、ローカル環境でstate lock機能を使用するためには、tfstateのバックエンド設定にS3とDynamoDBを設定することで実現可能です。
(Terraform v1.10からはstate lockにDyanamoDBを設定する必要がないそうです!)
・apply後にPull Requestをマージ
Terraformのデプロイパイプラインツールとして有名なHCP Terraform(Terraform Cloud)は、SSOTなmainブランチの内容をもとにplan/applyを実行するSaaSプラットフォームなため、applyによるエラーは別途mainブランチを修正して再度plan/applyを実行する必要があります。
その点、AtlantisはPull Request上のコメントでplan/applyを実行するため、planで検知できないようなapplyエラーに対してもそのPull Request上で迅速に対応できます。
さらに、atlantisをカスタマイズすればいくつかのブランチルールをplan/applyに対しても適用できます。
例えば、approved
を適用すればコマンド実行にapproveが必須となり、undiverged
を適用すれば最新のmainブランチの反映が必須となるため、よりレビューベースでplan/applyを実行できます。
より具体的にAtlantisを紹介していた記事があるので、気になる方は参考にしてください。
2. Atlantisに必要な権限とコストはセルフホストのサーバのみ
HCP Terraformはマネージドなデプロイパイプラインが簡単に組める点では非常に魅力的です。しかし、HCP Terraformはresourceの数が500を超えると従量的にコストがかかるようです。
案件の数に比例してリソースも増えるため、コストの観点だけを見ればHCP Terraformはおすすめできません。その点、AtlantisはセルフホストなOSSであるため、リソースがいくら増えようともAtlantisサーバのランニングコストのみです。また、複数環境のようにAWSマルチアカウント環境に対してローカルでterraformコマンドを実行する場合、開発者数×環境数のエンティティに実行権限を発行する必要がありました。
漏洩対策でIAMユーザではなくSSOにしている場合でも、AWSアカウントの数が増えるほどprofileの切り替え忘れなどによるヒューマンエラーの可能性が増えます。
AWSの場合、AtlantisをEC2やECSにホストすれば実行権限のエンティティをAWSリソースにできるため、開発者ごとに実行権限を発行する必要がありません。
そして、上図のようにAtlantisサーバが存在する管理アカウントを起点としたクロスアカウントの設定を行えば、atlantis plan -p projectA
やatlantis plan -p projectB
のようにコマンドだけでデプロイ環境を切り替えることができます。
もちろんコマンドミスも考えられますが、state lock機能やapproved機能など様々なヒューマンエラーを防ぐ方法があるため安心です。
Terraform構成
最後に私たちが実際に運用しているTerraform構成の概要を紹介します。
ディレクトリ構成
├── atlantis.yaml
├── accounts
│ ├── management
│ │ ├── main.tf
│ │ ├── iam_for_tfstate.tf
│ │ └── iam_for_atlantis.tf
│ ├── projectA
│ │ ├── main.tf
│ │ ├── iam_for_tfstate.tf
│ │ └── iam_for_atlantis.tf
│ └── projectB
├── applications
│ ├── projectA
│ │ ├── prod
│ │ │ ├── main.tf
│ │ │ ├── locals.tf
│ │ │ └── modules.tf
│ │ └── staging
│ └── projectB
│ ├── prod
│ └── staging
├── managements
│ └── atlantis
│ ├── main.tf
│ ├── iam.tf
│ └── modules.tf
├── modules
│ ├── vpc
│ ├── alb
│ ├── ecs
│ ├── rds
│ ├── ...
- atlantis.yaml
-
-p
オプションやTerraformコマンドの実行順序などの設定ファイル
-
- accounts
- Terraformコマンドの実行権限やバックエンドのアクセス権限などクロスアカウントのためのIAMを作成
(例) iam_for_atlantis.tf
resource "aws_iam_role" "atlantis_access_from_management_account" { name = "atlantis-access-from-management-account" assume_role_policy = jsonencode({ "Version" : "2012-10-17", "Statement" : [ { "Effect" : "Allow", "Principal" : { "AWS" : "arn:aws:iam::{management_account_id}:root" }, "Action" : "sts:AssumeRole", "Condition" : {} } ] }) } data "aws_iam_policy" "atlantis_access" { arn = "arn:aws:iam::aws:policy/AdministratorAccess" } resource "aws_iam_role_policy_attachment" "atlantis_access" { role = aws_iam_role.atlantis_access_from_management_account.name policy_arn = data.aws_iam_policy.atlantis_access.arn }
- applications
- 各プロジェクトのインフラ設定
- managements/atlantis
- atlantisサーバの設定
(例) iam.tf
target_accounts = [ { id = "111111111111", name = "projectA" }, { id = "222222222222", name = "projectB" }, ] resource "aws_iam_policy" "atlantis_access_policy" { name = "atlantis-access-policy" policy = jsonencode({ "Version" : "2012-10-17", "Statement" : [ for account in local.target_accounts : { "Effect" : "Allow", "Action" : "sts:AssumeRole", "Resource" : "arn:aws:iam::${account.id}:role/atlantis-access-from-management-account" } ] }) } resource "aws_iam_policy" "s3_tfstate_access_policy" { name = "s3-tfstate-access-policy" policy = jsonencode({ "Version" : "2012-10-17", "Statement" : [ for account in local.target_accounts : { "Effect" : "Allow", "Action" : "sts:AssumeRole", "Resource" : "arn:aws:iam::${account.id}:role/s3-tfstate-access-from-management-account" } ] }) }
- modules
- インフラの基盤となるmodule群
下の記事のようにmoduleを使わずにtfstateの切り替えを行う方法もありますが、私たちの場合すべての案件で同じ構成になるわけではない(例えばメールは使わないためSESを外すなど)ため、moduleで複数環境を管理する方法を取りました。
ちなみに、Atlantisサーバのホスト方法ですが、Terraform moduleが提供されているため簡単に構築できます。
このようなディレクトリ構成にすることで、新たに案件が始まった場合も簡単にAWSアカウントを追加して検証や本番環境の作成・保守が可能になりました!
Discussion