Proxy VM + Regional MIG + iptables SNATをTerraformで構築してみた

こんにちは、SCSKの齋藤です。
本記事では、Regional MIGとiptables SNATを組み合わせたProxy VMの構築についてご紹介します。

はじめに

本シリーズの最終回です。前回まででPartner Interconnect、Cloud Router、Internal Passthrough LBの構築について書いてきました。今回はILBの背後で実際にパケットを処理するProxy VMを構築します。具体的には、Instance Template、Regional MIG、そしてiptables SNATの設定です。

正直なところ、iptablesによるNATの設定自体はLinuxの基本的な機能なので技術的には難しくありません。しかし、GCE上で動かす場合にはGoogle Cloud固有の考慮点があり、そこでかなり苦労しました。特に can_ip_forward の設定漏れでパケットが消える問題は、原因特定に最も時間がかかったポイントです。

また、Proxyサブネットがインターネットに接続されていないという制約から、カスタムイメージの事前ベイクが必須になるなど、通常のVM構築とは異なる工夫も必要でした。この記事ではこれらの工夫と苦労を中心に書いていきます。


Regional Managed Instance Group(MIG)とは

Managed Instance Group(MIG)は、Google Cloudが提供するVMの自動管理機能です。指定したInstance Template(VMの設計図)に基づいて、常に指定台数のVMを維持します。VMが何らかの理由で停止した場合は自動的に新しいVMを作成し、指定台数を回復させます。

MIGにはZonal MIGとRegional MIGの2種類があります。以下に違いをまとめます。

項目 Zonal MIG Regional MIG
管理範囲 単一ゾーン リージョン内の複数ゾーン
ゾーン障害耐性 ❌ 復旧不可 ✅ 別ゾーンで再作成
Stateful IP引き継ぎ ✅ 可能 ❌ 不可
今回の選択 ✅ 採用

Zonal MIGであればStateful MIGの機能でVMの内部IPを引き継ぐことが可能です。しかし今回は高可用性が要件なのでRegional MIGを選択しています。

Autohealing機能を使うと、ヘルスチェックに基づいてVMの正常性を監視し、異常を検知した場合に自動的にVMを削除して再作成します。これにより、プロセスの異常やOS内部の問題も含めた障害からの自動復旧が実現できます。

今回の構成では target_size = 1 としてRegional MIGで常に1台のVMを維持しています。


resource "google_compute_region_instance_group_manager" "proxy" {
  target_size = 1

  auto_healing_policies {
    health_check      = google_compute_health_check.ah.id
    initial_delay_sec = 60
  }

  update_policy {
    type               = "PROACTIVE"
    minimal_action     = "REPLACE"
    replacement_method = "RECREATE"
  }
}

複数台のVMを走らせる必要はなく、あくまで「障害時に自動復旧する1台」を維持することが目的です。Regional MIGにすることで、ゾーン障害時にも別ゾーンで自動的にVMが立ち上がります。

Instance Templateの設計

Instance TemplateはVMの設計図に相当するリソースで、マシンタイプ、ディスクイメージ、ネットワーク設定、メタデータ(startup-script含む)などを定義します。今回の構成で特に重要なのは以下の点です。

can_ip_forward = true を設定する必要がありました。これがないと、VMのNIC IPと異なる送信元のパケットがVPCレベルでドロップされます。iptables SNATで書き換えたパケット(送信元がFloating IP)を外部に送出するために必須の設定です。

ネットワークインターフェースには静的IPを割り当てません。Floating IPはILBのフロントエンドが保持するため、VM自体には動的IPが割り当てられます。これにより、VMが再作成されてIPが変わっても問題ありません。

startup-scriptには、loopback IPの追加とiptables SNATルールの設定を記述します。Terraformの templatefile() 関数を使って、環境に依存するパラメータ(Floating IPや宛先IP)を動的に注入する設計にしています。


resource "google_compute_instance_template" "proxy" {
  can_ip_forward = true

  metadata = {
    startup-script = templatefile("scripts/startup.sh", {
      floating_ip = var.floating_ip
      dest_ip     = var.destination_ip
    })
  }
}

iptables SNATの仕組み

iptablesのNATテーブルのPOSTROUTINGチェーンでSNATルールを設定します。


# loopbackにFloating IPを追加
ip addr add ${FLOATING_IP}/32 dev lo

# SNAT: 送信元IPをFloating IPに書き換え
iptables -t nat -A POSTROUTING -d ${DEST_IP}/32 \
  -j SNAT --to-source ${FLOATING_IP}

これにより、VMから外部サーバー宛てに送信されるパケットの送信元IPが、VMの動的IPからFloating IP(固定IP)に書き換えられます。

loopbackインターフェースへのFloating IP追加も合わせて行います。Internal Passthrough LBはパケットの宛先IPを書き換えないため、VMに届くパケットの宛先はFloating IPのままです。Linuxカーネルがこのパケットを受理するためには、「このIPは自分のもの」と認識させる必要があるので、loopbackに追加しています。

startup-scriptは冪等に書いています。MIGのAutohealingや手動再起動で複数回実行される可能性があるため、既に設定済みかどうかをチェックしてから追加する形にしています。チェックなしで実行するとiptablesルールが重複したり、loopbackに同じIPが複数回追加されてエラーになったりします。

Health Checkの分離設計

ヘルスチェックを2つに分けて設計しています。1つはILB用(バックエンド切り離し判定)、もう1つはAutohealing用(VM再作成判定)です。

ILB用ヘルスチェックはバックエンドからの切り離し判定に使います。判定結果は「トラフィックを流すか止めるか」であり、影響が比較的軽い(VMを殺さない)ため、短い間隔で素早く判定する設計にしています。

Autohealing用ヘルスチェックはVM再作成判定に使います。こちらは「VMを削除して作り直すか」という破壊的な判定なので、より長い間隔で慎重に判定する設計にしています。一時的なヘルスチェック失敗で不必要にVMが再作成されることを防ぐためです。

両方ともHTTPヘルスチェックを使い、プロキシソフトウェアのステータスエンドポイントを叩いています。


# ILB用
resource "google_compute_health_check" "ilb" {
  check_interval_sec  = x
  healthy_threshold   = x
  unhealthy_threshold = x

  http_health_check {
    port         = 8080
    request_path = "/healthz"
  }
}

# Autohealing用
resource "google_compute_health_check" "autohealing" {
  check_interval_sec  = x
  healthy_threshold   = x
  unhealthy_threshold = x

  http_health_check {
    port         = 8080
    request_path = "/healthz"
  }
}

TCPヘルスチェック(ポートが開いているかだけ確認)では、プロセスは動いているが設定が壊れている状態を検知できないため、HTTPにしました。

工夫したところ・ハマったところ

can_ip_forward を忘れてパケットが消えた

構築中に最もデバッグに時間がかかった問題です。iptables SNATの設定は正しく、VM内部ではパケットが正しく書き換わっているのに、VM外に一切出ていきません。tcpdumpでeth0を確認してもSNAT後のパケットが見えず、「SNATの設定が間違っているのか?」とiptablesの設定を何度も見直しました。

結局、問題はiptablesではなくGCE側の can_ip_forward 設定でした。GCEのデフォルトでは、VMのNIC IPと異なる送信元のパケットはVPCのネットワーク層でサイレントにドロップされます。ドロップされてもVM側にはエラーが返らないため、tcpdumpではVM内部(iptables処理後)までは正常に見えるのに、実際にはワイヤーに出ていない、という状態になります。

公式ドキュメントにはちゃんと記載されているのですが、「パケットが消える」という症状からこの設定に辿り着くまでに時間がかかりました。Passthrough LBと組み合わせてSNATを行う構成を作る場合は、最初に確認しておくとよい設定です。

startup-scriptのデバッグ

startup-scriptでエラーが発生すると、VMは起動するもののヘルスチェックが通らず、Autohealingが繰り返しVMを再作成する「Crash Loop」状態に陥ります。この状態のデバッグにはCloud Loggingのstartup-scriptログを確認する必要がありましたが、VMが数分で削除されてしまうためログの確認が難しいケースもありました。

対策として、Autohealingの initial_delay_sec を十分に長く設定し、VM起動後にログを確認する猶予を確保しました。この値はstartup-script完走時間にマージンを加えた値にしています。

シリーズ全体の振り返り

5回にわたって、Partner Interconnect経由の専用線通信基盤を構築してきました。第1回で全体設計と設計判断、第2回でPartner Interconnect、第3回でCloud Router + BGP + BFD、第4回でILB + Floating IP、そして本記事でProxy VM + MIG + SNATという構成です。

構成は確かに複雑ですが、各コンポーネントには明確な役割があり、組み合わせることで「固定IP + 高可用性」という一見両立しない要件を実現できました。一度構築してしまえば、VMが死んでもAutohealingが自動で復旧し、ネットワーク障害はBFDが検知してフェイルオーバーしてくれます。Google Cloudのマネージド機能を組み合わせることで、運用負荷を低く抑えられるのは大きなメリットだと感じました。

まとめ

– Regional MIG (target_size=1) + Autohealingで、ゾーン障害にも耐える自動復旧を実現
can_ip_forward = true はSNAT構成では必須。忘れるとパケットがサイレントに消える
– startup-scriptは冪等に書く。Autohealingでの再実行に備える
– 構成は複雑だが、一度構築すれば運用はGoogle Cloudのマネージド機能に任せられる

著者について

SCSK株式会社
ソリューション事業グループ
基盤ソリューション事業本部

齋藤雄太をフォローする

クラウドに強いによるエンジニアブログです。

SCSKクラウドサービス(Google Cloud)は、Google Cloudの多彩なAIや各種サービスを活用したワンストップソリューションを提供します。SCSKのノウハウや体制を有効活用し、業務課題の解決に必要な全体検討と組み合わせで、最適な業務実装まで支援します。

Google Cloudその他技術ナレッジ
シェアする
×
タイトルとURLをコピーしました