kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

Kubernetesのnginx-ingressを理解する

kubernetesのnginx-ingress、いろいろ調べたり手を動かしてみても、どうしても理解ができなくてずっと悩んでましたが、少し理解が進んだ気がしたので、自分用まとめ。

前提

  • type:loadbalancerは使わない。metal-lbも使わない。諸般の事情により公開はnodeportのみ。
  • クラスタVagrantでmaster x 1, worker x 2とします。以下のレポジトリで公開しています。

github.com

シンプルなnodeport serviceの場合

kubernetes bootcampのイメージをサンプルに、Deploymentを作ります。podは8080で待ち受けます。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: bootcamp
  name: bootcamp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: bootcamp
  template:
    metadata:
      labels:
        app: bootcamp
    spec:
      containers:
      - image: gcr.io/google-samples/kubernetes-bootcamp:v1
        name: bootcamp
        ports:
        - containerPort: 8080
      dnsPolicy: ClusterFirst
      restartPolicy: Always

Serviceはこんな感じです。nodeport:31001 -> port:80 -> targetport:8080 という感じでDeploymentのpodまで到達します。

apiVersion: v1
kind: Service
metadata:
  name: bootcamp
  labels:
    app: bootcamp
spec:
  type: NodePort
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
    nodePort: 31001
  selector:
    app: bootcamp

適用します。

$ kubectl apply -f bootcamp-deployment.yaml
deployment.apps/bootcamp created
$ kubectl apply -f bootcamp-service.yaml
service/bootcamp created

確認します

$ kubectl get deployment
NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
bootcamp                                 2/2     2            2           39s
$ kubectl get pod -o wide
NAME                                                      READY   STATUS    RESTARTS   AGE     IP                NODE       NOMINATED NODE   READINESS GATES
bootcamp-7465f56-4zt4d                                    1/1     Running   0          4m25s   192.168.226.81    worker-1   <none>           <none>
bootcamp-7465f56-gphwd                                    1/1     Running   0          4m26s   192.168.133.207   worker-2   <none>           <none>
$ kubectl get svc
NAME                                               TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
bootcamp                                           NodePort    10.101.29.7      <none>        80:31001/TCP                 2m13s

実際にアクセスしてみましょう。pod ip/cluster ip/nodeportでどれもアクセスができているのがわかりますね。

$ curl 192.168.226.81:8080
Hello Kubernetes bootcamp! | Running on: bootcamp-7465f56-4zt4d | v=1
$ curl 192.168.133.207:8080
Hello Kubernetes bootcamp! | Running on: bootcamp-7465f56-gphwd | v=1
$ curl 10.101.29.7:80
Hello Kubernetes bootcamp! | Running on: bootcamp-7465f56-gphwd | v=1
$ curl 10.101.29.7:80
Hello Kubernetes bootcamp! | Running on: bootcamp-7465f56-4zt4d | v=1
$ curl 10.240.0.22:31001
Hello Kubernetes bootcamp! | Running on: bootcamp-7465f56-gphwd | v=1
$ curl 10.240.0.21:31001
Hello Kubernetes bootcamp! | Running on: bootcamp-7465f56-4zt4d | v=1

図だとこんな感じかなと思います。

f:id:kun432:20200519030748p:plain

nginx-ingress の場合

ではnginx-ingressを使ってbootcampを公開してみましょう。まず、helmで nginx-ingress をインストールします。

$ helm install nginx-ingress stable/nginx-ingress

まずこの状態で見てみます。deploymentから。

$ kubectl get deployments
NAME                                    READY   UP-TO-DATE   AVAILABLE   AGE
nginx-ingress-controller                1/1     1            1           93s
nginx-ingress-default-backend           1/1     1            1           93s
bootcamp                                2/2     2            2           60m

nginx-ingress-controllerのpodが80番と443番ポートで待ち受けています。

$ kubectl get deployment nginx-ingress-controller -o yaml
apiVersion: apps/v1
kind: Deployment
・・・
metadata:
・・・
  name: nginx-ingress-controller
  namespace: default
・・・
        name: nginx-ingress-controller
        ports:
        - containerPort: 80
          name: http
          protocol: TCP
        - containerPort: 443
          name: https
          protocol: TCP
・・・

nginx-ingress-default-backend のpodが8080で待ち受けています。

$ kubectl get deployment nginx-ingress-default-backend -o yaml
apiVersion: apps/v1
kind: Deployment
・・・
  name: nginx-ingress-default-backend
  namespace: default
・・・
        ports:
        - containerPort: 8080
          name: http
          protocol: TCP
・・・

podに直接アクセスしてみます。

$ kubectl get pod -o wide
NAME                                                     READY   STATUS    RESTARTS   AGE    IP                NODE       NOMINATED NODE   READINESS GATES
bootcamp-7465f56-4zt4d                                   1/1     Running   0          73m    192.168.226.81    worker-1   <none>           <none>
bootcamp-7465f56-gphwd                                   1/1     Running   0          73m    192.168.133.207   worker-2   <none>           <none>
nginx-ingress-controller-857967b4f-2sz64                 1/1     Running   0          14m    192.168.226.82    worker-1   <none>           <none>
nginx-ingress-default-backend-7c868597f4-fftss           1/1     Running   0          14m    192.168.133.208   worker-2   <none>           <none>

全部404ではありますけど、何かしら応答はしてますね。ちょっと置いときましょう。

$ curl http://192.168.226.82/
default backend - 404
$ curl --insecure https://192.168.226.82/
default backend - 404
$ curl 1192.168.133.208:8080
default backend - 404

serviceを見てみます。

$ kubectl get svc
NAME                                    TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
nginx-ingress-controller                LoadBalancer   10.103.246.235   <pending>     80:32012/TCP,443:32050/TCP   19m
nginx-ingress-default-backend           ClusterIP      10.99.30.83      <none>        80/TCP                       19m

もう少し詳しく。まずはnginx-ingress-controllerから。

$ kubectl get svc nginx-ingress-controller -o yaml
apiVersion: v1
kind: Service
・・・
  name: nginx-ingress-controller
  namespace: default
・・・
spec:
  clusterIP: 10.103.246.235
  externalTrafficPolicy: Cluster
  ports:
  - name: http
    nodePort: 32012
    port: 80
    protocol: TCP
    targetPort: http
  - name: https
    nodePort: 32050
    port: 443
    protocol: TCP
    targetPort: https
・・・
  type: LoadBalancer

冒頭でお伝えしたとおり、type: LoadBalancerは使えない(だからEXTERNAL-IPはpending)なのですが、そこはちょっと置いといて、ポートのところを見ると、

  • nodeport:32012 -> port:80 -> targetport:80
  • nodeport:32005 -> port:443 -> targetport:443

になっています。つまり、nginx-ingress-controllerのpodに向かって転送しているわけですね。

ということはすなわち、cluster ipでもnodeportでもアクセスができるはずです。

$ curl http://10.103.246.235:80
default backend - 404
$ curl --insecure https://10.103.246.235:443
default backend - 404
$ curl http://10.240.0.21:32012
default backend - 404
$ curl http://10.240.0.22:32012
default backend - 404
$ curl --insecure https://10.240.0.21:32050
default backend - 404
$ curl --insecure https://10.240.0.22:32050
default backend - 404

できましたね。nginx-ingress-default-backendも詳しく見てみましょう。

$ kubectl get svc nginx-ingress-default-backend -o yaml
apiVersion: v1
kind: Service
・・・
  name: nginx-ingress-default-backend
  namespace: default
spec:
  clusterIP: 10.99.30.83
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: http
・・・
  type: ClusterIP

こちらはcluster ipなので外部からのアクセスはできません。cluster ipに対してアクセスすると応答します。

$ curl 10.99.30.83:80
default backend - 404

これがnginx-ingressをインストールした直後です。nginx-ingressを使うと関係者が多くなるのでちょっとわかりにくくなるのですが、これだけ見てるとtype: nodeportで公開しているのとそんなに変わらないですよね。

で、今回type: loadbalancerは使えないので、nginx-ingress-controllerのserviceはtype: nodeportに変更します。また80/443それぞれのnodeportも固定します。

$ kubectl edit service nginx-ingress-controller

変更点だけ。

・・・
  - name: http
    nodePort: 32001
・・・
  - name: https
    nodePort: 32002
・・・
  type: NodePort

確認します。

$ kubectl get service
NAME                                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
nginx-ingress-controller                NodePort    10.103.246.235   <none>        80:32001/TCP,443:32002/TCP   49m
nginx-ingress-default-backend           ClusterIP   10.99.30.83      <none>        80/TCP                       49m

変わりました。

ではingressを追加します。こんな感じにしてみました。時間の都合上httpだけ、default-backendは後で調べます。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
  name: bootcamp-nginx-ingress
spec:
  rules:
    - host: example.com
      http:
        paths:
          - backend:
              serviceName: bootcamp
              servicePort: 80
            path: /

serviceNameのところはserviceのmetadata.nameと合わせる必要がありますので注意してください。

ではアクセスしてみましょう。架空のホスト名なのでhostヘッダをつけています。

$ curl -H 'host: example.com' http://10.240.0.21:32001
Hello Kubernetes bootcamp! | Running on: bootcamp-7465f56-gphwd | v=1
$ curl -H 'host: example.com' http://10.240.0.22:32001
Hello Kubernetes bootcamp! | Running on: bootcamp-7465f56-4zt4d | v=1

うまくいきました!アクセスログも見てみましょう。

$ kubectl logs nginx-ingress-controller-857967b4f-2sz64
10.0.2.15 - - [18/May/2020:17:40:08 +0000] "GET / HTTP/1.1" 200 81 "-" "curl/7.58.0" 75 0.008 [default-bootcamp-80] [] 192.168.226.81:8080 92 0.004 200 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
192.168.133.192 - - [18/May/2020:17:40:12 +0000] "GET / HTTP/1.1" 200 92 "-" "curl/7.58.0" 75 0.003 [default-bootcamp-80] [] 192.168.133.207:8080 92 0.004 200 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

アクセス元IPアドレスのところがおかしいのはvagrantだからですね。ちゃんとアクセスが来てるので良しとします。図にまとめるとこんな感じになるのかな。

f:id:kun432:20200519030802p:plain

serviceがmanfiestだけで動くのに対し、nginx-ingressはservice/pod/ingressといろいろなものが強調して動くことになるので、非常にわかりにくいですね。しかもtype: LoadBalancerならもちょっとシンプルなはずなんですが、そうじゃないので余計にわかりにくい・・・

とりあえずなんとなくは理解できたんじゃないかと。