Cloud NATでGKEの外向きIPアドレスを固定化するのをDeployment Managerでやってみた

## 動機

GKEでサービスを稼働させているのですが、そのサービス上からある既存外部システムに接続することになりました。
その外部システムでは接続元IPアドレスをホワイトリストでブロックしている為、GKEの外向きの接続IPアドレスを固定する必要があります。

Cloud NAT登場以前は方法が見つからず[^1]、GKEクラスタ作成後にノードのIPアドレスを直接gcloudコマンドで書き換えていました。

参考: https://stackoverflow.com/a/42295292

しかしその方法だとGKEのアップグレードが走るとIPアドレスが剥がれてしまったり、ノードのスケールができなかったりと、かなり不便な状態でした。

Cloud NAT登場で外向けIPアドレスを固定できる様になったので、その手順を紹介します。
さらにそれをDeployment Managerを使ってデプロイします。

[^1]: もしかしたら最新GKEやKubernetesでは外向きIPを固定できる機能があるのかもしれません。ご存知の方の目に止まりましたらコメントで教えていただけると幸甚です。

## VPCネットワーク周りの定義

以下のサンプルを参考にしてVPCネットワークを作成します。
https://cloud.google.com/nat/docs/gke-example#step_1_create_a_vpc_network_and_subnet

### network 定義

下記のコマンドと同等の設定になるようにします。

```sh
gcloud compute networks create custom-network1 --subnet-mode custom
```

ドキュメントを参考にして設定ファイルを記述します。
https://cloud.google.com/compute/docs/reference/latest/networks?hl=ja

- custom モードにしたいので、 autoCreateSubnetworks を false に設定します。

```yaml
- name: custom-network1
  type: compute.v1.network
  properties:
    name: custom-network1
    autoCreateSubnetworks: false
```

### subnetwork 定義

下記のコマンドと同様になるように設定します。

```sh
gcloud compute networks subnets create subnet-asia-northeast1-192 \
   --network custom-network1 \
   --region asia-northeast1 \
   --range 192.168.1.0/24
```

ドキュメントを参考にして設定ファイルを記述します。
https://cloud.google.com/compute/docs/reference/rest/v1/subnetworks?hl=ja

- network に上記で定義した custom-network1 を設定します。
- ipCidrRange が range にあたるので、サンプルと同じ値を設定します。

```yaml
- name: subnet-asia-northeast1-192
  type: compute.v1.subnetwork
  properties:
    name: subnet-asia-northeast1-192
    network: $(ref.custom-network1.selfLink)
    region: asia-northeast1
    ipCidrRange: 192.168.1.0/24
```

## Static IP 定義の作成

今回は Static IP を指定したいので、ドキュメントを参考にして Static IP の定義を作成します。
https://cloud.google.com/compute/docs/reference/latest/addresses?hl=ja

```yaml
- name: my-static-ip
  type: compute.v1.address
  properties:
    name: my-static-ip
    region: asia-northeast1
```

## Cloud Router, Cloud NAT 定義

以下のサンプルを参考にして Cloud Router と Cloud NAT を作成します。
https://cloud.google.com/nat/docs/gke-example#step_6_create_a_nat_configuration_using

下記のコマンドと同様になるように設定します。
※ 今回は静的IPにするので、nat-external-ip-pool に上記で定義したIPアドレスを設定します。

```sh
gcloud compute routers nats create nat-config \
    --router=nat-router \
    --nat-all-subnet-ip-ranges
    --nat-external-ip-pool=my-static-ip
```

ドキュメントを参考にして設定ファイルを記述します。
https://cloud.google.com/compute/docs/reference/latest/routers?hl=ja

- network に上記で定義した custom-network1 を設定します。
- natIps に上記で定義した my-static-ip を設定します。
- natIps に値を設定するので、 natIpAllocateOption に MANUAL_ONLY を設定します。

```yaml
- name: nat-router
  type: compute.v1.router
  properties:
    name: nat-router
    network: $(ref.custom-network1.selfLink) 
    region: asia-northeast1
    nats:
    - name: nat-config
      sourceSubnetworkIpRangesToNat: ALL_SUBNETWORKS_ALL_IP_RANGES
      natIpAllocateOption: MANUAL_ONLY
      natIps:
      - $(ref.my-static-ip.selfLink)
```

## Cluster の定義

以下のサンプルを参考にしてクラスタを作成します。
https://cloud.google.com/nat/docs/gke-example#step_2_create_a_private_cluster

下記のコマンドと同様になるように設定します。

```sh
gcloud container clusters create "nat-test-cluster" \
    --zone "asia-northeast1-a" \
    --enable-private-nodes \
    --master-ipv4-cidr "172.16.0.0/28" \
    --enable-ip-alias \
    --network "projects/PROJECT_ID/global/networks/custom-network1" \
    --subnetwork "projects/PROJECT_ID/regions/asia-northeast1/subnetworks/subnet-asia-northeast1-192" \
    --enable-master-authorized-networks
```

ドキュメントを参考にして設定ファイルを記述します。
https://cloud.google.com/kubernetes-engine/docs/reference/rest/v1/projects.zones.clusters

- network に上記で定義した custom-network1 を設定します。
- subnetwork に上記で定義した subnet-asia-northeast1-192 を設定します。
- 限定公開クラスタでないと Cloud NAT を使用できないので、enablePrivateNodes を true に設定します。
- 限定公開クラスタはエイリアスIPを有効にする必要があるので、useIpAliases を true　に設定します。

```yaml
- name: nat-test-cluster
  type: container.v1.cluster
  properties:
    zone: asia-northeast1-a
    cluster:
      name: nat-test-cluster
      network: $(ref.custom-network1.selfLink)
      subnetwork: $(ref.subnet-asia-northeast1-192.selfLink)
      nodePools:
      - name: default-pool
        initialNodeCount: 1
      privateClusterConfig:
        enablePrivateNodes: true
        enablePrivateEndpoint: false
        masterIpv4CidrBlock: 172.16.0.0/28
      ipAllocationPolicy:
        useIpAliases: true
      masterAuthorizedNetworksConfig:
        enabled: true
        cidrBlocks:
        - displayName: any
          cidrBlock: 0.0.0.0/0
```

## 完成した定義ファイル

### my-cluster-with-cloud-nat.jinja

```yaml
resources:
- name: custom-network1
  type: compute.v1.network
  properties:
    name: custom-network1
    autoCreateSubnetworks: false

- name: subnet-asia-northeast1-192
  type: compute.v1.subnetwork
  properties:
    name: subnet-asia-northeast1-192
    network: $(ref.custom-network1.selfLink)
    region: asia-northeast1
    ipCidrRange: 192.168.1.0/24

- name: my-static-ip
  type: compute.v1.address
  properties:
    name: my-static-ip
    region: asia-northeast1

- name: nat-router
  type: compute.v1.router
  properties:
    name: nat-router
    network: $(ref.custom-network1.selfLink) 
    region: asia-northeast1
    nats:
    - name: nat-config
      sourceSubnetworkIpRangesToNat: ALL_SUBNETWORKS_ALL_IP_RANGES
      natIpAllocateOption: MANUAL_ONLY
      natIps:
      - $(ref.my-static-ip.selfLink)

- name: nat-test-cluster
  type: container.v1.cluster
  properties:
    zone: asia-northeast1-a
    cluster:
      name: nat-test-cluster
      network: $(ref.custom-network1.selfLink)
      subnetwork: $(ref.subnet-asia-northeast1-192.selfLink)
      nodePools:
      - name: default-pool
        initialNodeCount: 1
      privateClusterConfig:
        enablePrivateNodes: true
        enablePrivateEndpoint: false
        masterIpv4CidrBlock: 172.16.0.0/28
      ipAllocationPolicy:
        useIpAliases: true
      masterAuthorizedNetworksConfig:
        enabled: true
        cidrBlocks:
        - displayName: any
          cidrBlock: 0.0.0.0/0
```

### my-cluster-with-cloud-nat.yaml

my-cluster-with-cloud-nat.jinja を参照しているだけです。

```yaml
imports:
- path: my-cluster-with-cloud-nat.jinja

resources:
- name: my-cluster-with-cloud-nat
  type: my-cluster-with-cloud-nat.jinja
```

## リソースのデプロイ

下記のコマンドでデプロイします。

```sh
gcloud deployment-manager deployments create my-cluster-deployment --config ./my-cluster-with-cloud-nat.yaml
```

## IPアドレス確認

下記のコマンドで認証情報を取得します。

```sh
$ gcloud container clusters get-credentials --zone asia-northeast1-a nat-test-cluster
```

サンプルイメージをクラスタにデプロイします。

```sh
$ kubectl create deployment hello-server --image=gcr.io/google-samples/hello-app:1.0
```

ポッドに入って IPアドレスを確認します。

```sh
$ kubectl get pod
NAME                            READY   STATUS    RESTARTS   AGE
hello-server-xxxxxx   1/1     Running   0          3s

$ kubectl exec -it hello-server-xxxxxx /bin/sh

$ wget -qO- httpbin.org/ip
```