September 15, 2022

Ingress Nginxで複数のIngressを作成したときの挙動

こんにちは。

kubernetes/ingress-nginxを使っていて、2つのIngressABを作成したときの挙動がどのようになるのかを調査したのでしたのでまとめます。

結論

2つのIngressABを作成したとき、Ingressコントローラによって単一のNginxサーバーのバックエンドとして動的に読み込まれます。

コードリーディング

1. IngressClassの追加

Ingress-NginxをインストールするときはHelmなどを使用することが多いと思います。 そのときにIngressClassが追加されて、nginxを指定することによってIngress NginxによるL7ロードバランサーを作成することができるようになります。

対象のコードはここです。

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  labels:
    {{- include "ingress-nginx.labels" . | nindent 4 }}
    app.kubernetes.io/component: controller
    {{- with .Values.controller.labels }}
    {{- toYaml . | nindent 4 }}
    {{- end }}
  name: {{ .Values.controller.ingressClassResource.name }}
{{- if .Values.controller.ingressClassResource.default }}
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
{{- end }}
spec:
  controller: {{ .Values.controller.ingressClassResource.controllerValue }}
  {{ template "ingressClass.parameters" . }}
{{- end }}

nginxのIngress Classのコントローラーをspec.controllerで指定しています。これを行うことによって、例えば以下のような

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80

Ingressを作成したときに、コントローラーに実際のロードバランサを依頼するリクエストが送られます。

2. NginxのPodについて

どのNginxに設定をすべきか、どのようにどのようにコントローラーは決めているのでしょうか。

実際のNginxはコントローラーのPOD_NAMEで指定されたPod名でPOD_NAMESPACEで指定されたNamespaceに作成されます (コード)。

func GetIngressPod(kubeClient clientset.Interface) error {
 podName := os.Getenv("POD_NAME")
 podNs := os.Getenv("POD_NAMESPACE")

 if podName == "" || podNs == "" {
  return fmt.Errorf("unable to get POD information (missing POD_NAME or POD_NAMESPACE environment variable")
 }

 pod, err := kubeClient.CoreV1().Pods(podNs).Get(context.TODO(), podName, metav1.GetOptions{})
 if err != nil {
  return fmt.Errorf("unable to get POD information: %v", err)
 }
    ...

このようにして、どのNginxを設定しないといけないのかをコントローラーが判別しています。

3. Ingressを作成したときの挙動

実際にIngressを作成したときにどのような挙動になるのかを説明します。

3.1 Nginxの設定

syncIngressがすべてのIngressを取得して、Nginxの設定を生成します。

ngs := n.store.ListIngresses()
hosts, servers, pcfg := n.getConfiguration(ings)

これによって、たくさんあるIngressが一つのNginxに束ねられます。Ingressが作成または削除されるたびに、Nginxのserverが増えていきます。Nginxの設定ファイルを生成することによってIngressがNginxのバックエンドとして設定されます。

余談ですが何もIngressがないときのNginxは以下の初期設定で実行されています(ソース)。

pid /tmp/nginx.pid;

events {}
http {}
daemon off;
3.1.1 設定のテンプレート

Nginxの設定ファイルはあらかじめ定義していたテンプレートによって生成されます。

それはnginx.tmplで定義されているため、これを書き換えることによって設定の変更を加えることができます。

3.2 Nginxに読み込ませる

LuaによってハンドリングされているNginxの内部的なHTTP endpointに新しいバックエンドの情報を送ってNginxに反映します(コード)。

実際には/configuration/backendsエンドポイントへすべてのバックエンドの情報を送ってセットをしています。 NginxのLuaのハンドラはここに実装されています。

このような仕組みによって、動的に、Nginxのバックエンドを追加できています。

終わりに

Ingress Nginx Controllerの簡単な調査をしました。 設定ファイルを再度読み込むことなく、動的にNginxにサーバーの追加を反映するために、Luaで拡張をしていたことは非常に面白かったです。 Kubernetesは拡張方法が明確な分、コードを読めば簡単に仕組みがわかるので非常に楽だなと改めて感じました。

© KeisukeYamashita 2023