Carpe Diem

備忘録

Kubernetes の Ingress を理解する

概要

KubernetesにはL4ロードバランサのServiceとL7のIngressがあります。
IngressはControllerによって挙動が大きく変わるので実際に手を動かして学んでみます。

環境

Ingress Controller

Ingress ControllerはIngressリソースを動かすためのものです。

例えば以下のようなIngress Controllerがあります。

Ingress Controllers | Kubernetes

Ingressは大別すると2種類

Ingressは大別すると

の2種類に分けられます。

例えばGKEのデフォルトは前者で、Nginx Ingressは後者です。

後者の場合は作成したIngressPod自体に、クラスタ外からアクセスするためのLoadBalancer Serviceが必要です。

Nginx Ingress Controller

数あるIngress Controllerの中で、今回はよく使われるNginx Ingress Controllerを使って検証します。

Welcome - NGINX Ingress Controller

アーキテクチャ

f:id:quoll00:20190808214301p:plain

ref: Ingress with NGINX controller on Google Kubernetes Engine

このようにリクエストが登録したIngress Resource(マニフェスト)のどれに当てはまるかをIngress Controllerが判断し、後ろのServiceへとルーティングします。

この図からも分かりますが、Nginx Ingress Controllerを使った場合Ingressの実体もNginx Ingress Controllerであり、トラフィッククラスタ内のServiceへ流します。

Nginx Ingressを使う上で必要なもの

Nginx Ingressを利用する上でそもそも以下の4つが必要になります。

  • Nginx Ingress ControllerのPodを作成するためのDeployment
  • クラスタ外から↑のPodにアクセスするためのService(Type: LoadBalancer)
  • default-http-backendのDeployment
  • default-http-backendのService(Type: ClusterIP)

f:id:quoll00:20191211110631p:plain

default-http-backendはこの図のように、Ingress Resourceのルーティングと一致しない場合に流すためのものです。

これはhelmを使うことで全て作成可能です。

$ helm install --name nginx-ingress stable/nginx-ingress

Ingress ControllerのPodとdefault backendのPodができ、

$ kubectl get po
NAME                                             READY   STATUS    RESTARTS   AGE
nginx-ingress-controller-7b8649fd49-m8xht        1/1     Running   0          42s
nginx-ingress-default-backend-6c7657c899-bqpq7   1/1     Running   0          42s

Ingress ControllerのServiceとdefault backendのServiceができます。

$ kubectl get svc
NAME                            TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
kubernetes                      ClusterIP      10.96.0.1       <none>        443/TCP                      22h
nginx-ingress-controller        LoadBalancer   10.103.253.24   <pending>     80:32727/TCP,443:30863/TCP   88s
nginx-ingress-default-backend   ClusterIP      10.111.46.223   <none>        80/TCP                       88s

minikubeではexternal-ipは用意できないのでIPをチェックしておきます。

$ minikube ip
192.168.99.102

検証環境の用意

Nginx Ingress Controllerを用意できたので、

  • 検証用Deployment
  • ↑用のService
  • Ingressリソース

の3つを用意します。

検証用Deployment

マニフェスト

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world-deployment
spec:
  selector:
    matchLabels:
      app: hello-world
  replicas: 3
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
        - image: "strm/helloworld-http"
          imagePullPolicy: Always
          name: hello-world-container
          ports:
            - containerPort: 80

作成

$ kubectl get po
NAME                                             READY   STATUS    RESTARTS   AGE
hello-world-deployment-5c8cd966cd-7zffj          1/1     Running   0          35s
hello-world-deployment-5c8cd966cd-gf4jj          1/1     Running   0          35s
hello-world-deployment-5c8cd966cd-skhp5          1/1     Running   0          35s

↑用のService

マニフェスト

apiVersion: v1
kind: Service
metadata:
  name: hello-world-svc
spec:
  type: NodePort
  ports:
   - port: 8080
     protocol: TCP
     targetPort: 80
  selector:
    app: hello-world

Nginx Ingress Controllerの場合はtype: NodePortは不要ですが、別のIngress(例えばGKEのようにクラスタ外のロードバランサを利用するもの)はNodePortを経由するので必要です。
今回はできるだけ共通になるよう付けておいてください。

作成

$ kubectl get svc
NAME                            TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
hello-world-svc                 ClusterIP      10.98.63.214    <none>        8080/TCP                     2m17s

Ingressリソース

マニフェスト

Virtual Hostをhello-world.infoにしておきます。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: nginx-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: hello-world.info
    http:
      paths:
      - backend:
          serviceName: hello-world-svc
          servicePort: 8080
        path: /

rulesによってnginxのconfigurationが設定されます。

例えばhost: hello-world.info

server {
    server_name hello-world.info ;
    ...
}

に設定されますし、path: /

location / {
    ...
}

に変換されます。

$ kubectl exec -it nginx-ingress-controller-xxx-yyy -- cat nginx.conf

とすると変換された設定ファイルを全て確認できます。

作成

$ kubectl get ingress
NAME            HOSTS              ADDRESS   PORTS   AGE
nginx-ingress   hello-world.info             80      3m

作成するとnginx ingress controllerのpodでは以下のログが流れます。

nginx-ingress-controller-7b8649fd49-m8xht nginx-ingress-controller I0808 13:32:05.194069       6 event.go:209]
Event(v1.ObjectReference{Kind:"Ingress", Namespace:"default", Name:"nginx-ingress", UID:"f91efd54-7c8d-4332-87fe-5c522392393b", APIVersion:"extensions/v1beta1", ResourceVersion:"53784", FieldPath:""}): type: 'Normal' reason: 'CREATE' Ingress default/nginx-ingress

検証

Virtual Hostを設定しているので、minikube ipでのIPを/etc/hostsに設定しておきます。

192.168.99.102 hello-world.info

この状態でcurlを叩くと、検証用Podへリクエストが流れることが確認できます。

$ curl http://hello-world.info:32727
<html><head><title>HTTP Hello World</title></head><body><h1>Hello from hello-world-deployment-5c8cd966cd-7zffj</h1></body></html
$ curl http://hello-world.info:32727
<html><head><title>HTTP Hello World</title></head><body><h1>Hello from hello-world-deployment-5c8cd966cd-skhp5</h1></body></html

このようにバランシングされてます。

Nginx Ingress Controllerには以下のログが流れます。

nginx-ingress-controller-7b8649fd49-m8xht nginx-ingress-controller 172.17.0.1 - [172.17.0.1] - - [08/Aug/2019:13:52:27 +0000] "GET / HTTP/1.1" 200 129 "-" "curl/7.54.0" 86 0.002 [default-hello-world-svc-8080] 172.17.0.7:80 129 0.001 200 7fbf1eba2e9642c34ac19504a06c5884
nginx-ingress-controller-7b8649fd49-m8xht nginx-ingress-controller 172.17.0.1 - [172.17.0.1] - - [08/Aug/2019:13:56:00 +0000] "GET / HTTP/1.1" 200 129 "-" "curl/7.54.0" 86 0.001 [default-hello-world-svc-8080] 172.17.0.8:80 129 0.002 200 d7ead53538f33f2fc32524caf68ccd92

その他

検証中に気になったことまとめ

Nginx Ingress冗長化するには?

先に述べたように、Nginx Ingress Controllerの場合Ingressの実体もNginx Ingress Controllerです。
なのでIngress冗長化をする場合はNginx Ingress ControllerのReplica数自体を上げます。

なぜminikubeではexternal-ipを用意できないの?

Serviceのtype: LoadBalancerKubernetesクラスタが構築されているインフラがこの仕組みに対応している必要があります。
基本的にGCPAWS・Azureといったクラウドプロバイダが対応していますが、ローカル開発では利用できないです。

Docker for Macではlocalhostが、minikubeではminikube ipでのNodePortのようなServiceになります。

GKEで作るとIngressのAddressとNginx Ingress ControllerのIPが異なる

GKEでも作れるか検証したところ、期待通りLoadBalancerにexternal-ipが振られて

$ kubectl get svc
NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)                      AGE
hello-world-svc                      NodePort       10.170.12.105   <none>         8080:31223/TCP               5m33s
kubernetes                           ClusterIP      10.170.0.1      <none>         443/TCP                      50m
nginx-ingress-controller        LoadBalancer   10.170.7.157    34.85.122.81   80:30714/TCP,443:31264/TCP   9m25s
nginx-ingress-default-backend   ClusterIP      10.170.11.220   <none>         80/TCP                       9m25s

そこからアクセスできることは確認できました。

$ curl http://34.85.122.81/ -H "Host: hello-world.info"
<html><head><title>HTTP Hello World</title></head><body><h1>Hello from hello-world-deployment-57584b8758-4kbd6</h1></body></html

しかしなぜかIngressに載っているAddressは別でした。

$ kubectl get ing
NAME            HOSTS              ADDRESS          PORTS   AGE
nginx-ingress   hello-world.info   35.194.123.227   80      64s

また、疎通もできませんでした。

$ curl http://35.194.123.227/ -H "Host: hello-world.info"
curl: (52) Empty reply from server

Helmインストール時に追加設定が必要

github.com

このissueに書いてあるように--set controller.publishService.enabled=trueが必要でした。

$ helm install --name nginx-ingress-test \
  --set controller.publishService.enabled=true stable/nginx-ingress

これで作成してみると両方一致し、疎通もできました。

$ kubectl get ing
NAME            HOSTS              ADDRESS        PORTS   AGE
nginx-ingress   hello-world.info   34.85.122.81   80      16s
$
$ kubectl get svc
NAME                                 TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)                      AGE
hello-world-svc                      NodePort       10.170.2.250   <none>         8080:31103/TCP               23s
kubernetes                           ClusterIP      10.170.0.1     <none>         443/TCP                      60m
nginx-ingress-controller        LoadBalancer   10.170.8.120   34.85.122.81   80:30095/TCP,443:32259/TCP   105s
nginx-ingress-default-backend   ClusterIP      10.170.1.34    <none>         80/TCP                       105s

ホスト名のDNS登録どうするの?

今回は簡単のため手動で/etc/hostsに設定しましたが、例えばexternal-dnsを使うとannotationをつけるだけでRoute53やCloudDNSで登録して公開してくれます。

サンプルコード

今回のサンプルコードはこちら↓

github.com

ソース