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


はじめに

前回の投稿で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 で作成します。

GKEクラスタの作成

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

$ 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 をいくつかデプロイします。

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

クラスタ内のネットワーク

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

今回作成した 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 IPService IP は 自動的に割り当てられているようです。

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

答えは NO です。

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

# 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 範囲 を2つ作成します。
セカンダリ IP のひとつは Pod IP の CIDR、もうひとつには Service IP の CIDR を設定します。(下図参照)

セカンダリ 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 と、前述でサブネットに設定した2つのセカンダリ IP の CIDR を指定します。

#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 ネイティブ クラスタ作成後のネットワークは、下図のように構成されています。

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

$ 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)する必要があるので注意してください。

# 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 の色々なサービスを利用できるようになりました

GKE サービスのエンドポイントについて

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

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

YAML ファイルの準備

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

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-typeinternal と定義することで、Node IP(≒クラスタに設定したサブネットのプライマリ IP の CIDR)から任意の IP アドレスがエンドポイントに割り当てられます。
ちなみに任意の IP アドレスではなく、割り当てる IP アドレスを指定したい場合は、loadBalancerIP のコメントを削除して [IP-ADDRESS] にその IP アドレスを記述します。

サービスの作成

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

$ kubectl create -f service_plot.yaml

サービスの確認

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

$ 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