モノレポ terraform で atlantis を活用している話

Atlantis とは

Atlantisをご存じでしょうか。

簡単に言うと

GitHubやGitLabなどでterraform を管理する際、自動で terraform plan 結果を表示してくれたり、 リポジトリ内で terraform apply まで行ってくれるツールで、terraform 作業をgit repositoryで完結させれるようになるツールです。

ただ、何も考えずに利用すると、 リポジトリのtopで terraform plan などと実行するため、ディレクトリに分割して管理していると辛い面があります。

そこで他の CIツールのように .atlantis.yaml というファイルを置いておくと、動作をある程度指定できるようになります。

version: 3
automerge: true
projects:
- name: my-project-name
  dir: .
  workspace: default
  terraform_version: v0.11.0
  autoplan:
    when_modified: ["*.tf", "../modules/**.tf"]
    enabled: true
  apply_requirements: [mergeable, approved]
  workflow: myworkflow
workflows:
  myworkflow:
    plan:
      steps:
      - run: my-custom-command arg1 arg2
      - init
      - plan:
          extra_args: ["-lock", "false"]
      - run: my-custom-command arg1 arg2
    apply:
      steps:
      - run: echo hi
      - apply

こちらがそのyamlのサンプルです。 公式のドキュメントから持ってきました。

分割統治する

atlantisでは、terraform plan/apply する対象を projectという単位で管理します。

上記サンプルでは、ルートディレクトリを起点とする、1つのプロジェクトしか記載がありませんが、リポジトリのトップをルートとして、任意のディレクトリをプロジェクトの起点として設定できます。

たとえば

version: 3
projects:
  - dir: service-A/
  - dir: service-B/

などです。

projectで必須なのはディレクトリ指定だけなので、最小の設定が上記のようなものになります。

プロジェクトが定義されていると、そのプロジェクトの起点ディレクトリより下にあるファイルが更新されると、更新されたプロジェクトのみが CI/CDの対象となります。

これにより、モノレポであっても、更新が必要な部分のみを対象として CI/CDが回せるようになります。とても便利です。

moduleを扱う

ただ、terraform には、共通部分を切り出すためにモジュールという機能があります。このモジュールは複数のprojectで共有されるものなので、モジュールが更新されたら、モジュールを利用しているプロジェクトはCI/CDの対象としたいところです。

そのための機能として、 autoplan という設定があります

たとえば

version: 3
projects:
  - dir: service-A/
     autoplan:
       when_modified: ["../modules/**/*.tf", "*.tf*"]
  - dir: service-B/

などです。

autoplanwhen_modified に、moduleの場所をそのプロジェクトのトップからの相対パスで指定しておくと、モジュールファイルが変更された際に、CI/CDの対象とすることができるようになります

任意の処理をはさむ

また、プロジェクトには当然、そこで利用するvarファイルやprovider.tfなどの設定が必要になりますが、これをいちいちプロジェクトごとに管理したくない、共通のものを利用したい、など、terraform plan/apply を独自に設定したいという場合もあります。

そのための機能として、 custom workflow があります

workflows:
  myworkflow:
    plan:
      steps:
      - run: terraform init -input=false -no-color
      
      # If you're using workspaces you need to select the workspace using the
      # $WORKSPACE environment variable.
      - run: terraform workspace select -no-color $WORKSPACE
      
      # You MUST output the plan using -out $PLANFILE because Atlantis expects
      # plans to be in a specific location.
      - run: terraform plan -input=false -refresh -no-color -out $PLANFILE
    apply:
      steps:
      # Again, you must use the $PLANFILE environment variable.
      - run: terraform apply -no-color $PLANFILE

公式ドキュメントにあるサンプルはこのようなものです。

plan apply 共に、steps に、行いたい処理をリストで書いていけます。 provider.tf をコピーしたりなど、行いたい作業を記載することができます。

私の会社では、 terraform apply の結果をslackに通知するなどのことも行っており、このcustom workflowを利用しています。

さらに便利にしたい

この atlantis.yaml ですが、管理が面倒そうだなという印象を受けるかと思います。なぜなら、プロジェクトが増えるたびに、依存するモジュールが増えるたびに、書き換える必要があるからです。

とてもではありませんが、それらをきちんと、手動で管理するのは無理です。

そこで、atlantis.yamlを自動生成するツールを作成して利用しています。

+-module-+-module1
|        +-module2
|
+-projects-+-projectA
         +-projectB

このように、モジュールを置くディレクトリをプロジェクトを置くディレクトリを分割し、ツールにはprojectsディレクトリを走査するようにして、 各projectのtfファイルを読んで、依存するモジュールリストを作成する。

ということを行っています。 モジュールがモジュールに依存するようなケースについてはいったんあきらめることでツール自体は簡素化しています。

リポジトリにあらたなプロジェクトを追加する際や、モジュールを新たに利用する際には、リポジトリにpushする前にツールを用いてatlantis.yamlファイルを更新する。

という方法で今のところは運用を行っています。 gitのhookでpush前に自動実行したりすると良いのでしょうが、今のところ、そこまで追加や削除が多くないので、pushした際に 再生成するのを忘れたことに気づいて再生成してpushするなどで運用は回っています。

おわりに

便利なAtlantisですが、実運用にのせてますという話があんまり多くなさそうだったのでこうして筆をとりました。

terraform plan/apply までがリポジトリのCI/CDで完結するのは、とても便利ですし、監査的にも良いのでかなりお勧めです。

plan結果がそのままpull requestに表示されるので、手動でplan結果を記載する手間もなくなり、ファイルの変更箇所と実際のインフラの変化がすぐにチェックできてとても良いです。

実運用に十分耐えられるのでぜひ使ってみてはいかがでしょうか。