ECSにおけるVPC設計のポイント

はじめに

RailsアプリをECS上で稼働させることをやったのですが、そのときに色々と学んだので備忘録として残しています。今回はVPCについて。

VPC設計

AWSを使う上で欠かせない設計項目の一つがVPCです。色々な設計考慮ポイントがあるかなと思いますが、個人的には以下の点に気をつけています。

  • CIDRは大きなものを取る
  • 極力パブリックサブネットには配置しない
  • NATゲートウェイではなくVPCエンドポイントの利用ができないか検討する

CIDRについては、RFC 1918で定められた範囲の中で取るようにします。詳細は以下AWSのドキュメントに記載があるのでそれに従います。

docs.aws.amazon.com

今見えているインスタンスの数を元に設定すると、将来的に困りますしVPCのCIDRを編集することはできないので大きめに取るのが良いかなと考えています。

repost.aws

VPCをもとにサブネットを作成しますが、ここでもCIDRは大きなものを取ります。なお、以下のAWSのドキュメントにも記載がありますが、使えないIPアドレスが5つ発生することは押さえておいたほうが良いかなと思います。

docs.aws.amazon.com

VPCエンドポイントの利用は、単純に料金が安いためです。NATゲートウェイは動いている時間+処理しているデータ量(インターネットからの入力と、インターネットへの出力の両方)にかかり、単価は2024年9月現在でそれぞれ0.062USD/時間と0.062USD/GBです(東京リージョンの場合)。

aws.amazon.com

VPCエンドポイントの料金も同じ料金体系ですが、単価は2024年9月現在でそれぞれ0.014USD/時間と0.01USD/GBです。

aws.amazon.com

NATゲートウェイがもともとない状態で、インターネットに出たいためにNATゲートウェイを作ろうとするのは避けたほうが料金の観点から良いかなと思っています。

実装

実装はCDK(TypeScript)でやってみました。こんな感じの実装になります。

import { GatewayVpcEndpointAwsService, InterfaceVpcEndpointAwsService, IpAddresses, SelectedSubnets, SubnetType, Vpc } from "aws-cdk-lib/aws-ec2";
import { Construct } from "constructs";

export class MyVpc extends Construct {
    public readonly value: Vpc;

    constructor(scope: Construct, id: string, private readonly resourceName: string) {
        super(scope, id);

        this.value = new Vpc(this, "Vpc", {
            vpcName: `${this.resourceName}-vpc`,
            availabilityZones: ["ap-northeast-1a", "ap-northeast-1c"],
            ipAddresses: IpAddresses.cidr("10.0.0.0/16"),
            subnetConfiguration: [
                {
                    name: `${this.resourceName}-public`,
                    cidrMask: 24,
                    subnetType: SubnetType.PUBLIC,
                },
                {
                    name: `${this.resourceName}-ecs`,
                    cidrMask: 24,
                    subnetType: SubnetType.PRIVATE_ISOLATED,
                },
                {
                    name: `${this.resourceName}-rds`,
                    cidrMask: 24,
                    subnetType: SubnetType.PRIVATE_ISOLATED,
                },
            ],
            natGateways: 0,
        });

        // VPCエンドポイントを作成
        this.value.addInterfaceEndpoint("EcrEndpoint", {
            service: InterfaceVpcEndpointAwsService.ECR,
        });
        this.value.addInterfaceEndpoint("EcrDockerEndpoint", {
            service: InterfaceVpcEndpointAwsService.ECR_DOCKER,
        });
        this.value.addInterfaceEndpoint("CloudWatchLogsEndpoint", {
            service: InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS,
        });
        this.value.addGatewayEndpoint("S3Endpoint", {
            service: GatewayVpcEndpointAwsService.S3,
            subnets: [
                {
                    subnets: this.value.isolatedSubnets,
                },
            ],
        });
    }
}

ちょっとハマったのはVPCエンドポイントの記述で、以下のドキュメントを見ながら何が必要なのかを試行錯誤して設定しました。

docs.aws.amazon.com

考察

たかだかECSを動かすためのVPCを作るだけですが、設計し始めると思いの外奥が深いです。 特にVPCエンドポイントの利用やNATゲートウェイを極力使わないところについては、検証環境で試すぐらいでしたら微々たる料金ですのであまり気にしないのですが、本番環境となると地味にコストがかかるので、きちんと設計する必要があるなと感じました。