この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
もう2度とググりたくない
こんにちは!AWS事業本部のおつまみです。
皆さん「VPCエンドポイントとAWS PrivateLinkの違い」を自分の言葉で説明できますか?私は時折、記憶喪失になり違いを忘れてしまいます。
もう2度とググりたくないという思いがあり、今回は違いをまとめました。
実際にハンズオンできるよう初心者向けの内容となっているため、VPCエンドポイントやPrivateLinkの知識が全くない方のお役に立てればと思います!
結論
- VPCエンドポイント
- VPCと他サービス間でプライベートな接続を提供するコンポーネント
- サービス利用側のVPC内で作成
- AWS PrivateLink
- プライベート接続を介したサービスを提供するためのサービス
- 以下の2つがセットとなり、AWS PrivateLinkが提供されている。
- VPCエンドポイント(サービス利用側のVPC内で作成)
- VPCエンドポイントサービス(サービス提供側のVPC内で作成)
すみません。文章だけはわかりにくいですね。
簡単に図で表しました。
ざっくりAWS PrivateLinkの接続にVPCエンドポイントが使われていると理解できればOKです。
もう少し深く掘り下げてみます。
VPCエンドポイントとは
VPCと他サービス間でプライベートな接続を提供するコンポーネントです。
サービス利用側のVPC内に作成されます。
2022/9/29時点では、これら3種類のVPCエンドポイントが提供されています。
1. インターフェイスエンドポイント
一部のAWSサービスやNLBを介した独自サービス、サポートされているAWS Marketplaceサービスにプライベートに接続する
2. Gateway Load Balancerエンドポイント
Gateway Load Balancerを介したサービスにプライベートに接続する
3. ゲートウェイエンドポイント
S3及びDynamoDBへのアクセスをプライベートに接続する
ポイントは各タイプによって通信経路が異なることです。
1のインターフェイスエンドポイント及び2のGateway Load Balancerエンドポイントの場合は、VPC内にプライベートアドレスをもつENI(Elastic Network Interface)が作成されます。
このENI経由でサービス提供側のVPCエンドポイントサービスに接続されます。
3のゲートウェイエンドポイントの場合は、サービス利用側のルートテーブルにエンドポイントのルーティング設定が必要となります。VPCエンドポイントサービスは作成されません。
詳細な違いは、こちらのブログが大変わかりやすかったです。
次にVPCエンドポイントサービスをみてみましょう。
VPCエンドポイントサービスとは
サービス提供側のVPC内にあるサービスをPrivateLink経由で公開する場合の設定です。
この設定がないと、インターフェイスエンドポイントもしくはGateway Load Balancerエンドポイントにサービスを提供できません。
またゲートウェイエンドポイントの場合はVPCエンドポイントサービスは作成されません。(2回目)
次にAWS PrivateLinkをみてみましょう。
AWS PrivateLinkとは
プライベート接続を介したサービスを提供するためのサービスです。
ネットワーク間のトラフィックをインターネット経由せずに、プライベートに通信することができます。
上記で述べた、以下の2つがセットとなり提供されています。
・VPCエンドポイント(サービス利用側のVPC内で作成)
・VPCエンドポイントサービス(サービス提供側のVPC内で作成)
ちなみにAWS PrivateLinkはAWSのサービス名でありません!
マネジメントコンソールで「AWS PrivateLink」と検索すると、[機能]に以下の2つがヒットします。
これがPrivateLinkの実体ということです。
実際にやってみた
VPCエンドポイントとAWS PrivateLinkの違いは理解できたところで、VPCエンドポイントによるAWS PrivateLink接続を試したいと思います!
今回の構成図です。
ゴールはサービス利用側(Consumer)のEC2からサービス提供側(Provider)のEC2に接続し、Apacheのテストページが表示されることです。
事前準備
ただ全てマネジメントコンソールで作成すると、かなり骨が折れます。 そのため、この構成を下準備として、CloudFormationで以下の構成を事前に作成しておきます。
Consumer.yml
Consumer.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Consumer Template."
Parameters:
# ------------------------------------------------------------#
# Common
# ------------------------------------------------------------#
Prefix:
Type: String
Default: "prefix"
# ------------------------------------------------------------#
# Network
# ------------------------------------------------------------#
VpcCidr:
Type: String
Default: "10.0.0.0/16"
PrivateSubnetCidr:
Type: String
Default: "10.0.0.0/24"
# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------#
EC2InstanceName:
Type: String
Default: "ec2"
EC2InstanceAMI:
Type: AWS::EC2::Image::Id
Default: "ami-078296f82eb463377" # Amazon Linux 2 AMI (HVM) - Kernel 5.10, SSD Volume Type
EC2InstanceInstanceType:
Type: String
Default: "t3.nano"
EC2InstanceVolumeType:
Type: String
Default: "gp2"
EC2InstanceVolumeSize:
Type: String
Default: "8"
Resources:
# ------------------------------------------------------------#
# Network
# ------------------------------------------------------------#
Vpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${Prefix}-vpc
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PrivateSubnetCidr
VpcId: !Ref Vpc
AvailabilityZone:
Fn::Select:
- "0"
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: !Sub ${Prefix}-private-subnet
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
Tags:
- Key: Name
Value: !Sub "${Prefix}-table"
PrivateSubnetTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateRouteTable
SecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
VpcId: !Ref Vpc
GroupName: !Sub "${Prefix}-sg"
GroupDescription: "-"
Tags:
- Key: "Name"
Value: !Sub "${Prefix}-sg"
# ------------------------------------------------------------#
# VPC Endpoint
# ------------------------------------------------------------#
SsmVpcEndpointSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub ${Prefix}-ssm-vpc-endpoint-sg
GroupName: !Sub ${Prefix}-ssm-vpc-endpoint-sg
VpcId: !Ref Vpc
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
SourceSecurityGroupId: !Ref SecurityGroup
Tags:
- Key: Name
Value: !Sub ${Prefix}-ssm-vpc-endpoint-sg
SsmVpcEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
VpcId: !Ref Vpc
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref SsmVpcEndpointSecurityGroup
SsmMessagesVpcEndpointSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub ${Prefix}-ssmmessages-vpc-endpoint-sg
GroupName: !Sub ${Prefix}-ssmmessages-vpc-endpoint-sg
VpcId: !Ref Vpc
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
SourceSecurityGroupId: !Ref SecurityGroup
Tags:
- Key: Name
Value: !Sub ${Prefix}-ssmmessages-vpc-endpoint-sg
SsmMessagesVpcEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
VpcId: !Ref Vpc
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref SsmMessagesVpcEndpointSecurityGroup
# ------------------------------------------------------------#
# Ec2InstanceProfile
# ------------------------------------------------------------#
Ec2Role:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${Prefix}-ec2-role
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:
InstanceProfileName: !Sub ${Prefix}-ec2-instance-profile
Roles:
- !Ref Ec2Role
# ------------------------------------------------------------#
# EC2Instance
# ------------------------------------------------------------#
EC2Instance:
Type: "AWS::EC2::Instance"
Properties:
Tags:
- Key: Name
Value: !Sub "${Prefix}-${EC2InstanceName}"
ImageId: !Ref EC2InstanceAMI
InstanceType: !Ref EC2InstanceInstanceType
IamInstanceProfile: !Ref Ec2InstanceProfile
DisableApiTermination: false
EbsOptimized: false
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
DeleteOnTermination: true
VolumeType: !Ref EC2InstanceVolumeType
VolumeSize: !Ref EC2InstanceVolumeSize
SecurityGroupIds:
- !Ref SecurityGroup
SubnetId: !Ref PrivateSubnet
UserData: !Base64 |
#! /bin/bash
yum update -y
Provider.yml
Provider.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Provider Template."
Parameters:
# ------------------------------------------------------------#
# Common
# ------------------------------------------------------------#
Prefix:
Type: String
Default: "prefix"
# ------------------------------------------------------------#
# Network
# ------------------------------------------------------------#
VpcCidr:
Type: String
Default: "10.1.0.0/16"
PrivateSubnetCidr:
Type: String
Default: "10.1.0.0/24"
# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------#
EC2InstanceName:
Type: String
Default: "ec2"
EC2InstanceAMI:
Type: AWS::EC2::Image::Id
Default: "ami-078296f82eb463377" # Amazon Linux 2 AMI (HVM) - Kernel 5.10, SSD Volume Type
EC2InstanceInstanceType:
Type: String
Default: "t3.nano"
EC2InstanceVolumeType:
Type: String
Default: "gp2"
EC2InstanceVolumeSize:
Type: String
Default: "8"
Resources:
# ------------------------------------------------------------#
# Network
# ------------------------------------------------------------#
Vpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${Prefix}-vpc
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PrivateSubnetCidr
VpcId: !Ref Vpc
AvailabilityZone:
Fn::Select:
- "0"
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: !Sub ${Prefix}-private-subnet
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
Tags:
- Key: Name
Value: !Sub "${Prefix}-table"
PrivateSubnetTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateRouteTable
SecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
VpcId: !Ref Vpc
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: "0.0.0.0/0"
GroupName: !Sub "${Prefix}-sg"
GroupDescription: "-"
Tags:
- Key: "Name"
Value: !Sub "${Prefix}-sg"
# ------------------------------------------------------------#
# VPC Endpoint
# ------------------------------------------------------------#
VPCS3Endpoint:
Type: "AWS::EC2::VPCEndpoint"
Properties:
RouteTableIds:
- !Ref PrivateRouteTable
ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
VpcEndpointType: Gateway
VpcId: !Ref Vpc
# ------------------------------------------------------------#
# EC2Instance
# ------------------------------------------------------------#
EC2Instance:
Type: "AWS::EC2::Instance"
Properties:
Tags:
- Key: Name
Value: !Sub "${Prefix}-${EC2InstanceName}"
ImageId: !Ref EC2InstanceAMI
InstanceType: !Ref EC2InstanceInstanceType
DisableApiTermination: false
EbsOptimized: false
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
DeleteOnTermination: true
VolumeType: !Ref EC2InstanceVolumeType
VolumeSize: !Ref EC2InstanceVolumeSize
SecurityGroupIds:
- !Ref SecurityGroup
SubnetId: !Ref PrivateSubnet
UserData: !Base64 |
#! /bin/bash
sudo yum update -y
sudo yum -y install httpd
sudo systemctl start httpd
sudo systemctl enable httpd
# ------------------------------------------------------------#
# NLB
# ------------------------------------------------------------#
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
VpcId: !Ref Vpc
Name: !Sub "${Prefix}-tg"
Protocol: TCP
Port: 80
Tags:
- Key: Name
Value: !Sub "${Prefix}-tg"
Targets:
- Id: !Ref EC2Instance
Port: 80
NLB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub "${Prefix}-nlb"
Tags:
- Key: Name
Value: !Sub "${Prefix}-nlb"
Scheme: "internal"
LoadBalancerAttributes:
- Key: "deletion_protection.enabled"
Value: false
Subnets:
- !Ref PrivateSubnet
Type: network
NLBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroup
Type: forward
LoadBalancerArn: !Ref NLB
Port: 80
Protocol: TCP
CloudFormationテンプレートはこちらの記事で公開されているものをカスタマイズさせていただきました。 ありがとうございます!!
いよいよAWS PrivateLink接続のための手順となります。今回は吹き出しの部分をマネジメントコンソールを触りながら、理解を深めたいと思います。
1. VPCエンドポイントサービスの作成
VPCサービスより「エンドポイントサービス」を選択し、「エンドポイントサービスの作成」をクリックします。
以下のように任意の名前を入力し、「ロードバランサーのタイプ」を「ネットワーク」にし、ロードバランサーにはCloudFormationで作成されたNLBを指定します。
「承諾が必要」にチェックを入れておきます。
ここにチェックを入れることで、第3者からの接続を防ぐことができます。
チェック後、「作成」をクリックします。
Consumer側でエンドポイント作成時に使用する「サービス名」を控えておきます。
2. VPCエンドポイントの作成
VPCサービスより「エンドポイント」を選択します。
既にCloudFormationで作成された S3のゲートウェイエンドポイントとSession Managerを使用するためのエンドポイントが作成されています。「エンドポイントの作成」をクリックします。
以下のように任意の名前を入力し、「サービスカテゴリ」に「その他のエンドポイントサービス」を指定します。「サービス名」には先程控えておいたサービス名を指定します。入力後、「サービスの検証」をクリックします。
「サービス名が検証されました」が出力されればOKです。
VPCやサブネットはConsumer側のEC2が配置されているものを指定します。
NLBのエンドポイント用のセキュリティグループを作成していなかったため、今回はdefaultを指定しました。
※エンドポイント用のセキュリティグループを作成することを推奨します。
後ほど、 Consumer側のEC2インスタンスから接続できるようにセキュリティグループを修正します。
セキュリティグループ指定後、「エンドポイントを作成」をクリックします。
作成したエンドポイントの「ステータス」が「pendingAcceptance」になりました。
話は逸れますが、この画面からインターフェイスエンドポイントのみENIが作成され、ゲートウェイエンドポイントにはENIが作成されてないことがわかります。
先にVPCエンドポイントのセキュリティグループを修正しておきます。
エンドポイントのセキュリティグループよりグループIDを指定します。
セキュリティグループのインバウンドルールにCunsumer側のEC2インスタンスに設定されているプライベートIPアドレスからのHTTP通信を許可するように設定します。
3. エンドポイントの承諾
再度VPCサービスより「エンドポイントサービス」を選択し、作成されているエンドポイントサービスの「エンドポイント接続」のタブをクリックします。
状態が「pendingAcceptance」中のエンドポイントが1つ紐づいていることがわかります。
「アクション」から「エンドポイント接続リクエストの承諾」を行います。
承諾確認画面が表示されるため、「承諾」を入力し、「承諾」をクリックします。
しばらくすると、作成したエンドポイントの「ステータス」が「使用可能」になります。
作成された「エンドポイント」のDNS名より、NLBを経由してProvider側のEC2にアクセスできるようになります。
ちなみにDNS名には、「リージョン固有のDNSホスト名」と「ゾーンごとのDNSホスト名」の2つが発行されています。どちらからでも接続できますが、原則的にはリージョンDNS名を利用し、AZ跨ぎのレイテンシや通信料金が気になる場合のみゾーン固有のDNSを利用するのがよいようです。
4. サービス利用側のEC2から接続確認
最後にConsumer側のEC2インスタンスからの接続を確認します。
EC2サービスからConsumer側のEC2インスタンスを選択し、「接続」をクリックします。
「セッションマネージャー」のタブから「接続」をクリックします。
先程控えておいた「エンドポイント」のDNS名を指定して、アクセスしてみます。
無事、Apacheのテストページが表示されることが確認できました!
最後に
今回はVPCエンドポイントとAWS PrivateLinkの違いを実際に構築して理解してみました。
恥ずかしながら、今までインターフェースエンドポイントとAWS PrivateLinkは同義だと思い込んでいました。。。
実際に構築したことで違いを知るきっかけになったので、やはり実践が1番ですね!
おそらくもう2度とググることはないと思います。
今回は触れませんでしが、AWS PrivateLink・VPCエンドポイントを利用するメリットやユースケースはこちらの記事がわかりやすかったので参考にしてください。
最後までお読みいただきありがとうございました! この記事が誰かのお役に立てば幸いです。
以上、おつまみ(@AWS11077)でした!
参考
AWS PrivateLink および VPC エンドポイント - Amazon Virtual Private Cloud