コンテンツにスキップ

LB@AWS リソース

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. LB

ロードバランシングしたいプロトコルに合わせて、使用するロードバランサーを選択する。

LB 名 OSI 階層モデルのレイヤー リスナーが処理できるプロトコル ターゲット リクエストヘッダー (L7) パケットヘッダーのフィールド (L4) セキュリティグループ
ALB:Application Load Balancer L7 (アプリケーション層) HTTP、HTTPS、gRPC IP アドレス、Amazon EC2 インスタンス、Lambda URL、HTTP ヘッダー ポート番号フィールド
NLB:Network Load Balancer L4 (トランスポート層) TCP、UDP、TLS IP アドレス、Amazon EC2 インスタンス、ALB 不可 IP アドレスフィールド、ポート番号フィールド 不可
GLB:Gateway Load Balancer L3 (ネットワーク層) 、L4 IP IP アドレス、Amazon EC2 インスタンス 不可 IP アドレスフィールド、ポート番号フィールド 不可
CLB:Classic Load Balancer L4L7 HTTP、HTTPS、TCP、SSL/TLS なし URL、HTTP ヘッダー IP アドレスフィールド、ポート番号フィールド


02. ALB:Application Load Balancing

ALB とは

クラウドリバースプロキシサーバー、かつクラウド L7 ロードバランサーとして働く。

Amazon EC2 へのリクエストをバランスよく分配することで、サーバーへの負荷を緩和する。

aws_alb


02-02. セットアップ

コンソール画面の場合

▼ 設定項目と説明

設定項目 説明 補足
リスナー ALB に割り振るポート番号と受信するプロトコルを設定する。リバースプロキシサーバーかつロードバランサ-として、これらの通信をターゲットグループにルーティングする。
スキーム パブリックネットワークからのリクエストを待ち受けるか、あるいはプライベートネットワークからのリクエストを待ち受けるかを設定する。
セキュリティポリシー リクエストの送信者が使用する SSL/TLS プロトコルや暗号化方式のバージョンに合わせて、ALB が受信できるこれらのバージョンを設定する。 ・リクエストの送信者には、ブラウザ、API にリクエストを送信する外部サービス、フォワーディング元の AWS リソース (例:CloudFront など) などを含む。
・- https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html#describe-ssl-policies
ルール リクエストのルーティングのロジックを設定する。
ターゲットグループ ルーティング時に使用するプロトコルと、宛先とするポート番号を設定する。 ターゲットグループ内のターゲットのうち、トラフィックはヘルスチェックが OK になっているターゲットにルーティングされる。
ヘルスチェック ターゲットグループに所属するプロトコルとアプリケーションのポート番号を指定して、定期的にリクエストを送信する。

▼ ターゲットグループ

ターゲットの指定方法 補足
Amazon EC2 インスタンス ターゲットは Amazon EC2 である必要がある。
IP アドレス ターゲットのパブリック IP アドレスは静的である必要がある。
Lambda ターゲットは Lambda である必要がある。


ルールの設定例

ユースケース ポート IF THEN
リクエストが 80 番ポートを指定したときに、443 番ポートにリダイレクトしたい。 80 それ以外の場合はルーティングされないリクエスト ルーティング先:https://#{host}:443/#{path}?#{query}
ステータスコード:HTTP_301
リクエストが 443 番ポートを指定したときに、ターゲットグループにフォワーディングしたい。 443 それ以外の場合はルーティングされないリクエスト 特定のターゲットグループ


Terraform の場合

▼ Amazon EKS に紐づける ALB

module "alb_eks" {
  source  = "terraform-aws-modules/alb/aws"
  version = "= 9.13.0"

  name                             = "foo-alb-eks"
  enable_cross_zone_load_balancing = true
  vpc_id                           = "*****"
  subnets                          = ["*****", "*****"]
  associate_web_acl                = true
  web_acl_arn                      = "*****"
  access_logs = {
    bucket = "*****"
    prefix = "foo-alb-eks"
  }

  security_group_egress_rules = {
    all = {
      from_port = 0
      to_port   = 65535
      protocol  = "-1"
      cidr_ipv4 = "0.0.0.0/0"
    }
  }

  security_group_ingress_rules = {
    https = {
      from_port = 443
      to_port   = 443
      protocol  = "-1"
      cidr_ipv4 = "0.0.0.0/0"
    }
  }

  listeners = {

    https = {
      port            = 443
      protocol        = "HTTPS"
      certificate_arn = "*****"
      forward = {
        # ターゲットグループのキー名を指定する
        target_group_key = "eks"
      }
    }

    http = {
      port        = 80
      protocol    = "HTTP"
      action_type = "redirect"
      redirect = {
        port        = 443
        protocol    = "HTTPS"
        status_code = "HTTP_301"
      }
    }
  }

  # ターゲットグループを定義
  # aws_lb_target_groupを使用しても良い
  target_groups = {

    # 自由にキー名を設定する
    eks = {
      port              = 30180
      protocol          = "HTTP"
      # Nodeにルーティングするためにinstanceとする
      target_type       = "instance"
      # ターゲットグループへのEC2の登録はEKSマネージドNodeグループに委譲する
      create_attachment = false

      health_check = {
        enabled  = true
        path     = "/healthz/ready"
        port     = 30000
        protocol = "HTTP"
        matcher  = "200"
      }
    }
  }
}

# ターゲットグループとマネージドNodeグループを紐づける
resource "aws_autoscaling_attachment" "alb_eks" {
  autoscaling_group_name = aws_eks_node_group.foo.resources[0].autoscaling_groups[0].name
  lb_target_group_arn = module.alb_eks.target_group_arns["eks"]
}

▼ メンテナンス用 ALB

module "alb_eks_maintenance" {
  source  = "terraform-aws-modules/alb/aws"
  version = "= 9.13.0"

  name                             = "foo-alb-maintenance"
  enable_cross_zone_load_balancing = true
  vpc_id                           = "*****"
  subnets                          = ["*****", "*****"]
  associate_web_acl                = true
  web_acl_arn                      = "*****"
  access_logs = {
    bucket = "*****"
    prefix = "foo-alb-maintenance"
  }

  security_group_egress_rules = {
    all = {
      from_port = 0
      to_port   = 65535
      protocol  = "-1"
      cidr_ipv4 = "0.0.0.0/0"
    }
  }

  security_group_ingress_rules = {
    https = {
      from_port = 443
      to_port   = 443
      protocol  = "-1"
      cidr_ipv4 = "0.0.0.0/0"
    }
  }

  listeners = {

    https = {
      port            = 443
      protocol        = "HTTPS"
      certificate_arn = "*****"

      # JSONデータを含む固定レスポンス
      action_type = "fixed-response"
      fixed_response = {
        content_type = "application/json"
        message_body = jsonencode({
          code = "9999"
          # メンテナンスモード時の業務コード
          reason = "MAINTENANCE"
        })
        status_code = "503"
      }
      # XMLデータを含む固定レスポンスの場合
      # fixed_response = {
      #   content_type = "text/xml;charset=UTF-8"
      #   message_body = <<-XML
      #     <?xml version="1.0" encoding="UTF-8"?>
      #     <error>
      #       <code>9999</status>
      #       <reason>MAINTENANCE</statusReason>
      #     </error>
      #   XML
      #   status_code = "503"
      # }
    }

    http = {
      port        = 80
      protocol    = "HTTP"
      action_type = "redirect"
      redirect = {
        port        = 443
        protocol    = "HTTPS"
        status_code = "HTTP_301"
      }
    }
  }
}


AWS Load Balancer Controller の場合


設計パターン

▼ 同じドメインを設け、パスで複数の BFF に振り分ける場合

flowchart LR
    user[クライアント]

    r53[Route53<br/>example.com]
    alb[ALB<br/>HTTPS :443]

    subgraph EKS[EKS Cluster]
        svcA[Service A<br/>Port 3000]
        svcB[Service B<br/>Port 4000]
    end

    user --> r53
    r53 --> alb

    alb -- "/app1/*" --> svcA
    alb -- "/app2/*" --> svcB

▼ 異なるドメインを設け、通信経路を分ける場合

flowchart LR
    user[クライアント]

    r53A[Route53<br/>app1.example.com]
    r53B[Route53<br/>app2.example.com]

    albA[app1 ALB<br/>HTTPS :443]
    albB[app2 ALB<br/>HTTPS :443]

    subgraph EKS[EKS Cluster]
        svcA[Service A<br/>Port 3000]
        svcB[Service B<br/>Port 4000]
    end

    user --> r53A
    user --> r53B

    r53A --> albA
    r53B --> albB

    albA -- "Host: app1.example.com" --> svcA
    albB -- "Host: app2.example.com" --> svcB


ALB インスタンス

▼ ALB インスタンスとは

ALB の実体で、各 ALB インスタンスが異なるグローバル IP アドレスを持つ。

複数の AZ にルーティングするように ALB を設定した場合、各 AZ に ALB インスタンスが 1 つずつ配置される。

alb-instance

▼ 割り当てられる IP アドレス

ALB に割り当てられる IP アドレスには、Amazon VPC のものが適用される。

そのため、Amazon EC2 のセキュリティグループでは、Amazon VPC の CIDR ブロックを許可するように設定する必要がある。

▼ オートスケーリング

単一障害点にならないように、負荷が高まると ALB インスタンスが増えるように自動スケールアウトする仕組みを持つ。

500 系ステータスの原因

▼ ALB のセキュリティグループ

Amazon Route 53 からルーティングされるパブリック IP アドレスを受信できるようにしておく必要がある。

パブリックネットワークに公開するサイトであれば、IP アドレスはすべての範囲 (0.0.0.0/0::/0) にする。

社内向けのサイトであれば、社内のプライベート IP アドレスのみ (*.*.*.*/32) を許可する。


常時 SSL のアプリケーションへのルーティング

▼ 問題

アプリケーションが常時 SSL になっているアプリケーション (例:WordPress) の場合、ALB からアプリケーションに HTTP プロトコルでルーティングすると、HTTPS プロトコルへのリダイレクトループが発生してしまう。

常時 SSL がデフォルトになっていないアプリケーションであれば、これは起こらない。

▼ Web サーバーにおける対処方法

ALB を経由したリクエストには、リクエストヘッダーに X-Forwarded-Proto ヘッダーが付与される。

これには、ALB に対するリクエストのプロトコルの種類が文字列で代入されている。

これが『HTTPS』だった場合、Web サーバーへのリクエストを HTTPS プロトコルとみなすように設定する。

これにより、アプリケーションへのリクエストのプロトコルが HTTPS プロトコルとなる (この設定では、アプリケーション側の対応は不要) 。

*実装例*

SetEnvIf X-Forwarded-Proto https HTTPS=on

▼ アプリケーションにおける対処方法

ALBからEC2に対するリクエストのプロトコルをHTTPSプロトコルと見なす

ALB を経由したリクエストには、リクエストヘッダーに HTTP_X_FORWARDED_PROTO ヘッダーが付与される。

これには、ALB に対するリクエストのプロトコルの種類が文字列で代入されている。

ALB へのリクエストが HTTPS プロトコルだった場合は、ALB からアプリケーションへのリクエストも HTTPS プロトコルとみなす。これを実現するために、index.php へ処理を追加する (この処理を追加した場合は、Web サーバー側の対応は不要) 。

*実装例*

<?php

// index.php
if (isset($_SERVER["HTTP_X_FORWARDED_PROTO"])
    && $_SERVER["HTTP_X_FORWARDED_PROTO"] == "https") {
    $_SERVER["HTTPS"] = "on";
}


負荷分散方式

▼ 負荷分散方式とは

ターゲットに対するリクエストフォワーディング時の負荷分散方式を設定する。

▼ ラウンドロビン方式

受信したリクエストを、ターゲットに均等にルーティングする。

▼ 最小未処理リクエスト方式 (ファステスト)

受信したリクエストを、未処理のリクエスト数がもっとも少ないターゲットにルーティングする。

▼ スロースタート方式

受信したリクエストをルーティングするときに、スロースタート方式 (通過させるリクエストの数を少しずつ増加させる) で負荷分散を実施する。

リクエスト数の非常に多い高トラフィックなシステムで、起動直後のパフォーマンスが悪いアプリケーション (例:キャッシュに依存、接続プールの作成が必要、ウォームアップが必要な JVM 言語製アプリケーション) にいきなり高負荷をかけないようにできる。


アクセスログ

▼ HTTPS リクエストの場合

HTTPS リクエストのアクセスログのフォーマットは以下の通りである。

https 2018-07-02T22:23:00.186641Z app/my-loadbalancer/50dc6c495c0c9188 192.168.131.39:2817 10.0.0.1:80 0.086 0.048 0.037 200 200 0 57 "GET https://www.example.com:443/ HTTP/1.1" "curl/7.46.0" ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/my-targets/73e2d6bc24d8a067 "Root=1-58337281-1d84f3d73c47ec4e58577259" "www.example.com" "arn:aws:acm:us-east-2:123456789012:certificate/12345678-1234-1234-1234-123456789012" 1 2018-07-02T22:22:48.364000Z "authenticate,forward" "-" "-" "10.0.0.1:80" "200" "-" "-" TID_123456
https # リクエストタイプ
2018-07-02T22:23:00.186641Z
app/my-loadbalancer/50dc6c495c0c9188 # ロードバランサー名
192.168.131.39:2817 # クライアント側の情報
10.0.0.1:80  # ターゲット側の情報 (Amazon EC2、Amazon ECS、Amazon EC2 Node の IP アドレスとポート番号)
0.086
0.048
0.037
200 # ロードバランサーのレスポンスのステータスコード
200
0
57
"GET https://www.example.com:443/ HTTP/1.1"
"curl/7.46.0" # ユーザーエージェント
ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2
arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/my-targets/73e2d6bc24d8a067
"Root=1-58337281-1d84f3d73c47ec4e58577259"
"www.example.com"
"arn:aws:acm:us-east-2:123456789012:certificate/12345678-1234-1234-1234-123456789012"
1
2018-07-02T22:22:48.364000Z
"waf,authenticate,forward" # AWS WAF を通過している場合はここに記録される
"-"
"-"
"10.0.0.1:80"
"200" # サーバー側のレスポンスのステータスコード
"-"
"-"
TID_123456


L7 防御

AWS WAF をアタッチし、L7 を防御する。


03. CLB: Classic Load Balancer

キューを持ち、クラウド L4/L7 ロードバランサーとして働く。

ALB、NLB、ではもともと実装されていたキューを廃止した経緯がある。


04. NLB:Network Load Balancer

クラウド L4 ロードバランサーとして働く。

固定 IP アドレスを設定できる。

そのため、ドメインを指定できず、IP アドレスを指定しないといけない HTTP リクエストを送信できないような通信元にも対応している。

この場合、NLB で受信した HTTP リクエストを ALB(internal タイプ)へ送信するようにするとよい。

flowchart LR
    Route53
    NLB
    ALB[internal ALB]
    EKS

    Route53 --> NLB --> ALB --> EKS


セットアップ

▼ L3/L4 防御

Amazon EC2 Security Group をアタッチし、L3/L4(IP アドレス、プロトコル、ポート番号) を防御する。

flowchart LR
    Route53
    NLB[NLB with SG]
    ALB[internal ALB]
    EKS

    Route53 --> NLB --> ALB --> EKS