実践!AWS CDK #13 ルートテーブル
はじめに
今回はルートテーブルを作成します。
プログラムは長めです。
前回の記事はこちら。
AWS 構成図
前回と変わらずです。ネットワークのルーティングなので構成図には記載していません。
設計
作成するルートテーブルは 4 つ。プロパティは以下の通りです。
リソース名 | 関連付けるサブネット |
---|---|
devio-stg-rtb-public | devio-stg-subnet-public-1a devio-stg-subnet-public-1c |
devio-stg-rtb-app-1a | devio-stg-subnet-app-1a |
devio-stg-rtb-app-1c | devio-stg-subnet-app-1c |
devio-stg-rtb-db | devio-stg-subnet-db-1a devio-stg-subnet-db-1c |
以下、詳細。
devio-stg-rtb-public
送信先 | ターゲット |
---|---|
10.0.0.0/16 | local |
0.0.0.0/0 | devio-stg-igw |
パブリックサブネットに関連付けるルートテーブル。
VPC 内の通信(ローカルルート)以外はインターネットゲートウェイをターゲットとします。
devio-stg-rtb-app-1a
送信先 | ターゲット |
---|---|
10.0.0.0/16 | local |
0.0.0.0/0 | devio-stg-ngw-1a |
ap-northeast-1a
にあるアプリケーション層プライベートサブネットに関連付けるルートテーブル。
ローカルルート以外は同一 AZ の NAT ゲートウェイへ。
devio-stg-rtb-app-1c
送信先 | ターゲット |
---|---|
10.0.0.0/16 | local |
0.0.0.0/0 | devio-stg-ngw-1c |
ap-northeast-1c
にあるアプリケーション層プライベートサブネットに関連付けるルートテーブル。
ローカルルート以外は同一 AZ の NAT ゲートウェイへ。
devio-stg-rtb-db
送信先 | ターゲット |
---|---|
10.0.0.0/16 | local |
データ層のプライベートサブネットに関連付けるルートテーブル。
特に指定なし。とりあえず作成する。
上記のローカルルート設定はルートテーブルを作成すると自動で付与されます。よって、この部分に関してはプログラムを書きません。
実装
ルートテーブルに関する処理を行うクラスはこちら。
import * as cdk from '@aws-cdk/core'; import { CfnRouteTable, CfnRoute, CfnSubnetRouteTableAssociation, CfnVPC, CfnSubnet, CfnInternetGateway, CfnNatGateway } from '@aws-cdk/aws-ec2'; import { Resource } from './abstract/resource'; interface RouteInfo { readonly id: string; readonly destinationCidrBlock: string; readonly gatewayId?: () => string; readonly natGatewayId?: () => string; } interface AssociationInfo { readonly id: string; readonly subnetId: () => string; } interface ResourceInfo { readonly id: string; readonly resourceName: string; readonly routes: RouteInfo[]; readonly associations: AssociationInfo[]; readonly assign: (routeTable: CfnRouteTable) => void; } export class RouteTable extends Resource { public public: CfnRouteTable; public app1a: CfnRouteTable; public app1c: CfnRouteTable; public db: CfnRouteTable; private readonly vpc: CfnVPC; private readonly subnetPublic1a: CfnSubnet; private readonly subnetPublic1c: CfnSubnet; private readonly subnetApp1a: CfnSubnet; private readonly subnetApp1c: CfnSubnet; private readonly subnetDb1a: CfnSubnet; private readonly subnetDb1c: CfnSubnet; private readonly internetGateway: CfnInternetGateway; private readonly natGateway1a: CfnNatGateway; private readonly natGateway1c: CfnNatGateway; private readonly resources: ResourceInfo[] = [ { id: 'RouteTablePublic', resourceName: 'rtb-public', routes: [{ id: 'RoutePublic', destinationCidrBlock: '0.0.0.0/0', gatewayId: () => this.internetGateway.ref }], associations: [ { id: 'AssociationPublic1a', subnetId: () => this.subnetPublic1a.ref }, { id: 'AssociationPublic1c', subnetId: () => this.subnetPublic1c.ref } ], assign: routeTable => this.public = routeTable }, { id: 'RouteTableApp1a', resourceName: 'rtb-app-1a', routes: [{ id: 'RouteApp1a', destinationCidrBlock: '0.0.0.0/0', natGatewayId: () => this.natGateway1a.ref }], associations: [{ id: 'AssociationApp1a', subnetId: () => this.subnetApp1a.ref }], assign: routeTable => this.app1a = routeTable }, { id: 'RouteTableApp1c', resourceName: 'rtb-app-1c', routes: [{ id: 'RouteApp1c', destinationCidrBlock: '0.0.0.0/0', natGatewayId: () => this.natGateway1c.ref }], associations: [{ id: 'AssociationApp1c', subnetId: () => this.subnetApp1c.ref }], assign: routeTable => this.app1c = routeTable }, { id: 'RouteTableDb', resourceName: 'rtb-db', routes: [], associations: [ { id: 'AssociationDb1a', subnetId: () => this.subnetDb1a.ref }, { id: 'AssociationDb1c', subnetId: () => this.subnetDb1c.ref } ], assign: routeTable => this.db = routeTable } ]; constructor( vpc: CfnVPC, subnetPublic1a: CfnSubnet, subnetPublic1c: CfnSubnet, subnetApp1a: CfnSubnet, subnetApp1c: CfnSubnet, subnetDb1a: CfnSubnet, subnetDb1c: CfnSubnet, internetGateway: CfnInternetGateway, natGateway1a: CfnNatGateway, natGateway1c: CfnNatGateway ) { super(); this.vpc = vpc; this.subnetPublic1a = subnetPublic1a; this.subnetPublic1c = subnetPublic1c; this.subnetApp1a = subnetApp1a; this.subnetApp1c = subnetApp1c; this.subnetDb1a = subnetDb1a; this.subnetDb1c = subnetDb1c; this.internetGateway = internetGateway; this.natGateway1a = natGateway1a; this.natGateway1c = natGateway1c; } createResources(scope: cdk.Construct) { for (const resourceInfo of this.resources) { const routeTable = this.createRouteTable(scope, resourceInfo); resourceInfo.assign(routeTable); } } private createRouteTable(scope: cdk.Construct, resourceInfo: ResourceInfo): CfnRouteTable { const routeTable = new CfnRouteTable(scope, resourceInfo.id, { vpcId: this.vpc.ref, tags: [{ key: 'Name', value: this.createResourceName(scope, resourceInfo.resourceName) }] }); for (const routeInfo of resourceInfo.routes) { this.createRoute(scope, routeInfo, routeTable); } for (const associationInfo of resourceInfo.associations) { this.createAssociation(scope, associationInfo, routeTable); } return routeTable; } private createRoute(scope: cdk.Construct, routeInfo: RouteInfo, routeTable: CfnRouteTable) { const route = new CfnRoute(scope, routeInfo.id, { routeTableId: routeTable.ref, destinationCidrBlock: routeInfo.destinationCidrBlock }); if (routeInfo.gatewayId) { route.gatewayId = routeInfo.gatewayId(); } else if (routeInfo.natGatewayId) { route.natGatewayId = routeInfo.natGatewayId(); } } private createAssociation(scope: cdk.Construct, associationInfo: AssociationInfo, routeTable: CfnRouteTable) { new CfnSubnetRouteTableAssociation(scope, associationInfo.id, { routeTableId: routeTable.ref, subnetId: associationInfo.subnetId() }); } }
長くなってしまった。が、少し複雑な依存関係なので仕方なし。
例によってリソース情報は ResourceInfo
型の配列にまとめて宣言しています。
今回が初の構成なのですが、この配列の中に更に別のリソース情報が入れ子になっています。それら ルート と 関連付け の情報をそれぞれ RouteInfo
, AssociationInfo
というインタフェースで定義しました。
そのコードがこちらです。
interface RouteInfo { readonly id: string; readonly destinationCidrBlock: string; readonly gatewayId?: () => string; readonly natGatewayId?: () => string; } interface AssociationInfo { readonly id: string; readonly subnetId: () => string; } interface ResourceInfo { readonly id: string; readonly resourceName: string; readonly routes: RouteInfo[]; readonly associations: AssociationInfo[]; readonly assign: (routeTable: CfnRouteTable) => void; }
あとはいつものようにそれぞれリソースを生成するためのメソッドを作成し、ループ処理を行っています。
少し特殊な処理を実行しているのが以下のハイライト部分です。
private createRoute(scope: cdk.Construct, routeInfo: RouteInfo, routeTable: CfnRouteTable) { const route = new CfnRoute(scope, routeInfo.id, { routeTableId: routeTable.ref, destinationCidrBlock: routeInfo.destinationCidrBlock }); if (routeInfo.gatewayId) { route.gatewayId = routeInfo.gatewayId(); } else if (routeInfo.natGatewayId) { route.natGatewayId = routeInfo.natGatewayId(); } }
CfnRoute でターゲットを指定する場合、インターネットゲートウェイならば gatewayId
を、NAT ゲートウェイならば natGatewayId
を設定する必要があります。よってここでは先に必須項目を指定して CfnRoute インスタンスを生成してやり、その後リソース情報に応じてそれらの ID をインスタンスのプロパティに設定するという作りになっています。
このため RouteInfo
が持つこれらのプロパティは Optional としています。
interface RouteInfo { readonly id: string; readonly destinationCidrBlock: string; readonly gatewayId?: () => string; readonly natGatewayId?: () => string; }
メインのプログラムはこちら。
ハイライト部分を追記しました。
import * as cdk from '@aws-cdk/core'; import { Vpc } from './resource/vpc'; import { Subnet } from './resource/subnet'; import { InternetGateway } from './resource/internetGateway'; import { ElasticIp } from './resource/elasticIp'; import { NatGateway } from './resource/natGateway'; import { RouteTable } from './resource/routeTable'; export class DevioStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // VPC const vpc = new Vpc(); vpc.createResources(this); // Subnet const subnet = new Subnet(vpc.vpc); subnet.createResources(this); // Internet Gateway const internetGateway = new InternetGateway(vpc.vpc); internetGateway.createResources(this); // Elastic IP const elasticIp = new ElasticIp(); elasticIp.createResources(this); // NAT Gateway const natGateway = new NatGateway( subnet.public1a, subnet.public1c, elasticIp.ngw1a, elasticIp.ngw1c ); natGateway.createResources(this); // Route Table const routeTable = new RouteTable( vpc.vpc, subnet.public1a, subnet.public1c, subnet.app1a, subnet.app1c, subnet.db1a, subnet.db1c, internetGateway.igw, natGateway.ngw1a, natGateway.ngw1c ); routeTable.createResources(this); } }
テスト
テストコードはこちら。
import { expect, countResources, haveResource, anything } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import * as Devio from '../../lib/devio-stack'; test('RouteTable', () => { const app = new cdk.App(); const stack = new Devio.DevioStack(app, 'DevioStack'); expect(stack).to(countResources('AWS::EC2::RouteTable', 4)); expect(stack).to(haveResource('AWS::EC2::RouteTable', { VpcId: anything(), Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-rtb-public' }] })); expect(stack).to(haveResource('AWS::EC2::RouteTable', { VpcId: anything(), Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-rtb-app-1a' }] })); expect(stack).to(haveResource('AWS::EC2::RouteTable', { VpcId: anything(), Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-rtb-app-1c' }] })); expect(stack).to(haveResource('AWS::EC2::RouteTable', { VpcId: anything(), Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-rtb-db' }] })); expect(stack).to(countResources('AWS::EC2::Route', 3)); expect(stack).to(haveResource('AWS::EC2::Route', { RouteTableId: anything(), DestinationCidrBlock: '0.0.0.0/0', GatewayId: anything() })); expect(stack).to(haveResource('AWS::EC2::Route', { RouteTableId: anything(), DestinationCidrBlock: '0.0.0.0/0', NatGatewayId: anything() })); expect(stack).to(countResources('AWS::EC2::SubnetRouteTableAssociation', 6)); expect(stack).to(haveResource('AWS::EC2::SubnetRouteTableAssociation', { RouteTableId: anything(), SubnetId: anything() })); });
以下を確認しています。
- ルートテーブルリソースが 4 つあること
- ルートリソースが 3 つあること
- サブネットとルートテーブルの関連付けリソースが 6 つあること
- 各リソースのプロパティが正しいこと
また、今回から anything()
というプロパティ比較用の関数を利用しています。(便利そうなの見つけました!)
これまでは他のリソースの値を参照する部分に関してはテストを書いていなかったのですが、この関数を使うことにより プロパティが設定されていること をテストすることができます。anything()
を指定した場合、値がどんなものでもテストは通ります。
確認
マネジメントコンソール上でリソースを確認してみましょう。
上手にできました。
ルートやサブネットの関連付けもバッチリです。
CloudFormation 版
今回のコードを CFn で書くと以下のようになります。
RouteTablePublic: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: Vpc Tags: - Key: Name Value: devio-stg-rtb-public RoutePublic: Type: AWS::EC2::Route Properties: RouteTableId: Ref: RouteTablePublic DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: InternetGateway AssociationPublic1a: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: RouteTablePublic SubnetId: Ref: SubnetPublic1a AssociationPublic1c: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: RouteTablePublic SubnetId: Ref: SubnetPublic1c RouteTableApp1a: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: Vpc Tags: - Key: Name Value: devio-stg-rtb-app-1a RouteApp1a: Type: AWS::EC2::Route Properties: RouteTableId: Ref: RouteTableApp1a DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: Ref: NatGateway1a AssociationApp1a: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: RouteTableApp1a SubnetId: Ref: SubnetApp1a RouteTableApp1c: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: Vpc Tags: - Key: Name Value: devio-stg-rtb-app-1c RouteApp1c: Type: AWS::EC2::Route Properties: RouteTableId: Ref: RouteTableApp1c DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: Ref: NatGateway1c AssociationApp1c: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: RouteTableApp1c SubnetId: Ref: SubnetApp1c RouteTableDb: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: Vpc Tags: - Key: Name Value: devio-stg-rtb-db AssociationDb1a: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: RouteTableDb SubnetId: Ref: SubnetDb1a AssociationDb1c: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: RouteTableDb SubnetId: Ref: SubnetDb1c
過去最長です。合計 13 のリソースを定義しています。
GitHub
今回のソースコードは コチラ です。
おわりに
今回のプログラミングがこれまでで一番楽しかったです。まだ改善の余地はあるかもしれませんが、複雑なデータ構造をうまくコードに落とし込めたのではないかと思います。
次回作成するリソースは ネットワーク ACL
です。
これが終わればネットワークまわりは一段落。
リンク
- class CfnRouteTable (construct) | AWS CDK API Reference
- class CfnRoute (construct) | AWS CDK API Reference
- class CfnSubnetRouteTableAssociation (construct) | AWS CDK API Reference
- AWS::EC2::RouteTable | AWS CloudFormation User Guide
- AWS::EC2::Route | AWS CloudFormation User Guide
- AWS::EC2::SubnetRouteTableAssociation | AWS CloudFormation User Guide
- VPC のルートテーブル | Amazon VPC User Guide