実践!AWS CDK #12 NAT ゲートウェイ

題字・息子たち
2021.06.21

はじめに

NAT ゲートウェイ作ります!

前回の記事はこちら。

AWS 構成図

1

前回作成した Elastic IP を使用して、パブリックサブネットに配置する NAT ゲートウェイを作成していきます。

設計

NAT ゲートウェイのプロパティはこちら。

リソース名 サブネット Elastic IP
devio-stg-ngw-1a devio-stg-subnet-public-1a devio-stg-eip-ngw-1a
devio-stg-ngw-1c devio-stg-subnet-public-1c devio-stg-eip-ngw-1c

2 つの NAT ゲートウェイを作成します。
サブネットと Elastic IP はそれぞれ以前作成したものを設定します。

実装

NAT ゲートウェイに関する処理を行うクラスはこちら。

lib/resource/natGateway.ts

import * as cdk from '@aws-cdk/core';
import { CfnNatGateway, CfnSubnet, CfnEIP } from '@aws-cdk/aws-ec2';
import { Resource } from './abstract/resource';

interface ResourceInfo {
    readonly id: string;
    readonly resourceName: string;
    readonly allocationId: () => string;
    readonly subnetId: () => string;
    readonly assign: (natGateway: CfnNatGateway) => void;
}

export class NatGateway extends Resource {
    public ngw1a: CfnNatGateway;
    public ngw1c: CfnNatGateway;

    private readonly subnetPublic1a: CfnSubnet;
    private readonly subnetPublic1c: CfnSubnet;
    private readonly elasticIpNgw1a: CfnEIP;
    private readonly elasticIpNgw1c: CfnEIP;
    private readonly resourcesInfo: ResourceInfo[] = [
        {
            id: 'NatGateway1a',
            resourceName: 'ngw-1a',
            allocationId: () => this.elasticIpNgw1a.attrAllocationId,
            subnetId: () => this.subnetPublic1a.ref,
            assign: natGateway => this.ngw1a = natGateway
        },
        {
            id: 'NatGateway1c',
            resourceName: 'ngw-1c',
            allocationId: () => this.elasticIpNgw1c.attrAllocationId,
            subnetId: () => this.subnetPublic1c.ref,
            assign: natGateway => this.ngw1c = natGateway
        }
    ];

    constructor(
        subnetPublic1a: CfnSubnet,
        subnetPublic1c: CfnSubnet,
        elasticIpNgw1a: CfnEIP,
        elasticIpNgw1c: CfnEIP
    ) {
        super();
        this.subnetPublic1a = subnetPublic1a;
        this.subnetPublic1c = subnetPublic1c;
        this.elasticIpNgw1a = elasticIpNgw1a;
        this.elasticIpNgw1c = elasticIpNgw1c;
    };

    createResources(scope: cdk.Construct) {
        for (const resourceInfo of this.resourcesInfo) {
            const natGateway = this.createNatGateway(scope, resourceInfo);
            resourceInfo.assign(natGateway);
        }
    }

    private createNatGateway(scope: cdk.Construct, resourceInfo: ResourceInfo): CfnNatGateway {
        const natGateway = new CfnNatGateway(scope, resourceInfo.id, {
            allocationId: resourceInfo.allocationId(),
            subnetId: resourceInfo.subnetId(),
            tags: [{
                key: 'Name',
                value: this.createResourceName(scope, resourceInfo.resourceName)
            }]
        });

        return natGateway;
    }
}

NAT ゲートウェイ生成時には allocationIdsubnetId を指定する必要があります。これらは動的に取得したいため、ResourceInfo インタフェースで関数として定義しています。

allocationId: () => this.elasticIpNgw1a.attrAllocationId,
subnetId: () => this.subnetPublic1a.ref,

resourcesInfo でこのように指定することで、各変数から getter のように取得できます。

なお、allocationId は CfnEIP インスタンスの attrAllocationId で取得しなければなりません。ref で取得できる値は IP アドレス となるのでご注意ください。(ハマリポイントです)

メインのプログラムはこちら。
ハイライト部分を追記しました。

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';

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);
  }
}

今回はコンストラクタに渡す引数が多めです。しかし、このように書くことで関連するリソースがわかりやすくなっていますね。

テスト

テストコードはこちら。

test/resource/natGateway.test.ts

import { expect, countResources, haveResource } from '@aws-cdk/assert';
import * as cdk from '@aws-cdk/core';
import * as Devio from '../../lib/devio-stack';

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

    expect(stack).to(countResources('AWS::EC2::NatGateway', 2));
    expect(stack).to(haveResource('AWS::EC2::NatGateway', {
        Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-ngw-1a' }]
    }));
    expect(stack).to(haveResource('AWS::EC2::NatGateway', {
        Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-ngw-1c' }]
    }));
});

以下を確認しています。

  • NAT ゲートウェイリソースが 2 つあること
  • 各 NAT ゲートウェイのリソース名が正しいこと

確認

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

2

きちんと作成されていますね。

CloudFormation 版

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

NatGateway1a:
  Type: AWS::EC2::NatGateway
  Properties:
    AllocationId:
      Fn::GetAtt:
        - ElasticIpNgw1a
        - AllocationId
    SubnetId:
      Ref: SubnetPublic1a
    Tags:
      - Key: Name
        Value: devio-stg-ngw-1a
NatGateway1c:
  Type: AWS::EC2::NatGateway
  Properties:
    AllocationId:
      Fn::GetAtt:
        - ElasticIpNgw1c
        - AllocationId
    SubnetId:
      Ref: SubnetPublic1c
    Tags:
      - Key: Name
        Value: devio-stg-ngw-1c

GitHub

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

おわりに

これでインターネットにアクセスする口はできました。
ネットワークに関して作成したいリソースはあと 2 つ。

  • ルートテーブル
  • ネットワーク ACL

順に作っていきましょう。

リンク