ストイックに生きたい
Ingress Nginxは何をしてるのか
NGINX Ingress Controller は何を起動して実装はどうなっているかを読んだので書く
このエントリーをはてなブックマークに追加

前置き

ここで書いている内容は全ての事は書いてないし気になったところだけを順番に見ている。
公開後は随時追記や修正をしていくつもりである。

間違っている箇所があったりしたらTwitter(@let_constant)へDMとかで教えてください。

日付内容
2020-07-19ingress-nginx-2.7.0 の内容で公開

NGINX Ingress Controllerとは

NginxをベースイメージとしてリバースプロキシやLBとして動作するIngress実装である。
L7で動作するのでホスト名やパスでのルーティング、SSLのオフロード等が実現できる。

他のIngress実装はTraefikContourがあり、ここにまとめられている。

お気持ち

ServiceはClusterIPで公開する限りServiceにIPが振られるのでIngressはそれを見ていると思っていた。
ではそうなるとSticky Sessionsはどうやって実現しているのか不思議になった。
How it worksを読むと普通に書いてあるが、Endpointsを使用している。

これを知った時感動した。

Nginx IngressがServiceじゃなくてEndpoints見てるって知った時感動したよね

— さかもとりょーた(25) (@let_constant) May 27, 2020

ところで Nginx Ingress Controller のことを nginx-ingress なのか ingress-nginx って言えばいいのかいつもわからなくなる。正解を知りたい。

全体像

まずは大まかな全体像の図を貼る。
NodePort で公開されていたり、Job により Secret が生成されていたりする。

all.png

実装の話

バージョンは2020-06-21(日)時点で最新のingress-nginx-2.7.0
マニフェストはbaremetal/deploy.yaml

マニフェストを見るとcontrollerのDeploymentやJobが動いている事がわかる。
Podとして動いているものを順番に見ていく事にする。

ingress-nginx-controller {#ingress-nginx-controller}

イメージとしては下記のような順番で生成されている。

  1. alpine:3.11
  2. quay.io/kubernetes-ingress-controller/nginx:e3c49c52f4b74fe47ad65d6f3266a02e8b6b622f
  3. quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.33.0

ビルド

ビルドは build/build.sh で行なっていて3つのバイナリを作っている。

go build \
  -ldflags "-s -w \
    -X ${PKG}/version.RELEASE=${TAG} \
    -X ${PKG}/version.COMMIT=${GIT_COMMIT} \
    -X ${PKG}/version.REPO=${REPO_INFO}" \
  -o "rootfs/bin/${ARCH}/nginx-ingress-controller" "${PKG}/cmd/nginx"

go build \
  -ldflags "-s -w \
    -X ${PKG}/version.RELEASE=${TAG} \
    -X ${PKG}/version.COMMIT=${GIT_COMMIT} \
    -X ${PKG}/version.REPO=${REPO_INFO}" \
  -o "rootfs/bin/${ARCH}/dbg" "${PKG}/cmd/dbg"

go build \
  -ldflags "-s -w \
    -X ${PKG}/version.RELEASE=${TAG} \
    -X ${PKG}/version.COMMIT=${GIT_COMMIT} \
    -X ${PKG}/version.REPO=${REPO_INFO}" \
  -o "rootfs/bin/${ARCH}/wait-shutdown" "${PKG}/cmd/waitshutdown"

controller本体 {#controller}

マニフェスト

提供されているマニフェストでは下記のような引数で起動している。
全ての引数の詳細はCommand line argumentsに書いてある。

args:
  - /nginx-ingress-controller
  - --election-id=ingress-controller-leader
  - --ingress-class=nginx
  - --configmap=ingress-nginx/ingress-nginx-controller
  - --validating-webhook=:8443
  - --validating-webhook-certificate=/usr/local/certificates/cert
  - --validating-webhook-key=/usr/local/certificates/key

main.go

フラグのパースやサーバーの起動を実装している。

$ kubectl -n ingress-nginx exec -it ingress-nginx-controller-7fd7d8df56-dv7s6 -- bash -c "curl -s localhost:10254/metrics | grep go_gc"
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 3.58e-05
go_gc_duration_seconds{quantile="0.25"} 9.96e-05
go_gc_duration_seconds{quantile="0.5"} 0.0002579
go_gc_duration_seconds{quantile="0.75"} 0.0006037
go_gc_duration_seconds{quantile="1"} 0.009535
go_gc_duration_seconds_sum 0.0567369
go_gc_duration_seconds_count 75

controller.NewNGINXController にはcontrollerの実装が書かれているので次はここの実装を見る
SIGTERM を投げることで停止されるようになっている。

ngx := controller.NewNGINXController(conf, mc)

mux := http.NewServeMux()
registerHealthz(nginx.HealthPath, ngx, mux)
registerMetrics(reg, mux)

go startHTTPServer(conf.ListenPorts.Health, mux)
go ngx.Start()

handleSigterm(ngx, func(code int) {
	os.Exit(code)
})

internal/ingress/controller/nginx.go

func NewNGINXController(*Configuration, metric.Collector) *NGINXController {#NewNGINXController}

eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(klog.Infof)
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{
	Interface: config.Client.CoreV1().Events(config.Namespace),
})
n.store = store.New(
	config.Namespace,
	config.ConfigMapName,
	config.TCPConfigMapName,
	config.UDPConfigMapName,
	config.DefaultSSLCertificate,
	config.ResyncPeriod,
	config.Client,
	n.updateCh,
	pod,
	config.DisableCatchAll)
n.syncQueue = task.NewTaskQueue(n.syncIngress)

func (*NGINXController) Start() {#controller-start}

func (*NGINXController) Stop() error

func (*NGINXController) syncIngress(interface{}) error {#syncIngress}

キューにタスクが追加されるたびにこの関数が実行される。
実行はここでしている。

endp, err := n.serviceEndpoints(svcKey, path.Backend.ServicePort.String())

Admission Controller {#admission-controller}

Admission Controller のHadnlerの実装はここでしている。

if n.cfg.ValidationWebhook != "" {
		n.validationWebhookServer = &http.Server{
			Addr:      config.ValidationWebhook,
			Handler:   adm_controler.NewAdmissionControllerServer(&adm_controler.IngressAdmission{Checker: n}),
			TLSConfig: ssl.NewTLSListener(n.cfg.ValidationWebhookCertPath, n.cfg.ValidationWebhookKeyPath).TLSConfig(),
		}
	}

NGINXControllerCheckIngress実装していて、
Admission Controller がリクエストを受け取ると (*IngressAdmission) HandleAdmission(*v1beta1.AdmissionReview) が叩かれてその中で CheckIngress が叩かれる。

(*NGINXController) CheckIngress(*networking.Ingress) error

下記のような事を行なっている。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: application-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  backend:
    serviceName: application-service
    servicePort: 8080

Leader Election {#leader-election}

Leaderとなった PodIngress のステータスを毎秒同期している。

client-go実装されていて、 ConfigMap を使いロックをしている。
Leaderが落ちたりして renewTime の更新ができなかった場合は他の PodrenewTimeholderIdentity を更新しLeaderに昇格する。

$ kubectl -n ingress-nginx get cm/ingress-controller-leader-nginx -o yaml
apiVersion: v1
kind: ConfigMap
metadata:
  annotations:
    control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"ingress-nginx-controller-75f84dfcd7-f22xq","leaseDurationSeconds":30,"acquireTime":"2020-06-22T14:38:14Z","renewTime":"2020-07-15T18:22:19Z","leaderTransitions":0}'
  creationTimestamp: "2020-06-22T14:38:14Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .: {}
          f:control-plane.alpha.kubernetes.io/leader: {}
    manager: nginx-ingress-controller
    operation: Update
    time: "2020-07-15T18:22:19Z"
  name: ingress-controller-leader-nginx
  namespace: ingress-nginx
  resourceVersion: "295822"
  selfLink: /api/v1/namespaces/ingress-nginx/configmaps/ingress-controller-leader-nginx
  uid: 00f21dfe-d5d9-4dc0-becf-e7bbd45be4ff

wait-shutdown {#wait-shutdown}

シンプルな実装だった。

err := exec.Command("bash", "-c", "pkill -SIGTERM -f nginx-ingress-controller").Run()
if err != nil {
	klog.Errorf("unexpected error terminating ingress controller: %v", err)
	os.Exit(1)
}

// wait for the NGINX process to terminate
timer := time.NewTicker(time.Second * 1)
for range timer.C {
	if !nginx.IsRunning() {
		timer.Stop()
		break
    }
}

dbg {#dbg}

デバッグ用のCLIである。

$ /dbg --help
dbg is a tool for quickly inspecting the state of the nginx instance

Usage:
  dbg [command]

Available Commands:
  backends    Inspect the dynamically-loaded backends information
  certs       Inspect dynamic SSL certificates
  conf        Dump the contents of /etc/nginx/nginx.conf
  general     Output the general dynamic lua state
  help        Help about any command

Flags:
  -h, --help   help for dbg

Use "dbg [command] --help" for more information about a command.

ingress-nginx-admission-create {#ingress-nginx-admission-create}

kube-webhook-certgenに実装がある。
これは何をしているかというと期限の長い自己証明書を生成してシークレットに設定してくれるやつだ。

指定されているnamespaceのSecretが存在しなかった場合のみ生成されるようになっている。
ここではAdmission Controllerの証明書を生成しようとしているのがわかる。

- name: create
  image: jettech/kube-webhook-certgen:v1.2.0
  imagePullPolicy: IfNotPresent
  args:
    - create
    - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.ingress-nginx.svc
    - --namespace=ingress-nginx
    - --secret-name=ingress-nginx-admission

100年後で生成されているのが確認できる。

$ kubectl -n ingress-nginx describe secret/ingress-nginx-admission
Name:         ingress-nginx-admission
Namespace:    ingress-nginx
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
cert:  623 bytes
key:   227 bytes
ca:    485 bytes

$ kubectl -n ingress-nginx get secret/ingress-nginx-admission -o jsonpath="{.data.ca}" | base64 --decode | openssl x509 -noout -dates
notBefore=Jun 22 14:32:42 2020 GMT
notAfter=May 29 14:32:42 2120 GMT

ingress-nginx-admission-patch {#ingress-nginx-admission-patch}

ではpatchは何をしているのか

webhook-name に指定されている名前の ValidatingWebhookConfiguration を取得し、
実装としては各webhookに対して先程生成した自己証明書と failure-policy を設定している。

- name: create
  image: jettech/kube-webhook-certgen:v1.2.0
  imagePullPolicy: IfNotPresent
  args:
  - create
  - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.ingress-nginx.svc
  - --namespace=ingress-nginx
  - --secret-name=ingress-nginx-admission

何をしているかはわかったがこれはcreateと一緒に済ませる事ができそうな感じがしてるので
これが導入された経緯も探してみたが特に書いてないので jettech/kube-webhook-certgen についても深追いしてみる。

jettech/kube-webhook-certgen

結論から書くと設計思想のようなものは見つける事ができなかった。

helmチャートのPRを見るとわかるが、 Implementation is inspired by the prometheus operator webhooks と書いていて 同じ仕組みで動いている。

© Ryota Sakamoto, 2023