職場でPrivateLinkという単語を聞き、「PrivateLinkってエンドポイント貼るやつっすよね!」って発言したところ、そんな単純な概念ではないと冷静に返されました。。
考えてみたら、今までエンドポイントを強く意識して環境構築をしたことって無かったので、改めてエンドポイントを意識したハンズオンでもやってみるかと本記事を執筆した次第です。
概要
本記事の目的
- PrivateLinkを利用したプライベートなWeb環境を構築する。
- 環境構築にはCloudFormationを使用する。
- クライアントクラスターのEC2からプロパイダークラスターのEC2(Webサーバ)に対して、プライベートリンクを経由したcurlコマンドによる疎通確認を行う。
インフラ構成図
- 本記事で構築するインフラ構成を下図の通りとする。
構築リソース
- プロパイダークラスター(アクセス先)
- ネットワーク環境を構築(VPC,Subnet,Routetable)
- サーバ環境を構築(SG,EC2,NLB,EndPointService)
- クライアントクラスター(アクセス元)
- ネットワーク環境を構築(VPC,Subnet,Routetable)
- サーバ環境を構築(SG,EC2,EndPoint)
PrivateLinkについて
PrivateLinkとは?
PrvateLinkとは、分かりやすく言うと「AWSへのAPIアクセスをインターネットを経由せずに行えるインターフェースタイプのVPCエンドポイント」です。
EC2からAWS CLIを使用してAWSのAPIを叩く際、APIのリクエストのトラフィックはインターネットを通ります。そのため、インターネットゲートウェイがないとインターネットに出られず、APIが叩けない事になります。
しかし、PrivateLinkを使えば、インターネットに出ずにEC2のAPIを叩くことができるようになります。
どういう時に使うの?
インターネットを経由せずにAWSのAPIを叩くリソースを手軽に構築する際に利用します。
PrivateLinkの実態はエンドポイントなので、VPCピアリングのようにNWセグメント同士で繋げるわけではないので、余計な通信経路が発生することはありません。
ハンズオン
プロバイダークラスターの構築
以下のCFnテンプレートを使用し、環境構築を行います。
- CloudFormationテンプレート
- network.yml(ネットワーク環境を構築)
- server.yml(サーバ環境を構築)
CloudFormationテンプレート
AWSTemplateFormatVersion: "2010-09-09"
Description: "Create Provider-Network-Env to PrivateLink-PrivateWeb-dev-Env"
# ------------------------------------------------------------
# Metadate
# ------------------------------------------------------------
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Parameters:
- PJPrefix
- VPCCIDR
- PrivateSubnetCIDR
# ------------------------------------------------------------
# Input Parameters
# ------------------------------------------------------------
Parameters:
### Project Prefix ###
PJPrefix:
Type: String
### VPC ###
VPCCIDR:
Type: String
Default: "192.168.0.0/20"
### Private Subnet ###
PrivateSubnetCIDR:
Type: String
Default: "192.168.1.0/24"
### Resources ###
Resources:
# ------------------------------------------------------------
# VPC
# ------------------------------------------------------------
VPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsSupport: "true"
EnableDnsHostnames: "true"
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-Provider-VPC"
# ------------------------------------------------------------
# Subnet
# ------------------------------------------------------------
### Private Subnet ###
PrivateSubnet:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1a"
CidrBlock: !Ref PrivateSubnetCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-Provider-PrivateSubnet"
# ------------------------------------------------------------
# RouteTable
# ------------------------------------------------------------
### Privatec Subnet Routing ###
PrivateRTB:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-Provider-PrivateRoute"
PrivateSubnetRTBAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateRTB
# ------------------------------------------------------------
# Output Parameter
# ------------------------------------------------------------
Outputs:
### VPC ###
VPC:
Value: !Ref VPC
Export:
Name: !Sub "${PJPrefix}-Provider-VPC"
### Private Subnet ###
PrivateSubnet:
Value: !Ref PrivateSubnet
Export:
Name: !Sub "${PJPrefix}-Provider-PrivateSubnet"
AWSTemplateFormatVersion: "2010-09-09"
Description: "Create Provider-Server-Env to PrivateLink-PrivateWeb-dev-Env"
# ------------------------------------------------------------
# Metadate
# ------------------------------------------------------------
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Parameters:
- PJPrefix
- EC2AMI
# ------------------------------------------------------------
# Input Parameters
# ------------------------------------------------------------
Parameters:
### Project Prefix ###
PJPrefix:
Type: 'String'
EC2AMI:
Type: 'String'
InstanceType:
Type: 'String'
### Resources ###
Resources:
# ------------------------------------------------------------
# SecurityGroup
# ------------------------------------------------------------
### WebServer Security Group ###
WebSG:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "http"
GroupName: !Sub "${PJPrefix}-Web-SG"
VpcId: { "Fn::ImportValue": !Sub "${PJPrefix}-Provider-VPC" }
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-Provider-Web-SG"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort : 80
ToPort : 80
CidrIp: 0.0.0.0/0
# ------------------------------------------------------------
# EC2
# ------------------------------------------------------------
### WebServer ###
WebServerA:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref EC2AMI
InstanceType: !Ref InstanceType
NetworkInterfaces:
- AssociatePublicIpAddress: "false"
DeviceIndex: "0"
SubnetId: { "Fn::ImportValue": !Sub "${PJPrefix}-Provider-PrivateSubnet" }
GroupSet:
- !Ref WebSG
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-Provider-WebServerA"
UserData:
Fn::Base64: !Sub |
#!/bin/bash
sudo yum -y update
sudo yum -y install httpd
sudo systemctl start httpd
sudo systemctl enable httpd
sudo echo "chibiharu's Qiita Apache Test Page For Success" > /var/www/html/index.html
# ------------------------------------------------------------
# TargetGroup
# ------------------------------------------------------------
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
VpcId: { "Fn::ImportValue": !Sub "${PJPrefix}-Provider-VPC" }
Name: !Sub "${PJPrefix}-Provider-TG"
Protocol: TCP
Port: 80
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-Provider-TG"
Targets:
- Id: !Ref WebServerA
Port: 80
# ------------------------------------------------------------
# NetworkLoadBalancer
# ------------------------------------------------------------
NLB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub "${PJPrefix}-Provider-NLB"
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-Provider-NLB"
Scheme: "internal"
LoadBalancerAttributes:
- Key: "deletion_protection.enabled"
Value: false
Subnets:
- { "Fn::ImportValue": !Sub "${PJPrefix}-Provider-PrivateSubnet" }
Type: network
NLBListenerHTTP:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroup
Type: forward
LoadBalancerArn: !Ref NLB
Port: 80
Protocol: TCP
# ------------------------------------------------------------
# EndPointService
# ------------------------------------------------------------
VPCEndpointService:
Type: AWS::EC2::VPCEndpointService
Properties:
AcceptanceRequired: true
NetworkLoadBalancerArns:
- !Ref NLB
# ------------------------------------------------------------
# Output Parameters
# ------------------------------------------------------------
Outputs:
VPCEndpointService:
Value: { "Fn::Sub": "com.amazonaws.vpce.${AWS::Region}.${VPCEndpointService}" }
Export:
Name: !Sub "${PJPrefix}-VPCEndpointService"
クライアントクラスターの構築
以下のCFnテンプレートを使用し、環境構築を行います。
- CloudFormationテンプレート
- network.yml(ネットワーク環境を構築)
- server.yml(サーバ環境を構築)
CloudFormationテンプレート
AWSTemplateFormatVersion: "2010-09-09"
Description: "Create Client-Network-Env to PrivateLink-PrivateWeb-dev-Env"
# ------------------------------------------------------------
# Metadate
# ------------------------------------------------------------
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Parameters:
- PJPrefix
- VPCCIDR
- PrivateSubnetCIDR
# ------------------------------------------------------------
# Input Parameters
# ------------------------------------------------------------
Parameters:
### Project Prefix ###
PJPrefix:
Type: String
### VPC ###
VPCCIDR:
Type: String
Default: "192.168.0.0/20"
### Private Subnet ###
PrivateSubnetCIDR:
Type: String
Default: "192.168.1.0/24"
### Resources ###
Resources:
# ------------------------------------------------------------
# VPC
# ------------------------------------------------------------
VPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsSupport: "true"
EnableDnsHostnames: "true"
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-Client-VPC"
# ------------------------------------------------------------
# Subnet
# ------------------------------------------------------------
### Private Subnet ###
PrivateSubnet:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1a"
CidrBlock: !Ref PrivateSubnetCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-Client-PrivateSubnet"
# ------------------------------------------------------------
# RouteTable
# ------------------------------------------------------------
### Privatec Subnet Routing ###
PrivateRTB:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-Client-PrivateRoute"
PrivateSubnetRTBAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateRTB
# ------------------------------------------------------------
# Output Parameter
# ------------------------------------------------------------
Outputs:
### VPC ###
VPC:
Value: !Ref VPC
Export:
Name: !Sub "${PJPrefix}-Client-VPC"
### Private Subnet ###
PrivateSubnet:
Value: !Ref PrivateSubnet
Export:
Name: !Sub "${PJPrefix}-Client-PrivateSubnet"
AWSTemplateFormatVersion: "2010-09-09"
Description: "Create Client-Server-Env to PrivateLink-PrivateWeb-dev-Env"
# ------------------------------------------------------------
# Metadate
# ------------------------------------------------------------
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Parameters:
- PJPrefix
- EC2AMI
- InstanceType
- KeyName
# ------------------------------------------------------------
# Input Parameters
# ------------------------------------------------------------
Parameters:
### Project Prefix ###
PJPrefix:
Type: 'String'
EC2AMI:
Type: 'String'
InstanceType:
Type: 'String'
KeyName:
Type: 'AWS::EC2::KeyPair::KeyName'
### Resources ###
Resources:
# ------------------------------------------------------------
# SecurityGroup
# ------------------------------------------------------------
### WebServer Security Group ###
ClientSG:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "http"
GroupName: !Sub "${PJPrefix}-Client-SG"
VpcId: { "Fn::ImportValue": !Sub "${PJPrefix}-Client-VPC" }
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-Client-SG"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort : 22
ToPort : 22
CidrIp: 0.0.0.0/0
# ------------------------------------------------------------
# EC2
# ------------------------------------------------------------
### ClientServer ###
ClientServer:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref EC2AMI
InstanceType: !Ref InstanceType
KeyName: !Ref KeyName
NetworkInterfaces:
- AssociatePublicIpAddress: "false"
DeviceIndex: "0"
SubnetId: { "Fn::ImportValue": !Sub "${PJPrefix}-Client-PrivateSubnet" }
GroupSet:
- !Ref ClientSG
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-ClientServer"
# ------------------------------------------------------------
# EndPoint
# ------------------------------------------------------------
VpcEndpoint:
Type: "AWS::EC2::VPCEndpoint"
Properties:
VpcId: { "Fn::ImportValue": !Sub "${PJPrefix}-Client-VPC" }
ServiceName: { "Fn::ImportValue": !Sub "${PJPrefix}-VPCEndpointService" }
VpcEndpointType: "Interface"
SubnetIds:
- { "Fn::ImportValue": !Sub "${PJPrefix}-Client-PrivateSubnet" }
SecurityGroupIds:
- !Ref ClientSG
動作確認
動作確認にはcurlコマンドを使用します。
ClientServerからClient側で作成したエンドポイントのDNS名宛にcurlコマンドを実行し、Provider側への接続試験を行います。
エンドポイント経由での接続試験
ClientServerのコンソール上へ接続します。
以下のコマンドを実行し、WebServerのテストページが返ってくることを確認します。
# Client側で作成したエンドポイントのDNS名宛にcurlコマンドを実行
$ curl vpce-xxxxxxxxxxx.vpce-svc-xxxxxxxxxxxxxxx.ap-northeast-1.vpce.amazonaws.com
chibiharu's Qiita Apache Test Page For Success WebServerA
まとめ
PrivateLinkは非常に便利で手軽に構築できる反面、作り過ぎてPrivateLink(VPC EndPointとNLB)まみれになってしまうことがあります。
ご利用は計画的に..ってね。
コメント