ALBに複数のACM証明書をアタッチしてマルチドメイン対応する

目次

SSL化したマルチドメイン構成

AWS SOAの出題範囲の1つでもある、セキュリティとコンプライアンスに関連する内容です。ELBには、複数のACM証明書をアタッチすることができるということを確認します。

2つのドメイン(Webサイト)用の構成を、1つのALBと2つのEC2インスタンスで作成することを目的とします。ACMでSSL証明書を取得して、ALBにアタッチすることで、両ドメインへのアクセスを、HTTPSで行えるように設定します。

構築する環境

Diagram of attaching multiple ACM certificates to ALB to support multiple domains.

Apacheのバーチャルホスト機能を使用して、1つのEC2インスタンスに2つのドメイン用の設定を行います。そして可用性を高めるために、同じ設定のインスタンスをもう1台用意し、ALBでロードバランシングする構成を取ります。
バーチャルホスト機能にはいくつか種類がありますが、今回は、ポートベースのバーチャルホスト機能を使用します。以下が今回の構築する構成におけるドメイン名とポート番号の対応となります。なおドメインはRoute 53で取得したものを使用します。

  • awstut.net:81番
  • awstut.link:82番
Diagram of port numbers and domains.

今回はクライアント〜ALB間の通信をSSL化します。そのために2ドメイン用のSSL証明書をACMで取得します。取得したACM証明書をALBにアタッチします。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。以下のURLにCloudFormationテンプレートファイルを設置しています。

https://github.com/awstut-an-r/awstut-soa/tree/main/04/002

テンプレートファイルのポイント解説

プライベートサブネット内のEC2インスタンスをALBにアタッチする

今回の構成では、ALBにアタッチするEC2インスタンスは、プライベートサブネットに設置されています。これらのインスタンスをALBにアタッチするためには、パブリックサブネットを用意し、ALBに関連付ける必要があるという点がポイントです。詳細は以下のページをご確認ください。

あわせて読みたい
プライベートサブネット内のインスタンスをALBにアタッチする 【プライベートサブネット内のインスタンスをALBにアタッチする構成】 プライベートサブネット内に設置されたインスタンスを、ALBにアタッチする方法を確認します。 AWS...

ALBのマルチドメイン対応

soa-04-002-alb.yamlでALB関係のリソースを定義していますが、ALBをマルチドメイン対応するためにポイントとなる設定を取り上げます。

マルチドメイン用のALBターゲットグループを作成する

ポートベースのバーチャルホスト機能を使用するために、ポート番号ごとにALBターゲットグループを作成します。

Resources:
  # domain1
  ALBTargetGroup1:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      VpcId: !Ref VPC
      Name: !Sub "${Prefix}-ALBTargetGroup1"
      Protocol: HTTP
      Port: !Ref Port1
      HealthCheckProtocol: HTTP
      HealthCheckPath: /
      HealthCheckPort: traffic-port
      HealthyThresholdCount: !Ref HealthyThresholdCount
      UnhealthyThresholdCount: !Ref UnhealthyThresholdCount
      HealthCheckTimeoutSeconds: !Ref HealthCheckTimeoutSeconds
      HealthCheckIntervalSeconds: !Ref HealthCheckIntervalSeconds
      Matcher:
        HttpCode: !Ref HttpCode
      Targets:
        - Id: !Ref Instance1
        - Id: !Ref Instance2

  # domain2
  ALBTargetGroup2:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      VpcId: !Ref VPC
      Name: !Sub "${Prefix}-ALBTargetGroup2"
      Protocol: HTTP
      Port: !Ref Port2
      HealthCheckProtocol: HTTP
      HealthCheckPath: /
      HealthCheckPort: traffic-port
      HealthyThresholdCount: !Ref HealthyThresholdCount
      UnhealthyThresholdCount: !Ref UnhealthyThresholdCount
      HealthCheckTimeoutSeconds: !Ref HealthCheckTimeoutSeconds
      HealthCheckIntervalSeconds: !Ref HealthCheckIntervalSeconds
      Targets:
        - Id: !Ref Instance1
        - Id: !Ref Instance2
Code language: YAML (yaml)

Portプロパティの値以外は、2つのターゲットグループは同じ設定です。一方は81、もう一方は82をPortプロパティに設定します。Targetsプロパティでは、両方のグループで、2つのインスタンスを指定します。このように設定することによって、どちらのポート向けの通信でも、つまりどちらのドメイン用の通信が行われる場合でも、ALBは2つのインスタンスに対してトラフィックを配信します。

ALBに複数のACM証明書をアタッチする

ACM証明書をALBにアタッチすることで、クライアント〜ALB間の通信をSSL化することができます。ACM証明書をALBにアタッチする方法の基本については、以下のページをご確認ください。

あわせて読みたい
ACM証明書で、独自ドメイン通信をSSL化する 【ACM証明書を使用して、HTTPSで独自ドメインにアクセスする構成】 以下のページで、Route 53で取得した独自ドメインを使って、ALBにアクセスする構成をご紹介しました...

今回の構成が上記のページと異なる点は、証明書を複数アタッチするという点です。

Resources:
  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      Certificates:
        - CertificateArn: !Ref Certificate1
      DefaultActions:
        - TargetGroupArn: !Ref ALBTargetGroup1
          Type: forward
      LoadBalancerArn: !Ref ALB
      Port: !Ref HTTPSPort
      Protocol: HTTPS

  ALBListenerCertificates:
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: !Ref Certificate2
      ListenerArn: !Ref ALBListener
Code language: YAML (yaml)

複数のACM証明書をALBにアタッチする場合、ListenerCertificateを作成します。ポイントはメインの証明書はListenerに設定し、それ以外の証明書はListenerCertificateに設定する必要があるという点です。例えばListenerに複数の証明書をアタッチしたり、ListenerCertificateだけに証明書をアタッチすることはできませんのでご注意ください。

ドメイン名に基づくリスナールールを作成する

先述の2つのターゲットグループに対して、どのようにトラフィックを振り分けるかルールを定義します。ポイントはHTTPヘッダーのHostの値、つまりリクエスト先のホスト名を条件としたルールを作成するという点です。

Resources:
  # domain1
  ALBListenerRule1:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup1
      Conditions:
        - Field: host-header
          HostHeaderConfig:
            Values:
              - !Ref DomainName1
      ListenerArn: !Ref ALBListener
      Priority: 1

  # domain2
  ALBListenerRule2:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup2
      Conditions:
        - Field: host-header
          HostHeaderConfig:
            Values:
              - !Ref DomainName2
      ListenerArn: !Ref ALBListener
      Priority: 2
Code language: YAML (yaml)

Actionsプロパティで対象のターゲットグループを指定します。
Conditionsプロパティでルールの条件を設定します。Fieldプロパティに「host-header」を設定し、HTTPヘッダーのHostの値を確認するように設定します。HostHeaderConfigプロパティで、ターゲットグループに対応するホスト名を設定します。

Route 53にALBを登録して、独自ドメインでアクセスする

Route 53で取得したドメインをALBに関連付けます。ポイントはALBのDNS名をエイリアスレコードとして登録するという点です。詳細は以下のページをご確認ください。

あわせて読みたい
Route 53にALBを登録して、独自ドメインでアクセスする 【Route 53にALBを登録する構成】 以下のページで、ALBにプライベートサブネット内のEC2インスタンスをアタッチする方法をご紹介しました。 https://awstut.com/2021/11...

プライベートサブネット内でAnsibleを使う

Ansibleを使用して、2つのEC2インスタンスに対して、バーチャルホスト機能を設定します。Ansibleの実行は、SSMドキュメントAWS-ApplyAnsiblePlaybooksの利用がオススメです。ただ今回の構成のように、プライベートサブネット内のインスタンスで、このSSMドキュメントを利用する場合は注意が必要です。それは依存関係にあるAnsibleのインストールに失敗してしまうというものです。ワークアラウンドとしては、SSMドキュメントAWS-RunShellScriptで、個別にAnsibleをインストールする方法があります。詳細は以下のページでご確認ください。

あわせて読みたい
プライベートサブネットでAnsibleを使う 【プライベートサブネット内のインスタンスにAWS-ApplyAnsiblePlaybooksドキュメントを実行する構成】 以下のページで、EC2インスタンス(Amazon Linux 2)で初期化処理を...

(参考)Playbook

以下が今回使用するPlaybookです。

- hosts: all
  gather_facts: no
  become: yes

  tasks:
    - name: update yum
      yum: name=*
    - name: install the latest version of Apache
      yum: name=httpd state=latest
    - name: copy httpd.conf
      copy:
        src: ./httpd.conf
        dest: /etc/httpd/conf
        owner: root
        group: root
        mode: '0644'
    - name: start and enable Apache
      service: name=httpd state=started enabled=yes
    - name: make index.html for domain1
      shell: mkdir -p /var/www/html/domain1; echo 'domain1' > /var/www/html/domain1/index.html; ec2-metadata -i >> /var/www/html/domain1/index.html
    - name: make index.html for domain2
      shell: mkdir -p /var/www/html/domain2; echo 'domain2' > /var/www/html/domain2/index.html; ec2-metadata -i >> /var/www/html/domain2/index.html
Code language: YAML (yaml)

実行する内容ですが、Apacheをインストール後、httpd.confを置き換えます。これはバーチャルホスト用の設定がなされたものです。その後にApacheを起動し、2つのドメイン用のドキュメントルートに、index.htmlを用意します。

(参考)httpd.conf

以下がバーチャルホスト用の設定を行ったhttpd.confの一部です。

#Listen 80
Listen 81
Listen 82

...

NameVirtualHost *:81
NameVirtualHost *:82

...

<VirtualHost *:81>
  ServerName [domain1]
  DocumentRoot /var/www/html/domain1
</VirtualHost>

<VirtualHost *:82>
  ServerName [domain2]
  DocumentRoot /var/www/html/domain2
</VirtualHost>

...
Code language: plaintext (plaintext)

詳細はApache公式サイトをご確認ください。

2つのドメイン用の通信をセキュリティグループで許可する

本題とは少し外れますが、EC2インスタンスに適用するセキュリティグループもポイントです。ドメインに対応したポート番号で通信が行われることに注意してください。

Resources:
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${Prefix}-InstanceSecurityGroup"
      GroupDescription: Allow HTTP(81/tcp, 82/tcp) from ALBSecurityGroup.
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: !Ref Port1
          ToPort: !Ref Port2
          SourceSecurityGroupId: !Ref ALBSecurityGroup
Code language: YAML (yaml)

FromPortおよびToPortプロパティで、許可するインバウンド通信のポート番号をレンジで指定します。今回はそれぞれに81および82を設定して、2ドメイン用の通信を許可します。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

Ansible Playbookの準備

CloudFormationスタックを作成する前に、Ansibleの準備を行います。具体的にはPlaybookをZip化し、S3バケットに設置します。具体的なコマンドは以下のページをご確認ください。

あわせて読みたい
Linuxインスタンスの初期化方法4選 【Linuxインスタンスを初期化する4つの方法】 EC2インスタンスの起動時に初期化処理を実行する方法を考えます。 EC2インスタンスを構築時に初期化する以下の4つの手法を...

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • インスタンス1:i-02c368c2cf2f4d84f
  • インスタンス2:i-0086d086596a27762
  • ドメイン名1:awstut.net
  • ドメイン名2:awstut.link
  • awstut.net用のACM証明書:44206de3-9a28-4a69-af54-a0a88f45d317
  • awstut.link用のACM証明書:0079c615-b747-4780-ac00-a63c5fcdb5bb
  • ALB:soa-04-002-ALB

AWS Management Consoleからも、リソースの作成状況を確認します。まずACM証明書を確認します。

ACM certificates for the two domains have been created.

2ドメイン用で証明書が作成されていることが確認できます。

続いてRoute 53の設定状況を確認します。

The ALB is associated with domain 1.
The ALB is associated with domain 2.

共通して、2つのドメインにALBが関連づいています。タイプが「A」とありますので、エイリアスレコードであることがわかります。

ALBにアタッチされた証明書を確認します。

Two ACM certificates are attached to the ALB.

「awstut.net」の証明書がデフォルトとして設定され、「awstut.link」のものがサブという扱いです。これはCloudFormationテンプレートにて、Listenerにアタッチしたものがデフォルトとした扱われることに起因します。

ALBの2つのターゲットグループを確認します。

Two EC2 instances are registered in the ALB target group 1.
Two EC2 instances are registered in the ALB target group 2.

2つのグループに、同じ2つのEC2インスタンスが登録されています。一方で、グループにポート番号が対応していることも確認できます。つまりどちらのポート番号に向けた通信でも、2つのインスタンスに対してトラフィックが流れるということになります。

マルチドメイン動作検証①

準備が整いましたので、2つのドメインにアクセスします。まず「awstut.net」にアクセスします。

Access result 2 for domain 1.
Access result 1 for domain 1.

正常にアクセスすることができました。アクセスする度に表示される内容が変化します。これはALBが2つのEC2インスタンスに対して、ラウンドロビンでトラフィックを配分しているためです。

マルチドメイン動作検証②

今度は「awstut.link」にアクセスします。

Access result 1 for domain 2.
Access result 2 for domain 2.

こちらも正常にアクセスすることができました。先ほどと同様のインスタンスにアクセスしていることがわかります。

まとめ

ALBにACM証明書を複数アタッチすることで、マルチドメイン構成に対応することができました。

目次