AWS CDKによる【ALB】の構築

皆様こんにちは。
AWS CDKを利用してマルチAZ3層アーキテクチャ構築をしていきます。
この記事ではALBの作成を行います。

目次はこちら

1.ALBとは

ALB(Application Load Balancer)とは、AWS(Amazon Web Services)が提供するElastic Load Balancing(ELB)サービスの一部で、HTTP/HTTPSトラフィックを効率的に分散させるためのロードバランサーです。ALBはレイヤー7のロードバランサーであり、OSIモデルのアプリケーション層で動作します。以下に、ALBの主な機能と特徴について説明します。

主な機能と特徴

  1. レイヤー7のロードバランシング

    • HTTP/HTTPSトラフィックをターゲット(EC2インスタンス、コンテナ、IPアドレスなど)に効率的に分散させます。
    • ヘッダーやパスなどのリクエスト属性に基づいてトラフィックをルーティングできます。
  2. コンテナベースのアプリケーションサポート

    • Amazon ECS(Elastic Container Service)と統合されており、コンテナベースのアプリケーションに対してロードバランシングを提供します。
  3. ウェブソケットとHTTP/2のサポート

    • 長時間接続を必要とするアプリケーションのために、ウェブソケットとHTTP/2をサポートしています。
  4. 柔軟なターゲットグループ

    • インスタンス、IPアドレス、Lambda関数をターゲットとして使用できます。
    • 複数のターゲットグループを設定し、それぞれに対して異なるヘルスチェックを設定できます。
  5. 高度なルーティング機能

    • パスベース、ホストベースのルーティングが可能です。
    • SSL/TLS終了(オフロード)を行い、HTTPSトラフィックをデコードしてHTTPとしてバックエンドに転送することができます。
  6. スケーラビリティと高可用性

    • 自動的にスケールアップおよびスケールダウンし、トラフィックの負荷に対応します。
    • 複数のアベイラビリティゾーンにわたる高可用性を提供します。

詳細は公式ドキュメントを参照ください。

2.目的

下記条件のALBを構築する。

  • 証明書を付与したドメインを使用してALBのHTTPS通信を実現
  • HTTP通信をHTTPS通信にリダイレクトさせる
  • 2つのAZの各プライベートサブネットにあるEC2に負荷分散させる(ラウンドロビン)

3.構成図

4.全体構築ソースコード

        # ターゲットグループの作成
        target_group = elbv2.CfnTargetGroup(self, "MyTargetGroup",
            name="kitaya-tg-web",
            port=80,
            protocol="HTTP",
            vpc_id=vpc.vpc_id,
            health_check_protocol="HTTP",
            health_check_port="80",
            health_check_path="/healthcheck.php",
            target_type="instance",
            targets=[
                {"id": instance_01.ref},
                {"id": instance_02.ref}
            ]
        )
        Tags.of(target_group).add("Name", "kitaya-tg-web")

        # ALBの作成
        alb = elbv2.CfnLoadBalancer(self, "KitayaAlb",
            name="kitaya-alb",
            subnets=[public_subnet_az1.ref, public_subnet_az2.ref],
            security_groups=[alb_sg.security_group_id],
            type="application",
            scheme="internet-facing",
            ip_address_type="ipv4",
            load_balancer_attributes=[
                {
                    "key": "access_logs.s3.enabled",
                    "value": "true"
                },
                {
                    "key": "access_logs.s3.bucket",
                    "value": alb_access_logs_bucket.bucket_name
                },
                {
                    "key": "access_logs.s3.prefix",
                    "value": "alb-logs"
                }
            ]
        )
        Tags.of(alb).add("Name", "kitaya-alb")

        # HTTPSリスナーの作成
        https_listener = elbv2.CfnListener(self, "MyHttpsListener",
            load_balancer_arn=alb.ref,
            port=443,
            protocol="HTTPS",
            certificates=[{"certificateArn": certificate.certificate_arn}],
            default_actions=[{
                "type": "forward",
                "targetGroupArn": target_group.ref
            }]
        )

        # HTTPリスナーの作成(HTTPSへのリダイレクト設定)
        http_listener = elbv2.CfnListener(self, "MyHttpListener",
            load_balancer_arn=alb.ref,
            port=80,
            protocol="HTTP",
            default_actions=[{
                "type": "redirect",
                "redirectConfig": {
                    "protocol": "HTTPS",
                    "port": "443",
                    "statusCode": "HTTP_301",
                    "host": "#{host}",
                    "path": "/#{path}",
                    "query": "#{query}"
                }
            }]
        )

        # 依存関係の設定
        https_listener.node.add_dependency(target_group)
        https_listener.node.add_dependency(alb)
        http_listener.node.add_dependency(alb)

5.ソースコード詳細

5-1.ターゲットグループの作成

ロードバランサーのトラフィックを特定の一連のターゲット(EC2インスタンスなど)にルーティングするために作成します。

  • "elbv2.CfnTargetGroup"を使用
    論理ID(テンプレート内で一意)の指定をしたのち、詳細のプロパティを指定しています。

プロパティは下記

使用するプロパティ 設定値 説明
name "kitaya-tg-web" ターゲットグループの名前を指定します。
port 80 ターゲットグループがリクエストを受け付けるポート番号を指定します。
protocol "HTTP" ターゲットグループが使用するプロトコルを指定します。
vpc_id vpc.vpc_id ターゲットグループが属するVPCのIDを指定します。
health_check_protocol "HTTP" ヘルスチェックに使用するプロトコルを指定します。
health_check_port "80" ヘルスチェックに使用するポート番号を指定します。
health_check_path "/healthcheck.php" ヘルスチェックリクエストのパスを指定します。
target_type "instance" ターゲットグループのターゲットタイプを指定します。
targets [ {"id": instance_01.ref},{"id": instance_02.ref}] ターゲットグループに登録するターゲットを指定します。
  • "Tags.of()"メソッドを使用してNameタグをつけます(書式は下記)
    Tags.of("リソース名").add("キー", "値")

  • ソースコード

        # ターゲットグループの作成
        target_group = elbv2.CfnTargetGroup(self, "MyTargetGroup",
            name="kitaya-tg-web",
            port=80,
            protocol="HTTP",
            vpc_id=vpc.vpc_id,
            health_check_protocol="HTTP",
            health_check_port="80",
            health_check_path="/healthcheck.php",
            target_type="instance",
            targets=[
                {"id": instance_01.ref},
                {"id": instance_02.ref}
            ]
        )
        Tags.of(target_group).add("Name", "kitaya-tg-web")

5-2.ALBの作成

サブネット、セキュリティグループを指定し、ALBを作成します。インターネットからアクセス可能な設定で、IPv4 アドレスを使用し
また、アクセスログを指定の S3 バケットに保存する設定をしています。

  • "CfnLoadBalancer"を使用してALBを作成
    論理ID(テンプレート内で一意)の指定をしたのち、詳細のプロパティを指定しています。

プロパティは下記

使用するプロパティ 設定値 説明
name "kitaya-alb" ALBの名前を指定します。
subnets [public_subnet_az1.ref,public_subnet_az2.ref] ALBが配置されるサブネットを指定します。
security_groups [alb_sg.security_group_id] ALBに適用されるセキュリティグループのIDを指定します。
type "application" ELBのタイプを指定します。ここでは「アプリケーションロードバランサー(ApplicationLoadBalancer)」が指定されています。
scheme "internet-facing" ALBのスキームを指定します。ここでは「インターネット向け(Internet-facing)」を指定。
ip_address_type "ipv4" ALBが使用するIPアドレスのタイプを指定します。
  • "load_balancer_attributes"を使用してALBの属性を設定
    ここでアクセスログに関する設定をします。
使用するプロパティ 設定値 説明
-access_logs.s3.enabled "true" アクセスログのS3バケットへの保存を有効にする設定です。
-access_logs.s3.bucket alb_access_logs_bucket.bucket_name アクセスログを保存するS3バケットの名前を指定します。
※S3構築で作成したバケットを指定
-access_logs.s3.prefix "alb-logs" アクセスログを保存するS3バケット内のプレフィックス(フォルダ名)を指定します。
  • "Tags.of()"メソッドを使用してNameタグをつけます(書式は下記)
    Tags.of("リソース名").add("キー", "値")

  • ソースコード

        # ALBの作成
        alb = elbv2.CfnLoadBalancer(self, "KitayaAlb",
            name="kitaya-alb",
            subnets=[public_subnet_az1.ref, public_subnet_az2.ref],
            security_groups=[alb_sg.security_group_id],
            type="application",
            scheme="internet-facing",
            ip_address_type="ipv4",
            load_balancer_attributes=[
                {
                    "key": "access_logs.s3.enabled",
                    "value": "true"
                },
                {
                    "key": "access_logs.s3.bucket",
                    "value": alb_access_logs_bucket.bucket_name
                },
                {
                    "key": "access_logs.s3.prefix",
                    "value": "alb-logs"
                }
            ]
        )
        Tags.of(alb).add("Name", "kitaya-alb")

5-3.HTTPSリスナーの作成

指定された ALB に対して HTTPS 通信を受け付けるリスナーを作成します。ポート 443 で HTTPS リクエストを受け取り、指定された証明書を使用して通信を暗号化し、デフォルトでリクエストを指定されたターゲットグループに転送します。

  • "CfnListener"を使用してリスナーを作成
    論理ID(テンプレート内で一意)の指定をしたのち、詳細のプロパティを指定しています。

プロパティは下記

使用するプロパティ 設定値 説明
load_balancer_arn alb.ref リスナーが関連付けられるロードバランサーのARNを指定します。
port 443 リスナーがリクエストを受け付けるポート番号を指定します。HTTPS通信の標準ポートである443を指定。
protocol "HTTPS" リスナーが使用するプロトコルを指定します。HTTPSを指定し通信が暗号化されます。
certificates [{"certificateArn":certificate.certificate_arn}] HTTPS通信に使用する証明書のARNを指定します。
※ACM構築で作成したリソースを指定
  • "default_actions"でリクエストを処理するためのアクションを定義します。
使用するプロパティ 設定値 説明
type "forward" アクションの種類を指定します。ここではリクエストを指定されたターゲットグループに転送する「forward」アクションが指定されています。
targetGroupArn target_group.ref リクエストを転送するターゲットグループのARNを指定します。ここではtarget_groupリソースのARNが指定されています。
  • ソースコード
        # HTTPSリスナーの作成
        https_listener = elbv2.CfnListener(self, "MyHttpsListener",
            load_balancer_arn=alb.ref,
            port=443,
            protocol="HTTPS",
            certificates=[{"certificateArn": certificate.certificate_arn}],
            default_actions=[{
                "type": "forward",
                "targetGroupArn": target_group.ref
            }]
        )

5-4.HTTPリスナーの作成

HTTP リスナーを作成し、HTTP リクエストを HTTPS にリダイレクトする設定を行っています。リスナーは指定された ALBに関連付けられ、ポート 80 でリクエストを受け付けます。

  • "CfnListener"を使用してリスナーを作成
    論理ID(テンプレート内で一意)の指定をしたのち、詳細のプロパティを指定しています。

プロパティは下記

使用するプロパティ 設定値 説明
type "redirect" アクションの種類を指定します。ここではリクエストをリダイレクトする設定がされています。
  • "default_actions"でリダイレクトの設定詳細を指定します。
使用するプロパティ 設定値 説明
protocol "HTTPS" リダイレクト先のプロトコルを指定します。HTTPSを指定。
port "443" リダイレクト先のポート番号を指定します。HTTPS通信の標準ポートである443を指定。
statusCode "HTTP_301" リダイレクトのHTTPステータスコードを指定します。301MovedPermanentlyを指定。
host "#{host}" リダイレクト先のホスト名を指定します。リクエストのホスト名が引き継がれます。
path "/#{path}" リダイレクト先のパスを指定します。リクエストのパスが引き継がれます。
query "#{query}" リダイレクト先のクエリパラメータを指定します。リクエストのクエリパラメータが引き継がれます。
  • ソースコード
        # HTTPリスナーの作成(HTTPSへのリダイレクト設定)
        http_listener = elbv2.CfnListener(self, "MyHttpListener",
            load_balancer_arn=alb.ref,
            port=80,
            protocol="HTTP",
            default_actions=[{
                "type": "redirect",
                "redirectConfig": {
                    "protocol": "HTTPS",
                    "port": "443",
                    "statusCode": "HTTP_301",
                    "host": "#{host}",
                    "path": "/#{path}",
                    "query": "#{query}"
                }
            }]
        )

5-5.依存関係の設定

依存関係を設定することにより、リソースが正しい順序で作成されるようにします。リソースの作成順序を自動で決定する場合もありますが、明示的に依存関係を設定することで、リソース間の関係性を正確に制御できます。これにより、スタック作成時や更新時にリソースが期待通りの順序で処理され、エラーの発生を防ぐことができます。

今回はリスナーの依存関係を設定します。

  • "node.add_dependency"を使用してリスナーを作成(書式は下記)
    "依存関係が設定されるリソース".node.add_dependency("依存先のリソース")

  • ソースコード

        # 依存関係の設定
        https_listener.node.add_dependency(target_group)
        https_listener.node.add_dependency(alb)
        http_listener.node.add_dependency(alb)

6.検証

  • Route53で作成したドメイン経由でWEBサーバーへアクセスし暗号化通信をできているか
  • ラウンドロビンで1号機・2号機に負荷分散できているか

6-1.Route53で作成したドメイン経由でWEBサーバーへアクセスし暗号化通信をできているか

Route53にて作成したドメインで検索します。

以下のように正しく表示され、HTTPSでの通信を確認しました。

また、HTTPを指定すると正しくHTTPSにリダイレクトされていることも確認できました。

6-2.ラウンドロビンで1号機・2号機に負荷分散できているか

今回はそれぞれのサーバーに別の画像を挿入し検証しました。
※各サーバーのPHPファイルに以下を追記

~~~~~~省略~~~~~~

// タイトルを表示
echo "<h1>WEBサーバー 1号機</h1>";

// 画像を表示する(/web01/web01.jpg を例にしています)
echo "<img src='/web01/web01.jpg' alt='Image from web01' style='max-width:100%; height:auto;'>";

// データが取得できた場合、HTMLテーブルとして表示する
if (\$result->num_rows > 0) {
    echo "<table border='1'>";

~~~~~~省略~~~~~~

1号機を表示すると下記表示になります。
ディレクトリの構成については左下の通りです。

その後、F5キーを押下し更新をかけると2号機の画面に切り替わります。

何度かF5キーを押してみたところ、交互に切り替わっていたので正しくラウンドロビンで負荷分散できていることが確認できました。

6.感想

リスナーの作成の際、エラーがでてしまいなかなか解決できなかった。
結果的に依存関係を設定することで解決できた。
ツールがすべて自動で依存関係を考慮してデプロイしてくれるとは限らないと勉強になった。

Last modified: 2024-08-14

Author