この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは!DA(データアナリティクス)事業本部 サービスソリューション部の大高です。
AWS PrivateLinkを利用すると、ネットワーク間をトラフィックをインターネットを経由させることなく、プライベートに通信することができます。
利用できる通信は、VPC、AWSのサービス、オンプレミスネットワークに対する通信がありますが、今回はVPC間のAWS PrivateLinkを試してみたいと思います。
やりたいこと
最終的に試したい構成は以下のような構成になります。
「Service Consumer VPC 内の EC2 インスタンス」から、「Service Provider VPC 内の EC 2インスタンス」へ簡単な通信を行い、うまく通信できているかを試したいです。
下準備
この構成への下準備として、CloudFormationテンプレートを利用して以下のような構成を作成します。
この構成を作成した後に、残りは管理コンソールから手動で設定を行い、理解を深めたいと思います。
CloudFormationテンプレートで2つのVPC環境を作成する
ということで、以下のテンプレートを利用して2つのVPC、Private Subnet、EC2を作成しました。
なお、主旨から外れるため構成図からは省略していますが、EC2への接続にセッションマネージャーを利用したかったため、セッションマネージャ用のVPC Endpointも併せて作成しています。
simple-private-server.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Simple Private Server 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-0ab0bbbd329f565e6" # 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
SecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
VpcId: !Ref Vpc
GroupName: !Sub "${Prefix}-sg"
GroupDescription: "-"
Tags:
- Key: "Name"
Value: !Sub "${Prefix}-sg"
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
通信テスト用のプログラムを用意する
次に、通信テスト用のプログラムを用意します。
今回は簡単な通信テストなので、以下のようなPythonプログラムを用意します。
app.py
from http.server import HTTPServer, SimpleHTTPRequestHandler
import socket
class MyHandler(SimpleHTTPRequestHandler):
def do_GET(self):
res = 'Hello!\n'.encode('utf-8')
self.send_response(200)
self.end_headers()
self.wfile.write(res)
host = socket.gethostname()
port = 8000
httpd = HTTPServer((host, port), MyHandler)
print(f'Available on: http://{host}:{port}')
httpd.serve_forever()
実際に、このプログラムを動かすと以下のように表示されます。
$ python app.py
Available on: http://HOSTNAME:8000
この状態で、サービスにアクセスすると以下のように応答が返ります。
$ curl HOSTNAME:8000
Hello!
実際には、このプログラムを「Service Provider VPC」内のEC2で動かして、VPC間での通信を試したいと思います。
Service Provider側の設定
では、追加で必要なものを手動で構築していきます。まずはService Provider側です。
Service Provider側では、「Pythonによる実際のサービス」、「Network Load Balancer」、「エンドポイントサービス」の3つを作成していきます。
Pythonによる実際のサービス
まず、EC2インスタンスで「実際のサービス」となるPythonプログラムを配置・起動しておきます。
EC2インスタンスにセッションマネージャ経由で接続してプログラムを起動させます。
# ユーザをec2-userに切り替える
$ sudo su - ec2-user
# 先程のPythonプログラムをvimで記述して保存する
$ vim app.py
# サービスを起動
$ nohup python3 app.py &
[1] 2412
念の為、別セッションで接続してプログラムが停止していないか確認しておきます。
# 別セッションから確認
$ curl 10.0.0.202:8000
Hello!
良さそうですね。
EC2インスタンスに紐づくセキュリティグループ
セキュリティグループの設定でTCP
プロトコルのポート8000
を解放しておきます。「ソース」は今回は0.0.0.0/0
としました。
Network Load Balancer
次にNetwork Load Balancer(NLB)を作成します。
今回はインターネットを経由しない通信用なので、作成する際に「Scheme」はInternal
にします。
「Network mapping」は作成済みのVPC、サブネットを指定していきます。
「Listner and routing」の設定でターゲットグループが必要となりますが、まだターゲットグループを作成していなかったので、これも同時に作成します。
それぞれ、上記のような設定としました。なお、Pythonで動かしたサービスはポート8000
で待ち受けているので、対象とするインスタンスの追加設定時にはポートを8000
に設定するのを忘れないようにします。
このあたりの設定ですが、ポート80
で通信を受け取って、ポート8000
に渡すように設定をしています。
ターゲットグループを作成したら、以下のように「Listner and routing」の設定で作成したターゲットグループを指定します。
これでNLBは作成できました。
エンドポイントサービス
最後にエンドポイントサービスを作成します。
設定は以下のように「ロードバランサーのタイプ」を「ネットワーク」にし、ロードバランサーには先程作成したNLBを指定します。
また、誰でも接続できるようにはしたくないので「承諾が必要」にはチェックを入れておきます。
作成したら「サービス名」を控えておきます。これはService Consumer側で利用します。
Service Consumer側の設定
Service Consumer側では、「エンドポイント」を作成します。
エンドポイント
エンドポイントの作成では、「サービスカテゴリ」に「その他のエンドポイントサービス」を指定して、「サービス名」に先程控えておいた名前を指定します。
VPCやサブネット、セキュリティグループにはService Consumer側のEC2が配置されているものを指定しておきます。
なお、本来であればセキュリティグループにはエンドポイント用のセキュリティグループを別途作成すべきなのですが、今回は既存のものを流用しました。
作成したエンドポイントの「ステータス」が「pendingAcceptance」になっているので、Service Provider側で許可を出しましょう。
Service Provider側のエンドポイントサービスを開いて、「エンドポイント接続」のタブから「エンドポイント接続リクエストの承諾」を行います。
しばらくすると、作成したエンドポイントの「ステータス」が「使用可能」になります。
最後に「エンドポイント」のDNS名を控えておきます。これは実際にService ConsumerからService Providerのサービスに接続する際に利用します。
セキュリティグループの設定
先程設定したVPCエンドポイントのセキュリティグループは、以下のようにService CunsumerのEC2インスタンスに設定されているプライベートIPアドレスからの通信を許可するように設定しておきます。
AWS PrivateLinkを試してみる
これで遂に以下の構成となる設定が完了しました!
早速接続できたか試してみましょう。Service Consumer側のEC2にセッションマネージャで接続します。
先程控えておいた「エンドポイント」のDNS名を指定して、アクセスしてみましょう。
$ curl vpce-XXXXXXXXXXXXXXXXX-XXXXXXXX.vpce-svc-XXXXXXXXXXXXXXXXX.ap-northeast-1.vpce.amazonaws.com
Hello!
通信できました!想定どおり、レスポンスが返ってきましたね。
まとめ
以上、VPC間のAWS PrivateLinkを試してみました。
実際に自分で作成してみると、セキュリティグループの設定などでハマることがあったのでとても勉強になりました。今後、プライベート環境でのネットワーク設定の際にうまくPrivateLinkを活用できればと思います。
どなたかのお役に立てば幸いです。それでは!