10月から株式会社マクアケでSREとしてお世話になっています。
そんなマクアケのアドベントカレンダー3日目の記事です。
昨日は、絶賛育休中のrenoinnさんのEnt+Atlasでマイグレーションするでした。
最近はTerraformよりもDBのお勉強が多いですが、久しぶりにTerraform関連のブログです。

概要

Terraformを使っていると、いろいろな理由で差分が発生することがある。

  • terraform管理と手動管理が混ざっている
  • terraformがアップデートされて使えなくなった構文がある
  • プロバイダーがアップデートされて使えなくなった構文がある

いずれの理由にせよ、構成ドリフト(設定差異)はterraform適用へのハードルをあげる。
targetで指定すれば良いが、どこか不安が残る、そんなコードになりがちになる。
管理を対象を全てのリソースにしないにせよ、terraformの差分が解消してあることが望ましい。
そんなこんなで、terraformの差分解消に伴うtipsやらポイントを自分のためにもまとめておく。

まずは何はともあれ現状を見てみる

差分を解消しなくてはいけないリポジトリは、常に触っているリポジトリとは限らない。
そうすると、terraform initからはじめる必要がある場合がある。
その場合、いきなりコケて心が折れるかもしれないけれど、まずは落ち着いて terraformのバージョン権限 を確認しよう。

特にAWSの権限が実は不足していたなんてことはままある。
backendに指定しているリソース、AWSでS3を使っている場合は、AWS CLIを使って対象のS3へアクセスできることを確認しよう。

私は必ずbackendにS3を使っている場合は、下記が実行できるかを必ず確認しておく。(VPNがつながっていない、SAML認証が通っていない、SAMLのアクセスキーの有効期限が切れていた、、、なんてことはありがち)

aws s3 ls XXX

terraform init でコケるときはプロバイダーのバージョンも疑う

terraform init がコケるなんて、おいおい、、、という気持ちかもしれないが、放置していればそういうこともある。
まして初めて触るリポジトリなら、ありえなくは無い。
上記権限等が問題ないのであれば、プロバイダーのバージョンを疑ってみる。
AWSのプロバイダーだけならまだしも、templateを使ってたり、他のプロバイダーを使っていればコケることはある。
最悪は、すでに動かしている人がいたら、その人から .terraform ごともらえば解消するかもしれない。

terraform planを実行するときは-outで出力するか、-no-colorをつける

ということでinitまで出来たら、次はterraform planをしてみる。その結果、大量に差分が出力されることがある。
その場合はファイルに出力してみると差分の作業がやりやすくなる。

terraform plan -out /tmp/diffs.txt

このファイルはバイナリで terraform show /tmp/diffs.txt で確認することが可能だ。
もし、ファイルにちょっとメモ書き込みしたり、対応したリソースの行を消したりしたい場合はファイルにリダイレクトすることになる。
しかし、単純にリダイレクトで下記のように実行するとシェルの色の文字コードが出力されてしまう。

$ terraform plan > ~/Downloads/diffs.txt

data.template_file.example: Reading...
data.template_file.example_test: Reading...
data.template_file.ecr_lifecycle_policy: Reading...

catやlessで見るだけなら良いが、vimなどのテキストエディタでアクセスする場合は見ていられない。
そこで -no-color を使って出力すれば、この文字コードは出力されずに済むので付与して実行するようにすると余計な加工の時間が減るのでオススメ。

state show/state rm

terraformのサブコマンドには state というものがあり、tfstateで管理しているリソースを参照したり、削除したりできる。

terraform state
terraform state show XXXX

削除するときは下記のみでOK。実際のリソースではなく、tfstateから設定が消える。

terraform state rm XXXX

バージョンの問題等で問題なかったコードで差分が発生して困ったら、一度対象リソースを state rm して terarform import で解消するのも一つ。

terraform planはtargetを指定する

このあたりは基本中の基本であるが、毎回全てのリソースを
terraform planしては時間がもったいないので -target で対象を絞る。
planで差分が出た # aws_ecs_task_definition.example must be replacedaws_ecs_task_definition.example をコピーしてtargetで指定するだけでOK。

terraform plan -target=aws_ecs_task_definition.example

方針

terraformのstate rmなり、importなりで解消しない場合はコードを修正していくことになる。
個人的には Warning の対応は後にしても良いと考えている。(いずれ問題になるので対応はしたほうがよい)
なぜならば、WarningがDeprecatedの場合、コードを書き直ししないといけない場合があるからである。
この場合、その調査と検証で大きく時間を要することになる場合もあるのと、WarningになってもTerraform は実行できることが殆どなので、まずは差分から解消しよう。

差分解消のポイント

destroyにはforce replacementがある

terraform plan を実行した際に、差分が発生して下記のように 1 to add, 0 to change, 1 to destroy. となる場合がある。

  # aws_ecs_task_definition.example must be replaced
-/+ resource "aws_ecs_task_definition" "example" {
      ~ arn                      = "arn:aws:ecs:ap-northeast-1:123456789012:task-definition/example-task:1" -> (known after apply)
      ~ container_definitions    = jsonencode(
          ~ [ # forces replacement
              ~ {
                  - mountPoints      = [] -> null
                    name             = "example"
                  - portMappings     = [] -> null
                } # forces replacement,
            ]
        )
      ~ id                       = "example-task" -> (known after apply)
    }

Plan: 1 to add, 0 to change, 1 to destroy.

これは、既存のリソースの差分により、作り直しが発生することを意味する。
つまり、削除→作成という挙動になる。
テスト環境ならまだしも、本番でこれが発生した場合はそのままApplyすることはまず出来ない。

ただし、状態を正しく修正していくことで解消する可能性が高い。
例えば、下記のような部分はTerraformで該当コードを同じように空の配列を追加することで解消する。

terraform plan 結果

- mountPoints      = [] -> null

この場合は、下記をタスク定義のjsonに追加すれば差分はなくなる。

"mountPoints": []

順番が固定されているリソースで差分になることがある

上記を見て追加したにも関わらず、下記のように値が追加と削除になる場合もある。

+ {
    + name      = "NANIKA_NO_PASSWORD"
    + valueFrom = "/example/NANIKA_NO_PASSWORD"
  },
  {
      name      = "S3_EXAMPLE_BUCKET"
      valueFrom = "/example/S3_EXAMPLE_BUCKET"
  },
+ {
    + name      = "NANIKA_NO_CREDENTIALS"
    + valueFrom = "/example/NANIKA_NO_CREDENTIALS"
  },
  {
      name      = "HTTPS_EXAMPLE_ENDPOINT"
      valueFrom = "/example/HTTPS_EXAMPLE_ENDPOINT"
  },
  # (9 unchanged elements hidden)
  {
      name      = "S3_EXAMPLE2_BUCKET"
      valueFrom = "/example/S3_EXAMPLE2_BUCKET"
  },
- {
    - name      = "NANIKA_NO_CREDENTIALS"
    - valueFrom = "/example/NANIKA_NO_CREDENTIALS"
  },
- {
    - name      = "NANIKA_NO_PASSWORD"
    - valueFrom = "/example/NANIKA_NO_PASSWORD"
  },

この場合は、配列の順番がすでに固定されていることに影響しているものなので素直に - になっているところと同じように配列の場所を修正してあげれば差分は解消する。

+差分になっていても、force replacementを修正すると解消することがある

例えばplanを実行して下記のような差分になった場合。

      ~ description = "parameter group" -> "Managed by Terraform" # forces replacement
      ~ id          = "develop" -> (known after apply)
        name        = "develop"
      + name_prefix = (known after apply)

name_prefix を削除しないといけないのか?と見えるが、terraformのコードでは name_prefix が無い。こんな場合は forces replacement になっている箇所をを解消すると消える場合がある。
そのため、まずは force replacement から解消を始めると良い。

どうしても差分が消えない場合はtfstateを修正する

古の方法ではあるが、tfstateはJSONファイルなので、terraformが解釈できて、且つ差分にならないように修正すれば差分は解消する。
どうしても修正してもうまくいかない場合は tfstateのバックアップ を取得した上で、直接修正しよう。

Warning/Deprecatedを対応する

差分がなくなった時はとても感動的になる。
差分が20個とかあったとしても、それが綺麗になると安心して、terraform applyができるようになると思う。
でも、差分だけが問題じゃない。
まだ、Warning/Deprecatedが残っているかもしれない。

下記にいくつかDeprecatedを解消したときの対応方法を記録している。
Deprecatedもやっつけて、きれいな状態にしよう。

Terraformのaws_s3_bucket_objectのdeprecated対応

Terraformのaws_s3_bucket_server_side_encryption_configuration対応

Terraformのaws_s3_bucket_lifecycle_configuration対応

Terraformのaws_s3_bucket_acl対応

Terraformのaws_ecs_cluster_capacity_providers対応

template_fileはdeprecatedなので組み込みのtemplatefileを使う

まとめ

terraformユーザーには基本中の基本かもしれないけれど、差分をひたすら修正しなくてはいけなくなったとき、少し頭の整理が必要になったことがあった。
それを改めて書き留めてみたので、これからteraform触るか〜とか、久しぶりに触るか〜なんて人の一助になれば幸いです。

<p style='padding: 5px;'>