この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
今回はルートテーブルを作成します。
プログラムは長めです。
前回の記事はこちら。
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 |
データ層のプライベートサブネットに関連付けるルートテーブル。
特に指定なし。とりあえず作成する。
上記のローカルルート設定はルートテーブルを作成すると自動で付与されます。よって、この部分に関してはプログラムを書きません。
実装
ルートテーブルに関する処理を行うクラスはこちら。
lib/resource/routeTable.ts
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;
}
メインのプログラムはこちら。
ハイライト部分を追記しました。
lib/devio-stack.ts
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);
}
}
テスト
テストコードはこちら。
test/resource/routeTable.test.ts
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