はじめに
platform チームの渡部です。
RevComm の platform チームでは、チームトポロジーのプラットフォームチームのように、ストリームアラインドチームの開発における負担を軽減するために、サービスやプラットフォームの提供を行っています。
今回は、その取り組みの1つである Atlantis を使用した Terraform リポジトリの提供についてご紹介します。組織の拡大とともに Terraform の管理に課題を感じている方や Atlantis の導入を検討している方の判断材料になれば幸いです。
Atlantis とは
Atlantis は Terraform のデプロイを行う Issue Ops ツールの OSS です。
GitHub の Pull Request の issue comment にコマンドを入力して、terraform plan
や terraform apply
を実行し、Pull Request 上でリソースのリリースまで行います。
我々のチームでは、Terraform リポジトリへの permission を持つ Github App が、ECS Fargate にホストされた Atlantis アプリケーションと通信して terraform コマンドを実行します。
この記事では、v0.30.0 の Atlantis を使用しています。
背景
我々が管理する Terraform リポジトリは、Atlantis を導入する以前から、複数のストリームアラインドチームが開発を行うモノリシックなリポジトリでした。 それ故に、Terraform Configurations が肥大化したり、リリースが滞留したりするなどの問題を抱えるようになりました。
そのような課題を解決するために、platform チームは Atlantis を導入しました。 これによって、CI/CD ツールのコードが複雑になることなく、以下のメリットを享受できると想定しています。
- 変更と関係のないリソースが壊れるリスクを背負う必要がなくなる
- 権限や責任を分離したい粒度や並行に開発を行える粒度で柔軟に HCP Terraform workspace を作成することができる
以降、モノリシックな Terraform リポジトリに Atlantis を導入するにあたり、工夫した点を紹介します。
ディレクトリ構成
Atlantis 導入後も、我々が管理する Terraform リポジトリは、複数のストリームアラインドチームが開発を行うモノリシックなリポジトリです。 そのため、ストリームアラインドチームごとにディレクトリを用意して、各チームがその配下に自由にディレクトリを作成することができるディレクトリ構成にしました。
各チームは、権限や責任を分離したい粒度や並行に開発を行える粒度に Terraform configurations を分割して(以下、コンポーネントと表現)、コンポーネントごとにディレクトリを作成します。 コンポーネントディレクトリ配下には、そのコンポーネントのモジュールや、そのコンポーネントのリソースがリリースされる AWS アカウントごとにディレクトリが分かれて管理されています。 下記はディレクトリ構成のイメージです。
. ├── stream_aligned_team_1 │ ├── component_1 │ │ ├── aws_account_1 │ │ │ ├── main.tf │ │ │ ├── providers.tf │ │ │ [omitted] │ │ │ │ │ ├── aws_account_2 │ │ │ ├── main.tf │ │ │ [omitted] │ │ │ │ │ └── modules │ │ ├── main.tf │ │ ├── variables.tf │ │ [omitted] │ │ │ └── component_2 │ ├── aws_account_1 │ [omitted] │ ├── stream_aligned_team_2 │ │ │ [omitted] │ [omited]
このようなディレクトリ構成にすることで、コンポーネントのデプロイ時に関係のないリソースが壊れるリスクを回避することができます。 また、後述のモジュールのバージョニングの際に、このディレクトリ構成をtag の命名規則に反映しています。
モジュールの運用
Terraform module は source に Github リポジトリを指定して使用することができます。
我々が管理する Terraform リポジトリでは、この機能を使用して、自リポジトリを参照することで、バージョン管理した module を使用しています。 具体的には、下記のフローでモジュールのバージョニングとその使用を行っています。
./stream_aligned_team_1/component_1/modules
配下にmoduleA
を作成して main ブランチにマージします。- Github の Releases 機能を使用して
stream_aligned_team_1-component_1-v1.0.0
という tag を作成してリリースします。 ./stream_aligned_team_1/component_1/aws_account_1/main.tf
では、以下のようにmoduleA
を指定して使用します。
module "this" { source = "git::ssh://git@github.com/{org_name}/{repo_name}//stream_aligned_team_1/component_1/modules/moduleA?ref=stream_aligned_team_1-component_1-v1.0.0" }
このとき、tag 名は Terraform リポジトリ内で一意になるように{stream_aligned_team_name}-{component_name}-{semantic_versioning}
の命名規則に沿うように行います。
これにより、チームごとにモジュールのバージョニングが可能になります。
また、開発環境用の AWS アカウントでは、module を Github リポジトリ参照ではなく、相対パスによる参照を行なっています。 これにより、わざわざ main ブランチへのマージと Release を行うことなく、module の動作確認やデバッグを行いながら開発を行うことができます。
AWS IAM role の設定
Atlantis は 1 つの AWS アカウントにのみホストされています。
したがって、各環境へ terraform apply
するためには、各環境の terraform コマンドを実行するための IAM Role を assume role しなければなりません。構成は下記のようになります。
また、我々が管理する Terraform リポジトリでは、ファイルレイアウトによってステートファイルを分離する方針をとっており、各 AWS アカウントのリソースの tfstate は、その AWS アカウントの remote backend(S3 bucket と DynamoDB table) に配置されています。 各 AWS アカウントの remote backend にアクセスするときにも、下記のように IAM role を assume role して tfstate を更新しています。 これにより、各環境は完全に分離されます。
backend "s3" { region = "ap-northeast-1" bucket = "account1-tfstate" key = "stream_aligned_team_1/component_1/terraform.tfstate" dynamodb_table = "account1-locks" encrypt = true assume_role = { role_arn = "arn:aws:iam::account1:role/account1-tf-exec-role" external_id = "account1-external-id" } }
おすすめの Atlantis の機能
Atlantis の機能は多岐に渡ります。
基本的な機能としては、atlantis plan
の実行前に該当のディレクトリをロックする Looking 機能や、Pull Request 作成時にatlantis plan
を自動的に実行する Autoplanning 機能などがあります。
最後に、以下のようなことを実現したいときに、Atlantis のどの機能を使えばよいかを紹介して終わりにしたいと思います。
特定の条件がパスされないと atlantis apply
を実行できないようにしたい
Atlantis には Command Requirements 機能という機能があります。
これは、atlantis plan
やatlantis apply
コマンドを実行する前に、特定の条件を満たすことを強制する機能です。
例えば、Atlantis の設定ファイルに下記のように設定すると、Pull Request が「マージ可能(Mergeable
)」な状態でなければatlantis apply
を実行できなくなります。
repos: - id: /.*/ apply_requirements: [mergeable]
「マージ可能」な状態は、Atlantis を使用する VCS によって異なります。
Github の場合は、branch protection rule をパスした状態を指します。
あとは、Github の Settings
で有効にしたい条件をチェックして、マージ可能な状態でなければ、下記のように atlantis apply
は失敗します。
atlantis apply
が実行されていない Pull Request がマージされないようにしたい
Atlantis を使用していると、atlantis apply
を実行する前に、誤って Pull Request をマージしてしまうことがあります。
こうなると、いちいち Pull Request を作り直すことになり、面倒です。
なので、下記のように Github の status checks にatlantis/apply
を追加したくなります。
追加して、いざatlantis apply
を実行してみると、上記のMergeable
で試したときと同じように失敗します。
これは、Mergeable
を有効にすると、Atlantis が Github の status checks もパスしたかを確認してしまうためです。
つまり、atlantis apply
を実行するためにatlantis/apply
をパスしなければならないという八方塞がりの状態になってしまいます。
そんなときのために、Altantis にはatlantis apply
を実行する前に、atlantis/apply
の status check をスキップして、マージ可能かどうかをチェックすることができるオプションが用意されています。
それが--gh-allow-mergeable-bypass-apply
です(環境変数でも設定可能です)。
atlantis server --gh-allow-mergeable-bypass-apply
これで、Mergeable を設定した状態で、atlantis apply
が実行されていない Pull Request がマージされないようにすることができます。
まとめ
Terraform のデプロイを行うツールである Atlantis と、Atlantis を導入した Terraform リポジトリの運用について紹介しました。
Atlantis には、今回紹介した機能以外にもたくさんの機能があります。ngrok を使用すればローカル環境でも気軽に試してみることができますので、興味のある方はぜひ動かしてみてください。