tech.guitarrapc.cóm

Technical updates

Infrastructure as Code に最低限求めること

Infrastructure as Code (以降 IaC) で組むときに IaC ツールを選ぶ基準はいろいろあります。 IaCの言語がチームとって扱いやすいか、クラウドやサービスへのIaCの対応状況は早い/十分か、コミュニティの大きさは十分でググれば情報を入手できるか、実装がオープンソースで公開されているか、利用事例が欲しいか、など様々な理由で選ぶことでしょう。

しかし、こういった基準よりまず 「IaC というからには最低限出来ていないと困ること」というのが個人的にあります。

最近、Bicep で組んでいた Azure の環境を Pulumi に移行したのですが、その理由は Bicep では IaC が維持できないと判断したためでした。 IaC ツールを使っていて IaC が維持できないと判断したのは初めてだったので、いい機会ですし IaC に最低限求めることを言語化しようと思います。

tl;dr;

私はIaC に、コードとリソース1 状態の一致保障、実行プランが出力できること、ステート2 を持てることを求めます。 これらが不完全なIaCでは、コードでのリソース管理は不完全なものになり、今回のように維持できない原因になるため避けます。

  • コードとリソース状態の一致を保障できることを求めます。一致が保障できることで、コードからリソースの状態を完全に制御できます。(コードファースト)

コードとリソースの状態が一致

  • 実行前に実行プランを出力できることを求めます。実行プランによりコードの変更適用前にリソースへの影響がわかるため、PRベースでレビューをうけつつ開発ができます。また、正確な実行プランには、ステートかそれに準じたものと継続した開発体制が必要になるでしょう

実行プランによるコード変更によるリソース差分の確認

  • ステートをIaCサービスやオブジェクトストレージに配置し、ステート実行時にロックできることを求めます。ステートをローカル以外に持ち、実行ごとにロックがかかることで、チーム開発でブランチを同時に複数稼働させることができます

ステートファイルは実行ごとにロック

IaC でやりたいこと

IaC でやりたいことは「コードでクラウドリソースを管理できる」 これだけです。しかし、コードでクラウドリソースを管理できるとはどういうことでしょうか?

私は、コードで定義を書いたらリソースが追加されてほしいし、コードの定義を変更したらリソースが変更されてほしいし、コードの定義を消したらリソースが削除されてほしいです。 コードの定義 = リソースの状態と一致するとコードでクラウドリソースが管理でき、クラウドコンソールで操作する機会を大きく減らすことができます。3

コードでリソースを管理するためにはチームでスムーズに開発を行いたいです。 チーム各自でブランチを切って並行してコードを修正、修正ごとに実行プランで差分を確認し、レビューを受け、問題ないならマージしたいでしょう。 ステートをリモートに置いたり、実行ごとにロックされるとチーム開発で扱いやすくなります。

レビューの漏れに対応するためには、コードで誤って消されてもリソースの削除をブロックする機能も必要になるでしょう。 コードをリソースに適用した後に、クラウド側でリソースに変更がかかったりすることがあるのを考慮すると、リソースの変更差分を無視する機能も必要になるでしょう。

IaC に最低限求めること

IaC でやりたいことから、IaC に最低限求めることがわかります。

  • コード = リソース状態になることを期待します
    • コードを追加したときに、リソースでも追加されることを期待します
    • コードを変更したときに、リソースでも変更されることを期待します
    • コードを削除したときに、リソースは削除されることを期待します
    • コードで誤って削除がかかる変更を記述したときに、リソースの削除をブロックする記述ができることを期待します。(protection)
    • コードの適用時に、リソースで起こった変更を無視したりコードの変更を無視する記述ができることを期待します。(ignore)
  • コードの適用前に、実行プランを表示して実リソースとの差分を正確に示せることを期待します
    • 実行プランは、「現在のリソース状態とコードの差分」を示せることを期待します。(ステートとの差分では実リソースとずれることが多くある)
  • ステートは、オブジェクトストレージやIaCのサービスで保持できることを期待します
    • 実行ごとに透過的にロックがかかるとよいでしょう

IaC でコードとリソースの状態一致が保証できないとどうなるのか

やりたいことや求めることは分かりましたが、それは本当に必要なのでしょうか? IaC でコードとリソースの一致を保証できないと何が起こるのかを考えてみましょう。

題材は、冒頭にあげたAzure の Bicep です。

ARM Template と Bicep について軽く説明しましょう。ARM TemplateBicep は、Azureにおいて IaC の位置づけにあります。Bicep は ARM Template のための DSL という位置づけでビルドすると ARM Template (JSON定義) を出力します。

Bicep でコードを記述し、Azure にデプロイしたときに次の挙動をします。

  1. Bicepコードをビルドして ARM Template を出力
  2. Azure に ARM Template をデプロイ
  3. Azure は ARM Template に基づいてリソースの作成、既存のリソースがある場合は変更を実施
  4. Azure へデプロイを Complete モードで実施した場合は、Azure のリソースが削除対象か見て削除対象ならリソースを削除、削除対象じゃない場合は ARM Template からは消すがリソースは維持

一連の流れの中で特徴的なのが、Step3 と Step4 です。

ステートを持たない

Step3 は、Bicep/ARM Template がステートを持たない IaC であり、展開されたARM Template に基づいてリソースを更新することを示します。 このためか、Bicep でコードとリソースの実行プランには6つのステータスがあります。4 ステータスで注目すべきは デプロイ という適用まで挙動が不定なケースが含まれていることです。

  • デプロイ: リソースは存在し、Bicep ファイル内で定義されています。 リソースは再デプロイされます。 リソースのプロパティは、変更される場合と変更されない場合があります。 いずれかのプロパティが変更されるかどうか判断するのに十分な情報がない場合、操作ではこの変更の種類が返されます。 この状況は、ResultFormat が ResourceIdOnly に設定されている場合にのみ表示されます

ARM Template は、ステートではなく変更する予定の定義になります。定義と実リソースでも差分が取得できるか不明というのは、実行プランが信用できないということであり残念な体験に思います。

コードとリソースは一致が保証されていない

Step4 は、Bicep/ARM Template は、その定義を消してもリソースが削除される保証はないことを示します。 これは 削除のステータス説明にも明記されています。

  • 削除: この変更の種類は、JSON テンプレートのデプロイに完全モードを使用する場合にのみ適用されます。 リソースは存在しますが、Bicep ファイル内では定義されていません。 完全モードでは、リソースが削除されます。 この変更の種類には、完全モードの削除をサポートしているリソースのみが含まれます

BicepのデプロイにはIncrement と Complete の2モードがあります。 Increment は増分デプロイで、Bicep コードから定義を消してもリソースは残ります。 Complete は 完全デプロイで、ARM Template とリソースの同期のため変更や削除をします。コードとリソースの一致を考えると Complete モード一択ですが、コードを削除してもリソースが消えるかはリソース次第です。

Bicep でIaC を行おうとしてもコードとリソースの一致保障がなく、コードとリソースの乖離が進みます。 コードを見てもリソースの状態は保証できないため、今の状態、適用した後の状態を正確に把握するには Azure ポータルやCLIで状態を取得するしかありません。IaC とは?

残念ながら、Bicep や ARM Template は IaC に最低限求めることができず、継続的に IaC をすることはできないと判断しPulumi に移行したのでした。 Pulumi に移行したのは、Azure のネイティブ実装がありAzure の新規API公開から最速1日で対応するなどの高速な対応と Terraform に比べた時のAzure API の網羅性からです。

まとめ

リソースを作る、変更する、削除するをすべてコードで行うには、TerraformPulumiCDK などを採用します。いずれも、現在のリソースの状態とコードの差分を常に管理でき、正確な実行プランを持ち、ステートを安全に維持できます。

リソースを作ることに注力し、変更や削除はコンソールでいいという場合、ARM TemplateBicep が候補に入ります。しかし、作りっぱなしでメンテナンスされなくなるコードは、限定的なシーンでしか活用できない上に負債返却の機会を失うため、採用には慎重です。(再現環境を作ったりするのにはいいんですけどね!)

IaC を選ぶ前に、それが自分の期待する IaC かは考えたいと思う日々でした。

おまけ

書こう書こうと思って、ずっと書いていなかったので、IaC を使うにあたっての注意をいくつかおまけにどうぞ。

IaC やクラウドによる挙動の違い

IaC実行時の挙動は、IaCの実装やクラウド側のAPIにより異なります。同じIaC でもクラウドによって挙動が変わることがあるのは、マルチクラウドをしたことがある人なら経験があるのではないでしょうか。

  • リソースの設定がコード上でリソースのインラインで設定するのではなく別リソース定義を作る場合、コード削除時のリソース状態は IaC によって変わります。コード未設定時の値戻らず、設定されたときの状態が残るケースが散見されます。この場合、コードとリソースが不一致になるため使いにくいと感じるでしょう
  • 連続でIaC を実行すると、前回の実行が残っていて後から実行してね、と言われれる IaC とクラウドの組み合わせがあったりします

IaC とコンソールやCLIの違い

クラウドコンソールや CLI でリソースを作ると、クラウド側で設定していないことまで設定してくれます。

一方で、IaC はコードで設定したことしか設定しない (デフォルト値はある) ということ多いです。

これにより、IaC で作ったときはコンソールやCLI より手間がかかるという面もありますが、コードとリソースの状態が一致する要素になっていると感じます。

IaC 実行はローカルかリモートか

IaC を実行したときに、ローカル実行されるか、IaCのサービスでリモート実行されるかは、 IaC や連携方法によって変わります。 例えば Terraform は Terraform Cloud を使うとリモート実行することもできますし、Terraform Cloud には ステートだけ保持してローカル実行をすることもできます。


  1. リソース = クラウド環境における設定対象とします。
  2. ステート = 前回IaCを適用した状態とします。
  3. クラウドによる面があるのは同意するところです。AWS や GCP の場合ほぼコンソール操作がほぼなくなりますが、Azure では何かとポータルを使う機会があります。
  4. 6つもある理由は不明ですが、ステートがないことに由来すると考えています。多くの IaC のステータスは 4つ (create、update、delete、ignore) なのを考えると多いですね。