🐷

terraformのロック制御にDynamoDBが必要な理由

2024/04/18に公開

背景

terraformのremote stateをs3に保存する場合、terraform apply(もしくはplan)の競合を防止するため以下のようにDynamoDBも併せて設定するかと思います。

terraform {
  backend "s3" {
    bucket         = "tfstate-backend"
    key            = "tfstate"
    region         = "ap-south-1"
    dynamodb_table = "terraform-lock"
    encrypt        = true
  }
  ...
}

一方Google Cloudですと以下のようにremote stateとしてGCSを設定すれば良く、これで競合も防止できています。

terraform {
  backend "gcs" {
    bucket  = "tfstate-backend"
    prefix  = "tfstate"
  }
  ...
}

何故AWSでは(厳密にはremote stateをs3に設定した場合では)DynamoDBを設定しないと競合を防止できないのか気になって調べたのでその内容を以下にまとめます。

TL;DR

  • s3は同時書き込みのオブジェクトロックをサポートしていない
  • DynamoDBテーブルへの条件付きPutItemを使って競合を防止している

terraformのロックについて

前提としてterraformでは競合防止のため実行前にロックを取得しています。ロックの取得方法はremote stateの種類によって異っており、例えばGCSだと.tflockというオブジェクトを配置し、このオブジェクトが存在する間は他ユーザのterraform実行をブロックしています。

ちなみにplanは以下のようにオプションを指定することでロックを取得せずに実行することができます。

terraform plan -lock=false

CIで何度も実行していると稀に.tflockが残ったままになり(例えばネットワークエラーで.tflockの削除リクエストが喪失した場合など)、terraformが実行できなくなるケースがあるので、個人的にはplan時にはロックは無効にするのが良いと考えています。

私の疑問は「s3でもGCSと同じように.tflockを使った実装にすれば良いのではないか?」というものでした。

s3だけでロック機構を完結させられない理由

自分自身の疑問に一言で回答すると「s3では無理だから」になります。

hashicorp/terraformのIssueでロック機構からDynamoDBを外してGCSと同じような実装に変えることが提案されていますが、AWS公式ドキュメントの文言を引用しつつ現段階では不可能としてcloseされています。

Amazon S3 は、同時書き込みのオブジェクトロックをサポートしていません。同じキーに対して 2 つの PUT リクエストが同時に行われた場合、最新のタイムスタンプを持つリクエストが優先されます。これが問題になる場合は、アプリケーション内にオブジェクトロックメカニズムを構築する必要があります。

https://github.com/hashicorp/terraform/issues/27070#issuecomment-1294251198

ここまで読まれている方にはわざわざ説明するまでもないかもしれませんが、オブジェクトストレージでterraformのロックを実装するにはPUTリクエストが複数同時に行われた場合、後から到達したリクエストをエラーで返す必要があります。s3はこの機能をサポートしていないのでロックを実装するためにDynamoDBを併用する必要があるということになります。

DynamoDBを使ったロック制御について

最後にDynamoDBでどのようにロック制御を実装しているのかを見ていきます。

s3でのロックは以下で実装されています。

https://github.com/hashicorp/terraform/blob/v1.7.5/internal/backend/remote-state/s3/client.go#L296-L323

DynamoDBテーブルにPutItemをリクエストしており、その際に条件式(LockIDという名前で既にアイテムが存在するか)を設けているので、この条件式を通らない場合はDynamoDBがエラーを返すようになっています。

Discussion