目次
はじめに
こんにちは。クラウド事業部の西川です。
今回は下記のブログを参考にALBとPrivateLinkを使用してWeb アプリケーションをインターネットに公開する方法をご紹介します。
AWS PrivateLinkやVPCエンドポイントに関しては下記のブログが非常にわかりやすいです。 また、コンソールでのPrivateLink作成方法も詳しく解説されています。
VPC同士の接続といえば、VPCピアリングの使用が真っ先に思い浮かぶのではないかと思いますが、今回のようなVPC同士を接続してWebアプリケーションを公開するシナリオでは下記のようなPrivateLinkを使用するメリットがあります。
まず注目すべきなのは異なるVPC間でのIPアドレスの範囲が重複していても接続が可能である点です。基本的にVPCピアリングではIPアドレスの範囲が重複している場合、ピアリングの設定はできませんが、PrivateLinkではこの制約がありません。
また、PrivateLinkは指定されたサービスのみを他のVPCに公開します。例えば、ポート80(HTTP)でリッスンするWebアプリケーションの場合、PrivateLinkを介したElastic Network Interface(ENI)はHTTP接続のみを受け入れます。これにより、Application-VPCに対して不要なトラフィックが入ることを防ぎ、セキュリティを向上させることができます。
さらに、PrivateLinkを使用した場合、接続はDMZ-VPCからApplication-VPCへの一方向のみで開始できます。つまり、Application-VPCからDMZ-VPCへの接続の開始はできず、不正アクセスのリスクを減少させ、より管理しやすいネットワーク環境を実現することができます。
構成図
DMZ-VPCに配置されたALBをリバースプロキシとして使用し、 PrivateLinkで接続されたインターネットゲートウェイの無いApplication-VPCのアプリケーションサーバー(今回はApache Webサーバー)を公開します。
参考ブログと異なる点としてVPCは2つだけ作成し、ALBのパスベースルーティングも省略しています。
また、この記事ではCloudFormationである程度リソースを作成し、実際に手を動かして作成するのはALBとALB用のターゲットグループのみです。
事前準備(AMI作成)
まず最初にApplication-TemplateのEC2の起動時に指定するAMIを作成します。今回はApacheをインストール&テストページを作成したものを用意します。
下記のテンプレートをCloudFormationで使用してスタックを作成してください。
AWSTemplateFormatVersion: '2010-09-09' Description: CloudFormation template to create a VPC, an Internet Gateway, a Public Subnet, and an EC2 Instance. Parameters: PJPrefix: Type: String EC2AMIId: Description: AMI ID Type : String Default: ami-0d0150aa305b7226d KeyName: Description: The EC2 Key Pair to allow SSH access to the instance Type: "AWS::EC2::KeyPair::KeyName" Resources: MyVPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Sub "${PJPrefix}-AMICreation-VPC" MyInternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: MyInternetGateway AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref MyVPC InternetGatewayId: !Ref MyInternetGateway MyPublicSubnet: Type: AWS::EC2::Subnet Properties: VpcId: !Ref MyVPC CidrBlock: 10.0.1.0/24 MapPublicIpOnLaunch: true AvailabilityZone: !Select [ 0, !GetAZs '' ] Tags: - Key: Name Value: !Sub "${PJPrefix}-AMICreation-PublicSubnet" MyRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref MyVPC Tags: - Key: Name Value: !Sub "${PJPrefix}-AMICreation-RouteTable" MyRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref MyRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref MyInternetGateway MySubnetRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref MyPublicSubnet RouteTableId: !Ref MyRouteTable MySecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow SSH and HTTP VpcId: !Ref MyVPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 Tags: - Key: Name Value: !Sub "${PJPrefix}-AMICreation-SecurityGroup" MyEC2Instance: Type: AWS::EC2::Instance Properties: InstanceType: t2.micro SecurityGroupIds: - !Ref MySecurityGroup SubnetId: !Ref MyPublicSubnet KeyName: !Ref KeyName ImageId: !Ref EC2AMIId Tags: - Key: Name Value: !Sub "${PJPrefix}-AMICreation-EC2Instance" UserData: Fn::Base64: !Sub | #!/bin/bash yum -y update yum -y install httpd systemctl start httpd systemctl enable httpd echo "<html> <head> <title>Test Page</title> </head> <body> <h1>Test Page</h1> <p>If you see this page, the web server is working correctly.</p> </body> </html>" > /var/www/html/index.html
スタック作成後、AMIを作成してください。AMI作成後はスタックを削除していただいて問題ありません。
AMIの作成方法は下記のブログで丁寧に紹介されています。
CloudFormationを初めて触る方は下記の記事のスタック作成の項目を参考にスタックを作成してください(RDSの設定などは不要です)
テンプレート
Application-Template
AWSTemplateFormatVersion: "2010-09-09" Description: CloudFormation template for Application-VPC Parameters: PJPrefix: Type: String Default: "PrivateLink-HandsOn" VPCCIDR: Type: String Default: "192.168.0.0/20" PublicSubnetCIDR: Type: String Default: "192.168.0.0/24" PrivateSubnetCIDR: Type: String Default: "192.168.1.0/24" EC2AMIId: Description: AMI ID Type : String KeyName: Description: The EC2 Key Pair to allow SSH access to the instance Type: "AWS::EC2::KeyPair::KeyName" Resources: VPC: Type: "AWS::EC2::VPC" Properties: CidrBlock: !Ref VPCCIDR EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Sub "${PJPrefix}-Application-VPC" PrivateSubnet: Type: "AWS::EC2::Subnet" Properties: AvailabilityZone: "ap-northeast-1a" VpcId: !Ref VPC CidrBlock: !Ref PrivateSubnetCIDR MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub "${PJPrefix}-Application-PrivateSubnet" PrivateRouteTable: Type: "AWS::EC2::RouteTable" Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${PJPrefix}-Application-PrivateRoute" PrivateSubnetRouteTableAssociation: Type: "AWS::EC2::SubnetRouteTableAssociation" Properties: SubnetId: !Ref PrivateSubnet RouteTableId: !Ref PrivateRouteTable SecurityGroup: Type: "AWS::EC2::SecurityGroup" Properties: VpcId: !Ref VPC GroupDescription: "Allow access to EC2 instances" SecurityGroupIngress: - IpProtocol: "tcp" FromPort: 80 ToPort: 80 CidrIp: "0.0.0.0/0" EC2Instance: Type: "AWS::EC2::Instance" Properties: InstanceType: "t2.micro" SubnetId: !Ref PrivateSubnet KeyName: !Ref KeyName SecurityGroupIds: - !Ref SecurityGroup ImageId: !Ref EC2AMIId Tags: - Key: Name Value: !Sub "${PJPrefix}-Application-WebServerA" TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: VpcId: !Ref VPC Name: !Sub "${PJPrefix}-NLB-TG" Protocol: TCP Port: 80 Tags: - Key: Name Value: !Sub "${PJPrefix}-NLB-TG" Targets: - Id: !Ref EC2Instance Port: 80 NLB: Type: "AWS::ElasticLoadBalancingV2::LoadBalancer" Properties: Subnets: - !Ref PrivateSubnet Name: !Sub "${PJPrefix}-NLB" Scheme: "internal" Type: "network" LoadBalancerAttributes: - Key: "deletion_protection.enabled" Value: "false" Tags: - Key: Name Value: !Sub "${PJPrefix}-NLB" NLBListener: Type: "AWS::ElasticLoadBalancingV2::Listener" Properties: DefaultActions: - Type: "forward" TargetGroupArn: !Ref TargetGroup LoadBalancerArn: !Ref NLB Port: 80 Protocol: "TCP" VPCEndpointService: Type: AWS::EC2::VPCEndpointService Properties: AcceptanceRequired: false NetworkLoadBalancerArns: - !Ref NLB Outputs: VPCEndpointService: Value: { "Fn::Sub": "com.amazonaws.vpce.${AWS::Region}.${VPCEndpointService}" } Export: Name: !Sub "${PJPrefix}-VPCEndpointService"
DMZ-Template
AWSTemplateFormatVersion: "2010-09-09" Description: CloudFormation template for DMZ-VPC Parameters: PJPrefix: Type: String Default: "PrivateLink-HandsOn" VPCCIDR: Type: String Default: "192.168.0.0/20" PublicSubnetACIDR: Type: String Default: "192.168.1.0/24" PublicSubnetCCIDR: Type: String Default: "192.168.2.0/24" PrivateSubnetCIDR: Type: String Default: "192.168.3.0/24" Resources: VPC: Type: "AWS::EC2::VPC" Properties: CidrBlock: !Ref VPCCIDR EnableDnsSupport: "true" EnableDnsHostnames: "true" InstanceTenancy: default Tags: - Key: Name Value: !Sub "${PJPrefix}-DMZ-VPC" InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ${PJPrefix}-VPC-IGW AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway PublicRouteTable: Type: AWS::EC2::RouteTable DependsOn: AttachGateway Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${PJPrefix}-VPC-RouteTable Route: Type: AWS::EC2::Route DependsOn: AttachGateway Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicSubnetA: Type: AWS::EC2::Subnet DependsOn: AttachGateway Properties: AvailabilityZone: "ap-northeast-1a" CidrBlock: !Ref PublicSubnetACIDR MapPublicIpOnLaunch: 'true' VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${PJPrefix}-VPC-PublicSubnet-A PublicRouteTableAssociationA: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnetA RouteTableId: !Ref PublicRouteTable PublicSubnetC: Type: AWS::EC2::Subnet DependsOn: AttachGateway Properties: AvailabilityZone: "ap-northeast-1c" CidrBlock: !Ref PublicSubnetCCIDR MapPublicIpOnLaunch: 'true' VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${PJPrefix}-VPC-PublicSubnet-C PublicRouteTableAssociationC: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnetC RouteTableId: !Ref PublicRouteTable PrivateSubnet: Type: "AWS::EC2::Subnet" Properties: AvailabilityZone: "ap-northeast-1a" CidrBlock: !Ref PrivateSubnetCIDR VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${PJPrefix}-DMZ-PrivateSubnet" PrivateRouteTable: Type: "AWS::EC2::RouteTable" Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${PJPrefix}-DMZ-PrivateRoute" PrivateSubnetRouteTableAssociation: Type: "AWS::EC2::SubnetRouteTableAssociation" Properties: SubnetId: !Ref PrivateSubnet RouteTableId: !Ref PrivateRouteTable EndPointSG: Type: "AWS::EC2::SecurityGroup" Properties: GroupDescription: "http" GroupName: !Sub "${PJPrefix}-DMZ-SG" VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${PJPrefix}-DMZ-SG" SecurityGroupIngress: - IpProtocol: tcp FromPort : 80 ToPort : 80 CidrIp: 0.0.0.0/0 VpcEndpoint: Type: "AWS::EC2::VPCEndpoint" Properties: VpcId: !Ref VPC ServiceName: { "Fn::ImportValue": !Sub "${PJPrefix}-VPCEndpointService" } VpcEndpointType: "Interface" SubnetIds: - !Ref PrivateSubnet SecurityGroupIds: - !Ref EndPointSG
リソース作成
AMIが作成できたら、上記のテンプレートを使用してリソースを作成します。
スタックはApplication-Templateスタック→DMZ-Templateスタックの順番で作成してください。 Application-Templateスタック作成時に事前準備で作成したAMIのIDを指定します。
※注:パラメーターのPJPrefixは同一のものにしてください。
続いて、ALBとALBのターゲットグループをコンソールで作成します。
まずはコンソールのVPC>仮想プライベートクラウド>エンドポイントのサブネットの項目でエンドポイントのIPアドレスを確認します。今回の自分の環境では192.168.3.145でした。
そしてEC2>ロードバランシング>ロードバランサーに移動し、右上の「ロードバランサーの作成」をクリックします。
作成画面でApplication Load Balancerの「作成」をクリックしてください。
基本的な設定の項目のロードバランサー名は適当なものを付けてください。スキームはインターネット向け、IP アドレスタイプはIPv4のままで問題ありません。
ネットワークマッピング の項目のVPCではDMZ-VPCを選択し、マッピングは2つのAZのパブリックサブネットを選択してください。
続いてセキュリティグループではDMZ-SGを選択してください。
その次のリスナーとルーティングの項目にある「ターゲットグループの作成」をクリックするとターゲットグループの作成画面に遷移します。
基本的な設定のターゲットタイプの選択は「IPアドレス」を選択し、ターゲットグループ名に適当な名前を付けてください。
その他の項目はデフォルトで問題ありませんが、念のためVPCの項目ではDMZ-VPCが選択されていることを確認してください。
「次へ」を押すとターゲットを登録する画面が表示されます。
ここで先ほど確認したエンドポイントのIPアドレスを入力します。
IPアドレスの入力後、「保留中として以下を含める」をクリックし、「ターゲットグループの作成」を押してください。
ターゲットグループが作成できたら先ほどのALBの作成画面に戻ります。
リスナーとルーティングのデフォルトアクションの右にあるリロードボタンを押すと、先ほど作成したターゲットグループが表示されるのでこれを選択します
設定に誤りがなければ「ロードバランサーの作成」をクリックしてください。
ALBがアクティブになるまで少し待ちます。
ALBがアクティブになったのち、ターゲットグループを確認してください。
下記のように登録済みターゲットのヘルスステータスがHealthyになっていればOKです。 ※下記画像では環境を作り直したためIPアドレスが変わっています。
動作確認
ではEC2のApache Webサーバーにインターネットからアクセスできるか、動作確認をします。
EC2>ロードバランシング>ロードバランサーに移動し、ALBのチェックボックスをクリックしてDNS名をコピーします。(下記画像の画面右下)
コピーしたDNS名でブラウザからアクセスし、下記画面が表示されれば動作確認はOKです。
リソース削除
ALB (+ターゲットグループ) → DMZ-Templateスタック → Application-Templateスタックの順番で削除してください。 AMI作成用のスタックとAMIの削除もお忘れなく。
まとめ
ALBとPrivateLinkを使用してWeb アプリケーションをインターネットに公開する方法をご紹介しました。
ご参考になれば幸いです。