午前2時の本番環境の停滞
ステージング環境を廃止しようとしており、すぐに終わるだろうと考えています。しかし、Terraformは180秒間ハングした後に DependencyViolation エラーでクラッシュします。ターミナルの出力は最悪の事態を告げています。何かがまだ使用しているため、Security Group または Subnet が削除できず停止しています。
Error: Error deleting Security Group: DependencyViolation: The security group 'sg-0a1b2c3d4e5f6g7h8' has dependencies and cannot be deleted.
status code: 400, request id: 12345678-90ab-cdef-1234-567890abcdef
この問題は、Terraformの状態ファイル(state file)とクラウドコンソールの実態との間の乖離に起因します。Terraformは自身がリソースのライフサイクルを管理していると考えています。しかし、手動でアタッチされたNetwork Interface (ENI)、残存しているLambda関数、あるいはLoad Balancerといった外部エンティティが、まだそのリソースに固執しています。クラウドのAPIは、ネットワークの切断を防ぐために削除リクエストを拒否します。
ステップ1:ゴースト依存関係の特定
Terraformは何を削除できないかは教えてくれますが、その「理由」については驚くほど口を閉ざします。AWSのSecurity Groupが削除できない場合、ほとんどの原因は隠れたNetwork Interface (ENI)にあります。これは、すぐにはスケールダウンしないElastic Load Balancer (ELB)やEKSノードで頻繁に発生します。
リソースを「人質」に取っている具体的なアタッチメントを見つけるために、AWS CLIを使用します:
# 削除できないSecurity GroupにリンクされているENIを検索
aws ec2 describe-network-interfaces \
--filters Name=group-id,Values=sg-0a1b2c3d4e5f6g7h8 \
--query 'NetworkInterfaces[*].{ID:NetworkInterfaceId,Description:Description,Status:Status}'
このコマンドがアクティブなIDを返した場合、ゴーストが見つかったことになります。AWS LambdaのENIは、この問題の常連です。関数が削除された後でも、VPCが回収するまでネットワークインターフェースが数分間残ることがあります。Terraformは、これらの手動またはシステム管理のアタッチメントが .tfstate ファイルに含まれていないため、検知することができません。
ステップ2:実践的な解決策
方法A:ライフサイクルによる修正(予防的)
apply の際、Terraformはしばしば新しいリソースを作成する前に古いリソースを削除しようとします。他のリソースがまだ古いIDを参照している場合、このプロセスは失敗します。lifecycle ブロックを使用して、このロジックを逆にすることができます。
resource "aws_security_group" "web_server" {
name = "web-sg-v2"
lifecycle {
create_before_destroy = true
}
}
このブロックは、Terraformにまず新しいSecurity Groupをプロビジョニングさせます。その後、依存するすべてのリソースを更新して新しいIDを指すようにします。古いリソースが完全に「孤立」した後にのみ、Terraformは削除を試みます。これは、ネットワーキングコードにおける依存関係のループを防ぐ最も効果的な方法です。
方法B:手動による介入(対症療法)
時には、手動で道を切り開く必要があります。これは、Terraformが追跡していないLoad BalancerをKubernetesクラスター (EKS) が作成した場合によく見られます。
- 特定とデタッチ: ステップ1で得たIDを使用して、AWSコンソールでリソースを見つけます。手動でENIまたはLoad Balancerを削除します。
- 状態の同期: 障害物がなくなったら、
terraform plan -refresh-onlyを実行してローカルの状態を更新します。 - 作業の完了: 再度
terraform destroyを実行します。今度は即座に完了するはずです。
方法C:状態から強制的に削除する
クラウドコンソール経由ですでにリソースを削除したにもかかわらず、Terraformが削除を試み続ける場合は、状態ファイルに「開頭手術」を行う必要があります。state rm コマンドを使用します。
terraform state rm aws_security_group.web_server
注意: これは追跡記録を削除するだけで、実際のインフラストラクチャを削除するわけではありません。物理的なリソースがすでに存在しない場合、または手動で管理するつもりの場合にのみ使用してください。
ステップ3:検証
planを実行して修正を確認します。create_before_destroy を実装した場合、出力は以下のようになるはずです:
# aws_security_group.web_server must be replaced
# +/- create replacement and then destroy
成功すると、Destroy complete! Resources: 1 destroyed. という満足のいくメッセージで終了します。
今後のための教訓
- 「ClickOps」を避ける: コンソールでSecurity GroupをEC2インスタンスに手動でアタッチすることは、次回のTerraform実行を確実に失敗させる方法です。
- Lambdaの遅延を考慮する: VPC対応のLambdaは、まれにENIを完全に解放するまで最大20分かかることがあります。これが頻繁に発生する場合は、CI/CDパイプラインにリトライロジックを組み込んでください。
- ライフサイクルルールを標準化する: SubnetやVPC Peering接続などの基礎的なコンポーネントには、デフォルトで
create_before_destroyを適用します。 - サイレントキラーに注意する: NAT GatewayやVPC Endpointは、親VPCの削除を妨げる長いクリーンアップ時間を要することがよくあります。

