見出し画像

renovateを加速するためにAtlantisにcontributeした話

SREチームの沼田です。今回は、renovateを高速に処理する上で、Atlantisが想定外の障壁となった話を書きます。

我々SREチームでは、依存関係のバージョンアップにrenovateを活用しています。対象の1つにTerraformがありますが、Terraformは本体以外にもProviderやModuleなど、依存関係が意外と多岐にわたるツールであり、さらにその1つ1つが高頻度に更新される傾向にあります。例えばterraform-provier-awsは、ほぼ1週間に1回更新されています。また、弊社では複数のTerraformコードをモノレポ管理しており、renovateではTerraformのディレクトリごとにPRを分ける設定をしています。一度providerなどが更新されると、ディレクトリの数と同じだけのPRが作成され、すべてマージしていくことになります。

そのため、高速にrenovateを実行し、マージしていきたいところですが、それには様々なテクニックが必要になることがあります。今回はAtlantisとの関係で問題がありました。

Atlantisを使っているとauto mergeができない

Atlantisは、GitHubやGitLabなどと協調し、Pull Request上での terraform plan と apply を可能にするOSSです。Pull Request作成時には自動的に terraform plan を実行して、その結果をPR commentとして貼付してくれます。その後、「atlantis apply」という文字列をPR commentで書き込むことで、先のplan結果に応じたapplyをAtlanitsが実行します。PR上でTerraformの想定差分を確認し、実行の記録まで残せる便利なツールです。

しかし、このAtlantisがrenovateを回す上でのハードルの1つとなっていました。

PR上でTerraformを実行できるのは非常に便利なのですが、裏を返すと、PR以外を通じてAtlantisでTerraformを実行させる手段がありません。そのため、うっかりapplyを忘れたままPRをmergeしてしまうと面倒なことになります。

これを防ぐため、PRがmergeableになる条件として、「atlantis/applyのcommit statusが『success』になっていること」を指定するというプラクティスがあります。これにより、 applyが成功していなければPRがmergeできなくなり、apply忘れを防ぐことができます。

Atlantisを運用していく上で、ほぼ必須とも言えるこのプラクティスですが、renovateを回す上では邪魔になることがありました。

renovateを高速に回す上での手段の1つがauto mergeです。これは、renovateが作成したPRがmergeableな状態になれば、自動的にPRをmergeしてくれるという設定です。これにより依存関係の更新作業を半自動化できます。しかしAtlantis + Terraformの場合、 atlantis applyを誰かが実行しなければ mergableにならない設定をしているため、自動作成されるrenovateのPRにおいては、auto mergeの恩恵を受けることができずにいました。

auto mergeのためにAtlantisの挙動を変える

この問題を解消する方法はいくつかあるかと思いますが、今回選択したのはAtlantisに手を入れる方向性でした。具体的には、planの結果がNo Changes 、つまりresourceに対する変更が入らないのであれば、 applyを実行していなくとも、atlantis/applyのstatusはsuccessとなるべきではないかという考え方です。Atlantisにおけるcommit statusの扱い自体を変える形での解決を考えました。

類似の例としては、同じくTerraformの実行ツールであるTerraform Cloudにおいて、No Changesの際はapplyの実行がスキップされるという仕様があります。No Changesでもapplyが必要となるケースは考えづらかったですし、このアイデアをissueとして起票したところ、それなりの数の +1 も得られたので、実装を試みることにしました。

https://github.com/runatlantis/atlantis/issues/2546

実際に出したPRが以下のものです。

要点だけ解説します。altnaits/applyのcommit status判定ロジックは以下のようになっています。

status := models.SuccessCommitStatus

//  ...中略...

if numErrored > 0 {
	status = models.FailedCommitStatus
} else if numSuccess < len(pullStatus.Projects) {
	// If there are plans that haven't been applied yet, we'll use a pending
	// status.
	status = models.PendingCommitStatus
}

Atlantisでは、Terraformの実行単位はProjectというオブジェクトで管理されます。SuccessしているProjectの数を表すnumSuccessが、全Project数に対して不足していれば、atlantis/applyは「pending」のstatusになります。エラーになっているProjectが存在している場合は「failure」です。それ以外の場合が「success」となります。

PRでは、numSuccessの集計方法を変更し、「Planを実行し、その結果がNo Changesになった」Projectも「成功」として扱うようにしました。

- numSuccess = pullStatus.StatusCount(models.AppliedPlanStatus)
+ numSuccess = pullStatus.StatusCount(models.AppliedPlanStatus) + pullStatus.StatusCount(models.PlannedNoChangesPlanStatus)

Projectには .PlanSuccess.NoChanges() という、Plan結果における差分の有無を示す真偽値を返すメソッドがすでに存在しており、これを活用することで実装はかなり楽に済みました。

ちなみにこの変更は、当初オプションとして導入するつもりでPRを作成したのですが、デフォルトでこの挙動でも問題ないのではないか、という声がコミッターから挙がり、数か月の議論の末にオプションではなく仕様の変更として取り込まれています。これにより Atlantis v0.25.0 より、すべてのAtlantisにおいて、No Changesの場合はatlantis/applyがsuccessとみなされるようになりました。

auto mergeの実現とさらなる課題

肝心のrenovateは加速できたのか?という点ですが、結果としては「多少速くなった」というところです。目論見通りにauto mergeできるようになったのは嬉しいところで、renovateのPRを処理出来る数は確実に増えています。

一方で別の課題があり、思ったほどにはPR mergeが増えていない面もあります。

大きな原因が、「ベースブランチとPull Requestの同期の維持」を行っており、常にベースブランチをPRに取り込んでいなくてはならないとしている点です。Atlantisがmerge前のPR branch上でapplyを行う都合上、この設定を行わないと、ベースブランチから劣後した古いTerraformコードをapplyしてしまう危険性が出てきます。

この設定があるがために、renovateではprHourlyLimitを1に指定して、複数のPRが同時に作成されないようにしています。仮に複数PRの同時作成を許可したとしても、先にauto mergeされなかったほうのPRはその時点でベースブランチから劣後することになり、「同期の維持」の条件からnot mergeableと判定され、auto mergeされずに残存することになってしまいます。従ってPRの複数同時作成には意味がなく、 prHourlyLimit は1にせざるを得ない状況です。

そしてこのprHourlyLimitなのですが、あくまで1時間以内に作成「可能な」PRの上限値であり、必ず1時間に1PRを作ってくれる、というものではありません。ドキュメントの記載は見つからず、動作ログからの推定にはなりますが、我々が利用しているrenovateのGItHub Appの場合、PRを作成するのは数時間に1回のようです。renovateは夜間にのみ実行させているのですが、結果として一晩のうちに3〜4PR程度しかauto mergeできない、という状況になっています。

auto mergeが可能になったことで、確かに楽になった面は大きいのですが、まだまだ加速させたいところです。これ以上加速させるのであれば、GitHub Appでの実行をやめ、セルフホストしたrenovateで実行頻度を調整するしかないのではないか、と今は考えています。

まとめ

今回の「renovateを加速したい」という問題ですが、他の解決策としては「renovateのPRに『atlantis apply』と書き込むGitHub Actionを作る」といったことも考えられました(考慮が漏れるとかなり危険なものになりそうではありますが)。しかし、それでは自社内の問題解決に留まってしまいます。事実、今回の件で建てたissueには、同じことで悩んでいるユーザーの声が寄せられ、これが我々だけの問題ではないことが実感できました。OSSを活用していくなかで、自社が直面した問題についてレイヤーを上げて解決していく、良い経験になったと感じています。

一方、デフォルト挙動を変える結果になったのは予想外ではありました。確かに副作用の小さい変更のようにも思いましたが、私の想定できていないユースケースがあるのではないかと、リリース前はかなり悩まされました。実のところ、このPRに起因した不具合は出ているようです。GitLabではcommit statusの扱いがGitHubと少し異なる部分があるようで、想定外の挙動になっていることがあるようでした。

ただ、すでにissueが立ち、修正PRも作られつつあります。このように複数の目が入りながら開発が進んでいくのがOSSの良いところなのだと改めて実感しましたし、だからこそ、あまり怖がらずに今後も開発へ関わっていきたいところです。

一緒に働く仲間を募集しています!

グロービスの開発組織では、一緒に働ける仲間を探しています。
まずは、カジュアル面談を通して、あなたに合う組織かどうか確かめてみませんか?
https://recruiting-tech-globis.wraptas.site/

この記事が気に入ったらサポートをしてみませんか?