青い照明のサーバールームに並ぶネットワーク機器のラック
現場の実践

Terraformのstate管理ミス、CI/CDで409エラーになる前に確認すべきこと

目次を見る

AWSのコンソール画面をクリックしながら手動でリソースを作り、ある日突然「あれ、このS3バケットとLambda、誰がいつ設定したんだっけ」と分からなくなった経験がある方に向けた内容です。手動構築からTerraform(インフラをコードで定義・管理するIaCツール)へ移行する際に起きがちな衝突エラーと、その根本原因、CI/CD導入前に押さえるべき順番を整理します。

クラウド移行の現場では、最初はAWSマネジメントコンソールで試行錯誤しながらリソースを作り、後からコード化するという順番になることがよくあります。この「後追いでコード化する」パターンには特有の落とし穴があり、事前に知っておくと回避しやすくなります。

何が起きるのか、なぜ注目すべきか

手動で作成済みのS3バケットやDynamoDBテーブル、Lambda関数をTerraformの管理下に置こうとすると、terraform apply実行時に409エラー(リソースが既に存在するという競合エラー)が発生します。

これはTerraformが「まだ存在しないリソース」だと誤認識し、新規作成しようとしてAWS側から拒否されるために起きます。GitHub Actions(GitHubが提供するCI/CDの自動実行基盤)でこのパイプラインを動かすと、ローカル環境では気づかなかった同じ問題が本番相当の自動化フローで再現します。

この問題は個人のポートフォリオ構築だけでなく、既存システムをオンプレミスからクラウドへ移行する現場でも同じ構造で起こります。すでに稼働しているリソースをコード管理下に移す作業は、多くの移行プロジェクトで避けて通れない工程です。

技術的な仕組みを段階的に見る

まず理解しておきたいのは、Terraformが「現実のインフラ」を直接見ているわけではないという点です。

Terraformはstateファイル(自分が管理しているリソースの一覧を記録した状態ファイル)を頼りに動作します。手動で作られたリソースはこのstateファイルに記録がないため、Terraformから見ると「存在しないもの」として扱われます。

次に問題になるのがstateファイルの置き場所です。デフォルトではstateファイルはコマンドを実行したマシンのローカルディスクに保存されます。

ここで大きな落とし穴があります。ローカルでterraform applyを実行して手元のstateにリソース情報を記録しても、GitHub Actionsのランナー(CI/CDがコマンドを実行する使い捨ての実行環境)にはそのstateが存在しません。

GitHub ActionsはCI/CD実行のたびにまっさらな環境からスタートするため、ローカルで積み上げたstateの記録を引き継げないのです。結果としてCI側は「何もない状態」からapplyを始め、既存リソースとの衝突が発生します。

これを解決するのがリモートバックエンド(stateファイルをS3など共有ストレージに保存する仕組み)です。設定はproviders.tfに以下のようなブロックを書きます。

terraform {
  backend "s3" {
    bucket = "your-terraform-state-bucket"
    key    = "project/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

設定後はterraform init -migrate-stateを実行し、ローカルのstateをS3へ移行します。これでローカルとCI/CDが同じstateを参照できるようになります。

手動で作成済みのリソースについては、terraform importコマンドでstateに取り込みます。

terraform import aws_s3_bucket.resume my-existing-bucket-name
terraform import aws_lambda_function.counter my-existing-function-name
terraform import aws_dynamodb_table.counter my-existing-table-name

このコマンドは「既存のAWSリソースをTerraformの管理台帳に登録する」作業だけを行い、リソース自体は一切変更しません。importが終わってからterraform planを実行し、差分がゼロに近いことを確認するのが安全な進め方です。

背景・関連技術との比較

AWSにはIaCツールとしてAWS SAM(Serverless Application Model、サーバーレス構成に特化したCloudFormationの拡張)も用意されています。SAMはLambdaやAPI Gatewayなどサーバーレス構成との親和性が高く、AWS単体構成なら学習コストは低めです。

一方でTerraformはクラウドベンダーに依存しないマルチクラウド対応が特徴です。AWSだけでなくAzureやGCPも同じHCL(HashiCorpが設計した設定記述言語)で記述できるため、複数クラウドを扱う組織や、将来の移行可能性を残しておきたい現場で採用されやすい傾向があります。

日本の現場でIaCというとAWS CloudFormationやAzureのARMテンプレート、あるいはPulumi(汎用プログラミング言語でインフラを書けるツール)が比較対象になります。Terraformはこれらの中で最もエコシステムが大きく、モジュール(再利用可能なインフラ部品)やドキュメントが充実している点が実務上のメリットです。

state管理という観点では、Terraform Cloud/Enterpriseやtfstateのロック機能(同時実行による競合を防ぐ仕組み)を使う選択肢もあります。S3バックエンドにDynamoDBのstate lockを組み合わせると、複数人・複数パイプラインが同時にapplyを走らせた際の破損リスクを下げられます。

読者への影響と、今日確認できること

自分のプロジェクトがこの問題に該当するかは、次の観点で確認できます。

  • terraform state listを実行し、想定しているリソースが本当にstateへ登録されているか
  • providers.tfまたはbackend.tfにS3など共有ストレージのbackend設定があるか、それともデフォルトのローカルstateのままか
  • GitHub Actionsのワークフローファイル(.github/workflows/*.yml)でterraform initが毎回まっさらな環境から実行される構成になっていないか
  • 手動作成したリソースがまだ残っていないか、AWSコンソール側とTerraformコード側を突き合わせて棚卸しする

CI/CDを組む前にリモートバックエンドを先に整備しておくと、409エラーの多くは未然に防げます。逆の順番、つまりCI/CDを先に組んでからバックエンドを後付けする進め方は、パイプライン実行のたびに衝突エラーへ悩まされる原因になりやすいので避けたいところです。

障害対応の観点では、terraform planの出力を必ずCI上でログとして残しておくことも有効です。差分の記録があれば、後から「いつ・どのリソースが・なぜ変更されたか」を追跡でき、根本原因分析がしやすくなります。

リモートバックエンドの整備は、CI/CD自動化の前提条件であって後付けのオプションではありません。

まとめ

手動構築からIaCへ移行する際は、次の順序を意識すると安全です。

  • stateファイルをローカルからS3などのリモートバックエンドへ先に移行する
  • 既存リソースはterraform importでstateに取り込んでからapplyを回す
  • CI/CDパイプラインの導入はリモートバックエンド整備の後に行う
  • terraform planの差分ログをCI上に残し、障害調査の手がかりにする

まずは自分のプロジェクトのbackend設定とterraform state listの中身を見比べるところから始めてみてください。

参考

Cloud Resume Challenge Week 3— Infrastructure as Code with Terraform and CI/CD with GitHub Actions

この記事について: 本記事は AI を活用して作成し、forva AI 編集部が内容を確認・監修しています。

AI 駆動開発のご相談は forva AI へ。まずはお気軽にどうぞ。