TT Lab
はじめる
学ぶ 学習パス コース

Terraform/OpenTofu基礎

宣言的インフラと状態ファイルという台帳

TT Labで続きを見る

一言でいうと

Terraformは、「このコマンドを実行せよ」ではなく、「終わったときにこのようなインフラがあるべきだ」を書くツールです。その約束を守るために、ツールは状態ファイルという帳簿を持ちます。

なぜ必要なのか

コンソールでクリックしてサーバーを作ると、3つのものが同時に失われます。同じ環境を作り直す方法、誰がいつ何を変更したかの記録、そして開発と本番が同じだという確信です。サーバー10台までは、人が覚えて持ちこたえますが、100台は不可能です。dev・stage・prodが微妙に食い違う構成ドリフトは、まさにこの地点から始まります。

そこで、シェルスクリプトに移してみると、新しい問題が出てきます。最初は「作れ」の1行だったのが、2回目の実行で「すでにある」というエラーが出るので条件文を付け、属性が変わっているかもしれないので比較文を付けます。結局、スクリプトの半分が現在の状態を確認するコードになります。宣言的なツールは、その半分を丸ごと引き受けます。人は望ましい姿だけを書き、現在と望ましいものの差を計算する仕事は、ツールが行います。

ここで本当の疑問が出てきます。ツールは「現在の状態」をどうやって知るのか。毎回クラウド全体をなぞるのは遅く、何より、その膨大なリソースの中から、何が自分が作ったものかを区別する方法がありません。この問題の答えが、状態ファイルです。

どう動くのか

planは、3つの値を比較します。

値 どこにあるか 意味
望ましい状態 .tfコード 人が宣言したもの
最後に知っている状態 terraform.tfstate 前回の適用の結果
実際の状態 プロバイダーのAPI 今本当に存在するもの

まず実際を照会して(refresh)状態を更新し、更新された状態とコードを比較して、プランを作ります。そのため、状態ファイルがなければ、ツールは「初めて見るインフラ」と判断して、すべてを新しく作ろうとします。状態ファイルが、インフラ自体よりも危険な資産である理由が、ここにあります。

状態ファイルはJSONで、実務で実際に読むフィールドは、いくつもありません。

{
  "version": 4,
  "serial": 42,
  "lineage": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "resources": [
    { "mode": "managed", "type": "local_file", "name": "hello",
      "instances": [ { "attributes": { "filename": "/root/out/hello.txt" },
                       "dependencies": ["random_pet.suffix"] } ] }
  ]
}

ここで、attributesは最後に適用された結果の記録であるという点が重要です。状態に書かれた値と、実際のリソースの値が食い違っていれば、それ自体がドリフトの定義です。そのため、状態を読むときは、習慣的に「この値は実際と同じか」も一緒に確認する癖が必要です。

versionは、状態ファイルのフォーマットのバージョンで、現在は4です。serialは、状態が変わるたびに上がる番号で、リモートバックエンドで、同時の更新の競合を捕まえるのに使われます。lineageは、この状態ファイルの系譜を表す固有の識別子で、別のプロジェクトの状態を誤って上書きする事故を防ぎます。resourcesの中のtypeとnameをつなげたlocal_file.helloが、私たちが扱うアドレスです。

一方、initは、コードのrequired_providersを読んで、プロバイダーをダウンロードし、.terraform.lock.hclに、確定したバージョンとハッシュを書いておきます。このロックファイルは、リポジトリにコミットする必要があります。そうすれば、自分のノートパソコンとCIが、同じプロバイダーを使います。

現場での姿

1つ目は、状態ファイルをGitにコミットする事故です。状態には、データベースのパスワードやトークンが平文で入ります。しかも、2人がそれぞれapplyすると、マージの競合が起き、誤ってマージされた状態は、リソースを丸ごと失わせます。そのため、実務では、S3のようなリモートバックエンドにバージョニングを有効にして置き、ロックで同時のapplyを防ぎます。AWSなら、長い間、S3+DynamoDBの組み合わせが標準でした。

2つ目は、系譜が違う状態を上書きした日です。チームがバックエンドのキーを間違えて、別のスタックの状態の上に、自分の状態をアップロードしてしまうと、次のplanは「数百個を削除する」と言います。このとき、復活するのは、S3バージョニングの以前のバージョンです。lineageは、そのような事故を事前に察知するために存在するフィールドです。

3つ目は、状態の中の秘密情報です。OpenTofuは、状態とプランファイルの暗号化を、ツール自体でサポートします。Terraformで同じレベルを得るには、有料のサービスが必要です。コマンド名とサブコマンドの構造はほとんど同じなので、このラボでtofuで学んだことは、terraformにそのまま持ち越せます。

次のラボですること

/root/tf/firstで、プロバイダーを取得して初期化し、ファイルを1つ宣言して適用します。続けて、ランダムな名前を作るリソースを加え、宣言を変えて再び適用しながら、serialが上がるのを目で確認します。一時的なリソースを1つ作ってから、それだけを選んで削除し、状態に残ったアドレスの一覧を取り出します。最後に、値をコピーする代わりに参照して、依存関係が状態に記録されるようにします。