AWS本番環境をTerraform化してみた:第二回 リソースのインポート-ネットワーク、IAM編
連載について
- 第一回: インポート戦略編
- 第二回: リソースのインポート-ネットワーク、IAM編<このページ>
- 第三回: Terraform環境別設定管理術
- 第四回: リソースのインポート-ECS、タスク定義管理編※執筆中
- 第五回: リソースのインポート-RDS、S3、CloudFront編※執筆中
- 第六回: リソースのインポート-Route53、SES、SNS編※執筆中
- 第七回: 総括※執筆中
第一回: インポート戦略編では移行戦略と計画について解説しました。
今回は実際のインポート作業、特にPhase 1(VPCとネットワーク)とPhase 2(セキュリティグループとIAM)で実践した内容を解説します。
背景と課題
前回記事で説明したとおり、本番環境はGUIで構築されており、命名規則も統一されていない状態でした。これをTerraformでIaC化するため、terraform importコマンドを使って既存リソースを取り込んでいきます。
対象読者
- terraform importを実際に使用する予定のエンジニア
- 既存AWSリソースをTerraform管理下に置きたい方
- インポートスクリプトの実装例を探している方
この記事からわかること
- 実際のインポートスクリプトの実装方法
- リソースIDの調査方法
- インポート時のエラーハンドリング
- Phase 1とPhase 2の具体的なインポート手順
この記事では説明しないこと
- Terraformのstate管理の詳細
- Phase 3以降のインポート内容(次回以降で解説)
方針検討
CloudFormationのスキャン結果から、本番環境のネットワーク、セキュリティに関する主なリソースは以下があることがわかっています。
Phase 1(ネットワーク)
- VPC: 1個
- Internet Gateway: 1個
- Route Table: 4個
- Subnet: 7個
- Route Table Association: 7個
Phase 2(セキュリティ)
- Security Group: 11個
- IAM Role: 2個
合計33個のリソースを手動でインポートするのは面倒ですミスのもとです。そこでインポート作業をスクリプト化します。
インポートスクリプトによるterraform import
リソースIDの調査
インポートスクリプトを作成する前に、まず既存リソースのIDを調査する必要があります。CloudFormationでスキャンした結果も参考にしつつ、AWS CLIで最新の情報を取得します。
--output tableはお好みで
# VPCのID一覧
aws ec2 describe-vpcs --query 'Vpcs[*].[VpcId,Tags[?Key==`Name`].Value|[0]]' --output table
# サブネット一覧(VPC IDでフィルタ)
aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-0123456789abcdef0" \
--query 'Subnets[*].[SubnetId,Tags[?Key==`Name`].Value|[0],AvailabilityZone]' --output table
# セキュリティグループ一覧
aws ec2 describe-security-groups --filters "Name=vpc-id,Values=vpc-0123456789abcdef0" \
--query 'SecurityGroups[*].[GroupId,GroupName,Description]' --output table
# Route Table Association確認
aws ec2 describe-route-tables --query 'RouteTables[*].Associations[*].[SubnetId,RouteTableId]' --output table
インポート順序
依存関係を考慮して順番を決めます。依存関係の根幹となるVPCから始めることで、後続のリソースがスムーズにインポートできます。
- VPC
- Internet Gateway(VPCに依存)
- Route Table(VPCに依存)
- Subnet(VPCに依存)
- Route Table Association(SubnetとRoute Tableに依存)
Phase 1: VPCとネットワークのインポート
まずはネットワーク基盤からインポートします。なおインポート作業のログが流れてしまうので、適当にログファイルに出力しておきます。
./scripts/import-phase1.sh 2>&1 | tee import-phase1.log
import-phase1.shのイメージ:
#!/bin/bash
# Phase 1: Import VPC and Networking Resources
set -euo pipefail
# Change to terraform directory
cd "$(dirname "$0")/.."
# Function to import resource
import_resource() {
local resource_type=$1
local resource_name=$2
local resource_id=$3
echo "Importing ${resource_type} ${resource_name}..."
if terraform import "${resource_type}.${resource_name}" "${resource_id}"; then
echo "✓ Successfully imported ${resource_name}"
else
echo "✗ Failed to import ${resource_name} - continuing..."
fi
}
# Import VPC
import_resource "aws_vpc" "main" "vpc-0123456789abcdef0"
# Import Internet Gateway
import_resource "aws_internet_gateway" "main" "igw-0123456789abcdef1"
# Import Route Tables
import_resource "aws_route_table" "public" "rtb-0123456789abcdef2"
import_resource "aws_route_table" "private_1a" "rtb-0123456789abcdef3"
import_resource "aws_route_table" "rds" "rtb-0123456789abcdef5"
# Import Subnets
import_resource "aws_subnet" "public_1a" "subnet-0123456789abcdef6"
import_resource "aws_subnet" "private_1a" "subnet-0123456789abcdef8"
import_resource "aws_subnet" "rds_1a" "subnet-0123456789abcdefa"
...
# Import Route Table Associations
# 注意: フォーマットは subnet-id/route-table-id
import_resource "aws_route_table_association" "public_1a" "subnet-0123456789abcdef6/rtb-0123456789abcdef2"
...
Route Table Associationの特殊性
通常のリソースと異なり、Route Table Associationは指定の仕方が異なります。
# 通常のリソース
terraform import aws_vpc.main vpc-xxxxx
# Route Table Association
terraform import aws_route_table_association.name subnet-id/route-table-id
この形式を知らないと、以下のようなエラーでに遭遇します
Error: Invalid address to import
Route table association import requires the format: subnet-id/route-table-id
Phase 2: セキュリティグループとIAMのインポート
続けてセキュリティグループとIAMです。
import-phase2.shのイメージ:
#!/bin/bash
# Phase 2: Import Security Groups and IAM Resources
set -euo pipefail
# Phase 1と同じimport_resource関数を使用
# Import Security Groups
import_resource "aws_security_group" "alb" "sg-0123456789abcdefd"
import_resource "aws_security_group" "ecs" "sg-0123456789abcdefe"
...
# Import IAM Roles
import_resource "aws_iam_role" "ecs_task_execution" "ecsTaskExecutionRole"
import_resource "aws_iam_role" "rds_monitoring" "rds-monitoring-role"
...
エラーハンドリング
importするだけなので本番環境を変更してしまうことはありませんが、念の為スクリプトには以下の対策をしています。
-
set -euo pipefail
- 未定義変数の使用を防ぐ
- パイプライン内のエラーでも停止 ※pipefailをつけないとパイプでコマンドを繋げた場合、最後のコマンドにしか効かない
- エラーでスクリプト全体を停止
-
個別インポートのエラー処理
- インポート済みリソースの再インポートは失敗するが続行
- 視覚的なフィードバック(✓/✗)で進捗を把握
Terraform planでのインポート確認
各フェーズ完了後、terraform planで差分を確認します。
terraform plan
# No changes. Your infrastructure matches the configuration. と表示されればOK!
まとめと次回予告
実際にインポート作業を行ってみると、まあTerraformはよくできた仕組みだなと感じます。リソース名とリソースIDの指定はドキュメントを読む必要がありますが、importコマンド自体で苦労することはほぼなく、生成される.tfファイルも個人的にはCloudFormationテンプレートより人間の可読性が高いと感じます。
今回のPhase 1-2で基盤となるネットワークとセキュリティの設定をTerraform管理下に置くことができました。これで後続のフェーズの準備が整いました。
次回はPhase 3以降の、より複雑なリソース(RDS、S3など)のインポートについて解説します。