「terraform initを一度実行した後でbackendの設定を変えたのにplanに反映されない。なぜか」
この2つの疑問は、backendブロックが「terraform initのタイミングにしか評価されない」という動作仕様から来ている。plan・applyの段階ではbackendブロックは読み直されない。そのため変数解決も行われず、設定変更も無視される。
この記事では、backendブロックの評価タイミングの仕組みを整理した上で、s3・gcs・httpの3種別それぞれの必須キーと認証情報の渡し方を解説する。どのbackendを選ぶべきかという選定比較は本記事の対象外とし、実装上の設定キーと認証の仕組みに絞って説明する。
この記事のポイント
・backendブロックはterraform init時にしか評価されない(planでは読まれない)
・backendブロックで変数は使えない。-backend-configで外から渡す
・s3はbucket/key/region、gcsはbucket、httpはaddressが必須キー
・認証情報はbackendに書かず環境変数かIAMロールで渡すのが標準
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜbackendブロックはinit時にしか評価されないのか
Terraformの初期化フローを確認すると理由がわかる。terraform initはbackendブロックを読み取り、接続先のstateバックエンドを確立する。この結果は.terraform/ディレクトリ内の.terraform.tfstate(ローカルバックエンドメタデータ)やプロバイダーキャッシュとして保存される。terraform planやterraform applyはこの保存済みのバックエンド設定を使ってstateを読み書きする。backendブロックをplan時に再評価してしまうと、同一のstateに対して「どのバックエンドを使うか」が実行ごとに変わりうる状況になり、stateの一貫性が保証できなくなる。そのため設計上、backendブロックの評価はinitの1回に限定されている。つまり、backendブロックを変更したら
terraform initを再実行しなければならない。再実行しないと変更は反映されないまま、古いバックエンド設定が使われ続ける。# backendブロックを変更した後は必ずinitを再実行する $ terraform init Initializing the backend... Do you want to copy existing state to the new backend? Pre-existing state was found while migrating the previous "s3" backend to the newly configured "gcs" backend. No existing state was found in the newly configured "gcs" backend. Do you want to copy this state to the new backend? Enter "yes" to copy and "no" to start with an empty state. Enter a value: yes
backendブロックで変数・式が使えない制約と回避策
1. 変数が使えない理由とエラーメッセージ
backendブロックの評価はTerraformの初期化フェーズ(init)で行われる。このタイミングはHCLの変数解決やlocalsの評価より前であるため、var.xxxやlocal.xxxをbackendブロックの中に書いてもTerraformは値を解決できない。以下は変数を使おうとした場合に出るエラーの例だ。
# NG: backendブロックでvar.xxxを使おうとした場合 terraform { backend "s3" { bucket = var.state_bucket # これはエラーになる key = "prod/terraform.tfstate" region = var.aws_region # これもエラーになる } }
$ terraform init ╷ │ Error: Variables may not be used here │ │ on main.tf line 3, in terraform: │ 3: bucket = var.state_bucket │ │ Variables may not be used here. ╵
local.xxxや文字列テンプレート("${var.env}-bucket"形式)も使えない。backendブロック内の値はリテラル文字列か、後述の-backend-configで外から注入する方式に限定される。2. -backend-configオプションで外から値を注入する
変数の代わりに使うのがterraform initの-backend-configフラグだ。このフラグでキーと値のペアをコマンドライン引数として渡すと、backendブロックの対応する設定を上書き・補完できる。# -backend-configで環境ごとにbucketとkeyを切り替える例 $ terraform init \ -backend-config="bucket=mycompany-tfstate-prod" \ -backend-config="key=prod/terraform.tfstate" \ -backend-config="region=ap-northeast-1"
-backend-configに展開して渡すパターンが多い。# GitHub Actionsでの例(リポジトリ変数から値を展開) - name: Terraform Init run: | terraform init \ -backend-config="bucket=$TF_STATE_BUCKET" \ -backend-config="key=$TF_STATE_KEY" \ -backend-config="region=$AWS_REGION" env: TF_STATE_BUCKET: ${{ vars.TF_STATE_BUCKET }} TF_STATE_KEY: ${{ vars.TF_STATE_KEY }} AWS_REGION: ${{ vars.AWS_REGION }}
3. partial configurationでファイルに分離する
設定項目が多い場合は、backendブロックを空のまま(型だけ宣言)にして、設定ファイルを-backend-configで指定するpartial configurationが使いやすい。# backend.tf - 型だけを宣言。必須キーは書かない terraform { backend "s3" {} }
# backend-prod.tfvars - 値だけを別ファイルに切り出す bucket = "mycompany-tfstate-prod" key = "prod/terraform.tfstate" region = "ap-northeast-1" dynamodb_table = "terraform-lock-prod" encrypt = true
# initでファイルごと渡す $ terraform init -backend-config=backend-prod.tfvars
backend-prod.tfvarsをgit管理外(.gitignore)に置いてもよいし、環境ごとにbackend-dev.tfvars/backend-stg.tfvars/backend-prod.tfvarsと分けて切り替えることもできる。
>> Terraform実践セミナーの詳細はこちら
S3 backendの必須キーと認証情報の渡し方
1. 必須キーと推奨キー
S3 backendの必須キーはbucket・key・regionの3つだ。dynamodb_tableは必須ではないが、チームで同一stateを操作する場合はロック制御のために設定しておくべきだ。terraform { backend "s3" { bucket = "mycompany-tfstate" # 必須: S3バケット名 key = "prod/ec2/terraform.tfstate" # 必須: バケット内のパス region = "ap-northeast-1" # 必須: バケットのリージョン dynamodb_table = "terraform-lock" # 推奨: stateロック用 encrypt = true # 推奨: サーバーサイド暗号化 } }
keyは「どのプロジェクト・環境のstateか」を識別するパスになる。prod/ec2/terraform.tfstateのように階層を付けておくと、同一バケット内で複数のTerraform管理リソースを分離して管理できる。2. AWS認証情報の渡し方
S3 backendはAWSのCredential Chainに従って認証情報を解決する。backendブロックにaccess_key/secret_keyを直接書く方法もあるが、コードに認証情報が残るため推奨しない。# ローカル開発: 環境変数で渡す $ export AWS_ACCESS_KEY_ID="AKIA..." $ export AWS_SECRET_ACCESS_KEY="..." $ export AWS_DEFAULT_REGION="ap-northeast-1" $ terraform init Initializing the backend... Successfully configured the backend "s3"! Terraform will automatically use this backend unless the backend configuration changes.
# GitHub Actions: aws-actions/configure-aws-credentialsでIAMロールを引き受ける - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/terraform-state-role aws-region: ap-northeast-1
~/.aws/credentialsが存在する場合はそちらも使える。プロファイルを指定したい場合はbackendブロックにprofile = "myprofile"を追記するか、環境変数AWS_PROFILEを設定する。GCS backendの必須キーと認証情報の渡し方
1. 必須キーと推奨キー
GCS(Google Cloud Storage)backendの必須キーはbucketの1つだけだ。prefixを指定しないとdefault.tfstateというファイル名でバケット直下に保存される。環境ごとにstateを分けるためにprefixは実質的に必須になる。terraform { backend "gcs" { bucket = "mycompany-tfstate" # 必須: GCSバケット名 prefix = "prod/terraform/state" # 推奨: バケット内のパスプレフィックス } }
.tflockファイル)で実現するため、DynamoDBのような追加リソースは不要だ。2. GCP認証情報の渡し方
GCS backendはApplication Default Credentials(ADC)を使って認証する。ローカル開発ではgcloud auth application-default loginが最もシンプルだ。# ローカル開発: gcloudでADCを設定する $ gcloud auth application-default login Credentials saved to file: [/home/user/.config/gcloud/application_default_credentials.json] # または: サービスアカウントキーをJSONファイルで指定する場合 $ export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json" $ terraform init
# CI/CD: サービスアカウントのJSONをGOOGLE_CREDENTIALSに直接渡す場合 $ export GOOGLE_CREDENTIALS='{ "type": "service_account", "project_id": "myproject", ... }' $ terraform init
GOOGLE_CREDENTIALSにJSONファイルのパスではなくJSON文字列そのものを渡す方法は、CIでシークレットとして扱いやすい。GKEやCloud Runで実行する場合はWorkload Identityが使えるため認証情報のファイル管理が不要になる。HTTP backendの必須キーと認証情報の渡し方
HTTP backendはRESTful APIでstateを読み書きするバックエンドで、GitLab Managed Terraform State等に使われる。1. 必須キーと任意キー
HTTP backendの必須キーはaddressの1つだけだ。stateロック機能を使う場合はlock_addressとunlock_addressも設定する。terraform { backend "http" { address = "https://gitlab.example.com/api/v4/projects/42/terraform/state/prod" lock_address = "https://gitlab.example.com/api/v4/projects/42/terraform/state/prod/lock" unlock_address = "https://gitlab.example.com/api/v4/projects/42/terraform/state/prod/lock" lock_method = "POST" # デフォルト: LOCK unlock_method = "DELETE" # デフォルト: UNLOCK } }
2. 認証情報の渡し方(TF_HTTP_USERNAME/PASSWORD)
HTTP backendへの認証はbackendブロックのusername/passwordまたは対応する環境変数で設定する。変数が使えないbackendブロックに認証情報を直接書くとコードに残ってしまうため、環境変数を使う方が安全だ。# TF_HTTP_*環境変数で渡す(backendブロックには書かない) $ export TF_HTTP_USERNAME="myuser" $ export TF_HTTP_PASSWORD="mytoken" # GitLabならPersonal Access Token $ terraform init
# GitLab CI/CDの場合: CI変数を展開して渡す $ export TF_HTTP_ADDRESS="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE_NAME}" $ export TF_HTTP_LOCK_ADDRESS="${TF_HTTP_ADDRESS}/lock" $ export TF_HTTP_UNLOCK_ADDRESS="${TF_HTTP_ADDRESS}/lock" $ export TF_HTTP_USERNAME="gitlab-ci-token" $ export TF_HTTP_PASSWORD="${CI_JOB_TOKEN}" $ terraform init
よくあるエラーと対処法
「Variables may not be used here」backendブロックで
var.xxxやlocal.xxxを使おうとしたときのエラー。-backend-configかpartial configurationに切り替えること。「Backend initialization required」
backendブロックの内容を変更したのに
terraform initを再実行していない状態でplanを実行すると発生する。terraform initを再実行することで解消する。$ terraform plan ╷ │ Error: Backend initialization required, please run "terraform init" │ │ Reason: Backend configuration changed for "s3" ╵ # 対処: terraform initを再実行する $ terraform init
S3 backendでバケット名・リージョン・認証情報のいずれかが誤っている場合に出る。
aws s3 ls s3://バケット名 --region リージョンでバケットへのアクセスを確認してから再実行する。GCS backendで「google: could not find default credentials」
Application Default Credentialsが設定されていない状態。
gcloud auth application-default loginかGOOGLE_APPLICATION_CREDENTIALSの設定が必要だ。本記事のまとめ
| backendの種別 | 必須キー | 認証情報の渡し方 |
|---|---|---|
| s3 | bucket / key / region |
環境変数(AWS_ACCESS_KEY_ID等)またはIAMロール |
| gcs | bucket |
GOOGLE_APPLICATION_CREDENTIALS またはADC |
| http | address |
TF_HTTP_USERNAME / TF_HTTP_PASSWORD 環境変数 |
・変数・式はbackendブロックに書けない → -backend-configかpartial configurationで渡す
・backendブロックを変更したらterraform initを必ず再実行する
・認証情報はbackendブロックに直書きせず環境変数かIAMロールで渡す
この3点を押さえれば、s3・gcs・http、どのbackend種別でも設定上のハマりどころを避けて実装できる。
>> Terraform実践セミナーの詳細はこちら
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:Route 53のサブドメインをTerraformで別ゾーンに切り出す設計|NSレコード委任と環境ごとの権限分離
- この記事の属するカテゴリ:Terraformへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます