Google Cloud の Cloud NAT を使って GKE の External IP を固定化してみた

## 前提

- GKE は Kubernetes をベースとした Container as a Service です。
- そのためクラスタには node-pools があり、 pod はこのリソースを使用して起動します。
- node-pools はスケーリングすることを前提として存在するため、外部と通信するための External IP は変動するものと思った方が良いです。
- ただそうすると IP アドレスによるアクセス制限をしている外部サービスとの連携に困ります。
- そこでこれまでなら自力で Gateway サーバーを立ててそれを介して通信するようにしていました。

## tl;dr

- JST 2018-10-11 に Google Cloud / Cloud NAT という NAT サービスがベータリリースされました。
- これを GKE と連携して使えば解決！

## 具体的な方法

1. VPC を作る
2. サブネットを作る
3. External Static IP を払い出す
4. ルーターを作る
5. Cloud NAT を作る
6. クラスタを作る
7. pod 立てて External IP を調べてみる

### VPC を作る

```bash
$ gcloud compute networks create ${vpc_name}\
  --project=${project_id}\
  --bgp-routing-mode=regional\
  --subnet-mode=custom
```

- 変数部分は環境に合わせて指定
- **--bgp-routing-mode**
    - global でも良いけど今回はリージョンに閉じた話なので regional
- **--subnet-mode**
    - auto は勝手にサブネット作って煩わしいので使わない
    - legacy は古い方式で Cloud NAT とか使えない
    - やりたいことやるだけなら custom で手動設定

### サブネットを作る

```bash
$ gcloud compute networks subnets create ${subnet_name}\
  --project=${project_id}\
  --region=${region}\
  --network=${vpc_name}\
  --range=${subnet_range}
```

- 変数部分は環境に合わせて指定
    - **--range=${subnet_range}** は RFC1918 のお好きな IP 帯

### External Static IP を払い出す

```bash
$ gcloud compute addresses create ${external_ip_name}\
  --region=${region}
```

- 変数部分は環境に合わせて指定

### ルーターを作る

```bash
$ gcloud compute routers create ${router_name}\
  --region=${region}\
  --network=${vpc_name}\
  --asn=65001
```

- 変数部分は環境に合わせて指定
- **--asn=65001**
    - 自律システム番号（ASN）
    - ネットワーク内で一意な 64512 〜 65534 または 4200000000 〜 4294967294 の範囲で決めろとのこと

### Cloud NAT を作る

```
$ gcloud compute routers nats create ${nat_name}\
  --region=${region}\
  --router=${router_name}\
  --nat-external-ip-pool="${external_ip_name}"\
  --nat-custom-subnet-ip-ranges="${subnet_name}"
```

- 変数部分は環境に合わせて指定
- **--nat-external-ip-pool="${external_ip_name}"**
    - **--auto-allocate-nat-external-ips** で自動払出するか、割り当てる IP を列挙する
- **--nat-custom-subnet-ip-ranges="${subnet_name}"**
    - **--nat-all-subnet-ip-ranges** や **--nat-primary-subnet-ip-ranges** でネットワーク内全てのサブネットまたは primary 全てを範囲とするか、 NAT を使う IP 帯を列挙する

> (2020-01-24) (@zawataki さんからのご指摘) alpha じゃなくなり `--nat-external-ip-pool` `--nat-custom-subnet-ip-ranges` の指定方法が変わっていたので更新

### クラスタを作る

```bash
$ gcloud container clusters create ${gke_cluster_name}\
  --project=${project_id}\
  --zone=${zone}\
  --network=${vpc_name}\
  --subnetwork=${subnet_name}\
  --enable-ip-alias\
  --enable-private-nodes\
  --master-ipv4-cidr=172.16.0.0/28\
  --enable-master-authorized-networks\
  --master-authorized-networks=0.0.0.0/0\
  --no-enable-legacy-authorization\
  --no-enable-basic-auth\
  --no-issue-client-certificate
```

- 変数部分は環境に合わせて指定
- 細かくは割愛
- **--network=${vpc_name}** と **--subnetwork=${subnet_name}** で Cloud NAT の対象サブネットを指定する
- node-pools が External IP を持っていないことが Cloud NAT を使用する条件なので **--enable-private-nodes** にする
- External IP がないと `kubectl` で操作に困るようなのでマスターエンドポイントへのアクセスを **--enable-master-authorized-networks** で出来るようにする
    - **--master-authorized-networks=0.0.0.0/0** で指定したアクセス元だけ許可される

### pod 立てて External IP を調べてみる

```bash
$ kubectl run tmp-pod -it --rm --image=${image} -n ${namespace} --restart=Never -- curl httpbin.org/ip
{
  "origin": "${external_ip}"
}
```