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

Terraform/OpenTofu基礎

変数の優先順位、出力、そして機密値の錯覚

TT Labで続きを見る

一言でいうと

同じコードでdev・stage・prodをまかなうには、値がコードの外に出る必要があります。問題は、値を入れられる場所が複数あり、その間に決まった強弱があることです。

なぜ必要なのか

環境が3つで、違うのはインスタンスのサイズとレプリカ数だけだとします。最も簡単な解決策は、ディレクトリを3つコピーすることです。そして半年後、3つのディレクトリは、互いに別の生き物になっています。prodにだけ入ったセキュリティ設定がstageにはなく、devで直したバグがprodには反映されていません。事故は、いつもその隙間で起きます。

変数は、このコピーを防ぐ仕組みです。コードは1つにして、値だけを差し替えれば、環境間の違いが「3つのファイルのdiff」から「値いくつか」に減ります。すると、人が違いを目で確認できるようになります。

どう動くのか

値を入れる場所は複数あり、あとに来るものが勝ちます。

強さ 場所 性格
最も弱い 変数宣言のdefault 「誰も与えなければこの値」
弱い TF_VAR_이름環境変数 CIランナーやシェルから注入
中程度 terraform.tfvars このディレクトリの標準の値
やや強い *.auto.tfvars(ファイル名の辞書順) 自動的に一緒に読み込まれる値
強い -var-file=파일 今回の実行に選んだ値のまとまり
最も強い -var 이름=값 今回1回限りの例外

表のコードに含まれる韓国語のプレースホルダーは、名前・ファイル・値を表します。

変数宣言には、値のほかに付けられるものが3つあります。typeは、誤った形を早い段階で防ぎ、descriptionは、この値が何かを知らせ、validationは、許容範囲をコードで固定します。validationの本当の価値は、失敗する時点を早めることです。検証がなければ、誤った値が適用の途中でプロバイダーのエラーとして爆発し、そのときはすでに半分が作られた状態です。

localsは変数に見えますが、性格が違います。外から注入できない、名前を付けた式です。複数の変数を組み合わせた名前の接頭辞のように、1回定義して複数の場所で使いたい値に使います。

outputは、この設定の公開インターフェースです。モジュールとして使われるときは、他のコードが参照する唯一の窓口なので、すべての出力にdescriptionを付けるのがよいです。そして、-jsonオプションを付けると、機械が読む形式で出力されて、パイプラインで使いやすいです。

最後にsensitive = trueです。これは出力を画面で隠すだけです。状態ファイルには値が平文で残り、output -jsonにも値がそのまま入っています。この事実を知らないと、「機密の指定をしたから安全だ」という錯覚で、状態ファイルをどこにでも置いてしまいます。

現場での姿

1つ目は、-varで消して、忘れた火です。障害対応中に-var replicas=10でサービスを救ったなら、その値は必ずリポジトリにコミットされる必要があります。そうしないと、次の通常のデプロイが、静かに以前の値に戻します。コマンドラインの値が最も強いという性質は、緊急時に役立つ分、記録が残らないという代償も一緒に支払います。

2つ目は、デフォルト値の落とし穴です。「とりあえず動くように」するために、すべての変数にデフォルト値を入れると、値を与えなかったときに、静かに開発用の設定で本番が作られます。環境ごとに必ず違う必要がある値には、デフォルト値を与えないほうが安全です。ツールは、値がなければ尋ねるか失敗しますが、その失敗は、事故よりずっと安くつきます。

3つ目は、プランのログに残る秘密情報です。planの出力には、リソースの属性がそのまま出力されます。CIのログを社内の誰でも見られる場所にためておけば、それがそのまま流出経路です。機密の指定とは別に、ログの保管ポリシーも一緒に見る必要がある理由です。

値がどこから来るかをはっきりさせる

変数1つに値を与えられる経路が6つあります。優先順位を知らないと、「確かに変えたのに変わらない」を経験します。下にいくほど強いです。

  1. デフォルト値(default)
  2. 環境変数TF_VAR_이름
  3. terraform.tfvars
  4. *.auto.tfvars(ファイル名の順序)
  5. -var-file=で与えたファイル
  6. -var=で直接与えた値

同じファイルの中で2回書くとエラーですが、別の経路なら静かに上書きされます。 CIがTF_VAR_を仕込んであるのに、ローカルでterraform.tfvarsを直しているなら、3つ目が2つ目に勝つので、ローカルでは動いて、CIでは動かないことになります。値がどこから来たかを確認するには、プランをファイルとして取得して読みます。

terraform plan -out=tfplan
terraform show -json tfplan | jq '.variables'

検証は、変数の宣言に付けます。 誤った値をapplyの途中で見つけたら、すでに半分は作られたあとです。

variable "env" {
  type = string
  validation {
    condition     = contains(["dev", "stg", "prod"], var.env)
    error_message = "env 는 dev, stg, prod 중 하나여야 합니다."
  }
}

sensitive = trueは、画面だけを隠します。 状態ファイルとプランファイルには、値がそのまま入ります。秘密情報は、変数で渡さずに、シークレットストアからデータソースで読むほうがよく、それでも状態には残ることを前提に、リポジトリを扱います。

出力は、次の人に残すインターフェースです。 モジュールの出力は、そのモジュールを使う側が依存するようになるので、一度決めたら変えにくいです。内部で使うリソース名をそのまま出力する代わりに、意味で名前を付けます。instance_idよりapp_server_idが、ipよりprivate_endpointが、長持ちします。

terraform output -json | jq -r '.app_endpoint.value'

次のラボですること

/root/tf/varsで、同じ変数に、デフォルト値・tfvars・コマンドライン・環境変数の4つの方法で値を入れて、どれが勝つかをファイルで確認します。validationで誤った値を拒否させて、そのメッセージを保存します。localsで名前の接頭辞を組み合わせ、説明が付いた出力を4つ以上作ります。最後に、機密の変数を宣言して、人が見る出力では隠されるのに、JSONには値が残るという事実を目で確認します。