既存の野良リソースをIaC管理下に完全移行する秘訣:`ImportExistingResources` の深淵と実践
数年間にわたる本番運用の歴史、深夜の障害対応、コンソールを直接ポチって構築された「誰が作ったか分からない(だが止めてはいけない)野良リソース」。
クラウドインフラストラクチャを運用するすべてのSREが直面するこの呪縛から逃れる唯一の手段が、AWS CloudFormationの Resource Import(`ImportExistingResources`) だ。
だが、甘いマニュアル通りに手を動かしたエンジニアの多くが、スタック作成の失敗、リソースの意図しない置換(Re-creation)、そして容赦ないダウンタイムの洗礼を受けてきたはずだ。
本稿では、CloudFormationのスタックインポート機能の内部アーキテクチャを紐解き、ダウンタイムゼロ、かつ一切のリソース破壊を引き起こさずに「野良リソース」を完全にIaCの支配下に置くための極限の知見を授ける。
—
1. 内部アーキテクチャの理解:インポートとは何が起きているのか
多くのエンジニアは、「インポート」を既存リソースとCloudFormationテンプレートの「紐付け」程度に考えている。大間違いだ。
CloudFormationのバックエンドにおいて、インポート操作は以下のフェーズで厳密に実行される。
1. リソース状態の静的アサーション: テンプレートで定義されたプロパティ値と、AWS Control Planeが返す実際のAPIレスポンスの突合。
2. スタックメタデータの強制注入: リソースのARNに対してCloudFormation管理用の内部タグおよびメタデータ(Stack ID、Logical ID)をアトミックに付与。
3. ドライ・ランとロック: インポート処理中、対象リソースのライフサイクルは一時的にCloudFormationの管理下に置かれ、外部からの意図しない変更から保護される。
ここで重要なのは、「テンプレート側の記述が現実のインポート対象リソースの構成と1バイトたりとも違ってはならない」という鉄則である。少しでも差異があれば、CloudFormationは「差分を埋めるためにリソースの置換(削除して作り直し)」を試み、本番環境を灰燼に帰す。
—
2. 実戦ステップバイステップ:安全領域でのインポート手順
「野良リソース」を安全にインポートするための、エリートSREのためのステップバイステップを示す。今回は例として、野良で稼働している `AWS::EC2::Volume`(EBSボリューム)を対象とする。
ステップ 1: 正確なリソース識別子の特定
コンソールやAWS CLIを叩き、対象リソースの一意の識別子(Physical Resource ID)を特定する。EBSであれば `vol-0123456789abcdef0` だ。
ステップ 2: 完璧なテンプレートのスクラッチ(あるいは逆アセンブル)
既存リソースの現在の設定を `aws ec2 describe-volumes` 等で完全に出力し、CloudFormationの構文へ落とし込む。この時、プロパティを1つでも省略すると、CloudFormationは「デフォルト値への変更」とみなして更新を走らせる危険がある。
ステップ 3: リソースインポートファイル(Mapping File)の作成
インポート対象がどの論理ID(Logical ID)と物理ID(Physical ID)に紐づくかを定義するJSON/YAMLファイルを用意する。
[
{
“ResourceType”: “AWS::EC2::Volume”,
“LogicalResourceId”: “StrayProductionVolume”,
“ResourceIdentifier”: {
“VolumeId”: “vol-0123456789abcdef0”
}
}
]
ステップ 4: 差分検出(ChangeSet)の極限検証
通常のスタック作成ではなく、`–change-set-type IMPORT` を指定してチェンジセットを作成する。
aws cloudformation create-change-set \
–stack-name production-storage-stack \
–template-body file://template.yaml \
–resources-to-import file://resources-to-import.json \
–change-set-type IMPORT \
–change-set-name ImportStrayVolumeChangeSet
ここでコンソールまたはCLIでチェンジセットの中身を隅々まで確認せよ。「Add(追加)」ではなく「Modify(変更)」や「Replace(置換)」のシグナルが出ていたら、即座に中止しろ。プロパティの型や値の不一致が存在する証拠だ。
—
3. 罠の回避策:論理IDの割り当てミスと既存タグの整合性
現場で最も頻発する障害のパターンを二つ挙げる。
A. 論理IDの割り当てミスとドリフト
一度インポートに成功した後に論理IDを変更してはならない。論理IDを変えることは、CloudFormationにとっては「旧リソースの削除」と「新リソースの作成」を意味する。
対策: チーム内で命名規則を厳格化し、インポート時のLogical IDは二度と変更しないこと。
B. 既存タグの不整合による「置換地獄」
野良リソースによくあるのが、コンソールで適当につけられたタグ(例: `Name: old-server`)と、テンプレート側の `Tags` プロパティの微妙な不一致だ。
CloudFormationは厳密にタグのキー・バリューを比較する。順序が違うだけでも、プロバイダーによっては更新(または置換)が走る。
極意: インポート時のテンプレートには、現在AWS上に存在するタグと完全に同一のタグを記述してインポートを完了させよ。タグの整理や標準化(Tag Policyの適用)は、インポートが完了し、スタックが安定(UPDATE_COMPLETE)した後の通常アップデートで行うのが鉄則である。
—
4. 自動化の極み:大規模移行のための独自CLIスクリプト
数千個の野良リソースを手動でインポートするなどエンジニアの恥だ。ここでは、Python (`boto3`) を用いて、特定タグが付与された「野良RDSインスタンス」を検出し、動的にインポート用テンプレートとマッピングファイルを生成してAPI経由で移行を自動化するスクリプトの断片を示す。
import json
import boto3
def generate_import_payload(tag_key, tag_value):
rds_client = boto3.client(‘rds’)
# 野良リソースの探索
paginator = rds_client.get_paginator(‘describe_db_instances’)
resources_to_import = []
template_resources = {}
for page in paginator.paginate():
for db in page[‘DBInstances’]:
# タグによるフィルタリング
tags = rds_client.list_tags_for_resource(ResourceName=db[‘DBInstanceArn’])[‘TagList’]
if any(t[‘Key’] == tag_key and t[‘Value’] == tag_value for t in tags):
db_id = db[‘DBInstanceIdentifier’]
logical_id = f”ImportedDB{db_id.replace(‘-‘, ”)}”
# マッピングファイルの要素を追加
resources_to_import.append({
“ResourceType”: “AWS::RDS::DBInstance”,
“LogicalResourceId”: logical_id,
“ResourceIdentifier”: {
“DBInstanceIdentifier”: db_id
}
})
# 最小限のテンプレート断片を生成(実際にはより詳細なプロパティが必要)
template_resources[logical_id] = {
“Type”: “AWS::RDS::DBInstance”,
“Properties”: {
“DBInstanceIdentifier”: db_id,
“Engine”: db[‘Engine’],
“DBInstanceClass”: db[‘DBInstanceClass’],
# 既存の環境を破壊しないよう、スナップショットからの復元や既存設定に合わせる
}
}
# ファイル出力
with open(‘resources-to-import.json’, ‘w’) as f:
json.dump(resources_to_import, f, indent=2)
print(f”Generated import payload for {len(resources_to_import)} resources.”)
if __name__ == “__main__”:
generate_import_payload(“Environment”, “StrayOrphan”)
このスクリプトをパイプラインに組み込み、インポート対象を動的にコード化することで、人間の目視によるヒューマンエラーを完全に排除できる。
—
5. エキスパートの哲学:インポート後のポスト・ガバナンス
インポートが成功し、スタックの状態が `IMPORT_COMPLETE` になった瞬間に歓声を上げてはならない。そこからが真のSREの仕事だ。
1. ドリフト検出(Drift Detection)の即時実行:
インポート直後に `aws cloudformation detect-stack-drift –stack-name
2. スタックポリシー(Stack Policy)の鉄壁化:
インポートした野良リソースが再び誰かに手動で改変されないよう、Update/Deleteを拒絶するスタックポリシーを直ちにアタッチする。
{
“Statement” : [
{
“Effect” : “Deny”,
“Action” : [“Update:Modify”, “Update:Replace”, “Update:Delete”],
“Principal” : “”,
“Resource” : “”
}
]
}
野良リソースのインポートとは、混沌とした過去の遺産を、宣言的で美しいIaCの秩序へと服従させる聖戦である。
仕様の裏側を熟知し、リスクをコントロールした者だけが、真のインフラストラクチャの支配者となることができる。さあ、コードを開き、野良リソースどもを統制下に入れろ。