技術検証：AWS CDKを使ったVPC、サブネット、およびルートテーブルの作成方法

## はじめに

前回の記事に引き続き、AWS CDKの理解を深めるために、ハンズオン形式で技術検証を行います。

:::note warn
***今回の内容は、以前CloudFormationやTerraformで作成したコードをAWS CDKに変換して試すという形式になっています。***
:::

https://qiita.com/free-honda/items/fb49c3000aaa79639265

***AWS CDKを学び始めたばかりの初心者向けの内容ですので、その点をご了承いただければ幸いです。***

## 前回の記事の続きです

自分用の備忘録として、AWS CDKの基本的な構造、機能、メリットを簡単にまとめた内容になっています。***ぜひ参考にしてください。***

https://qiita.com/free-honda/items/f7ca96959a2ed33c2062

***また、知り合いが書いたとても分かりやすい神記事もありますので、ぜひこちらもご覧ください！***

https://qiita.com/hosomatu/items/e14d4de0888fe91ebd9a

## 前提準備

この記事では、AWS CDKプロジェクトを実行するためにAWS CLIコマンドを使用してインフラをデプロイしていきます。

https://qiita.com/free-honda/items/389059edbdb5e7adcd0b

https://qiita.com/free-honda/items/866986d98fdb2fc1a56e

:::note info
***まだAWS CLIの導入や初期セットアップが終わっていない方は、上記の記事をご参考ください。***
:::


## 必要な環境を準備する

AWS CDKはNode.jsを使用します。まずは以下のコマンドを実行してCDK CLIをインストールしてください。

```bash
npm install -g aws-cdk
```

また、今回はPython 3.7以上の環境を使用してインフラを構築します。仮想環境を作成し、必要なモジュールをインストールしてください。

```bash
python3 -m venv .env
source .env/bin/activate  # Windows の場合: .env\\Scripts\\activate
```

以下は、M1 MacBookでセットアップを行った際の画面です。

![スクリーンショット 2024-12-28 18.11.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/c4553c2e-9ac5-4c23-24a5-553931370fa3.png)

続いて、以下のコマンドを実行して、AWS CDK v2とconstructsモジュールをインストールします。

```bash
pip install aws-cdk.core aws-cdk.aws-ec2
pip install aws-cdk-lib constructs
```

## プロジェクトの作成とファイル準備

新しいAWS CDKプロジェクトを初期化するために、以下のコマンドを実行してください。

```bash
mkdir my-cdk-project
cd my-cdk-project
cdk init app --language python
```

次に、```lib/```フォルダを作成し、その中に```vpc_stack.py```ファイルを作成します。

```bash
mkdir lib
touch lib/vpc_stack.py
```

## ```vpc_stack.py```ファイルの作成

```vpc_stack.py``` ファイルをエディタで開き、以下のコードを記述してください。

```lib/vpc_stack.py
from aws_cdk import Stack, CfnOutput
from aws_cdk import aws_ec2 as ec2

class VpcStack(Stack):
    def __init__(self, scope: Stack, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # Create VPC
        vpc = ec2.Vpc(
            self, "ProjectVpc",
            ip_addresses=ec2.IpAddresses.cidr("10.0.0.0/16"),
            max_azs=2,
            subnet_configuration=[
                ec2.SubnetConfiguration(
                    name="PublicSubnet",
                    subnet_type=ec2.SubnetType.PUBLIC,
                    cidr_mask=20
                ),
                ec2.SubnetConfiguration(
                    name="PrivateSubnet",
                    subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS,  # PRIVATE_WITH_EGRESS を使用
                    cidr_mask=20
                )
            ]
        )

        # Output VPC ID
        CfnOutput(self, "VpcId", value=vpc.vpc_id)
        
        # Associate Public Route Table with Subnets (without IGW)
        public_route_table = ec2.CfnRouteTable(
            self, "PublicRouteTable",
            vpc_id=vpc.vpc_id,
            tags=[{
                "key": "Name",
                "value": "プロジェクト-rtb-public"
            }]
        )

        for index, subnet in enumerate(vpc.public_subnets):
            ec2.CfnSubnetRouteTableAssociation(
                self, f"PublicSubnetAssociation{index}",
                subnet_id=subnet.subnet_id,
                route_table_id=public_route_table.ref
            )

        # Create Private Route Table (also without IGW)
        private_route_table = ec2.CfnRouteTable(
            self, "PrivateRouteTable",
            vpc_id=vpc.vpc_id,
            tags=[{
                "key": "Name",
                "value": "プロジェクト-rtb-private1-ap-northeast-1a"
            }]
        )

        for index, subnet in enumerate(vpc.private_subnets):
            ec2.CfnSubnetRouteTableAssociation(
                self, f"PrivateSubnetAssociation{index}",
                subnet_id=subnet.subnet_id,
                route_table_id=private_route_table.ref
            )

        # Output for Route Tables (Debugging Purpose)
        CfnOutput(self, "PublicRouteTableId", value=public_route_table.ref)
        CfnOutput(self, "PrivateRouteTableId", value=private_route_table.ref)
```

***このコードは AWS CDK を使用して、VPC、サブネット、ルートテーブルを設定しています。***

## ```app.py``` ファイルの作成

プロジェクトのルートにある ```app.py``` をエディタで開き、以下の内容を記述してください。

```app.py 
from aws_cdk import App
from lib.vpc_stack import VpcStack  # lib/vpc_stack.py から VpcStack をインポート

app = App()
VpcStack(app, "VpcStack")
app.synth()
```

## ```cdk synth``` コマンドを実行してみた

プロジェクトの最終的なディレクトリ構造は次のようになります。

```css
my-cdk-project/
├── app.py                 # エントリーポイント
├── cdk.json               # CDK 設定ファイル
├── requirements.txt       # 必要なパッケージ
├── lib/                   # スタック定義を格納するフォルダ
│   └── vpc_stack.py       # VPC スタックのコード
・・・・・
```

以下のコマンドを実行して、テンプレートが正しく生成されるかを確認してください。

```bash
cdk synth
```

もし以下のようなエラーが発生した場合は、```cdk bootstrap``` コマンドを実行する必要があります。

:::note alert
***このエラーは、AWS CDK のブートストラップリソースがまだ作成されていないことを示しています。***
:::

```bash
Parameters:
  BootstrapVersion:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /cdk-bootstrap/hnb659fds/version
    Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]
```

このエラーが発生した場合、以下のコマンドを実行してください。

```bash
cdk bootstrap
```

***AWS CDK では、一部のリソース（例: IAM ロールや S3 バケットなど）をデプロイする前提条件として、環境にブートストラップリソースを作成する必要があります。***

## ```cdk deploy``` コマンドを実行してみた

テンプレートが正しく生成されたら、以下のコマンドを実行してインフラを AWS にデプロイします。

```bash
cdk deploy
```

実行例は以下の通りです。

```bash
(.env) ➜  my-cdk-project git:(honda-branch) ✗ cdk deploy                                        

✨  Synthesis time: 7.95s

current credentials could not be used to assume 'arn:aws:iam::xxx:role/cdk-hnb659fds-deploy-role-xxx-ap-northeast-1', but are for the right account. Proceeding anyway.

（・・・省略）

 ✅  VpcStack

✨  Deployment time: 162.06s

Outputs:
VpcStack.PrivateRouteTableId = rtb-037c1b43e4fbf90d6
VpcStack.PublicRouteTableId = rtb-0b4fc7c2bb4074c57
VpcStack.VpcId = vpc-02fe119e49ac8cec8
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:xxx:stack/VpcStack/5b2c8f50-c505-11ef-bd14-0aa6878f9a5d

✨  Total time: 170.02s
```

## AWS マネジメントコンソールでの確認

AWS マネジメントコンソール上でも、Python を用いて作成したインフラ構築が自動的に完了していることを確認できました。

![スクリーンショット 2024-12-28 19.42.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/99db1915-9c41-8387-f87c-c7d0c872abac.png)

***正直かなり苦戦しましたが、無事に検証を終えることができてよかったです（笑）。***

## まとめ

ここまで読んでいただき、ありがとうございました！

今回の挑戦では、AWS CDK を用いてインフラを構築するという初めての体験をしました。

:::note warn
***正直、手こずる場面も多くありましたが、何とか検証を終えることができてホッとしています。***
:::

***基本的な内容ではありますが、誰かの技術的な助けとなれば幸いです！***

## 参考記事

https://qiita.com/Brutus/items/6c8d9bfaab7af53d154a

https://www.cloudsolution.tokai-com.co.jp/white-paper/2023/0720-403.html

https://zenn.dev/issy/articles/zenn-cdk-overview

https://qiita.com/hosomatu/items/e14d4de0888fe91ebd9a