実践!AWS CDK #5 サブネット

題字・息子たち
2021.05.27

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

VPC に続くリソース 2 つ目、サブネット の構築です。

前回の記事はこちら。

AWS 構成図

今回作るものはこれ。

1

以前作成した VPC 上で 2 つの Availability Zone(AZ)に 3 つずつサブネットを構築します。一般的な 三層アーキテクチャ ですね。プレゼンテーション層を Public、アプリケーション層とデータ層を Private にします。

設計

各サブネットのプロパティはこちら。

リソース名 CIDR ブロック Availability Zone
devio-stg-subnet-public-1a 10.0.11.0/24 ap-northeast-1a
devio-stg-subnet-public-1c 10.0.12.0/24 ap-northeast-1c
devio-stg-subnet-app-1a 10.0.21.0/24 ap-northeast-1a
devio-stg-subnet-app-1c 10.0.22.0/24 ap-northeast-1c
devio-stg-subnet-db-1a 10.0.31.0/24 ap-northeast-1a
devio-stg-subnet-db-1c 10.0.32.0/24 ap-northeast-1c

リソース名は私の好みです。プレゼンテーション層のサブネットにはわかりやすく public という名称を付けています(presentation <- 長い、pre <- 別の意味になりそう)。アプリケーション層が app、データ層(データベース層)が db です。

CIDR ブロックも同様(私の好み)。各層の上から順に十の位が 1, 2, 3、一の位を自然数(1 開始)にすることで各階層のサブネット数をわかりやすくします。

実装

前回の状態はこちらです。

lib/devio-stack.ts

import * as cdk from '@aws-cdk/core';
import { CfnVPC } from '@aws-cdk/aws-ec2';

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

    const systemName = this.node.tryGetContext('systemName');
    const envType = this.node.tryGetContext('envType');

    new CfnVPC(this, 'Vpc', {
      cidrBlock: '10.0.0.0/16',
      tags: [{ key: 'Name', value: `${systemName}-${envType}-vpc` }]
    });
  }
}

VPC を生成するコードの下にサブネットのコードを書いていきます。

サブネットの作成には VPC の ID が必要なため、まずは前回作成した VPC を保持する変数 vpc を用意します。

const vpc = new CfnVPC(this, 'Vpc', {
  cidrBlock: '10.0.0.0/16',
  tags: [{ key: 'Name', value: `${systemName}-${envType}-vpc` }]
});

そして以下がサブネット生成のコードです。
いずれ必要になるのでこちらは最初から変数に代入しています。

const subnetPublic1a = new CfnSubnet(this, 'SubnetPublic1a', {
  cidrBlock: '10.0.11.0/24',
  vpcId: vpc.ref,
  availabilityZone: 'ap-northeast-1a',
  tags: [{ key: 'Name', value: `${systemName}-${envType}-subnet-public-1a` }]
})

必要なプロパティを指定してサブネットを作成します。VPC の ID は CfnVPC クラスが持つ ref プロパティで取得することができます。少し分かりづらいですが、この ref は CFn 各リソースの Return values の Ref を表します。VPC の場合 この値 は VPC の ID を返す仕様となっているので、この表記で目的の VPC ID が取得できるというわけです。

Return values は各リソースによって返却するものが異なるので、利用する場合は CFn のリファレンスをしっかり確認しましょう。(ハマリポイントです)

似たようなのをあと 5 つ作ります。
完成形がこちら。(変更箇所をハイライト)

lib/devio-stack.ts

import * as cdk from '@aws-cdk/core';
import { CfnVPC, CfnSubnet } from '@aws-cdk/aws-ec2';

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

    const systemName = this.node.tryGetContext('systemName');
    const envType = this.node.tryGetContext('envType');

    const vpc = new CfnVPC(this, 'Vpc', {
      cidrBlock: '10.0.0.0/16',
      tags: [{ key: 'Name', value: `${systemName}-${envType}-vpc` }]
    });

    const subnetPublic1a = new CfnSubnet(this, 'SubnetPublic1a', {
      cidrBlock: '10.0.11.0/24',
      vpcId: vpc.ref,
      availabilityZone: 'ap-northeast-1a',
      tags: [{ key: 'Name', value: `${systemName}-${envType}-subnet-public-1a` }]
    })
    const subnetPublic1c = new CfnSubnet(this, 'SubnetPublic1c', {
      cidrBlock: '10.0.12.0/24',
      vpcId: vpc.ref,
      availabilityZone: 'ap-northeast-1c',
      tags: [{ key: 'Name', value: `${systemName}-${envType}-subnet-public-1c` }]
    })
    const subnetApp1a = new CfnSubnet(this, 'SubnetApp1a', {
      cidrBlock: '10.0.21.0/24',
      vpcId: vpc.ref,
      availabilityZone: 'ap-northeast-1a',
      tags: [{ key: 'Name', value: `${systemName}-${envType}-subnet-app-1a` }]
    })
    const subnetApp1c = new CfnSubnet(this, 'SubnetApp1c', {
      cidrBlock: '10.0.22.0/24',
      vpcId: vpc.ref,
      availabilityZone: 'ap-northeast-1c',
      tags: [{ key: 'Name', value: `${systemName}-${envType}-subnet-app-1c` }]
    })
    const subnetDb1a = new CfnSubnet(this, 'SubnetDb1a', {
      cidrBlock: '10.0.31.0/24',
      vpcId: vpc.ref,
      availabilityZone: 'ap-northeast-1a',
      tags: [{ key: 'Name', value: `${systemName}-${envType}-subnet-db-1a` }]
    })
    const subnetDb1c = new CfnSubnet(this, 'SubnetDb1c', {
      cidrBlock: '10.0.32.0/24',
      vpcId: vpc.ref,
      availabilityZone: 'ap-northeast-1c',
      tags: [{ key: 'Name', value: `${systemName}-${envType}-subnet-db-1c` }]
    })
  }
}

長いですねー。キライです。

テスト

テストコードはこちら。

test/devio.test.ts

test('Subnet', () => {
  const app = new cdk.App();
  const stack = new Devio.DevioStack(app, 'DevioStack');

  expect(stack).to(countResources('AWS::EC2::Subnet', 6));
  expect(stack).to(haveResource('AWS::EC2::Subnet', {
    CidrBlock: '10.0.11.0/24',
    AvailabilityZone: 'ap-northeast-1a',
    Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-subnet-public-1a' }]
  }));
  expect(stack).to(haveResource('AWS::EC2::Subnet', {
    CidrBlock: '10.0.12.0/24',
    AvailabilityZone: 'ap-northeast-1c',
    Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-subnet-public-1c' }]
  }));
  expect(stack).to(haveResource('AWS::EC2::Subnet', {
    CidrBlock: '10.0.21.0/24',
    AvailabilityZone: 'ap-northeast-1a',
    Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-subnet-app-1a' }]
  }));
  expect(stack).to(haveResource('AWS::EC2::Subnet', {
    CidrBlock: '10.0.22.0/24',
    AvailabilityZone: 'ap-northeast-1c',
    Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-subnet-app-1c' }]
  }));
  expect(stack).to(haveResource('AWS::EC2::Subnet', {
    CidrBlock: '10.0.31.0/24',
    AvailabilityZone: 'ap-northeast-1a',
    Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-subnet-db-1a' }]
  }));
  expect(stack).to(haveResource('AWS::EC2::Subnet', {
    CidrBlock: '10.0.32.0/24',
    AvailabilityZone: 'ap-northeast-1c',
    Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-subnet-db-1c' }]
  }));
});

以下を確認しています。

  • サブネットリソースが 6 つあること
  • 各サブネットのプロパティが正しいこと

こういう 値が似ているリソース のテストは本当に助かりますね。リソース生成前のミスを検出する確率が格段に上がります。最高。

変更確認

cdk diff コマンドを利用すれば、既にデプロイされているバージョンと現在のバージョンを比較することができます。

VPC がデプロイ済みの状態でコマンドを実行した場合は次のようになります。

$ cdk diff

Stack DevioStack
Resources
[+] AWS::EC2::Subnet SubnetPublic1a SubnetPublic1a
[+] AWS::EC2::Subnet SubnetPublic1c SubnetPublic1c
[+] AWS::EC2::Subnet SubnetApp1a SubnetApp1a
[+] AWS::EC2::Subnet SubnetApp1c SubnetApp1c
[+] AWS::EC2::Subnet SubnetDb1a SubnetDb1a
[+] AWS::EC2::Subnet SubnetDb1c SubnetDb1c

このスタックを実行すると AWS::EC2::Subnet リソースが 6 つ追加されることが デプロイ前に 確認できます。CFn の 変更セット のような機能です。積極的に利用していきましょう。

なお今回からは cdk synth, cdk deploy, cdk destroy は省略しますので、デプロイする場合は各自適切なタイミングで実施してください。

確認

マネジメントコンソール上でリソースを確認してみましょう。

2

サブネットが正しく作成されていることが確認できました。リソース名、CIDR、AZ、オール OK です。

今回の場合だと IPv4 CIDR で並び替えると見やすいです。

CloudFormation 版

今回のコードを CFn で書くと以下のようになります。

SubnetPublic1a:
  Type: AWS::EC2::Subnet
  Properties:
    CidrBlock: 10.0.11.0/24
    VpcId:
      Ref: Vpc
    AvailabilityZone: ap-northeast-1a
    Tags:
      - Key: Name
        Value: devio-stg-subnet-public-1a
SubnetPublic1c:
  Type: AWS::EC2::Subnet
  Properties:
    CidrBlock: 10.0.12.0/24
    VpcId:
      Ref: Vpc
    AvailabilityZone: ap-northeast-1c
    Tags:
      - Key: Name
        Value: devio-stg-subnet-public-1c
SubnetApp1a:
  Type: AWS::EC2::Subnet
  Properties:
    CidrBlock: 10.0.21.0/24
    VpcId:
      Ref: Vpc
    AvailabilityZone: ap-northeast-1a
    Tags:
      - Key: Name
        Value: devio-stg-subnet-app-1a
SubnetApp1c:
  Type: AWS::EC2::Subnet
  Properties:
    CidrBlock: 10.0.22.0/24
    VpcId:
      Ref: Vpc
    AvailabilityZone: ap-northeast-1c
    Tags:
      - Key: Name
        Value: devio-stg-subnet-app-1c
SubnetDb1a:
  Type: AWS::EC2::Subnet
  Properties:
    CidrBlock: 10.0.31.0/24
    VpcId:
      Ref: Vpc
    AvailabilityZone: ap-northeast-1a
    Tags:
      - Key: Name
        Value: devio-stg-subnet-db-1a
SubnetDb1c:
  Type: AWS::EC2::Subnet
  Properties:
    CidrBlock: 10.0.32.0/24
    VpcId:
      Ref: Vpc
    AvailabilityZone: ap-northeast-1c
    Tags:
      - Key: Name
        Value: devio-stg-subnet-db-1c

長い。キライです!

GitHub

今回のソースコードは コチラ です。

おわりに

これで VPC 上にサブネットができました。しかし作成したコードや CFn 版との比較からもわかるようにイマイチ AWS CDK の旨味を活かせていない状態です。今後リソースが増えるにつれて devio-stack.ts のコード量が増大することは容易に想像できます。ということで今のうちに対応しましょう!

今後やりたいことはこんな感じ。

  • ファイル分割
  • 抽象化
  • リファクタリング

さらにさらにその前にしておきたい小さな改善。

次回のお題は「Metadata」です。

リンク