投稿日:
更新日:

Cloud VPN HA 構成で VPC 間をセキュアに接続

Authors

目次

banner.webp

はじめに

複数の Google Cloud プロジェクトに跨るリソース間の通信が発生する場面では、VPC ネットワークを通じて安全に接続できる仕組みが必要です。

Google Cloud では、主に Cloud VPN によるプライベート接続で VPC 間にトンネリングを構築する方法、VPC Peering で VPC 間を直接接続する方法、比較的規模の大きいサービスでは Shared VPC を利用してネットワークを共有する方法等があります。 中でも Cloud VPN を使用した接続は、プロジェクト間を柔軟かつセキュアに接続したい、異なるネットワーク構成を維持したまま通信させたいといった場合に有効です。

Cloud VPN は、トラフィックの暗号化や論理的なネットワーク分離を重視するケース、推移的ルーティングが必要となる場面で多く採用されています。

今回のブログでは、Cloud VPN を利用して異なるプロジェクトの VPC をセキュアに接続する方法について紹介したいと思います。

Cloud VPN

cloud-vpn.png

Cloud VPN は IPsec を利用して、異なるネットワーク同士をセキュアに接続するためのソリューションです。 例えば、自社のデータセンターと Google Cloud 上の VPC 間を接続したり、Google Cloud プロジェクト上の VPC 間を接続したりする際に利用されます。

Cloud VPN では、外部をネットワーク経由するトラフィックが暗号化されるため、盗聴や改竄のリスクを抑えることができます。 通信の際は、片方の VPN Gateway がデータを暗号化して送り出し、もう一方の VPN Gateway がそのデータを復号して受け取る仕組みになっています。

VPC が別プロジェクトに存在する場面、VPC Peering の制限を避けたい場面では Cloud VPN を使った接続が有効です。

VPC Peering との棲み分け

Cloud VPN の他にも VPC Peering を利用することで、拠点間もしくは Google Cloud の VPC 間を接続することができますが、Cloud VPN と比較して次のような制限があります。

名前解決の制限

VPC Peering では、基本的にピア VPC の内部 DNS 名を使った名前解決が直接できません。

例えば、ピア接続された先のインスタンスに対して vm-name.internal のように名前でアクセスしたくても、それが解決されない場合があります。

この制限を回避するためには、ピア VPC にある Cloud DNS の Private Zone と DNS ポリシを共有する必要があります。 つまり DNS の共有設定をしない限り、接続先のリソースに対して名前ではなく IP アドレスでアクセスする必要があり、運用コストが増加する懸念があります。

トラフィック制御の制限

VPC Peering では 推移的ルーティング(Transitive Routing) が利用できません。

例えば、A–B 間、B–C 間が VPC Peering で接続されていても、A から C へ直接通信することはできず、別途フルメッシュ接続を構築する必要があります。

また、このような推移的ルーティングが必要になる場合、代わりに Cloud VPN や Cloud Router を使った Hub-and-Spoke 構成 を取ることが推奨されています。

hub-and-spoke-architecture.png

特に Google Cloud では、Memorystore や Cloud SQL、Vertex AI を VPC に紐付ける際は、Private Service Access を構成する必要があり、VPC Peering では直接的な接続ができない場合があるため、注意が必要です。

Cloud VPN の仕組み

Cloud VPN は、双方の VPC 境界に VPN Gateway を設置し、その間に UDP トンネルを構築します。 ネットワークトンネルは IKE(Internet Key Exchange) プロトコルで確立され、内部は事前共有キーで暗号化された IPSec 通信となります。

後述する HA-VPN 構成では BGP の経路広報を利用して、VPN トンネルを通じたルートテーブルの交換が自動的に処理されます。

HA-VPN

HA-VPN は、オンプレミスや他のネットワークと Google Cloud の VPC を IPsec VPN を通じてセキュアに接続するための高可用な VPN ソリューションです。 HA-VPN では、最大 99.99% の SLA が保証されています。

VPN の HA 構成

HA-VPN の VPN Gateway には 2 つのインターフェースがあり、それぞれに 1 つずつグローバル IP アドレスが割り当てられます。 各インターフェースは、複数の VPN トンネルを張ることが可能で、トラフィックを冗長構成で処理できます。

なお、確保したグローバル IP アドレスは VPN Gateway を削除すると自動的に解放されます。

Google Cloud では、VPC 間またはオンプレミスと VPC 間の安定した通信を維持するために、各方向に 2 本、合計 4 本の VPN トンネルを構成する ことが推奨されています。

フェールオーバの仕組み

4 本のトンネルで構成された HA-VPN 環境ではいずれか 1 本の経路に障害が発生した場合、トラフィックは自動的に他のトンネルにフェールオーバされます。

これにより、物理的なネットワーク障害や IPSec セッションの断絶、ルーティングの経路不達といった事象が発生しても、継続した通信が可能です。

HA-VPN では、各トンネル上で BGP プロトコルを利用して動的に経路情報を交換します。 あるトンネル上の BGP セッションが切断されると、Cloud Router はその経路を無効化し、代替パスの中から最も優先度の高いルートを最短ホップや最小 MED 等に基づいて選択し、通信を継続します。

フェールオーバ中の切り替えはマネージドに行われ、通常は BGP の Hold Timer によって、数秒から数十秒程度で処理されます。 フェールオーバによるトンネルの切り替え処理は、BGP ルータがピアと通信できないと判断するまでのタイミングに依存します。

トンネルが復旧すると、Cloud Router 間で BGP セッションが再確立され、ルート情報も再度交換されます。 再び使用可能になったトンネルの経路は、他のトンネルとの優先順位をもとにルーティングテーブルに追加され、必要に応じてトラフィックがそちらへ流れるようになります。

構成要素

Cloud VPN の HA 構成には以下が含まれます。

  • VPN Gateway
    • VPC との 接続に利用する口 を作る
    • 各 VPN Gateway はインターフェースを 2 つ持つ
    • 複数のトンネルにより冗長構成を実現
    • Cloud Router と組み合わせることで BGP プロトコルによる経路交換が可能
  • Cloud Router
    • 接続のためのネットワーク経路 を作る
    • IPsec トンネルで接続されたピアルータと BGP セッションを張る
    • サブネットや経路情報の自動交換・動的ルーティングが可能
    • IP アドレスの手動登録の必要がなくなる
  • VPN Tunnel
    • VPN Gateway に対応する 実際の VPN トンネル を作る
    • IKE プロトコルを利用してトンネル通信を IPsec で暗号化
    • 内部は Cloud Router 経由で BGP セッションによる経路広報によって構築される
  • Router Interface
    • VPN Gateway に接続するためのインターフェース を作る
    • Cloud Router が使用するネットワークインターフェースに接続用 IP を割り振る
    • VPN トンネルとの紐付けを 1 対 1 で行う
  • BGP Session
    • Cloud Router 間で BGP セッションを張る
    • 自ルータとピアルータ間で経路情報を動的に交換
    • 冗長トンネルで自動フェイルオーバ(障害対応時のリカバリルートを見つける)を実行
    • ルートの優先度制御(Priority)も可能

セッションステータス

VPN トンネルのステータス

ステータス概要
Allocating resources トンネルが作成された直後の初期状態
ソースが割り当てられている途中でトンネルが間もなく起動する
Waiting for full config ルート情報やルーティング構成がまだ準備できていない状態
経路情報の待機中。
First Handshake ピアの VPN との Phase 1(IKE SA)ネゴシエーションを試行中
少なくとも 1 回は失敗している可能性がある
Established トンネルが正常に確立されルートも構成済み
VPN 通信が安定して動作している状態
No Incoming Packets ピアの VPN Gateway からの受信パケットが確認できない状態
トラフィックが一方向のみ、または到達していない可能性がある

BGP のステータス

ステータス概要
Connect BGP ピアは正常に設定されているが BGP セッションはまだ確立されていない
対応する BGP ピアステータス:DOWN
Disabled BGP ピアは意図的に停止されている
対応する BGP ピアステータス:DOWN
Established BGP ピアは正常に構成され BGP セッションが確立されている
対応する BGP ピアステータス:UP
Idle BGP ピアは構成されているが内部エラーが発生している
対応する BGP ピアステータス:DOWN
The VPN tunnel
Interconnect attachment is unknown.
BGP ピアに関連付けられた VPN トンネルまたは VLAN アタッチメントが見つからない
対応する BGP ピアステータス:DOWN
The VPN Tunnel is not in Established state. VPN トンネルがダウンしている
対応する BGP ピアステータス:DOWN
This interface
BGP peer is invalid.
Cloud Router が Network Connectivity Center 用に設定されているが、インターフェースが紐付けられていない
対応する BGP ピアステータス:DOWN
No interface found for the bgp peer. Cloud Router が BGP ピアに紐付くインターフェースを見つけられない
対応する BGP ピアステータス:DOWN

可用性 SLA を最大化するトポロジ

HA-VPN で 99.99% の可用性 SLA を満たすためには、HA-VPN Gateway にある 2 つのインターフェースの両方から、それぞれトンネルを張る必要があります。 このため、各インターフェースに少なくとも 1 本以上のトンネルが必要になります。

後述する フルメッシュ構成 は、より高い冗長性やレジリエンスが要求される場合を除いて、可用性 SLA を満たすための必須条件ではありません。 ピア側にインターフェースが 1 つしかない場合でも、HA-VPN Gateway の 2 つのインターフェースから、それぞれその単一のピアインターフェースに対してトンネルを張ることで SLA を満たす構成を作ることは可能です。

具体的には、以下の構成パターンがあります。

2 つの VPN Gateway で 1 つずつインターフェースを持つパターン

  • Connect two peer VPN gateways
    • 2 つの ピアの VPN Gateway で単一のグローバル IP アドレスを持つ
    • HA-VPN Gateway はピアの VPN Gateway のインターフェースに 1 つずつ 計 2 本のトンネル を構築する
vpn-topology-01.png

この構成パターンでは、ピアネットワーク側に 2 つの VPN Gateway が存在し、それぞれに 1 つずつグローバル IP アドレスが割り当てられています。 Google Cloud 側の HA-VPN Gateway は、それらの 2 つに対してそれぞれ 1 本ずつトンネルを張り、合計 2 本の VPN トンネルを構成します。

これにより、ピア側のいずれかの機器に障害が発生した場合でも、自動でもう一方の経路に切り替えて通信を継続することができます。

また、一方の VPN Gateway を停止してソフトウェアアップデートやメンテナンスを行っても、サービスを中断することなく運用を続けることが可能です。

この構成は、TWO_IPS_REDUNDANCY と呼ばれ、99.99% の可用性 SLA が保証されています。

1 つの VPN Gateway で 2 つのインターフェースを持つパターン

  • Connect one peer VPN gateway with two IP addresses
    • 単一の HA-VPN Gateway が 2 つのグローバル IP アドレスを持つ
    • HA-VPN Gateway はピアの VPN Gateway 上の各インターフェースに 1 つずつ 計 2 本のトンネル を構築する
vpn-topology-02.png

この構成パターンでは、ピア側に 1 つの VPN Gateway しか存在しないものの、2 つのグローバル IP アドレスが割り当てられています。 Google Cloud 側の HA-VPN Gateway は、それぞれの IP アドレスに対して 1 本ずつ、合計 2 本のトンネルを構成します。

ピアの VPN 機器自体は 1 つであっても、インターフェースやネットワーク経路を分けることで冗長性を実現し、片方の経路に問題が発生した場合でも自動的に切り替えることができます。

この構成も、TWO_IPS_REDUNDANCY に該当し、99.99% の可用性 SLA が保証されています。

上の構成と比較して、ネットワーク機器を増設することなく冗長性を確保したい場合に適した方法になります。

1 つの VPN Gateway で 1 つのインターフェースを持つパターン

  • Connect one peer VPN gateway with one IP address
    • 1 つの HA-VPN Gateway が 1 つのグローバル IP アドレスを持つ 1 つのピア VPN Gateway に接続
    • HA-VPN Gateway では 2 本のトンネル を使用し、両方がピアの VPN Gateway の 1 つのインターフェースに接続される
vpn-topology-03.png

この構成パターンでは、ピア側に 1 つの VPN Gateway があり、割り当てられているグローバル IP アドレスも 1 つのみです。 Google Cloud 側の HA-VPN Gateway は、その 1 つのグローバル IP に対して異なるインターフェースから 2 本のトンネルを張り、Google Cloud の内部で冗長構成を実現します。

ピア側では特別な冗長構成を取る必要がなく、構成が比較的シンプルで導入が容易なのがメリットです。

また、トンネルの一方に異常があっても、もう一方に自動でトラフィックをフェールオーバできるため、可用性が維持されます。

この構成は、SINGLE_IP_INTERNALLY_REDUNDANT と呼ばれ、上 2 つと同様に 99.99% の可用性 SLA が保証されています。

フルメッシュ構成

full-mesh-vpn-topology.png

すべての HA-VPN Gateway およびインターフェース間にトンネルを張ったトポロジは フルメッシュ構成 と呼ばれます。

フルメッシュ構成では、2 つの HA-VPN Gateway で、それぞれ 2 つずつインターフェースを持ちます。 HA-VPN Gateway の 2 つのインターフェースは、ピアの VPN Gateway の対応するインターフェースへのトンネルを 1 つずつ構築し、計 4 本のトンネル を張ります。

  1. HA-VPN の Interface 0 を ピア の Interface 0 に接続
  2. HA-VPN の Interface 0 を ピア の Interface 1 に接続
  3. HA-VPN の Interface 1 を ピア の Interface 0 に接続
  4. HA-VPN の Interface 1 を ピア の Interface 1 に接続

アンチパターン

HA-VPN の片方のインターフェースしか使用せず、もう一方が未使用の状態(トンネルが張られていない)では SLA 上の冗長性要件を満たせないため、99.99% の可用性は保証されません。 このため、必ず両方のインターフェースにトンネルを構成する必要があります。

例えば、以下のように HA-VPN インターフェース 0 からピア interface 0 にしか接続されていない場合、可用性 SLA は満たされていません。

anti-pattern-topology.png

HA-VPN フルメッシュ構成の検証

HA-VPN のフルメッシュ構成を検証してみます。

下記の通り、HA-VPN + BGP で計 4 本のトンネルリングを構築します。

VPN トンネル

経路系統HA-VPN I/FTunnel Name
1A IF 0 → B IF 0a-router-interface-00
2A IF 0 → B IF 1a-router-interface-01
3B IF 0 → A IF 0b-router-interface-00
4B IF 0 → A IF 1b-router-interface-01

BGP セッション

  • BGP CIDR 1:169.254.1.0/30
  • BGP CIDR 2:169.254.1.0/30
経路系統BGP IPBGP Peer IPBGP ASNPeer BGP ASN
1169.254.1.1169.254.1.26500165002
2169.254.1.5169.254.1.66500165002
3169.254.1.2169.254.1.16500265001
4169.254.1.6169.254.1.56500265001
verification-architecture.png

Project A と Project B は便宜上、次の名前とします。

  • Project A:sunrise
### locals.tf
locals {
  project      = "sunrise" # Project A
  peer_project = "sunset"  # Project B
  region       = "asia-east1"
  name         = "cloud-vpn-playground"
  subnet_ip    = "10.0.0.0/24"

  gateway_count = ["0", "1"]
  tunnel_ip_ranges = {
    "0" = "169.254.1.1/30" # IP range: 169.254.1.0-3
    "1" = "169.254.1.5/30" # IP range: 169.254.1.4-7
  }
  peer_bgp_ips = {
    "0" = "169.254.1.2"
    "1" = "169.254.1.6"
  }
  router_asn      = 65001
  peer_router_asn = 65002
}
  • Project B:sunset
### locals.tf
locals {
  project      = "sunset"  # Project B
  peer_project = "sunrise" # Project A
  region       = "asia-east1"
  name         = "cloud-vpn-playground"
  subnet_ip    = "10.0.1.0/24"

  gateway_count = ["0", "1"]
  tunnel_ip_ranges = {
    "0" = "169.254.1.2/30" # IP range: 169.254.1.0-3
    "1" = "169.254.1.6/30" # IP range: 169.254.1.4-7
  }
  peer_bgp_ips = {
    "0" = "169.254.1.1"
    "1" = "169.254.1.5"
  }
  router_asn      = 65002
  peer_router_asn = 65001
}

こちらの locals.tf を利用して以下の Terraform をそれぞれ実行します。

VPC の構築

### vpc.tf
module "vpc" {
  source  = "terraform-google-modules/network/google"
  version = "11.0.0"

  project_id   = local.project
  network_name = "${local.name}-vpc"
  routing_mode = "GLOBAL"

  subnets = [
    {
      subnet_name           = "${local.name}-${local.region}-01"
      subnet_ip             = local.subnet_ip
      subnet_region         = local.region
      description           = "VPC subnet for Project A"
      subnet_private_access = "true"
      subnet_flow_logs      = "true"
    },
  ]

  firewall_rules = [
    {
      name        = "${module.vpc.network_name}-allow-internal"
      description = "Allow internal traffic"
      direction   = "INGRESS"
      allow = [
        {
          protocol = "icmp"
        },
        {
          protocol = "tcp"
          ports    = ["1-65535"]
        },
        {
          protocol = "udp"
          ports    = ["1-65535"]
        }
      ]
      ranges = module.vpc.subnets_ips
    },
    {
      name        = "${module.vpc.network_name}-allow-iap-ssh"
      description = "Allow IAP SSH traffic"
      direction   = "INGRESS"
      allow = [
        {
          protocol = "tcp"
          ports    = ["22", "3389"]
        },
        {
          protocol = "icmp"
        }
      ]
      ranges = ["35.235.240.0/20"] # https://cloud.google.com/iap/docs/using-tcp-forwarding#create-firewall-rule
    }
  ]
}

Cloud VPN の構築

まず、VPN Gateway と Cloud Router を両プロジェクトの VPC にそれぞれ作成します。

### vpn_gateway.tf
resource "google_compute_ha_vpn_gateway" "vpn_gateway" {
  name        = "${local.project}-to-${local.peer_project}-vpn-gateway"
  project     = local.project
  description = "HA VPC VPN Gateway for ${local.project} to ${local.peer_project}"
  network     = "${local.name}-vpc"
  region      = local.region
}

resource "google_compute_router" "router" {
  name        = "${local.project}-to-${local.peer_project}-router"
  project     = local.project
  description = "HA VPC VPN Router for ${local.project} to ${local.peer_project}"
  region      = local.region
  network     = "${local.name}-vpc"

  bgp {
    asn = local.router_asn
  }
}

次に、Secret Manager を利用してトンネル通信を暗号化するための共有シークレット(IKE)を定義します。

Terraform では Secret Store のみを作成し、実際のシークレット値はコンソールから手動で設定します。 なお、IKE は Project A と Project B で同じ値を設定する必要があります。

### secret_manager.tf
resource "google_secret_manager_secret" "vpn_shared_secret" {
  secret_id = "${local.project}-to-${local.peer_project}-vpn-ike-shared_secret"
  replication {
    auto {}
  }

  labels = {
    resource-name = "${local.project}-to-${local.peer_project}-vpn-ike-shared_secret"
  }
}

data "google_secret_manager_secret_version" "vpn_shared_secret_version" {
  secret  = google_secret_manager_secret.vpn_shared_secret.id
  version = "latest"
}

data "google_secret_manager_secret_version_access" "vpn_shared_secret_access" {
  secret  = data.google_secret_manager_secret_version.vpn_shared_secret_version.secret
  version = data.google_secret_manager_secret_version.vpn_shared_secret_version.version
}
config-01.png

両プロジェクトで VPN Gateway と Cloud Router を作成した後、VPN トンネルの定義を追加します。

### vpn_gateway.tf
resource "google_compute_vpn_tunnel" "vpn_tunnel" {
  for_each = toset(local.gateway_count)

  name        = "${local.project}-to-${local.peer_project}-vpn-tunnel-${format("%02d", tonumber(each.key))}"
  project     = local.project
  description = "HA VPC VPN Tunnel for ${local.project} to ${local.peer_project}"
  region      = local.region

  vpn_gateway           = google_compute_ha_vpn_gateway.vpn_gateway.id
  vpn_gateway_interface = tonumber(each.key)
  peer_gcp_gateway      = "${local.project}-to-${local.peer_project}-vpn-gateway"
  shared_secret         = data.google_secret_manager_secret_version_access.vpn_shared_secret_access.secret_data ## プロジェクトA-B で共通の値を設定する(Secret Manager から取得)
  ike_version           = 2
  router                = google_compute_router.router.name

  labels = {
    resource-name = "${local.project}-to-${local.peer_project}-vpn-tunnel-${format("%02d", tonumber(each.key))}",
  }

  depends_on = [
    google_compute_ha_vpn_gateway.vpn_gateway,
    google_compute_router.router,
    google_secret_manager_secret.vpn_shared_secret
  ]
}

BGP セッションを張るためのルータインターフェースと BGP ピアを定義します。

### vpn_gateway.tf
resource "google_compute_router_interface" "router_interface" {
  for_each = google_compute_vpn_tunnel.vpn_tunnel

  name       = "${local.project}-router-interface-${format("%02d", tonumber(each.key))}"
  project    = local.project
  router     = google_compute_router.router.name
  region     = local.region
  ip_range   = local.tunnel_ip_ranges[each.key]
  vpn_tunnel = each.value.name

  depends_on = [
    google_compute_vpn_tunnel.vpn_tunnel,
    google_compute_router.router
  ]
}

resource "google_compute_router_peer" "bgp_peer" {
  for_each = google_compute_router_interface.router_interface

  name                      = "${local.project}-to-${local.peer_project}-bgp-session-${format("%02d", tonumber(each.key))}"
  project                   = local.project
  router                    = google_compute_router.router.name
  region                    = local.region
  peer_asn                  = local.peer_router_asn
  interface                 = each.value.name
  peer_ip_address           = local.peer_bgp_ips[each.key]
  advertised_route_priority = 100

  depends_on = [
    google_compute_router_interface.router_interface,
    google_compute_router.router
  ]
}

構成確認

プロジェクト間の VPN トンネルと BGP セッションが確立されていることを確認します。

VPN Tunnel

  • Project A の VPN トンネル
vpn-tunnel-project-a.png
$ gcloud compute vpn-tunnels describe surise-to-sunset-vpn-tunnel-00 \
  --region=asia-east1 \
  --project=surise

$ gcloud compute vpn-tunnels describe surise-to-sunset-vpn-tunnel-01 \
  --region=asia-east1 \
  --project=surise
vpn-tunnel-project-b.png
  • Project B の VPN トンネル
$ gcloud compute vpn-tunnels describe sunset-to-surise-vpn-tunnel-00 \
  --region=asia-east1 \
  --project=sunset

$ gcloud compute vpn-tunnels describe sunset-to-surise-vpn-tunnel-01 \
  --region=asia-east1 \
  --project=sunset

Cloud Router

  • Project A の Cloud Router
cloud-router-project-a.png
$ gcloud compute routers describe surise-to-sunset-router \
  --region=asia-east1 \
  --project=surise
  • Project B の Cloud Router
cloud-router-project-b.png
$ gcloud compute routers describe sunset-to-surise-router \
  --region=asia-east1 \
  --project=sunset

BGP Session

  • Project A の BGP セッション
bgp-session-project-a.png
$ gcloud compute routers get-status surise-to-sunset-router \
  --region=asia-east1 \
  --project=surise
  • Project B の BGP セッション
bgp-session-project-b.png
$ gcloud compute routers get-status sunset-to-surise-router \
  --region=asia-east1 \
  --project=sunset

プライベート IP アドレスで疎通確認

Project A と Project B の VPC に配置した Compute Engine インスタンス間で、プライベート IP アドレスによる疎通確認を行います。

Compute Engine インスタンスの準備

### compute_engine.tf
resource "google_service_account" "default" {
  account_id   = "${local.name}-gce"
  display_name = "${local.name}-gce"
  description  = "GCE Service Account for ${local.name}"
}

resource "google_compute_instance" "default" {
  name         = "${local.name}-instance-01"
  description  = "Cloud VPN Playground Instance 01"
  machine_type = "e2-micro"
  zone         = "asia-east1-a"
  tags = [
    "${local.name}-instance-01",
    "allow-icmp", ## ICMP を許可するためのタグ
  ]
  can_ip_forward = false

  boot_disk {
    auto_delete = true

    initialize_params {
      image = "debian-cloud/debian-12"
      size  = 10
      type  = "pd-balanced"
    }
  }

  network_interface {
    network    = "${local.name}-vpc"
    subnetwork = "${local.name}-${local.region}-01"
    network_ip = local.instance_ip

    access_config {
      nat_ip                 = ""
      public_ptr_domain_name = ""
      network_tier           = "PREMIUM"
    }
  }

  service_account {
    email = google_service_account.default.email
    scopes = [
      "https://www.googleapis.com/auth/devstorage.read_only",
      "https://www.googleapis.com/auth/logging.write",
      "https://www.googleapis.com/auth/monitoring.write",
      "https://www.googleapis.com/auth/service.management.readonly",
      "https://www.googleapis.com/auth/servicecontrol",
      "https://www.googleapis.com/auth/trace.append"
    ]
  }

  labels = {
    resource-name = "${local.name}-instance-01"
  })

  depends_on = [
    google_service_account.default
  ]
}
  • Project A のインスタンス
compute-engine-project-a.png
$ gcloud compute instances describe cloud-vpn-playground-instance-01 \
  --zone=asia-east1-a \
  --project=sunrise
  • Project B のインスタンス
compute-engine-project-b.png
$ gcloud compute instances describe cloud-vpn-playground-instance-01 \
  --zone=asia-east1-a \
  --project=sunset

ファイアウォールの追加

ピアプロジェクトのインスタンスからの ICMP パケットを許可するため、ファイアウォールルールを追加します。

### firewall.tf
resource "google_compute_firewall" "allow_icmp_from_vpn_peer" {
  name    = "${local.name}-vpc-allow-internal-tagbased-icmp"
  network = "${local.name}-vpc"

  direction = "INGRESS"
  priority  = 1000

  allow {
    protocol = "icmp"
  }

  # NOTE: Project A は 10.0.1.0/24(Project B)からの通信を許可
  # NOTE: Project B は 10.0.0.0/24(Project A)からの通信を許可
  source_ranges = ["10.0.1.0/24"]
  target_tags   = ["allow-icmp"]

  description = "Allow ICMP (ping) from peer VPC over VPN"
}
  • Project A のファイアウォールルール
firewall-project-a.png
$ gcloud compute firewall-rules describe cloud-vpn-playground-vpc-allow-internal-tagbased-icmp \
  --project=sunrise
  • Project B のファイアウォールルール
firewall-project-b.png
$ gcloud compute firewall-rules describe cloud-vpn-playground-vpc-allow-internal-tagbased-icmp \
  --project=sunset

ping / tcpdump 確認

両プロジェクトの VPC に配置したインスタンス間で、プライベート IP アドレスによる ICMP 疎通が取れるかを確認します。

  • Project A → Project B
$ ping -c 5 10.0.1.100
PING 10.0.1.100 (10.0.1.100) 56(84) bytes of data.
64 bytes from 10.0.1.100: icmp_seq=1 ttl=62 time=2.64 ms
64 bytes from 10.0.1.100: icmp_seq=2 ttl=62 time=0.927 ms
64 bytes from 10.0.1.100: icmp_seq=3 ttl=62 time=0.988 ms
64 bytes from 10.0.1.100: icmp_seq=4 ttl=62 time=0.906 ms
64 bytes from 10.0.1.100: icmp_seq=5 ttl=62 time=0.901 ms

--- 10.0.1.100 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4004ms
rtt min/avg/max/mdev = 0.901/1.272/2.638/0.683 ms

$ sudo tcpdump -n icmp and src host 10.0.0.100
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on ens4, link-type EN10MB (Ethernet), snapshot length 262144 bytes
08:33:38.787757 IP 10.0.0.100 > 10.0.1.100: ICMP echo request, id 1249, seq 1, length 64
08:33:39.788648 IP 10.0.0.100 > 10.0.1.100: ICMP echo request, id 1249, seq 2, length 64
08:33:40.789807 IP 10.0.0.100 > 10.0.1.100: ICMP echo request, id 1249, seq 3, length 64
08:33:41.790852 IP 10.0.0.100 > 10.0.1.100: ICMP echo request, id 1249, seq 4, length 64
08:33:42.791438 IP 10.0.0.100 > 10.0.1.100: ICMP echo request, id 1249, seq 5, length 64
  • Project B → Project A
$ ping -c 5 10.0.0.100
PING 10.0.0.100 (10.0.0.100) 56(84) bytes of data.
64 bytes from 10.0.0.100: icmp_seq=1 ttl=62 time=2.96 ms
64 bytes from 10.0.0.100: icmp_seq=2 ttl=62 time=0.915 ms
64 bytes from 10.0.0.100: icmp_seq=3 ttl=62 time=0.920 ms
64 bytes from 10.0.0.100: icmp_seq=4 ttl=62 time=0.957 ms
64 bytes from 10.0.0.100: icmp_seq=5 ttl=62 time=0.913 ms

--- 10.0.0.100 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4046ms
rtt min/avg/max/mdev = 0.913/1.332/2.957/0.812 ms

$ sudo tcpdump -n icmp and src host 10.0.1.100
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on ens4, link-type EN10MB (Ethernet), snapshot length 262144 bytes
08:34:13.498877 IP 10.0.1.100 > 10.0.0.100: ICMP echo request, id 917, seq 1, length 64
08:34:14.499081 IP 10.0.1.100 > 10.0.0.100: ICMP echo request, id 917, seq 2, length 64
08:34:15.518945 IP 10.0.1.100 > 10.0.0.100: ICMP echo request, id 917, seq 3, length 64
08:34:16.543027 IP 10.0.1.100 > 10.0.0.100: ICMP echo request, id 917, seq 4, length 64
08:34:17.544119 IP 10.0.1.100 > 10.0.0.100: ICMP echo request, id 917, seq 5, length 64

Cloud VPN を通じてプライベート IP アドレスで ICMP 疎通が取れていることが分かります。

まとめ

今回のブログでは、Cloud VPN を利用して異なるプロジェクトの VPC をセキュアに接続する方法について紹介しました。

Cloud VPN(HA-VPN)は、IPsec による暗号化と Cloud Router を活用した BGP による動的ルーティングに対応し、可用性と柔軟性に優れる接続方式です。 VPC Peering に比べ、推移的ルーティングや DNS 解決等の制約が少なく、プロジェクト間の独立性を保ったまま接続することが可能で、マルチプロジェクト構成でのセキュアな通信手段として信頼性の高い選択肢と言えます。

参考・引用