Terraform Actionを使ってみた。 命令的な操作をリソースのライフサイクルイベントに紐づけて実行できます

Terraform Actionを使ってみた。 命令的な操作をリソースのライフサイクルイベントに紐づけて実行できます

Terraform v1.14で追加された新機能「Actions」について、CloudFrontのキャッシュ削除やデプロイ通知の自動実行を例に、実装方法と活用シーンを詳しく解説します。
2026.07.10

Terraform Actions とは

Terraform v1.14 で追加された新機能です。これまで Terraformは「宣言的」にリソースを管理するだけでしたが、Actions によって「命令的な操作」をライフサイクルイベントに紐付けて実行できるようになりました。

例えば、以下のようなActionブロックを定義します。CloudFrontのキャッシュの全無効化を実施するActionです。

action "aws_cloudfront_create_invalidation" "cache_bust" {
  config {
    distribution_id = aws_cloudfront_distribution.site.id
    paths           = ["/*"]
    timeout         = 900
  }
}

これを2つの方法で実行できます。

1. CLIで実行

-invoke オプションで、実行したいActionを明示的に指定します。

% terraform apply -invoke=action.aws_cloudfront_create_invalidation.cache_bust

(略)

Terraform will perform the following actions:

Plan: 0 to add, 0 to change, 0 to destroy. Actions: 1 to invoke.

Terraform will invoke the following action(s):

  # action.aws_cloudfront_create_invalidation.cache_bust will be invoked
    action "aws_cloudfront_create_invalidation" "cache_bust" {
        config {
            distribution_id = "E2DE08ODLWUNIV"
            paths           = [
                "/*",
            ]
            timeout         = 900
        }
    }

Would you like to invoke the specified actions?
  Terraform will invoke the actions described above, and any changes will be written to the state without modifying real infrastructure
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

Action started: action.aws_cloudfront_create_invalidation.cache_bust (triggered by CLI)
Action action.aws_cloudfront_create_invalidation.cache_bust (triggered by CLI): Starting cache invalidation for CloudFront distribution E2DE08ODLWUNIV...
Action action.aws_cloudfront_create_invalidation.cache_bust (triggered by CLI): Creating invalidation request for 1 path(s)...
Action action.aws_cloudfront_create_invalidation.cache_bust (triggered by CLI): Invalidation I5HIO1HL8629S7IUZJO43K1C6U created, waiting for completion...
Action action.aws_cloudfront_create_invalidation.cache_bust (triggered by CLI): Invalidation I5HIO1HL8629S7IUZJO43K1C6U is currently 'InProgress', continuing to wait for completion...
Action action.aws_cloudfront_create_invalidation.cache_bust (triggered by CLI): CloudFront cache invalidation I5HIO1HL8629S7IUZJO43K1C6U completed successfully for distribution E2DE08ODLWUNIV
Action complete: action.aws_cloudfront_create_invalidation.cache_bust (triggered by CLI)

Apply complete! Resources: 0 added, 0 changed, 0 destroyed. Actions: 1 invoked.

2. 他リソースのライフサイクルイベントで発火

本命の使い方はこちらです。

CloudFrontのキャッシュを削除したいケースとしては、オリジンのコンテンツを更新したときが代表的です。コンテンツ更新をトリガーに、このキャッシュ削除Actionを自動実行できます。

サンプルとして、S3に静的サイト(site/index.htmlsite/style.css)を配置し、コンテンツが変更されたときにキャッシュ削除とデプロイ完了通知を自動実行する構成を組んでみます。

まず、デプロイ完了通知のActionは未作成なので作成します。

resource "aws_sns_topic" "notifications" {
  name = "${var.project_name}-deploy-notifications"

  tags = {
    Project = var.project_name
  }
}

resource "aws_sns_topic_subscription" "email" {
  topic_arn = aws_sns_topic.notifications.arn
  protocol  = "email"
  endpoint  = var.notification_email
}

action "aws_sns_publish" "deploy_notify" {
  config {
    topic_arn = aws_sns_topic.notifications.arn
    subject   = "[${var.project_name}] デプロイ完了"
    message = jsonencode({
      project     = var.project_name
      environment = "production"
      cloudfront  = aws_cloudfront_distribution.site.domain_name
      s3_bucket   = aws_s3_bucket.site.id
    })
  }
}

次にS3で実現する静的サイトの部分です。site/配下のファイルをfileset()で列挙し、for_eachで1オブジェクトずつS3にアップロードします。ファイルの内容が変わるとfilemd5で計算したetagが変わるため、apply時に差分として検知できます。

locals {
  content_dir   = "${path.module}/site"
  content_files = fileset(local.content_dir, "**")
}

resource "aws_s3_object" "site" {
  for_each = local.content_files

  bucket = aws_s3_bucket.site.id
  key    = each.value
  source = "${local.content_dir}/${each.value}"
  etag   = filemd5("${local.content_dir}/${each.value}")
}

このコンテンツ変更を検知してActionを発火させるのが、次のterraform_dataリソースです。

resource "terraform_data" "deploy_trigger" {
  input = sha256(jsonencode({
    for key, obj in aws_s3_object.site : key => obj.etag
  }))

  lifecycle {
    action_trigger {
      events = [after_create, after_update]
      actions = [
        action.aws_cloudfront_create_invalidation.cache_bust,
        action.aws_sns_publish.deploy_notify,
      ]
    }
  }

  depends_on = [
    aws_s3_bucket_policy.site,
    aws_sns_topic_subscription.email,
  ]
}

ポイントはinputの作り方です。全オブジェクトのkeyetagをまとめてsha256でハッシュ化しているため、次のどちらが起きてもinputの値が変わり、terraform_dataが更新対象になります。

  • 既存ファイルの内容が変わる(etagが変わる)
  • ファイルが追加・削除される(ハッシュ化対象のkeyの集合が変わる)

inputが変化するとterraform_dataafter_update(初回applyではafter_create)イベントを発火し、action_triggeractionsに登録した2つのActionが呼び出されます。

  1. action.aws_cloudfront_create_invalidation.cache_bust: CloudFrontのキャッシュを全パス(/*)で無効化
  2. action.aws_sns_publish.deploy_notify: SNS経由でデプロイ完了をメール通知

depends_onaws_s3_bucket_policy.siteaws_sns_topic_subscription.emailを指定しているのは、バケットポリシーやSNSサブスクリプションが整う前にActionが実行されてしまわないようにするためです。

これで、site/index.htmlの内容を書き換えてapplyするだけで、S3オブジェクトの更新に続けてキャッシュ削除とデプロイ通知が自動的に呼び出されるようになりました。CLIで都度-invokeするのと違い、コンテンツ更新時にキャッシュ削除を忘れる心配がなくなります。

何が嬉しいのか

これまでTerraformで「デプロイ後にCloudFrontのキャッシュを消す」「完了をSNSで通知する」といった操作をしたい場合、local-exec provisionerやCI/CDのTerraform実行後段のスクリプトに頼るのが定番でした。しかし、これらはTerraformのplan/applyのライフサイクル外で動くため、実行されたかどうかの確認や実行順序の管理はTerraformの管轄外になってしまいます。これらをActionsに移行すると嬉しいポイントは次の3つです。

1. plan/applyのフローに乗る

「1. CLIで実行」で見た通り、-invokeで単体実行する際はterraform applyが呼び出すActionを明示し、実行前に承認を求めます。CI/CDスクリプトに埋め込んだaws cloudfront create-invalidationのように、Terraformの外側でひっそり実行されるオペレーションがなくなります。

2. リソースのライフサイクルに直接フックできる

action_triggereventsafter_create / after_updateなど)でリソースの状態変化に紐付けられるため、「コンテンツを更新したらキャッシュを消す」を毎回手動で実行する必要がありません。

3. 複数の運用操作を1か所にまとめられる

action_triggeractionsにはActionをリストで複数指定できます。本記事の例のように「キャッシュ削除 → デプロイ通知」のような一連の運用フローをコード側に集約できます。

各 Provider 対応状況 (2026年7月9日時点)

AWS

Terraform AWS Provider(v6.x系)では、以下の11種類のActionが提供されています。

  • aws_cloudfront_create_invalidation
  • aws_codebuild_start_build
  • aws_dms_start_replication_task_assessment_run
  • aws_dynamodb_create_backup
  • aws_ec2_stop_instance
  • aws_events_put_events
  • aws_lambda_invoke
  • aws_ses_send_email
  • aws_sfn_start_execution
  • aws_sns_publish
  • aws_transcribe_start_transcription_job

https://github.com/hashicorp/terraform-provider-aws/tree/main/website/docs/actions

aws_lambda_invoke は汎用性が高そうですね。

Azure

azurerm providerでは、以下の6種類のActionが提供されています。

  • azurerm_data_protection_backup_instance_protect
  • azurerm_front_door_cache_purge
  • azurerm_managed_redis_databases_flush
  • azurerm_mssql_execute_job
  • azurerm_netapp_volume_bucket_credentials
  • azurerm_virtual_machine_power

https://github.com/hashicorp/terraform-provider-azurerm/tree/main/website/docs/actions

tfe

HCP Terraform / Terraform Enterprise を操作する公式provider tfe では、以下の1actionが提供されています。

未提供provider

以下のproviderに関しては現時点でActionsは提供されていないことを確認しました。

補足

terraform apply -invoke="xxx" 実行時は他のコードは無視される

例えば新リソースをコードに追加したとします。

+ resource "aws_s3_bucket" "new" {
+ }

-invoke なしで terraform apply もしくは terraform plan を実行した場合、当然以下のようにバケットをひとつ新規作成するプランが作成されます。

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_s3_bucket.new will be created
  + resource "aws_s3_bucket" "new" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = (known after apply)
      + bucket_domain_name          = (known after apply)
      + bucket_namespace            = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_region               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = "ap-northeast-1"
      + request_payer               = (known after apply)
      + tags_all                    = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + cors_rule (known after apply)

      + grant (known after apply)

      + lifecycle_rule (known after apply)

      + logging (known after apply)

      + object_lock_configuration (known after apply)

      + replication_configuration (known after apply)

      + server_side_encryption_configuration (known after apply)

      + versioning (known after apply)

      + website (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

ところが、-invoke を追加するとこのコード↔リソース実体の差分は無視され、指定したActionだけが実行されるプランになります。

% terraform apply -invoke=action.aws_cloudfront_create_invalidation.cache_bust
(略)

Terraform will perform the following actions:

Plan: 0 to add, 0 to change, 0 to destroy. Actions: 1 to invoke.

Terraform will invoke the following action(s):

  # action.aws_cloudfront_create_invalidation.cache_bust will be invoked
    action "aws_cloudfront_create_invalidation" "cache_bust" {
        config {
            distribution_id = "E2DE08ODLWUNIV"
            paths           = [
                "/*",
            ]
            timeout         = 900
        }
    }

Would you like to invoke the specified actions?
  Terraform will invoke the actions described above, and any changes will be written to the state without modifying real infrastructure
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: 

CLI実行の際に複数のActionを実行できる?

できません。
-invoke を複数個指定したところエラーになりました。

% terraform apply \
-invoke=action.aws_cloudfront_create_invalidation.cache_bust \
-invoke=action.aws_sns_publish.deploy_notify

│ Error: Invalid arguments

│ Only one action can be invoked at a time.


For more help on using this command, run:
  terraform apply -help

local-execでやる場合との比較

「何が嬉しいのか」欄で少し触れましたが、今回の例の「デプロイ後にCloudFrontのキャッシュを消す」「完了をSNSで通知する」といった操作をしたい場合、これまでもlocal-exec provisionerを使って実現できました。例えば以下のようなコードです。local-exec内でAWS CLIを実行します。

resource "terraform_data" "deploy_trigger" {
  triggers_replace = sha256(jsonencode({
    for key, obj in aws_s3_object.site : key => obj.etag
  }))

  provisioner "local-exec" {
    command = <<-EOT
      aws cloudfront create-invalidation \
        --distribution-id ${aws_cloudfront_distribution.site.id} \
        --paths "/*"
    EOT
  }

  provisioner "local-exec" {
    command = <<-EOT
      aws sns publish \
        --topic-arn ${aws_sns_topic.notifications.arn} \
        --subject "[${var.project_name}] デプロイ完了" \
        --message '${jsonencode({
    project     = var.project_name
    environment = "production"
    cloudfront  = aws_cloudfront_distribution.site.domain_name
    s3_bucket   = aws_s3_bucket.site.id
})}'
    EOT
  }

  depends_on = [
    aws_s3_bucket_policy.site,
    aws_sns_topic_subscription.email,
  ]
}

この local-exec案とAction案を比較してみます。結論から言うとActionsの方が優れていると考えています。

観点 local-exec + AWS CLI Terraform action
ローカル依存 実行環境に aws CLI・シェルが必要 不要(プロバイダーが実行)。CI や HCP でも一貫
認証情報 シェルの環境変数に依存(provider と別系統になりがち) provider の認証をそのまま使う(assume_role 等も一貫)
設定の検証 生の文字列コマンド。タイポ・エスケープ事故 スキーマ検証あり
plan での可視性 ほぼ不透明(apply 時に走るだけ) plan にアクション実行が現れる
位置づけ HashiCorp 公式が「代替手段を尽くしてから使うべき」との見解 この用途のために作られた機能
CLIからの単発実行 不可(リソースのライフサイクルに紐づく) terraform apply -invoke=... で可能

「plan での可視性」の詳細

上記 local-execのコードで S3ファイルに変更が発生した際のplanは以下の様になります。terraform_data リソースがreplaceされるのがわかるだけで、 local-exec provisionerで定義されているAWS CLIの処理は見えません。

  # terraform_data.deploy_trigger must be replaced
-/+ resource "terraform_data" "deploy_trigger" {
      ~ id               = "f42e2c3d-434d-6983-fa7a-9fec8af87209" -> (known after apply)
      ~ triggers_replace = "6b5b9313b851580d010b2a7b18c5d04a8c021b42e03c8acb3727eac7aba5d96b" -> "bcc13bcaf69c72b31cfff943731b3ec1e61f5d3e4acfe1fab9b1decb3fbb24bc"
    }

一方 Actionの場合は以下の様に実行されるActionの内容もplan内で確認可能です。

  # terraform_data.deploy_trigger will be updated in-place
  ~ resource "terraform_data" "deploy_trigger" {
        id     = "1aed8433-5931-ba60-b1da-a4109b14bee2"
      ~ input  = "6b5b9313b851580d010b2a7b18c5d04a8c021b42e03c8acb3727eac7aba5d96b" -> "bcc13bcaf69c72b31cfff943731b3ec1e61f5d3e4acfe1fab9b1decb3fbb24bc"
      ~ output = "6b5b9313b851580d010b2a7b18c5d04a8c021b42e03c8acb3727eac7aba5d96b" -> (known after apply)
    }

    # Actions to be invoked after this change in order:
    action "aws_cloudfront_create_invalidation" "cache_bust" {
        config {
            distribution_id = "E2DE08ODLWUNIV"
            paths           = [
                "/*",
            ]
            timeout         = 900
        }
    }
    action "aws_sns_publish" "deploy_notify" {
        config {
            message   = jsonencode(
                {
                    cloudfront  = "d3sq6rvt2o83h0.cloudfront.net"
                    environment = "production"
                    project     = "actions-test"
                    s3_bucket   = "actions-test-site-047429787746"
                }
            )
            subject   = "[actions-test] デプロイ完了"
            topic_arn = "arn:aws:sns:ap-northeast-1:047429787746:actions-test-deploy-notifications"
        }
    }

検証環境情報

  • Terraform version: v1.14.0
  • Terraform AWS Provider version: 6.42.0

参考情報

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事