こんにちは、SCSKの齋藤です。
本記事では、Internal Passthrough Load BalancerのFloating IPパターンを使って、VMの生死に影響されない固定IPを実現する方法についてご紹介します。
はじめに
前回までの記事でPartner InterconnectとCloud Routerの構築について書きました。今回はいよいよ、固定IPを実現するための核心部分であるInternal Passthrough LBの構築に入ります。
第1回の全体設計で説明した通り、固定IPをVMに直接持たせるとRegional MIGでゾーンをまたいだ際にIPが変わってしまう問題がありました。この問題を解決するために、ILB(Internal Passthrough Load Balancer)のフロントエンドに予約IPを持たせ、VMの生死からIPを切り離す「Floating IPパターン」を採用しています。
この記事では、Floating IPパターンの仕組みと、Terraformで構築する際に理解しておくべきポイント、そして構築中にハマった点について書いていきます。特に all_ports = true の必要性とPassthrough型の動作理解については、知らないとハマりやすいポイントなので詳しく説明します。
Internal Passthrough Load Balancerとは
Internal Passthrough Load Balancer(内部パススルーネットワークロードバランサ)は、Google Cloudが提供するL4のロードバランサです。「Passthrough」という名前が示す通り、パケットの送信元IPや宛先IPを一切書き換えずに、そのままバックエンドVMに転送するのが最大の特徴です。
通常のロードバランサ(Application Load BalancerやProxy型のNetwork Load Balancer)は、クライアントからの接続をLBが終端し、LBからバックエンドに対して新しい接続を張ります。つまりバックエンドから見るとクライアントのIPは見えず、LBのIPが送信元になります。一方、Passthrough型はパケットをそのまま転送するので、バックエンドVMはクライアントのIPを直接見ることができます。
この特性は通常「クライアントIPの保持」のために使われますが、今回はむしろ逆の発想で活用しています。「パケットの宛先がLBのフロントエンドIPのままVMに届く」という特性を利用して、VM側でそのIPに対する処理(iptables SNAT等)を行う設計です。
Internal Passthrough LBは内部(VPC内部)トラフィック向けのサービスで、インターネット向けには使えません。フロントエンドIPとしてVPCサブネット内の内部IPアドレスを予約して使います。このIPは google_compute_address で予約する形になり、VMが再作成されても不変です。
resource "google_compute_address" "floating_ip" {
name = "proxy-floating-ip"
address_type = "INTERNAL"
subnetwork = google_compute_subnetwork.proxy.id
address = "xx.xx.xx.xx" # 予約IPを指定
}
ヘルスチェック機能も備えており、バックエンドVMの正常性を監視してトラフィックを正常なVMだけに流すことができます。TCPヘルスチェックとHTTPヘルスチェックが選択できます。
Floating IPパターンの仕組み
Floating IPパターンとは、ロードバランサのフロントエンドIPを「浮動IP(Floating IP)」として利用し、バックエンドVMの入れ替えに影響されない固定のエンドポイントを提供するパターンです。
通常、GCEインスタンスに内部静的IPを割り当てると、そのIPはインスタンスのNICに紐づきます。MIGでインスタンスが再作成されると新しいインスタンスには別のIPが割り当てられます。Floating IPパターンでは、IPをVMのNICではなくILBのフロントエンドに持たせることで、VMの生死に関わらずIPが固定されます。
resource "google_compute_forwarding_rule" "ilb" {
name = "proxy-ilb"
load_balancing_scheme = "INTERNAL"
ip_address = google_compute_address.floating_ip.address
all_ports = true
backend_service = google_compute_region_backend_service.proxy.id
}
バックエンドVMには動的IPのみが割り当てられ、ILBがFloating IPへのトラフィックを適切なVMにルーティングします。VMが再作成されてIPが変わっても、ILBのバックエンドとして新しいVMが登録されれば、Floating IPへの通信は引き続き新VMに届きます。
Passthrough型ならではの設計上の考慮点
Passthrough型はパケットを書き換えないため、通常のロードバランサとは異なる設計上の考慮が必要でした。具体的には以下の3点をVM側で対応する必要がありました。
これらの設計上の考慮点をまとめると以下の通りです。
| 設定 | 目的 | 設定しないとどうなるか |
|---|---|---|
| loopbackにFloating IP追加 | VMカーネルがFloating IP宛パケットを受理 | パケットが破棄される |
| can_ip_forward = true | Floating IPを送信元としたパケット送出を許可 | VPCレベルでパケットがドロップ |
| all_ports = true | 戻りパケット(エフェメラルポート宛)を通過 | TCP接続が成立しない |
第一に、loopbackインターフェースへのFloating IP追加です。ILBがパケットの宛先IPを書き換えないため、VMに届くパケットの宛先はFloating IPのままです。Linuxカーネルは自分が持つIPアドレス宛てでないパケットをデフォルトで破棄するため、loopbackにFloating IPを追加して「このIPは自分宛て」と認識させる必要があります。
# startup-scriptでの設定
if ! ip addr show lo | grep -q "${FLOATING_IP}/32"; then
ip addr add ${FLOATING_IP}/32 dev lo
fi
第二に、can_ip_forward = true の設定です。GCEのデフォルトでは、VMのNIC IPと異なる送信元のパケットはVPCレベルでドロップされます。SNATでFloating IPを送信元に書き換えたパケットを外部に送出するには、この設定をInstance Templateで有効にしておく必要があります。
第三に、all_ports = true の設定です。これはForwarding Ruleに対する設定で、外部サーバーからの戻りパケットを漏れなくVMに届けるために必須です。戻りパケットの宛先ポートはエフェメラルポート(32768-60999)のいずれかになるため、特定ポートに制限するとTCP接続が成立しません。
工夫したところ・ハマったところ
all_ports = true に気づくまでに時間がかかった
構築中、最初はForwarding Ruleに接続先のポート番号だけを指定していました。しかしTCP接続が全く成立しません。SYNは送れるのにSYN-ACKが返ってこない、という状態でした。
tcpdumpでパケットを追っていくと、外部サーバーからのSYN-ACKパケットがILBのFloating IP宛てに送られてきているはずなのに、VM側に届いていないことがわかりました。原因はSYN-ACKの宛先ポートがエフェメラルポートであり、Forwarding Ruleで許可したポート以外だったためILBがドロップしていたのです。
all_ports = true を設定した瞬間にTCP接続が成立し、問題が解消しました。Internal Passthrough LBのPassthrough特性を正しく理解していれば事前に気づけたはずですが、「Forwarding Ruleのポートは接続先ポートを指定するもの」という思い込みがあり、戻りパケットのことを考慮できていませんでした。
Shared VPCでのILB配置
Shared VPC構成では、ILBのForwarding Ruleを作成する際にHost Projectのネットワークとサブネットを参照する必要がありました。ILB自体はService Projectに属するリソースとして作成しますが、network と subnetwork にはHost Projectのものを指定します。
最初はService Project自身のVPCを指定してしまい、「指定したネットワークが存在しない」というエラーに悩みました。Shared VPC構成では、Service ProjectはHost Projectのネットワークを利用するため、Service Project自身にはVPCが存在しないのです。このエラーメッセージからすぐに原因に辿り着けず、しばらく時間を使いました。
まとめ
– Internal Passthrough LBはパケットを書き換えないL4ロードバランサで、Floating IPパターンの実現に最適
– Floating IPパターンでは、IPをVMではなくILBのフロントエンドに持たせることで、VMの生死からIPを切り離す
– Passthrough型ならではの設計考慮(loopback IP、can_ip_forward、all_ports)を理解しておかないとハマりやすい
– all_ports = true は戻りパケットのために必須。エフェメラルポートの存在を忘れないようにしたいところです
次回はProxy VM + Regional MIG + iptables SNATの構築について書きます。

