はじめに
クラウドのシェア率は AWS / Azure / Google Cloud(GCP) の 3 強が続いており、これまでAWSが一歩リードしていましたが、近年は AWS との差が縮まりつつあります。そうした状況の中で、AWS と他のクラウドサービスを組み合わせたマルチクラウド連携をする機会というのも増えていくのかなと考えています。
今回はそのマルチクラウド連携で、VPN を用いたやり方を実践してみました。
具体的には GCPの Cloud Runから、VPN トンネルを経由して AWS の Aurora(MySQL)に接続する という構成を構築しました。
参考ソースコード
参考コードを下記に配置していて、この後の記事ではコードを抜粋しながらの説明をさせていただきます。
(これから本記事に記載する内容と、「terraform/README.md」を参考にしていただければ、同じような環境を作れるはず・・・です・・・。)
VPN 接続に必要な要素
AWS と GCP を VPN で繋ぐには、大きく 3 つのことをやる必要があります。
| やること | Step | 何をするか | AWS | GCP |
|---|---|---|---|---|
| ① 出入り口を用意する | Step 1 | AWS 側に VPN Gateway を作成する | VPN Gateway(VGW) | - |
| Step 2 | GCP 側に HA VPN Gateway を作成する。外部 IP が 2 つ払い出され、次の Step 3 で AWS に渡す | - | HA VPN Gateway | |
| ② 相手の情報を登録し、暗号化トンネルを作る | Step 3 | Step 2 で払い出された GCP の IP を AWS 側に登録し、トンネルを作成する。トンネル用 IP が 4 つ払い出され、次の Step 4 で GCP に渡す | Customer Gateway(CGW)+ VPN Connection | - |
| Step 4 | Step 3 で払い出された AWS の IP を GCP 側に登録し、トンネルを作成する。GCP の 2 インターフェース × AWS の 2 接続で計 4 本張る | - | External VPN Gateway + VPN Tunnel | |
| ③ 通信ルールを定義する | Step 5 | 「どの IP をトンネルに流すか」を BGP でお互いに交換する | VGW 内蔵(自動) | Cloud Router + BGP Peer |
| Step 6 | BGP で学習した経路を AWS のルートテーブルに反映させる | Route Propagation | - |
以降、各 Step の詳細を順番に解説します。なお AWS ↔ GCP 間には IP アドレスの循環依存があるため、Terraform の apply は段階的に行う必要があります。
Step 1: AWS 側に VPN の出入り口を作る
最初に、AWS 側の VPN の「出入口」を作ります。「仮想プライベートゲートウェイ(VGW)」と呼ばれるリソースで、VPC にアタッチします。
# terraform/aws/modules/vpn/main.tf
resource "aws_vpn_gateway" "main" {
vpc_id = var.vpc_id
amazon_side_asn = 64512 # ← BGP の AS 番号(Step 5 で詳しく説明)
}
amazon_side_asn = 64512 は、「AWS 側のネットワーク識別番号」※1です。
この時点では、VPN Gateway に外部 IP はまだ割り当てられません。AWS の場合、外部 IP は Step 3 で VPN Connection を作った時に初めて払い出されます。
※1 ASN (AS番号) とは
BGP※2 では、各ネットワークを「AS(自律システム)」として識別します。AS ごとに一意の番号(ASN)を割り当て、「自分は ASN 64512 で、10.0.0.0/16 を持っている」と相手に広告します。
| 側 | ASN | 設定箇所 |
|---|---|---|
| AWS | 64512 | Step 1 の VPN Gateway(amazon_side_asn) |
| GCP | 65534 | Step 5 の Cloud Router(bgp.asn) |
ASN はプライベート範囲(64512〜65534)から選びます。AWS と GCP で異なる番号にするのがルールです(同じ ASN だと BGP セッションが張れません)。
※2 BGP(Border Gateway Protocol)とは
ネットワーク同士が経路情報を交換するためのプロトコルです。
「自分はこの IP アドレス帯を持っている」とお互いに広告し合うことで、パケットの転送先を自動的に決めます。
VPN トンネルを「道路」とすると、BGP は「この道路を通るとどこに行けるか」を知らせる標識のような役割です。
Step 2: GCP 側に VPN の出入り口を作る
次に、GCP 側の VPN の「出入口」を作ります。GCP では「HA VPN Gateway」を使います。
# terraform/gcp/modules/vpn/main.tf
resource "google_compute_ha_vpn_gateway" "main" {
name = "poc-dev-ha-vpn-gw"
network = var.network_self_link # アタッチ先の VPC
region = "us-central1"
}
ここが AWS との大きな違い: このリソースを作った瞬間に、GCP から外部 IP が 2 つ自動的に払い出されます。
GCP HA VPN Gateway
├── interface0: 34.xxx.xxx.1 ← 自動払い出し
└── interface1: 34.xxx.xxx.2 ← 自動払い出し
HA(High Availability)は冗長構成のことで、2 つのインターフェースを持つことで片方が落ちても通信が継続します。
この 2 つの IP が、次の Step 3 で AWS 側に渡す情報になります。
Step 3: AWS 側に Customer Gateway と VPN Connection を作成【相手の情報登録 + IPsec トンネル】
GCP の外部 IP が確定したので、AWS 側に「相手先(GCP)の情報の設定」と「暗号化トンネル」を作ります。
Customer Gateway(CGW)── 相手の情報を登録する
AWS では「Customer Gateway」というリソースで、接続相手の情報を登録します。GCP の HA VPN は IP を 2 つ持つので、CGW も 2 つ作ります。
# terraform/aws/modules/vpn/main.tf
# GCP interface0 に対応する CGW
resource "aws_customer_gateway" "gcp_interface0" {
bgp_asn = 65534 # ← GCP 側の ASN(Step 5 で詳しく説明)
ip_address = var.gcp_vpn_gateway_ip0 # ← Step 2 で払い出された GCP の IP
type = "ipsec.1"
}
# GCP interface1 に対応する CGW
resource "aws_customer_gateway" "gcp_interface1" {
bgp_asn = 65534
ip_address = var.gcp_vpn_gateway_ip1 # ← Step 2 で払い出された GCP の IP
type = "ipsec.1"
}
bgp_asn = 65534 は「相手(GCP)の ASN」です。Step 5 で GCP 側の Cloud Router に設定する値と一致させます。
VPN Connection ── 暗号化トンネルを作る
Customer Gateway(相手の情報)と VPN Gateway(自分の出入口)を紐づけて、VPN Connection を作ります。
# terraform/aws/modules/vpn/main.tf
resource "aws_vpn_connection" "tunnel0" {
vpn_gateway_id = aws_vpn_gateway.main.id # Step 1 で作った VGW
customer_gateway_id = aws_customer_gateway.gcp_interface0[0].id # 上で作った CGW
type = "ipsec.1"
static_routes_only = false # BGP 動的ルーティングを使う(Step 5 で説明)
# 事前共有鍵(PSK)── GCP 側と同じ値を設定する
tunnel1_preshared_key = var.tunnel1_preshared_key
tunnel2_preshared_key = var.tunnel2_preshared_key
# 暗号化設定
tunnel1_ike_versions = ["ikev2"]
tunnel1_phase1_encryption_algorithms = ["AES256"]
tunnel1_phase1_integrity_algorithms = ["SHA2-256"]
tunnel1_phase1_dh_group_numbers = [14]
tunnel1_phase2_encryption_algorithms = ["AES256"]
tunnel1_phase2_integrity_algorithms = ["SHA2-256"]
tunnel1_phase2_dh_group_numbers = [14]
# tunnel2 も同様の設定
}
# GCP interface1 向けの VPN Connection も同様に作成
resource "aws_vpn_connection" "tunnel1" {
vpn_gateway_id = aws_vpn_gateway.main.id
customer_gateway_id = aws_customer_gateway.gcp_interface1[0].id
# ... 暗号化設定は同様
}
VPN Connection を作ると、AWS 側のトンネル用外部 IP が払い出されます。 1 つの VPN Connection につきトンネルが 2 本あるので、VPN Connection 2 つで合計 4 つの IP が確定します。
AWS VPN Connection 0 → tunnel1_address: 52.x.x.1, tunnel2_address: 52.x.x.2
AWS VPN Connection 1 → tunnel1_address: 52.x.x.3, tunnel2_address: 52.x.x.4
この 4 つの IP が、次の Step 4 で GCP 側に渡す情報になります。
暗号化のパラメータ
VPN Connection に設定する暗号化パラメータは、GCP 側と揃える必要があります。
IPsec のトンネル確立は 2 段階で行われます。
- Phase 1:まず「鍵交換のための安全な通信路」を作ります。ここでの暗号化設定は、この鍵交換チャネル自体を守るためのものです。
- Phase 2:Phase 1 で作った安全な通信路を使って、実際のデータを流すトンネル(IPsec SA)を確立します。ここでの設定が、実際のパケットに適用される暗号化です。
| パラメータ | 設定値 | 説明 |
|---|---|---|
| IKE バージョン | IKEv2 | 鍵交換プロトコル。v2 が推奨 |
| Phase 1 暗号化 | AES-256 | IKE SA の暗号化アルゴリズム |
| Phase 1 整合性 | SHA2-256 | IKE SA の整合性チェック |
| Phase 2 暗号化 | AES-256 | IPsec SA(実データ)の暗号化 |
| Phase 2 整合性 | SHA2-256 | IPsec SA の整合性チェック |
| DH グループ※3 | 14 | Diffie-Hellman 鍵交換のグループ |
| 認証方式 | Pre-Shared Key | 事前共有鍵。AWS と GCP で同じ値を設定 |
PSK(事前共有鍵)は AWS と GCP で完全に一致させる必要があります。 1 文字でも違うとトンネルが UP になりません。
※3 DH グループ(Diffie-Hellman グループ)とは
DH グループ(Diffie-Hellman グループ)とは、暗号化に使う鍵をお互いに安全に共有するための数学的な計算方式の強度設定です。
鍵そのものをネットワーク上に流さず、両者がそれぞれ計算することで同じ鍵を導き出します。
グループ番号が大きいほど計算の複雑さが増し、解読が難しくなります。
グループ 14 は 2048 bit 相当の強度で、セキュリティと処理速度のバランスが取れた一般的な選択肢です。
Step 4: GCP 側に External VPN Gateway とトンネルを作る【相手の情報登録 + IPsec トンネル】
AWS のトンネル用 IP が 4 つ確定したので、GCP 側に「相手先(AWS)の情報」を登録し、トンネルを張ります。
External VPN Gateway ── AWS の情報を登録する
GCP では「External VPN Gateway」で、接続相手(AWS)のトンネル用 IP を登録します。
# terraform/gcp/modules/vpn/main.tf
resource "google_compute_external_vpn_gateway" "aws" {
name = "poc-dev-aws-vpn-gw"
redundancy_type = "FOUR_IPS_REDUNDANCY"
# Step 3 で払い出された AWS の 4 つのトンネル用 IP
interface {
id = 0
ip_address = var.aws_tunnel0_tunnel1_address # VPN Connection 0 の tunnel1
}
interface {
id = 1
ip_address = var.aws_tunnel0_tunnel2_address # VPN Connection 0 の tunnel2
}
interface {
id = 2
ip_address = var.aws_tunnel1_tunnel1_address # VPN Connection 1 の tunnel1
}
interface {
id = 3
ip_address = var.aws_tunnel1_tunnel2_address # VPN Connection 1 の tunnel2
}
}
VPN Tunnel × 4 ── 暗号化トンネルを張る
GCP の 2 つのインターフェースと AWS の 4 つのトンネルアドレスを組み合わせて、4 本のトンネルを張ります。
# terraform/gcp/modules/vpn/main.tf
resource "google_compute_vpn_tunnel" "tunnel1" {
name = "poc-dev-tunnel1"
vpn_gateway = google_compute_ha_vpn_gateway.main.id # Step 2 の GW
vpn_gateway_interface = 0 # GCP interface0
peer_external_gateway = google_compute_external_vpn_gateway.aws[0].id # 上で作った External GW
peer_external_gateway_interface = 0 # AWS の tunnel0-tunnel1
shared_secret = var.tunnel1_preshared_key # ← Step 3 の PSK と同じ値
router = google_compute_router.main.id # Step 5 で作る Router
ike_version = 2
}
# tunnel2: GCP interface0 → AWS tunnel0-tunnel2
# tunnel3: GCP interface1 → AWS tunnel1-tunnel1
# tunnel4: GCP interface1 → AWS tunnel1-tunnel2
# (同様の構成で計 4 本作成)
4 本のトンネルの対応関係はこうなります:
GCP interface0 ──→ AWS VPN Connection 0 の tunnel1 ... tunnel1
GCP interface0 ──→ AWS VPN Connection 0 の tunnel2 ... tunnel2
GCP interface1 ──→ AWS VPN Connection 1 の tunnel1 ... tunnel3
GCP interface1 ──→ AWS VPN Connection 1 の tunnel2 ... tunnel4
4 本すべてが冗長構成で、どれか 1 本でも生きていれば通信は継続します。
ここまでで、暗号化されたトンネル自体は張れました。 しかし、まだ通信はできません。「どの IP 宛のパケットをこのトンネルに流すか」が決まっていないからです。それを次の Step 5 で設定します。
Step 5: BGP ルーティングを設定する
トンネルが張れても、「どの IP 宛のパケットをこのトンネルに流すか」をお互いに教え合わないと、実際のパケットは流れません。「10.0.0.0/16(AWS)宛のパケットはトンネルに流す」「10.1.0.0/24(GCP)宛のパケットはトンネルに流す」という経路情報を交換する必要があります。
これを自動でやるのが BGP(Border Gateway Protocol) です。
BGP はネットワーク同士が「自分の持つ IP アドレス帯」をお互いに通知し合うプロトコルです。AWS 側は「10.0.0.0/16 は自分のところにある」、GCP 側は「10.1.0.0/24 は自分のところにある」と広告し合うことで、VPN トンネルを通じてパケットが正しい宛先へ自動的に流れるようになります。
Cloud Router ── GCP 側の BGP を担当するルーター
GCP では Cloud Router が BGP を担当します。
# terraform/gcp/modules/vpn/main.tf
resource "google_compute_router" "main" {
name = "poc-dev-router"
network = var.network_self_link # VPC にアタッチ
bgp {
asn = 65534 # ← GCP 側の ASN
}
}
AWS 側は VPN Gateway に BGP 機能が内蔵されているので、別途ルーターを作る必要はありません。
BGP セッション用の inside IP
BGP セッションを張るには、トンネルの「内側」で使う専用の IP アドレスが必要です。169.254.x.x/30 のリンクローカルアドレスを使います。
/30 のアドレス空間(4 IP のうち使えるのは 2 つ):
169.254.202.0/30
.0 = ネットワークアドレス(使えない)
.1 = AWS 側の inside IP
.2 = GCP 側の inside IP
.3 = ブロードキャスト(使えない)
この inside CIDR は、Step 3 で AWS の VPN Connection を作った時に各トンネルに自動的に割り当てられます。その値を GCP 側に渡して、Cloud Router の Interface と BGP Peer に設定します。
# terraform/gcp/modules/vpn/main.tf
# Cloud Router のインターフェース(GCP 側の inside IP を設定)
resource "google_compute_router_interface" "tunnel1" {
vpn_tunnel = google_compute_vpn_tunnel.tunnel1[0].name
router = google_compute_router.main.name
# /30 の .2 が GCP 側
ip_range = "${cidrhost(var.tunnel1_inside_cidr, 2)}/${split("/", var.tunnel1_inside_cidr)[1]}"
}
# BGP ピア(AWS 側の inside IP と ASN を設定)
resource "google_compute_router_peer" "tunnel1" {
router = google_compute_router.main.name
peer_asn = 64512 # ← AWS の ASN
interface = google_compute_router_interface.tunnel1[0].name
# /30 の .1 が AWS 側
peer_ip_address = cidrhost(var.tunnel1_inside_cidr, 1)
}
各トンネル(4 本)に対して、Router Interface + BGP Peer を 1 セットずつ作ります。
BGP が確立すると何が起きるか
BGP セッションが確立すると、以下の経路交換が自動的に行われます:
AWS (ASN 64512) → GCP に広告: 「10.0.0.0/16 は自分の先にあるよ」
GCP (ASN 65534) → AWS に広告: 「10.1.0.0/24 は自分の先にあるよ」
これにより:
- GCP VPC 内で
10.0.0.0/16宛のパケット → VPN トンネルへ - AWS VPC 内で
10.1.0.0/24宛のパケット → VPN トンネルへ
と自動的にルーティングされるようになります。
Step 6: AWS 側のルートテーブルに経路を反映する
GCP 側は Cloud Router が BGP で受け取った経路を VPC のルーティングに自動反映してくれるので、追加の設定は不要です。
一方、AWS 側は「VPN Gateway Route Propagation」を明示的に有効にする必要があります。これを設定すると、BGP で受け取った経路がルートテーブルに自動追加されます。
# terraform/aws/modules/vpc/main.tf
resource "aws_vpn_gateway_route_propagation" "private" {
vpn_gateway_id = aws_vpn_gateway.main.id # Step 1 の VGW
route_table_id = aws_route_table.private.id # Aurora があるプライベートサブネットのルートテーブル
}
これにより、プライベートサブネットのルートテーブルに以下の経路が自動で追加されます:
送信先: 10.1.0.0/24 → ターゲット: vgw-xxxxxxxx(VPN Gateway)
ここまでで、VPN 接続は完成です。 GCP VPC 内のパケットが AWS のプライベートサブネットに到達できるようになりました。
おわりに
今回、VPNを使ったAWSとGCPのマルチクラウド連携をやってみました。
AWSとGCP間をあっちいったりこっちいったりするので中々ややこしいですし、IPも色々な種類が出てくるのでさらに分かりずらいですね。。
VPNという単語自体は、セキュリティ関連の話でよく耳にすることはありますが、ただ実際にVPN環境を作成してみるという機会は中々ないかなと思うので、もしそういう機会に遭遇した人の少しでも助けになれば幸いです!







