ドリフトを放置すると、次のterraform applyで意図しないリソース削除や変更が走るリスクがある。本記事では、Terraform 1.1以降で正式サポートされた
terraform plan -refresh-only を使ったドリフト検出の手順と、実務で役立つ3つの修正戦略、CI/CDへの組み込み設計、lifecycle ignore_changesで意図的に許容するパターンまで、設計の視点から解説する。この記事のポイント
・terraform plan -refresh-onlyはstateと実インフラの乖離(ドリフト)だけを表示するサブモード
・ドリフト修正には「コードで上書き・stateを現実に合わせる・ignore_changesで許容」の3戦略がある
・CI/CDに週次のrefresh-onlyジョブを組み込むことで早期発見できる設計を紹介
・lifecycle ignore_changesは「意図的に許容するドリフト」を宣言する正しい設計手段
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
インフラドリフトとは何か(Terraformと実インフラの乖離が起きる仕組み)
Terraformはインフラの現在状態を「state(tfstate)」ファイルで管理する。terraform apply を実行すると、Terraformは「stateに書かれた状態」と「実際のクラウドリソース」を突き合わせ、コードとの差分だけを適用する仕組みだ。ドリフトが発生するのは、このstateと実インフラが乖離したときだ。主な原因は次のようなケースだ。
・コンソール・AWS CLIによる手動変更:緊急対応でインバウンドルールを手動追加し、Terraformへの反映を忘れる
・別ツールによる変更:CloudFormationやAWS CDKが同じリソースに触れている
・AWSサービスの自動更新:ECSタスク定義のリビジョン番号、Auto ScalingのDesired Countなど
・別ワークスペースからの変更:チームメンバーが別環境で実行したterraform applyが想定外のリソースに触れた
旧来の
terraform refresh コマンドはstateを実インフラに自動更新するため危険とみなされ、Terraform 1.1以降で非推奨になった。現行の正解は terraform plan -refresh-only と terraform apply -refresh-only の2段構えで確認してから適用する設計だ。terraform plan -refresh-onlyの仕組みと実行手順
terraform plan -refresh-only は「現在のクラウドリソースを読み取り、stateとの差分(ドリフト)を表示するが、コードとの差分は表示しない」サブモードだ。コードを変えずにドリフトだけを安全に確認できる。1. 基本的な実行コマンド
# ドリフト検出(読み取りのみ・stateは変更しない) terraform plan -refresh-only # 出力をファイルに保存して後でレビューする場合 terraform plan -refresh-only -out=drift-$(date +%Y%m%d).tfplan
2. 実際の出力例(セキュリティグループのドリフトを検出)
AWSコンソールでセキュリティグループのインバウンドルールを手動追加した後、terraform plan -refresh-only を実行した実際の出力例だ。環境はTerraform 1.9・AWS provider 5.62を使用している。$ terraform plan -refresh-only aws_security_group.web: Refreshing state... [id=sg-0abc12345def67890] aws_instance.web: Refreshing state... [id=i-0fedcba9876543210] Note: Objects have changed outside of Terraform Terraform detected the following changes made outside of Terraform since the last "terraform apply" which may have affected this plan: # aws_security_group.web has changed ~ resource "aws_security_group" "web" { id = "sg-0abc12345def67890" name = "web-sg" + ingress { + cidr_blocks = ["203.0.113.10/32"] + description = "Emergency admin access" + from_port = 8080 + protocol = "tcp" + to_port = 8080 } } This is a refresh-only plan, so Terraform will not take any actions to undo these. If you were expecting these changes then you can apply this plan to record the updated values in the Terraform state without changing any remote objects. Plan: 0 to add, 0 to change, 0 to destroy.
Plan: 0 to add, 0 to change, 0 to destroy と表示されるのは、コードへの変更は一切行われないからだ。3. plan・apply・-refresh-onlyの関係を整理する
| コマンド | 表示内容 | stateの更新 | 実インフラへの変更 |
|---|---|---|---|
terraform plan |
コードとstateの差分(ドリフト含む) | しない | しない |
terraform plan -refresh-only |
stateと実インフラの差分のみ | しない | しない |
terraform apply -refresh-only |
確認後にstateを実インフラに合わせる | する | しない |
terraform apply |
コードの状態に実インフラを合わせる | する | する |
>> Terraform実践セミナーの詳細はこちら
ドリフトが発生する典型パターン3つ
1. 緊急対応での手動変更(最多パターン)
本番障害の際、担当者がTerraformのPRレビューを待てずにコンソールでセキュリティグループルールを追加する——このケースが最も多い。対応後にTerraformコードへ反映し忘れると、次のterraform apply でそのルールが削除される危険がある。「緊急対応した後は必ずコードに起こす」というチームルールだけでは限界があるため、後述するCI/CDによる定期検出が有効だ。2. AWSサービスの自動更新によるドリフト
ECSタスク定義やLaunch Templateのような「バージョン管理があるリソース」は、AWSが自動的に新しいリビジョンを作成することがある。Terraformが管理しているリビジョン番号とズレが生じると、毎回同じdiffが出続ける「ノイジーなplan」になる。# ECSタスク定義のリビジョン番号が自動インクリメントされドリフトが発生する例 ~ resource "aws_ecs_task_definition" "app" { ~ revision = 15 -> 16 }
lifecycle ignore_changes で対処する。3. 複数のIaCツールが同一リソースに触れる
TerraformとAWS CDKが同じVPCやセキュリティグループを管理していると、お互いの変更を上書きし合うドリフトが継続的に発生する。このパターンはplan -refresh-only で検出できるが、根本的にはツール間の責任境界(どのリソースをどのツールが管理するか)をアーキテクチャで定める必要がある。ドリフト修正の3つの戦略
ドリフトを発見したら、次の3つの戦略から状況に合わせて選ぶ。重要なのは「どちらが正しいか」の判断を先に行うことだ。戦略1: terraform applyでコードの状態に戻す(手動変更が誤りの場合)
手動変更が「一時対応」または「意図せず行われた変更」だった場合は、通常のterraform apply でコードの状態に戻す。これが最もシンプルな解決策だ。# 通常のplanでドリフトが差分として表示されることを確認 terraform plan # コードの状態に戻す(手動変更を上書き) terraform apply
戦略2: terraform apply -refresh-onlyでstateを現実に合わせる(手動変更が正しい場合)
手動変更が「正しい変更」でありコードにも反映したい場合に使う。まずstateを現実に合わせてから、コードに追記するという2ステップで対処する。# ステップ1: ドリフトを確認 terraform plan -refresh-only # ステップ2: stateを実インフラに合わせて更新(実インフラは変更しない) terraform apply -refresh-only # ステップ3: コードにも手動変更を反映してコミット # main.tfなどにリソース設定を追記し、差分がゼロになることを確認 terraform plan
戦略3: terraform importで正式にIaC管理下へ(新規リソースの取り込み)
ドリフトではなく、Terraform管理外で新たに作成されたリソースをIaCに取り込む場合はterraform import を使う。Terraform 1.5以降では importブロックを使った宣言型の方法が推奨だ。# Terraform 1.5以降: importブロックによる宣言型import import { to = aws_security_group.admin_access id = "sg-0abc12345def67890" } # importブロック定義後にplanで取り込み内容を確認 terraform plan # applyで正式にIaC管理下へ terraform apply
CI/CDにドリフト検出を組み込む設計
ドリフトは発生してすぐ発見するほど対処が楽だ。GitHub Actionsを使ってスケジュールされたDrift Detectionジョブを組み込む設計を紹介する。# .github/workflows/drift-detection.yml name: Terraform Drift Detection on: schedule: - cron: '0 0 * * 1' # 毎週月曜 09:00 JST(UTC 00:00) workflow_dispatch: # 手動実行も可能 jobs: detect-drift: runs-on: ubuntu-latest permissions: id-token: write # OIDC認証でAWS IAMロールを引き受ける contents: read steps: - uses: actions/checkout@v4 - uses: hashicorp/setup-terraform@v3 with: terraform_version: "1.9.0" - name: Configure AWS credentials (OIDC) uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/github-actions-readonly aws-region: ap-northeast-1 - name: Terraform Init run: terraform init -input=false - name: Terraform Plan -refresh-only id: drift_check run: | terraform plan -refresh-only -detailed-exitcode -no-color 2>&1 | tee plan-output.txt echo "exitcode=${?}" >> $GITHUB_OUTPUT continue-on-error: true - name: Notify drift detected if: steps.drift_check.outputs.exitcode == '2' uses: slackapi/slack-github-action@v1 with: payload: | { "text": ":rotating_light: インフラドリフトを検出しました。 ワークフロー確認: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
-detailed-exitcode オプションの終了コードが重要だ。0 なら変更なし、2 なら変更あり(ドリフト検出)、1 はエラーだ。このコードをSlack通知のトリガーとして使える。週次で実行するだけでなく、Terraformコードの変更PRにも
plan -refresh-only を組み込んでおくと、applyの前にドリフトが存在しないか確認できる。lifecycle ignore_changesで意図的ドリフトを許容する設計
すべてのドリフトが「修正すべき問題」ではない。AWSが自動で更新するフィールドや、Auto Scalingのように外部システムが管理する値は、lifecycle ignore_changes で明示的に除外するのが正しい設計だ。典型的なignore_changesの使いどころ
# ECSサービス: Auto Scalingが管理するdesired_countを除外 resource "aws_ecs_service" "app" { name = "app-service" cluster = aws_ecs_cluster.main.id task_definition = aws_ecs_task_definition.app.arn desired_count = 2 # 初期値のみTerraformで設定 lifecycle { ignore_changes = [desired_count] } } # EC2 Launch Template: PakerによるAMIローリングアップデートを許容 resource "aws_launch_template" "app" { name_prefix = "app-lt-" image_id = data.aws_ami.amazon_linux.id lifecycle { # Packerで最新AMIに更新する運用のため、image_idのドリフトを許容 ignore_changes = [image_id] } }
・使うべきケース:AWSが自動更新する値、Auto Scalingや外部デプロイツールが管理する値
・使うべきでないケース:本来Terraformで管理したい値のドリフトを「見て見ぬふり」するための乱用
ignore_changesを設定する際は、なぜignore_changesにしたかをコメントで残しておくと、後からチームが判断しやすくなる。「設計上の意図」と「怠惰な握り潰し」を区別するための記録だ。
よくあるトラブルと対処法
1. stateロックでplan -refresh-onlyが実行できない
DynamoDBを使ったstateロックが有効な環境で、別プロセスがstateを掴んでいると-refresh-only も失敗する。Error: Error acquiring the state lock Lock Info: ID: c1234567-abcd-1234-efgh-1234567890ab Path: s3://my-tfstate/terraform.tfstate Operation: OperationTypePlan # ロックを保持しているジョブを特定してからforce-unlock terraform force-unlock c1234567-abcd-1234-efgh-1234567890ab
force-unlock は対象IDを確認してから慎重に実行すること。CI/CDパイプラインが途中で死んだ場合はジョブをキャンセルしてからロック解除するのが正しい順序だ。2. 毎回同じドリフトが出続ける(タグや番号の自動更新)
AWSが自動付与するタグ(aws:autoscaling:groupName など)やリビジョン番号が毎回ドリフトとして出る場合は、ignore_changes での除外を検討する。または tags_all と tags の混同がないか確認する。デフォルトタグはAWS providerレベルで設定し、リソース個別で重複管理しないことも対策になる。3. -refresh-onlyで差分ゼロなのにplanで差分が出る
plan -refresh-only でドリフトなし、しかし通常の plan では差分あり——これは「コードとstateの差分」であり、ドリフトではない。未適用のコード変更があるということだ。この場合は通常の terraform apply で解消できる。ドリフト(外部変更)と未適用変更(コード変更)は別の概念なので混同しないようにしよう。本記事のまとめ
| 状況 | 使うコマンド・設定 | 実インフラへの影響 |
|---|---|---|
| ドリフトの有無だけ確認したい | terraform plan -refresh-only |
なし(読み取りのみ) |
| stateを実インフラに合わせたい | terraform apply -refresh-only |
なし(stateのみ更新) |
| 手動変更を取り消してコードに戻したい | terraform apply |
あり(コードの状態に戻す) |
| 管理外リソースをIaC管理下に入れたい | terraform import(importブロック推奨) |
なし(stateに取り込むのみ) |
| 意図的に外部管理させたい値がある | lifecycle { ignore_changes = [...] } |
なし(ドリフトを無視) |
plan -refresh-only をCI/CDに組み込んで週次で検出する設計と、ignore_changes で意図的ドリフトを明示する設計——この2つを習慣にすれば、大きなインシデントにつながる前に乖離を制御できるようになる。>> Terraform実践セミナーの詳細はこちら
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:TerraformでGCPのVPCとCompute EngineインスタンスをIaC管理する方法|google providerの設定とリソース設計の実践手順
- この記事の属するカテゴリ:Terraformへ戻る

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