Repro では AWS 等のリソース管理に Terraform を活用しています。
この度 Terraform で管理しているコードの CI を Atlantis に移行したので、その経緯などについて書きます。
背景
Repro では以下のリソースを Terraform を使ってコード化して GitHub で管理しています。
- AWS で構築したインフラ
- DataDog のモニターやアラート
- Google Cloud Platform で利用している一部のリソース
- GitHub の reproio organization のメンバーやチーム
- Kafka Topic
- MySQL アカウント
- PagerDuty の通知まわりの設定
- Rollbar の通知
移行前は CircleCI や AWS CodeBuild を活用して独自に CI を構築していました。
課題
初期から CicleCI で構築していた CI は、独自の作り込みも多く、少し手を入れようとするとそこそこ手間がかかる状態になっていました。また Kafka Topic の管理と MySQL アカウントの管理は CircleCI 上で Terraform を実行するのではなく CodeBuild で実行していました。CodeBuild で実行していた理由は、CircleCI から AWS VPC 内で動いている MySQL や Kafka に安全な経路でアクセスするためです。
上記のとおり、複数の CI サービスに同じようなワークフローを構築して運用していました。
CircleCI や CodeBuild で構築したワークフローでは以下の通りです。
- plan
- コミットを積むたびに CircleCI/CodeBuild で
terraform plan
用ワークフローを起動する - いくつかの事前チェックを行う
terraform plan
を実行し、結果を S3 に保存するterraform plan
の結果を見やすく加工して PR にコメントする1
- コミットを積むたびに CircleCI/CodeBuild で
- apply
- PR をマージしたら
terraform apply
用ワークフローを起動する - いくつか事前チェックを行う
- S3 に保存した
terraform plan
の結果を使用してterraform apply
を実行する
- PR をマージしたら
事前チェックは、正しく安全に動作させたり、作業しやすくするためにそこそこ作り込んでいました。一部を抜粋します。
- PR のブランチが master ブランチに追従していなかったら CI を失敗させる
terraform fmt -check -diff -recursive
を実行して差分があったら CI を失敗させる- IAM User の名前がアルファベット順にソートされていなかったら CI を失敗させる2
*.tf
に書いてあるヒアドキュメントが JSON として valid でなければ CI を失敗させる
また、事前チェックではありませんが terraform plan
と並列で tflint によるチェックも実行していました 3。
このように作り込み部分も多くあり、同じようなワークフローを CicleCI と CodeBuild で複数メンテナンスし続けるのも負担に感じていました。
他にも課題はいくつかあって、これまであげたものも含めて列挙すると以下の通りです。
- CircleCI と CodeBuild で同じようなワークフローをメンテナンスしている
- CircleCI と CodeBuild でワークフローを書くための YAML の記法が異なる
- 作り込み部分のメンテナンスが大変
- CircleCI の環境変数やコンテキストが使いづらい
terraform apply
に失敗すると PR を新しく作らなければならないterraform apply
に失敗したとき、修正に手間取ると別の PR がマージされてしまうことがある
Atlantis の検討
軽く Atlantis について調査し、他社の事例等も確認してみたところ、課題が解決できそうな感触を得たので本格的に調査・検討しました。
CircleCI と CodeBuild で同じようなワークフローをメンテナンスしている
Terraform の CI を Atlantis に統一することで、ワークフローを集約でき、課題を解決できます。
CircleCI と CodeBuild でワークフローを書くための YAML の記法が異なる
Atlantis のワークフローにするので、新たに Atlantis の記法を覚える必要がありますが、とてもシンプルなので CircleCI と CodeBuild の記法で書かれたワークフローをメンテナンスし続けるのをやめて Atlantis に統一することでメンテナンス性の向上が期待できます。
作り込み部分のメンテナンスが大変
作り込み部分のほとんどは Atlantis の機能や設定と GitHub の設定で対応可能であることがわかりました。
機能 | 対応 | 補足 |
---|---|---|
terraform plan で作成した plan file を S3 に PUT する |
Atlantis では S3 を使わない | |
S3 に保存した plan file を GET して terraform apply を実行する |
Atlantis では S3 を使わない | |
terraform plan の結果を PR にコメントする |
Atlantis の機能に含まれている | |
様々なチェックの結果を PR にコメントする | Atlantis の機能に含まれている | |
terraform fmt -check -diff -recursive を実行して差分があったら CI を失敗させる |
Custom workflow を使う | |
PR のブランチが master ブランチに追従していなかったら CI を失敗させる | GitHub の Branch protection rule | Atlantis v0.28.5 では Rulesets に未対応 |
IAM User の名前がアルファベット順にソートされていなかったら CI を失敗させる | 最初はしない | Conftest で対応可能かも? |
*.tf に書いてあるヒアドキュメントが JSON として valid でなければ CI を失敗させる |
最初はしない | Conftest で対応可能かも? |
terraform apply に失敗したら Slack に通知する |
最初はしない | Atlantis にも Slack 連携はある |
PR にコメントする | Atlantis の標準機能 | Custom Workflow で標準出力に書き込むとその内容がコメントされる |
CircleCI の環境変数やコンテキストが使いづらい
CircleCI の環境変数やコンテキストは、CircleCI の管理画面で管理する必要があり4、コメントなども記録できません。そのため、誰が、いつ、何のためにその環境変数やコンテキストを追加したのかが把握しづらくなっています。
Repro では AWS のリソースが最も多いので Atlantis は ECS Task として動作させることにしました。そのため ECS Task Definition の secrets 経由で Parameter Store などが使えます。 Parameter Store であれば、説明を書けるので AWS マネジメントコンソールからも内容を確認できます。
terraform apply
に失敗すると PR を新しく作らなければならない
Atlantis では PR をマージする前に atlantis apply
とコメントすることで terraform apply
を実行します。そのため terraform apply
に失敗しても PR は open のままなので、修正コミットを積んでリトライできます。
Branch protection rules の設定によって、コミットを積んだら再レビューを要求できるので、うまく活用したいところです。
Atlantis のセットアップ
Atlantis のセットアップについては Installation Guide をよく読むのがよいです。
Terraform は基本的に各種 IaaS/SaaS の Web API を叩くことでリソースを管理するので Web API を叩ければどこに Atlantis を設置しても大丈夫です。
Repro では内部利用するためのリソースを置くための AWS アカウントに ECS Fargate でタスクを起動することにしました。terraform-aws-modules/atlantis/aws | Terraform Registry というコミュニティモジュールはありますが、そのまま使うとあとでカスタムしづらかったので、参考にして別のモジュールを作りました。
MySQL や Kafka のようにインターネットからアクセスできないプライベートネットワークに隔離されているリソースについては VPC Peering 等で経路を確保した上で、セキュリティグループでアクセス元を絞るような設定をしました。
Atlantis の設定
Atlantis の設定は大きく分けて3種類あります。
- Server Config
- Server Side Repo Config
- 起動時に読み込む設定で、全てのリポジトリに適用する workflow などを設定する
- Repo Config
ECS Task を使うので Server Configuration | Atlantis を見ながら環境変数を設定します。
各種 IaaS/SaaS へのアクセスは以下のように設定しています。
IaaS/SaaS | アクセス方法 | 補足 |
---|---|---|
AWS | AWS アカウントごとに特定の IAM Role に切り替える | 全アカウントで IAM Role 名は同じにしている |
Google Cloud Platform | Workload Identity | |
DataDog | API Key & App Key | |
GitHub | GitHub Apps | Atlantis で使用しているのとは別 |
PagerDuty | API token & User token | |
Rollbar | プロジェクトごとの API key |
Server Config や Server Side Repo Config は環境変数で設定できるので Terraform で管理しています。 API キーなどの秘匿情報は SSM Parameter Store に保存して ECS Task の secrets から環境変数に入れています。
tfmigrate の設定
以下の記事を参考に tfmigrate も使えるようにしました。
以前は CircleCi と CodeBuild で tfmigrate の history file を分けて管理していたのですが Atlantis に統一したので history file の管理も一本化しました。
Atlantis を導入してよくなったこと
Atlantis を導入してよくなったことをいくつか紹介します。
terraform plan/apply が早くなった
CircleCI だと変更をプッシュしてから CI が開始されるまで30秒以上かかっていましたが、ほぼ待ち時間がなくなりました。
特に terraform apply
は早くなってとても快適になりました。
これは Atlantis が動いている ECS Task 上で直接 Terraform を呼び出しているのが大きいと考えています5。
特定のファイルに変更があった場合のみ CI を実行するのが簡単かつ確実になった
CircleCI でもpath-filtering orbを使ってダイナミックコンフィグを使用したり、自分でフィルタリングするロジックを書けば実現可能でした。 Atlantisには機能として組込まれているので、簡単に書くことができ、特別な工夫をしなくても無駄な処理が走らなくなりました。
AWS のクレデンシャルを管理しなくてよくなった
以前から OIDC は導入していたのですが、一部 AWS のクレデンシャルを使わざるを得ない部分がありました。 今回 Atlantis を AWS Fargate で動かすようにしたので AWS のクレデンシャルを管理しなくてよくなりました。
メンテナンスするものが減った
メンテナンスするものが減りました。
- CircleCI のワークフロー(*.yml): 864 行
- CodeBuild のワークフロー(*.yml): 333 行
- CodeBuild のリソース(*.tf): 652+ 行6
- CircleCI/CodeBuild 用スクリプト: 1177 行
- Atlantis のワークフロー: 288 行
- Atlantis のリソース: 707+ 行7
- Atlantis 用スクリプト: 319 行
単純な行数だけで比べるのは難しいですがCircleCI/CodeBuild用のコードは合計で3026行あり、Atlantis用のコードは1314行と半分以下になりました。 またスクリプトは26個あったのが6個に減りました。
Atlantis 導入後、困ったこと
Atlantis 導入後、困ったことを紹介します。
tflint が CPU を使いすぎる
CircleCI/CodeBuild の頃は、tflint はほぼ全てのファイルをチェックするようになっていました。 無駄が多い実装だったのですが、CircleCI/CodeBuild では PR ごとに実行環境が分離されていたためリソース不足で困ることはありませんでした。
Atlantis では ECS Task 上で直接ワークフローを実行する仕組みになっているため、1つの ECS Task 上で複数の tflint プロセスを実行するようになりました。 そのため、複数の PR で同時に CI が実行されると、多くの tflint プロセスが実行され CPU が tflint の実行で埋まるようになってしまいました。
tflint の実行方法を見直して、一度の実行でチェックするファイルを減らすようにしました。
tfmigrate apply の実行中に別の PR から terraform apply を実行されてしまうことがある
tfmigrate apply
はその仕組み上、通常の terraform apply
よりも時間がかかるため、tfmigrate apply
の実行中に別の terraform apply
が実行される可能性がありました。
atlantis.yamlで指定しているプロジェクトやディレクトリが異なるため Atlantis でプロジェクトごとにロックをかけても、効果はありませんでした。
そのままだと tfstate が壊れて難しい状況になる可能性があったので、早急に対処が必要でした。
tfmigrate apply
実行中は全ての terraform apply
を実行させたくないのですが、公開された API はありませんでした。
調べてみたところAtlantis の issueにあるように、Atlantis のダッシュボードにある DISABLE APPLY COMMANDS
ボタンから使っている非公開 API を使えば tfmigrate apply
の前後でロック・アンロックの操作ができることがわかりました。
なお tfmigrate についてはロックのモードを on_plan
にしています。
まとめ
CircleCI/CodeBuild から Atlantis へ移行した背景や移行したあとどうなったかを紹介しました。
まだ Atlantis を活用しきれていない面もありますが、今後もより活用できるようにしていきたいです。
- https://tech.repro.io/entry/2022/07/05/113226↩
- これらのチェックは Ruby で実装されていましたが、長年の運用の中で動作しなくなっていました…↩
- tflint によるチェックに失敗するとワークフロー全体が失敗するように設定していました↩
- これらを管理するための terraform-provider-circleci のようなものもありましたが、それ単体では秘匿情報の管理ができませんでした。↩
- CircleCI/CodeBuild だと実行環境の準備にそこそこ時間がかかります。↩
- 一部共通リソースは集計していない↩
- 一部共通リソースは集計していない↩