CloudFormation コーディング規約を策定し、rain fmt + cfn-lint + Claude Code skillsで担保する仕組みを整備してみた
こんにちは。クラウド事業本部コンサルティング部の桑野です。
チームでAWS CloudFormationを使ってAWSリソースのデプロイをする際、成果物となるテンプレートの品質を担保したいと考えたことはありますか?
最近、その実現を手助けするために、テンプレートの記述ルールを定めたコーディング規約を作成することがありました。
また、単に規約を作っただけでなく、それを自然に守れるツール環境も整備しました。
今回は、「どのような規約を作成したか」、それを守るために「どのようなツールを導入したか」の2本立てでご紹介します。
コーディング規約だけでなく、ツールの導入もしようと考えた背景
モチベーションは「コーディング規約を自然と満たせるような開発体験をしてもらいたかった」というものです。
コーディング規約の作成・導入にも良し悪しがあると考えていて、私の感覚としてはきっちりと守るのは結構大変な印象があります。
私の経験上、導入後を見据えなかった結果として、以下のことに陥りがちです。
- 守ることを重視した結果、開発速度が遅くなった
- コーディング規約そのものが形骸化した
ですので、開発者があまり意識することなく、自然とコーディング規約を守れるような環境を提供できるかが重要だと考えています。
そこでFormatterやLinterといったツールが非常に強力な味方として活躍してくれます。
今回は以下のツールを導入しています。
- rain
- cfn-lint
- Claude Code skills
まずはどのようなコーディング規約を作成したかをご紹介していきます。
コーディング規約の概要
AWS CloudFormation ベストプラクティスをベースに、プロジェクト固有のルールを加えた規約を作成しました。
規約の全文はGitHubリポジトリに公開していますので、ここでは要点を紹介します。
規約レベルの定義
各規約には MUST / SHOULD / MAY の3段階でレベルを設けています。
| レベル | 意味 |
|---|---|
| MUST | 必ず従うこと。違反はレビューで却下する |
| SHOULD | 原則従うこと。例外がある場合はレビューで理由を説明する |
| MAY | 推奨。知っておくと良いガイダンス |
この区分があることで、「絶対に守るべきルール」と「状況次第で柔軟に判断してよいルール」が明確になり、レビューの判断基準がブレにくくなります。
※今回の規約ではMAY区分のものはありませんが、用意しておくと今後追加事項が出てきた際に柔軟に対応しやすいかと思います。
テンプレートの基本構造
テンプレートを書くうえでの基本的なルールを定めています。
すべてMUSTのルールです。
使用する言語は英語のみ
マルチバイト文字列はCloudFormationコンソール上で文字化けします。
「作成のためだけにCloudFormationを使い、その後のメンテナンスはマネジメントコンソールで行う」という場合であっても、お客様環境上に文字化けした状態が残るのは具合が悪いです。
事前に例外となるケースが思い浮かばないようであれば、英語のみとした方が良いと考えています。
フォーマットはYAML
JSONとの選択になります。
コメントが書けることもあり、可読性の観点からYAMLを選択しました。
AWSTemplateFormatVersionを必ず記載
現在有効な値は2010-09-09だけなのですが、値を指定しない場合、CloudFormationは最新のテンプレート形式バージョンを使用します。
今後アップデートにより、新しいバージョンが選択できるようになった際に意図せずに動作が変わる可能性があります。
動作の安定性を保証するために必ず明記するようにしています。
Descriptionを必ず記載
テンプレートの目的を示すために設けています。
なるべく簡潔に記載してほしいということがあり、文章にならないようピリオドは使わないこととしています。
セクションの記述順序を統一
記述順序は規約に記載のとおりです。
これは以下のAWS公式ドキュメントにも記載されている内容に合わせています。
ちなみにですが、rainでフォーマットすると自然とその順序に整形されます。
rainの整形順序が明記されているページが見つかりませんでしたが、以下のソースコードから順序がAWS公式ドキュメントと一致することは確認できています。
なお、条件付きでMetadataとOutputsは必須にしています。
Metadataはコンソール上でのParametersの順序を制御するためParametersがある場合に必須、Outputsはクロススタック参照を行う場合に必須としています。
また、この規約を作成したプロジェクトで特に利用シーンが想定されないRules、Transformは原則使用しないという形にしています。
命名規則
MUSTのルールです。
論理名(Logical ID)、パラメータ名、Output名はすべてPascalCaseで統一しています。
プロジェクトの都合上、デプロイするAWSリソースの命名規則は別で行っていたため、今回の規約では含めていません。
パラメータ設計
パラメータはスタック作成時のユーザー入力に直結するため、入力ミスを防ぐルールを手厚く設けています。
MUSTとSHOULDのルールがあります。
AWS固有のパラメータタイプを使用する
MUSTのルールです。
VPC IDやサブネットIDなどを受け取るパラメータにはStringではなく、AWS::EC2::VPC::Idなどの専用のタイプを使うこととしています。
有効な値を制限することにより、スタック作成前にAWS側で値を検証することができ、コンソール上ではドロップダウンにより既存のパラメータから選択することが可能となります。
パラメータ制約を設定する
SHOULDのルールです。
値の範囲が限定できるパラメータは、制約をつけておいた方が入力が楽になります。
制約の設定はできる、できないの両パターンが考えられるため、規約レベルを落としています。
ConstraintDescriptionを必ず記載する
MUSTのルールです。
制約を設定したパラメータには、制約違反時に何を入力すべきかを明示するメッセージがないと利用側で自己解決がしづらいです。
テンプレート作成者とデプロイ実施者が必ずしも同一とは限らないため、必須とするルールが望ましいです。
Default値の設計
SHOULDのルールです。
環境ごとに異なるべき値にはDefault値を設定しないようにします。
例を以下に示します。
- CIDR
環境ごとに異なることがわかっている値についてはDefaultはなしにします。 - 環境名(
dev,stg,prd)
本番から最も遠いdevをDefault値にします。 - EC2のインスタンスタイプ
選択肢の中で一番低コストなタイプをDefault値にします。
Outputs 設計
すべてMUSTのルールです。
Descriptionを必ず記載する
出力値の用途を明確にしたい意図があります。
Outputsはクロススタック参照を行う場合にのみ記述するようなルールとしていますので、他スタックから参照する際に迷わない作りにしたいと考えて設けました。
Export名は${AWS::StackName}-<出力名>の形式で統一する
複数スタック間での名前衝突を防ぐためのルールです。
どのスタックのOutput値であるかが明確でわかりやすくなると考えたため、スタック名をプレフィックスにしました。
セキュリティ
セキュリティリスクのある定義を回避するために設けています。
すべてMUSTのルールです。
認証情報の埋め込み禁止
パスワードやAPIキーなど、機密情報をテンプレートに直接記述するのは流出のリスクを高めます。
このような値はAWS Secrets ManagerやSSM Parameter Storeの動的参照を使用することでセキュアに利用できるように徹底します。
機密パラメータのNoEcho
機密情報をパラメータで受け取る場合はNoEcho: trueを必ず設定するよう徹底しています。
設定した値はログ等に出力されなくなるため、作業者以外に見られてしまうということを避けるようにしています。
テンプレートの記述ルール
テンプレートの可読性と移植性を高めるためのルールです。
MUSTとSHOULDのルールがあります。
疑似パラメータの使用
MUSTのルールです。
アカウントIDやリージョン等はハードコードせず、AWS::AccountIdや AWS::Region等の疑似パラメータを使用します。
テンプレートを複数アカウント、リージョンで展開する際の移植性が向上するため必ず守るようにします。
組み込み関数の短縮記法
MUSTのルールです。
Ref: ではなく !Ref、Fn::Sub: ではなく !Sub のように短縮記法で統一します。
rainによる整形で完全記法は短縮記法に変換されるため、あまり意識しないで良いルールだと考えています。
組み込み関数のネスト回避
SHOULDのルールです。
過度なネストは可読性を下げるため、MappingsやConditionsなどを使って分解して記述します。
この部分は判断が難しいため、レビューでチェックしたい箇所になると考えています。
DeletionPolicy / UpdateReplacePolicy
MUSTのルールです。
データを持つリソース(RDS、DynamoDB、S3等)には必ず設定し、意図せずにデータごとリソースを削除しないように制御します。
文字列値のクォートはrain fmtの出力に従う
MUSTのルールです。
執筆時は迷ったら文字列にはダブルクォーテーションをつけて記述するようにします。
rainの整形で不要なクォートは除去されるため、最終的にはそちらに従うこととしました。
ツール選定
冒頭ご紹介しましたが、以下のツールを導入しました。
rainとcfn-lintはどちらもAWS公式チームが開発しているため、信頼性の観点から採用を決めました。
Formatter
rain
AWS CloudFormationのテンプレートやスタックを操作するためのコマンドラインツールです。
対話型のデプロイや新しいスケルトンテンプレートの作成といった機能がありますが、今回使用するのはCloudFormationテンプレートのフォーマット機能です。
MacユーザーならばHomebrew、Windowsユーザーならばバイナリをインストールして使用します。
Linter
cfn-lint
AWS CloudFormationのYAML/JSONテンプレートを検証してくれるコマンドラインツールです。
Python、Homebrewでの利用が想定されますが、OSを気にしなくても使えるように今回はPython版を選択しました。
rain fmt
rain fmtの活用方法をご紹介します。
何をしてくれるか
今回の活用の仕方としては3点あります。
- セクション順序の修正
- 完全記法を短縮記法に変換
- クォートスタイルの正規化
どれもMUSTのルールであり、これらを意識せずに自然と守れるように働きかけてくれます。
VSCodeとの連携
テンプレートファイルの保存時にrain fmtを実行してくれるように設定したいというモチベーションがありました。
rainのGitHubページを確認しましたが、VSCodeの拡張機能はなさそうだったため、Run on Saveという拡張機能を利用しました。
マッチしたパスの保存時に任意のコマンドを実行できるため、やりたいことは実現できました。
.vscode/settings.jsonでの設定内容は以下のような感じです。
{
"emeraldwalk.runonsave": {
"commands": [
{
"match": "itaws/cloudformation/.*\\.yaml$",
"cmd": "rain fmt -w ${file}"
}
]
}
}
cfn-lint
cfn-lintの活用方法をご紹介します。
セットアップ
cfn-lintはuvプロジェクトとしてローカル管理しています。
Pythonのバージョンは3.10 ~ 3.13がサポートされているため、3.13を選択しました。
pyproject.tomlは以下のとおりです。
[project]
name = "cloudformation"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
"cfn-lint>=1.47.0",
]
また、.cfnlintrcでも設定を行っています。
include_checks:
- I3011
templates:
- templates/**/*.yaml
append_rules:
- rules/
I3011はDeletionPolicy/UpdateReplacePolicyの設定漏れを検出するルールです。デフォルトでは無効なので、明示的に有効化しています。
append_rulesにはカスタムルールのディレクトリを指定しており、詳細は後述します。
VSCodeとの連携
拡張機能がcfn-lintのGitHubページで紹介されていました。
テンプレートを解析し、違反している部分をラインで表示してくれます。
ビルトインルールでカバーできる範囲
cfn-lintにはビルトインで多数のルールが用意されており、テンプレートの構文エラー、セクション構造の不備、AWSリソース仕様との整合性などを検証してくれます。
今回のコーディング規約との関連では、I3011がDeletionPolicy / UpdateReplacePolicyの設定漏れを検出してくれるルールとして活躍します。
このルールはデフォルトでは無効のため、前述の.cfnlintrcでinclude_checksに指定して有効化しています。
他にはリソース間で一意であるべき識別子の重複や未使用パラメータなど、検出しておくことでレビューでの指摘事項を開発者自身が気付けるというメリットがあります。
cfn-lintカスタムルール
ビルトインルールではカバーできないプロジェクト固有のルールについては、カスタムルールを実装しています。
cfn-lintではルールIDの9000番台がユーザー定義用に予約されており、Warningを意味するWプレフィックスと組み合わせてW9001〜W9004の4つを作成しています。
カスタムルールはPythonコードで実装しています。
.cfnlintrcのappend_rulesで指定したディレクトリはこれらのPythonコードが格納されています。
なおこれらのルールはClaude Codeと一緒に作成しています。
W9001: ConstraintDescription必須チェック
パラメータに制約(AllowedValues、AllowedPattern、MinLength、MaxLength、MinValue、MaxValue)が設定されているにもかかわらず、ConstraintDescriptionがないケースを検出します。
コーディング規約の「ConstraintDescriptionを必ず記載する」に対応するルールです。
W9002: Description必須チェック
トップレベルのDescriptionと、Outputsの各項目のDescriptionが存在するかをチェックします。
コーディング規約の「Descriptionを必ず記載」「Outputs Descriptionを必ず記載する」の2つに対応しています。
W9003: Descriptionのピリオド禁止
テンプレートのDescriptionとOutputsのDescriptionにピリオドが含まれていないかをチェックします。
コーディング規約の「Descriptionにピリオドを使わない」に対応するルールです。
W9004: アカウントID/ARNハードコード検出
Resourcesのプロパティ内にハードコードされた12桁のアカウントIDやARNリテラルがないかを検出します。
!Subで疑似パラメータを使用している場合は検出対象外としており、正しくパラメータ化されたARNは警告されません。
コーディング規約の「疑似パラメータの使用」に対応するルールです。
ツールで自動担保する範囲とレビューで担保する範囲
ここまで紹介してきたツールと規約項目の対応を整理します。
「どの規約をどのツールがカバーしているか」「レビューで見るべき項目はどれか」を一覧にしました。
| 規約項目 | レベル | rain fmt | cfn-lint | レビュー |
|---|---|---|---|---|
| セクション順序の統一 | MUST | ○ | ||
| 組み込み関数の短縮記法 | MUST | ○ | ||
| 文字列値のクォートスタイル | MUST | ○ | ||
| DeletionPolicy / UpdateReplacePolicy | MUST | ○ (I3011) | ||
| ConstraintDescription の記載 | MUST | ○ (W9001) | ||
| Description の記載 | MUST | ○ (W9002) | ||
| Description のピリオド禁止 | MUST | ○ (W9003) | ||
| 疑似パラメータの使用 | MUST | ○ (W9004) | ||
| 使用する言語は英語のみ | MUST | ○ | ||
| AWSTemplateFormatVersion の記載 | MUST | ○ | ||
| AWS 固有パラメータタイプの使用 | MUST | ○ | ||
| Export 名の命名規則 | MUST | ○ | ||
| 認証情報の埋め込み禁止 | MUST | ○ | ||
| 機密パラメータの NoEcho | MUST | ○ | ||
| パラメータ制約の設定 | SHOULD | ○ | ||
| パラメータの Default 値設計 | SHOULD | ○ | ||
| 組み込み関数のネスト回避 | SHOULD | ○ |
rain fmtはファイル保存時に自動実行されるため、開発者は意識することなく3項目が担保されます。
cfn-lintは5項目をカバーしており、うち4項目はプロジェクト固有のカスタムルールです。
残りの9項目は機械的な判定が難しく、レビューで担保する必要があります。
このレビュー担当の9項目を人手で毎回チェックするのは負荷が高いため、次のセクションで紹介するClaude Codeのスキルとして自動化しています。
Claude Code skillsによるレビュー補助
/cfn-reviewというスキルを作成しました。
前述の通り、rain、cfn-lintで対応できる部分とできない部分があるため、それを補ってくれるツールとしてClaude Code skillsを採用しています。
これがあることにより、テンプレートPR作成前にセルフチェックができます。
また、レビュー者もこのスキルでざっくりとチェックしたあと、同じセッションでチェック観点を掘り下げてレビューすることが可能になります。
cfn-reviewスキルの仕組み
スキルは以下の4ステップで動作します。
Step 0: 必要なツールの存在確認
当然ながら、このスキルの使用にはrainとcfn-lintの実行環境が必要です。
もし仮にインストールされていない場合は、OSに応じたインストール方法を案内するように作っています。
Step 1: 対象ファイルの収集
テンプレートディレクトリ配下の全YAMLファイルを収集します。
サンプルテンプレートなど、レビュー対象外のディレクトリは除外しています。
Step 2: rain fmt --verify
各テンプレートに対してrain fmt --verifyをドライランで実行し、フォーマット違反がないかを確認します。
差分がある場合はフォーマット違反として報告し、rain fmtの実行を提案します。
Step 3: cfn-lint の実行
各テンプレートに対してcfn-lintを実行します。
検出結果があれば、ルールIDと行番号に加えて日本語で問題箇所やどう直すべきかを補足します。
Step 4: ガイドラインレビュー(9項目)
前述の整理表で「レビュー」に分類された9項目をClaude Codeがチェックします。
ガイドライン本文を読み込んだうえで各テンプレートを検査し、違反箇所ごとに「ファイルパス:行番号」「違反内容」「修正案(コード例)」を提示します。
MUST違反は必ず指摘し、SHOULD違反は原則指摘しつつ理由があれば許容する、という規約レベルの考え方をそのまま反映しています。
出力例
最終的に全ステップの結果をサマリテーブルとして出力します。
## サマリ
| ファイル | rain fmt | cfn-lint | レビュー (MUST) | レビュー (SHOULD) | 結果 |
|---|---|---|---|---|---|
| vpc.yaml | OK | 0 | 0 | 0 | OK |
| ec2.yaml | OK | 1 | 2 | 1 | NG |
各ステップの詳細な検出結果はサマリの前にファイルごとに出力されるため、NG項目の具体的な違反内容と修正案をすぐに確認できるようになっています。
スキルに不具合や改善点があった場合
これは私が他者に使ってもらう前提のスキルを作成する場合のお作法としていることです。
うまくいかなかった場合は、Issueを作成してもらえるように、スキルで案内するように組み込んでいます。
また、サマリの後に、以下のメッセージを毎回出力するようにしています。
💡 cfn-lint のルール・ガイドライン・レビュー項目に不具合や改善点を見つけた場合は、GitHub Issue で報告してください。
報告先: https://github.com/k-kuwan0/cfn-coding-guidelines/issues
スキルの作成も現状手探りなことが多いため、「なんかうまくいかなかった」ということも発生すると考えています。
その際に、「使えない」と判断するのではなく、「どうすればうまくできるか」をチームで育てていくという共通認識を持って改善していけると良いですね。
最後に
成果物の品質を統一する規約とある程度規約を守ってCloudFormationテンプレートを開発できる環境を整備しました。
CloudFormationテンプレートを作成する際、ルールを定めるのが大事という記事は多く見かけます。
しかし、作成した規約と規約を守るための環境整備をセットで紹介しているのは、私が探した限り見つけることができませんでした。
本記事が、CloudFormationのチーム運用で悩む方のヒントになりつつ、すでに運用できている方が、「うちはこんな規約で運用しているよ」と紹介してくれるようなきっかけになれると幸いです。
まだプロジェクト内で運用段階ですが、使用感を確認して改善していければと思っています。
最後までご覧いただきありがとうございました。








