コンテンツにスキップ

LB@AWSリソース

はじめに

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


01. LB

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

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


02. ALB:Application Load Balancing

ALBとは

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

AWS 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になっているターゲットにルーティングされる。
ヘルスチェック ターゲットグループに所属するプロトコルとアプリケーションのポート番号を指定して、定期的にリクエストを送信する。

▼ ターゲットグループ

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


ルールの設定例

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


Terraformの場合

▼ 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アドレスには、AWS VPCのものが適用される。

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

▼ オートスケーリング

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

500系ステータスの原因

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

AWS Route53からルーティングされるパブリック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  # ターゲット側の情報 (AWS EC2、AWS ECS、AWS 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


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