1つのEC2 (Webサーバー) にバーチャルホストを設定してPrivateLinkでアクセスする検証を行ったのでブログに残します。
環境
以下構成図になります。
左側のサブネットで起動しているEC2 (コンテンツ確認用) から80番ポートでVPCエンドポイントへアクセスした場合は80番ポート用のコンテンツ、8080番でアクセスした場合は8080番用のコンテンツを表示するように設定を行います。
設定
AWSリソース作成
AWSリソースはCloudFormationで作成します。
まずはVPCエンドポイント以外の部分を以下のCloudFormationテンプレートで作成します。
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"
Description: PrivateLink
Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Parameters for VPC
Parameters:
- VPCCIDR1
- VPCCIDR2
- Label:
default: Parameters for Subnet
Parameters:
- PublicSubnet01CIDR
- PublicSubnet02CIDR
- Label:
default: Parameters for EC2
Parameters:
- EC2VolumeSize
- EC2VolumeIOPS
- EC2AMI
- EC2InstanceType
Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
VPCCIDR1:
Default: 10.0.0.0/16
Type: String
VPCCIDR2:
Default: 10.1.0.0/16
Type: String
PublicSubnet01CIDR:
Default: 10.0.0.0/24
Type: String
PublicSubnet02CIDR:
Default: 10.1.0.0/24
Type: String
EC2VolumeSize:
Default: 32
Type: Number
EC2VolumeIOPS:
Default: 3000
Type: Number
EC2AMI:
Default: ami-067871d950411e643
Type: AWS::EC2::Image::Id
EC2InstanceType:
Default: t3.micro
Type: String
Resources:
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------#
VPC1:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCIDR1
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: private-link-service-vpc
VPC2:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCIDR2
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: private-link-client-vpc
# ------------------------------------------------------------#
# Subnet
# ------------------------------------------------------------#
PublicSubnet01:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1a
CidrBlock: !Ref PublicSubnet01CIDR
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: private-link-service-subnet
VpcId: !Ref VPC1
PublicSubnet02:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1a
CidrBlock: !Ref PublicSubnet02CIDR
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: private-link-client-subnet
VpcId: !Ref VPC2
# ------------------------------------------------------------#
# InternetGateway
# ------------------------------------------------------------#
InternetGateway1:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: private-link-service-igw
InternetGatewayAttachment1:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway1
VpcId: !Ref VPC1
InternetGateway2:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: private-link-client-igw
InternetGatewayAttachment2:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway2
VpcId: !Ref VPC2
# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------#
PublicRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC1
Tags:
- Key: Name
Value: private-link-service-rtb
PublicRouteTableRoute1:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway1
RouteTableId: !Ref PublicRouteTable1
PublicRtAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable1
SubnetId: !Ref PublicSubnet01
PublicRouteTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC2
Tags:
- Key: Name
Value: private-link-client-rtb
PublicRouteTableRoute2:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway2
RouteTableId: !Ref PublicRouteTable2
PublicRtAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable2
SubnetId: !Ref PublicSubnet02
# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------#
ServiceEC2SG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: for EC2 Web
GroupName: EC2-web-sg
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
FromPort: -1
IpProtocol: -1
ToPort: -1
SecurityGroupIngress:
- FromPort: 80
IpProtocol: tcp
ToPort: 80
CidrIp: !Ref VPCCIDR1
- FromPort: 8080
IpProtocol: tcp
ToPort: 8080
CidrIp: !Ref VPCCIDR1
Tags:
- Key: Name
Value: EC2-web-sg
VpcId: !Ref VPC1
ClientEC2SG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: for EC2 Client
GroupName: EC2-client-sg
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
FromPort: -1
IpProtocol: -1
ToPort: -1
Tags:
- Key: Name
Value: EC2-client-sg
VpcId: !Ref VPC2
PrivateLinkSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: for private link
GroupName: privatelink-sg
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
FromPort: -1
IpProtocol: -1
ToPort: -1
SecurityGroupIngress:
- SourceSecurityGroupId: !Ref ClientEC2SG
FromPort: 80
IpProtocol: tcp
ToPort: 80
- SourceSecurityGroupId: !Ref ClientEC2SG
FromPort: 8080
IpProtocol: tcp
ToPort: 8080
Tags:
- Key: Name
Value: privatelink-sg
VpcId: !Ref VPC2
# ------------------------------------------------------------#
# IAM
# ------------------------------------------------------------#
EC2IAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
RoleName: iam-role-ec2
Tags:
- Key: Name
Value: iam-role-ec2
EC2IAMInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: iam-instanceprofile-ec2
Roles:
- !Ref EC2IAMRole
# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------#
ServiceEC2:
Type: AWS::EC2::Instance
Properties:
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
DeleteOnTermination: true
Encrypted: true
Iops: !Ref EC2VolumeIOPS
VolumeSize: !Ref EC2VolumeSize
VolumeType: gp3
DisableApiTermination: false
IamInstanceProfile: !Ref EC2IAMInstanceProfile
ImageId: !Ref EC2AMI
InstanceType: !Ref EC2InstanceType
NetworkInterfaces:
- DeleteOnTermination: true
DeviceIndex: 0
GroupSet:
- !Ref ServiceEC2SG
SubnetId: !Ref PublicSubnet01
Tags:
- Key: Name
Value: private-link-service-ec2
UserData: !Base64 |
#!/bin/bash
dnf install httpd -y
systemctl start httpd
systemctl enable httpd
echo "Private Link Port 80" > /var/www/html/index.html
mkdir /var/www/port8080
echo "Private Link Port 8080" > /var/www/port8080/index.html
touch /etc/httpd/conf.d/port8080.conf
echo "Listen 8080" > /etc/httpd/conf.d/port8080.conf
echo "<VirtualHost *:8080>" >> /etc/httpd/conf.d/port8080.conf
echo " DocumentRoot /var/www/port8080" >> /etc/httpd/conf.d/port8080.conf
echo "</VirtualHost>" >> /etc/httpd/conf.d/port8080.conf
systemctl reload httpd
ClientEC2:
Type: AWS::EC2::Instance
Properties:
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
DeleteOnTermination: true
Encrypted: true
Iops: !Ref EC2VolumeIOPS
VolumeSize: !Ref EC2VolumeSize
VolumeType: gp3
DisableApiTermination: false
IamInstanceProfile: !Ref EC2IAMInstanceProfile
ImageId: !Ref EC2AMI
InstanceType: !Ref EC2InstanceType
NetworkInterfaces:
- DeleteOnTermination: true
DeviceIndex: 0
GroupSet:
- !Ref ClientEC2SG
SubnetId: !Ref PublicSubnet02
Tags:
- Key: Name
Value: private-link-client-ec2
# ------------------------------------------------------------#
# NLB
# ------------------------------------------------------------#
NLB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
IpAddressType: ipv4
LoadBalancerAttributes:
- Key: deletion_protection.enabled
Value: false
Name: private-link-service-nlb
Scheme: internal
Subnets:
- !Ref PublicSubnet01
Tags:
- Key: Name
Value: private-link-service-nlb
Type: network
TargetGroup1:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
DependsOn:
- ServiceEC2
Properties:
HealthCheckEnabled: true
HealthCheckIntervalSeconds: 30
HealthCheckPort: traffic-port
HealthCheckProtocol: TCP
HealthyThresholdCount: 5
IpAddressType: ipv4
Name: private-link-service-nlb-tg-80
Port: 80
Protocol: TCP
TargetGroupAttributes:
- Key: preserve_client_ip.enabled
Value: true
Targets:
- Id: !Ref ServiceEC2
Port: 80
TargetType: instance
VpcId: !Ref VPC1
TargetGroup2:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
DependsOn:
- ServiceEC2
Properties:
HealthCheckEnabled: true
HealthCheckIntervalSeconds: 30
HealthCheckPort: traffic-port
HealthCheckProtocol: TCP
HealthyThresholdCount: 5
IpAddressType: ipv4
Name: private-link-service-nlb-tg-8080
Port: 8080
Protocol: TCP
TargetGroupAttributes:
- Key: preserve_client_ip.enabled
Value: true
Targets:
- Id: !Ref ServiceEC2
Port: 8080
TargetType: instance
VpcId: !Ref VPC1
Listener1:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroup1
Type: forward
LoadBalancerArn: !Ref NLB
Port: 80
Protocol: TCP
Listener2:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroup2
Type: forward
LoadBalancerArn: !Ref NLB
Port: 8080
Protocol: TCP
# ------------------------------------------------------------#
# VPC Endpoint Service
# ------------------------------------------------------------#
VPCEndpointService:
DependsOn:
- NLB
Type: AWS::EC2::VPCEndpointService
Properties:
AcceptanceRequired: true
NetworkLoadBalancerArns:
- !Ref NLB
Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
VPC2ID:
Value: !Ref VPC2
Export:
Name: VPC2ID
PrivateLinkSGID:
Value: !Ref PrivateLinkSG
Export:
Name: PrivateLinkSGID
PublicSubnet02ID:
Value: !Ref PublicSubnet02
Export:
Name: PublicSubnet02ID
上記CloudFormationテンプレートではVPCエンドポイント以外の部分が作成されます。
369~433行目のターゲットグループ、リスナールールで80番ポート、8080番ポートを振り分ける設定を入れています。
デプロイは以下のコマンドを実行します。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --capabilities CAPABILITY_NAMED_IAM
デプロイが完了したらVPCエンドポイントを作成していきます。
以下のCloudFormationテンプレートで作成します。
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"
Description: Client Stack
Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
ServiceName:
Type: String
Resources:
# ------------------------------------------------------------#
# VPC Endpoint
# ------------------------------------------------------------#
PrivateLink:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: false
ServiceName: !Ref ServiceName
VpcId: !ImportValue VPC2ID
SubnetIds:
- !ImportValue PublicSubnet02ID
SecurityGroupIds:
- !ImportValue PrivateLinkSGID
上記CloudFormationテンプレートではVPCエンドポイントを作成しています。
パラメータにあるServiceNameはマネジメントコンソールからVPC>エンドポイントサービスの詳細から確認が可能です。
デプロイは以下のコマンドを実行します。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=ServiceName,ParameterValue=VPCエンドポイントサービス名
デプロイが完了したらマネジメントコンソールからVPC>エンドポイントサービス>該当のVPCエンドポイントサービス選択>エンドポイント接続>エンドポイント接続リクエストの承諾でリクエストの承諾を行います。
しばらくすると「状態」がPendingからAvailableになります。
ここまででリソースの作成が完了です。
動作確認
以下の手順でSession Managerを使用してEC2 (private-link-client-ec2) へ接続します。
セッションを開始する (Amazon EC2 コンソール)
接続ができたら以下のコマンドで80番ポートと8080番ポートにアクセスします。
curl http://VPCエンドポイントDNS名:80
curl http://VPCエンドポイントDNS名:8080
成功すると80番ポートの時は「Private Link Port 80」、8080番ポートの時は「Private Link Port 8080」とレスポンスが返ってきます。
さいごに
PrivateLinkの後ろにいるのがNLBなのでポート番号での振り分けは可能だろうと思いやってみました。
NLBのターゲットにALBが設定可能なのでURLでの振り分けができるのか今度検証してみたいと思いました。