Terraformのはじめかた4

さて、TerraformでResource,Data,Valiableを使用できるようになれば、効率的な記述やイテレーション、リソースの分離記述や変数の動的な変更など、使えると便利極まりないけれど、最低限としてAWSのリソースを羅列するだけなら必須ではない、というあたりを除けば一通り記述できるようになります。

基本的にTerraformもCloudFormationも、そのリソース粒度はAWS CLIの粒度そのものであり、つまりはAPIの粒度と一致しています。

なので、Terraformのリソースのパラメータが複雑に感じられる、ということは、それはそもそもAWSのCLIを使えない、ということとほぼ一致してしまいます。
もちろん、これは各種SDKにおけるライブラリでの実装粒度とも一致するものであり、逆に言えばここが躓いてしまうならばそもそもマネジメントコンソール以外使いようがありません。AWSをはじめとするクラウドサービスにおいて、APIを知る、というのはそのサービスそのものを知ることとほぼほぼ同義とも言えます。

つまり、Terraformの運用、とはこのリソース、データ、そして変数値の管理であり、そしてその実行結果の適切な管理にほかなりません。

Terraformのことが嫌いになっても、AWS CLIのことは嫌いにならないで上げてください。

というか、この二つのどっちかだけ好き、ってあり得ないんですけどね。なので、CloudFormationもTerraformも基本的には記述式が異なるだけで、同じ「AWS CLIのパラメータ記述方式」でしかありません。
Perlで書くかPythonで書くか、というよりもはるかに差が小さくて、JSONとYAMLくらいの記述差でしかありません。どちらも同じ構造を記述できてしまいます。

さて、Terraformの実行結果は、Terraformのコードを記述した同じディレクトリにterraform.tfstateというファイルで残されています。このファイルがTerraformにおける実行結果、かつログ、かつ現在のステート記録です。これを消したら、Terraformは自分が作成したリソースは存在しない、と認識します。

tfstateは状態ファイル、と呼ばれるもので、現在のAWSに作成したリソースの状態を記録しています。この内容とコードの内容を比較して、差分を基に実行計画を立てます。

つまり、このコードと状態ファイルがセットになって、初めてTerraformの構成管理、という側面が動作します。しかし、問題があります。

それは、複数人でIaCを管理する場合、そもそもこのterraform.tfstateはどこにあるべきなのでしょうか。この問題はとりあえず先送りしておきますが、基本はそもそも同じディレクトリに作成されるべきではありません。このコードはそもそも汎用的に流用したい、というのが最終目標になってくるはずなので、そこにある環境での実行結果が混ざっておいてあったら本末転倒です。

もっともお手軽なのはS3バケットをステート管理保持領域とすることです。
ほかにもいろいろあったりはしますが、HasiCorpさんのTerraform Enterpriseを利用するとATLUSへのTerraform.tfstateの記録ができるようになります。

さて、とりあえずTerraformを実行した結果、このコードは何度実行されても問題がない、ということがわかると思います。手動変更を受けていれば元に戻します。何をもって正とするのか、という点において、TerraformやCloudFrontは「コードをもって正とする」という回答を済ませています。ここに手作業があった場合どうするのだ、という問いを投げかけることほどナンセンスなものはありません。コードは手作業による変更を検出しますし、それがどこかも示します。つまり、回答は「コードを修正せよ」なのは自明です。そもそも、コードを正とする、という回答が済んでしまっていますから、手作業は誤です。それを正とするのはコードに反映させて初めてそれが正となります。

IaCは当然の話ですが、DevOpsを構成する重要な要素です。DevOpsとは開発とオペレーションの一体化であって、オペレーションにも開発同様の性能を求められます。勿論、開発にはオペレーション品質を求められるのですが。IaCにおいて、このIaCコード記述とそのオペレーションは全く不可分です。どちらか一方だけができる、などということはあり得ませんし、あってはいけません。なぜなら、それは正しくオペレーションがコードを修正できないことを意味してしまいます。

オペレーションはIaCコードが開発できなければなりませんし、読めなければなりません(そもそもAWS CLIを使っていたら悩むほどのコードではないのです)。

では、実行したTerraformのstateを見ていきましょう。

[Terraform-test]$ terraform state list
data.aws_ami.amazon-linux2
data.aws_caller_identity.self
data.aws_subnet_ids.subnets
data.aws_vpc.default_vpc
aws_instance.web-server
aws_key_pair.key_pair
aws_security_group.ssh-allow
local_file.private_key
local_file.public_key
random_integer.select_subnet
tls_private_key.key-pair

これがこのコード内に含まれている、Resourceとdataの一覧となります。このうち、いずれかのリソースがすでに手で削除されてしまい、コード実行に伴って再作成されては困るような場合は、terraform state rmを利用して該当のリソースをtfstateから削除します。同様に、コード中でラベル名を変えた、などといった場合はstate mvを用いてラベルを付け替えたりもできますが、これらは基本的にはリファクタリングの際によく行われます。

個々のリソースの記録状態を得たい、というのが通常の運用においてはよく発生するケースかと思います。その場合はterraform state show local_file.private_keyといった感じになります。

これが実際に実行する際にTerraformからAWS CLIにわたるパラメータ、そして実行結果としてAWSから戻されたリザルトを含む結果です。

同様に運用ステージでよく用いられるものとしては

terraform taint <リソース名>.<ラベル>
terraform untaint <リソース名>.<ラベル>

があります。それぞれ、taintは次回の実行時に当該リソースを強制的に再作成を指示するもの、そしてuntaintはその逆で、taintのマークを打ち消すものです。Terraform planで再作成がマークされたリソースもuntaintで打ち消すことが可能です。

これらは、実際のAWSのリソース状況とTerraformの記録状況に差異が生じるケースがあるためです。発生ケースはいろいろなので割愛しますが、往々にしてコードの記録状況と実際のリソースの差分は発生します。そういった場合、どのように解消していくか、というのが重要となります。

最近ですと、AWSのIDの桁が増えたリソースなどがあったりしますが、これらはIDの桁が増えただけで別に今までのリソースから変わったわけではありません。しかし、ID変更はTerraformから見れば追従できない内容で、Terraformからすればリソースが削除されたように見えてしまいます。当然Terraformはリソースの再作成を指示するわけですが、もともと作成したリソースもIDが変更されて残っています(そして管理から外れて残り続けます)。

これはIaCとしては望ましくはありません。

そこで、まずこのリソースのtfstate上の記録をrmしてしまいます。これでリソースは再作成ではなく、新規作成として取り扱われます。
そして、新しいIDとなったリソースをterraform importでimportし、コードと実際のリソースの紐づけを行います。

ID以外に変更が存在しなければ、Terraformはコード上の内容と実際の内容の差分がないことを検出すれば、planは再作成を指示することはなくなります。

こういった作業時には、特定のリソースだけをapplyして実行する、ということはよくあるため、applyやplanでは–target <リソース名>.<ラベル>を用いて、必要なリソースだけをターゲットとして更新することができます。

なお、このリソースの更新によって影響を受けるほかのリソースがある場合、たとえtargetを指定していても、依存関係として更新対象となります。これにより、コードのどこを修正すべきか、といったことを実行者は認識できます。こういった部分は基本的にplanで検出可能です。

このように、運用ステージで利用されるサブコマンドは、

terraform apply
terraform import
terraform plan
terraform refresh
terraform show(terraform state show)
terraform taint
terraform untaint
terraform state

などが中心となります。用いられるサブコマンドは、コードの作成時よりも、ずっと多くなるのです。運用ステージでは、IaCと実際のリソースの差分に基づいて、適切に対象のリソースの状態を更新し、変更し、そしてコードを修正し、それを部分的に適用するといったように、一括実行とはまた違う作業が必要になってきます。

refreshはapplyやplan実行時にもそれらに先駆けて実行されますが、単体でも実行できます。tfstateをS3においてない場合などはこれらによる更新が必要な場合があります。現在のAWSのリソース状態を取得し、状態ファイルに反映させます。

状態ファイルに反映させるだけなので、これをもって何かしらの変更が起きる、ということはありません。
また、TerraformやCloudFormationは繰り返しになりますが、あくまでAWS APIへのパラメータの定義です。それがどういうことかというと、この時点で「コードに記載された通りにリソースが生成される(エラーがなければ)」が担保されていると言い換えることができます。

コードが誤っている、コードの記載内容が意図したものとは異なる、というケースはありますが、それは論理的な問題(ロジックバグ)です。少なくともコード通りにリソースが作成されたかどうかを確認する必要は実はありません。シンタックスエラーに類するような、ステートメントバグは原則あり得ません。

これは当然の自明で、そもそも我々が人間の目でAWSのリソースの確認に使っているマネジメントコンソールもAWS APIを介したデータの取得と表示を行っているものです。AWS APIでリソースを作成し、AWS APIで状態を取得して比較しているのですから、コード通りに作られている、というのは担保されています。そもそも単純なシステムでも数百のリソースを生成するAWSのリソースを目視管理すること自体がすでに無理が生じてしまいます。

ただし、これとシステムの設計上の構成正当性とか、ロジックの正当性、動作の正当性は全くの別問題である、ということは理解しておかなくてはなりません。
これらは当然従来通りのストレステストや疎通テスト、アプリケーションの単体や結合テストを実行すべきもので、AWS上のリソース作成とその結果のテストとは別の概念です。
基本的にAWSなどのクラウドサービスのリソースは疎であり、個々のリソースは個々のリソースの正常性しか担保しえません。

システム全体にわたって俯瞰し、検査し、理解できるのはそれを作る人間だけです。逆に言えば、そこは人間にしかできないことですので、リソースを作る、検査する、といった単純に機械にできてしまう部分は機械にやらせて、人間は人間にしかできないことをすべきです。

ツールはそれを為すための道具であり、実現するための手段です。だからこそ、オペレーションも、Terraformを使うこと、に腐心するのではなく、Terraformをツールとして、いままで目視でやっていた部分をスポイルし、より人間にしかできないオペレーションをするべき、ということになります。

人間にしかできないオペレーションとは何か、という話が出てきたりはしますが、それはそれ。というか、日々のリソースの状態を観測し、予測し、予見し、推定するのはまだまだ人間の仕事です。得られたデータから何を読み取り、何が起きると予測し、その予防の提言は今のとこと人間にしかなしえないオペレーションです。ちょっと条件を限定的にした状況ではディープラーニングが迫ってきてはいますが、それでも彼はそのための可能性を示すところまでです。決断をできるのは人です。

TerraformやCloudFormationは、あくまでそのような人間にしかできない作業のための時間を作る、これまでのオペレーションにおける時間コストの使い方の変化とも言えます。

さて、余談が過ぎました。

次回はTerraformでの既存リソースの取り込みの様子を見ていきましょう。