RevComm Tech Blog

コミュニケーションを再発明し 人が人を想う社会を創る

Atlantis はじめました

はじめに

platform チームの渡部です。

RevComm の platform チームでは、チームトポロジーのプラットフォームチームのように、ストリームアラインドチームの開発における負担を軽減するために、サービスやプラットフォームの提供を行っています。

今回は、その取り組みの1つである Atlantis を使用した Terraform リポジトリの提供についてご紹介します。組織の拡大とともに Terraform の管理に課題を感じている方や Atlantis の導入を検討している方の判断材料になれば幸いです。

Atlantis とは

Atlantis は Terraform のデプロイを行う Issue Ops ツールの OSS です。 GitHub の Pull Request の issue comment にコマンドを入力して、terraform planterraform 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 を使用しています。 具体的には、下記のフローでモジュールのバージョニングとその使用を行っています。

  1. ./stream_aligned_team_1/component_1/modules 配下に moduleA を作成して main ブランチにマージします。
  2. Github の Releases 機能を使用して stream_aligned_team_1-component_1-v1.0.0 という tag を作成してリリースします。
  3. ./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 planatlantis 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 を使用すればローカル環境でも気軽に試してみることができますので、興味のある方はぜひ動かしてみてください。