概要 #
ロードバランサのトラブルは「ヘルスチェックが赤い」から入ることが多く、そこを直せば通ります。リージョン外部 Application Load Balancer には、緑のまま通らない入り方があります。
バックエンドから見ると、同じロードバランサからの通信がヘルスチェックと実トラフィックの2つに分かれています。片方だけを塞ぐと、こうなります。
今回測ったのは、IPv4のVMを GCE_VM_IP_PORT 型のゾーンNEGに登録した構成です。ハイブリッドNEGやバックエンドバケットでは経路が変わるので、そのまま当てはめられません。
| 何が来るか | 送信元 |
|---|---|
| ヘルスチェック | 35.191.0.0/16 |
| 実トラフィック | proxy-only サブネットの範囲 |
ファイアウォールを片方しか開けていないと、片方だけ通ります。ヘルスチェックだけ開けていれば、バックエンドは HEALTHY のまま、クライアントには 504 が返ります。
ファイアウォールを1本だけ外す対照実験で、これを実際に起こします。
Google Cloud のロードバランサを触ったことがある人向けです。ALB の種類の選び方や、Cloud Armor・セッションアフィニティは扱いません。
検証環境 #
- Terraform v1.14.3、
hashicorp/googlev7.46.0 - Google Cloud SDK 562.0.0
使用するコードは Advanced-Examples/08-regional-external-alb-neg です。
cd Advanced-Examples/08-regional-external-alb-neg
cp terraform.tfvars.example terraform.tfvars # project_id と iap_member を書き換える
terraform init
terraform apply
Apply complete! Resources: 33 added, 0 changed, 0 destroyed.
Outputs:
vip = "34.146.203.171"
バックエンドVMに外部IPはありません。インターネットからの入口はロードバランサだけです。
構成 #
flowchart LR
C["クライアント"] -->|":81 / :82"| FR["転送ルール2本
同じ VIP"]
FR --> ENV["Envoy プロキシ
proxy-only subnet
10.128.0.0/23"]
HC["ヘルスチェック
35.191.0.0/16"] --> VM
ENV -->|"送信元は 10.128.0.x"| VM["バックエンドVM ×3
10.80.0.0/24
tcp/8080"]
バックエンドに入る矢印が2本あります。ここが記事の全部です。
:81 はゾーンaの2台、:82 はゾーンcの1台に向きます。同じ VIP のポート違いで分けており、パスによる振り分けは使っていません。
$ gcloud compute forwarding-rules list --regions=asia-northeast1 \
--format="table(name,IPAddress,portRange,loadBalancingScheme)"
NAME IP_ADDRESS PORT_RANGE LOAD_BALANCING_SCHEME
tf-adv-elb08-fr-81 34.146.203.171 81-81 EXTERNAL_MANAGED
tf-adv-elb08-fr-82 34.146.203.171 82-82 EXTERNAL_MANAGED
$ curl -sS http://34.146.203.171:81/
backend-a
$ curl -sS http://34.146.203.171:81/
backend-a2
$ curl -sS http://34.146.203.171:82/
backend-b
proxy-only サブネットは何か #
リージョン外部 ALB は Envoy で動きます。その Envoy が置かれるのが proxy-only サブネットです。
$ gcloud compute networks subnets list --regions=asia-northeast1 \
--format="table(name,ipCidrRange,purpose,role)"
NAME RANGE PURPOSE ROLE
tf-adv-elb08-proxy 10.128.0.0/23 REGIONAL_MANAGED_PROXY ACTIVE
tf-adv-elb08-subnet 10.80.0.0/24 PRIVATE
普通のサブネットとは用途が違い、他には使えません。VMを置こうとすると断られます。
$ gcloud compute instances create tf-probe-in-proxy \
--zone=asia-northeast1-a --subnet=tf-adv-elb08-proxy --machine-type=e2-micro
ERROR: (gcloud.compute.instances.create) Could not fetch resource:
- Subnetwork must have purpose=PRIVATE.
ACTIVE は同じリージョン・同じVPCに2つ置けません。
$ gcloud compute networks subnets create tf-probe-proxy2 \
--network=tf-adv-elb08-vpc --region=asia-northeast1 \
--range=10.129.0.0/23 --purpose=REGIONAL_MANAGED_PROXY --role=ACTIVE
ERROR: (gcloud.compute.networks.subnets.create) Could not fetch resource:
- The resource '.../subnetworks/tf-adv-elb08-proxy' already exists
エラーが指しているのは、作ろうとした名前ではなく既存のほうです。名前を変えても通りません。ドキュメントも、制限を active に限定しています。
In a given VPC network and region, only a single proxy-only subnet with purpose
REGIONAL_MANAGED_PROXYcan be active at any point in time.
BACKUP なら共存できます。 アドレス範囲を変えるときなどに使います。
$ gcloud compute networks subnets create tf-probe-proxy-backup \
--network=tf-adv-elb08-vpc --region=asia-northeast1 \
--range=10.129.0.0/23 --purpose=REGIONAL_MANAGED_PROXY --role=BACKUP
Created [...].
$ gcloud compute networks subnets list --regions=asia-northeast1 \
--format="table(name,ipCidrRange,purpose,role)"
NAME RANGE PURPOSE ROLE
tf-adv-elb08-proxy 10.128.0.0/23 REGIONAL_MANAGED_PROXY ACTIVE
tf-adv-elb08-subnet 10.80.0.0/24 PRIVATE
tf-probe-proxy-backup 10.129.0.0/23 REGIONAL_MANAGED_PROXY BACKUP
「2つ目は作れない」ではなく「ACTIVE は1つだけ」です。
同じ資料に、送信元についての記述もあります。
Packets sent from a proxy to a backend VM or endpoint has a source IP address from the proxy-only subnet.
送信元を実際に数える #
バックエンドは python3 -m http.server で、アクセスログが /var/log/http.log に出ます。VMに外部IPが無いので、IAP 経由で入ります。
gcloud compute ssh tf-adv-elb08-a --project=YOUR_PROJECT_ID \
--zone=asia-northeast1-a --tunnel-through-iap
tf-adv-elb08-a の上で、送信元だけを数えます。
sudo awk '{print $1}' /var/log/http.log | sort | uniq -c | sort -rn
27 35.191.235.172
27 35.191.235.169
27 35.191.235.168
1 10.128.0.4
1 10.128.0.3
1 10.128.0.2
きれいに分かれました。35.191.x がヘルスチェック、10.128.0.x が実トラフィックです。後者は proxy-only サブネットの 10.128.0.0/23 に入っています。
ヘルスチェック由来が81件、proxy-only サブネット由来が3件でした。これはログファイル全体の集計で、比率は観測時間と実リクエスト数で変わります。それでも、放っておけばログの大半がヘルスチェックになることは見て取れます。
ヘルスチェックの送信元はドキュメントにあります。
For IPv4 health checks to the backends: 35.191.0.0/16
このサンプルのファイアウォールは 130.211.0.0/22 も許可していますが、今回の観測ではすべて 35.191.x からでした。
ファイアウォールを1本だけ外す #
3本あります。
$ gcloud compute firewall-rules list --filter="network~tf-adv-elb08" \
--format="table(name,sourceRanges.list(),allowed[].map().firewall_rule().list())"
NAME SOURCE_RANGES ALLOW
tf-adv-elb08-vpc-allow-hc 130.211.0.0/22,35.191.0.0/16 tcp:8080
tf-adv-elb08-vpc-allow-iap-ssh 35.235.240.0/20 tcp:22
tf-adv-elb08-vpc-allow-proxies 10.128.0.0/23 tcp:8080
allow-proxies だけを消します。ルートもバックエンドも転送ルールも触りません。
事前 #
Terraform の差分が無い状態から始めます。 これを確かめておかないと、あとの「1本だけ」が言えません。
$ terraform plan -detailed-exitcode
No changes. Your infrastructure matches the configuration. # exit 0
$ gcloud compute backend-services get-health tf-adv-elb08-bs-a --region=asia-northeast1
HEALTHY;HEALTHY
$ curl -sS http://34.146.203.171:81/
backend-a
削除する #
$ gcloud compute firewall-rules delete tf-adv-elb08-vpc-allow-proxies --quiet
Deleted [...].
$ terraform plan
# google_compute_firewall.allow_proxies will be created
Plan: 1 to add, 0 to change, 0 to destroy.
差分は1件だけです。消したルール以外は動いていません。
60秒待ってから見ます。
$ gcloud compute backend-services get-health tf-adv-elb08-bs-a --region=asia-northeast1
HEALTHY;HEALTHY
$ curl -sS --max-time 60 -o /dev/null -w "HTTP %{http_code} / %{time_total}秒\n" http://34.146.203.171:81/
HTTP 504 / 30.025136秒
ヘルスチェックが何度も回ったあとも緑のまま、504 が30秒で返ります。
30秒はどこから来るか #
バックエンドサービスの timeoutSec が30です。変えて確かめます。
$ gcloud compute backend-services update tf-adv-elb08-bs-a --region=asia-northeast1 --timeout=15
$ curl -sS --max-time 60 -o /dev/null -w "HTTP %{http_code} / %{time_total}秒\n" http://34.146.203.171:81/
HTTP 504 / 15.027065秒
HTTP 504 / 15.027665秒
timeoutSec |
504 までの時間 |
|---|---|
| 30(既定) | 30.025秒 |
| 15 | 15.027秒 |
設定した値がそのまま出ます。
戻す #
$ terraform apply
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
$ gcloud compute backend-services get-health tf-adv-elb08-bs-a --region=asia-northeast1
HEALTHY;HEALTHY
$ curl -sS http://34.146.203.171:81/
backend-a
$ terraform plan -detailed-exitcode
No changes. # exit 0
削除も復旧もファイアウォール1本で、前後とも差分ゼロ。 緑+200 → 緑+504 → 緑+200 と動きました。
「ヘルスチェックが緑だから、バックエンドまでは届いている」とは言えません。HEALTHY が示すのは、設定したプローブが正常判定の条件を満たしたことだけです。今回の構成ではプローブは 35.191.0.0/16 から届きますが、proxy-only サブネットからの実リクエストが通るかは見ていません。
ログで切り分ける #
リージョン外部 ALB のログは、グローバルとはリソース種別もフィールド名も違います。
| グローバル外部ALB | リージョン外部ALB | |
|---|---|---|
resource.type |
http_load_balancer |
http_external_regional_lb_rule |
| 失敗の理由 | jsonPayload.statusDetails |
jsonPayload.proxyStatus |
グローバルのつもりで resource.type="http_load_balancer" と書くと、このロードバランサのログは検索対象から外れます。エラーにはなりません。検索範囲にグローバル外部ALBのログがあれば、そちらが返ります。
gcloud logging read 'logName=~"external_regional_requests"' --freshness=2h --limit=40 --format=json
実験中のログです。
status= 200 latency=0.007180s proxyStatus=(空)
status= 504 latency=30.001081s proxyStatus=error="http_response_timeout"; details="client_timed_out"
status= - latency=9.991448s proxyStatus=error="connection_terminated"; details="client_disconnected_before_any_response"
3行目はステータスが空です。クライアントが --max-time 10 で先に諦めた回で、ロードバランサは応答を返す前に切られたことを記録しています。
ドキュメントは proxyStatus の値を説明しています。
http_response_timeout—— バックエンドの応答を待って設定したタイムアウトに達した(504 / 408)connection_terminated—— 完全な応答を受け取る前に接続が終わった(0 / 502 / 503)destination_unavailable—— ヘルスチェック失敗などでバックエンドが使えない(500 / 503)
今回の 504 は destination_unavailable ではなく http_response_timeout でした。ロードバランサはバックエンドを使えると判断したうえで、応答を待って諦めています。ヘルスチェックが緑だったことと整合します。
ただし details は読み解けていません。同時に記録された details="client_timed_out" を、資料はクライアント接続のアイドルタイムアウトとして説明しています。
この組み合わせになる理由は確かめられませんでした。 約30秒という実測が timeoutSec=30 と一致するところまでが今回の裏づけです。切り分けるなら、timeoutSec だけを変えて504までの時間と両フィールドを取り直します。
つまずいたときの確認先 #
- proxy-only サブネットの制約 → Proxy-only subnets
- ヘルスチェックの送信元 → Health checks overview
- ログのフィールド → Regional external ALB logging
- クエリの書き方 → よく使うCloud Loggingクエリまとめ
- 外部IPと転送ルールの料金 → VPC network pricing
後片付け #
$ terraform destroy
Destroy complete! Resources: 33 destroyed.
外部VIP・Cloud NAT・VM3台は、動いている間ずっと時間課金されます。 検証が終わったら消します。
VIP は転送ルールとして課金されます(Cloud Load Balancer Forwarding Rule ...)。VM の外部IPにも専用の SKU があり、Spot では最初の1時間から課金されます(External IP Charge on a Spot Preemptible VM)。使っていれば無料、ではありません。
まとめ #
今回測ったのは、IPv4のVMを GCE_VM_IP_PORT 型のゾーンNEGに登録したリージョン外部ALBです。
- バックエンドに入る経路は2つある。 ヘルスチェックは
35.191.0.0/16、実トラフィックは proxy-only サブネット - ヘルスチェックが緑でも通るとは限らない。 HEALTHY はプローブが条件を満たしたことしか示さない
- ファイアウォール1本を外すと、緑のまま 504。
timeoutSecを15にすると15秒で返った - proxy-only サブネットにVMは置けない。
purpose=PRIVATEを要求される ACTIVEは1つだけ。BACKUPは共存できる- リージョンとグローバルでログが違う。
http_external_regional_lb_ruleとproxyStatus。グローバルの種別で書くと、このLBのログは対象から外れる proxyStatusは切り分けに使えるが、値だけで原因は決まらない。destination_unavailableはヘルスチェック失敗以外でも出る