GKEでIPを固定した内部Ingressを作成する

公開:2020/11/03
更新:2021/02/19
8 min読了の目安(約7500字TECH技術記事

2021/02/19 更新

内部Ingressでもサポートされるようになった

TL;DL;

Kubernetes Ingressでは内部Ingreesの固定IPはサポートしていないのでnginx-ingressを利用する

背景

GKEで内部Ingressを作成する際に、LBのIPを固定させたかった
ドキュメントを読むとKubernetes Ingressでは対応していなかった。
対策を調べてたらnginx-ingressを使うと解決できるというのを見た。
やったのでまとめ

限定公開のGKEを作成する

パブリック エンドポイントへ無制限にアクセス可能な限定公開クラスタの作成をします。

GCPコンソールから作成

  1. GKEのメニューからクラスタの作成を選択
  2. 名前はprivate-cluster-2, ロケーションタイプはゾーンasia-northeast1-cを選択
  3. 左のネットワークキングを開く
  4. 限定公開クラスタにチェック
  5. マスター IP 範囲172.16.0.32/28を入力
  6. ネットワークとノードのサブネットをdefaultに設定
  7. マスター承認済みネットワークを有効にするのチェックボックスをオフのままにしておく(これオンにすると、追加設定しないとcloud shellからkubectlでアクセスできない)
  8. 左のセキュリティを開く
  9. クライアント証明書を発行する チェックボックスがオフのままにしておく
  10. 作成

接続確認

クラスタが作成されたらCLOUD SHELLを開き、以下を実行する

$ gcloud container clusters get-credentials private-cluster-2 --zone asia-northeast1-c --project ${GOOGLE_CLOUD_PROJECT}

さらに以下を実行して、結果が取得できればOK

$ kubectl get no
NAME                                               STATUS   ROLES    AGE   VERSION
gke-private-cluster-2-default-pool-24553f8b-0fpw   Ready    <none>   96s   v1.17.12-gke.1504
gke-private-cluster-2-default-pool-24553f8b-38vk   Ready    <none>   95s   v1.17.12-gke.1504
gke-private-cluster-2-default-pool-24553f8b-nd4r   Ready    <none>   96s   v1.17.12-gke.1504

nginx-ingressをデプロイ

CLOUD SHELLでさらに作業を進めます。
まずnginx-ingressのマニフェストを取得します。

$ wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/cloud/deploy.yaml

デフォルトだと外部LBを作成するので、内部LBかつIPを固定して起動するように変更します。

vi deploy.yml
# Source: ingress-nginx/templates/controller-service.yaml
apiVersion: v1
kind: Service
metadata:
+  annotations: 
+    cloud.google.com/load-balancer-type: "Internal"
  labels:
    helm.sh/chart: ingress-nginx-3.6.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.40.2
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: controller
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  type: LoadBalancer
+  loadBalancerIP: 10.146.0.101
  externalTrafficPolicy: Local
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: http
    - name: https
      port: 443
      protocol: TCP
      targetPort: https
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/component: controller

固定IPについては、GKEのクラスタから疎通可能なIPである必要があります。とりあえずGKEと同一のsubnet内のIPを指定することにします。asis-northeast1のdefaultサブネットで作っているなら、10.146.0.0/20が利用できます。(ここでは、10.146.0.101を指定してみました。

変更したら、applyします。

$ kubectl apply -f deploy.yml

以下のコマンドで、正常にLBが作成されているか確認します。EXTERNAL-IPと書かれてますが、実際はここが内部IPになります。指定した内部IPアドレスが使われていることが確認できます。

$ kubectl get svc ingress-nginx-controller -n ingress-nginx
NAME                       TYPE           CLUSTER-IP    EXTERNAL-IP    PORT(S)                      AGE
ingress-nginx-controller   LoadBalancer   10.0.26.117   10.146.0.101   80:31379/TCP,443:32372/TCP   51s

最後に疎通確認を行います。
内部LBにCLOUD SHELLからはアクセスできないので, GKEのnodeから疎通確認をしてみます。
GCEの画面をGCPコンソールで開いて、NodeインスタンスのどれかのSSHを開きます。

SSH画面が開いた、以下のように内部LBへ疎通ができるか確認します。nginxの応答が帰って来ればOKです。

$ curl 10.146.0.101
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

バックエンドをデプロイ

nginx-ingressを利用するバックエンドをデプロイします
ここからまたCLOUD SHELLで作業をします。以下をapp.ymlというファイル名で作成します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-app
spec:
  selector:
    matchLabels:
      app: hello
  replicas: 3
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
      - name: hello
        image: "gcr.io/google-samples/hello-app:2.0"
---
apiVersion: v1
kind: Service
metadata:
  name: hello-service
  labels:
    app: hello
spec:
  type: NodePort
  selector:
    app: hello
  ports:
  - name: hello-port
    port: 80
    targetPort: 8080
    protocol: TCP
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: hello-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host:
    http:
      paths:
      - path: /
        backend:
          serviceName: hello-service
          servicePort: hello-port

これをこのままデプロイすると下記のようなエラーが出ると思います

$ kubectl apply -f app.yml
deployment.apps/hello-app unchanged
service/hello-service unchanged
Error from server (InternalError): error when creating "app.yml": Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io": Post https://ingress-nginx-controller-admi
ssion.ingress-nginx.svc:443/networking/v1beta1/ingresses?timeout=10s: context deadline exceeded

これの対策としてコントロールプレーンからノードへのTCP8443ポート接続を許可させる必要があります

GCPのドキュメントを見ながら作業します。

以下のコマンドで、GKEのノードが使っているTARGET_TAGSを確認します。

$ gcloud compute firewall-rules list \
           --filter 'name~^gke-private-cluster-2' \
            --format 'table(
                    name,
                    network,
                    direction,
                    sourceRanges.list():label=SRC_RANGES,
                    allowed[].map().firewall_rule().list():label=ALLOW,
                    targetTags.list():label=TARGET_TAGS
            )'
NAME                                       NETWORK  DIRECTION  SRC_RANGES      ALLOW                         TARGET_TAGS
gke-private-cluster-2-4ffa8a93-all         default  INGRESS    10.4.0.0/14     tcp,udp,icmp,esp,ah,sctp      gke-private-cluster-2-4ffa8a93-node
gke-private-cluster-2-4ffa8a93-master      default  INGRESS    172.16.0.32/28  tcp:10250,tcp:443             gke-private-cluster-2-4ffa8a93-node
gke-private-cluster-2-4ffa8a93-vms         default  INGRESS    10.128.0.0/9    icmp,tcp:1-65535,udp:1-65535  gke-private-cluster-2-4ffa8a93-node

gke-private-cluster-2-4ffa8a93-nodeというTARGET_TAGSでした

上記の手順通りでGKEクラスタを作成している場合はコマンド変更不要です。CLUSTER_NAMEをprivate-cluster-2以外にした場合はコマンドの--filter 'name~^gke-private-cluster-2'の部分を--filter 'name~^gke-設定したクラスタ名'に変更してください

これを確認したら以下でファイアウォールルールを追加します

gcloud compute firewall-rules create gke-private-cluster-2-allow-8443 \
     --action ALLOW \
     --direction INGRESS \
     --source-ranges 172.16.0.32/28 \
     --rules tcp:8443 \
     --target-tags gke-private-cluster-2-4ffa8a93-node

--target-tagsに1つ前のコマンドで確認したタグを指定してください

上記の手順通りでGKEクラスタを作成している場合はその他のオプション変更は不要です。マスターIP範囲を172.16.0.32/28以外にした場合は--source-rangesを変更してください

ここまで完了したら、app.ymlをデプロイしてください

$ kubectl apply -f app.yml 
deployment.apps/hello-app created
service/hello-service created
ingress.extensions/hello-ingress created

デプロイが完了したら、また内部LBへ疎通可能なGKEのノードにSSHします。
先ほどと同じコマンドを打って以下のような結果が帰ってこればOKです。

$ curl 10.146.0.101
Hello, world!
Version: 2.0.0
Hostname: hello-app-7f46745f74-8zxfs