Terraform の state とは何か
Terraform が管理するインフラの現状を記録したデータベース(JSONファイル)
state の役割
- 差分計算の基盤
- Terraform は「HCL に書いた理想」と「state に記録された現状」を比較して差分を出します
→ state がなければ Terraform は「現状」を知らず、毎回すべてを新規作成しようとします
- Terraform は「HCL に書いた理想」と「state に記録された現状」を比較して差分を出します
- 実リソースとの対応付け
- 論理名 (aws_instance.web) とクラウド上の実リソースID (i-0abc123...) をマッピングします
→ これにより Terraform は「このリソースを更新すべきか」「削除すべきか」を正しく判断できます。
- 論理名 (aws_instance.web) とクラウド上の実リソースID (i-0abc123...) をマッピングします
- 依存関係の解決
- state 内には依存関係が保存されており、apply 時にリソースをどの順番で作成・変更すべきかを決定できます
stateとセキュリティリスク
セキュリティの注意点
- Secrets Manager / Parameter Store など「外部の秘匿ストア」を使っても、Terraform が管理するなら state に値は残ります
- 例:
aws_ssm_parameter
のvalue
をsecurestring
にしても、state 内には平文で保存されます(暗号化されない)
- 例:
-
data
ソースで参照した値も state に保存されます。Secrets Manager などから読み取った機密情報もそのまま記録されるので注意が必要です -
sensitive = true
は plan や apply のログ出力を抑制するだけで、state ファイルには必ず保存されます
セキュリティ的に残るパターン
- ECS / Fargate タスク定義の環境変数
- Lambda の環境変数
- Kubernetes Secret / ConfigMap
- CloudFormation パラメータ
避けるべき書き方と推奨パターン
ECS / Fargate タスク定義の環境変数
NG(値が state に平文で残ります)
variable "db_password" {
type = string
sensitive = true
}
resource "aws_ecs_task_definition" "app" {
# 中略
container_definitions = jsonencode([{
name = "app"
image = "myimage"
environment = [
{ name = "DB_PASSWORD", value = var.db_password } # ← ここは state に平文で残ります
]
}])
}
OK(値は渡さず、Secrets/SSMの参照を渡しません)
resource "aws_ecs_task_definition" "app" {
container_definitions = jsonencode([{
name = "app"
image = "myimage"
secrets = [
{ name = "DB_PASSWORD", valueFrom = aws_secretsmanager_secret.db.arn } # 値は残らないです
]
}])
}
Lambda の環境変数
NG(値が state に平文で残ります)
variable "secret_key" {
type = string
sensitive = true
}
resource "aws_lambda_function" "fn" {
# 中略
environment {
variables = {
SECRET_KEY = var.secret_key # ← state に平文で残ります
}
}
}
OK(値は env に入れません。関数が実行時に取りに行きます)
# Lambda に Secrets Manager 読み取り権限を付与
resource "aws_iam_role_policy_attachment" "attach" {
role = aws_iam_role.lambda.name
policy_arn = aws_iam_policy.lambda_read_secret.arn
}
resource "aws_lambda_function" "fn" {
environment {
variables = {
DB_SECRET_ARN = aws_secretsmanager_secret.db.arn # ARNのみ
}
}
}
# 値は Lambda 実行時に SDK などで取得
Kubernetes プロバイダ経由の Secret / ConfigMap
NG(値が state に平文で残ります)
variable "db_password" {
type = string
sensitive = true
}
# ConfigMap でも同様(機密は本来 Secret を使うべきですが、state には同様に残ります)
resource "kubernetes_config_map" "cfg" {
metadata { name = "app-config" }
data = {
api_key = var.db_password # ← state に平文で残ります
}
}
OK(External Secrets Operator 等で“参照”を管理します)
resource "kubernetes_manifest" "db_external_secret" {
manifest = {
apiVersion = "external-secrets.io/v1beta1"
kind = "ExternalSecret"
metadata = { name = "db-secret" }
spec = {
secretStoreRef = { name = "aws", kind = "ClusterSecretStore" }
target = { name = "db-secret" }
data = [{
secretKey = "password"
remoteRef = { key = "prod/app/db_password" } # キー名だけ残ります
}]
}
}
}
CloudFormation 経由でのパラメータ渡し
NG(値が state に平文で残ります)
variable "db_password" {
type = string
sensitive = true
}
resource "aws_cloudformation_stack" "app" {
name = "app-stack"
template_body = file("template.yaml")
parameters = {
DBPassword = var.db_password # ← state に平文で残ります
}
}
OK(テンプレート側を“動的参照”にし、Terraform から値を渡しません)
Parameters:
DBPassword:
Type: AWS::SSM::Parameter::Value<String>
Default: /prod/app/db_password # 名前だけ残ります
resource "aws_cloudformation_stack" "app" {
name = "app-stack"
template_body = file("template.yaml")
# parameters で値は渡しません
}
stateに残る、書き込まれる条件
state に残る条件
- Terraform管理リソース(
resource
)-
apply
やimport
で Terraform 管理下に入った実体は、論理名と実リソースID・属性がresources
配下に保存されます。モジュール名、タイプ、名前、インスタンス配列などの構造で保持されます
-
- Data ソース(
data
)の参照結果- 実体は作りませんが、参照結果は state に
mode: "data"
として記録されます(キャッシュ用途)
- 実体は作りませんが、参照結果は state に
- Outputs
-
output
に定義した値はoutputs
セクションに保存されます(refresh
/-refresh-only
でも更新される)
-
-
count
/for_each
で展開された個々のインスタンス- それぞれが
instances
配列の要素として保存され、for_each
利用時は state 構造に対応フィールド(EachMode)が現れます
- それぞれが
- モジュール階層(フルアドレス)
-
module.ecs.aws_...
のように、どのモジュール配下のリソースかがmodule
フィールドで保存されます
-
state に書き込まれる条件
-
terraform apply
- 望ましい定義(HCL)と state(現状) の差分をもとに作成/更新/削除し、その結果を state に反映します(差分計算の“起点”が state)
-
terraform import
- 既存リソースを管理下に取り込むと、そのマッピングが state に追加されます
-
terraform refresh
/terraform apply -refresh-only
- コンソール等で外部から手動変更があった場合、実体に合わせて state を更新します(実体は変えない)。
output
も更新対象です
- コンソール等で外部から手動変更があった場合、実体に合わせて state を更新します(実体は変えない)。
stateに残らない/ずれる条件
state に残らない条件
- locals など HCL 内の変数計算結果
-
locals
の評価結果は state には保持されないです。毎回再評価されるため、state に追跡されることはないです
-
- terraform.tfvars や環境変数から渡した値
- 変数値そのものは state に保存されないです。ただしリソース属性に使えば、その属性として state に残ります
state がずれる条件
- Terraform が直接管理しない実体
- 例:コンソールや CLI で手動作成したリソースは、
terraform import
をしない限り state には載らないです
- 例:コンソールや CLI で手動作成したリソースは、
- 外部で手動変更した場合
- Terraform の外でリソースを変更しても、その瞬間には state は更新されないです。次回
terraform plan
で差分として検出され、terraform refresh
で state に反映されます
- Terraform の外でリソースを変更しても、その瞬間には state は更新されないです。次回
- ignore_changes を指定した属性
-
lifecycle { ignore_changes = [...] }
を設定すると、対象属性が HCL と state で違っても「差分なし」と扱われます
ただし state には最新値が反映されるので、「HCL の定義どおりに戻す」ことは行われないです。
-
- state 操作コマンドを使った場合
-
terraform state rm
で state からリソースを削除すると、クラウド上には実体が残りますが state には存在しなくなるです
逆にterraform state mv
を使うと論理名だけが変わり、実体は変わらないです
-
- provider の制約で読み取られない属性
- 一部のプロバイダやリソースは API 仕様の都合で「読み取れない属性」があり、その値は state に反映されないです
この場合、plan
のたびに差分が出続けることがあります
- 一部のプロバイダやリソースは API 仕様の都合で「読み取れない属性」があり、その値は state に反映されないです
state管理のベストプラクティス
リモートバックエンド(S3 / GCS / Terraform Cloud など)を利用
state をローカルに置いておくと、紛失や同時編集でトラブルになりやすいです。
そのため、S3 や GCS、Terraform Cloud などの リモート保存先を使うのが基本です。
こうしておくと、チームで同じ state を安全に共有でき、誤って上書きする心配も少なくなります。
state ファイルを Git 管理しない
state ファイルには、パスワードなどの秘密情報やクラウドのリソース ID が含まれます。
そのため Git に入れて公開したり、チーム全員がコピーを持ったりするのは危険です。
代わりに、S3 や Terraform Cloud などの リモート保存先に置いて管理します。そこでなら自動で履歴も残り、安全に共有できます。
バージョン管理とチーム運用の注意点
- ロックの有効化(DynamoDB もしくは
use_lockfile
)で同時実行を防ぎます - 権限の最小化(state バケット/Terraform Cloud への最小権限)と 暗号化・バージョニング を有効にします
- plan/apply の分離(レビュー→承認→適用) を CI で徹底します
state コマンド活用
-
terraform state list
:state に載っている論理リソースを一覧できます -
terraform state show
:特定リソースの state 上の属性を確認できます -
terraform state mv
:論理名やモジュール構成の変更時に 作り直さず マッピングを移せます -
terraform state rm
:実体を消さずに state からだけ除外できます(扱いは慎重にします)
drift(ずれ)を検知する方法
state と現実のインフラはズレ(drift)が必ず発生します。そのまま apply すると意図しない変更や事故につながるため、定期的に drift を検知する仕組みが重要です。
-
terraform plan
- plan を実行すると、HCL の定義と state、実リソースを比較し、差分があれば表示されます
→ 定期的に plan を CI で流して確認すると、外部変更を早めに気づけます。
- plan を実行すると、HCL の定義と state、実リソースを比較し、差分があれば表示されます
-
terraform plan -refresh-only
- 「リソースの変更はしないで、state を実リソースに合わせる」ための差分チェックです
→ 「コンソールで変えられていないか?」の監査用途に向いています。
- 「リソースの変更はしないで、state を実リソースに合わせる」ための差分チェックです
- CI/CD での自動検知
- GitHub Actions や GitLab CI で
terraform plan
を定期実行し、差分があれば Slack 通知します
- GitHub Actions や GitLab CI で
まとめ
Terraform の state は、インフラの現状を記録する不可欠な台帳であり、差分計算や依存解決の要です。
一方で環境変数や Secret を誤って扱うと機密が平文で残るため、Secrets Manager や SSM 参照、リモートバックエンドによる安全な管理を徹底することが重要です。