terraformで作るHTTP(S) 負荷分散

はじめまして、昨年8月よりgrasysにJoinしました大川と申します。
以後お見知り置きを。

構成要素が多く地味に作るのが面倒なHTTP(S) 負荷分散について、シンプルな構成をterraformで作る方法をドキュメントを追>いつつゆるく解説させていただきます。

  1. GCPを触ったことがある
  2. terraformを触ったことがある
  3. httplb作るのメンドい

といった方にとって有用な情報となれば幸いです。

前提知識

GCP HTTP(S) 負荷分散

Google社が提供するロードバランサーです。
https://cloud.google.com/compute/docs/load-balancing/http/?hl=ja

Google Cloud Platform(GCP)の HTTP(S) 負荷分散は、インスタンスの HTTP(S) リクエストに対してグローバルな負荷分散を提供します。あるセットのインスタンスに一部の URL をルーティングし、別のインスタンスには別の URL をルーティングする URL ルールを設定できます。

–公式ドキュメントより

HTTPS(S)ロードバランサは下記のように複数のコンポーネントから構成されています。 今回はコンポーネントごとにバックエンドよりフロントに向けて作っていきます。

Cross-region load balancing diagram

terraform

hashicorp社が公開しているオーケストレーションツールです。

https://www.terraform.io/

クラウドや仮想基盤、CDNなど多岐にわたる項目をコードベースで管理することができ、冪等性を担保することが出来ます。 GCPやAWSをはじめとするメジャーなCloud Providerに対応しています。

作ってみる

それでは、実際にHTTP(S) 負荷分散を作ってみます。
まずはバックエンドサービスの配下から作り始めます。

HTTP(S) 負荷分散バックエンド サービスはバックエンドを管理するための集中管理サービスであり、ユーザー リクエストを処理するインスタンスを管理します。負荷分散サービスは、バックエンド サービスにリクエストがルーティングされるように設定します。

1. Backendの作成

ドキュメントより、概要図右端にあるインスタンスグループから作成します。

各バックエンドは、1 つのインスタンス グループと、追加の提供容量メタデータから構成されます。

Backend

1.1. Instance Groupに所属するインスタンスの作成

インスタンスグループに所属するインスタンスを作成します。
今回はhttpでアクセスするwebサーバを作成します。
※サーバ内容については割愛します。

https://www.terraform.io/docs/providers/google/r/compute_instance.html

resource "google_compute_instance" "webserver" {
  name         = "webserver"
  machine_type = "n1-standard-1"
  zone         = "asia-northeast1-a"

  tags = ["web"]

  boot_disk {
    initialize_params {
      image = "centos-7-v20180815"
      size = "10"
      type = "pd-standard"
    }
  }

  network_interface {
    network = "default"

    access_config {
      // Ephemeral IP
    }
  }

  metadata {
    foo = "server-web"
  }

  service_account {
    scopes = ["userinfo-email", "compute-ro", "storage-ro"]
  }
}

1.2. Instance Groupの作成

次に、1.1で作成したInstanceが所属するInstance Groupを作成します。

Instance Group
code

https://www.terraform.io/docs/providers/google/r/compute_instance_group.html

resource "google_compute_instance_group" "webserver" {
  name        = "webserver"
  description = "webserver instance group"
  
  instances = [
    "${google_compute_instance.webserver.self_link}",
  ]

  named_port {
    name = "http"
    port = "80"
  }

  zone        = "asia-northeast1-a"
}

2. Backend serviceの作成

次に、Backendを管理するBackend serviceを作成します。

バックエンド サービスによって、受信トラフィックが 1 つ以上の関連バックエンドに振り向けられます。

2.1. health check

Backend service作成時に要求されるhealth checkを先に作成します。

ここではSSLはLBで終端し、webserverへはhttpでアクセスするため http_health_checkを指定します。
health checkとして一定間隔でアクセスするため、比較的軽いページを選択するか、status check専用のページを作ると良いでしょう。

Health Check
document

https://cloud.google.com/compute/docs/load-balancing/health-checks?hl=ja

各バックエンド サービスは、使用可能なインスタンスに対して実行されるヘルスチェックも指定します。

code

https://www.terraform.io/docs/providers/google/r/compute_http_health_check.html

resource "google_compute_http_health_check" "webserver" {
  name         = "webserver-health-check"
  request_path = "/health_check"

  timeout_sec         = 5
  check_interval_sec  = 5
  healthy_threshold   = 2
  unhealthy_threshold = 2
}

2.2. Backend service

Backend,health-checkの作成が終わりましたので、backend serviceを作成します。
backendhealth_checksの項目にはそれぞれ先に作成したリソースのself_linkを設定します。

Backend Service
code

https://www.terraform.io/docs/providers/google/r/compute_backend_service.html

resource "google_compute_backend_service" "webserver-backend" {
  name        = "webserver-backend"
  description = "webserver-backend"
  port_name   = "http"
  protocol    = "HTTP"
  timeout_sec = 10
  enable_cdn  = false

  backend {
    group = "${google_compute_instance_group.webserver.self_link}"
  }

  health_checks = ["${google_compute_http_health_check.webserver.self_link}"]
}

2.3 Backend用firewall rule

backendとhealth_checksを設定しただけの状態ではまだfirewall ruleが存在しないため、health-checkが全てInstanceに届かず失敗する状態です。
そこで、health-check用のIPアドレスを許可するfirewall ruleを作成します。

document

https://cloud.google.com/compute/docs/load-balancing/health-checks?hl=ja#health_check_source_ips_and_firewall_rules

HTTP(S)、SSL プロキシ、TCP プロキシまたは内部負荷分散でヘルスチェックを使用した場合、ヘルスチェック プローブを範囲 130.211.0.0/22 と 35.191.0.0/16 のアドレスから受信します。これらの接続を、負荷分散されるすべてのインスタンスに許可するファイアウォール ルールを作成する必要があります。

code

https://www.terraform.io/docs/providers/google/r/compute_firewall.html

resource "google_compute_firewall" "webserver" {
  name        = "backend-http-server"
  network     = "default"

  allow {
    protocol = "tcp"
    ports    = [
      "80",   # http
    ]
  }

  source_ranges = [
    "130.211.0.0/22",  # Google LB
    "35.191.0.0/16",   # Google LB
  ]

  target_tags   = [
    "web",
  ]
}

ここまでで、Backend serviceの作成が完了しました。
ようやく半分です。

3. Url Map

URL マップを作成します。

URLベースでバックエンドサービスを指定できる機能で、アクセス頻度や負荷の高い処理は別ディレクトリに切り出しバックエンドサーバを分けて負荷の影響を昨日単位にとどめるといった運用も可能です。
最低でも一つ path_rule が必要なため、ここでは / に対するルールを作成します。

Backend Service
document

https://cloud.google.com/compute/docs/load-balancing/http/url-map?authuser=19&hl=ja

Compute Engine の HTTP(S)負荷分散を使用すると、受信 URL に基づいてトラフィックを異なるインスタンスに転送できます。たとえば、http://www.example.com/audio へのリクエストは音声ファイル配信用に設定されているインスタンスを含むバックエンド サービスに送信し、http://www.example.com/video へのリクエストは動画ファイル配信用に設定されているインスタンスを含む別のバックエンド サービスに送信することができます。

code

https://www.terraform.io/docs/providers/google/r/compute_url_map.html

resource "google_compute_url_map" "website" {
  name        = "website"
  description = "a description"

  default_service = "${google_compute_backend_service.webserver-backend.self_link}"

  host_rule {
    hosts        = ["example.grasys.io"]
    path_matcher = "allpaths"
  }

  path_matcher {
    name            = "allpaths"
    default_service = "${google_compute_backend_service.webserver-backend.self_link}"

    path_rule {
      paths   = ["/"]
      service = "${google_compute_backend_service.webserver-backend.self_link}"
    }
  }
}

4. Target Proxy

プロトコルとURLマップを指定します。

Backend Service
document

https://cloud.google.com/compute/docs/load-balancing/http/target-proxies?authuser=19&hl=ja

ターゲット プロキシはグローバル転送ルールによって参照されます。HTTP(S) 負荷分散の場合、プロキシは着信リクエストを URL マップにルーティングします。SSL プロキシと TCP プロキシの負荷分散の場合、ターゲット プロキシは着信リクエストを直接バックエンド サービスにルーティングします。

4.1. SSL certificate

ここではhttpsのルールを作成するため、SSL証明書を登録します。
証明書は中間証明書を連結して登録します。
nginxを運用されている方にはおなじみかと思います。

document

https://cloud.google.com/compute/docs/load-balancing/http/ssl-certificates?authuser=19&hl=ja

HTTPS または SSL 負荷分散を使用するには、ロードバランサのターゲット プロキシで使用できる SSL 証明書を少なくとも 1 つ作成する必要があります。最大 10 個の SSL 証明書を使用してターゲット プロキシを設定できます。SSL 証明書ごとに、まず SSL 証明書リソースを作成します。SSL 証明書リソースには証明書情報が含まれています。

code

https://www.terraform.io/docs/providers/google/r/compute_ssl_certificate.html

resource "google_compute_ssl_certificate" "website-20180815" {
  name        = "website-20180815"
  description = "a description"
  private_key = "${file("path/to/private.key")}"
  certificate = "${file("path/to/certificate.crt")}"
}

4.2. target https proxy

先立って作成したurl_mapとssl_certificateを設定します。

code

https://www.terraform.io/docs/providers/google/r/compute_target_https_proxy.html

resource "google_compute_target_https_proxy" "website" {
  name             = "website"
  description      = "a description"
  url_map          = "${google_compute_url_map.website.self_link}"
  ssl_certificates = ["${google_compute_ssl_certificate.website-20180815.self_link}"]
}

5. Global Forwarding Rule

最後にグローバル転送ルールを設定します。

Backend Service

5.1. global address

HTTP(S) 負荷分散が受け付けるIPアドレスを設定します。
静的外部IPアドレスのうち、グローバル静的外部IPアドレスが必要になります。

document

https://cloud.google.com/compute/docs/ip-addresses/#reservedaddress

グローバル静的外部 IP アドレスは、グローバル負荷分散に使用されるグローバル転送ルールでのみ使用できます。グローバル IP アドレスをリージョン IP アドレスまたはゾーンリソースに割り当てることはできません。

code

https://www.terraform.io/docs/providers/google/d/datasource_compute_global_address.html

resource "google_compute_global_address" "website" {
  name = "website"
}

5.2. global_forwarding_rule

作成されたグローバル静的IPアドレスとURLマップを設定します。
これによりInternetから受け付けるIPアドレスとその後の経路の紐付けを行えます。

document

https://cloud.google.com/compute/docs/load-balancing/http/global-forwarding-rules?authuser=19&hl=ja

グローバル転送ルールは、自分のサイトの DNS レコードで使用できる単一のグローバル IPv4 または IPv6 アドレスを提供します。特定のプロキシについて複数の転送ルールを使用することができるため、IPv4 トラフィック用に 1 つ、IPv6 トラフィック用に 1 つのルールを設定することができます。

code

https://www.terraform.io/docs/providers/google/r/compute_global_forwarding_rule.html

resource "google_compute_global_forwarding_rule" "website" {
  name         = "website"
  depends_on   = [
    "google_compute_target_https_proxy.website",
    "google_compute_global_address.website"
  ]
  target       = "${google_compute_target_https_proxy.website.self_link}"
  ip_protocol  = "TCP"
  port_range   = "443"
  ip_address   = "${google_compute_global_address.website.address}"
}

まとめ

だいぶ駆け足となってしまいましたが、以上でHTTP(S)負荷分散を作成できたかと思います。

こうして紐解いてみると案外多数の要素で構成されていることがわかるかと思います。 機能をひとつひとつ読み解くことで理解の一助になりますと幸いです。