在动态Kubernetes云环境中对NGINX Ingress Controller进行性能测试

NGINX | 2020年9月22日

编者:本文是由10篇文章组成的系列文章之一:

  1. 借助生产级Kubernetes降低复杂性
  2. 如何通过高级流量管理提升Kubernetes的韧性
  3. 如何提高Kubernetes中的可观测性
  4. 使用流量管理工具保护Kubernetes的六种方法
  5. Ingress Controller选择指南(第1部分):明确需求
  6. Ingress Controller选择指南(第2部分):风险与面向未来的规划
  7. Ingress Controller 选择指南,第3部分:开源、默认与商业方案对比
  8. Ingress Controller 选择指南,第4部分:NGINX Ingress Controller 选项
  9. 如何选择服务网格
  10. 在动态Kubernetes云环境中对NGINX Ingress Controller进行性能测试(本文)

还可以免费下载包含完整系列博客的电子书《推动Kubernetes从测试走向生产》。

随着越来越多的企业在生产环境中运行容器化应用,Kubernetes作为容器编排标准工具的地位不断巩固。与此同时,COVID‑19疫情推动了居家办公计划,加速了互联网流量增长,使云计算需求提前了数年。由于客户正遭遇严重的网络中断和过载问题,各公司正在迅速升级基础设施。

要在基于云的微服务环境中达到所需的性能水平,需要采用快速、完全动态的软件,充分利用新一代超大规模数据中心的可扩展性和性能。许多使用Kubernetes管理容器的组织依靠基于NGINX的Ingress控制器向用户交付应用。

本文将介绍我们在真实多云环境中对三款NGINX Ingress控制器进行性能测试的结果,测试衡量了客户端通过互联网建立连接时的延迟:

测试方案和收集的指标

我们使用负载生成程序wrk2模拟客户端,在规定时间段内通过HTTPS持续发送请求。受测的Ingress控制器包括社区Ingress控制器、基于NGINX Open Source的NGINX Ingress Controller或基于NGINX Plus的NGINX Ingress Controller。这些控制器将请求转发到部署在Kubernetes Pod中的后端应用,并将应用生成的响应返回给客户端。我们生成稳定的客户端流量,对Ingress控制器进行压力测试,并收集以下性能指标:

  • 延迟:从客户端发出请求到收到响应所需的时间。我们以百分位分布形式报告延迟。例如,如果延迟测试有100个样本,则第99百分位的值是100次测试中第二慢的响应延迟。
  • 连接超时:由于Ingress控制器未能在规定时间内响应请求,TCP连接被静默断开或丢弃。
  • 读取错误:由于Ingress控制器的套接字已关闭,尝试从连接中读取数据失败。
  • 连接错误:客户端与Ingress控制器之间未能建立TCP连接。

拓扑

在所有测试中,我们使用了在AWS客户端计算机上运行的wrk2实用工具来生成请求。该AWS客户端连接到Ingress控制器的外部IP地址。此控制器在Google Kubernetes Engine(GKE)环境中的DaemonSet上以Kubernetes GKE-node-1形式部署。Ingress控制器配置为执行SSL终止(引用Kubernetes Secret)和第7层路由,并通过Kubernetes Service公开,其Type为LoadBalancer。后端应用以Kubernetes Deployment形式在GKE-node-2上运行。

有关云计算机类型和软件配置的完整详情,请参阅“附录”。

测试方法

客户端部署

我们在AWS客户端计算机上运行了以下wrk2(版本4.0.0)脚本。该脚本会启动2个wrk线程,共同与部署在GKE中的Ingress控制器建立1,000个连接。在每次3分钟的测试运行期间,该脚本每秒生成30,000个请求(RPS),我们认为这可以较好地模拟生产环境中Ingress控制器承受的负载。

wrk -t2 -c1000 -d180s -L -R30000 https://app.example.com:443/

其中:

  • -t:设置线程数(2)
  • -c,设置TCP连接数(1000)
  • -d,设置测试运行时长(以秒为单位,180 秒,即 3 分钟)
  • -L,生成详细的延迟百分位数信息,以便导出到分析工具
  • -R,设置RPS数量(30,000)

对于TLS加密,我们采用了密钥长度为2048位的RSA和完全前向保密。

后端应用(访问地址为https://app.example.com:443)的每个响应均包含约1 KB的基本服务器元数据,以及200 OK HTTP状态码。

后端应用部署

我们分别对后端应用的静态部署和动态部署进行了测试运行。

在静态部署中,共有5个Pod副本,且未通过Kubernetes API应用任何更改。

对于动态部署,我们使用以下脚本定期将后端nginx部署的Pod副本数从5个扩展到7个,再缩减回5个。这样可以模拟动态Kubernetes环境,并测试Ingress控制器适应端点变化的效果。

while [ 1 -eq 1 ]
do
  kubectl scale deployment nginx --replicas=5
  sleep 12
  kubectl scale deployment nginx --replicas=7
  sleep 10
done

性能结果

静态部署的延迟结果

如图所示,在后端应用采用静态部署时,三种Ingress控制器的性能表现相近。这是合理的,因为它们都基于NGINX Open Source,而且静态部署不需要Ingress控制器重新配置。

动态部署的延迟结果

该图显示了动态部署中每个Ingress控制器产生的延迟。在此部署中,我们定期将后端应用从5个副本Pod扩展到7个,然后再缩减到5个(有关详细信息,请参阅“后端应用部署”)。

显然,在此环境中,只有基于NGINX Plus的Ingress控制器表现良好,直至第99.99百分位几乎都没有延迟。社区Ingress控制器和基于NGINX Open Source的Ingress控制器在较低百分位均出现明显延迟,但二者的变化模式截然不同。社区Ingress控制器的延迟缓慢但持续上升,直至第99百分位,并稳定在约5,000 ms(5秒)。基于NGINX Open Source的Ingress控制器在第99百分位时,延迟急剧升至约32秒;到第99.99百分位时,又升至60秒。

正如我们在动态部署的超时和错误结果中进一步讨论的那样,社区版和基于NGINX Open Source的Ingress控制器出现延迟,是因为后端应用的端点发生变化后,NGINX配置随之更新并重新加载,进而引发错误和超时。

下面更细致地展示了社区版Ingress控制器和基于NGINX Plus的Ingress控制器在与上图相同测试条件下的结果。基于NGINX Plus的Ingress控制器在第99.99百分位之前几乎不会引入延迟,之后延迟开始上升,到第99.9999百分位时接近254 ms。社区版Ingress控制器的延迟则持续增加,到第99百分位时达到5,000 ms,随后趋于平稳。

动态部署的超时和错误结果

此表更详细地说明了延迟结果的原因。

NGINX Open Source 社区版 NGINX Plus连接错误 33365 0 0连接超时 309 8809 0读取错误 4650 0 0

使用基于NGINX Open Source的Ingress控制器时,后端应用端点每次发生变化都需要更新并重新加载NGINX配置,因而会导致大量连接错误、连接超时和读取错误。在NGINX重新加载的短暂过程中,如果客户端尝试连接的套接字已不再分配给NGINX进程,就会发生连接或套接字错误。如果客户端已与Ingress控制器建立连接,但后端端点已不可用,就会发生连接超时。错误和超时都会严重影响延迟,第99百分位的延迟会飙升至32秒,第99.99百分位则会再次飙升至60秒。

使用社区版Ingress控制器时,后端应用扩缩容引起端点变化,导致了8,809次连接超时。社区版Ingress控制器使用Lua代码,避免在端点发生变化时重新加载配置。结果表明,在NGINX中运行Lua处理程序来检测端点变化,可以缓解基于NGINX Open Source的版本存在的部分性能限制。这些限制源于该版本要求在每次端点发生变化后重新加载配置。尽管如此,连接超时仍会发生,并在较高百分位造成显著延迟。

使用基于NGINX Plus的Ingress控制器时,没有出现错误或超时,动态环境对性能几乎没有影响。这是因为该控制器使用NGINX Plus API,在端点发生变化时动态更新NGINX配置。如前所述,最高延迟为254 ms,并且仅出现在第99.9999百分位。

结论

性能结果表明,要在动态Kubernetes云环境中彻底消除超时和错误,Ingress控制器必须能够根据后端端点的变化动态调整,而无需使用事件处理程序或重新加载配置。根据这些结果,可以认为NGINX Plus API非常适合在动态环境中对NGINX进行动态重新配置。在我们的测试中,只有基于NGINX Plus的Ingress控制器在高度动态的Kubernetes环境中实现了稳定无误的性能,有助于持续提供令用户满意的体验。

附录

云计算机规格

机器 云提供商 机器类型Client AWS m5a.4xlargeGKE-node-1 GCP e2-standard-32GKE-node-2 GCP e2-standard-32

NGINX Open Source和NGINX Plus Ingress Controller配置

Kubernetes配置

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nginx-ingress
  namespace: nginx-ingress
spec:
  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

注意事项:

  • 此配置适用于NGINX Plus。在NGINX Open Source的配置中,已根据需要调整对“nginx‑plus”的引用。
  • 该镜像中包含NGINX App Protect(gcr.io/nginx-demos/nap-ingress:edge),但已被禁用(省略了-enable-app-protect标志)。

ConfigMap

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-config
  namespace: nginx-ingress
data:
  worker-connections: "10000"
  worker-rlimit-nofile: "10240"
  keepalive: "100"
  keepalive-requests: "100000000"

Community NGINX Ingress Controller 配置

Kubernetes配置

apiVersion: apps/v1
kind: DaemonSet
metadata:
  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-nginx
spec:
  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: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  max-worker-connections: "10000"
  max-worker-open-files: "10204"
  upstream-keepalive-connections: "100"
  keep-alive-requests: "100000000"

后端应用配置

Kubernetes 配置

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  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
---

ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: main-conf
  namespace: default
data:
  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: v1
kind: ConfigMap
metadata:
  name: app-conf
  namespace: default
data:
  app.conf: "server {listen 8080;location / {default_type text/plain;expires -1;return 200 'Server address: $server_addr:$server_portnServer name:$hostnamenDate: $time_localnURI: $request_urinRequest ID: $request_idn';}location /healthz {return 200 'I am happy and healthy :)';}}"
---

服务

apiVersion: v1
kind: Service
metadata:
  name: app-svc
spec:
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: nginx
---


分享

关于作者

相关博客文章

NGINX Plus R26 正式发布
NGINX | 2022/02/15

NGINX Plus R26 正式发布

NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
NGINX | 2022/02/10

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击

gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
NGINX | 2021/09/13

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划

了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。

NGINX Plus R19 正式发布
NGINX | 2019/09/03

NGINX Plus R19 正式发布

NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。

NGINX现已正式加入F5
NGINX | 2019/05/09

NGINX现已正式加入F5

NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。

使用NGINX和NGINX Plus实现高性能缓存
NGINX | 2016/08/24

使用NGINX和NGINX Plus实现高性能缓存

我将介绍NGINX在内容缓存和性能方面的工作原理,并分享一些控制NGINX内容缓存方式的技巧。