GKEのPodからVPNを経由してAWSと通信してみた

# はじめに
[前回の投稿](https://qiita.com/nagase/items/df18e308e6fc98e11aaa)でGCPとAWSをVPNで繋ぐところまでやってみました。
その続きで今回は GKE の Pod から VPN を経由して AWS のサービスを利用したかったので、その方法をまとめます。

# 普通にサブネットと GKE クラスタを作成して、AWS と通信できるか確認

## サブネットの準備
AWS と VPN 接続されている VPC `aws-internal-connect` に、サブネット `aws-internal-connect-subnet-1` を CIDR `10.1.0.0/24` で作成します。
![gcp_vpc_subnet_1.png](https://qiita-image-store.s3.amazonaws.com/0/94476/c1a3af76-eb5c-4aa8-fbc4-7167bffc7331.png)

## GKEクラスタの作成
クラスタ `test-cluster` を、普通に作成してみます。その際、サブネットには上記 `aws-internal-connect-subnet-1` を設定します。

```bash
$ gcloud beta container clusters create test-cluster \
    --region=asia-northeast1 \
    --cluster-version=1.9.7-gke.3 \
    --machine-type=n1-standard-1 \
    --image-type=cos \
    --num-nodes=1 \
    --network=aws-internal-connect \
    --subnetwork=aws-internal-connect-subnet-1 \
    --enable-autorepair
```

## デプロイ
作成したクラスタに Pod をいくつかデプロイします。

```bash
$ kubectl run <Deployment Name> \
    --image=asia.gcr.io/<My Project>/<Image Name>:v1 \
    --replicas=1 \
    --port=80
deployment.apps "<Deployment Name>" created
```


## クラスタ内のネットワーク
デプロイ後のGKEクラスタのネットワークを調べてみた結果、下図のようなネットワークを構成していることが判りました。

![topology_1.png](https://qiita-image-store.s3.amazonaws.com/0/94476/4440a82c-11d3-7815-6e35-4000da869a23.png)

今回作成した GKE クラスタのネットワークは、次の CIDR で構成されていました。

- Node IP (10.1.0.0/24)
- Pod IP (10.32.0.0/24, 10.32.1.0/24, 10.32.2.0/24)
- Service IP (10.35.240.0/20) 

**Node IP** はクラスタ作成時に指定したサブネット `aws-internal-connect-subnet-1 ` から IP が割り当てられており、**Pod IP** と **Service IP** は 自動的に割り当てられているようです。

さて、このクラスタ内の Pod から AWS VPC 内のプライベート IP に対して通信することはできるでしょうか？

答えは **NO** です。

Cloud Router の BGP アドバタイズ（広報）を確認すれば判るのですが、あくまでもアドバタイズされるのは VPC 内に作成したサブネットの CIDR だけですので、**Pod IP** や **Service IP** の CIDR が AWS 側にアドバタイズされない為です。（AWSへリクエストは到達するが、AWS側はPodへのルートを知らない為、レスポンスを GCP に返せない）



```bash
# Cloud Router の BGP アドバタイズ確認
$ gcloud compute routers get-status <Cloud Router Name> --region asia-northeast1
･･･
  bgpPeerStatus:
  - advertisedRoutes:
    - destRange: 10.1.0.0/24
      kind: compute#route
      nextHopIp: 169.254.24.222
      priority: 100
    ipAddress: 169.254.24.222
    name: aws-vpn-1-bgp-1
    numLearnedRoutes: 6
    peerIpAddress: 169.254.24.221
    state: Established
    status: UP
    uptime: 1 weeks, 1 days, 23 hours, 0 minutes, 0 seconds
    uptimeSeconds: '774000'
  - advertisedRoutes:
    - destRange: 10.1.0.0/24
      kind: compute#route
      nextHopIp: 169.254.27.98
      priority: 100
    ipAddress: 169.254.27.98
    name: aws-vpn-2-bgp-1
    numLearnedRoutes: 6
    peerIpAddress: 169.254.27.97
    state: Established
    status: UP
    uptime: 2 days, 13 hours, 19 minutes, 0 seconds
    uptimeSeconds: '220740'
･･･
```

# Pod から AWS へプライベート IP 通信する方法
GKE クラスタ内の Pod から VPN を介してプライベート IP で通信させるには、 「**Pod IP と Service IP を BGP アドバタイズできるように GKE クラスタを構成すればよい**」のです。
具体的には、IP エイリアスを利用した『**VPCネイティブ クラスタ**』を作成すれば、Pod IP と Service IP がアドバタイズされるようになります。

## セカンダリ IP の CIDR を指定してサブネットを作成
新しいサブネット `aws-internal-connect-subnet-2` を作成します。その際、`セカンダリ IP 範囲` を２つ作成します。
セカンダリ IP のひとつは **Pod IP** の CIDR、もうひとつには **Service IP** の CIDR を設定します。（下図参照）

![gcp_vpc_subnet_2.png](https://qiita-image-store.s3.amazonaws.com/0/94476/6c48c01e-1769-40e2-eb83-a393dbe079fc.png)

セカンダリ IP 範囲を設定する際、注意する点は**ネットマスク長**です。
GKE クラスタでは各 IP 範囲のマスク長に制限が設けられています（下表参照）ので、その範囲を超えないマスク長をセカンダリ IP の CIDR に設定します。
今回はそれぞれ最小マスク長でセカンダリ IP の CIDR を設定しました。

|IP 範囲|デフォルトマスク長|最小マスク長|最大マスク長|
|---|:-:|:-:|:-:|
|Pod IP|**/14**|**/19**|**/11**|
|Service IP|**/20**|**/22**|**/18**|


これでプライマリ IP  とセカンダリ IP の CIDR （つまり Pod IP と Service IP の CIDR）もアドバタイズされるようになります。

## IP エイリアスを使用して VPC ネイティブ クラスタを再作成
VPC ネイティブ（IP エイリアス）を有効化して GKE クラスタ `test-cluster` を再作成します。
VPC ネイティブを有効化するには、次のようにオプション `--enable-ip-alias` と、前述でサブネットに設定した２つのセカンダリ IP の CIDR を指定します。


```bash
#GKEクラスタ作成(VPCネイティブ有効)
$ gcloud beta container clusters create test-cluster \
    --region=asia-northeast1 \
    --cluster-version=1.9.7-gke.3 \
    --machine-type=n1-standard-1 \
    --image-type=cos \
    --num-nodes=1 \
    --network=aws-internal-connect \
    --subnetwork=aws-internal-connect-subnet-2 \
    --enable-ip-alias \
    --cluster-secondary-range-name=subnet-2-pod \
    --services-secondary-range-name=subnet-2-svc \
    --enable-autorepair
```

クラスタ作成後は、任意のPodをデプロイします。

## クラスタのネットワーク確認
**VPC ネイティブ クラスタ**作成後のネットワークは、下図のように構成されています。

![topology_2.png](https://qiita-image-store.s3.amazonaws.com/0/94476/fc990910-41f9-12eb-c66d-4e99be5009e6.png)


Cloud Router の BGP アドバタイズを確認すると、**Pod IP** と **Service IP** の CIDR がアドバタイズされていることが判ります。

```bash
$ gcloud compute routers get-status <Cloud Router Name> --region asia-northeast1
･･･
  bgpPeerStatus:
  - advertisedRoutes:
    - destRange: 10.1.128.0/19
      kind: compute#route
      nextHopIp: 169.254.24.222
      priority: 100
    - destRange: 10.1.160.0/22
      kind: compute#route
      nextHopIp: 169.254.24.222
      priority: 100
    - destRange: 10.1.0.0/24
      kind: compute#route
      nextHopIp: 169.254.24.222
      priority: 100
    - destRange: 10.1.1.0/24
      kind: compute#route
      nextHopIp: 169.254.24.222
      priority: 100
    ipAddress: 169.254.24.222
    name: aws-vpn-1-bgp-1
    numLearnedRoutes: 6
    peerIpAddress: 169.254.24.221
    state: Established
    status: UP
    uptime: 1 weeks, 1 days, 23 hours, 0 minutes, 0 seconds
    uptimeSeconds: '774000'
  - advertisedRoutes:
    - destRange: 10.1.128.0/19
      kind: compute#route
      nextHopIp: 169.254.27.98
      priority: 100
    - destRange: 10.1.160.0/22
      kind: compute#route
      nextHopIp: 169.254.27.98
      priority: 100
    - destRange: 10.1.0.0/24
      kind: compute#route
      nextHopIp: 169.254.27.98
      priority: 100
    - destRange: 10.1.1.0/24
      kind: compute#route
      nextHopIp: 169.254.27.98
      priority: 100
    ipAddress: 169.254.27.98
    name: aws-vpn-2-bgp-1
    numLearnedRoutes: 6
    peerIpAddress: 169.254.27.97
    state: Established
    status: UP
    uptime: 2 days, 13 hours, 19 minutes, 0 seconds
    uptimeSeconds: '220740'
･･･
```

クラスタ内にある Pod にログイン（`kubectl exec`）し、AWS VPC 内にある任意の IPアドレス 宛に疎通テストを行ってみます。
尚、以下では EC2 インスタンス 宛に Ping テストを行いましたが、テストの前に EC2 インスタンス 側のセキュリティグループのルールに、 Pod IP の CIDR を追加（プロトコル ＝ ICMP）する必要があるので注意してください。


```bash
# Pod 内から AWS の VPC IP へ疎通テスト 
$ ping -c 1 10.0.1.7
PING 10.0.1.7 (10.0.1.7) 56(84) bytes of data.
64 bytes from 10.0.1.7: icmp_seq=1 ttl=62 time=10.0 ms

--- 10.0.1.7 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 10.000/10.000/10.000/0.000 ms
```

これで GKE クラスタ内の Pod から VPN を介して、AWS の色々なサービスを利用できるようになりました :grinning:

# GKE サービスのエンドポイントについて
Pod が提供するアプリケーションは**サービス**を作成し、そのサービスを介してネットワーク上に公開しますが、そのサービスのエンドポイントにはデフォルトでは外部（パブリック） IP アドレスが割り当てられます。

折角オンプレミス、AWS、GCP を VPN で繋いでシームレスに連携できる環境が出来たので、社内向けに公開したいサービス等、GKE サービスのエンドポイントにプライベート IP アドレスを割り当てたいと思います。

## YAML ファイルの準備
サービスを定義した YAML ファイルを準備します。
今回は例として `plot` というサービスを作成することにします。

```yaml:service_plot.yaml
apiVersion: v1
kind: Service
metadata:
  name: plot
  namespace: default
  annotations:
    cloud.google.com/load-balancer-type: "internal"
  labels:
    run: plot
spec:
  type: LoadBalancer
  #loadBalancerIP: [IP-ADDRESS]
  ports:
  - port: 80
    protocol: TCP
    targetPort: 5000
  selector:
    run: plot
```

ポイントは `annotations` の部分です。
`cloud.google.com/load-balancer-type` を `internal` と定義することで、**Node IP**（≒クラスタに設定したサブネットのプライマリ IP の CIDR）から任意の IP アドレスがエンドポイントに割り当てられます。
ちなみに任意の IP アドレスではなく、割り当てる IP アドレスを指定したい場合は、`loadBalancerIP` のコメントを削除して `[IP-ADDRESS]` にその IP アドレスを記述します。

## サービスの作成
準備した YAML ファイルを元に、サービスを作成します。

```bash
$ kubectl create -f service_plot.yaml
```

## サービスの確認
作成されたサービスを確認します。
`EXTERNAL-IP`が外部 IP アドレスではなく、Node IP の CIDR から IP アドレスが割り当てられていることが判ります :grinning:

```bash
$ kubectl get svc
NAME         TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
plot         LoadBalancer   10.1.163.57   10.1.1.7      80:32141/TCP   6d
```

# 参考
https://cloud.google.com/kubernetes-engine/docs/ip-aliases?hl=ja
https://cloud.google.com/kubernetes-engine/docs/how-to/alias-ips