Teste de desempenho de controladores de Ingress NGINX em um ambiente de nuvem Kubernetes dinâmico

NGINX | 22 de setembro de 2020

Editor – Este post faz parte de uma série de 10 partes:

  1. Reduza a complexidade com Kubernetes pronto para produção
  2. Como aumentar a resiliência no Kubernetes com o gerenciamento avançado de tráfego
  3. Como melhorar a visibilidade no Kubernetes
  4. Seis maneiras de proteger o Kubernetes usando ferramentas de gerenciamento de tráfego
  5. Guia para escolher um controlador de Ingress, Parte 1: identifique seus requisitos
  6. Guia para escolher um controlador de Ingress, Parte 2: Riscos e preparação para o futuro
  7. Guia para escolher um controlador de Ingress, Parte 3: código aberto vs. padrão vs. comercial
  8. Um guia para escolher um controlador de Ingress, parte 4: opções de controladores de Ingress do NGINX
  9. Como escolher uma malha de serviços
  10. Teste de desempenho de controladores de Ingress NGINX em um ambiente de nuvem Kubernetes dinâmico (este post)

Você também pode baixar a série completa de artigos como um eBook gratuito – Levando o Kubernetes do teste à produção.

À medida que cada vez mais empresas executam aplicações em contêineres em ambientes de produção, o Kubernetes continua consolidando sua posição como a ferramenta padrão para a orquestração de contêineres. Ao mesmo tempo, a demanda por computação em nuvem foi antecipada em alguns anos, pois as iniciativas de trabalho remoto motivadas pela pandemia de COVID‑19 aceleraram o crescimento do tráfego da Internet. As empresas estão trabalhando rapidamente para modernizar sua infraestrutura, pois seus clientes estão enfrentando grandes interrupções e sobrecargas de rede.

Para alcançar o nível de desempenho necessário em ambientes de microsserviços baseados em nuvem, você precisa de um software rápido e totalmente dinâmico que aproveite a escalabilidade e o desempenho dos data centers de hiperescala de última geração. Muitas organizações que usam o Kubernetes para gerenciar contêineres dependem de um controlador Ingress baseado no NGINX para disponibilizar seus aplicativos aos usuários.

Neste blog, apresentamos os resultados dos nossos testes de desempenho em três controladores Ingress do NGINX em um ambiente multicloud realista, medindo a latência das conexões de clientes pela Internet:

Protocolo de testes e métricas coletadas

Usamos o programa de geração de carga wrk2 para simular um cliente, fazendo solicitações contínuas por HTTPS durante um período definido. O controlador Ingress em teste — o controlador Ingress da comunidade, o NGINX Ingress Controller baseado no NGINX Open Source ou o NGINX Ingress Controller baseado no NGINX Plus — encaminhou as solicitações para aplicações de backend implantadas em Pods do Kubernetes e retornou ao cliente a resposta gerada pelas aplicações. Geramos um fluxo constante de tráfego de clientes para submeter os controladores Ingress a testes de estresse e coletamos as seguintes métricas de desempenho:

  • Latência – O intervalo de tempo entre o cliente gerar uma solicitação e receber a resposta. Apresentamos as latências em uma distribuição por percentis. Por exemplo, se houver 100 amostras de testes de latência, o valor no 99º percentil será a segunda maior latência de resposta entre as 100 execuções de teste.
  • Tempos limite de conexão – Conexões TCP que são silenciosamente interrompidas ou descartadas porque o controlador de Ingress não responde às solicitações dentro de um determinado período.
  • Erros de leitura – Tentativas de leitura em uma conexão que falham porque um socket do controlador de Ingress está fechado.
  • Erros de conexão – Conexões TCP entre o cliente e o controlador Ingress que não são estabelecidas.

Topologia

Em todos os testes, usamos o utilitário wrk2 em execução em uma máquina cliente na AWS para gerar solicitações. O cliente da AWS se conectou ao endereço IP externo do controlador Ingress, que foi implantado como um DaemonSet do Kubernetes no GKE-node-1 em um ambiente do Google Kubernetes Engine (GKE). O controlador Ingress foi configurado para terminação SSL (com referência a um Secret do Kubernetes) e roteamento de camada 7, e exposto por meio de um Service do Kubernetes com Type LoadBalancer. A aplicação de backend foi executada como um Deployment do Kubernetes no GKE-node-2.

Para obter detalhes completos sobre os tipos de máquinas em nuvem e as configurações de software, consulte o Apêndice.

Metodologia de testes

Implantação do cliente

Executamos o seguinte script wrk2 (versão 4.0.0) na máquina cliente da AWS. Ele inicia 2 threads do wrk que, juntas, estabelecem 1000 conexões com o controlador Ingress implantado no GKE. Durante cada execução de teste de 3 minutos, o script gera 30.000 solicitações por segundo (RPS), o que consideramos uma boa simulação da carga sobre um controlador Ingress em um ambiente de produção.

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

onde:

  • -t – Define o número de threads (2)
  • -c – Define o número de conexões TCP (1000)
  • -d – Define a duração da execução do teste em segundos (180, ou 3 minutos)
  • -L – Gera informações detalhadas sobre percentis de latência para exportação para ferramentas de análise
  • -R – Define o número de RPS (30.000)

Para a criptografia TLS, usamos RSA com uma chave de 2048 bits e Perfect Forward Secrecy.

Cada resposta do aplicativo de back-end (acessado em https://app.example.com:443) contém cerca de 1 KB de metadados básicos do servidor, além do código de status HTTP 200 OK.

Implantação da aplicação de back-end

Realizamos testes com implantações estática e dinâmica da aplicação de back-end.

Na implantação estática, havia cinco réplicas de Pods, e nenhuma alteração foi aplicada usando a API do Kubernetes.

Para a implantação dinâmica, usamos o script a seguir para escalar periodicamente a implantação de backend do nginx de cinco réplicas de Pod para sete e, em seguida, reduzi-la novamente para cinco. Isso simula um ambiente Kubernetes dinâmico e testa a eficácia com que o controlador Ingress se adapta às alterações nos endpoints.

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

Resultados de desempenho

Resultados de latência da implantação estática

Como indicado no gráfico, os três controladores Ingress apresentaram desempenho semelhante com uma implantação estática da aplicação de back-end. Isso faz sentido, já que todos são baseados no NGINX Open Source e a implantação estática não exige reconfiguração por parte do controlador Ingress.

Resultados de latência para a implantação dinâmica

O gráfico mostra a latência gerada por cada controlador Ingress em uma implantação dinâmica na qual escalamos periodicamente a aplicação de back-end de cinco Pods de réplica para sete e depois de volta para cinco (consulte Implantação da aplicação de back-end para obter detalhes).

Está claro que apenas o controlador Ingress baseado no NGINX Plus apresenta bom desempenho nesse ambiente, praticamente sem latência até o percentil 99,99. Tanto o controlador Ingress da comunidade quanto o baseado no NGINX Open Source apresentam latência perceptível já em percentis relativamente baixos, embora seguindo padrões bastante diferentes. No controlador Ingress da comunidade, a latência aumenta de forma gradual, porém constante, até o percentil 99, quando se estabiliza em cerca de 5.000 ms (5 segundos). No controlador Ingress baseado no NGINX Open Source, a latência aumenta drasticamente para cerca de 32 segundos no percentil 99 e novamente para 60 segundos no percentil 99,99.

Como discutimos em mais detalhes em Resultados de tempo limite e erros para a implantação dinâmica, a latência observada com os controladores Ingress da comunidade e baseados no NGINX Open Source é causada por erros e tempos limite que ocorrem após a configuração do NGINX ser atualizada e recarregada em resposta às alterações nos endpoints da aplicação de back-end.

Veja uma análise mais detalhada dos resultados do controlador Ingress da comunidade e do controlador Ingress baseado no NGINX Plus, nas mesmas condições de teste do gráfico anterior. O controlador Ingress baseado no NGINX Plus praticamente não introduz latência até o percentil 99,99, quando ela começa a aumentar, chegando a 254 ms no percentil 99,9999. O padrão de latência do controlador Ingress da comunidade aumenta continuamente até atingir 5.000 ms no percentil 99, ponto em que se estabiliza.

Resultados de timeout e erros da implantação dinâmica

Esta tabela mostra com mais detalhes a causa dos resultados de latência.

NGINX Open Source Comunidade NGINX PlusErros de conexão 33365 0 0Tempos limite de conexão 309 8809 0Erros de leitura 4650 0 0

Com o controlador Ingress baseado no NGINX Open Source, a necessidade de atualizar e recarregar a configuração do NGINX a cada alteração nos endpoints da aplicação de back-end causa muitos erros de conexão, tempos limite de conexão e erros de leitura. Erros de conexão/soquete ocorrem durante o breve período necessário para o NGINX recarregar, quando os clientes tentam se conectar a um soquete que não está mais alocado ao processo do NGINX. Tempos limite de conexão ocorrem quando os clientes estabelecem uma conexão com o controlador Ingress, mas o endpoint de back-end não está mais disponível. Tanto os erros quanto os tempos limite afetam gravemente a latência, com picos de até 32 segundos no 99º percentil e novamente de até 60 segundos no 99,99º percentil.

Com o controlador Ingress da comunidade, ocorreram 8.809 tempos limite de conexão devido às alterações nos endpoints à medida que a aplicação de back-end aumentava e reduzia sua escala. O controlador Ingress da comunidade usa código Lua para evitar recarregamentos da configuração quando os endpoints são alterados. Os resultados mostram que a execução de um manipulador Lua no NGINX para detectar alterações nos endpoints soluciona algumas das limitações de desempenho da versão baseada no NGINX Open Source, decorrentes da necessidade de recarregar a configuração após cada alteração nos endpoints. Ainda assim, continuam ocorrendo tempos limite de conexão, o que resulta em latência significativa nos percentis mais altos.

Com o controlador Ingress baseado no NGINX Plus, não houve erros nem tempos limite – o ambiente dinâmico praticamente não afetou o desempenho. Isso ocorre porque ele usa a API do NGINX Plus para atualizar dinamicamente a configuração do NGINX quando os endpoints são alterados. Como mencionado, a maior latência foi de 254 ms e ocorreu apenas no percentil 99,9999.

Conclusão

Os resultados de desempenho mostram que, para eliminar completamente os tempos limite e os erros em um ambiente de nuvem Kubernetes dinâmico, o controlador Ingress deve se ajustar dinamicamente às alterações nos endpoints de back-end, sem manipuladores de eventos nem recarregamentos de configuração. Com base nos resultados, podemos afirmar que a API do NGINX Plus é a solução ideal para reconfigurar dinamicamente o NGINX em um ambiente dinâmico. Em nossos testes, somente o controlador Ingress baseado no NGINX Plus alcançou o desempenho impecável necessário em ambientes Kubernetes altamente dinâmicos para manter seus usuários satisfeitos.

Apêndice

Especificações da máquina na nuvem

Máquina Provedor de nuvem Tipo de máquinaCliente AWS m5a.4xlargeGKE-node-1 GCP e2-standard-32GKE-node-2 GCP e2-standard-32

Configuração dos controladores de Ingress NGINX Open Source e NGINX Plus

Configuração do 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

Observações:

  • Esta configuração é para o NGINX Plus. As referências a nginx‑plus foram ajustadas conforme necessário na configuração do NGINX Open Source.
  • NGINX App Protect está incluído na imagem (gcr.io/nginx-demos/nap-ingress:edge), mas foi desabilitado (o sinalizador -enable-app-protect foi omitido).

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"

Configuração do NGINX Ingress Controller da comunidade

Configuração do 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"

Configuração do aplicativo de back-end

Configuração do 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
---

ConfigMaps

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 :)';}}"
---

Serviço

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


Compartilhar

Sobre o autor

Publicações relacionadas no blog

Como o F5 NGINX One aprimora o gerenciamento e a observabilidade
NGINX | 10/10/2024

Como o F5 NGINX One aprimora o gerenciamento e a observabilidade

Descubra como o F5 NGINX One aprimora o gerenciamento e a observabilidade de ambientes híbridos e multicloud ao oferecer um console web centralizado baseado em SaaS, com insights em tempo real, conformidade automatizada e gerenciamento simplificado de configurações.

Do OpenAPI ao NGINX como gateway de API usando uma API declarativa
NGINX | 17/06/2024

Do OpenAPI ao NGINX como gateway de API usando uma API declarativa

Descubra como transformar uma definição de esquema OpenAPI em uma configuração NGINX totalmente funcional, operando como um API Gateway com a segurança de um Web Application Firewall e um Portal do Desenvolvedor, usando uma abordagem declarativa de API.

Como gerenciar picos repentinos de tráfego e sobrecarga do servidor
NGINX | 14/07/2021

Como gerenciar picos repentinos de tráfego e sobrecarga do servidor

Picos de tráfego e sobrecarga de servidores são preocupações sérias para varejistas online em qualquer época, mas especialmente durante a “Cyber Season” de compras de fim de ano. Detalhamos como você pode aproveitar as soluções de entrega de aplicações de nível de produção da NGINX para se preparar para aumentos repentinos de tráfego.

Distribuição segura de chaves privadas SSL com o NGINX
NGINX | 02/04/2019

Distribuição segura de chaves privadas SSL com o NGINX

Descrevemos três maneiras progressivamente mais seguras de proteger chaves privadas SSL ao configurar o NGINX para processar tráfego HTTPS: permitir acesso de leitura apenas ao usuário root, criptografar as chaves com senhas armazenadas separadamente e distribuir as senhas a partir de um repositório central.

Agrupando solicitações de API em lotes com NGINX Plus e o módulo JavaScript do NGINX
NGINX | 24/05/2018

Agrupando solicitações de API em lotes com NGINX Plus e o módulo JavaScript do NGINX

O módulo JavaScript do NGINX lançado com o NGINX Plus R15 oferece suporte a subsolicitações, possibilitando uma classe totalmente nova de casos de uso para o NGINX Plus. Este exemplo completo também usa a API do NGINX Plus e os módulos de chave-valor para converter solicitações de API dos clientes em solicitações separadas para as APIs dos microsserviços que compõem um aplicativo.

Como superar o esgotamento de portas efêmeras no NGINX e no NGINX Plus
NGINX | 19/04/2016

Como superar o esgotamento de portas efêmeras no NGINX e no NGINX Plus

Confira dicas para evitar o esgotamento de portas efêmeras com ajustes no kernel do Linux, conexões keepalive e conexões vinculadas dinamicamente a endereços IP locais.