algonote

There's More Than One Way To Do It

ゆっくりAWS CDK (Python)

ChatGPTを活用して作ったAWS CDK (Python)のハンズオンです。誤りを含む可能性があります。

第1章 イントロダクション

テーマ:なぜ IaC?/CDKとは?/Pythonで学ぶ意義/ハンズオン準備


🏠 導入:IaCの世界へようこそ

霊夢「ねぇ魔理沙、AWSの設定ってコンソールでポチポチやるのめんどくさくない?」
魔理沙「めんどくさいし、同じ構成を再現できないのが地獄だぜ。」
霊夢「じゃあ“コードでインフラを書く”ってやつをやってみようよ!」
魔理沙「それが“Infrastructure as Code”、略して IaC だな!」


💡 1. なぜ Infrastructure as Code(IaC)か

霊夢「IaCって、要は“インフラもプログラムで管理する”ってこと?」
魔理沙「そうそう。例えばこんな感じだぜ。」

# CloudFormation(YAML)の例
Resources:
  MyBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-sample-bucket

霊夢「なるほど、設定がコードとして残るのね。」
魔理沙「そう。コードにすることで、」

  • 再現性(同じ環境を何度でも構築可能)
  • レビュー性(Gitで差分を見られる)
  • 自動化(CI/CDに組み込みやすい)

魔理沙「この3つがめっちゃ強い!」
霊夢「でもYAML書くの面倒そう…」
魔理沙「そこで登場するのがAWS CDKだぜ!」


🚀 2. AWS CDK の概要と v2 への移行ポイント

魔理沙「AWS CDK(Cloud Development Kit)は、AWSのリソースをPythonやTypeScriptなどのプログラミング言語で書けるIaCツールだぜ。」

from aws_cdk import (
    App, Stack,
    aws_s3 as s3,
)
from constructs import Construct

class MyStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)
        s3.Bucket(self, "MyBucket", versioned=True)

app = App()
MyStack(app, "MyStack")
app.synth()

霊夢「おお、これPythonのクラスで書けるんだ!」
魔理沙「そう。CloudFormationを“自動生成”してくれる。 手でYAMLを書かなくても、CDKが裏で作ってくれるのさ。」


🆕 CDK v2の特徴まとめ

比較項目 v1 v2
パッケージ構成 サービスごとに分割(例: aws-cdk.aws-s3) 1つに統合(aws-cdk-lib)
constructモジュール constructs v3系未対応 constructs v10対応
CLI互換 v1と基本互換 同様(cdk synth, cdk deploy
対応言語 Python, TS, Java等 同様+型改善

魔理沙「つまりv2はシンプルで依存地獄から解放された。」
霊夢「Pythonの環境構築も楽になったってことね。」


🐍 3. Python で扱う CDK のメリット・適用シーン

霊夢「Python版のCDKって、TypeScript版とどう違うの?」
魔理沙「Pythonのメリットは“読みやすさ”と“データ操作の柔軟さ”だぜ。」

魔理沙「具体的には――」

  • 🧩 可読性が高い(学習コストが低い)
  • 📊 Pythonの標準ライブラリで設定を柔軟に生成可能
  • 🔬 AI/自動生成との親和性が高い(ChatGPT等で補完しやすい)
  • 🧠 機械学習や運用スクリプトとも統合しやすい

霊夢「つまりPythonエンジニアならすぐ始められるってことね!」
魔理沙「そう、インフラエンジニアじゃなくてもデプロイできるんだぜ。」


🧑‍💻 4. 本書の読み進め方・演習環境の構成

霊夢「この本ではどう進めるの?」
魔理沙「だいたいこんな流れだぜ。」

第1章: CDKの概要と環境構築
第2章: S3, EC2, Lambdaなど基本リソースをコード化
第3章: ネットワーク/サーバレス構成の実践
第4章: CI/CDとマルチ環境運用
第5章: 大規模設計とベストプラクティス

霊夢「演習環境はどうするの?」
魔理沙「ローカルのPython環境でOKだぜ。」

🧱 環境構築の流れ(ハンズオン準備)

# 1. 仮想環境を作成
python3 -m venv .venv
source .venv/bin/activate

# 2. CDK CLIをインストール
npm install -g aws-cdk

# 3. Pythonプロジェクトを初期化
mkdir my_cdk_project && cd my_cdk_project
cdk init app --language python

# 4. 依存関係をインストール
pip install -r requirements.txt

# 5. 動作確認
cdk synth

霊夢「なるほど、これで準備完了ね。」
魔理沙「次章では、実際に“Hello CDK”でS3バケットを立ち上げるぜ!」


📘 まとめ

項目 内容
IaCとは インフラをコードで管理する考え方
AWS CDKとは AWSリソースをPython/TS等で構築するIaCツール
v2の特徴 aws-cdk-libで統一、依存が簡潔に
Pythonの利点 可読性・柔軟性・AI親和性
次のステップ cdk init → S3バケット構築へGo!

霊夢「CDK、なんか魔法陣を描くみたいでワクワクするね!」
魔理沙「そうだろ? コード一発でインフラが立ち上がるんだぜ。まるで呪文みたいにな!」

第2章 開発環境の構築とプロジェクト初期化


🌱 1. 前提条件:AWS アカウント、AWS CLI、Node.js、Python/venv 等

霊夢「さて魔理沙、CDKの準備って何が必要なの?」
魔理沙「大事なのは“AWSアカウントと開発環境”だぜ。IaCの舞台を整えよう!」


🧩 必要なものチェックリスト

項目 内容 確認コマンド
AWS アカウント IAMユーザー or ルートアカウント https://console.aws.amazon.com
AWS CLI AWS操作用コマンドライン aws --version
Node.js CDK CLI動作に必要 node -v
npm Node.jsに付属 npm -v
Python 3.9〜3.12 CDK本体を書く言語 python3 --version
pip Pythonパッケージ管理 pip --version
venv 仮想環境ツール 標準で付属
Git コード管理 git --version

霊夢「つまり“Node.jsはCDK CLI用”、Pythonは“CDKの中身を書く用”ってことね!」
魔理沙「その通り。PythonだけじゃCDK動かないから注意だぜ。」


🧙 AWS CLIの設定

aws configure

霊夢「あっ、これでアクセスキー設定ね!」
魔理沙「そう。リージョンも聞かれるけど、最初は ap-northeast-1(東京)でいいぜ。」


🪄 2. CDK v2 プロジェクトの初期化

魔理沙「さぁ、魔法陣を描こう(プロジェクトを作ろう)!」

🏗️ CDK CLIのインストール

npm install -g aws-cdk

霊夢aws-cdk って npm で入れるのね。」
魔理沙「CLIはNode.jsで動くからな。Pythonのコードはあとで書くぜ。」


🧱 プロジェクトを作成

mkdir my_cdk_app
cd my_cdk_app
cdk init app --language python

出力例:

Applying project template app for python
Initializing a new git repository...
Executing Creating virtualenv...
Executing Installing dependencies...
✅ All done!

霊夢「あっ、自動でvenvと依存関係入ってる!」
魔理沙「そう。ディレクトリ構成を見てみよう。」

my_cdk_app/
├── app.py
├── my_cdk_app/
│   ├── __init__.py
│   └── my_cdk_app_stack.py
├── requirements.txt
├── cdk.json
└── README.md

🧩 3. 仮想環境・requirements.txt・aws-cdk-lib モジュール管理

霊夢「venvが自動でできるけど、手動で作る方法も知っておきたいわね。」

🧙 仮想環境の作成と有効化

python3 -m venv .venv
source .venv/bin/activate  # macOS/Linux
# .venv\Scripts\activate   # Windows

🔧 requirements.txt の中身

aws-cdk-lib==2.149.0
constructs>=10.0.0,<11.0.0

霊夢aws-cdk-lib がCDKの本体ね!」
魔理沙「そうだぜ。v2からはこれ1本でOK。 昔みたいに aws-cdk.aws-s3 とかは不要になった。」


📦 依存関係のインストール

pip install -r requirements.txt

🔍 インストール確認

pip list | grep cdk

出力例:

aws-cdk-lib        2.149.0
constructs         10.3.0

🧠 4. スタック・アプリ・コンストラクトの役割整理

霊夢app.pymy_cdk_app_stack.py の違いがまだわからない…」
魔理沙「CDKの構成は“3層構造”で覚えようぜ。」

App → Stack → Construct
階層 役割
App 全体をまとめる親 app.py
Stack デプロイ単位(CloudFormationスタック) MyCdkAppStack
Construct 個々のAWSリソースを表す部品 s3.Bucket()

📄 app.py の例

#!/usr/bin/env python3
from aws_cdk import App
from my_cdk_app.my_cdk_app_stack import MyCdkAppStack

app = App()
MyCdkAppStack(app, "MyCdkAppStack")
app.synth()

📄 my_cdk_app_stack.py の例

from aws_cdk import (
    Stack,
    aws_s3 as s3,
)
from constructs import Construct

class MyCdkAppStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs):
        super().__init__(scope, construct_id, **kwargs)

        # S3バケットを作成
        s3.Bucket(
            self, "MySampleBucket",
            versioned=True,
            removal_policy=None
        )

霊夢「おお、CDKの階層構造が見えてきた!」
魔理沙「StackはCloudFormationテンプレートに対応してるんだぜ。 synth コマンドで裏側のYAMLも見れる。」


⚙️ 5. デプロイの基本(bootstrap/synth/deploy/destroy)

🧱 デプロイ前の初期化(bootstrap)

cdk bootstrap

魔理沙「CDKが使うS3バケットやIAMロールを自動で作るコマンドだぜ。」


🧬 テンプレートの確認

cdk synth

出力例(抜粋)

Resources:
  MySampleBucket8D7B05F3:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: Enabled

霊夢「これが自動生成されたCloudFormationテンプレートか!」
魔理沙「そう。Pythonコードがインフラ定義に変わった瞬間だぜ。」


🚀 デプロイ実行

cdk deploy

出力例:

✨  Synthesis time: 3.9s
MyCdkAppStack: deploying...
 ✅  MyCdkAppStack (no changes)

霊夢「おぉ…AWSに本当にS3バケットができてる!」
魔理沙「これがIaCの魔法だぜ。」


💣 削除(destroy)

cdk destroy

霊夢「消すのも一瞬なのね。」
魔理沙「CI/CDで自動破棄もできるし、テストにも便利だぜ。」


📘 まとめ

コマンド 説明
cdk init app --language python 新しいCDKプロジェクト作成
cdk synth CloudFormationテンプレート生成
cdk deploy AWSへデプロイ
cdk destroy スタック削除
cdk bootstrap 初回デプロイ用リソース作成

霊夢「これでPythonでAWSを操る準備が整ったね!」
魔理沙「ああ。次章ではいよいよ“S3・EC2・Lambda”の実戦だぜ。」
霊夢「コード多めのハンズオン、腕が鳴るわね!」

第3章 CDK の基本概念と構文

テーマ:CDKの中核概念を理解し、最初のS3バケットをコードで立ち上げよう! (この章から本格的にコードブロック多めで進行します)


🧩 1. Stack/App/Construct/Scope の用語整理

霊夢「ねぇ魔理沙、CDKのコードって App とか Stack とか Construct とか、似た単語が多くて混乱するんだけど…」
魔理沙「それはCDKの“3階建て構造”を覚えるのが早いぜ。」


🏗️ CDKの構造イメージ

App
 └── Stack
      └── Construct
           └── AWSリソース(S3, Lambda, EC2など)

用語まとめ

用語 意味 対応イメージ
App CDK全体のエントリーポイント(アプリケーション) 「家の設計図全体」
Stack CloudFormationのスタック単位(デプロイ単位) 「1つの建物」
Construct AWSリソースや論理構成を表す部品 「部屋や家具」
Scope Constructを配置する“親要素” 「親子関係を示すもの」

霊夢「Scopeってつまり“どこに置くか”ってこと?」
魔理沙「そうだぜ。self, scope, id の3つは常にワンセットだ。」

class MyStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

📦 2. aws-cdk-lib パッケージとサービスモジュールの扱い

霊夢「CDKのv2になって、importがめちゃくちゃシンプルになったって聞いたわ!」
魔理沙「そう。v1では分割されてたAWSモジュールが、v2では全部 aws_cdk_lib に統合されたんだぜ。」


v1(旧)とv2(新)の違い

# v1(旧)
from aws_cdk import (
    core,
    aws_s3 as s3,
    aws_ec2 as ec2,
)

# v2(新)
from aws_cdk import (
    App, Stack,
    aws_s3 as s3,
    aws_ec2 as ec2,
)

v2での基本構造(例)

from aws_cdk import (
    App, Stack,
    aws_s3 as s3,
)
from constructs import Construct

class MyStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)
        s3.Bucket(self, "MyBucket", versioned=True)

app = App()
MyStack(app, "MyStack")
app.synth()

霊夢「PythonコードからAWSリソースが呼び出せるって新鮮ね!」
魔理沙「しかも、すべてCloudFormationテンプレートに変換されるんだぜ。」


🐍 3. Python ならではの注意点(snake_case/予約語対応など)

霊夢「Pythonって命名ルール厳しいけど、CDKでも関係あるの?」
魔理沙「めっちゃある。AWSのプロパティはcamelCaseなのに、Pythonはsnake_case。 CDKでは自動で変換されるから安心だぜ。」


🐍 例:プロパティ変換

CloudFormationのキー Pythonでの指定方法
bucketName bucket_name
removalPolicy removal_policy
versioned versioned(そのまま)

⚠️ Python予約語との衝突例

霊夢「もしプロパティ名がPythonの予約語とかぶったら?」
魔理沙「末尾にアンダースコア _ をつけるんだぜ。」

# 例:lambda は予約語なので
aws_lambda.Function(self, "Fn",
    function_name="my-func",
    handler="app.handler",
    runtime=aws_lambda.Runtime.PYTHON_3_11,
    code=aws_lambda.Code.from_asset("lambda"),
)

lambda というキーワードが使えないため、aws_lambda モジュール名で回避)


💬 蛇足Tips:Python構文での注意

  • True / False / None をAWSテンプレートに変換可能
  • 文字列は基本 str 型、リストや辞書も自動でCFN形式に変換される
  • **kwargs で可変引数を渡す設計が多い

🧱 4. 初めてのスタック:S3バケット1つを作るハンズオン

霊夢「よし、そろそろ作ってみようか!」
魔理沙「いよいよだな。S3バケットを1行で作れる魔法を見せてやるぜ。」


📄 app.py

#!/usr/bin/env python3
from aws_cdk import App
from my_cdk_app.my_cdk_app_stack import MyCdkAppStack

app = App()
MyCdkAppStack(app, "MyCdkAppStack")
app.synth()

📄 my_cdk_app_stack.py

from aws_cdk import (
    Stack,
    aws_s3 as s3,
)
from constructs import Construct

class MyCdkAppStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- 🌟 S3バケットを1つ作成する ---
        bucket = s3.Bucket(
            self, "MyFirstBucket",
            bucket_name="my-first-cdk-bucket-2025",
            versioned=True,
            block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
        )

💻 デプロイ手順

cdk synth
cdk deploy

✅ 出力例(デプロイ時)

✨  Synthesis time: 3.9s
MyCdkAppStack: deploying...
MyCdkAppStack: creating CloudFormation changeset...
 ✅  MyCdkAppStack

Outputs:
MyCdkAppStack.MyFirstBucketName = my-first-cdk-bucket-2025

霊夢「S3バケットできた!ほんとにコード1行で作れたのね。」
魔理沙「しかも、裏でCloudFormationが自動生成されてるんだぜ。」


🧬 5. CDK を用いた CloudFormation テンプレートの生成イメージ

霊夢cdk synth の裏側ってどうなってるの?」
魔理沙「PythonコードがJSON/YAMLのCloudFormationテンプレートに変換されてるんだ。」


📄 出力例(cdk.out/MyCdkAppStack.template.json

{
  "Resources": {
    "MyFirstBucket8D7B05F3": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "BucketName": "my-first-cdk-bucket-2025",
        "VersioningConfiguration": { "Status": "Enabled" },
        "PublicAccessBlockConfiguration": {
          "BlockPublicAcls": true,
          "BlockPublicPolicy": true,
          "IgnorePublicAcls": true,
          "RestrictPublicBuckets": true
        }
      }
    }
  }
}

霊夢「つまり、Pythonのクラスが最終的にはこのテンプレートに変わるってこと?」
魔理沙「そうだぜ。これがCDK最大の魅力、“コードでCloudFormationを書く”仕組みさ。」


📘 まとめ

概念 ポイント
App/Stack/Construct CDKの3階層構造。Appが親でStackがデプロイ単位
aws-cdk-lib v2で統合されたメインパッケージ
Pythonの注意点 snake_case・予約語・自動型変換に注意
初めてのスタック s3.Bucket() でS3を簡単作成
CloudFormation連携 cdk synth → JSON/YAMLテンプレート生成

霊夢「PythonのクラスがそのままAWSリソースになるの、感動的ね。」
魔理沙「次章では、VPCやセキュリティグループをコードで構築して、 本格的なネットワーク構成に踏み込むぜ!」

第4章 ネットワーキング基盤 – VPC/サブネット/セキュリティグループ


🌐 1. VPC をコードで定義する(パブリック/プライベート)

霊夢「ねぇ魔理沙、AWSってVPCがないと何も始まらないんでしょ?」
魔理沙「その通り。VPCはクラウドの“ネットワークの土台”だぜ。まずはVPCをコードで作ってみよう。」


📄 network_stack.py

from aws_cdk import (
    Stack,
    aws_ec2 as ec2,
)
from constructs import Construct

class NetworkStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- 🌍 VPC作成 ---
        self.vpc = ec2.Vpc(
            self, "MyVpc",
            ip_addresses=ec2.IpAddresses.cidr("10.0.0.0/16"),
            max_azs=2,  # 2つのAZを利用
            nat_gateways=1,
            subnet_configuration=[
                ec2.SubnetConfiguration(
                    name="public",
                    subnet_type=ec2.SubnetType.PUBLIC,
                    cidr_mask=24
                ),
                ec2.SubnetConfiguration(
                    name="private",
                    subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS,
                    cidr_mask=24
                ),
            ]
        )

霊夢「すご、これだけでVPC+サブネット+ルート構成が自動でできるの?」
魔理沙「ああ。Vpc コンストラクトが全部まとめてくれる。 しかも max_azs=2 で可用性も確保だ。」


💡 出力イメージ

10.0.0.0/16
├── PublicSubnet1 (10.0.0.0/24)
├── PublicSubnet2 (10.0.1.0/24)
├── PrivateSubnet1 (10.0.2.0/24)
└── PrivateSubnet2 (10.0.3.0/24)

🛣️ 2. サブネット、ルートテーブル、IGW、NAT ゲートウェイ

霊夢「自動生成でもいいけど、自分で細かく定義したいときは?」
魔理沙「もちろんできるぜ。CDKはCloudFormationリソースを直接定義できる。」


📄 custom_vpc_stack.py

from aws_cdk import (
    Stack,
    aws_ec2 as ec2,
)
from constructs import Construct

class CustomVpcStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- VPC作成 ---
        vpc = ec2.Vpc(self, "ManualVpc",
            ip_addresses=ec2.IpAddresses.cidr("10.1.0.0/16"),
            max_azs=2,
            nat_gateways=0,  # 後で追加
            subnet_configuration=[]
        )

        # --- IGW作成 ---
        igw = ec2.CfnInternetGateway(self, "InternetGateway")
        ec2.CfnVPCGatewayAttachment(
            self, "VpcGatewayAttachment",
            vpc_id=vpc.vpc_id,
            internet_gateway_id=igw.ref
        )

        # --- パブリックサブネット ---
        public_subnet = ec2.CfnSubnet(
            self, "PublicSubnetA",
            vpc_id=vpc.vpc_id,
            cidr_block="10.1.0.0/24",
            availability_zone="ap-northeast-1a",
            map_public_ip_on_launch=True
        )

        # --- ルートテーブル ---
        route_table = ec2.CfnRouteTable(self, "PublicRouteTable", vpc_id=vpc.vpc_id)
        ec2.CfnRoute(self, "PublicRoute",
            route_table_id=route_table.ref,
            destination_cidr_block="0.0.0.0/0",
            gateway_id=igw.ref
        )

        ec2.CfnSubnetRouteTableAssociation(self, "PublicAssoc",
            subnet_id=public_subnet.ref,
            route_table_id=route_table.ref
        )

        # --- NATゲートウェイ(オプション) ---
        eip = ec2.CfnEIP(self, "Eip")
        nat_gw = ec2.CfnNatGateway(
            self, "NatGateway",
            subnet_id=public_subnet.ref,
            allocation_id=eip.attr_allocation_id
        )

霊夢「すごい、全部コードで制御できるんだね。」
魔理沙「CDKは“L2 Construct”のラッパー(ec2.Vpc)も、“L1リソース”(CfnVpc)も両方使える。 柔軟性が高いんだぜ。」


🛡️ 3. セキュリティグループ/ネットワークACL/構成ポリシー

霊夢「次はファイアウォール的なやつね!」
魔理沙「そうだ。まずはセキュリティグループ(SG)を作って、HTTP/SSHだけ許可しよう。」


📄 security_stack.py

from aws_cdk import (
    Stack,
    aws_ec2 as ec2,
)
from constructs import Construct

class SecurityStack(Stack):
    def __init__(self, scope: Construct, id: str, vpc: ec2.IVpc, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- Webサーバ用SG ---
        self.web_sg = ec2.SecurityGroup(
            self, "WebSg",
            vpc=vpc,
            description="Allow HTTP and SSH",
            allow_all_outbound=True
        )

        # --- ルール設定 ---
        self.web_sg.add_ingress_rule(ec2.Peer.any_ipv4(), ec2.Port.tcp(22), "SSH from anywhere")
        self.web_sg.add_ingress_rule(ec2.Peer.any_ipv4(), ec2.Port.tcp(80), "HTTP from anywhere")

霊夢「めっちゃシンプルね!」
魔理沙「Network ACLも同じように定義できるけど、実務ではSGの方が柔軟だぜ。」


📘 Network ACL(例:オプション)

nacl = ec2.NetworkAcl(self, "MyAcl", vpc=vpc)
nacl.add_entry("InboundHTTP",
    cidr=ec2.AclCidr.any_ipv4(),
    rule_number=100,
    traffic=ec2.AclTraffic.tcp_port(80),
    direction=ec2.TrafficDirection.INGRESS,
    rule_action=ec2.Action.ALLOW,
)

🧰 4. 実践演習:Webアプリ用ネットワーク構成の作成

霊夢「ここまでのVPCとSGを組み合わせて、Webアプリの基盤を作ろう!」
魔理沙「おう、VPC+EC2+SGで“Hello from Web Server”を出す環境を構築するぜ。」


📄 web_stack.py

from aws_cdk import (
    Stack,
    aws_ec2 as ec2,
)
from constructs import Construct

class WebStack(Stack):
    def __init__(self, scope: Construct, id: str, vpc: ec2.IVpc, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- セキュリティグループ ---
        web_sg = ec2.SecurityGroup(
            self, "WebSecurityGroup",
            vpc=vpc,
            description="Allow SSH and HTTP",
            allow_all_outbound=True
        )
        web_sg.add_ingress_rule(ec2.Peer.any_ipv4(), ec2.Port.tcp(22), "SSH access")
        web_sg.add_ingress_rule(ec2.Peer.any_ipv4(), ec2.Port.tcp(80), "HTTP access")

        # --- EC2インスタンス ---
        instance = ec2.Instance(
            self, "WebServer",
            instance_type=ec2.InstanceType("t3.micro"),
            machine_image=ec2.MachineImage.latest_amazon_linux2023(),
            vpc=vpc,
            security_group=web_sg,
            vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PUBLIC),
        )

        # --- ユーザーデータでApache起動 ---
        instance.user_data.add_commands(
            "yum install -y httpd",
            "systemctl enable httpd",
            "systemctl start httpd",
            "echo 'Hello from CDK Web Server' > /var/www/html/index.html"
        )

💻 デプロイコマンド

cdk synth
cdk deploy

🌐 出力例

Outputs:
WebStack.WebServerInstancePublicIp = 13.115.xxx.xxx

ブラウザでアクセスすると:

http://13.115.xxx.xxx/
→ Hello from CDK Web Server

📘 まとめ

項目 内容
VPC定義 ec2.Vpc でサブネット・NAT・IGWを一括生成可能
サブネット構成 SubnetConfiguration で自動分割 or Cfnリソースで手動制御
セキュリティ SGで柔軟制御、NACLで低レベル制御
実践演習 VPC+EC2+SG+Apache起動まで自動構築

霊夢「コードだけでネットワーク全部作れるなんて、もうAWSマスターだね!」
魔理沙「まだ序章だぜ。次章では、この基盤にアプリを載せる“コンピュート基盤(EC2/ECS/Fargate)”を構築するぞ!」

第5章 コンピュート基盤 – EC2、AutoScaling、ECS/Fargate


☁️ 1. EC2 インスタンス定義とキーペア/IAMロールの設定

霊夢「VPCは作れたけど、やっぱりサーバー(EC2)を立ててみたい!」
魔理沙「よっしゃ、まずは最小構成のEC2をCDKで書こうぜ。」


📄 ec2_stack.py

from aws_cdk import (
    Stack,
    aws_ec2 as ec2,
    aws_iam as iam,
)
from constructs import Construct

class Ec2Stack(Stack):
    def __init__(self, scope: Construct, id: str, vpc: ec2.IVpc, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- EC2用IAMロール ---
        role = iam.Role(
            self, "Ec2Role",
            assumed_by=iam.ServicePrincipal("ec2.amazonaws.com")
        )
        role.add_managed_policy(
            iam.ManagedPolicy.from_aws_managed_policy_name("AmazonSSMManagedInstanceCore")
        )

        # --- セキュリティグループ ---
        sg = ec2.SecurityGroup(
            self, "Ec2SecurityGroup",
            vpc=vpc,
            description="Allow SSH and HTTP",
            allow_all_outbound=True
        )
        sg.add_ingress_rule(ec2.Peer.any_ipv4(), ec2.Port.tcp(22), "SSH")
        sg.add_ingress_rule(ec2.Peer.any_ipv4(), ec2.Port.tcp(80), "HTTP")

        # --- EC2インスタンス ---
        instance = ec2.Instance(
            self, "MyEc2Instance",
            instance_type=ec2.InstanceType("t3.micro"),
            machine_image=ec2.MachineImage.latest_amazon_linux2023(),
            vpc=vpc,
            security_group=sg,
            role=role,
            key_name="my-keypair"  # AWSコンソールで作成済みキー
        )

        # --- ユーザーデータ ---
        instance.user_data.add_commands(
            "yum install -y httpd",
            "systemctl enable httpd",
            "systemctl start httpd",
            "echo '<h1>Hello from EC2 via CDK</h1>' > /var/www/html/index.html"
        )

霊夢「これでEC2+Apacheが自動で起動するんだ!」
魔理沙「しかもIAMロールも設定済み。SSMで入れるようになるぜ。」


💻 デプロイ

cdk synth
cdk deploy

📈 2. オートスケーリンググループの導入

霊夢「アクセス増えても耐えられるようにしたいわ!」
魔理沙「それならAutoScalingGroup(ASG)を使うぜ。」


📄 asg_stack.py

from aws_cdk import (
    Stack,
    aws_autoscaling as autoscaling,
    aws_ec2 as ec2,
)
from constructs import Construct

class AutoScalingStack(Stack):
    def __init__(self, scope: Construct, id: str, vpc: ec2.IVpc, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- セキュリティグループ ---
        sg = ec2.SecurityGroup(
            self, "AsgSg",
            vpc=vpc,
            allow_all_outbound=True
        )
        sg.add_ingress_rule(ec2.Peer.any_ipv4(), ec2.Port.tcp(80))

        # --- AutoScalingグループ ---
        asg = autoscaling.AutoScalingGroup(
            self, "WebAsg",
            vpc=vpc,
            instance_type=ec2.InstanceType("t3.micro"),
            machine_image=ec2.MachineImage.latest_amazon_linux2023(),
            desired_capacity=2,
            min_capacity=1,
            max_capacity=3,
            security_group=sg
        )

        asg.user_data.add_commands(
            "yum install -y httpd",
            "systemctl enable httpd",
            "systemctl start httpd",
            "echo 'Hello from AutoScalingGroup!' > /var/www/html/index.html"
        )

📈 スケーリングポリシーを追加

asg.scale_on_cpu_utilization(
    "CpuScaling",
    target_utilization_percent=60
)

霊夢「CPU60%超えたら自動で増えるのね!」
魔理沙「そう。クラウドらしいスケーラビリティだぜ。」


🐳 3. コンテナ運用:ECS or Fargate 利用のための CDK 構築

霊夢「最近のアプリはコンテナで動かすことが多いよね?」
魔理沙「おう! AWSならECSを使う。CDKなら簡単に構築できるぞ。」


📄 ecs_stack.py

from aws_cdk import (
    Stack,
    aws_ecs as ecs,
    aws_ecs_patterns as ecs_patterns,
    aws_ec2 as ec2,
)
from constructs import Construct

class EcsStack(Stack):
    def __init__(self, scope: Construct, id: str, vpc: ec2.IVpc, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- ECSクラスタ ---
        cluster = ecs.Cluster(
            self, "MyCluster",
            vpc=vpc
        )

        # --- Fargateサービス (Hello Worldコンテナ) ---
        ecs_patterns.ApplicationLoadBalancedFargateService(
            self, "MyFargateService",
            cluster=cluster,
            cpu=256,
            memory_limit_mib=512,
            desired_count=2,
            public_load_balancer=True,
            task_image_options=ecs_patterns.ApplicationLoadBalancedTaskImageOptions(
                image=ecs.ContainerImage.from_registry("nginx:latest"),
                container_port=80,
            ),
        )

霊夢nginx のコンテナが2台、ALB付きで自動展開されるってこと!?」
魔理沙「そうだぜ。これがわずか数十行でできるのがCDKの強みだ。」


💡 CDKの力技:ECS + ALB 一括構築

  • ALB(Application Load Balancer)
  • ターゲットグループ
  • ECSクラスター
  • Fargateサービス → すべて自動作成

⚙️ 4. 演習:Webサービスを ECS+ALB で展開

霊夢「じゃあ、ECS+ALBで実際のWebアプリを動かしてみよう!」
魔理沙「Python FlaskをDocker化して動かす構成にしよう。」


📁 ディレクトリ構成

myapp/
├── app.py
├── Dockerfile
└── requirements.txt

📄 myapp/app.py

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello from Flask on Fargate via CDK!"

📄 myapp/requirements.txt

flask

📄 myapp/Dockerfile

FROM public.ecr.aws/lambda/python:3.11
COPY . .
RUN pip install -r requirements.txt
CMD ["app.app"]

📄 ecs_flask_stack.py

from aws_cdk import (
    Stack,
    aws_ecs as ecs,
    aws_ecs_patterns as ecs_patterns,
    aws_ecr_assets as ecr_assets,
    aws_ec2 as ec2,
)
from constructs import Construct

class EcsFlaskStack(Stack):
    def __init__(self, scope: Construct, id: str, vpc: ec2.IVpc, **kwargs):
        super().__init__(scope, id, **kwargs)

        cluster = ecs.Cluster(self, "FlaskCluster", vpc=vpc)

        # --- DockerイメージをCDKでビルドしてECRにアップ ---
        image_asset = ecr_assets.DockerImageAsset(
            self, "FlaskImage",
            directory="myapp"
        )

        ecs_patterns.ApplicationLoadBalancedFargateService(
            self, "FlaskService",
            cluster=cluster,
            cpu=256,
            memory_limit_mib=512,
            desired_count=2,
            public_load_balancer=True,
            task_image_options=ecs_patterns.ApplicationLoadBalancedTaskImageOptions(
                image=ecs.ContainerImage.from_docker_image_asset(image_asset),
                container_port=8080,
            )
        )

💻 デプロイ

cdk synth
cdk deploy

🌐 出力例

Outputs:
EcsFlaskStack.FlaskServiceLoadBalancerDNS = flask-alb-123456.ap-northeast-1.elb.amazonaws.com

ブラウザでアクセス:

http://flask-alb-123456.ap-northeast-1.elb.amazonaws.com
→ Hello from Flask on Fargate via CDK!

📘 まとめ

項目 内容
EC2構築 IAMロール・セキュリティグループ・ユーザーデータ設定
AutoScaling CPU利用率などでインスタンス数を自動調整
ECS/Fargate コンテナをマネージド実行環境で展開
演習 FlaskアプリをECS+ALBで自動デプロイ

霊夢「ついにCDKだけでコンテナWebアプリを立ち上げた!」
魔理沙「IaCとコンテナ運用を一緒にできるのがCDKの真骨頂だぜ。」
霊夢「次はデータベースを追加して、本格的なWebアプリにしたいわね!」
魔理沙「よし、第6章は“RDS/DynamoDB/ElastiCache”のデータ基盤だ!」

第6章 データ基盤 – RDS/DynamoDB/ElastiCache


🏗️ 1. RDS(MySQL/PostgreSQL)インスタンス定義とセキュリティ設定

霊夢「アプリ作ったけど、データを保存する場所が欲しいわ!」
魔理沙「任せろ。RDSをCDKで構築するぜ!」


📄 rds_stack.py

from aws_cdk import (
    Stack,
    aws_ec2 as ec2,
    aws_rds as rds,
    aws_secretsmanager as secretsmanager,
)
from constructs import Construct

class RdsStack(Stack):
    def __init__(self, scope: Construct, id: str, vpc: ec2.IVpc, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- Secrets ManagerにDBパスワードを自動生成 ---
        db_secret = secretsmanager.Secret(
            self, "DBSecret",
            generate_secret_string=secretsmanager.SecretStringGenerator(
                secret_string_template='{"username": "admin"}',
                generate_string_key="password",
                exclude_punctuation=True,
                password_length=16
            )
        )

        # --- セキュリティグループ(DBアクセス用) ---
        db_sg = ec2.SecurityGroup(
            self, "DbSecurityGroup",
            vpc=vpc,
            description="Allow access to DB",
            allow_all_outbound=True
        )

        # --- RDSインスタンス作成 ---
        self.db_instance = rds.DatabaseInstance(
            self, "MyRdsInstance",
            engine=rds.DatabaseInstanceEngine.mysql(
                version=rds.MysqlEngineVersion.VER_8_0_35
            ),
            credentials=rds.Credentials.from_secret(db_secret),
            instance_type=ec2.InstanceType("t3.micro"),
            vpc=vpc,
            vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS),
            multi_az=False,
            allocated_storage=20,
            max_allocated_storage=100,
            storage_encrypted=True,
            security_groups=[db_sg],
            deletion_protection=False,
            publicly_accessible=False,
            database_name="app_db",
        )

霊夢「すご!これだけでMySQLサーバーが立ち上がるの?」
魔理沙「そうだぜ。パスワードはSecrets Managerに自動保存されるし、セキュリティも安全設計だ。」


💡 もしPostgreSQLを使いたい場合

engine=rds.DatabaseInstanceEngine.postgres(
    version=rds.PostgresEngineVersion.VER_15
)

🔒 アプリからのアクセス用設定(例)

db_sg.add_ingress_rule(
    peer=ec2.Peer.any_ipv4(),
    connection=ec2.Port.tcp(3306),
    description="Allow MySQL access from app"
)

🧩 2. DynamoDB テーブル+グローバルセカンダリインデックス

霊夢「NoSQLならDynamoDBでしょ?」
魔理沙「もちろん。スキーマレスでスケーラブルなテーブルをCDKで構築できるぜ。」


📄 dynamodb_stack.py

from aws_cdk import (
    Stack,
    aws_dynamodb as dynamodb,
)
from constructs import Construct

class DynamoDbStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- DynamoDBテーブル定義 ---
        table = dynamodb.Table(
            self, "UserTable",
            partition_key=dynamodb.Attribute(
                name="user_id", type=dynamodb.AttributeType.STRING
            ),
            billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,  # サーバレス課金
            removal_policy=None
        )

        # --- GSI(グローバルセカンダリインデックス) ---
        table.add_global_secondary_index(
            index_name="email-index",
            partition_key=dynamodb.Attribute(
                name="email", type=dynamodb.AttributeType.STRING
            )
        )

霊夢「SQLいらずでテーブル作れるのね!」
魔理沙「そう。読み取り専用のGSIで検索高速化も簡単だ。」


💬 DynamoDBの特徴メモ

項目 内容
課金体系 オンデマンド(リクエスト単価制)またはプロビジョニング
スキーマ 無し(属性は動的)
インデックス GSI(グローバル)、LSI(ローカル)
利用例 ユーザー情報、セッション管理、キャッシュ的用途

⚡ 3. ElastiCache(Redis)導入例とキャッシュ戦略

霊夢「DBだけだと遅くなる時あるよね?」
魔理沙「そういう時はElastiCache(Redis)でキャッシュを挟むのが定番だぜ。」


📄 redis_stack.py

from aws_cdk import (
    Stack,
    aws_ec2 as ec2,
    aws_elasticache as elasticache,
)
from constructs import Construct

class RedisStack(Stack):
    def __init__(self, scope: Construct, id: str, vpc: ec2.IVpc, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- Redis用SG ---
        redis_sg = ec2.SecurityGroup(
            self, "RedisSg",
            vpc=vpc,
            description="Allow Redis access",
            allow_all_outbound=True
        )

        # --- サブネットグループ ---
        subnet_group = elasticache.CfnSubnetGroup(
            self, "RedisSubnetGroup",
            description="Subnet group for Redis",
            subnet_ids=[subnet.subnet_id for subnet in vpc.private_subnets]
        )

        # --- Redisクラスタ作成 ---
        redis_cluster = elasticache.CfnCacheCluster(
            self, "RedisCluster",
            engine="redis",
            cache_node_type="cache.t3.micro",
            num_cache_nodes=1,
            cluster_name="MyRedisCluster",
            vpc_security_group_ids=[redis_sg.security_group_id],
            cache_subnet_group_name=subnet_group.ref
        )

霊夢「RedisもCDKで書けるんだ!」
魔理沙「ああ。これでRDSやDynamoDBの読み取りをキャッシュできるぜ。」


💡 キャッシュ戦略まとめ

ケース 戦略
RDS+Redis クエリ結果をキャッシュ(読み取り負荷分散)
DynamoDB+Redis 頻出データを短期間保持(TTL付き)
API Gateway+Lambda+Redis サーバレス構成で低レイテンシ化

🧠 4. 演習:API サービス+RDS or DynamoDB 関係構築

霊夢「最後にアプリからDBへ実際につないでみよう!」
魔理沙「Flask+RDS or DynamoDBでAPIサーバーを作るぜ。」


📁 構成例

api_app/
├── app.py
├── Dockerfile
├── requirements.txt

📄 api_app/app.py(RDS利用)

from flask import Flask, jsonify
import pymysql, os

app = Flask(__name__)

DB_HOST = os.getenv("DB_HOST")
DB_USER = os.getenv("DB_USER")
DB_PASS = os.getenv("DB_PASS")
DB_NAME = os.getenv("DB_NAME", "app_db")

@app.route("/")
def hello():
    return "Hello from Flask + RDS!"

@app.route("/users")
def users():
    conn = pymysql.connect(host=DB_HOST, user=DB_USER, password=DB_PASS, database=DB_NAME)
    with conn.cursor() as cur:
        cur.execute("SELECT NOW() as current_time")
        result = cur.fetchone()
    conn.close()
    return jsonify(result)

📄 api_app/Dockerfile

FROM public.ecr.aws/lambda/python:3.11
COPY . .
RUN pip install flask pymysql
CMD ["app.app"]

📄 api_app/requirements.txt

flask
pymysql

📄 ecs_api_stack.py

from aws_cdk import (
    Stack,
    aws_ecs as ecs,
    aws_ecs_patterns as ecs_patterns,
    aws_ecr_assets as ecr_assets,
    aws_ec2 as ec2,
)
from constructs import Construct

class EcsApiStack(Stack):
    def __init__(self, scope: Construct, id: str, vpc: ec2.IVpc, db_host: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        cluster = ecs.Cluster(self, "ApiCluster", vpc=vpc)

        image_asset = ecr_assets.DockerImageAsset(
            self, "ApiImage",
            directory="api_app"
        )

        ecs_patterns.ApplicationLoadBalancedFargateService(
            self, "ApiService",
            cluster=cluster,
            cpu=256,
            memory_limit_mib=512,
            desired_count=2,
            public_load_balancer=True,
            task_image_options=ecs_patterns.ApplicationLoadBalancedTaskImageOptions(
                image=ecs.ContainerImage.from_docker_image_asset(image_asset),
                environment={
                    "DB_HOST": db_host,
                    "DB_USER": "admin",
                    "DB_PASS": "(Secrets Managerから取得する設定も可能)"
                },
                container_port=8080,
            )
        )

💻 デプロイ後にアクセス

curl http://api-alb-12345.ap-northeast-1.elb.amazonaws.com/users
→ {"current_time": "2025-10-29 11:20:33"}

📘 まとめ

項目 内容
RDS構築 MySQL/PostgreSQLをCDKで生成、Secrets Manager連携
DynamoDB構築 スキーマレス+GSI対応、オンデマンド課金
ElastiCache構築 Redisキャッシュクラスタでレスポンス高速化
演習 Flask+RDS/DynamoDB接続APIをECS上に構築

霊夢「これでデータ基盤もバッチリね!」
魔理沙「次はこの基盤を“複数環境(dev/stg/prod)”で自動デプロイできるようにするぜ!」

第7章 サーバーレス基盤 – Lambda/API Gateway/EventBridge


🧠 1. Lambda 関数定義(Python)と IAM ポリシー設定

霊夢「サーバーを立てずに関数だけ動かすって、どういう仕組み?」
魔理沙「それが AWS Lambda だ。Pythonで関数を書くだけでコードが実行できるんだぜ。」


📁 ディレクトリ構成

lambda_app/
├── handler.py
└── requirements.txt

📄 lambda_app/handler.py

import json

def handler(event, context):
    print("Received event:", json.dumps(event))
    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps({"message": "Hello from Lambda!"})
    }

📄 lambda_stack.py

from aws_cdk import (
    Stack,
    aws_lambda as _lambda,
    aws_iam as iam,
)
from constructs import Construct

class LambdaStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- Lambda 関数定義 ---
        fn = _lambda.Function(
            self, "HelloLambda",
            runtime=_lambda.Runtime.PYTHON_3_11,
            handler="handler.handler",
            code=_lambda.Code.from_asset("lambda_app"),
            timeout=None
        )

        # --- IAM ポリシーを付与(CloudWatch Logs など) ---
        fn.add_to_role_policy(iam.PolicyStatement(
            actions=["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
            resources=["*"]
        ))

霊夢「これでコードを置くだけで動くの?サーバーいらないの?」
魔理沙「ああ。AWSが自動でコンテナ化して実行してくれる。デプロイも cdk deploy 一発だ。」


💻 デプロイ確認

cdk synth
cdk deploy

出力例:

Outputs:
LambdaStack.HelloLambdaFunctionName = HelloLambda

💡 簡易テスト(CLI)

aws lambda invoke --function-name HelloLambda response.json
cat response.json

出力例:

{"message": "Hello from Lambda!"}

🌐 2. API Gateway(REST or HTTP)を使った API構築

霊夢「Lambda単体だと呼び出しづらいね。」
魔理沙「API Gatewayを組み合わせると、HTTPで呼べるREST APIになるんだぜ。」


📄 api_gateway_stack.py

from aws_cdk import (
    Stack,
    aws_lambda as _lambda,
    aws_apigateway as apigateway,
)
from constructs import Construct

class ApiGatewayStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- Lambda関数 ---
        fn = _lambda.Function(
            self, "ApiLambda",
            runtime=_lambda.Runtime.PYTHON_3_11,
            handler="handler.handler",
            code=_lambda.Code.from_asset("lambda_app"),
        )

        # --- REST API ---
        api = apigateway.LambdaRestApi(
            self, "ApiGateway",
            handler=fn,
            rest_api_name="HelloApi",
            proxy=False
        )

        # --- /hello エンドポイント追加 ---
        hello = api.root.add_resource("hello")
        hello.add_method("GET")

霊夢「URL叩くだけでLambdaが呼ばれるんだね!」
魔理沙「そう。しかもCDKが自動でIAMとログも設定してくれるぜ。」


💻 デプロイ後に表示されるURL

Outputs:
ApiGatewayStack.ApiGatewayEndpoint = https://abc123.execute-api.ap-northeast-1.amazonaws.com/prod/hello

ブラウザでアクセス:

→ {"message": "Hello from Lambda!"}

💡 HTTP API版(軽量)

from aws_cdk import aws_apigatewayv2_alpha as apigw2
from aws_cdk import aws_apigatewayv2_integrations_alpha as integrations

api = apigw2.HttpApi(
    self, "HttpApi",
    default_integration=integrations.HttpLambdaIntegration("LambdaIntegration", fn)
)

🔔 3. イベント駆動:EventBridge/SNS/SQS の利用

霊夢「Lambdaが自動で起動する仕組みって?」
魔理沙「それがイベント駆動。EventBridgeやSNSでトリガーを設定できるぜ。」


📄 eventbridge_stack.py

from aws_cdk import (
    Stack,
    aws_events as events,
    aws_events_targets as targets,
    aws_lambda as _lambda,
)
from constructs import Construct

class EventBridgeStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- Lambda関数 ---
        fn = _lambda.Function(
            self, "EventLambda",
            runtime=_lambda.Runtime.PYTHON_3_11,
            handler="handler.handler",
            code=_lambda.Code.from_asset("lambda_app"),
        )

        # --- 5分ごとにLambda実行するスケジュールイベント ---
        rule = events.Rule(
            self, "ScheduleRule",
            schedule=events.Schedule.rate(cdk.Duration.minutes(5))
        )
        rule.add_target(targets.LambdaFunction(fn))

📄 SNS 連携例(メッセージ受信トリガー)

from aws_cdk import aws_sns as sns, aws_sns_subscriptions as subs

topic = sns.Topic(self, "MyTopic")
topic.add_subscription(subs.LambdaSubscription(fn))

📄 SQS キュー連携例(非同期処理)

from aws_cdk import aws_sqs as sqs

queue = sqs.Queue(self, "MyQueue")
fn.add_event_source_mapping(
    "QueueEventSource",
    event_source_arn=queue.queue_arn
)

霊夢「つまり“イベントが来たら自動実行”って仕組みが簡単にできるのね!」
魔理沙「その通り。CDKならイベントドリブン構成もPython数行で定義できるんだぜ。」


⚙️ 4. 演習:フルサーバーレス構成で API+バックエンドを構築

霊夢「じゃあ今までのを全部組み合わせて、API→Lambda→DynamoDBにデータ保存する構成を作ろう!」
魔理沙「いいな。これがサーバーレスアプリの基本形だぜ。」


📁 構成

serverless_app/
├── handler.py
├── requirements.txt

📄 serverless_app/handler.py

import json, boto3, os

table_name = os.getenv("TABLE_NAME")
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(table_name)

def handler(event, context):
    if event["httpMethod"] == "POST":
        body = json.loads(event["body"])
        item = {"id": body["id"], "message": body["message"]}
        table.put_item(Item=item)
        return {"statusCode": 201, "body": json.dumps({"status": "saved"})}
    elif event["httpMethod"] == "GET":
        result = table.scan()
        return {"statusCode": 200, "body": json.dumps(result["Items"])}
    else:
        return {"statusCode": 405, "body": "Method not allowed"}

📄 serverless_api_stack.py

from aws_cdk import (
    Stack,
    aws_lambda as _lambda,
    aws_dynamodb as dynamodb,
    aws_apigateway as apigateway,
)
from constructs import Construct

class ServerlessApiStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- DynamoDBテーブル ---
        table = dynamodb.Table(
            self, "MessagesTable",
            partition_key=dynamodb.Attribute(name="id", type=dynamodb.AttributeType.STRING),
            billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST
        )

        # --- Lambda関数 ---
        fn = _lambda.Function(
            self, "ServerlessHandler",
            runtime=_lambda.Runtime.PYTHON_3_11,
            handler="handler.handler",
            code=_lambda.Code.from_asset("serverless_app"),
            environment={"TABLE_NAME": table.table_name},
        )

        table.grant_read_write_data(fn)

        # --- API Gateway ---
        api = apigateway.LambdaRestApi(
            self, "ServerlessApi",
            handler=fn,
            proxy=False
        )

        # --- ルーティング ---
        messages = api.root.add_resource("messages")
        messages.add_method("GET")   # 一覧取得
        messages.add_method("POST")  # 登録

💻 デプロイ

cdk synth
cdk deploy

🌐 実行例

POST

curl -X POST https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/messages \
  -d '{"id": "1", "message": "Hello Serverless!"}'

GET

curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/messages

レスポンス:

[{"id": "1", "message": "Hello Serverless!"}]

霊夢「すごい!インフラもサーバーも書いてないのに動く!」
魔理沙「そう、これが“サーバーレス・アーキテクチャ”の真骨頂だぜ。」


📘 まとめ

項目 内容
Lambda Python関数を自動実行、サーバーレス処理単位
API Gateway HTTP経由でLambdaを公開できるエントリポイント
EventBridge/SNS/SQS イベント駆動・非同期処理を実現
演習構成 Lambda + API Gateway + DynamoDB の完全サーバーレスAPI

霊夢「これでもう、インスタンスもネットワーク設定もいらないね!」
魔理沙「次章ではこのLambdaアプリを本番運用するための“環境分離(dev/stg/prod)”とCI/CD構成を学ぶぜ!」

第8章 ストレージ・配信基盤 – S3/CloudFront/CDN構成


🪣 1. S3 バケット定義、バケットポリシー、ライフサイクル管理

霊夢「Webサイトの静的ファイルを置く場所といえば?」
魔理沙「もちろんS3バケットだな。CDKなら数行で定義できるぜ!」


📄 s3_stack.py

from aws_cdk import (
    Stack,
    aws_s3 as s3,
    Duration,
)
from constructs import Construct

class S3Stack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- S3バケット作成 ---
        self.bucket = s3.Bucket(
            self, "StaticSiteBucket",
            bucket_name="my-static-site-bucket-2025",
            versioned=True,  # バージョニング有効
            block_public_access=s3.BlockPublicAccess.BLOCK_ALL,  # 後でCloudFront経由のみアクセス
            encryption=s3.BucketEncryption.S3_MANAGED,
            removal_policy=None,
            lifecycle_rules=[
                s3.LifecycleRule(
                    id="ExpireOldVersions",
                    noncurrent_version_expiration=Duration.days(30)
                )
            ]
        )

霊夢block_public_access で直アクセスを防いでるのね!」
魔理沙「そう。CloudFront経由でしかアクセスできないようにするのがベストプラクティスだぜ。」


💡 ライフサイクル管理ルール

s3.LifecycleRule(
    id="ExpireTempFiles",
    prefix="tmp/",
    expiration=Duration.days(7)
)

霊夢tmp/ フォルダ配下のファイルを7日で削除できるのね。」
魔理沙「運用コスト削減にはこういうルールも大事だぜ。」


🧱 静的ホスティングを有効化(単独利用時)

self.bucket = s3.Bucket(
    self, "StaticSite",
    website_index_document="index.html",
    website_error_document="error.html",
    public_read_access=True
)

🌐 2. CloudFront/OAI(オリジンアクセスアイデンティティ)で静的サイト配信

霊夢「S3はできたけど、直接アクセスさせるのは不安よね?」
魔理沙「そこでCloudFront登場! OAIを使ってS3への安全な経路を作るんだ。」


📄 cdn_stack.py

from aws_cdk import (
    Stack,
    aws_s3 as s3,
    aws_cloudfront as cloudfront,
    aws_cloudfront_origins as origins,
)
from constructs import Construct

class CdnStack(Stack):
    def __init__(self, scope: Construct, id: str, bucket: s3.IBucket, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- OAI(オリジンアクセスアイデンティティ) ---
        oai = cloudfront.OriginAccessIdentity(self, "OAI")

        # --- バケットポリシーにOAIを許可 ---
        bucket.add_to_resource_policy(
            statement=cloudfront.CloudFrontWebDistributionOriginAccessIdentity(
                self, "AllowOaiAccess"
            )
        )

        # --- CloudFront ディストリビューション ---
        self.distribution = cloudfront.Distribution(
            self, "SiteDistribution",
            default_behavior=cloudfront.BehaviorOptions(
                origin=origins.S3Origin(bucket, origin_access_identity=oai),
                viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS
            ),
            default_root_object="index.html",
            comment="Static website distribution with OAI"
        )

霊夢「OAIって、CloudFrontからS3への“認証付きパイプ”みたいなもの?」
魔理沙「その通り! CloudFront以外からのアクセスをブロックできるぜ。」


💡 キャッシュ設定の例

cloudfront.BehaviorOptions(
    origin=origins.S3Origin(bucket, origin_access_identity=oai),
    cache_policy=cloudfront.CachePolicy.CACHING_OPTIMIZED,
    viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS
)

霊夢CACHING_OPTIMIZED で静的サイトをCDNキャッシュするのね!」
魔理沙「うむ。HTML・CSS・JSをCloudFrontが世界中に配る感じだな。」


💡 エラーページ指定

error_response = cloudfront.ErrorResponse(
    http_status=404,
    response_http_status=200,
    response_page_path="/index.html"
)

霊夢「SPA構成のルーティングにも使えそう!」
魔理沙「React/VueのフロントをS3で配信するときに便利だぜ。」


🚀 3. 演習:静的サイトホスティング+CloudFront を CDK で構築

霊夢「じゃあ、S3+CloudFrontでWebサイトを配信してみよう!」
魔理沙「おう! CDKで静的サイトをアップロードしてみようぜ。」


📁 構成例

static_site/
├── index.html
├── about.html
└── images/
    └── logo.png

📄 static_site_stack.py

from aws_cdk import (
    Stack,
    aws_s3 as s3,
    aws_s3_deployment as s3_deploy,
    aws_cloudfront as cloudfront,
    aws_cloudfront_origins as origins,
)
from constructs import Construct

class StaticSiteStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- S3バケット(公開はCloudFront経由のみ) ---
        site_bucket = s3.Bucket(
            self, "StaticSiteBucket",
            block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
            encryption=s3.BucketEncryption.S3_MANAGED
        )

        # --- CloudFront OAI ---
        oai = cloudfront.OriginAccessIdentity(self, "OAI")
        site_bucket.grant_read(oai)

        # --- CloudFront Distribution ---
        distribution = cloudfront.Distribution(
            self, "StaticSiteDistribution",
            default_root_object="index.html",
            default_behavior=cloudfront.BehaviorOptions(
                origin=origins.S3Origin(site_bucket, origin_access_identity=oai),
                viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
            ),
        )

        # --- S3へファイルを自動デプロイ ---
        s3_deploy.BucketDeployment(
            self, "DeployStaticSite",
            sources=[s3_deploy.Source.asset("./static_site")],
            destination_bucket=site_bucket,
            distribution=distribution,
            distribution_paths=["/*"]
        )

💻 デプロイ

cdk synth
cdk deploy

出力例:

Outputs:
StaticSiteStack.SiteDistributionDomainName = d123abc456.cloudfront.net

🌍 ブラウザでアクセス

https://d123abc456.cloudfront.net

表示:

Hello from my Static Website via CloudFront!

💡 追加:WAF・キャッシュ制御などの発展設定

cloudfront.Distribution(
    self, "AdvancedDistribution",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.S3Origin(site_bucket, origin_access_identity=oai),
        viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        cache_policy=cloudfront.CachePolicy.CACHING_OPTIMIZED,
    ),
    price_class=cloudfront.PriceClass.PRICE_CLASS_100,  # アジア中心
    enable_logging=True,
)

霊夢「キャッシュポリシーや価格クラスまでコードで設定できるのね!」
魔理沙「そうだ。CDKなら“CDNの構成管理”もIaCの範囲内だぜ。」


📘 まとめ

項目 内容
S3 静的ファイル格納・バージョニング・ライフサイクル管理
CloudFront グローバルCDN配信、HTTPSリダイレクト
OAI S3への直接アクセスを防ぎ、CloudFront経由のみに制限
演習結果 S3 + CloudFront構成でSPA/静的Webを高速配信可能

霊夢「S3とCloudFrontで、まるでGitHub Pagesみたいな環境ができちゃうね!」
魔理沙「それどころか、CDKなら自動更新・バージョン管理・セキュリティ設定まで含めてコード化できるんだぜ!」

第9章 マルチステージ/環境別構成管理


🌱 1. 開発/ステージング/本番環境の分離設計

霊夢「CDKって、一つのコードで環境ごとに構成を変えられるの?」
魔理沙「もちろんだ。devstgprod の3環境を1つのコードで管理できるぜ。」


📁 ディレクトリ構成例

cdk_project/
├── app.py
├── stacks/
│   ├── network_stack.py
│   ├── compute_stack.py
│   └── storage_stack.py
└── cdk.json

💡 CDKの環境管理の基本

要素 役割
Context CDK実行時に渡すパラメータ (cdk.json / --context)
Environment AWSアカウントとリージョン情報
StackProps スタック間で共通の設定を受け渡す

📄 cdk.json に環境設定を記述

{
  "app": "python3 app.py",
  "context": {
    "environments": {
      "dev": {
        "account": "111111111111",
        "region": "ap-northeast-1"
      },
      "stg": {
        "account": "222222222222",
        "region": "ap-northeast-1"
      },
      "prod": {
        "account": "333333333333",
        "region": "us-east-1"
      }
    }
  }
}

霊夢「Contextに全部の環境情報を書いておくのね!」
魔理沙「そう。cdk deploy --context stage=dev で切り替え可能だぜ。」


⚙️ 2. パラメータ化(Context, Environment, StackProps)

霊夢「環境ごとにバケット名やインスタンスタイプを変えるには?」
魔理沙StackPropsを使ってパラメータを渡すんだ。」


📄 stacks/app_stack.py

from aws_cdk import (
    Stack,
    aws_s3 as s3,
)
from constructs import Construct

class AppStack(Stack):
    def __init__(self, scope: Construct, id: str, env_name: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- S3 バケット名を環境ごとに変える ---
        s3.Bucket(
            self, "AppBucket",
            bucket_name=f"my-app-bucket-{env_name}",
            versioned=True
        )

📄 app.py

#!/usr/bin/env python3
from aws_cdk import App, Environment
from stacks.app_stack import AppStack
import json, os

app = App()

# --- Contextから環境を取得 ---
stage = app.node.try_get_context("stage") or "dev"

# --- cdk.jsonの環境定義を読み込み ---
env_config = app.node.try_get_context("environments")[stage]

env = Environment(
    account=env_config["account"],
    region=env_config["region"]
)

AppStack(
    app, f"MyApp-{stage}",
    env_name=stage,
    env=env
)

app.synth()

💻 デプロイコマンド

cdk deploy --context stage=dev
cdk deploy --context stage=stg
cdk deploy --context stage=prod

霊夢「同じコードで環境を切り替えられるなんて便利ね!」
魔理沙「これがIaCの真価だ。マルチアカウントにも対応できるぜ。」


💡 StackPropsを使った例(明示的に渡す)

from dataclasses import dataclass

@dataclass
class AppStackProps:
    env_name: str
    bucket_suffix: str
props = AppStackProps(env_name=stage, bucket_suffix="001")
AppStack(app, f"MyApp-{stage}", **vars(props), env=env)

🌍 3. ブートストラップとクロスアカウント・クロスリージョン構成

霊夢「マルチアカウントにデプロイするときの準備ってあるの?」
魔理沙「あるぜ。cdk bootstrap で“デプロイ用リソース”を先に作っておくんだ。」


💡 ブートストラップとは?

CDKが内部で使う以下のリソースを作成する:

  • CDKデプロイ用S3バケット
  • CloudFormation実行ロール
  • File Asset用ECRリポジトリ

📘 各アカウント・リージョンで実行

cdk bootstrap aws://111111111111/ap-northeast-1  # dev
cdk bootstrap aws://222222222222/ap-northeast-1  # stg
cdk bootstrap aws://333333333333/us-east-1       # prod

霊夢「ブートストラップしないとデプロイできないの?」
魔理沙「そうだ。CDKが使う“中間リソース”を準備しておく必要があるんだ。」


💡 クロスアカウント構成例(Pipelineなど)

from aws_cdk import pipelines as pipelines

pipeline = pipelines.CodePipeline(
    self, "MyPipeline",
    synth=pipelines.ShellStep("Synth",
        input=pipelines.CodePipelineSource.git_hub("user/repo", "main"),
        commands=["npm install -g aws-cdk", "cdk synth"]
    ),
    cross_account_keys=True
)

霊夢cross_account_keys=True でクロスアカウント対応なのね!」
魔理沙「そう、複数AWSアカウントにデプロイする時に必要な設定だぜ。」


🧪 4. 演習:dev/stg/prod 環境を CDK でテンプレート化

霊夢「じゃあ、環境ごとにリソースを分けて自動で作る構成をやってみよう!」
魔理沙「3つの環境をループで定義して、それぞれ別のAWS環境にデプロイするぜ。」


📄 multi_env_app.py

#!/usr/bin/env python3
from aws_cdk import App, Environment
from stacks.app_stack import AppStack

app = App()

# --- 環境設定をループで定義 ---
environments = {
    "dev":  {"account": "111111111111", "region": "ap-northeast-1"},
    "stg":  {"account": "222222222222", "region": "ap-northeast-1"},
    "prod": {"account": "333333333333", "region": "us-east-1"},
}

for stage, cfg in environments.items():
    AppStack(
        app, f"MyApp-{stage}",
        env_name=stage,
        env=Environment(account=cfg["account"], region=cfg["region"])
    )

app.synth()

💻 一括デプロイ(個別またはまとめて)

cdk deploy MyApp-dev
cdk deploy MyApp-stg
cdk deploy MyApp-prod

📦 出力例

✅  MyApp-dev: Deployed
✅  MyApp-stg: Deployed
✅  MyApp-prod: Deployed

💬 各環境で異なるS3バケット作成

環境 バケット名
dev my-app-bucket-dev
stg my-app-bucket-stg
prod my-app-bucket-prod

霊夢「おぉ、3環境が自動で分かれた!」
魔理沙「一度作れば、チーム全員が同じIaCテンプレートで環境を再現できるぜ。」


💡 さらに発展:環境ごとの設定ファイルを外部管理

# config/dev.yaml
bucket_suffix: dev
instance_type: t3.micro
import yaml
config = yaml.safe_load(open(f"config/{stage}.yaml"))
AppStack(app, f"MyApp-{stage}", **config)

霊夢「YAMLで管理すればOpsチームも編集しやすいね。」
魔理沙「IaCとConfigの分離、これぞプロのCDK設計だぜ。」


📘 まとめ

項目 内容
環境分離 dev / stg / prod を1コードで管理
Context & StackProps パラメータを動的に切り替える仕組み
Bootstrap 各アカウントでCDKが動くための下準備
クロスアカウント 複数AWSアカウント間でデプロイ
演習 3環境をループで生成、テンプレート化可能

霊夢「CDKって、まるでインフラをプログラムみたいに構築できるのね!」
魔理沙「その通り。次章では“CI/CDパイプライン構築”をやって、 コード変更→自動デプロイまで一気通貫で仕上げるぜ!」

第10章 CI/CD パイプラインの構築


🪄 1. AWS CodePipeline/AWS CodeBuild/GitHub Actions 連携

霊夢「いよいよCI/CDね。コードをpushしたら自動でデプロイしたい!」
魔理沙「そのための最強コンビが CodePipeline + CodeBuild だぜ。」


💡 CDKでCI/CDを構築する流れ

  1. CodeCommit/GitHub などのソースをトリガーに
  2. CodeBuild でテスト・ビルドを実行
  3. CodePipeline がステージ(dev→prod)に自動デプロイ

📄 pipeline_stack.py

from aws_cdk import (
    Stack,
    pipelines as pipelines,
)
from constructs import Construct

class PipelineStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- GitHub ソース設定 ---
        source = pipelines.CodePipelineSource.git_hub(
            "your-github-user/cdk-sample-app",
            "main",
            authentication=cdk.SecretValue.secrets_manager("github-token")
        )

        # --- Synth ステップ(CDKビルド) ---
        synth = pipelines.ShellStep(
            "Synth",
            input=source,
            commands=[
                "npm install -g aws-cdk",
                "python -m venv .venv",
                "source .venv/bin/activate",
                "pip install -r requirements.txt",
                "cdk synth"
            ]
        )

        # --- CodePipeline定義 ---
        self.pipeline = pipelines.CodePipeline(
            self, "CdkPipeline",
            pipeline_name="MyAppPipeline",
            synth=synth
        )

霊夢「GitHubのmainブランチをトリガーに自動でCDKがビルドされるのね!」
魔理沙「そう。Secrets ManagerにGitHubトークンを入れておけば、認証も安全だぜ。」


💡 GitHubトークン登録

AWS CLIから:

aws secretsmanager create-secret \
  --name github-token \
  --secret-string "ghp_xxxxxxxxxxxxxxxxxxxxx"

🧪 2. CDK アプリのテスト自動化(ユニットテスト/アサーション)

霊夢「CIにテストも入れたいわ!」
魔理沙「CDKにはassertionsを使ったテストも書けるんだ。」


📄 tests/test_cdk_stack.py

import aws_cdk as cdk
import aws_cdk.assertions as assertions
from stacks.app_stack import AppStack

def test_s3_bucket_created():
    app = cdk.App()
    stack = AppStack(app, "TestStack", env_name="dev")
    template = assertions.Template.from_stack(stack)

    template.has_resource_properties("AWS::S3::Bucket", {
        "VersioningConfiguration": {"Status": "Enabled"}
    })

霊夢「これで“S3バケットのバージョニング有効化”を自動チェックできるのね!」
魔理沙「CIでpytestを走らせるだけでIaCの構成も検証できるんだぜ。」


📄 buildspec.yml(CodeBuild用)

version: 0.2
phases:
  install:
    commands:
      - python -m venv .venv
      - source .venv/bin/activate
      - pip install -r requirements.txt
      - pip install pytest
  build:
    commands:
      - pytest -v
      - cdk synth

霊夢「CodeBuildでpytestまで回すのか!」
魔理沙「IaCの品質保証もCIに組み込むのが今風だぜ。」


🔁 3. デプロイ戦略(ブルーグリーン/ローリング)/ロールバック構成

霊夢「本番デプロイって、一気に入れ替えるのは怖いよね…?」
魔理沙「そこで“ブルーグリーン”や“ローリングデプロイ”の出番だ!」


💡 デプロイ戦略の違い

戦略 内容 利点
ブルーグリーン 新旧環境を並行稼働し、切り替え 即時ロールバック可能
ローリング 段階的に入れ替え リソース節約・安定運用
カナリア 一部トラフィックだけ新環境へ 検証が容易

📄 Lambdaのブルーグリーンデプロイ例

from aws_cdk import (
    Stack,
    aws_codedeploy as codedeploy,
    aws_lambda as _lambda,
)
from constructs import Construct

class BlueGreenStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- Lambda定義 ---
        fn = _lambda.Function(
            self, "BlueGreenLambda",
            runtime=_lambda.Runtime.PYTHON_3_11,
            handler="handler.handler",
            code=_lambda.Code.from_asset("lambda_app")
        )

        # --- エイリアス作成 ---
        alias = _lambda.Alias(
            self, "LambdaAlias",
            alias_name="live",
            version=fn.current_version
        )

        # --- CodeDeployでブルーグリーン管理 ---
        codedeploy.LambdaDeploymentGroup(
            self, "DeploymentGroup",
            alias=alias,
            deployment_config=codedeploy.LambdaDeploymentConfig.ALL_AT_ONCE,
            alarms=[],
            auto_rollback=codedeploy.AutoRollbackConfig(
                deployment_in_alarm=True,
                failed_deployment=True
            )
        )

霊夢auto_rollback=True で失敗時に自動で戻せるのね!」
魔理沙「そうだ。エラーが出たら即座に旧バージョンへ切り替わるぜ。」


📄 ECSローリングデプロイ例(Fargate)

from aws_cdk import aws_ecs_patterns as ecs_patterns

ecs_patterns.ApplicationLoadBalancedFargateService(
    self, "AppService",
    desired_count=3,
    circuit_breaker=ecs.DeploymentCircuitBreaker(rollback=True),
)

霊夢circuit_breaker(rollback=True)、まさに自動安全装置!」
魔理沙「CI/CDの“C”がContinuousだけじゃなくて“Careful”のCにもなるぜ。」


🚀 4. 演習:GitHub プッシュ時に dev→prod へのパイプライン構築

霊夢「実際にGitHub push → dev → prod 自動デプロイを組みたい!」
魔理沙「よっしゃ、実戦構成を見せるぜ。」


📄 multi_stage_pipeline_stack.py

from aws_cdk import (
    Stack,
    pipelines as pipelines,
    aws_codebuild as codebuild,
)
from constructs import Construct

class MultiStagePipelineStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- ソース (GitHub) ---
        source = pipelines.CodePipelineSource.git_hub(
            "your-github-user/cdk-sample-app", "main",
            authentication=cdk.SecretValue.secrets_manager("github-token")
        )

        # --- Synth ステップ ---
        synth_step = pipelines.ShellStep(
            "Synth",
            input=source,
            commands=[
                "python -m venv .venv",
                "source .venv/bin/activate",
                "pip install -r requirements.txt",
                "pytest -v",
                "cdk synth"
            ]
        )

        # --- パイプライン作成 ---
        pipeline = pipelines.CodePipeline(
            self, "Pipeline",
            synth=synth_step,
            cross_account_keys=True
        )

        # --- devステージ ---
        dev_stage = MyAppStage(self, "DevStage", env_name="dev")
        pipeline.add_stage(dev_stage)

        # --- 手動承認を挟んでprodへ ---
        pipeline.add_stage(
            MyAppStage(self, "ProdStage", env_name="prod"),
            pre=[pipelines.ManualApprovalStep("PromoteToProd")]
        )

📄 stages/my_app_stage.py

from aws_cdk import Stage
from stacks.app_stack import AppStack
from constructs import Construct

class MyAppStage(Stage):
    def __init__(self, scope: Construct, id: str, env_name: str, **kwargs):
        super().__init__(scope, id, **kwargs)
        AppStack(self, f"MyApp-{env_name}", env_name=env_name)

💻 デプロイ

cdk deploy MultiStagePipelineStack

💡 動作イメージ

GitHub push (main)
   ↓
CodePipeline 起動
   ↓
CodeBuild で pytest & synth
   ↓
DevStage 自動デプロイ
   ↓
手動承認
   ↓
ProdStage デプロイ

霊夢「GitHubにpushしただけで自動でdev→prodデプロイされるんだ!」
魔理沙「しかもテスト→承認→ロールバックまで自動。 もう“デプロイ職人”いらずだぜ。」


📘 まとめ

項目 内容
CodePipeline/CodeBuild CDKで構築可能。CI/CDを自動化
GitHub Actions連携 Secrets Manager経由で安全に認証
テスト統合 pytest + assertionsでIaCテスト
デプロイ戦略 ブルーグリーン・ローリング・ロールバック
演習構成 GitHub push → dev → 承認 → prod 自動展開

霊夢「CI/CDがここまでコードで書けるなんてすごいね!」
魔理沙「CDKはただのIaCじゃない。“デプロイオーケストレーション”までできるんだぜ!」

第11章 テストとトラブルシューティング


🧩 1. CDK アプリのテスト手法(Assertions, Snapshot)

霊夢「CDKのテストって、コードの挙動じゃなくて“インフラ構成”を検証するんでしょ?」
魔理沙「その通りだ。アプリコードじゃなく“CloudFormationテンプレートが正しいか”をテストするのがCDK流だぜ。」


📁 テスト用ディレクトリ構成

tests/
├── test_app_stack.py
└── snapshots/
    └── test_app_stack.snapshot.json

📄 tests/test_app_stack.py

import aws_cdk as cdk
import aws_cdk.assertions as assertions
from stacks.app_stack import AppStack

def test_s3_bucket_properties():
    app = cdk.App()
    stack = AppStack(app, "TestStack", env_name="dev")
    template = assertions.Template.from_stack(stack)

    # --- S3バケットが作成されていることを検証 ---
    template.resource_count_is("AWS::S3::Bucket", 1)

    # --- バージョニングが有効であることを確認 ---
    template.has_resource_properties("AWS::S3::Bucket", {
        "VersioningConfiguration": {"Status": "Enabled"}
    })

霊夢「これでCDKの出力テンプレートを直接検証できるのね!」
魔理沙「そう。コード変更による構成差分を安全にチェックできる。」


💡 Snapshotテスト(テンプレート丸ごと比較)

def test_template_snapshot(snapshot):
    app = cdk.App()
    stack = AppStack(app, "SnapshotStack", env_name="dev")
    template = assertions.Template.from_stack(stack)
    snapshot.assert_match(template.to_json(), "snapshot.json")

霊夢「テンプレート全体をJSONとしてスナップショット保存するのね。」
魔理沙「構成変更時に差分が出るから、レビューにも便利だぜ。」


📄 pytest.ini

[pytest]
addopts = --tb=short -v
python_files = test_*.py

💻 実行例

pytest -v

出力:

tests/test_app_stack.py::test_s3_bucket_properties PASSED
tests/test_app_stack.py::test_template_snapshot PASSED

🪵 2. デプロイ時のログ/CloudFormation スタック失敗対応

霊夢「テストは通っても、cdk deployで失敗することあるよね…」
魔理沙「あるある。CloudFormationログの見方を知っておくと安心だぜ。」


💻 デプロイ時のエラー確認

cdk deploy --verbose

出力例:

CREATE_FAILED AWS::IAM::Role MyRole Resource creation cancelled

💡 CloudFormationコンソールで詳細確認

AWSマネジメントコンソール → 「CloudFormation」 → スタックを選択 → 「イベント」 タブ

CREATE_FAILED: AccessDenied - IAMポリシーが不足

霊夢「CDKのデプロイエラーはCloudFormationの裏で起きてるのね!」
魔理沙「そう。cdkはあくまでテンプレートを流してるだけだから、根本はCloudFormationだ。」


📦 CloudFormationのスタック削除忘れ対応

aws cloudformation delete-stack --stack-name MyApp-dev

霊夢ROLLBACK_COMPLETE で止まったスタック、削除できない時あるんだよね。」
魔理沙「その場合は依存リソース(S3、VPC等)を手動で削除してから再試行だぜ。」


💡 CDKデプロイの出力ログを保存する例

cdk deploy --require-approval never --outputs-file ./cdk-outputs.json

出力例:

{
  "MyApp-dev": {
    "BucketName": "my-app-bucket-dev"
  }
}

⚠️ 3. よくある落とし穴とアンチパターン

霊夢「実際に使ってて失敗するポイントってある?」
魔理沙「めちゃくちゃある!IaC初心者がよくハマるやつを紹介するぜ。」


🧱 アンチパターン①:リソース肥大化

for i in range(1000):
    s3.Bucket(self, f"Bucket{i}")

霊夢「これ…スタック1つで1000個のバケット?」
魔理沙「CloudFormationにはリソース数制限がある(約500個)。 分割(Nested Stack)するか、S3だけ別スタックに切り出そう。」


🧹 アンチパターン②:削除ポリシーの設定忘れ

# ❌ remove_policy 未設定
s3.Bucket(self, "Bucket")

魔理沙「デフォルトだと削除時にバケットが残るぞ。」

✅ 正解:

s3.Bucket(
    self, "Bucket",
    removal_policy=cdk.RemovalPolicy.DESTROY
)

🔐 アンチパターン③:SecretやKeyを直書き

# ❌ 秘密情報をコードにベタ書き
db_password = "supersecret123"

✅ 修正版:

from aws_cdk import aws_secretsmanager as secretsmanager

secret = secretsmanager.Secret.from_secret_name_v2(
    self, "DbSecret", "my-db-secret"
)

💣 アンチパターン④:CDK diff未確認でデプロイ

魔理沙cdk diffで差分を見ずにdeployすると痛い目見るぜ。」

✅ 正解:

cdk diff

出力例:

IAM Policy Changes
[+] Policy: Add s3:DeleteObject permission

⚙️ ベストプラクティスまとめ

分野 ベストプラクティス
スタック設計 リソースは100〜200個以内/機能単位で分割
削除ポリシー 明示的にDESTROY or RETAINを指定
機密情報 Secrets Manager or Parameter Storeで管理
デプロイ前 cdk diffpytest を必ず実行
ログ分析 CloudFormation「イベント」 +cdk --verbose

🧰 4. 演習:テスト付き構成をリファクタリング

霊夢「じゃあ実際に“テストしやすいCDK構成”にリファクタリングしてみよう!」
魔理沙「おう。モノリシックなスタックを分割して、 テストを分離しやすい形にするんだ。」


📁 構成

stacks/
├── network_stack.py
├── app_stack.py
└── storage_stack.py
tests/
└── test_storage_stack.py

📄 stacks/storage_stack.py

from aws_cdk import Stack, aws_s3 as s3
from constructs import Construct

class StorageStack(Stack):
    def __init__(self, scope: Construct, id: str, env_name: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        self.bucket = s3.Bucket(
            self, "AppBucket",
            bucket_name=f"my-storage-{env_name}",
            versioned=True,
            removal_policy=None
        )

📄 tests/test_storage_stack.py

import aws_cdk as cdk
import aws_cdk.assertions as assertions
from stacks.storage_stack import StorageStack

def test_storage_bucket_created():
    app = cdk.App()
    stack = StorageStack(app, "StorageTest", env_name="dev")
    template = assertions.Template.from_stack(stack)

    template.resource_count_is("AWS::S3::Bucket", 1)
    template.has_resource_properties("AWS::S3::Bucket", {
        "VersioningConfiguration": {"Status": "Enabled"}
    })

💻 実行

pytest -v

出力例:

tests/test_storage_stack.py::test_storage_bucket_created PASSED

📄 改善例:テストと依存関係を明示する構成

# app.py
from aws_cdk import App
from stacks.network_stack import NetworkStack
from stacks.storage_stack import StorageStack

app = App()
network = NetworkStack(app, "Network")
StorageStack(app, "Storage", env_name="dev", tags={"DependsOn": network.stack_name})
app.synth()

霊夢「これならネットワークとストレージの関係もテスト単位で分けられるね!」
魔理沙「その通り。大規模プロジェクトでは“スタックごとの責務分離”が超重要だぜ。」


📘 まとめ

項目 内容
テスト Assertions/SnapshotでIaC構成を検証
ログ分析 CloudFormationイベント/--verbose/Outputs
落とし穴 リソース肥大化・削除忘れ・秘密情報直書き
リファクタリング演習 スタック分割+pytest検証で品質を担保

霊夢「CDKは“書いたあとが本番”って感じね!」
魔理沙「そう。テストと監視を組み合わせれば、IaCもプロダクトコードと同じ品質で運用できるぜ。」

第12章 再利用可能な Construct ライブラリ設計


🧱 1. L1/L2/L3 Construct の違いと使い分け

霊夢「CDKで“Construct”ってよく出てくるけど、どんな種類があるの?」
魔理沙「CDKのリソースは抽象度ごとに3つの階層に分かれてるんだぜ。」


📘 CDK Construct の階層構造

レベル 名前 説明
L1 CloudFormation Resource AWSのリソースをそのまま表す低レベルAPI CfnBucket, CfnInstance
L2 High-level Construct L1を包んで使いやすくした抽象化 s3.Bucket, ec2.Instance
L3 Pattern Construct 複数のL2を組み合わせた便利パターン ecs_patterns.ApplicationLoadBalancedFargateService

霊夢「なるほど、L1がCloudFormationそのもの、L2が“素のCDK”、L3が“便利セット”ね。」
魔理沙「そう。実務ではL2/L3を使うのが基本だが、L1も拡張時に重要だぜ。」


💡 例:L1 と L2 の違い

# --- L1 (低レベル) ---
from aws_cdk import aws_s3 as s3
bucket_l1 = s3.CfnBucket(self, "MyBucketL1", bucket_name="raw-l1-bucket")

# --- L2 (高レベル) ---
bucket_l2 = s3.Bucket(self, "MyBucketL2", versioned=True)

💡 例:L3 のパターン(ECS + ALB)

from aws_cdk import aws_ecs_patterns as ecs_patterns
service = ecs_patterns.ApplicationLoadBalancedFargateService(
    self, "WebApp",
    desired_count=2,
    public_load_balancer=True,
)

霊夢「この数行でECS+ALBが全部できるの、やっぱ便利すぎる…!」
魔理沙「そう。L3は“構築テンプレート”として再利用するのに最適なんだぜ。」


🧠 2. 自作 Construct/モジュール化のための設計ガイド

霊夢「自分の社内用やチーム用の構成をモジュール化できたら便利そう!」
魔理沙「それがこの章の本題、“自作Construct”だぜ。」


📁 プロジェクト構成例

cdk_constructs/
├── my_constructs/
│   ├── __init__.py
│   └── web_bucket_construct.py
├── app.py
└── requirements.txt

📄 my_constructs/web_bucket_construct.py

from aws_cdk import (
    aws_s3 as s3,
    aws_cloudfront as cloudfront,
    aws_cloudfront_origins as origins,
)
from constructs import Construct

class WebBucketConstruct(Construct):
    """静的サイト配信用のS3+CloudFrontセットを1コンポーネント化"""

    def __init__(self, scope: Construct, id: str, *, bucket_name: str):
        super().__init__(scope, id)

        self.bucket = s3.Bucket(
            self, "SiteBucket",
            bucket_name=bucket_name,
            block_public_access=s3.BlockPublicAccess.BLOCK_ALL
        )

        oai = cloudfront.OriginAccessIdentity(self, "OAI")
        self.bucket.grant_read(oai)

        self.distribution = cloudfront.Distribution(
            self, "Distribution",
            default_behavior=cloudfront.BehaviorOptions(
                origin=origins.S3Origin(self.bucket, origin_access_identity=oai),
                viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS
            ),
            default_root_object="index.html"
        )

霊夢「おお!これでS3とCloudFrontをまとめて“1行で使える部品”になるのね!」
魔理沙「そう。再利用性が爆上がりするぜ。」


📄 app.py(再利用例)

from aws_cdk import App, Stack
from my_constructs.web_bucket_construct import WebBucketConstruct
from constructs import Construct

class MainStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)
        WebBucketConstruct(self, "WebSite", bucket_name="demo-site-2025")

app = App()
MainStack(app, "ConstructDemo")
app.synth()

💡 パラメータ渡しのベストプラクティス

  • 明示的に __init__ の引数に * を入れてキーワード専用引数にする
  • self.<property> で外部から参照可能にする
  • type hintを明確に記述(mypy/Pyright対応)

🧪 3. バージョン管理/テスト付きライブラリ化/公開までの流れ

霊夢「作ったConstructをpipで使えるようにしたい!」
魔理沙「よし、CDK Construct Library化の手順を見ていこう。」


📁 ライブラリ構成例

my-cdk-lib/
├── my_cdk_lib/
│   ├── __init__.py
│   └── web_bucket_construct.py
├── tests/
│   └── test_web_bucket_construct.py
├── pyproject.toml
└── README.md

📄 pyproject.toml

[project]
name = "my-cdk-lib"
version = "0.1.0"
description = "My custom AWS CDK constructs"
authors = [{ name="Hiromichi Nomata", email="you@example.com" }]
dependencies = ["aws-cdk-lib>=2.150.0", "constructs>=10.0.0"]
readme = "README.md"

[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

📄 tests/test_web_bucket_construct.py

import aws_cdk as cdk
import aws_cdk.assertions as assertions
from my_cdk_lib.web_bucket_construct import WebBucketConstruct
from constructs import Construct

class DummyStack(cdk.Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)
        WebBucketConstruct(self, "Demo", bucket_name="demo-bucket")

def test_bucket_created():
    app = cdk.App()
    stack = DummyStack(app, "Dummy")
    template = assertions.Template.from_stack(stack)
    template.resource_count_is("AWS::S3::Bucket", 1)

💻 ビルド&テスト

pytest -v
python -m build

出力例:

tests/test_web_bucket_construct.py::test_bucket_created PASSED
Successfully built my-cdk-lib-0.1.0.tar.gz

📦 PyPI公開(または社内PyPI)

twine upload dist/*

霊夢「これでpip installできるの!?」
魔理沙「そうだ。チーム内で共有したい場合は社内PyPIやCodeArtifactに置くのがいいぜ。」


📄 社内配布例(AWS CodeArtifact)

aws codeartifact login --tool pip --repository my-repo --domain my-domain
pip install my-cdk-lib --extra-index-url https://my-domain-123.d.codeartifact.ap-northeast-1.amazonaws.com/pypi/my-repo/simple/

⚙️ 4. 演習:自分用の CDK Construct パッケージを作る

霊夢「じゃあ実際に“自分のCDKライブラリ”を作ってみよう!」
魔理沙「おう。S3+CloudFrontを自作Constructにして公開してみるぜ。」


📁 演習構成

custom_cdk_lib/
├── custom_cdk_lib/
│   └── static_site_construct.py
├── tests/
│   └── test_static_site_construct.py
└── pyproject.toml

📄 custom_cdk_lib/static_site_construct.py

from aws_cdk import (
    aws_s3 as s3,
    aws_cloudfront as cloudfront,
    aws_cloudfront_origins as origins,
)
from constructs import Construct

class StaticSiteConstruct(Construct):
    """S3 + CloudFrontで静的サイトをホスティングする自作Construct"""
    def __init__(self, scope: Construct, id: str, *, site_name: str):
        super().__init__(scope, id)

        self.bucket = s3.Bucket(
            self, "SiteBucket",
            bucket_name=f"{site_name}-bucket",
            block_public_access=s3.BlockPublicAccess.BLOCK_ALL
        )

        oai = cloudfront.OriginAccessIdentity(self, "OAI")
        self.bucket.grant_read(oai)

        self.distribution = cloudfront.Distribution(
            self, "Distribution",
            default_behavior=cloudfront.BehaviorOptions(
                origin=origins.S3Origin(self.bucket, origin_access_identity=oai),
                viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS
            )
        )

📄 tests/test_static_site_construct.py

import aws_cdk as cdk
import aws_cdk.assertions as assertions
from custom_cdk_lib.static_site_construct import StaticSiteConstruct
from constructs import Construct

class DummyStack(cdk.Stack):
    def __init__(self, scope: Construct, id: str):
        super().__init__(scope, id)
        StaticSiteConstruct(self, "Demo", site_name="testsite")

def test_bucket_and_distribution():
    app = cdk.App()
    stack = DummyStack(app, "TestStack")
    template = assertions.Template.from_stack(stack)
    template.resource_count_is("AWS::S3::Bucket", 1)
    template.resource_count_is("AWS::CloudFront::Distribution", 1)

💻 ビルド・テスト・公開(ローカル用)

pytest -v
python -m build
pip install dist/custom_cdk_lib-0.1.0-py3-none-any.whl

📄 利用例(他プロジェクトで)

from aws_cdk import App, Stack
from custom_cdk_lib.static_site_construct import StaticSiteConstruct
from constructs import Construct

class DemoStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)
        StaticSiteConstruct(self, "MySite", site_name="portfolio")

app = App()
DemoStack(app, "MyDemo")
app.synth()

📘 まとめ

項目 内容
L1/L2/L3の違い CloudFormation直層/抽象化API/便利パターン
自作Construct設計 S3+CloudFrontなどを1クラスにまとめる
ライブラリ化 pyproject.toml + pytest + build + twine
再利用 pip install / CodeArtifactで社内配布
演習 自作CDKパッケージでS3+CloudFront構成を自動生成

霊夢「自作Constructを作ると、会社でもOSSでも使い回せるんだね!」
魔理沙「そうだ。CDKの真骨頂は“インフラ設計そのものをコード資産化できる”ことなんだぜ!」

第13章 大規模組織向けアーキテクチャ設計


🌍 1. 複数アカウント・複数リージョン展開戦略

霊夢「CDKって、1つのAWSアカウントにしかデプロイできないの?」
魔理沙「いや、複数アカウント・リージョンを跨いで構築できるぜ!」


📁 構成イメージ

app.py
stacks/
 ├── network_stack.py
 ├── app_stack.py
 └── logging_stack.py

📄 app.py

from aws_cdk import App, Environment
from stacks.network_stack import NetworkStack
from stacks.app_stack import AppStack
from stacks.logging_stack import LoggingStack

app = App()

# --- 各アカウント/リージョン設定 ---
env_dev = Environment(account="111111111111", region="ap-northeast-1")
env_prod = Environment(account="222222222222", region="us-east-1")

# --- スタック展開 ---
network_dev = NetworkStack(app, "Network-Dev", env=env_dev)
app_dev = AppStack(app, "App-Dev", env=env_dev)
logging_global = LoggingStack(app, "Logging-Global", env=env_prod)

app.synth()

霊夢「これで環境ごとにアカウントもリージョンも変えられるのね!」
魔理沙「うむ。Environmentを明示的に渡せば、CDKが自動で分離デプロイしてくれる。」


💡 クロスアカウント依存関係の注意点

  • CDKは同一リージョン内の依存関係のみ自動解決できる
  • 異なるリージョン/アカウント間の値共有は SSM Parameter StoreS3出力ファイルを経由

📄 例:SSMでクロスリージョン情報共有

from aws_cdk import aws_ssm as ssm

# devアカウント
ssm.StringParameter(self, "VpcIdParam",
    parameter_name="/network/vpc_id",
    string_value=vpc.vpc_id
)

# prodアカウント側
vpc_id = ssm.StringParameter.from_string_parameter_name(
    self, "ImportVpcId", parameter_name="/network/vpc_id"
).string_value

霊夢「なるほど。Parameter Storeを“共有メモリ”として使うわけね。」
魔理沙「そう。組織全体で安全に構成値を渡せるんだぜ。」


🧑‍💼 2. セキュリティ・ガバナンス(IAM、Organization、SSO)

霊夢「大規模組織だと、誰が何を操作できるかが重要よね。」
魔理沙「その通り。CDKでもガバナンス設計をコードで管理できるんだぜ。」


📄 例:IAMロールとポリシーの明示的設計

from aws_cdk import (
    aws_iam as iam,
    Stack,
)
from constructs import Construct

class GovernanceStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- 開発者用ロール ---
        dev_role = iam.Role(
            self, "DeveloperRole",
            assumed_by=iam.AccountRootPrincipal(),
            description="Developer access for sandbox"
        )

        dev_role.add_managed_policy(
            iam.ManagedPolicy.from_aws_managed_policy_name("PowerUserAccess")
        )

        # --- 本番ロール (読み取り専用) ---
        prod_role = iam.Role(
            self, "ProdReadOnly",
            assumed_by=iam.ServicePrincipal("ec2.amazonaws.com")
        )
        prod_role.add_managed_policy(
            iam.ManagedPolicy.from_aws_managed_policy_name("ReadOnlyAccess")
        )

霊夢「CDKでIAMロール設計もできるのね!」
魔理沙「うむ。さらにaws_organizationsaws_ssoモジュールを組み合わせれば、 組織単位でアカウントをプロビジョニングできるんだ。」


📄 Organizationユニット構成例

from aws_cdk import aws_organizations as org

org.Organization(
    self, "MyOrg",
    feature_set=org.FeatureSet.ALL
)

霊夢「これで新しいAWSアカウント作成も自動化できちゃうの!?」
魔理沙「ああ、組織単位で構成管理が可能だぜ。」


💡 SSO連携設計ポイント

  • AWS IAM Identity Center (旧SSO) を活用し、ロール権限をコードで管理
  • cdk diff で権限差分をレビュー(ヒューマンエラー防止)
  • 環境ごとにRole ARNをContextで管理

💰 3. コスト最適化/モニタリング/ログ管理

霊夢「大規模構成だとコストも気になるよね。」
魔理沙「もちろん。CDKでもコストを意識したリソース設計ができる。」


📄 CloudWatch+Cost Anomaly Detection

from aws_cdk import (
    aws_cloudwatch as cw,
    aws_cloudwatch_actions as cw_actions,
    aws_sns as sns,
)
from constructs import Construct

class MonitoringStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        topic = sns.Topic(self, "AlertTopic")
        topic.add_subscription(
            sns.Subscription(email_address="admin@example.com")
        )

        # --- CloudWatchアラーム(S3 APIコスト監視) ---
        metric = cw.Metric(
            namespace="AWS/S3",
            metric_name="NumberOfObjects",
            statistic="Sum",
            period=cdk.Duration.hours(1)
        )

        cw.Alarm(
            self, "S3ObjectAlarm",
            metric=metric,
            threshold=1_000_000,
            evaluation_periods=1,
            comparison_operator=cw.ComparisonOperator.GREATER_THAN_THRESHOLD
        ).add_alarm_action(cw_actions.SnsAction(topic))

💡 コスト最適化チェックリスト

項目 チェック内容
EC2 スポットインスタンス・Savings Plan検討
Lambda メモリ過剰割り当てを防ぐ(実行時間×メモリ)
S3 ライフサイクルルール・Intelligent Tiering
CloudFront キャッシュポリシーで転送コスト削減
DynamoDB オートスケーリング/オンデマンド課金

💾 ログ管理設計例

from aws_cdk import aws_logs as logs

logs.LogGroup(
    self, "AppLogs",
    retention=logs.RetentionDays.THREE_MONTHS,
    removal_policy=None
)

霊夢「ログ保持期間もコードで決められるのね!」
魔理沙「そう。監査対応・コスト両方を満たせる設計だぜ。」


🧱 4. 演習:Well-Architected フレームワークに沿った構成設計

霊夢「最後はAWS公式の“ベストプラクティス”に沿って構成を作るのね!」
魔理沙「そうだ。5つの柱に対応するCDK設計を実践してみよう。」


📘 AWS Well-Architected 5つの柱

内容 CDKでの実践例
運用優秀性 (Operational Excellence) IaCで再現性を担保 cdk synth, cdk diff
セキュリティ (Security) IAM最小権限、Secrets Manager aws_iam, aws_secretsmanager
信頼性 (Reliability) 冗長構成、AutoScaling aws_autoscaling, Multi-AZ
パフォーマンス効率 (Performance) キャッシュ・スケーリング aws_cloudfront, lambda
コスト最適化 (Cost Optimization) サーバレス・スポット活用 aws_lambda, SavingsPlan

📄 well_arch_stack.py(例)

from aws_cdk import (
    Stack,
    aws_lambda as _lambda,
    aws_s3 as s3,
    aws_cloudfront as cloudfront,
    aws_cloudfront_origins as origins,
    aws_iam as iam,
)
from constructs import Construct

class WellArchStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # --- セキュアなS3バケット ---
        bucket = s3.Bucket(
            self, "SecureBucket",
            encryption=s3.BucketEncryption.S3_MANAGED,
            block_public_access=s3.BlockPublicAccess.BLOCK_ALL
        )

        # --- Lambda関数 (最小権限) ---
        fn = _lambda.Function(
            self, "LambdaFn",
            runtime=_lambda.Runtime.PYTHON_3_11,
            handler="handler.main",
            code=_lambda.Code.from_inline("def main(event, ctx): return 'OK'"),
        )
        fn.add_to_role_policy(
            iam.PolicyStatement(actions=["s3:ListBucket"], resources=[bucket.bucket_arn])
        )

        # --- CloudFrontキャッシュで性能最適化 ---
        cloudfront.Distribution(
            self, "AppDist",
            default_behavior=cloudfront.BehaviorOptions(
                origin=origins.S3Origin(bucket),
                viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
            )
        )

霊夢「セキュリティ・信頼性・パフォーマンス・コスト、全部カバーできるのね!」
魔理沙「そうだ。Well-Architectedの5本柱はCDK設計のチェックリストとして超便利なんだぜ。」


📘 まとめ

項目 内容
マルチアカウント構成 Environment で分離、SSMで共有
セキュリティ設計 IAM、Organization、SSOをコード管理
コスト最適化 CloudWatch+S3ライフサイクルで自動化
Well-Architected準拠 5つの柱で構成設計をレビュー

霊夢「これでエンタープライズ級のAWS設計もできるね!」
魔理沙「ああ。CDKを使えば“設計ドキュメント”すらコード化できるんだぜ!」

第14章 将来的な技術動向と CDK 活用展望


🧭 1. CDK v2 の最新動向とアップグレード戦略

霊夢「CDK v2 って、v1 と何が違うの?」
魔理沙「v2 では“統合・安定・整理”が進んでる。もう“モジュール地獄”とはおさらばだぜ。」


💡 v1 → v2 の主な変更点

項目 v1 v2
モジュール構成 @aws-cdk/aws-s3 など個別 aws-cdk-lib に統合
Constructバージョン constructs@3 系など混在 constructs@10 に統一
coreモジュール core Stack, App へ直接統合
API安定性 各L2 APIが頻繁変更 安定LTSベース

📄 v1 コード(旧)

from aws_cdk import core
from aws_cdk import aws_s3 as s3

class OldStack(core.Stack):
    def __init__(self, scope: core.Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)
        s3.Bucket(self, "MyBucket")

📄 v2 コード(新)

from aws_cdk import (
    Stack,
    aws_s3 as s3,
)
from constructs import Construct

class NewStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)
        s3.Bucket(self, "MyBucket")

⚙️ v2 へのアップグレード手順

pip uninstall aws-cdk.core aws-cdk.aws-* -y
pip install aws-cdk-lib constructs
cdk doctor
# synthテスト
cdk synth
# 差分確認
cdk diff
# 実デプロイ
cdk deploy

霊夢「移行って思ったより簡単ね!」
魔理沙「v2は“複雑さの整理”が目的だからな。構成もシンプルで堅牢だぜ。」


🧰 2. 他の IaC ツールとの比較(Terraform/Pulumi)

霊夢「CDKって他のツールより何がいいの?」
魔理沙「TerraformとPulumiと比べながら整理してみよう。」


📊 IaCツール比較表

項目 AWS CDK Terraform Pulumi
言語 TypeScript / Python / Go / Java 独自HCL言語 TypeScript / Python / Go
管理方式 CloudFormationベース 独自Stateファイル(tfstate) 独自バックエンド
冪等性 AWS標準の差分管理 HCL定義に準拠 動的言語で柔軟
拡張性 Constructライブラリ化 Module再利用 SDKで自由度高
マルチクラウド AWS中心 強い(Azure/GCP対応) 強い(API直結)
OSSエコシステム Construct Hub Terraform Registry Pulumi Registry

霊夢「Terraformはマルチクラウド強いけど、AWS中心ならCDKが自然ね。」
魔理沙「そうだな。Pulumiはコードベース柔軟性が高いけど、AWS公式のCDKは安定性が強みだぜ。」


💡 CDK + Terraform の併用例(tf module呼び出し)

from aws_cdk import (
    Stack,
    custom_resources as cr,
)
from constructs import Construct

class HybridStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # Terraform module呼び出し例
        cr.AwsCustomResource(
            self, "TFBridge",
            on_create=cr.AwsSdkCall(
                service="S3",
                action="createBucket",
                parameters={"Bucket": "tf-shared-bucket"},
                physical_resource_id=cr.PhysicalResourceId.of("tf-bucket")
            ),
            policy=cr.AwsCustomResourcePolicy.from_sdk_calls(resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE)
        )

霊夢「TerraformやPulumiのいいとこも組み合わせられるのね!」
魔理沙「そうだ。CDKはAWSの中核に最も近いレイヤーだから、拡張性が高いんだ。」


🔁 3. 継続的な運用・リファクタリング・技術負債回避

霊夢「長期運用してるとCDKプロジェクトも肥大化するよね。」
魔理沙「うむ。IaCも“継続的リファクタリング”が大事だぜ。」


💡 構成の分割戦略(Modular Monolithスタイル)

stacks/
├── network/
│   ├── vpc_stack.py
│   └── sg_stack.py
├── app/
│   ├── api_stack.py
│   └── worker_stack.py
└── shared/
    ├── s3_stack.py
    └── kms_stack.py
# app.py
from aws_cdk import App
from stacks.network.vpc_stack import VpcStack
from stacks.app.api_stack import ApiStack

app = App()
vpc = VpcStack(app, "Vpc")
ApiStack(app, "Api", vpc=vpc)
app.synth()

💡 リファクタリングのテスト支援

import aws_cdk as cdk
import aws_cdk.assertions as assertions
from stacks.app.api_stack import ApiStack

def test_api_resources():
    app = cdk.App()
    stack = ApiStack(app, "Test")
    template = assertions.Template.from_stack(stack)
    template.resource_count_is("AWS::Lambda::Function", 2)

霊夢「CIで構成テストできれば安心だね!」
魔理沙「IaCもコード。テスト、レビュー、リリース管理の文化を取り入れるんだぜ。」


💡 技術負債を避ける3原則

原則 内容
1. Infrastructure as Software IaCをアプリコード同様にリファクタ・Lint
2. Version as Policy Constructのバージョン固定・アップデート管理
3. Reuse over Rebuild L3 Construct再利用で重複削減

🏗️ 4. 小規模から大規模へ、次のステップを見据えて

霊夢「個人プロジェクトでもCDK使える?」
魔理沙「もちろんだ!CDKは“スモールスタート→エンタープライズ化”が得意なんだぜ。」


📘 ステップアップのロードマップ

フェーズ 規模 目標 活用ポイント
Phase 1:個人/小規模 1〜2人 小規模Webサービス構築 S3, Lambda, API Gateway
Phase 2:チーム開発 3〜10人 CI/CD自動化 CodePipeline, CDK Pipelines
Phase 3:組織運用 10人〜 権限/コスト管理 Organization, SSO, Cost Anomaly Detection
Phase 4:グローバル展開 100人〜 マルチリージョン最適化 multi-env, cross-account

📄 例:CDK Pipelines + GitOps連携

from aws_cdk import pipelines as pipelines
from constructs import Construct
from aws_cdk import Stack

class PipelineStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        synth = pipelines.ShellStep(
            "Synth",
            input=pipelines.CodePipelineSource.git_hub(
                "hiromichinomata/aws-cdk-sample", "main"
            ),
            commands=[
                "python -m venv .venv",
                "source .venv/bin/activate",
                "pip install -r requirements.txt",
                "pytest -v",
                "cdk synth"
            ]
        )

        pipeline = pipelines.CodePipeline(self, "Pipeline", synth=synth)

霊夢「コードとパイプラインを同じリポジトリで管理できるんだ!」
魔理沙「そう。これが“GitOps with CDK”。未来の運用はこれが標準になるぜ。」


📘 まとめ

項目 内容
CDK v2の進化 統合・安定・単一ライブラリ構成
他IaC比較 Terraformはマルチクラウド、Pulumiは柔軟、CDKはAWS最適化
運用・負債回避 テスト+リファクタ+Construct再利用
未来展望 GitOps/マルチアカウント/自作ライブラリ運用

霊夢「CDKって、ただのIaCツールじゃなくて“クラウド開発のOS”って感じね。」
魔理沙「まさに!コードでクラウドを設計・構築・進化させる── それがCDKの未来であり、次世代インフラエンジニアの武器なんだぜ。」


🌟 最終章まとめ:

「CDKでインフラを“資産”として育てよう。」
コードで設計し、テストで守り、リファクタで進化させる。 それが、現代クラウドエンジニアリングの理想形だ。


💬 霊夢「次は“CDKライブラリ開発者”としてConstruct Hubデビューしようか!」
💬 魔理沙「いいな。AWSの未来をコードで描こうぜ!」

付録 A〜D


🧰 A. CDK CLI コマンドリファレンス

霊夢「CDKのコマンド、結構いろいろあるよね…」
魔理沙「うむ。IaCの“gitコマンド”みたいなもんだ。 ここで一気にまとめておこう。」


💡 基本構文

cdk [COMMAND] [OPTIONS]

🧱 CDKプロジェクト操作コマンド

# 新規プロジェクト作成
cdk init app --language python

# 必要な依存関係をインストール
pip install -r requirements.txt

🏗️ スタック生成・展開関連

# CloudFormationテンプレート生成(dry-run)
cdk synth

# 実際にデプロイ(初回はbootstrapが必要)
cdk deploy

# 複数スタック指定
cdk deploy NetworkStack AppStack

🧨 スタック削除・差分確認

# 削除
cdk destroy

# 差分確認(本番前チェック)
cdk diff

🧾 環境確認・ドキュメント

# 環境診断
cdk doctor

# 使用リージョン・アカウント確認
cdk context

# コマンド一覧
cdk --help

霊夢cdk diffcdk synth は絶対習慣にしたいね。」
魔理沙「そうだな。デプロイ前のcdk diffは“プルリクレビュー”と同じくらい大事だぜ。」


🐍 B. Python 開発環境ベストプラクティス

霊夢「CDKのPython環境って、どう整えるのがいいの?」
魔理沙「CDKはAWS SDKも絡むから、“隔離された仮想環境”が基本だぜ。」


💡 仮想環境構築手順

python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate
pip install --upgrade pip
pip install aws-cdk-lib constructs

📦 推奨 requirements.txt

aws-cdk-lib==2.150.0
constructs>=10.0.0,<11.0.0
pytest
black
flake8
boto3
mypy

💡 開発補助ツール

ツール 用途 コマンド例
black 自動整形 black .
flake8 スタイルチェック flake8 stacks/
pytest テスト pytest -v
mypy 型チェック mypy stacks/

📄 .pre-commit-config.yaml(自動整形設定)

repos:
  - repo: https://github.com/psf/black
    rev: 23.7.0
    hooks:
      - id: black
  - repo: https://github.com/pycqa/flake8
    rev: 6.1.0
    hooks:
      - id: flake8
pip install pre-commit
pre-commit install

霊夢「コード整形やテストを自動で走らせるのね!」
魔理沙「CDKの品質は“インフラの安全性”に直結する。 Pythonツールで守るのがベストプラクティスだぜ。」


🧩 C. トラブルシュート用チェックリスト

霊夢「CDKデプロイでエラー出た時って、どこを見ればいいの?」
魔理沙「よくあるトラブルを一覧でまとめておいたぞ。」


⚠️ デプロイ失敗時のチェックリスト

症状 確認ポイント 対処法
CREATE_FAILED IAMポリシー不足 権限拡張 or --role-arn指定
ROLLBACK_COMPLETE 依存リソース削除済み? aws cloudformation delete-stack
AccessDenied CLI認証ミス aws configure で再設定
Too many resources スタック肥大化 NestedStack に分割
Timeout NAT/ALB構成誤り SecurityGroup確認
Version mismatch CDK v2未統一 `pip freeze grep cdk`
context.json 不整合 古い値キャッシュ cdk context --clear

💡 デバッグコマンド

cdk deploy --verbose
cdk synth --trace

📄 CloudFormationイベントの確認

aws cloudformation describe-stack-events --stack-name MyAppStack

📄 CDKリソースの調査補助スクリプト

import boto3

cf = boto3.client("cloudformation")
for stack in cf.list_stacks(StackStatusFilter=["CREATE_FAILED"])["StackSummaries"]:
    print(stack["StackName"], stack["StackStatus"])

霊夢「CDKエラーって見た目怖いけど、原因はだいたいパターン化されてるのね。」
魔理沙「そう。焦らずcdk diffCloudFormationイベントを見れば大体わかるぜ。」


🗂️ D. リポジトリ構成・サンプルコードへのアクセス

霊夢「本書のサンプルコードはどこにあるの?」
魔理沙「以下の構成をベースに、GitHubでも同様に提供してるぜ。」


📁 推奨ディレクトリ構成

aws-cdk-hands-on/
├── README.md
├── app.py
├── requirements.txt
├── cdk.json
├── stacks/
│   ├── network_stack.py
│   ├── compute_stack.py
│   ├── storage_stack.py
│   ├── app_stack.py
│   └── pipeline_stack.py
├── constructs/
│   ├── web_bucket_construct.py
│   └── lambda_api_construct.py
├── tests/
│   ├── test_app_stack.py
│   ├── test_pipeline_stack.py
│   └── snapshot/
├── .github/
│   └── workflows/
│       └── deploy.yml
└── .pre-commit-config.yaml

📄 .github/workflows/deploy.yml(GitHub Actions自動デプロイ)

name: CDK Deploy

on:
  push:
    branches: [ "main" ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install dependencies
        run: |
          python -m venv .venv
          source .venv/bin/activate
          pip install -r requirements.txt
      - name: CDK Deploy
        run: |
          source .venv/bin/activate
          cdk synth
          cdk deploy --require-approval never

💡 GitHubリポジトリ例(サンプル)(注: まだないです)

git clone https://github.com/hiromichinomata/aws-cdk-python-hands-on.git
cd aws-cdk-python-hands-on
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cdk synth

霊夢「これで環境再現も一瞬ね!」
魔理沙「そう。CDKの世界では“リポジトリそのものが設計書”なんだぜ。」


🧾 付録まとめ

付録 内容
A. CLIリファレンス cdk synth, cdk diff, cdk deploy の使い方まとめ
B. Python環境ベストプラクティス venv, Linter, pre-commit設定例
C. トラブルシュート よくあるエラーと確認ポイント
D. リポジトリ構成 実践的なスタック・テスト・CI構成例

💬 霊夢「これでCDKの全章コンプリートだね!」
💬 魔理沙「おう!IaCを“読む・書く・テストする・運用する”力が全部身についたぜ!」


🎉 Final Message

AWS CDKは「クラウドのためのソフトウェア工学」 。 あなたのインフラ設計が“コード”として語り継がれる時代が来た。