• Ingress Controller,和 Kubernetes API 通信,实时更新 Nginx 配置。
  • Ingress Nginx,实际运行转发、规则的载体。

部署方式

Deployment + LB

image

Deployment + LB 直通 Pod

image

Daemonset + HostNetwork + LB

image

Ingress Nginx 如何工作

image

Ingress 本身也是一个 Pod。 外部流量统一经过这个 Pod,然后通过该 Pod 内部的 Nginx 反向代理到各个服务的 Endpoint。

Ingress 的 Pod 也会发生漂移,为了不让它漂移,通过 DaemonSet、nodeAffinity、taint 来实现独享 + 可控。

转发的核心逻辑 balancer_by_lua_blockhttps://github.com/q8s-io/ingress-openresty/blob/q8s-0.26.2/rootfs/etc/nginx/template/nginx.tmpl#L413

具体逻辑实现: https://github.com/q8s-io/ingress-openresty/blob/q8s-0.26.2/rootfs/etc/nginx/lua/balancer.lua

如何加载: https://github.com/q8s-io/ingress-openresty/blob/q8s-0.26.2/rootfs/etc/nginx/template/nginx.tmpl#L109

serviceName + servicePort 确定转发:

metadata:
  name: echoserver-demo
  labels:
    app: echoserver-demo
spec:
  selector:
    app: echoserver-demo
  ports:
    - name: echoserver-demo-80
      port: 80
      protocol: TCP
      targetPort: 8080

最终的配置会体现在 nginx.conf 中。 会影响 nginx.conf 的配置入口有三个,ingress-configmapnginx.tmpl、Ingress。

Nginx reload 场景:

  • Ingress 创建/删除
  • Ingress 添加新的 TLS 引用
  • Ingress annotations 配置变更
  • Ingress path 配置变更
  • Ingress、Service、Secret 删除
  • Secret 配置变更
  • Ingress 中的引用对象由缺失变成可用,比如 Service、Secret

注意:这里没说 nginx.tmpl 变更,会 reload。实际测试也不会!

刷新流程:

image

Kubernetes Controller 利用同步循环模式来检查控制器中所需的状态是否已更新或需要更改。

Ingress Controller 为了从集群中获取该对象,使用了 Kubernetes Informers,尤其是 FilteredSharedInformer。 可以对使用回调的更改做出反应添加,修改或删除新对象时的更改。 但是没有办法知道特定的更改是否会影响最终的配置文件。

nginx ingress 默认是 Pod IP/Pport,可以配置 Service 的 cluster IP: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#service-upstream

Nginx 模型

Ingress Controller 每次更改时,都必须根据集群的状态从头开始重建一个新模型,并将其与当前模型进行比较。 如果新模型等于当前模型,那么避免生成新的 Nginx 配置并触发重新加载。 否则,检查差异是否仅与 Endpoint 有关。 如果是,使用 HTTP POST 请求将新的 Endpoint 列表发送到在 Nginx 内运行的 Lua 处理程序,并再次避免生成新的 Nginx 配置并触发重新加载。 如果运行模型和新模型之间的差异不仅仅是 Endpoint,将基于新模型创建新的 Nginx 配置。 该模型的用途之一是避免状态没有变化时避免不必要的重载,并检测定义中的冲突。

使用同步循环,通过使用 Queue,可以不丢失更改并删除 sync.Mutex 来强制执行一次同步循环。

还可以在同步循环的开始和结束之间创建一个时间窗口,从而允许丢弃不必要的更新。

模型的建立
  • 按时间顺序加载规则。
  • 如果多个 Ingress 使用了相同的 hostpath,以最旧规则为准。
  • 如果多个 Ingress 使用了相同的 host,但 TLS 不同,以最旧规则为准。
  • 如果多个 Ingress 定义了一个影响 Server block 的 annotation,以最旧规则为准。
  • 创建 Nginx Server。
  • 如果多个 Ingress 为同一个 host 定义了不同的 path,则 Ingress 会合并。
  • annotation 将应用于 Ingress 中的所有 path
  • 多个 Ingress 可以定义不同的 annotation。这些 annotation 在 Ingress 之间不共享。

调优

调大连接队列的大小

进程监听的 socket 的连接队列最大的大小受限于内核参数 net.core.somaxconn

在高并发环境下,如果队列过小,可能导致队列溢出,使得连接部分连接无法建立。

要调大 Nginx Ingress 的连接队列,只需要调整 somaxconn 内核参数的值即可。

进程调用 listen() 系统调用来监听端口的时候,还会传入一个 backlog 的参数,这个参数决定 socket 的连接队列大小,其值不得大于 somaxconn 的取值。

Go 程序标准库在 listen 时,默认直接读取 somaxconn 作为队列大小。

Nginx 监听 socket 时没有读取 somaxconn,而是有自己单独的参数配置。 在 nginx.conf 中 listen 端口的位置,还有个叫 backlog 参数可以设置,它会决定 nginx listen 的端口的连接队列大小。

server {
    listen  80  backlog=1024;
}

如果不设置,backlog 在 linux 上默认为 511。

backlog=number

sets the backlog parameter in the listen() call that limits the maximum length for the queue of pending connections.
By default, backlog is set to -1 on FreeBSD, DragonFly BSD, and macOS, and to 511 on other platforms.

Nginx Ingress Controller 会自动读取 somaxconn 的值作为 backlog 参数写到生成的 nginx.conf 中。 也就是说 Nginx Ingress 的连接队列大小只取决于 somaxconn 的大小: https://github.com/q8s-io/ingress-openresty/blob/q8s-0.26.2/internal/ingress/controller/nginx.go#L591

扩大源端口范围

高并发场景会导致 Nginx Ingress 使用大量源端口与 upstream 建立连接。

源端口范围从 net.ipv4.ip_local_port_range 这个内核参数中定义的区间随机选取。

在高并发环境下,端口范围小容易导致源端口耗尽,使得部分连接异常。

TIME_WAIT 复用

如果短连接并发量较高,它所在 netns 中 TIME_WAIT 状态的连接就比较多。

TIME_WAIT 连接默认要等 2MSL 时长才释放,长时间占用源端口。 当这种状态连接数量累积到超过一定量之后可能会导致无法新建连接。

TIME_WAIT 重用,即允许将 TIME_WAIT 连接重新用于新的 TCP 连接 net.ipv4.tcp_tw_reuse=1

调大最大文件句柄数

Nginx 作为反向代理,对于每个请求,它会与 client 和 upstream server 分别建立一个连接,即占据两个文件句柄。

理论上来说 Nginx 能同时处理的连接数最多是系统最大文件句柄数限制的一半。

系统最大文件句柄数由 fs.file-max 这个内核参数来控制。

调高 keepalive 连接最大请求数

Nginx 针对 client 和 upstream 的 keepalive 连接,均有 keepalive_requests 参数来控制单个 keepalive 连接的最大请求数,且默认值均为 100。 当一个 keepalive 连接中请求次数超过这个值时,就会断开并重新建立连接。

频繁断开跟 client 建立的 keepalive 连接,然后就会产生大量 TIME_WAIT 状态连接。 Nginx Ingress 的配置对应 keep-alive-requests。

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-ingress-controller
data:
  # https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#keep-alive-requests
  keep-alive-requests: "10000"

在高并发下场景下调大 upstream-keepalive-requests,避免频繁建联导致 TIME_WAIT 飙升: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#upstream-keepalive-requests

一般情况应该不必配此参数,如果将其调高,可能导致负载不均。 Nginx 与 upstream 保持的 keepalive 连接过久,导致连接发生调度的次数就少了,连接就过于固化,使得流量的负载不均衡。

调高 keepalive 最大空闲连接数

Nginx 与 upstream 保持长连接的最大空闲连接数,默认 32。

空闲连接数多了之后关闭空闲连接,就可能导致 Nginx 与 upstream 频繁断开和建立链接,引发 TIME_WAIT 飙升。

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-ingress-controller
data:
  # https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#upstream-keepalive-connections
  upstream-keepalive-connections: "200"
调高单个 worker 最大连接数

max-worker-connections 控制每个 worker 进程可以打开的最大连接数。

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-ingress-controller
data:
  # https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#max-worker-connections
  max-worker-connections: "65536"

监控

https://github.com/kubernetes/ingress-nginx/tree/master/deploy/grafana/dashboards

Tips

  • 调试费劲,需要重新编译。编译需要翻墙,不然下不下来包。
  • by_lua_block 改成 by_lua_file。Lua 文件可以通过挂载实现热加载。如果是 lua_code_cache off 的方式,依然太 trick 了。
  • 对全局 ingress-configmap 的修改,不会影响到单个 Ingress 对象。
Header 包涵下划线
kind: ConfigMap
apiVersion: v1
data:
  enable-underscores-in-headers: "true"
metadata:
  name: nginx-configuration
  namespace: ingress-nginx