tfstateファイルをS3に保管する
tfstateファイルは実際の環境がterraformの管理下にあるのかを記述している情報が記載されている。個人で運用する分には問題ないが、複数人で運用する際はtfstateファイルのズレが発生する。terraform applyでコードを環境に反映した際にtfstateファイルも更新されるからだ。
そうなると各開発者のローカル環境で統一されたtfstateファイルを運用することは不可能であり、もし運用したとしたら、おそらく環境が壊れる。
GitHubなどのリモートリポジトリ管理も選択肢に入るかもしれないが、これもコンフリクトや機密情報を含む可能性があるため、非推奨の管理となる。
と言ったところで選択肢に入るのがS3での管理である。 ただしここにも注意点があり、Terraform管理対象環境でのS3管理は避けた方が良い。Terraform自身によって環境を変化させてしまう危険性があるからだ。 またS3のみを使用しても今度は同時更新のリスクも出てきてしまう。それらを完全回避するにはDynamoDBを使用した排他制御が必要になる。 今回は練習のためTerraform管理環境でS3+DynamoDBを使用した管理を記載していく。
※tfstateファイルの管理が多い、もしくはS3などを使用したくない場合はTerraform Cloudを使用するのが楽かもしれない。 Terraform Cloud
前提
TerraformでAWS環境を変更できること(AWS CLIやterrafrom init済みなこと)
main.tfを作成
main.tfは下記のように作成しておく。 この後、S3を作成する際に再度編集する。 main.tf
terraform {
required_version = "1.12.1"
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.99.1"
}
}
}
provider "aws" {
region = "ap-northeast-1"
profile = "admin"
default_tags {
tags = {
Env = var.environment
Project = var.project
}
}
}
variables.tf 変数定義
variable "project" {
type = string
description = "プロジェクト名"
}
variable "environment" {
type = string
description = "環境名"
}
terraform.tfvars 変数への値設定
project = "tfstate-storage"
environment = "prod"
S3を作成
まずは本体を作成する s3.tfを作成。 バケット名は一意である必要があるため、randomリソースを使用して命名に使用している
# ランダム値生成
resource "random_string" "s3_unique_key" {
length = 6
upper = false
lower = true
numeric = true
special = false
}
# 本体
resource "aws_s3_bucket" "tfstate_storage" {
bucket = "tfstate-storage-${random_string.s3_unique_key.result}"
tags = {
Name = "${var.project}-${var.environment}-s3"
}
}
S3のバージョニングを有効にする。 こうすることでtfstateの上書きを避けて、ロールバックが可能な状態にできる。(バージョニングを使用しな場合はそのまま上書きされてしまうため、絶対に必要な設定である)
# バージョニング設定
resource "aws_s3_bucket_versioning" "tfstate_storage" {
bucket = aws_s3_bucket.tfstate_storage.id
versioning_configuration {
status = "Enabled"
}
}
アクセス設定を行う。publicアクセスは認めない設定で作成
# アクセス設定(publicアクセスをできないようにした)
resource "aws_s3_bucket_public_access_block" "tfstate_storage" {
bucket = aws_s3_bucket.tfstate_storage.id
block_public_acls = true # パブリックACLの設定を拒否する
block_public_policy = true # すでに存在するパブリックACLを無視する
ignore_public_acls = true # パブリックポリシー(Principal: "*")の設定をブロック
restrict_public_buckets = true # パブリックポリシーが存在しても実際のアクセスを制限
}
ここまでで一旦、S3の作成は完了である(この後も追記はする) 全体像
# ランダム値生成
resource "random_string" "s3_unique_key" {
length = 6
upper = false
lower = true
numeric = true
special = false
}
# 本体
resource "aws_s3_bucket" "tfstate_storage" {
bucket = "tfstate-storage-${random_string.s3_unique_key.result}"
tags = {
Name = "${var.project}-${var.environment}-s3"
}
}
# バージョニング設定
resource "aws_s3_bucket_versioning" "tfstate_storage" {
bucket = aws_s3_bucket.tfstate_storage.id
versioning_configuration {
status = "Enabled"
}
}
# アクセス設定(publicアクセスをできないようにした)
resource "aws_s3_bucket_public_access_block" "tfstate_storage" {
bucket = aws_s3_bucket.tfstate_storage.id
block_public_acls = true # パブリックACLの設定を拒否する
block_public_policy = true # すでに存在するパブリックACLを無視する
ignore_public_acls = true # パブリックポリシー(Principal: "*")の設定をブロック
restrict_public_buckets = true # パブリックポリシーが存在しても実際のアクセスを制限
}
Key Management Serviceでキーの作成
tfstateファイルにはVPC IDやDBのパスワードなどが含まれる。 そのため、 S3に保存されたtfstateファイルを暗号化しておきたい。 KMSは暗号鍵を安全に生成・管理・利用するためのマネージドサービスであり、これを連携していく。(実際に暗号化と複合化はS3で行う。そのために設定も追加していく)
key.tfを作成する
resource "aws_kms_key" "tsstate_storage_key" {
description = "description" # 説明
deletion_window_in_days = 7 # 削除する際は7日後に削除される
enable_key_rotation = true # キーのローテションをするか
tags = {
Name = "${var.project}-${var.environment}-kms"
}
}
resource "aws_kms_alias" "tsstate_storage_key_alias" {
name = "alias/tsstate_storage_key_alias"
target_key_id = aws_kms_key.tsstate_storage_key.id
}
エイリアスをつける理由として key-id(uuid)で呼び出しを行わなければならないため、可読性が非常に低い。かつ、エイリアス先は変更できるため、鍵の変更も容易になる。
# エイリアスあり
kms_master_key_id = "alias/terraform-tfstate"
# エイリアスなし
kms_master_key_id = "c8cf2cdc-399f-42fa-8b76-c01efff5f855"
S3への追記 S3の暗号化設定で先ほどのKMSの鍵を使用するように指定。 s3.tf
# ランダム値生成
resource "random_string" "s3_unique_key" {
length = 6
upper = false
lower = true
numeric = true
special = false
}
# 本体
resource "aws_s3_bucket" "tfstate_storage" {
bucket = "tfstate-storage-${random_string.s3_unique_key.result}"
tags = {
Name = "${var.project}-${var.environment}-s3"
}
}
# 省略~~~~~~~~~~
# 追記
# 暗号化設定
resource "aws_s3_bucket_server_side_encryption_configuration" "tfstate_storage" {
bucket = aws_s3_bucket.tfstate_storage.id
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.tsstate_storage_key.arn
sse_algorithm = "aws:kms"
}
}
}
ここまででS3の準備は完了である。 これでもtfstateファイルは運用可能な状態であるが、複数人で行う際には問題が発生するケースががある。 それは同時に変更を行った場合である。同時変更が記録され、tfstateファイルの中身が壊れてしまう恐れがある。 これを回避するためにDynamoDBを使用し、terraform plan / apply 開始時に DynamoDB からロック取得できるようにする。
DynamoDBを作成する
db.tfを作成する
resource "aws_dynamodb_table" "tfstate-lock-db" {
name = "tfstate-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID" # 主キーの名前
attribute { # 属性定義
name = "LockID"
type = "S" # String
}
}
コマンド実行
terraformコマンドを実行し、S3、KMS、DynamoDBを実施に反映していく
terraform apply
main.tfに追記を行う
実際にAWS上にリソースが作成されたのを確認したら、main.tfにtfstateファイルの保存先の設定を記載してく terraformブロックのbackendでtfstateファイルの保存先を指定できる
backendの中では変数や使えないのでハードコーディングをすることになる main.tf
terraform {
required_version = "1.12.1"
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.99.1"
}
}
# 設定を追記
backend "s3" {
bucket = "tfstate-storage-icpnvm"
key = "state/terraform.tfsstate"
region = "ap-northeast-1"
encrypt = true
kms_key_id = "alias/tsstate_storage_key_alias"
dynamodb_table = "tfstate-lock"
}
}
記載し終わったら、
terraform init
を行い
terraform apply
をすれば完了である。
CLIアカウントに適切なポリシーをアタッチしていない場合はエラーになるため、エラー内容に従ってポリシーをアタッチしていけば良い。 筆者は S3への権限、DynamoDBへの権限でエラーになったのを確認できたため、設定を行い無事にtfstateファイルの保存をs3にすることができた。
簡単ではあるが、以上とする。