BLOG | NGINX

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

Amir Rawdat Miniatura
Amir Rawdat
Publicado em 22 de setembro de 2020

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

  1. Reduza a complexidade com o Kubernetes de nível de produção
  2. Como melhorar a resiliência no Kubernetes com 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. Um guia para escolher um controlador Ingress, parte 1: Identifique seus requisitos
  6. Um guia para escolher um controlador Ingress, parte 2: Riscos e preparação para o futuro
  7. Um guia para escolher um controlador Ingress, parte 3: Código aberto vs. Padrão vs. Comercial
  8. Um guia para escolher um controlador Ingress, parte 4: Opções do controlador de entrada NGINX
  9. Como escolher uma malha de serviço
  10. Teste de desempenho de controladores de entrada NGINX em um ambiente de nuvem Kubernetes dinâmico (esta postagem)

Você também pode baixar o conjunto completo de blogs como um e-book gratuito – Levando o Kubernetes do teste à produção .

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

Para atingir 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 em NGINX para entregar seus aplicativos aos usuários.

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

Protocolo de teste e métricas coletadas

Usamos o programa de geração de carga wrk2 para emular um cliente, fazendo solicitações contínuas via HTTPS durante um período definido. O controlador Ingress em teste – o controlador Ingress da comunidade, NGINX Ingress Controller baseado no NGINX Open Source ou NGINX Ingress Controller baseado no NGINX Plus – encaminhou solicitações para aplicativos de backend implantados em Kubernetes Pods e retornou a resposta gerada pelos aplicativos para o cliente. Geramos um fluxo constante de tráfego de clientes para testar o estresse dos controladores do Ingress e coletamos as seguintes métricas de desempenho:

  • Latência – A quantidade de tempo entre o cliente gerar uma solicitação e receber a resposta. Relatamos as latências em uma distribuição percentual. Por exemplo, se houver 100 amostras de testes de latência, o valor no 99º percentil será a latência de respostas mais lenta nas 100 execuções de teste.
  • Tempos limite de conexão – Conexões TCP que são silenciosamente descartadas ou eliminadas porque o controlador Ingress não responde às solicitações dentro de um determinado tempo.
  • Erros de leitura – Tentativas de leitura em uma conexão que falham porque um soquete do controlador Ingress está fechado.
  • Erros de conexão – Conexões TCP entre o cliente e o controlador Ingress que não são estabelecidas.

Topologia

Para todos os testes, usamos o utilitário wrk2 em execução em uma máquina cliente na AWS para gerar solicitações. O cliente AWS se conectou ao endereço IP externo do controlador Ingress, que foi implantado como um Kubernetes DaemonSet no GKE-node-1 em um ambiente do Google Kubernetes Engine (GKE). O controlador Ingress foi configurado para terminação SSL (referenciando um Kubernetes Secret ) e roteamento de Camada 7, e exposto por meio de um Kubernetes Service do Tipo LoadBalancer . O aplicativo de backend foi executado como uma implantação 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 Teste

Implantação do cliente

Executamos o seguinte script wrk2 (versão 4.0.0) na máquina cliente da AWS. Ele gera 2 threads wrk que, juntos, estabelecem 1.000 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 em 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 de percentil de latência para exportação para ferramentas de análise
  • -R – Define o número de RPS (30.000)

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

Cada resposta do aplicativo back-end (acessado em https://app.example.com:443) consiste em cerca de 1 KB de metadados básicos do servidor, juntamente com o 200 OK Código de status HTTP.

Implantação de aplicativos de back-end

Realizamos testes com uma implantação estática e dinâmica do aplicativo de back-end.

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

Para a implantação dinâmica, usamos o seguinte script para dimensionar periodicamente a implantação do nginx de backend de cinco réplicas de Pod para sete e depois reduzir para cinco. Isso emula um ambiente Kubernetes dinâmico e testa a eficácia com que o controlador Ingress se adapta às alterações do endpoint.

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 para a implantação estática

Conforme indicado no gráfico, todos os três controladores Ingress obtiveram desempenho semelhante com uma implantação estática do aplicativo de back-end. Isso faz sentido, já que todos eles são baseados no NGINX Open Source e a implantação estática não requer reconfiguração do controlador Ingress.

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

O gráfico mostra a latência incorrida por cada controlador Ingress em uma implantação dinâmica em que dimensionamos periodicamente o aplicativo de back-end de cinco Pods de réplica para sete e vice-versa (consulte Implantação de aplicativo de back-end para obter detalhes).

Está claro que apenas o controlador Ingress baseado em NGINX Plus tem bom desempenho neste ambiente, sofrendo praticamente nenhuma latência até o 99,99º percentil. Tanto a comunidade quanto os controladores Ingress baseados em NGINX Open Source apresentam latência perceptível em percentis bastante baixos, embora em um padrão bastante diferente. Para o controlador Ingress da comunidade, a latência sobe suave, mas constantemente, até o 99º percentil, onde se estabiliza em cerca de 5.000 ms (5 segundos). Para o controlador Ingress baseado em NGINX Open Source, a latência aumenta drasticamente para cerca de 32 segundos no 99º percentil e novamente para 60 segundos no 99,99º.

Conforme discutimos mais detalhadamente em Resultados de tempo limite e erro para a implantação dinâmica , a latência experimentada com a comunidade e os controladores Ingress baseados em código aberto NGINX é causada por erros e tempos limite que ocorrem após a configuração do NGINX ser atualizada e recarregada em resposta às alterações de endpoints para o aplicativo de back-end.

Aqui está uma visão mais detalhada dos resultados para o controlador Ingress da comunidade e o controlador Ingress baseado em NGINX Plus na mesma condição de teste do gráfico anterior. O controlador Ingress baseado no NGINX Plus não apresenta praticamente nenhuma latência até o 99,99º percentil, onde começa a subir para 254 ms no 99,9999º percentil. O padrão de latência para o controlador Ingress da comunidade cresce continuamente para uma latência de 5000 ms no 99º percentil, ponto em que a latência se estabiliza.

Resultados de tempo limite e erro para a implantação dinâmica

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

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

Com o controlador Ingress baseado em código aberto NGINX, a necessidade de atualizar e recarregar a configuração do NGINX para cada alteração nos pontos de extremidade do aplicativo de back-end causa muitos erros de conexão, tempos limite de conexão e erros de leitura. Erros de conexão/socket ocorrem durante o breve período em que o NGINX leva para recarregar, quando os clientes tentam se conectar a um socket que não está mais alocado ao processo NGINX. Tempos limite de conexão ocorrem quando os clientes estabelecem uma conexão com o controlador Ingress, mas o ponto de extremidade de backend não está mais disponível. Tanto erros quanto tempos limite impactam severamente a latência, com picos de até 32 segundos no 99º percentil e novamente de até 60 segundos no 99,99º.

Com o controlador Ingress da comunidade, houve 8.809 tempos limite de conexão devido às alterações nos endpoints conforme o aplicativo de back-end era ampliado e reduzido. O controlador Ingress da comunidade usa código Lua para evitar recarregamentos de configuração quando os endpoints mudam . Os resultados mostram que executar um manipulador Lua dentro do NGINX para detectar alterações de endpoint aborda algumas das limitações de desempenho da versão baseada em código aberto do NGINX, que resultam da necessidade de recarregar a configuração após cada alteração nos endpoints. No entanto, tempos limite de conexão ainda ocorrem e resultam em latência significativa em percentis mais altos.

Com o controlador Ingress baseado em NGINX Plus não houve erros ou tempos limite – o ambiente dinâmico praticamente não teve efeito no desempenho. Isso ocorre porque ele usa a API NGINX Plus para atualizar dinamicamente a configuração do NGINX quando os endpoints mudam. Como mencionado, a latência mais alta 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 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 ou recarregamentos de configuração. Com base nos resultados, podemos dizer que a API NGINX Plus é a solução ideal para reconfigurar dinamicamente o NGINX em um ambiente dinâmico. Em nossos testes, apenas o controlador Ingress baseado em NGINX Plus obteve o desempenho perfeito em ambientes Kubernetes altamente dinâmicos que você precisa para manter seus usuários satisfeitos.

Apêndice

Especificações da máquina em nuvem

Máquina Provedor de Nuvem Tipo de máquina
Cliente AWS m5a.4xgrande
GKE-nó-1 GCP e2-padrão-32
GKE-nó-2 GCP e2-padrão-32

Configuração para controladores NGINX Open Source e NGINX Plus Ingress

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

Notas:

  • Esta configuração é para o NGINX Plus. As referências ao nginx‑plus foram ajustadas conforme necessário na configuração do NGINX Open Source.
  • O 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).

Mapa de configuração

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 para o controlador de entrada NGINX 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

Mapa de configuração

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 para aplicativo 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
---

Mapas de configuração

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_port\nServer name:$hostname\nDate: $time_local\nURI: $request_uri\nRequest ID: $request_id\n';}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
---

"Esta postagem do blog pode fazer referência a produtos que não estão mais disponíveis e/ou não têm mais suporte. Para obter as informações mais atualizadas sobre os produtos e soluções F5 NGINX disponíveis, explore nossa família de produtos NGINX . O NGINX agora faz parte do F5. Todos os links anteriores do NGINX.com redirecionarão para conteúdo semelhante do NGINX no F5.com."