第1部はこちら → https://qiita.com/REALKTMR/items/325e5c5726c0989ade1c
第2部はこちら → https://qiita.com/REALKTMR/items/0bafb70313b6d3dff3a9
はじめに
第1部では、KiroのバイブコーディングモードでAWS三層アーキテクチャのCloudFormationテンプレートを生成し、Well-Architected Framework 30項目で採点しました(最大26/30)。
第2部では、Kiroのspecモードと詳細な仕様書を組み合わせることで、30/30満点のテンプレートを生成できることを検証しました。
第3部のテーマは「生成したIaCを、壊さずに変更し続ける仕組みを作る」 です。
Kiroでテンプレートを生成できても、その後の変更管理が甘ければ品質は劣化します。インフラは「作って終わり」ではなく、「育て続ける」ものです。
今回は以下の2つを組み合わせて、IaC変更の二段構えの安全ゲートを構築します。
- Kiro Agent Hooks:ローカルで編集するたびに cfn-lint を自動実行し、クリーンなら commit/push まで完結
- AWS CodePipeline:Change Set による人間レビューを経てからデプロイ
アーキテクチャ概要
パイプライン全体の流れは以下の通りです。
【ローカル:Kiro IDE】
template.yaml を編集
↓ 自動(fileEdited Hook 発火)
Kiro Agent Hook(cfn-lint)
↓ エラーあり → 修正提案。人間は「承認」するだけ
↓ クリーン → 「コミットしてpushしますか?」
↓ 人間が「はい。」(承認①)
git add → git commit(メッセージ自動生成)→ git push
【クラウド:AWS】
CodePipeline 自動起動(GitHub App Webhook)
↓
Stage 1 Source:GitHub からソース取得
↓
Stage 2 Validate:CodeBuild で cfn-lint
↓
Stage 3 CreateChangeSet:差分プレビュー生成
↓
Stage 4 Approval:人間が Change Set を確認して承認(承認②)
↓
Stage 5 ExecuteChangeSet:スタック更新
人間がやることは2回の承認だけです。 コマンドを一切打たずに、IaC変更を安全に本番へ届けられます。
ローカルとクラウドで同じ cfn-lint を二重に実行するのは冗長に見えますが、それぞれ役割が違います。
| レイヤー | ツール | タイミング | 目的 |
|---|---|---|---|
| ローカル | Kiro Agent Hook | 編集のたびに即座 | 早期フィードバック(タイプミス・構文エラー) |
| クラウド | CodeBuild(Validate) | push のたびに | チーム共通のゲート |
| クラウド | Change Set + Approval | デプロイ前に | 人間による破壊的変更の検知 |
Kiro Agent Hooks の実装
フックファイルの形式
Kiro Agent Hooks は .kiro/hooks/ ディレクトリに *.kiro.hook という形式で定義します。hooks.json のような単一ファイルではなく、フックごとに独立したファイルを置く設計です。
then.type は2種類あります。
| タイプ | 説明 |
|---|---|
runCommand |
シェルコマンドを直接実行 |
askAgent |
エージェント(LLM)にプロンプトを送信 |
実装したフック
今回の最終的なフック定義です。
{
"enabled": true,
"name": "cfn-lint on save",
"description": "template.yaml 保存時に cfn-lint を自動実行してエラーを即座にフィードバック",
"version": "1",
"when": {
"type": "fileEdited",
"patterns": ["**/template.yaml"]
},
"then": {
"type": "askAgent",
"prompt": "pattern-3/template.yaml に対して cfn-lint を実行してください。\n\nエラー・警告がある場合:内容・行番号・修正方法を日本語で報告してください。\n\nエラー・警告がない場合:「テンプレートに問題はありません。変更をコミットしてpushしますか?」と聞いてください。ユーザーがOKと答えたら git add と git commit(変更内容から適切なメッセージを自動生成)と git push を実行してください。"
}
}
fileEdited パターンにより、template.yaml を編集するたびにフックが発火します。エラーがあれば修正提案まで行い、クリーンであればそのまま commit/push の確認へ進む——この一連の流れを1つのフックで完結させています。
動作イメージ
エラー検出時(Kiro チャットパネルの表示)
cfn-lint で 1 件のエラーが検出されました。
・E2015(行 47):EC2InstanceType パラメータの Default 値 t4g.micr が
AllowedValues に含まれていません。t4g.micro のタイポミスです。
修正方法:
Default: t4g.micro ← "t4g.micr" → "t4g.micro"
直しますか?
クリーン時(Kiro チャットパネルの表示)
テンプレートに問題はありません。変更をコミットしてpushしますか?
→「はい。」と返すと Kiro が git add → git commit → git push を自動実行します。
Steering ドキュメントでエージェントに情報を伝える
.kiro/steering/setup.md にプロジェクト固有の情報を書いておくと、エージェントは毎回それを読んでから作業します。cfn-lint のインストール状況や検証対象ファイルなどを記載しておくことで、エージェントが余計な試行をせずに動作します。
# プロジェクトセットアップ
## cfn-lint
cfn-lint はグローバルにインストール済みです。`cfn-lint` コマンドをそのまま使用してください。
.kiro/steering/ ディレクトリに置いたMarkdownファイルは、エージェントが毎回自動で読み込みます。「Including Steering Documents: setup.md」という表示がエージェントの応答に出ていれば機能しています。
CodePipeline の構築
一括構築アプローチ
パイプライン自体も CloudFormation(pattern-3/pipeline.yaml)で管理します。S3・IAMロール・SNS・CodeBuild・CodePipeline をまとめて一枚のテンプレートで定義し、aws cloudformation deploy 一発で環境が揃います。
主要リソースの構成:
| リソース | 用途 |
|---|---|
| S3 バケット | アーティファクト一時保管 |
| IAM ロール(CodePipeline用) | ソース・ステージ間の権限 |
| IAM ロール(CodeBuild用) | cfn-lint 実行権限 |
| IAM ロール(CFnデプロイ用) | Change Set 作成・実行権限 |
| SNS トピック | Approval 通知メール送信 |
| CodeBuild プロジェクト | cfn-lint 実行 |
| CodePipeline | 全ステージのオーケストレーション |
buildspec.yml
CodeBuild の実行内容は pattern-3/buildspec.yml に定義しています。
version: 0.2
env:
variables:
TEMPLATE_PATH: "pattern-3/template.yaml"
phases:
install:
runtime-versions:
python: 3.11
commands:
- pip install cfn-lint --quiet
build:
commands:
- echo "=== cfn-lint validation ==="
- cfn-lint ${TEMPLATE_PATH}
- echo "cfn-lint passed"
artifacts:
files:
- pattern-3/template.yaml
discard-paths: no
Triggers 設定と Stages 構成
pipeline.yaml の CodePipeline 定義の核心部分です。
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
PipelineType: V2
Triggers:
- ProviderType: CodeStarSourceConnection
GitConfiguration:
SourceActionName: GitHub
Push:
- Branches:
Includes:
- main
Stages:
- Name: Source # GitHub からソース取得
- Name: Validate # CodeBuild で cfn-lint
- Name: CreateChangeSet
Configuration:
ActionMode: CHANGE_SET_REPLACE
ChangeSetName: pipeline-changeset
- Name: Approval
Configuration:
NotificationArn: !Ref ApprovalTopic
- Name: ExecuteChangeSet
Configuration:
ActionMode: CHANGE_SET_EXECUTE
CreateChangeSet(CHANGE_SET_REPLACE)→ Approval → ExecuteChangeSet(CHANGE_SET_EXECUTE)という3ステージの分離が、人間によるレビューを可能にしている構造のポイントです。
実際に動かしてわかったこと(ハマりポイント)
① CFnコンソールでパラメータ説明が文字化け(未解決)
現象:template.yaml の Description や Metadata: AWS::CloudFormation::Interface の Labels に書いた日本語が、CloudFormation コンソールで ???? と表示される
CloudFormation のテンプレートは UTF-8 で記述できるはずであり、実際にファイルのバイト列も正しい UTF-8(BOMなし・LF改行)であることを確認しました。しかし、コンソール上では日本語が ? に置換されて表示されます。
試したこと:
- CRLF → LF への変換(
fileコマンドで UTF-8 LF を確認) -
.gitattributesに*.yaml text eol=lfを追加 - CodePipeline を介さずコンソールから直接テンプレートをアップロード
いずれも改善せず。CloudFormation コンソールの GetTemplateSummary API が日本語テキストを正しく処理できていない可能性がありますが、原因は特定できていません。
機能自体(スタックの作成・更新)には影響がないため、そのまま進めています。情報をお持ちの方はコメントいただけると助かります。
② 第2部で満点を取ったテンプレートも cfn-lint の警告に引っかかった
背景:第3部で使用した template.yaml は、第2部の Spec モードで生成した pattern-2b をベースにしています。第2部では Well-Architected Framework 30項目のチェックで 30/30 満点 を獲得したテンプレートです。
「完璧なテンプレートだから cfn-lint もクリアできるはず」と思っていましたが、パイプラインの Validate ステージで FAILED になりました。
現象:cfn-lint が exit status 4 を返してパイプラインが FAILED になる
cfn-lint の exit status は以下の意味を持ちます。
| exit status | 意味 |
|---|---|
| 0 | 問題なし |
| 2 | エラーあり(E prefix) |
| 4 | 警告のみ(W prefix) |
| 6 | エラー+警告の両方 |
今回発生した警告:
| 警告コード | 内容 |
|---|---|
W3011 |
UpdateReplacePolicy と DeletionPolicy は両方設定することが推奨される |
W1020 |
変数を使っていない Fn::Sub は不要 |
対処:警告を無視するオプション(--ignore-checks)を使わずに、テンプレート自体を修正しました。
Kiro が生成した高品質なテンプレートでも、cfn-lint の厳密なチェックをすべてパスするわけではありません。「WA準拠スコア」と「lintクリーン」は別軸の評価です。パイプラインにlintを組み込むことで、こういった見落としを機械的に拾えます。
③ Change Set に 41 リソースの置き換えが出た
現象:Change Set を確認すると、41 リソースが Replace: True と表示された
経緯:
- テンプレートに
EngineVersion: '8.0'と書いていたが、CloudFormation は実際に8.0.44で RDS をデプロイしていた - cfn-lint が
'8.0'を E3691(有効な値ではない)としてエラー →'8.0.44'に修正 - 同時に W3011 の推奨に従い、全リソースに
UpdateReplacePolicy: Retainを追加 - 「EngineVersion の値変更」+「UpdateReplacePolicy の後付け追加」が重なり、RDS インスタンスが置き換え対象に → 依存する 40 リソースが連鎖して Replace=True に
注意:W3011 の推奨(UpdateReplacePolicy と DeletionPolicy を両方設定する)自体は現在も正しいベストプラクティスです。問題は推奨に従ったことではなく、デプロイ済みのスタックに対して他の変更と同時に後付けしたことです。最初のテンプレート作成時から設定しておけば、この問題は起きませんでした。
Change Set の「リソースの変更」を開くと、RDS インスタンスが Replace: True・ReplaceAndSnapshot になっていました。
もし承認して実行していたら:
- RDS インスタンスが削除・再作成される
-
ReplaceAndSnapshotのため削除前にスナップショットは取られる - しかしアプリケーションのデータベース接続は切断され、ダウンタイムが発生
- 新しい RDS インスタンスのエンドポイントが変わるため、アプリケーションの設定変更も必要
- 本番環境であれば、サービス停止+データ移行作業が発生していた
対処:Approval ステージで「拒否」 しました。これはパイプラインが正しく機能した証拠です。破壊的な変更が本番に適用される前に人間がレビューして止められました。
Change Set があることの価値がここで実証されました。 CloudFormation をコンソールや CLI から直接 apply していたら、確認画面もなくそのまま実行されていたはずです。「ちょっとした修正のつもりが、RDS の再作成を引き起こす」——これが IaC 変更管理の怖さであり、Change Set によるレビューが欠かせない理由です。
④ CodePipeline V2 の自動トリガーが動かない
現象:git push してもパイプラインが自動起動しない
原因:Triggers プロパティを設定しても、CodeConnections の接続が GitHub App として認証されていないと webhook が届きません。
対処:
- CodeConnections から既存の接続を削除
- 「接続を作成」から GitHub App として再インストール
- 新しい接続 ARN でパイプラインを更新
CodeConnections V2 の自動トリガーを使う場合は、OAuth ではなく GitHub App でインストールする ことが前提です。
⑤ Kiro Agent Hooks の仕様がドキュメントと違った
[公式ドキュメント](https://kiro.dev/docs/hooks/)では、フックの Event タイプとして「File Save」、Action タイプとして「Run Command」と記載されています。これを参考に JSON を書きましたが、Kiro 0.11 では以下のエラーが出て動作しませんでした。
Hook file has invalid data structure: Hook when.type must be one of:
fileEdited, fileCreated, fileDeleted, userTriggered, ...
実際に動作する JSON 値は以下の通りです。
| 項目 | ドキュメント表記 | JSON で動く値 |
|---|---|---|
when.type |
File Save | fileEdited |
then.type |
Run Command |
runCommand または askAgent
|
| ファイルパス変数 | ${file} |
展開されない(ハードコードか相対パスが必要) |
ドキュメントの表記(GUI 上の名称)と JSON スキーマのキー名が異なります。Kiro のバージョンアップに伴う仕様変更の可能性もあるため、実際に試して IDE のエラーメッセージで正しい値を確認するのが確実です。
また、runCommand を使うとコマンドの出力がチャットパネルに表示されません。エラー詳細を見せたい場合は askAgent を使う必要があります。
フックの実行許可ダイアログは 「Execute hook」タブ に出ます。メインの「New Session」タブには出ないため、気づかずに永遠に待ち続ける可能性があります。タスクリストの「CURRENT TASKS」を確認してください。
デモ:2つのシナリオ
シナリオ A:破壊的変更を事前検知して止める
変更内容:EngineVersion を変更
Change Set の結果:
| 項目 | 内容 |
|---|---|
| 変更リソース数 | 41 件 |
| 主なアクション | Replace |
| Replace | True(多数) |
アクション:Approval ステージで 「拒否」 → パイプラインが FAILED で終了、スタックは変更なし
シナリオ B:安全な変更をそのまま流す
変更内容:ALB ヘルスチェック間隔を変更(1行変更)
Change Set の結果:
| 項目 | 内容 |
|---|---|
| 変更リソース数 | 1 件(ALBTargetGroup) |
| アクション | Modify |
| Replace | False(インプレース更新) |
パイプライン全ステージの結果:
Source ✅ → Validate(cfn-lint) ✅ → CreateChangeSet ✅ → Approval ✅ → ExecuteChangeSet ✅
人間がやったこと:
| # | アクション | 手段 |
|---|---|---|
| 1 | template.yaml を編集 | Kiro IDE |
| 2 | cfn-lint フックの「Run」を承認 | クリック |
| 3 | 「はい。」と返答(commit/push 確認) | Kiro チャット |
| 4 | Change Set を確認して Approve | AWS Console クリック |
コマンドを一切打たずに、変更が本番環境に反映されました。
2つのシナリオを並べると、Change Set の価値が明確になります。
| シナリオ | 変更内容 | Change Set | 対応 | 結果 |
|---|---|---|---|---|
| A | EngineVersion変更 | 41リソース Replace=True | 拒否 | 本番障害を未然に防止 |
| B | ヘルスチェック間隔変更 | 1リソース Replace=False | 承認 | ダウンタイムなし適用 |
まとめ
今回構築した二段構えの仕組みをまとめます。
| レイヤー | ツール | 役割 | タイミング |
|---|---|---|---|
| ローカル | Kiro Agent Hook(cfn-lint) | 最初の砦。編集のたびに即座にフィードバック&commit/push | 開発中 |
| クラウド | CodeBuild(cfn-lint) | チーム共通のゲート | git push |
| クラウド | Change Set + Approval | 破壊的変更の人間レビュー | デプロイ前 |
Kiro Agent Hooks は、IDE を離れずにバリデーション結果が得られます。さらに「クリーンなら commit/push まで流す」という連続したフローを1つのフックに組み込むことで、開発者がコマンドを意識しない体験を実現できます。
CodePipeline の Change Set は、「何が変わるか」を適用前に人間が確認できる仕組みです。今回のシナリオ A では、41リソースの置き換えを Approval ステージで止めました。コンソールや CLI で直接デプロイしていれば、そのまま実行されていたケースです。
Kiroで生成した高品質なテンプレートも、変更管理の仕組みがなければ時間とともに劣化します。「作って終わり」ではなく「継続的に安全に育てる」IaCを実現するには、この二段構えのゲートが有効です。
検証コード(GitHub)
.kiro/hooks/cfn-lint.kiro.hook # Kiro Agent Hook 定義(cfn-lint + commit/push)
.kiro/steering/setup.md # エージェント向けセットアップ情報
pattern-3/template.yaml # 三層アーキテクチャ CFn テンプレート
pattern-3/buildspec.yml # CodeBuild 実行仕様
pattern-3/pipeline.yaml # パイプライン構築用 CFn テンプレート
セットアップ手順(cfn-lint のインストール):
pip install cfn-lint