概要

terraformではリソースの状態を管理するためにtfstateと呼ばれるファイルを使用しています。 tfstateはローカルのファイルとしても管理できますが、チームでterraformの運用をする際にAWSではS3などのストレージにtfstateを保管します。

ここで、複数人でtfstateを同時に書き込みしようとすると不整合が発生する可能性があります。 そのため、ひとりが作業中の場合他の人が書き込みできないようロックすると安全です。

AWS ProviderではDynamoDBのテーブルを利用してロック状態を管理する機能があります。 また、Terraformのv1.10.0以降ではDynamoDB不要でS3の機能のみでロック状態を管理できるようになるようです。

そこで、DynamoDBとS3単体の2通りの方法でロックを試してみます。

準備

S3バケットを作成する。

aws s3api create-bucket --bucket tfstate-2024111621 --region ap-northeast-1 --create-bucket-configuration LocationConstraint=ap-northeast-1

DynamoDBではString型でLockIDという名前のキーを持つテーブルを作成しておく。

aws dynamodb create-table \
    --table-name TerraformLock \
    --attribute-definitions \
        AttributeName=LockID,AttributeType=S \
    --key-schema \
        AttributeName=LockID,KeyType=HASH \
    --billing-mode PAY_PER_REQUEST

DynamoDBによるロック

バックエンドでS3のバケット名とDynamoDBのテーブル名を指定する。

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  backend "s3" {
    bucket = "tfstate-2024111621"
    key    = "terraform.tfstate"
    region = "ap-northeast-1"
    dynamodb_table = "TerraformLock"
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

# 適当なリソース
resource "aws_s3_bucket" "sample" {
  bucket = "my-sample-bucket-20241116"
}

terraform apply 中に別のターミナルで terraform apply を実行すると以下のようなエラーが出力された。

$ terraform apply
╷
│ Error: Error acquiring the state lock
│ 
│ Error message: operation error DynamoDB: PutItem, https response error StatusCode: 400, RequestID: FQ3943B2BIABCG3SVIVVHATI67VV4KQNSO5AEMVJF66Q9ASUAAJG, ConditionalCheckFailedException: The conditional request failed
│ Lock Info:
│   ID:        99cfb309-6d63-5df6-8280-8ad9667083ef
│   Path:      tfstate-2024111621/terraform.tfstate
│   Operation: OperationTypeApply
│   Who:       [email protected]
│   Version:   1.9.8
│   Created:   2024-11-16 13:39:54.451096 +0000 UTC
│   Info:      
│ 
│ 
│ Terraform acquires a state lock to protect the state from being written
│ by multiple users at the same time. Please resolve the issue above and try
│ again. For most commands, you can disable locking with the "-lock=false"
│ flag, but this is not recommended.

ロック中にDynamoDBのテーブルをスキャンするとtfstateの場所がキーで実行時間などの情報がInfo属性として保存されていることを確認できた。

aws dynamodb scan --table-name TerraformLock 
{
    "Items": [
        {
            "LockID": {
                "S": "tfstate-2024111621/terraform.tfstate-md5"
            },
            "Digest": {
                "S": "08687f0c391b84ae182a5ff906425f94"
            }
        },
        {
            "LockID": {
                "S": "tfstate-2024111621/terraform.tfstate"
            },
            "Info": {
                "S": "{\"ID\":\"99cfb309-6d63-5df6-8280-8ad9667083ef\",\"Operation\":\"OperationTypeApply\",\"Info\":\"\",\"Who\":\"[email protected]\",\"Version\":\"1.9.8\",\"Created\":\"2024-11-16T13:39:54.451096Z\",\"Path\":\"tfstate-2024111621/terraform.tfstate\"}"
            }
        }
    ],
    "Count": 2,
    "ScannedCount": 2,
    "ConsumedCapacity": null
}

terraform apply が終わると1行削除された。

{
    "Items": [
        {
            "LockID": {
                "S": "tfstate-2024111621/terraform.tfstate-md5"
            },
            "Digest": {
                "S": "d9c7a739c803389d8c42023bf352d835"
            }
        }
    ],
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null
}

S3単体によるロック

今回はMacOSだったのでtfenvを使用してterraform v1.10.0のRC1をtfenvした。

tfenv install 1.10.0-rc1
tfenv use 1.10.0-rc1

DynamoDBの指定を削除し、 use_lockfile = true を設定する。

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  backend "s3" {
    bucket = "tfstate-2024111621"
    key    = "terraform.tfstate"
    region = "ap-northeast-1"
    use_lockfile = true
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_s3_bucket" "sample" {
  bucket = "my-sample-bucket-20241116"
}

同様にロックされていることを確認できた。DynamoDB: PutItem から S3: PutObject でエラーになっていることがわかる。

$ terraform apply
╷
│ Error: Error acquiring the state lock
│ 
│ Error message: operation error S3: PutObject, https response error StatusCode: 412, RequestID: DYA3QZQW2NASF0ZQ, HostID: PJLEhRZMulo/l8m9C8rYnLwcShFiEyYaAwv9sFkDOD36/bkHKGqSCD0vchft1XINPcN0y2IENgWgFjPJAC5xAg==, api error PreconditionFailed: At
│ least one of the pre-conditions you specified did not hold
│ Lock Info:
│   ID:        b1b22d98-7157-9416-4181-c29e4f3eb76a
│   Path:      tfstate-2024111621/terraform.tfstate
│   Operation: OperationTypeApply
│   Who:       [email protected]
│   Version:   1.10.0
│   Created:   2024-11-16 14:04:56.292913 +0000 UTC
│   Info:      
│ 
│ 
│ Terraform acquires a state lock to protect the state from being written
│ by multiple users at the same time. Please resolve the issue above and try
│ again. For most commands, you can disable locking with the "-lock=false"
│ flag, but this is not recommended.
╵

S3バケットにtflockファイルが作成されている。2024年8月にS3に追加された条件付き書き込み機能によりtflockファイルが存在しなければtflockを作成し、存在すればエラー終了するようです。

aws s3 ls tfstate-2024111621
2024-11-16 22:49:32        180 terraform.tfstate
2024-11-16 23:13:35        243 terraform.tfstate.tflock

参考