チームの基盤にCloud NATを導入しました
三浦 泰嗣
この記事は、リクルートライフスタイル Advent Calendar 2019 の24日目の記事です!メリークリスマス!
昨日の記事は龍野さんによるWorkload Identityの実基盤への導入でした。
私たちのチームではGKEを使ったAPI基盤やCloud Composerを使ったJob基盤を提供しており、社内のエンジニアがこの基盤上で本番サービス向けの機能を開発しています。
この基盤のNATはGoogle Compute EngineのVMで稼働していましたが、自分たちでNATの管理をするのは運用コストが高く、またスケーラビリティも考慮できていないといった問題がありました。
そんな時、GCPからクラウドマネージドなNATであるCloud NATの提供が開始されたためこれを機にCloud NATへ移行しようということになりました。
しかし私たちの基盤では本番稼働しているAPIもあり、また稼働しているAPIやJobの数も膨大なため「基盤としてダウンタイムを発生させることなく」「出来るだけ移行のコストを下げつつ」マイグレーションを行うこととなりました。
この記事ではマイグレーションの詳細とCloud NATを扱う上でのポイントを紹介していきたいと思います。
既存の基盤とCloud NATの調査
既存の基盤
既存の基盤ではGoogle Compute EngineのVMとしてNATを構築しており、ネットワーク内のno-ip
タグが付与されたVMのトラフィックをNATに流すように設定しています。また外部IPを持っているVMは自身のIPで外部と通信します。
このno-ip
タグを付与されているVMはGCEのVMやGKEのNodeなど多岐に渡り、これらを漏れなくCloud NATを経由しての通信となるように移行していく必要があります。
これらの現状の仕様を考慮しつつスムーズにCloud NATに移行する手段として方針に上がったのがCloud NATのSubnetworkによる管理でした。
Subnetworkについて
Cloud NATではNATを使用するVMをSubnet単位で指定することができます。
例えばCloud NATにSubnet AとSubnet Bを紐づけた場合、Subnet AとSubnet Bに所属するVMはCloud NATを使って通信を行うように設定できます。
またno-ip
タグによるルーティングのように特別なルールがある場合にはそちらが優先され、優先順位としては ルーティングルール > 外部IP > Subnet となっています。
上記のことから、GCEのVMが属するSubnetやGKEクラスタのSubnetを洗い出してCloud NATと紐づけたあとにタグの削除を行えば、既存のVM群に影響を与えずにNAT VMからCloud NATへと移行できることがわかりました。
幸い、GCEのVMの多くは1~2個のSubnetに集まっていたのでSubnetを洗い出す作業にそれほど手間はかかりませんでした。
ports/vmについて
また、Cloud NATを扱う上で注意したい点としてports/vmの振る舞いがあります。
Cloud NATはCloud NATに使用する静的IPひとつにつき、65536からwell-knownポートの1024個を差し引いた64512個のポートをCloud NATを使うVMのTCP通信用に用意しています。
ひとつのVMがどれくらいの量のポートを使えるかを指定するのがmin_ports_per_vmというパラメータです。このパラメータで使用可能なポート数64512を割った値がCloud NATのひとつの静的IPで通信可能なVMの数になります。
このmin_ports_per_vmはひとつのIP:Portの組に対して適用される値なので、1500のコネクションをひとつのIP:Portの組に送るにはmin_ports_per_vmの値は1500に設定する必要がありますが、1500のコネクションをIPは同じでも別々のPortに送るのであればmin_ports_per_vmは1で済みます。
もしひとつのVMが同時に通信できる量を多くしておきたい(=min_ports_per_vmを高い値に設定したい)が、Cloud NATを使いたいVMの数が許容数を超えてしまうといった場合にはCloud NATに複数の静的IPを設定しておく必要があります。
GKEを使用している場合、min_ports_per_vmはpodではなくNodeに対する値となるため、注意が必要です。
またチームでports/vmの妥当な値を調べていたところ、「設定したports/vmの値以下のコネクションしか張ることができない」という事象に遭遇しました。
こちらはGCPのサポートチームに問い合わせたところ、Cloud NATのバグとのことでした。
今後GCP側で対応していくのでそれまではports/vmの値は想定の値より多め(2倍程度というアドバイスを受けました)を設定していくことになりました。
Cloud NATの設定
上記の調査を受けて、私たちのチームでは次のようなリソース定義をTerraformで行いました。
GCEの多くのVMが所属しているSubnetとGKEクラスタのSubnetをCloud NATの管理Subnetとして設定、そしてports/vmは512で設定しVMやpodの数も考慮してCloud NATの静的IPは複数取得して設定しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
resource "google_compute_address" "nat-external-address" {
count = 2
name = "nat-nw-${count.index}"
}
resource "google_compute_router" "router-nat-nw" {
name = "router-nat-nw"
network = var.nw_a
}
data "google_compute_subnetwork" "subnet-c" {
name = "subnet-c"
}
resource "google_compute_router_nat" "cloud-nat" {
name = "cloud-nat"
router = google_compute_router.router-nat-nw.name
min_ports_per_vm = 512
nat_ip_allocate_option = "MANUAL_ONLY"
nat_ips = google_compute_address.nat-external-address.*.self_link
source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS"
subnetwork {
name = data.google_compute_subnetwork.cluster-a.self_link
source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
}
subnetwork {
name = data.google_compute_subnetwork.cluster-b.self_link
source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
}
subnetwork {
name = data.google_compute_subnetwork.subnet-c.self_link
source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
}
log_config {
# filterをALLにすると全てのログを出力してしまうのでコストに注意
# Errorのログのみ出力する設定も可能
filter = "ALL"
enable = true
}
}
Terraformでの注意点
Terraformを使ってCloud NATのリソースを管理する場合、ひとつだけ注意点があります。
Terraformで使用しているGCPのproviderのバージョンがv2.12.0以下だとCloud NATのリソースのフィールドの一部に「ForceNew: true」の設定がされており、これによりCloud NATのリソースに手を加えた場合にCloud NATの再作成処理が走りダウンタイムが発生してしまいます。(v2.13.0で改善されました)
もし諸般の事情でGCPのproviderのバージョンをv2.12.0より上げられない際には、GCPのコンソール上から修正を行えばCloud NATにダウンタイムは発生しないので、(ちょっとハック的なやり方になってしまいますが)コンソール上から変更を加えてその状態にTerraformのリソースを合わせてapplyすることでダウンタイムを発生させずにCloud NATに変更を加えることが可能です。
Cloud NATの運用
Cloud NATの導入により、チームの基盤の構成は次のようになりました。
まだNAT VMの削除が済んでいないため、NAT VMとCloud NATが並行稼働している状態ですが、タグを削除することでCloud NATへトラフィックを移せる段階まで来ているので、NAT VMは近い将来に利用を停止しCloud NAT一本になる予定です。
最後に
本記事では、私たちのチームで行なったCloud NAT導入に向けた検証と設定のポイントを紹介しました。
NATはネットワーク内の全てのVMにとって単一障害点になりうる部分なので、プロジェクト内の通信数やVM数を確認しながら余裕のある設定を行うのが良いと思います。
リクルートライフスタイルでは、一緒にサービス・プロダクト改善をしてくれる仲間を募集しています! 興味のある方はぜひご連絡をお待ちしております (Twitter: @ymym3412)!
それではよいクリスマスを!