原文作者:Amir Rawdat
原文链接:NGINX Ingress Controller 在动态 Kubernetes 云环境中的性能测试 - NGINX
转载来源:NGINX 官方网站
随着越来越多的企业在生产环境中运行容器化应用,Kubernetes 将持续巩固其作为标准容器编排工具的地位。与此同时,在疫情中崛起的居家办公模式加快了互联网流量的增长,导致云计算需求提前几年爆发。目前很多公司都在紧锣密鼓地升级基础架构,帮助客户解决正在面临的重大网络中断和过载问题。
为了在基于云的微服务环境中达到所需的性能水平,您需要使用快速、完全动态的软件来释放下一代超大规模数据中心的可扩展性和性能潜力。许多使用 Kubernetes 管理容器的企业和机构都依赖基于 NGINX 的 Ingress Controller 来实现应用交付。
在这篇博客中,我们针对在互联网中客户端连接的延迟情况,提供了三种 NGINX Ingress Controller 在真实多云环境中的性能测试结果。这三种控制器分别为:
测试方法和收集的指标
我们使用性能测试程序 wrk2 模拟了一个客户端,在规定的时间段内发出连续的 HTTPS 请求。被测试的 Ingress Controller(社区版 Ingress Controller、NGINX 开源版 Ingress Controller 和 NGINX Plus Ingress Controller)将请求转发到部署在 Kubernetes Pods 中的后端应用,并将应用生成的响应返回给客户端。我们生成了稳定的客户端流量,并使用这些流量对 Ingress Controller 进行压力测试,收集了以下性能指标:
延迟 — 客户端从生成请求到接收响应所用的时间。我们用百分位分布的形式来报告延迟。例如,如果有 100 个延迟测试样本,则第 99 个百分位数的值是 100 次测试中仅次于最慢值的响应延迟。
连接超时 — 由于 Ingress Controller 在特定时间内无法响应请求而被静默断开或丢弃的 TCP 连接。
读取错误 — 尝试读取连接失败,因为来自 Ingress Controller 的套接字已关闭。
连接错误 — 客户端和 Ingress Controller 之间未建立 TCP 连接。
拓扑结构
对于所有测试,我们都在 AWS 客户端机器上运行 wrk2 程序,生成请求。AWS 客户端会连接到 Ingress Controller 的外部 IP 地址,其中 Ingress Controller 作为 Kubernetes DaemonSet 部署在 Google Kubernetes Engine (GKE) 环境中的 GKE-node-1 上。
Ingress Controller 配置成 SSL 终结(引用 Kubernetes Secret)和 7 层路由模式,并通过 LoadBalancer 类型的 Kubernetes 服务暴露。后端应用则作为 Kubernetes Deployment 在 GKE-node-2 上运行。
编辑切换为居中
添加图片注释,不超过 140 字(可选)
有关云主机类型和软件配置的全部信息,请参阅附录。
测试方法
客户端部署
我们在 AWS 客户端机器上运行以下 wrk2(版本 4.0.0)脚本。它生成了 2 个 wrk 线程,这些线程共与 GKE 中部署的 Ingress Controller 建立了 1000 个连接。在每轮持续 3 分钟的测试期间内,脚本每秒生成 30,000 个请求 (RPS),我们认为这很好地模拟了 Ingress Controller 在生产环境中的负载。
wrk -t2 -c1000 -d180s -L -R30000 https://app.example.com:443/
复制代码
其中:
对于 TLS 加密,我们使用了 2048 位 RSA 密钥加密和 PFS 完美正向加密。
每个来自后端应用的响应(访问地址:https://app.example.com:443)都包含大约 1 KB 的基础服务器元数据以及 200 OK HTTP 状态码。
B 后端应用部署
我们对后端应用程序进行了静态和动态部署的测试运行。
静态部署中有五个 Pod 副本,并且没有使用 Kubernetes API 做任何更改。
对于动态部署,我们使用以下脚本定期将后端 nginx 部署从五个 Pod 副本扩展到七个,然后再缩减到五个。这模拟了一个动态 Kubernetes 环境,能够测试 Ingress Controller 如何有效适应端点变更。
while [ 1 -eq 1 ]do kubectl scale deployment nginx --replicas=5 sleep 12 kubectl scale deployment nginx --replicas=7 sleep 10done
复制代码
性能结果
静态部署的延迟结果
如图所示,在静态部署后端应用时,三个 Ingress Controller 具有相似的性能。考虑到它们都基于 NGINX 开源版构建,并且静态部署不需要从 Ingress Controller 重新配置,这一结果也很合理。
动态部署的延迟结果
该图显示了在定期将后端应用从五个 Pod 副本扩展到七个、又减少到五个的动态部署下(详见“后端应用部署”部分),每种 Ingress Controller 产生的延迟。
很明显,只有 NGINX Plus Ingress Controller 在这种环境中表现良好,一直到第 99.99% 个都几乎没有任何延迟。社区版和 NGINX 开源版 Ingress Controller 虽然具有截然不同的延迟模式,但都在很低的百分位上产生了明显的延迟。对于社区版 Ingress Controller,延迟呈缓慢但稳定的上升状态,在第 99 %个达到大约 5000 毫秒(5 秒)并在之后趋于稳定。对于 NGINX 开源版 Ingress Controller,延迟呈急剧攀升状态,在第 99 %个达到大约 32 秒,到第 99.99% 个又变成了 60 秒。
社区版和 NGINX 开源版 Ingress Controller 所经历的延迟是由 NGINX 配置更新和重新加载(以响应后端应用不断变化的端点)后出现的错误和超时引起的,具体内容我们将在“动态部署中的超时和错误结果”部分进行进一步讨论。
这是一个更细粒度的视图,展示了社区版和 NGINX Plus Ingress Controller 在与上图相同的测试条件下得出的结果。NGINX Plus Ingress Controller 第 99.9999% 个的延迟为 254 毫秒,在此之前几乎没有任何延迟。社区版 Ingress Controller 的延迟在第 99% 个稳定增长到 5000 毫秒,此后趋于平稳。
动态部署中的超时和错误结果
此表更详细地显示了引起延迟的原因。
使用 NGINX 开源版 Ingress Controller 时,每次更改后端应用端点后都需要更新和重新加载 NGINX 配置,这会导致许多连接错误、连接超时和读取错误的问题。当客户端尝试连接不再分配给 NGINX 进程的套接字时,系统会在 NGINX 重新加载的短时间内发生连接/套接字错误。当客户端与 Ingress Controller 建立连接,但后端端点不再可用时,会发生连接超时。连接错误和连接超时会严重影响延迟,导致延迟在第 99% 个激增到 32 秒,然后在第 99.99% 个变成 60 秒。
使用社区版 Ingress Controller 时,端点随着后端应用的调整而更改,引发了 8809 个连接超时。社区版 Ingress Controller 使用 Lua 代码来避免在端点变更时重新加载配置。结果显示,在 NGINX 内部运行的检测端点变更的 Lua 处理程序解决了 NGINX 开源版 Ingress Controller 的一些性能限制问题——这些限制是由每次更改端点后都重新加载配置引起的。尽管如此,连接超时仍然会发生,导致更高百分位的显著延迟。
而使用 NGINX Plus Ingress Controller 时没有错误或超时——动态环境对性能几乎没有影响。这是因为 NGINX Plus Ingress Controller 使用了 NGINX Plus API,可以在端点变更后动态更新 NGINX 配置。如上文所述,它的最高延迟为 254 毫秒,并且仅发生在第 99.9999% 个。
结语
性能结果表明,要想完全消除动态 Kubernetes 云环境中的超时和错误,Ingress Controller 就必须动态适应后端端点的变更,且无需使用事件处理程序或重新加载配置。根据这些结果,NGINX Plus API 可以说是在动态环境中动态重新配置 NGINX 的最佳解决方案。在我们的测试中,只有 NGINX Plus Ingress Controller 在高度动态的 Kubernetes 环境中实现了符合用户需求的完美性能。
附录
云机器规格
NGINX 开源版和 NGINX Plus Ingress Controller 的配置
Kubernetes 配置
apiVersion: apps/v1kind: DaemonSetmetadata: name: nginx-ingress namespace: nginx-ingressspec: selector: matchLabels: app: nginx-ingress template: metadata: labels: app: nginx-ingress #annotations: #prometheus.io/scrape: "true" #prometheus.io/port: "9113" spec: serviceAccountName: nginx-ingress nodeSelector: kubernetes.io/hostname: gke-rawdata-cluster-default-pool-3ac53622-6nzr hostNetwork: true containers: - image: gcr.io/nginx-demos/nap-ingress:edge imagePullPolicy: Always name: nginx-plus-ingress ports: - name: http containerPort: 80 hostPort: 80 - name: https containerPort: 443 hostPort: 443 - name: readiness-port containerPort: 8081 #- name: prometheus #containerPort: 9113 readinessProbe: httpGet: path: /nginx-ready port: readiness-port periodSeconds: 1 securityContext: allowPrivilegeEscalation: true runAsUser: 101 #nginx capabilities: drop: - ALL add: - NET_BIND_SERVICE env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name args: - -nginx-plus - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret
复制代码
注:
ConfigMap
kind: ConfigMapapiVersion: v1metadata: name: nginx-config namespace: nginx-ingressdata: worker-connections: "10000" worker-rlimit-nofile: "10240" keepalive: "100" keepalive-requests: "100000000"
复制代码
社区版 NGINX Ingress Controller 的配置
Kubernetes 配置
apiVersion: apps/v1kind: DaemonSetmetadata: labels: helm.sh/chart: ingress-nginx-2.11.1 app.kubernetes.io/name: ingress-nginx app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/version: 0.34.1 app.kubernetes.io/managed-by: Helm app.kubernetes.io/component: controller name: ingress-nginx-controller namespace: ingress-nginxspec: selector: matchLabels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/component: controller template: metadata: labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/component: controller spec: nodeSelector: kubernetes.io/hostname: gke-rawdata-cluster-default-pool-3ac53622-6nzr hostNetwork: true containers: - name: controller image: us.gcr.io/k8s-artifacts-prod/ingress-nginx/controller:v0.34.1@sha256:0e072dddd1f7f8fc8909a2ca6f65e76c5f0d2fcfb8be47935ae3457e8bbceb20 imagePullPolicy: IfNotPresent lifecycle: preStop: exec: command: - /wait-shutdown args: - /nginx-ingress-controller - --election-id=ingress-controller-leader - --ingress-class=nginx - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller - --validating-webhook=:8443 - --validating-webhook-certificate=/usr/local/certificates/cert - --validating-webhook-key=/usr/local/certificates/key securityContext: capabilities: drop: - ALL add: - NET_BIND_SERVICE runAsUser: 101 allowPrivilegeEscalation: true env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace readinessProbe: httpGet: path: /healthz port: 10254 scheme: HTTP periodSeconds: 1 ports: - name: http containerPort: 80 protocol: TCP - name: https containerPort: 443 protocol: TCP - name: webhook containerPort: 8443 protocol: TCP volumeMounts: - name: webhook-cert mountPath: /usr/local/certificates/ readOnly: true serviceAccountName: ingress-nginx terminationGracePeriodSeconds: 300 volumes: - name: webhook-cert secret: secretName: ingress-nginx-admission
复制代码
ConfigMap
apiVersion: v1kind: ConfigMapmetadata: name: ingress-nginx-controller namespace: ingress-nginxdata: max-worker-connections: "10000" max-worker-open-files: "10204" upstream-keepalive-connections: "100" keep-alive-requests: "100000000"
复制代码
后端应用配置
Kubernetes 配置
apiVersion: apps/v1kind: Deploymentmetadata: name: nginxspec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: nodeSelector: kubernetes.io/hostname: gke-rawdata-cluster-default-pool-3ac53622-t2dz containers: - name: nginx image: nginx ports: - containerPort: 8080 volumeMounts: - name: main-config-volume mountPath: /etc/nginx - name: app-config-volume mountPath: /etc/nginx/conf.d readinessProbe: httpGet: path: /healthz port: 8080 periodSeconds: 3 volumes: - name: main-config-volume configMap: name: main-conf - name: app-config-volume configMap: name: app-conf---
复制代码
ConfigMaps
apiVersion: v1kind: ConfigMapmetadata: name: main-conf namespace: defaultdata: nginx.conf: |+ user nginx; worker_processes 16; worker_rlimit_nofile 102400; worker_cpu_affinity auto 1111111111111111; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid; events { worker_connections 100000; } http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; sendfile on; tcp_nodelay on; access_log off; include /etc/nginx/conf.d/*.conf; } --- apiVersion: v1kind: ConfigMapmetadata: name: app-conf namespace: defaultdata: app.conf: "server {listen 8080;location / {default_type text/plain;expires -1;return 200 'Server address: $server_addr:$server_port\nServer name:$hostname\nDate: $time_local\nURI: $request_uri\nRequest ID: $request_id\n';}location /healthz {return 200 'I am happy and healthy :)';}}"---
复制代码
服务
apiVersion: v1kind: Servicemetadata: name: app-svcspec: ports: - port: 80 targetPort: 8080 protocol: TCP name: http selector: app: nginx---
复制代码
NGINX 唯一中文官方社区 ,尽在 http://nginx.org.cn/
更多 NGINX 相关的技术干货、互动问答、系列课程、活动资源:
评论