Featured image of post kubeadmで作ったクラスタにNGINX Ingress Controllerを入れる

kubeadmで作ったクラスタにNGINX Ingress Controllerを入れる

IngressControllerを自分で設定したことないやつおる??

やったことのまとめ

自分は普段マネージドKubernetesばかり使っていて、当たり前のようにIngressを使ってアプリをクラスタ外に公開している。
でもよくよく考えるとIngressが使えるのはクラスタの管理者がIngress Controllerを用意してくれているからで、
今までそれ自体を自分で設定したことはなかった。

仕事でこれだけKubernetes触ってるのにIngress Controllerを設定したことがないのは流石に恥ずかしいと思い、
kubeadmで作った自前のクラスタNGINX Ingress Controllerを設定して、サービスがクラスタ外に公開できるところまで試した。

  • kubeadmで作った自前クラスタにingress-nginx(NGINX Ingress Controller)を入れてNodePort Serviceで動かした
  • Ingress Classの設定をした
  • MetalLBを使ってingress-nginxLoadBalancer Serviceで動かした

つかうもの

  • Ubuntu 20.04.3 LTS (GNU/Linux 5.11.0-1020-gcp x86_64)
  • containerd 1.4.11
  • Calico v3.20.2
  • Kubernetes v1.22.2
  • NGINX Ingress controller v1.0.4
  • MetalLB 0.10.3

やったこと

自前クラスタの作成

前回作ったクラスタをそのまま使う。

$ kubectl get nodes -o wide
NAME                STATUS   ROLES                  AGE     VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION    CONTAINER-RUNTIME
kubernetes-master   Ready    control-plane,master   5m56s   v1.22.2   10.240.0.2    <none>        Ubuntu 20.04.3 LTS   5.11.0-1020-gcp   containerd://1.4.11
kubernetes-worker   Ready    <none>                 3m30s   v1.22.2   10.240.0.3    <none>        Ubuntu 20.04.3 LTS   5.11.0-1020-gcp   containerd://1.4.11

$ kubectl get deploy -A
NAMESPACE          NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
calico-apiserver   calico-apiserver          1/1     1            1           5m15s
calico-system      calico-kube-controllers   1/1     1            1           6m16s
calico-system      calico-typha              2/2     2            2           6m17s
kube-system        coredns                   2/2     2            2           7m27s
tigera-operator    tigera-operator           1/1     1            1           6m24s

$ kubectl get ds -A
NAMESPACE       NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
calico-system   calico-node   2         2         2       2            2           kubernetes.io/os=linux   6m36s
kube-system     kube-proxy    2         2         2       2            2           kubernetes.io/os=linux   7m46s

$ kubectl get service -A
NAMESPACE          NAME                              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                  AGE
calico-apiserver   calico-api                        ClusterIP   10.96.0.75      <none>        443/TCP                  6m2s
calico-system      calico-kube-controllers-metrics   ClusterIP   10.97.190.112   <none>        9094/TCP                 6m27s
calico-system      calico-typha                      ClusterIP   10.97.47.173    <none>        5473/TCP                 7m4s
default            kubernetes                        ClusterIP   10.96.0.1       <none>        443/TCP                  8m16s
kube-system        kube-dns                          ClusterIP   10.96.0.10      <none>        53/UDP,53/TCP,9153/TCP   8m14s

$ kubectl get ingress -A
No resources found

ingress-nginxのインストール(NodePort)

Ingress Controllerにはいくつか種類があるが、Kubernetes公式でサポートされている中ではNGINX Ingress Controllerが自前クラスタに使えそう。

公式の手順に従ってインストールしてみる。

# https://kubernetes.github.io/ingress-nginx/deploy/#bare-metal
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.4/deploy/static/provider/baremetal/deploy.yaml

今回の手順だとクラスタ外のロードバランサーを用意していないので、Ingress Controller自体がNodePort Serviceでクラスタ外からのリクエストを受けてIngressのルールに則ったトラフィックの振り分けを行うらしい。

# Ingress ControllerのDeploymentが立っている
$ kubectl -n ingress-nginx get deploy
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
ingress-nginx-controller   1/1     1            1           7m34s
$ kubectl -n ingress-nginx get pods --field-selector=status.phase=Running -o wide
NAME                                        READY   STATUS    RESTARTS   AGE    IP                NODE                NOMINATED NODE   READINESS GATES
ingress-nginx-controller-644555766d-4pwm2   1/1     Running   0          7m8s   192.168.231.195   kubernetes-worker   <none>           <none>
$ kubectl -n ingress-nginx exec -it ingress-nginx-controller-644555766d-4pwm2 -- /nginx-ingress-controller --version
-------------------------------------------------------------------------------
NGINX Ingress controller
  Release:       v1.0.4
  Build:         9b78b6c197b48116243922170875af4aa752ee59
  Repository:    https://github.com/kubernetes/ingress-nginx
  nginx version: nginx/1.19.9

-------------------------------------------------------------------------------

# Ingress Controller自体がNodePort Serviceで公開されている
$ kubectl -n ingress-nginx get service
NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.104.81.185   <none>        80:31862/TCP,443:30703/TCP   8m29s
ingress-nginx-controller-admission   ClusterIP   10.98.56.107    <none>        443/TCP                      8m29s

$ kubectl -n ingress-nginx describe service ingress-nginx-controller
Name:                     ingress-nginx-controller
Namespace:                ingress-nginx
Labels:                   app.kubernetes.io/component=controller
                          app.kubernetes.io/instance=ingress-nginx
                          app.kubernetes.io/managed-by=Helm
                          app.kubernetes.io/name=ingress-nginx
                          app.kubernetes.io/version=1.0.4
                          helm.sh/chart=ingress-nginx-4.0.6
Annotations:              <none>
Selector:                 app.kubernetes.io/component=controller,app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/name=ingress-nginx
Type:                     NodePort
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.104.81.185
IPs:                      10.104.81.185
Port:                     http  80/TCP
TargetPort:               http/TCP
NodePort:                 http  31862/TCP
Endpoints:                192.168.231.195:80
Port:                     https  443/TCP
TargetPort:               https/TCP
NodePort:                 https  30703/TCP
Endpoints:                192.168.231.195:443
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

動作確認(NodePort)

Ingress Controllerが用意できたので、適当なPodIngressを用意して動作を確認する。
手順はKubernetes公式のものを一部改変して試した。

# 動作確認用Deployment, Service, Ingressの作成
kubectl create deployment web --image=gcr.io/google-samples/hello-app:1.0 --port=8080
kubectl expose deployment web --port=8080
kubectl create ingress example-ingress --rule="hello-world.info/*=web:8080"

作成したリソースはこんな感じ。

$ kubectl get pods -l app=web -o wide
NAME                   READY   STATUS    RESTARTS   AGE     IP                NODE                NOMINATED NODE   READINESS GATES
web-7c884bf475-tt9g6   1/1     Running   0          8m21s   192.168.231.196   kubernetes-worker   <none>           <none>

$ kubectl get service web -o wide
NAME   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE    SELECTOR
web    ClusterIP   10.99.167.187   <none>        8080/TCP   6m7s   app=web

# ちょっとあやしい...?
$ kubectl describe ingress example-ingress
Name:             example-ingress
Namespace:        default
Address:
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
  Host              Path  Backends
  ----              ----  --------
  hello-world.info
                    /   web:8080 (192.168.231.196:8080)
Annotations:        <none>
Events:             <none>

試しにmaster nodeからクラスタを介さずIngress経由で疎通できるか試してみる。
インストール後に確認したようにIngress Controller自体がNodePort Serviceで公開されているので、そこにリクエストを飛ばす。

# <nodeのIP>:<NodePort>にリクエストしたけどダメ...
$ curl -H "Host: hello-world.info" 10.240.0.3:31862/
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

は?失敗したんだが😭

どうやら作ったIngressIngress Classの指定がないのでIngress Controllerに認識されてないっぽい。
(1クラスタに複数のIngress Controllerが存在することがあるので、Ingress Classが指定されていないといろいろ問題になる)

対応方法としては

  • Ingress作成時にspec.ingressClassNameフィールドを指定する
  • Ingress Classにアノテーションをつけてデフォルト設定にする のどちらかだと思う。

今回はデフォルトIngress Classを設定する方法を試してみる。

# Ingress Classにアノテーションをつけてデフォルト設定にする
kubectl annotate ingressclass nginx ingressclass.kubernetes.io/is-default-class=true

# ingressを作り直す
kubectl delete ingress example-ingress
kubectl create ingress example-ingress --rule="hello-world.info/*=web:8080"

# (optional)IngressClassのデフォルト設定をしない場合は--classでIngressClassを指定する
kubectl create ingress example-ingress --class=nginx --rule="hello-world.info/*=web:8080"

Ingress Classの設定をした上で再度挑戦してみる。

# アノテーションがついている
$ kubectl describe ingressclass nginx
Name:         nginx
Labels:       app.kubernetes.io/component=controller
              app.kubernetes.io/instance=ingress-nginx
              app.kubernetes.io/managed-by=Helm
              app.kubernetes.io/name=ingress-nginx
              app.kubernetes.io/version=1.0.4
              helm.sh/chart=ingress-nginx-4.0.6
Annotations:  ingressclass.kubernetes.io/is-default-class: true
Controller:   k8s.io/ingress-nginx
Events:       <none>

# さっきと違ってIngress Controllerに認識されてそう
$ kubectl describe ingress example-ingress
Name:             example-ingress
Namespace:        default
Address:          10.240.0.3
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
  Host              Path  Backends
  ----              ----  --------
  hello-world.info
                    /   web:8080 (192.168.231.196:8080)
Annotations:        <none>
Events:
  Type    Reason  Age                   From                      Message
  ----    ------  ----                  ----                      -------
  Normal  Sync    2m8s (x2 over 2m12s)  nginx-ingress-controller  Scheduled for sync

# 今度は疎通できる
$ curl -H "Host: hello-world.info" 10.240.0.3:31862/
Hello, world!
Version: 1.0.0
Hostname: web-7c884bf475-tt9g6

今度はちゃんとIngress経由で通信できた。🎉
やったぜ。

MetalLBのインストール

とはいえやっぱりクラスタ外からアクセスするときにデカいポートをいちいち指定するのはしんどい。
次はNodePort ServiceではなくLoadBalancer Serviceを使って普通のポートでトラフィックを受けられるようにしてみる。

NGINX Ingress Controllerのドキュメントによると、クラウドプロバイダで管理されていないベアメタルクラスタでもMetalLBを導入すればLoadBalancer Serviceが使えるようになるらしい。

# NodePort版Ingress Controllerの削除
kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.4/deploy/static/provider/baremetal/deploy.yaml

# MetalLBのインストール
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.3/manifests/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.3/manifests/metallb.yaml

# L2 modeの設定
# https://metallb.universe.tf/configuration/#layer-2-configuration
# nodeのネットワークで使われていないIPを使用する(今回は10.240.0.0/24の中でnodeに割り当てられていないIP)
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 10.240.0.10-10.240.0.15
EOF

MetalLBのインストールと設定が正常にできていればこんな感じになる。

$ k -n metallb-system get all
NAME                             READY   STATUS    RESTARTS   AGE
pod/controller-77c44876d-wgqzj   1/1     Running   0          5m43s
pod/speaker-fdcrw                1/1     Running   0          5m43s
pod/speaker-kgz2w                1/1     Running   0          5m43s

NAME                     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
daemonset.apps/speaker   2         2         2       2            2           kubernetes.io/os=linux   5m43s

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/controller   1/1     1            1           5m43s

NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/controller-77c44876d   1         1         1       5m43s

ingress-nginxのインストール(with MetalLB)

あとはLoadBalancer Serviceを使う設定のingress-nginxをインストールする。

# LoadBalancerになるはず
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.4/deploy/static/provider/cloud/deploy.yaml

外部からリクエストを受けてトラフィックを振り分けるためのIngress Controllerが今度はNodePort ServiceでなくちゃんとLoadBalancer Serviceで公開されていて、しかもExternalIPMetalLBで指定したIPが設定されている。

$ k -n ingress-nginx get all
NAME                                            READY   STATUS      RESTARTS   AGE
pod/ingress-nginx-admission-create--1-fbqsf     0/1     Completed   0          28s
pod/ingress-nginx-admission-patch--1-l2pz5      0/1     Completed   1          28s
pod/ingress-nginx-controller-5c8d66c76d-wfbjf   1/1     Running     0          29s

NAME                                         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
service/ingress-nginx-controller             LoadBalancer   10.111.44.72    10.240.0.10   80:31646/TCP,443:32459/TCP   29s
service/ingress-nginx-controller-admission   ClusterIP      10.99.244.117   <none>        443/TCP                      29s

NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ingress-nginx-controller   1/1     1            1           29s

NAME                                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/ingress-nginx-controller-5c8d66c76d   1         1         1       29s

NAME                                       COMPLETIONS   DURATION   AGE
job.batch/ingress-nginx-admission-create   1/1           4s         29s
job.batch/ingress-nginx-admission-patch    1/1           4s         28s

動作確認(with MetalLB)

最後に、同じネットワークのnodeからMetalLBで払い出されたLoadBalancer ServiceのIP越しにIngress経由のリクエストを送ってみる。

# 払い出したExretnalIPで接続できた!
$ curl -H "Host: hello-world.info" 10.240.0.10/
Hello, world!
Version: 1.0.0
Hostname: web-7c884bf475-tt9g6

できた〜〜🎉

10.240.0.10にサーバが立ってないのにリクエストができるのはMetalLBspeakerが同じネットワークに送られたARPリクエストに対して応答するおかげらしい(まだちゃんと理解してない)。

よくわかってないけどとりあえず普通のポートでIngressが動いたのでヨシ!

おわり

自前のクラスタにIngress Controllerを突っ込む練習をしてみた。
正直なところドキュメント通りにyamlをapplyするだけで簡単に設定できてしまったので本当に身についたかは怪しいが、
Ingress ClassについてはIngress Controllerを意識しないと触れる機会が少ないし、実際あまりわかってなかったので勉強になってよかった。
MetalLBについてはまたどこかでお世話になりそうなのでそのときにちゃんと勉強したい。

おまけ

ねこか?