自由帳

とりとめのない学習メモです。主に Web サービスのシステム基盤や運用に関することを書いています

AWS CDK in TypeScript で VPC を作る

はじめに

https://dshimizu.hatenablog.com/entry/2024/01/21/000000AWS CDK を触ってみました。 しばらく時間が空いてしまったので、復習もかねつつ、今回は AWS CDK in TypeScript で VPC の作成をやってみました。

環境

Debian 12 の環境を利用します。

% uname -svor
Linux 6.1.0-12-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.52-1 (2023-09-07) GNU/Linux

Node.js は 18.14.0 を使ってます。

% node -v
v18.14.0

npx は 9.3.1 を使ってます。

% npx -v
9.3.1

TypeScript は 4.9.5 を使ってます。

% npx tsc -v
Version 4.9.5
% npx cdk --version
2.212.0 (build 9f2b78c )

AWS CLI の設定では、 sandbox という名前の認証プロファイルを作成して、このプロファイルを利用することとします。

  • ~/aws/credentials
[sandbox]
aws_access_key_id=********************
aws_secret_access_key=****************************************
  • ~/aws/config
[default]
region = ap-northeast-1
output = json

[profile sandbox]
region = ap-northeast-1
output = json
source_profile = default
role_arn = arn:aws:iam::**********:role/AdminRole

AWS CDKアプリケーションを作る

AWS CDKアプリケーションを作ってみます。

AWS CDKアプリケーションのプロジェクトの作成

AWS CDKアプリケーションのプロジェクトを作成してみます。

プロジェクト用のディレクトリを作成して、移動します。ここではテスト用に cdk-sandbox-vpc というディレクトリにしています。

% mkdir cdk-sandbox-vpc && cd cdk-sandbox-vpc

cdk init では app, lib, sample-app の 3 つのテンプレートが選択できるようです。通常のプロジェクトとして開始する場合は app を利用するようです。省略した場合は app がデフォルトになるようです。

lib は CDK ライブラリを作成する場合に利用し、 sample-app はその名の通りでサンプルアプリケーションが動くCDKコードが生成されるようです。

% npx cdk init
Available templates:
* app: Template for a CDK Application
   └─ cdk init app --language=[csharp|fsharp|go|java|javascript|python|typescript]
* lib: Template for a CDK Construct Library
   └─ cdk init lib --language=typescript
* sample-app: Example CDK Application with some constructs
   └─ cdk init sample-app --language=[csharp|fsharp|go|java|javascript|python|typescript]

言語は Typescript にします。

% cdk init --language typescript
Applying project template app for typescript
# Welcome to your CDK TypeScript project

This is a blank project for CDK development with TypeScript.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

* `npm run build`   compile typescript to js
* `npm run watch`   watch for changes and compile
* `npm run test`    perform the jest unit tests
* `npx cdk deploy`  deploy this stack to your default AWS account/region
* `npx cdk diff`    compare deployed stack with current state
* `npx cdk synth`   emits the synthesized CloudFormation template

Executing npm install...
✅ All done!

以下のようなファイルが生成されます。

% tree -L 2
.
├── README.md
├── bin
│   └── cdk-sandbox-vpc.ts
├── cdk.json
├── jest.config.js
├── lib
│   └── cdk-sandbox-stack-vpc.ts
├── node_modules
│   ├── @ampproject
 :
│   ├── yn
│   └── yocto-queue
├── package-lock.json
├── package.json
├── test
│   └── cdk-sandbox-vpc.test.ts
└── tsconfig.json

221 directories, 9 files

bin/cdk-sandbox-vpc.ts というファイルが生成されており、これが CDK アプリケーションのエントリーポイント(アプリケーションの中で一番最初に呼び出される部分)となります。

../lib/cdk-sandbox-vpc-stack から CdkSandboxVpcStack というクラスを読み込んで、インスタンスを生成しているようです。

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { CdkSandboxVpcStack } from '../lib/cdk-sandbox-vpc-stack';

const app = new cdk.App();
new CdkSandboxVpcStack(app, 'CdkSandboxVpcStack', {
  /* If you don't specify 'env', this stack will be environment-agnostic.
   * Account/Region-dependent features and context lookups will not work,
   * but a single synthesized template can be deployed anywhere. */

  /* Uncomment the next line to specialize this stack for the AWS Account
   * and Region that are implied by the current CLI configuration. */
  // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },

  /* Uncomment the next line if you know exactly what Account and Region you
   * want to deploy the stack to. */
  // env: { account: '123456789012', region: 'us-east-1' },

  /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
});

bin/cdk-sandbox-vpc.ts で読み込まれている lib/cdk-sandbox-vpc-stack.ts というファイルが生成されているので見てみます。 SQS を作成するための定義のサンプルがコメントアウト状態であるだけでした。

lib/ 配下にリソースを作成・管理するファイルを生成していくのがベースとなるように思いました。

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
// import * as sqs from 'aws-cdk-lib/aws-sqs';

export class CdkSandboxVpcStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here

    // example resource
    // const queue = new sqs.Queue(this, 'CdkSandboxVpcQueue', {
    //   visibilityTimeout: cdk.Duration.seconds(300)
    // });
  }
}

AWS CDK で VPC 作成

AWS CDK で VPC を作成してみます。以下のワークショップの内容をベースに作成してみます。

ちなみにAWS CDK の VPC 関連のドキュメントは下記のようです。

lib/cdk-sandbox-vpc-stack.ts を以下のように変更して VPC を作成してみます。

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';

import * as ec2 from 'aws-cdk-lib/aws-ec2';

export class CdkSandboxVpcStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

  // L2 コンストラクト
    const vpc = new ec2.Vpc(this, 'SandboxVPC', {
        ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
    })
  }
}

この状態でデプロイしてみます。

% npx cdk diff --profile sandbox
Stack CdkSandboxVpcStack
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)
IAM Statement Changes
┌───┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬────────────────────────────────────────────────────────────────┬───────────┐
│   │ Resource                                                                                                             │ Effect │ Action                                                                                                                       │ Principal                                                      │ Condition │
├───┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┼───────────┤
│ + │ ${Custom::VpcRestrictDefaultSGCustomResourceProvider/Role.Arn}                                                       │ Allow  │ sts:AssumeRole                                                                                                               │ Service:lambda.amazonaws.com                                   │           │
├───┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┼───────────┤
│ + │ arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:security-group/${SandboxVPC4161466E.DefaultSecurityGroup} │ Allow  │ ec2:AuthorizeSecurityGroupEgress                                                                                             │ AWS:${Custom::VpcRestrictDefaultSGCustomResourceProvider/Role} │           │
│   │                                                                                                                      │        │ ec2:AuthorizeSecurityGroupIngress                                                                                            │                                                                │           │
│   │                                                                                                                      │        │ ec2:RevokeSecurityGroupEgress                                                                                                │                                                                │           │
│   │                                                                                                                      │        │ ec2:RevokeSecurityGroupIngress                                                                                               │                                                                │           │
└───┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                                                   │ Managed Policy ARN                                                                           │
├───┼────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${Custom::VpcRestrictDefaultSGCustomResourceProvider/Role} │ {"Fn::Sub":"arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"} │
└───┴────────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Parameters
[+] Parameter BootstrapVersion 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]"}

Conditions
[+] Condition CDKMetadata/Condition CDKMetadataAvailable: {"Fn::Or":[{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"af-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ca-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-northwest-1"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-3"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"il-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"sa-east-1"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-2"]}]}]}

Resources
[+] AWS::EC2::VPC SandboxVPC SandboxVPC4161466E
[+] AWS::EC2::Subnet SandboxVPC/PublicSubnet1/Subnet SandboxVPCPublicSubnet1SubnetB9280C70
[+] AWS::EC2::RouteTable SandboxVPC/PublicSubnet1/RouteTable SandboxVPCPublicSubnet1RouteTable35C17C45
[+] AWS::EC2::SubnetRouteTableAssociation SandboxVPC/PublicSubnet1/RouteTableAssociation SandboxVPCPublicSubnet1RouteTableAssociationD8A992A4
[+] AWS::EC2::Route SandboxVPC/PublicSubnet1/DefaultRoute SandboxVPCPublicSubnet1DefaultRouteCD80277D
[+] AWS::EC2::EIP SandboxVPC/PublicSubnet1/EIP SandboxVPCPublicSubnet1EIPBE92916E
[+] AWS::EC2::NatGateway SandboxVPC/PublicSubnet1/NATGateway SandboxVPCPublicSubnet1NATGatewayD0904435
[+] AWS::EC2::Subnet SandboxVPC/PublicSubnet2/Subnet SandboxVPCPublicSubnet2SubnetC4239C5C
[+] AWS::EC2::RouteTable SandboxVPC/PublicSubnet2/RouteTable SandboxVPCPublicSubnet2RouteTable238E5110
[+] AWS::EC2::SubnetRouteTableAssociation SandboxVPC/PublicSubnet2/RouteTableAssociation SandboxVPCPublicSubnet2RouteTableAssociation3B41EDF2
[+] AWS::EC2::Route SandboxVPC/PublicSubnet2/DefaultRoute SandboxVPCPublicSubnet2DefaultRoute4EAE4B03
[+] AWS::EC2::EIP SandboxVPC/PublicSubnet2/EIP SandboxVPCPublicSubnet2EIPC06FF94C
[+] AWS::EC2::NatGateway SandboxVPC/PublicSubnet2/NATGateway SandboxVPCPublicSubnet2NATGateway1AF48573
[+] AWS::EC2::Subnet SandboxVPC/PrivateSubnet1/Subnet SandboxVPCPrivateSubnet1Subnet7AF1D8EE
[+] AWS::EC2::RouteTable SandboxVPC/PrivateSubnet1/RouteTable SandboxVPCPrivateSubnet1RouteTableC6704B6B
[+] AWS::EC2::SubnetRouteTableAssociation SandboxVPC/PrivateSubnet1/RouteTableAssociation SandboxVPCPrivateSubnet1RouteTableAssociationA661342E
[+] AWS::EC2::Route SandboxVPC/PrivateSubnet1/DefaultRoute SandboxVPCPrivateSubnet1DefaultRouteCE236D1A
[+] AWS::EC2::Subnet SandboxVPC/PrivateSubnet2/Subnet SandboxVPCPrivateSubnet2Subnet993EE236
[+] AWS::EC2::RouteTable SandboxVPC/PrivateSubnet2/RouteTable SandboxVPCPrivateSubnet2RouteTableAA1701A5
[+] AWS::EC2::SubnetRouteTableAssociation SandboxVPC/PrivateSubnet2/RouteTableAssociation SandboxVPCPrivateSubnet2RouteTableAssociationE49D912F
[+] AWS::EC2::Route SandboxVPC/PrivateSubnet2/DefaultRoute SandboxVPCPrivateSubnet2DefaultRoute1CA1A3C8
[+] AWS::EC2::InternetGateway SandboxVPC/IGW SandboxVPCIGW80B9AB55
[+] AWS::EC2::VPCGatewayAttachment SandboxVPC/VPCGW SandboxVPCVPCGWC3325084
[+] Custom::VpcRestrictDefaultSG SandboxVPC/RestrictDefaultSecurityGroupCustomResource SandboxVPCRestrictDefaultSecurityGroupCustomResourceE934C5D3
[+] AWS::IAM::Role Custom::VpcRestrictDefaultSGCustomResourceProvider/Role CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0
[+] AWS::Lambda::Function Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E

Other Changes
[+] Unknown Rules: {"CheckBootstrapVersion":{"Assertions":[{"Assert":{"Fn::Not":[{"Fn::Contains":[["1","2","3","4","5"],{"Ref":"BootstrapVersion"}]}]},"AssertDescription":"CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."}]}}


✨  Number of stacks with differences: 1

CdkSandboxVpcStack という名前のCloudFormationスタックが作成され、Resources の出力にある通り、これだけでVPC, プライベートサブネット * 2、パブリックサブネット * 2, NAT Gateway * 2 ... と必要そうなリソースが作成されました。

指定してないパラメーターはデフォルト値が定められているようで、この点が L1 コンストラクトを使う場合と比べてより抽象化されており、コードの記述量も減っていると思います。

ただ NAT Gateway とかはいらないのでもう少しカスタマイズします。

削除

一旦全てのリソースを削除します。CdkSandboxVpcStack という名前のCloudFormationスタックが削除されます。

% npx cdk destroy --profile sandbox
Are you sure you want to delete: CdkSandboxVpcStack (y/n)? y
CdkSandboxVpcStack: destroying... [1/1]

 ✅  CdkSandboxVpcStack: destroyed

VPC 再作成

lib/cdk-sandbox-vpc-stack.ts を以下のように変更します。

NAT Gateway は無しにして、AZは3つ、IPv6 を有効にします。 AWS CDK v2.121.0 で dual stack がサポートされ、 ipProtocol: ec2.IpProtocol.DUAL_STACK, と記述するだけで有効化できるようです。

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';

export class CdkSandboxVpcStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const vpc = new ec2.Vpc(this, 'SandboxVPC', {
        ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),

        ipProtocol: ec2.IpProtocol.DUAL_STACK,
        //ipv6Addresses: ec2.Ipv6Addresses.amazonProvided(),

        natGateways: 0,
        maxAzs: 3,
        subnetConfiguration: [
            {
                name: 'PublicSubnet',
                cidrMask: 24,
                subnetType: ec2.SubnetType.PUBLIC,
            },
            {
                name: 'PrivateSubnet',
                cidrMask: 24,
                subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
            },
        ],
    enableDnsHostnames: true,
    enableDnsSupport: true
    });
  }
}

再度デプロイしてみます。

% npx cdk deploy --profile sandbox

✨  Synthesis time: 7.79s

This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬────────────────────────────────────────────────────────────────┬───────────┐
│   │ Resource                                                                                                             │ Effect │ Action                                                                                                                       │ Principal                                                      │ Condition │
├───┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┼───────────┤
│ + │ ${Custom::VpcRestrictDefaultSGCustomResourceProvider/Role.Arn}                                                       │ Allow  │ sts:AssumeRole                                                                                                               │ Service:lambda.amazonaws.com                                   │           │
├───┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┼───────────┤
│ + │ arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:security-group/${SandboxVPC4161466E.DefaultSecurityGroup} │ Allow  │ ec2:AuthorizeSecurityGroupEgress                                                                                             │ AWS:${Custom::VpcRestrictDefaultSGCustomResourceProvider/Role} │           │
│   │                                                                                                                      │        │ ec2:AuthorizeSecurityGroupIngress                                                                                            │                                                                │           │
│   │                                                                                                                      │        │ ec2:RevokeSecurityGroupEgress                                                                                                │                                                                │           │
│   │                                                                                                                      │        │ ec2:RevokeSecurityGroupIngress                                                                                               │                                                                │           │
└───┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                                                   │ Managed Policy ARN                                                                           │
├───┼────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${Custom::VpcRestrictDefaultSGCustomResourceProvider/Role} │ {"Fn::Sub":"arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"} │
└───┴────────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)? y
CdkSandboxVpcStack: deploying... [1/1]
CdkSandboxVpcStack: creating CloudFormation changeset...

 ✅  CdkSandboxVpcStack

✨  Deployment time: 77.07s

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:************:stack/CdkSandboxVpcStack/36198690-d23b-11ee-af56-0a4a4aa379bb

✨  Total time: 84.86s

再度、CdkSandboxVpcStack という名前のCloudFormationスタックが作成されました。 合わせて、IPv6 が有効な Public Subnet, Private Subnet を3つずつ含む VPC リソースを作成できました。

まとめ

AWS CDK で VPC を作ってみました。

参考