VPCエンドポイントの集約について改めてまとめる
皆様こんにちは!SREチームの佐藤です!
弊社では、今まで分散管理していたAWSアカウントを集約管理に移行しており、アカウント集約が完了したからにはコスト最適な形を目指したいということで、今回はVPCエンドポイントの集約を検討してみたいと思います!
少し前にRoute53 Profilesを使ったVPCエンドポイントの集約方法が紹介されていましたが、キャッチアップできていなかったので、使ってみたいと思います!
そこそこコストがかかるリソースを作りますので、試してみたいという方はご留意ください。
私はre:Inventのクレジットがあったので、ブログで還元します(笑)
VPCエンドポイントとは?
VPCエンドポイントは、VPC内のリソースからAWSサービスのAPIを実行したい場合に、パブリックインターネットを経由せずにサービスエンドポイントにアクセスするために利用するリソースです。
以下の図の左側のようにインターネットへの経路がないVPC内のEC2インスタンスが、S3のAPIを実行しようとすると、ResolverからパブリックのIPアドレスを返されてしまい、出口がないためアクセスできません。
そこで、右側のようにVPCエンドポイントを作成してあげると、VPCのResolverにサービスエンドポイントへのレコードが登録され、VPCエンドポイント経由でサービスエンドポイントにアクセスできるようになります。
(よくわからないよーという方は、後ほど挙動を確認しているのでいったん読み進めてみてください。)
あれ?結局VPCの外に出てない?と思ったそこのあなた!VPCエンドポイント自体はVPC内のリソースですが、そこからサービスエンドポイントまでの通信はAWSの専有ネットワークを通じて行われます。
AWSの専有ネットワークというのはAWSリージョン間、AZ間の通信、AWSが所有するWANだと思っていただければと思います。
ネットワークとセキュリティが絡むAWSの話をする際、混同しないように通常のインターネットのことをパブリックインターネットと呼ぶこともよくあります。
そりゃインターネットなんだからパブリックだろと思っていた方はそういう意図だったのかと覚えておきましょう!
単一アカウントでのVPCエンドポイントの挙動
さて、実際の挙動を少し覗いてみましょう。
以下の構成を作成するCloudFormationテンプレートを用意しました。(パラメーターはこだわりなければデフォルトで問題ないです)
step1.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: >
VPC System - Private VPC with App/Endpoint/TGW subnets across 2 AZs,
SSM VPC Endpoints, and an EC2 instance.
# ------------------------------------------------------------
# Parameters
# ------------------------------------------------------------
Parameters:
SystemName:
Type: String
Default: system
Description: System name used for resource naming.
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
# ------------------------------------------------------------
# Resources
# ------------------------------------------------------------
Resources:
# System VPC 1
# ========================
# VPC
# ========================
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/24
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub vpc-${SystemName}
# ========================
# Subnets - App
# ========================
PriAppApne1a:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.0/28
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: pri-app-apne1a
PriAppApne1c:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.16/28
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: pri-app-apne1c
# ========================
# Subnets - Endpoint
# ========================
PriEpApne1a:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.32/28
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: pri-ep-apne1a
PriEpApne1c:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.48/28
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: pri-ep-apne1c
# ========================
# Subnets - TGW
# ========================
PriTgwApne1a:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.64/28
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: pri-tgw-apne1a
PriTgwApne1c:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.80/28
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: pri-tgw-apne1c
# ========================
# Route Tables
# ========================
RouteTableApp:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub rtb-pri-app-${SystemName}
RouteTableEp:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub rtb-pri-ep-${SystemName}
RouteTableTgw:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub rtb-pri-tgw-${SystemName}
# --- Route Table Associations ---
RtAssocAppA:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriAppApne1a
RouteTableId: !Ref RouteTableApp
RtAssocAppC:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriAppApne1c
RouteTableId: !Ref RouteTableApp
RtAssocEpA:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriEpApne1a
RouteTableId: !Ref RouteTableEp
RtAssocEpC:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriEpApne1c
RouteTableId: !Ref RouteTableEp
RtAssocTgwA:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriTgwApne1a
RouteTableId: !Ref RouteTableTgw
RtAssocTgwC:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriTgwApne1c
RouteTableId: !Ref RouteTableTgw
# ========================
# Security Groups
# ========================
SgEc2:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for EC2 instance
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub sg-ec2-${SystemName}
SgVpcEndpoint:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for VPC Endpoints (allow HTTPS from VPC)
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 10.0.0.0/24
Tags:
- Key: Name
Value: !Sub sg-vpce-${SystemName}
# ========================
# VPC Endpoints (SSM)
# ========================
VpceSSM:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref PriEpApne1a
- !Ref PriEpApne1c
SecurityGroupIds:
- !Ref SgVpcEndpoint
VpceSSMMessages:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref PriEpApne1a
- !Ref PriEpApne1c
SecurityGroupIds:
- !Ref SgVpcEndpoint
VpceEC2Messages:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref PriEpApne1a
- !Ref PriEpApne1c
SecurityGroupIds:
- !Ref SgVpcEndpoint
# ========================
# IAM Role for EC2 (SSM)
# ========================
Ec2SsmRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub role-ec2-ssm-${SystemName}
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Ec2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref Ec2SsmRole
# ========================
# EC2 Instance
# ========================
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: t3.micro
ImageId: !Ref LatestAmiId
SubnetId: !Ref PriAppApne1a
IamInstanceProfile: !Ref Ec2InstanceProfile
SecurityGroupIds:
- !Ref SgEc2
Tags:
- Key: Name
Value: !Sub ec2-${SystemName}
# ------------------------------------------------------------
# Outputs
# ------------------------------------------------------------
Outputs:
VpcId:
Description: VPC ID
Value: !Ref VPC
Ec2InstanceId:
Description: EC2 Instance ID
Value: !Ref Ec2Instance
VpceSSMId:
Description: SSM VPC Endpoint ID
Value: !Ref VpceSSM
VpceSSMMessagesId:
Description: SSM Messages VPC Endpoint ID
Value: !Ref VpceSSMMessages
VpceEC2MessagesId:
Description: EC2 Messages VPC Endpoint ID
Value: !Ref VpceEC2Messages
スタックの作成ができたら、EC2インスタンスにセッションマネージャーでアクセスします。
全然関係ないですが、昔よりセッションマネージャーに接続できるようになるの早くなりましたよね
ssmのエンドポイントへの名前解決を確認してみましょう!
sh-5.2$ nslookup ssm.ap-northeast-1.amazonaws.com
Server: 10.0.0.2 ←.2はVPCのResolverのIPアドレスです
Address: 10.0.0.2#53
Non-authoritative answer:
Name: ssm.ap-northeast-1.amazonaws.com
Address: 10.0.0.38 ←作成したVPCエンドポイントのIPアドレスです
Name: ssm.ap-northeast-1.amazonaws.com
Address: 10.0.0.54 ←作成したVPCエンドポイントのIPアドレスです
次に作成していないサービスのエンドポイントに名前解決をしてみます。
sh-5.2$ nslookup s3.ap-northeast-1.amazonaws.com
Server: 10.0.0.2
Address: 10.0.0.2#53
Non-authoritative answer:
Name: s3.ap-northeast-1.amazonaws.com
Address: 3.5.155.255 ←パブリックのS3サービスエンドポイントのIPアドレスです
Name: s3.ap-northeast-1.amazonaws.com
Address: 3.5.157.45
Name: s3.ap-northeast-1.amazonaws.com
Address: 3.5.157.226
Name: s3.ap-northeast-1.amazonaws.com
Address: 3.5.159.127
Name: s3.ap-northeast-1.amazonaws.com
Address: 52.219.199.8
Name: s3.ap-northeast-1.amazonaws.com
Address: 3.5.159.118
Name: s3.ap-northeast-1.amazonaws.com
Address: 3.5.159.85
VPCエンドポイントを作成したサービスはVPCエンドポイントのIPアドレスが返され、作成していないサービスはパブリックのIPアドレスが返されていることがわかりますね!
インターネットへの口がないため、s3へのリクエストをしても何も返ってきません。
VPCエンドポイントの集約
さて、ここからが本題です。
VPCエンドポイントは、安くはないサービスです。
東京リージョンでは各AZのVPCエンドポイント1つあたりの料金が $0.014/h です。
これだけだと高いかどうかわからないですよね(笑)
よく利用するVPCエンドポイントを2AZに展開した場合の課金額を試算しましょう。
以下のサービスを利用するとします。
- ssm
- ssm-messages
- ec2-messages
- logs
- metrics
この場合、月額は以下のようになります。
0.014 * 24 * 30 * 10 = 100.8
大体$100くらいかかりますね。
え?そんなに高くないって?
これ1アカウントですからね、10アカウントでこれを利用すればそれだけで$1000です。
ちょっとしたシステム1つ分です。
この料金を削減するために、VPCエンドポイントを集約しましょうという話が出てくるわけです!
ほんとに安いの?
勘のいい人は、集約したら集約したで新たにかかる費用があるんじゃない?と思われるかと思います。
主な要素はTransit Gatewayの通信料金かと思います。
通信量を変数にして集約する場合としない場合の損益分岐点を求めてみましょう。
条件は以下とします。
| 項目 | 値 | 単位 |
|---|---|---|
| 月間時間 | 730 | 時間 |
| AZ数(VPCE配置) | 2 | AZ |
| アカウント数(集約対象) | 5 | アカウント |
| エンドポイントの種類 | 5 | 種類 |
分散構成と集約構成の月額コストの比較は以下の通りです。
| 月間通信量 (GB, 全EP合計) | 分散構成 月額 (USD) | 集約構成 月額 (USD) | 差額 (分散-集約) (USD) | 判定 |
|---|---|---|---|---|
| 0 | $511.00 | $102.20 | $408.80 | 集約有利 |
| 50 | $511.50 | $104.70 | $406.80 | 集約有利 |
| 100 | $512.00 | $107.20 | $404.80 | 集約有利 |
| 200 | $513.00 | $112.20 | $400.80 | 集約有利 |
| 500 | $516.00 | $127.20 | $388.80 | 集約有利 |
| 1,000 | $521.00 | $152.20 | $368.80 | 集約有利 |
| 1,500 | $526.00 | $177.20 | $348.80 | 集約有利 |
| 2,000 | $531.00 | $202.20 | $328.80 | 集約有利 |
| 2,500 | $536.00 | $227.20 | $308.80 | 集約有利 |
| 3,000 | $541.00 | $252.20 | $288.80 | 集約有利 |
| 4,000 | $551.00 | $302.20 | $248.80 | 集約有利 |
| 5,000 | $561.00 | $352.20 | $208.80 | 集約有利 |
| 7,500 | $586.00 | $477.20 | $108.80 | 集約有利 |
| 10,000 | $611.00 | $602.20 | $8.80 | 集約有利 |
| 15,000 | $661.00 | $852.20 | ($191.20) | 分散有利 |
| 20,000 | $711.00 | $1,102.20 | ($391.20) | 分散有利 |
| 50,000 | $1,011.00 | $2,602.20 | ($1,591.20) | 分散有利 |
この条件の場合、合計の通信量が10TBを超える場合は分散の方がよさそうです。
logsなどの通信量が多いサービスを検討するときには気を付けたいですね。
もちろんアカウント数やAZ数、種類が増えれば損益分岐点の通信量も増加します。
エンドポイント集約前準備
さっそくエンドポイントの集約を試していきたいですが、今回は以下のような構成を想定して分散構成から集約構成への移行を検討するので、step1.yamlで作成したCloudFormationスタックを以下のテンプレートで更新してください。
構成は以下の図のようになっています。
少しボリュームがありますが、システム用VPCを2つ、集約用VPCを1つ用意してそれらをTGWで疎通させています。
今回は全疎通の設定にしています。
ややこしいのでセッションマネージャーのために必要なVPCエンドポイントは表記してないですが、すべてのVPCに作成されています。
step2.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: >
VPC System - Private VPC with App/Endpoint/TGW subnets across 2 AZs,
SSM VPC Endpoints, and an EC2 instance.
# ------------------------------------------------------------
# Parameters
# ------------------------------------------------------------
Parameters:
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
# ------------------------------------------------------------
# Resources
# ------------------------------------------------------------
Resources:
# Shared Resources
# ========================
# IAM Role for EC2 (SSM)
# ========================
Ec2SsmRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub role-ec2-ssm
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
- arn:aws:iam::aws:policy/ReadOnlyAccess
Ec2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref Ec2SsmRole
Tgw:
Type: AWS::EC2::TransitGateway
Properties:
DefaultRouteTableAssociation: enable
DefaultRouteTablePropagation: enable
Tags:
- Key: Name
Value: tgw
# System VPC 1
# ========================
# VPC
# ========================
VpcSystem1:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/24
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub vpc-system-1
# ========================
# Subnets - App
# ========================
PriAppApne1aSystem1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcSystem1
CidrBlock: 10.0.0.0/28
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: sys1-pri-app-apne1a
PriAppApne1cSystem1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcSystem1
CidrBlock: 10.0.0.16/28
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: sys1-pri-app-apne1c
# ========================
# Subnets - Endpoint
# ========================
PriEpApne1aSystem1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcSystem1
CidrBlock: 10.0.0.32/28
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: sys1-pri-ep-apne1a
PriEpApne1cSystem1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcSystem1
CidrBlock: 10.0.0.48/28
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: sys1-pri-ep-apne1c
# ========================
# Subnets - TGW
# ========================
PriTgwApne1aSystem1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcSystem1
CidrBlock: 10.0.0.64/28
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: sys1-pri-tgw-apne1a
PriTgwApne1cSystem1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcSystem1
CidrBlock: 10.0.0.80/28
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: sys1-pri-tgw-apne1c
# ========================
# TGW Attachment
# ========================
TgwAttachmentSystem1:
Type: AWS::EC2::TransitGatewayAttachment
Properties:
TransitGatewayId: !Ref Tgw
VpcId: !Ref VpcSystem1
SubnetIds:
- !Ref PriTgwApne1aSystem1
- !Ref PriTgwApne1cSystem1
# ========================
# Route Tables
# ========================
RouteTableAppSystem1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VpcSystem1
Tags:
- Key: Name
Value: !Sub sys1-rtb-pri-app
RouteTableEpSystem1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VpcSystem1
Tags:
- Key: Name
Value: !Sub sys1-rtb-pri-ep
RouteTableTgwSystem1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VpcSystem1
Tags:
- Key: Name
Value: !Sub sys1-rtb-pri-tgw
# --- Route ---
RouteToTgwSystem1:
DependsOn: TgwAttachmentSystem1
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RouteTableAppSystem1
DestinationCidrBlock: 0.0.0.0/0
TransitGatewayId: !Ref Tgw
# --- Route Table Associations ---
RtAssocAppASystem1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriAppApne1aSystem1
RouteTableId: !Ref RouteTableAppSystem1
RtAssocAppCSystem1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriAppApne1cSystem1
RouteTableId: !Ref RouteTableAppSystem1
RtAssocEpASystem1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriEpApne1aSystem1
RouteTableId: !Ref RouteTableEpSystem1
RtAssocEpCSystem1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriEpApne1cSystem1
RouteTableId: !Ref RouteTableEpSystem1
RtAssocTgwASystem1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriTgwApne1aSystem1
RouteTableId: !Ref RouteTableTgwSystem1
RtAssocTgwCSystem1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriTgwApne1cSystem1
RouteTableId: !Ref RouteTableTgwSystem1
# ========================
# Security Groups
# ========================
SgEc2System1:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for EC2 instance
VpcId: !Ref VpcSystem1
SecurityGroupIngress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub sg-ec2-system-1
SgVpcEndpointSystem1:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for VPC Endpoints (allow HTTPS from VPC)
VpcId: !Ref VpcSystem1
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 10.0.0.0/24
Tags:
- Key: Name
Value: !Sub sg-vpce-system-1
# ========================
# VPC Endpoints (SSM)
# ========================
VpceSSMSystem1:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VpcSystem1
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref PriEpApne1aSystem1
SecurityGroupIds:
- !Ref SgVpcEndpointSystem1
VpceSSMMessagesSystem1:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VpcSystem1
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref PriEpApne1aSystem1
SecurityGroupIds:
- !Ref SgVpcEndpointSystem1
VpceEC2MessagesSystem1:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VpcSystem1
ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref PriEpApne1aSystem1
SecurityGroupIds:
- !Ref SgVpcEndpointSystem1
# ========================
# EC2 Instance
# ========================
Ec2InstanceSystem1:
Type: AWS::EC2::Instance
Properties:
InstanceType: t3.micro
ImageId: !Ref LatestAmiId
SubnetId: !Ref PriAppApne1aSystem1
IamInstanceProfile: !Ref Ec2InstanceProfile
SecurityGroupIds:
- !Ref SgEc2System1
Tags:
- Key: Name
Value: !Sub ec2-system-1
# System VPC 2
# ========================
# VPC
# ========================
VpcSystem2:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.1.0.0/24
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub vpc-system-2
# ========================
# Subnets - App
# ========================
PriAppApne1aSystem2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcSystem2
CidrBlock: 10.1.0.0/28
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: sys2-pri-app-apne1a
PriAppApne1cSystem2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcSystem2
CidrBlock: 10.1.0.16/28
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: sys2-pri-app-apne1c
# ========================
# Subnets - Endpoint
# ========================
PriEpApne1aSystem2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcSystem2
CidrBlock: 10.1.0.32/28
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: sys2-pri-ep-apne1a
PriEpApne1cSystem2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcSystem2
CidrBlock: 10.1.0.48/28
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: sys2-pri-ep-apne1c
# ========================
# Subnets - TGW
# ========================
PriTgwApne1aSystem2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcSystem2
CidrBlock: 10.1.0.64/28
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: sys2-pri-tgw-apne1a
PriTgwApne1cSystem2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcSystem2
CidrBlock: 10.1.0.80/28
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: sys2-pri-tgw-apne1c
# ========================
# TGW Attachment
# ========================
TgwAttachmentSystem2:
Type: AWS::EC2::TransitGatewayAttachment
Properties:
TransitGatewayId: !Ref Tgw
VpcId: !Ref VpcSystem2
SubnetIds:
- !Ref PriTgwApne1aSystem2
- !Ref PriTgwApne1cSystem2
# ========================
# Route Tables
# ========================
RouteTableAppSystem2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VpcSystem2
Tags:
- Key: Name
Value: !Sub sys2-rtb-pri-app
RouteTableEpSystem2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VpcSystem2
Tags:
- Key: Name
Value: !Sub sys2-rtb-pri-ep
RouteTableTgwSystem2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VpcSystem2
Tags:
- Key: Name
Value: !Sub sys2-rtb-pri-tgw
# --- Route ---
RouteToTgwSystem2:
DependsOn: TgwAttachmentSystem2
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RouteTableAppSystem2
DestinationCidrBlock: 0.0.0.0/0
TransitGatewayId: !Ref Tgw
# --- Route Table Associations ---
RtAssocAppASystem2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriAppApne1aSystem2
RouteTableId: !Ref RouteTableAppSystem2
RtAssocAppCSystem2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriAppApne1cSystem2
RouteTableId: !Ref RouteTableAppSystem2
RtAssocEpASystem2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriEpApne1aSystem2
RouteTableId: !Ref RouteTableEpSystem2
RtAssocEpCSystem2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriEpApne1cSystem2
RouteTableId: !Ref RouteTableEpSystem2
RtAssocTgwASystem2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriTgwApne1aSystem2
RouteTableId: !Ref RouteTableTgwSystem2
RtAssocTgwCSystem2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriTgwApne1cSystem2
RouteTableId: !Ref RouteTableTgwSystem2
# ========================
# Security Groups
# ========================
SgEc2System2:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for EC2 instance
VpcId: !Ref VpcSystem2
SecurityGroupIngress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub sg-ec2-system-2
SgVpcEndpointSystem2:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for VPC Endpoints (allow HTTPS from VPC)
VpcId: !Ref VpcSystem2
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 10.1.0.0/24
Tags:
- Key: Name
Value: !Sub sg-vpce-system-2
# ========================
# VPC Endpoints (SSM)
# ========================
VpceSSMSystem2:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VpcSystem2
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref PriEpApne1aSystem2
SecurityGroupIds:
- !Ref SgVpcEndpointSystem2
VpceSSMMessagesSystem2:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VpcSystem2
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref PriEpApne1aSystem2
SecurityGroupIds:
- !Ref SgVpcEndpointSystem2
VpceEC2MessagesSystem2:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VpcSystem2
ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref PriEpApne1aSystem2
SecurityGroupIds:
- !Ref SgVpcEndpointSystem2
# ========================
# EC2 Instance
# ========================
Ec2InstanceSystem2:
Type: AWS::EC2::Instance
Properties:
InstanceType: t3.micro
ImageId: !Ref LatestAmiId
SubnetId: !Ref PriAppApne1aSystem2
IamInstanceProfile: !Ref Ec2InstanceProfile
SecurityGroupIds:
- !Ref SgEc2System2
Tags:
- Key: Name
Value: !Sub ec2-system-2
# Common VPC
# ========================
# VPC
# ========================
VpcCommon:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.2.0.0/24
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub vpc-common
# ========================
# Subnets - Endpoint
# ========================
PriEpApne1aCommon:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcCommon
CidrBlock: 10.2.0.0/28
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: common-pri-ep-apne1a
PriEpApne1cCommon:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcCommon
CidrBlock: 10.2.0.16/28
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: common-pri-ep-apne1c
# ========================
# Subnets - TGW
# ========================
PriTgwApne1aCommon:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcCommon
CidrBlock: 10.2.0.32/28
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: common-pri-tgw-apne1a
PriTgwApne1cCommon:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VpcCommon
CidrBlock: 10.2.0.48/28
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: common-pri-tgw-apne1c
# ========================
# TGW Attachment
# ========================
TgwAttachmentCommon:
Type: AWS::EC2::TransitGatewayAttachment
Properties:
TransitGatewayId: !Ref Tgw
VpcId: !Ref VpcCommon
SubnetIds:
- !Ref PriTgwApne1aCommon
- !Ref PriTgwApne1cCommon
# ========================
# Route Tables
# ========================
RouteTableEpCommon:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VpcCommon
Tags:
- Key: Name
Value: !Sub common-rtb-pri-ep
RouteTableTgwCommon:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VpcCommon
Tags:
- Key: Name
Value: !Sub common-rtb-pri-tgw
# --- Route ---
RouteToTgwCommon:
DependsOn: TgwAttachmentCommon
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RouteTableEpCommon
DestinationCidrBlock: 0.0.0.0/0
TransitGatewayId: !Ref Tgw
# --- Route Table Associations ---
RtAssocEpACommon:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriEpApne1aCommon
RouteTableId: !Ref RouteTableEpCommon
RtAssocEpCCommon:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriEpApne1cCommon
RouteTableId: !Ref RouteTableEpCommon
RtAssocTgwACommon:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriTgwApne1aCommon
RouteTableId: !Ref RouteTableTgwCommon
RtAssocTgwCCommon:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PriTgwApne1cCommon
RouteTableId: !Ref RouteTableTgwCommon
# ========================
# Security Groups
# ========================
SgVpcEndpointCommon:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for VPC Endpoints (allow HTTPS from all VPC)
VpcId: !Ref VpcCommon
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 10.0.0.0/8
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 172.0.0.0/24
Tags:
- Key: Name
Value: !Sub sg-vpce-common
# ========================
# VPC Endpoints (SSM)
# ========================
VpceSSMCommon:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VpcCommon
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref PriEpApne1aCommon
SecurityGroupIds:
- !Ref SgVpcEndpointCommon
VpceSSMMessagesCommon:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VpcCommon
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref PriEpApne1aCommon
SecurityGroupIds:
- !Ref SgVpcEndpointCommon
VpceEC2MessagesCommon:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VpcCommon
ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref PriEpApne1aCommon
SecurityGroupIds:
- !Ref SgVpcEndpointCommon
以下のような分散構成から、集約構成への変更をやってみます。
まずは分散構成を作成しますが、その前に今はec2のAPIが利用できないことを確認しましょう。
ec2-system-1か2にセッションマネージャーで接続し、以下を実行してみます。
aws ec2 describe-vpcs
とくに何も出力が出ないかと思います。
冒頭に確認した挙動と同じですね。
それでは、sys1とsys2のVPCにec2のVPCエンドポイントを作成しましょう。
マネジメントコンソールのVPCからエンドポイントを開き、エンドポイントを作成をクリックします。
以下の設定値で作成します。(記載のないものはデフォルト)
| 項目 | 値 |
|---|---|
| 名前タグ | ec2-ep-sys1 |
| タイプ | AWSのサービス |
| サービス | com.amazonaws.ap-northeast-1.ec2 |
| VPC | vpc-system-1 |
| プライベートDNS名を有効化 | ■ |
| サブネット | sys1-pri-ep-apne1a, sys1-pri-ep-apne1c |
| セキュリティグループ | ${StackName}-SgVpcEndpointSystem1-xxxxxx |
【プライベートDNS名の有効化について】
プライベートDNS名を有効化オプションをオンにすると、パブリックDNS名が自動的にVPCエンドポイントのプライベートIPアドレスに自動的に解決されるようになります。
これだけだとわかりにくいですね、逆にオフにしてしまうとどうなるのかというと、VPCエンドポイントのエンドポイント固有のDNS名を指定しないと、エンドポイントに名前解決ができません。
<オンの場合>
以下のように、パブリックDNS名に対して名前解決をするとVPCエンドポイントのIPアドレスが返ってきています。
sh-5.2$ nslookup ec2.ap-northeast-1.amazonaws.com
Server: 10.0.0.2
Address: 10.0.0.2#53
Non-authoritative answer:
Name: ec2.ap-northeast-1.amazonaws.com
Address: 10.0.0.36
Name: ec2.ap-northeast-1.amazonaws.com
Address: 10.0.0.61
したがって、オンの場合は、通常のAPI実行と変わらないコマンドあるいはプログラミングの書き方で実行ができます。
aws ec2 describe-vpcs
<オフの場合>
オフにした場合は、以下のようにエンドポイント固有のDNS名を指定して名前解決をする必要があります。
以下のようにパブリックDNS名に対して名前解決をするとパブリックIPアドレスが返ってきます。
sh-5.2$ nslookup ec2.ap-northeast-1.amazonaws.com
Server: 10.0.0.2
Address: 10.0.0.2#53
Non-authoritative answer:
Name: ec2.ap-northeast-1.amazonaws.com
Address: 52.195.201.182
したがって、エンドポイントを指定せずにAPIを叩くとサービスエンドポイントに到達できずに失敗してしまいます。
以下のようにエンドポイントURLを指定する必要があります。
aws ec2 describe-vpcs --endpoint-url https://vpce-0ec1580e6b38d1c17-51uap8e7.ec2.ap-northeast-1.vpce.amazonaws.com
同様の手順で、sys2のVPCにも作成しましょう。
| 項目 | 値 |
|---|---|
| 名前タグ | ec2-ep-sys2 |
| タイプ | AWSのサービス |
| サービス | com.amazonaws.ap-northeast-1.ec2 |
| VPC | vpc-system-2 |
| プライベートDNS名を有効化 | ■ |
| サブネット | sys2-pri-ep-apne1a, sys2-pri-ep-apne1c |
| セキュリティグループ | ${StackName}-SgVpcEndpointSystem2-xxxxxx |
これで、sys1とsys2のVPCからはAPIが利用できるようになりました。
エンドポイントの集約
いくつかやり方があるかと思いますが、Route53 Private Hosted Zoneを使う方法が最も一般的かと思います。
今回はRoute53 Profilesを使ってみたいので、以下のブログを参考に構築を進めます。
まずは、先ほどと同様の手順でVPCエンドポイントを作成します。
今回はプライベートDNS名を無効にします。(※プライベートホストゾーンでパブリックDNS名に対するレコードを登録するため)
| 項目 | 値 |
|---|---|
| 名前タグ | ec2-ep-common |
| タイプ | AWSのサービス |
| サービス | com.amazonaws.ap-northeast-1.ec2 |
| VPC | vpc-common |
| プライベートDNS名を有効化 | □ |
| サブネット | common-pri-ep-apne1a, common-pri-ep-apne1c |
| セキュリティグループ | ${StackName}-SgVpcEndpointCommon-xxxxxx |
Route53 > ホストゾーン > ホストゾーンの作成をクリックします。
以下の設定値で作成します。
| 項目 | 値 |
|---|---|
| ドメイン名 | ec2.ap-northeast-1.amazonaws.com |
| タイプ | プライベートホストゾーン |
| VPC | vpc-common |
ホストゾーンが作成できたら、VPCエンドポイントへのレコードを登録します。
以下の設定値で作成します。
| 項目 | 値 |
|---|---|
| エイリアス | オン |
| トラフィックのルーティング先 | VPCエンドポイントへのエイリアス、東京、ec2-ep-commonのプライベートDNS名 |
今まではここまでやったらあとは各VPCをプライベートホストゾーンに関連付ければ作業完了でした。
ただ、VPCエンドポイント×アカウント数分やる必要があり、クロスアカウントの場合、CLIでの実行が必須で、承認のCLIも打たないといけないというかなり面倒な作業です。
Route53 Profilesを使うことでこの辺りが簡単になるようなので早速試してみましょう!
以下の手順でプロファイルを作成します。
Route53の画面から、プロファイルを作成します。
以下の設定値で作成します。
| 項目 | 値 |
|---|---|
| プロファイル名 | common-profile |
プロファイルの作成ができたらVPCを関連付けましょう。
次にプライベートホストゾーンを関連付けます。
これで、sys1とsys2のVPCからもAPIが利用できるようになりました。
早速試してみましょう!
sh-5.2$ nslookup ec2.ap-northeast-1.amazonaws.com
Server: 10.0.0.2
Address: 10.0.0.2#53
Non-authoritative answer:
Name: ec2.ap-northeast-1.amazonaws.com
Address: 10.0.0.36
Name: ec2.ap-northeast-1.amazonaws.com
Address: 10.0.0.61
sys1側にVPCエンドポイントがある場合はこちらが優先されるようですね!
ではVPCエンドポイントを削除して、再度試してみます。
sh-5.2$ nslookup ec2.ap-northeast-1.amazonaws.com
Server: 10.0.0.2
Address: 10.0.0.2#53
Non-authoritative answer:
Name: ec2.ap-northeast-1.amazonaws.com
Address: 10.2.0.29
Name: ec2.ap-northeast-1.amazonaws.com
Address: 10.2.0.10
vpc-commonのIPアドレスが返ってきてますね!
切り替え時に、CLIを繰り返し実行していましたが、1回だけ実行が失敗しました。
瞬断はあるようです。(1秒未満)
クロスアカウントの場合は、Route53 ProfileをAWS RAMを使用して、共有し、各アカウント側でProfileにVPCをAssociateするといった手順になります。
Control Towerなど導入されている場合はVPCを作成するタイミングで自動化されるように組んでおきたいですね。
エンドポイントの直接関連付け
記事の手順に沿って実施してみましたが、Route53 ProfilesのタブにVPC endpointsがあったため、同じような手順で関連付けを試したところ、プライベートホストゾーンを作成しなくても解決が可能でした!
実際に利用するならこちらかもしれませんね!
レイテンシに懸念は?
TGWを経由する分、レイテンシに影響が出るのでは?と懸念される方もいらっしゃるかと思います。
分散型と集約型でdescribe-vpcsを100回実行した結果を比較してみます。
| 項目 | 分散型 | 集約型 |
|---|---|---|
| 平均 | 935.77ms | 941.85ms |
| 中央値 | 926.5ms | 929.0ms |
| 最小 | 900ms | 893ms |
| 最大 | 1524ms | 1833ms |
| p95 | 953ms | 952ms |
ほとんど同じレイテンシですので、レイテンシについては問題なさそうです。
Route53 Profilesの費用は?
VPCのアソシエーション数次第ですが、基本的には月額$540くらいと考えておけばよさそうです。
移行時の確認点
- エンドポイントのDNS名を指定している箇所がないか
- 瞬断を許容できるか
まとめ
今回はVPCエンドポイントの集約方法について改めてまとめてみました!
Route53 Profiles非常に便利そうですね!ただし、コストがかなり高めな印象です。
一律同じように名前解決できるようにするような環境の場合は、コストに対してのメリットが少ないかもしれません。
以下のように、いくつかのプロファイルにVPCをグループ分けできる場合は管理面のオーバーヘッドまで考えてコストメリットがありそうです!
【Profile1】
ssm
ssmmessages
ec2messages
【Profile2】
自社ドメインのPHZ
【Profile3】
自社ドメインのPHZ
ssm
ssmmessages
ec2messages
logs
ecr.api
瞬断が発生するのがネックになりそうですが、それを乗り越えてでもコストメリットがある場合も多いと思いますので是非皆さんも検討してみてはいかがでしょうか?
それでは、よいAWSライフを!
弊社では一緒に働く仲間を募集中です!
現在、様々な職種を募集しております。
カジュアル面談も可能ですので、ご連絡お待ちしております!
募集内容等詳細は、是非採用サイトをご確認ください。

















