Editor : Esta publicación es parte de una serie de 10 partes:
También puede descargar el conjunto completo de blogs como un libro electrónico gratuito: Cómo llevar Kubernetes de la prueba a la producción .
A medida que más empresas ejecutan aplicaciones en contenedores en producción, Kubernetes continúa consolidando su posición como la herramienta estándar para la orquestación de contenedores. Al mismo tiempo, la demanda de computación en la nube se ha adelantado un par de años porque las iniciativas de trabajo desde casa impulsadas por la pandemia de COVID-19 han acelerado el crecimiento del tráfico de Internet. Las empresas están trabajando rápidamente para actualizar su infraestructura porque sus clientes están experimentando importantes cortes y sobrecargas en la red.
Para lograr el nivel de rendimiento requerido en entornos de microservicios basados en la nube, necesita un software rápido y totalmente dinámico que aproveche la escalabilidad y el rendimiento de los centros de datos de hiperescala de próxima generación. Muchas organizaciones que utilizan Kubernetes para administrar contenedores dependen de un controlador Ingress basado en NGINX para entregar sus aplicaciones a los usuarios.
En este blog informamos los resultados de nuestras pruebas de rendimiento en tres controladores NGINX Ingress en un entorno multicloud realista, midiendo la latencia de las conexiones de clientes a través de Internet:
Utilizamos el programa de generación de carga wrk2
para emular un cliente, realizando solicitudes continuas a través de HTTPS durante un período definido. El controlador de Ingress bajo prueba (el controlador de Ingress comunitario, el controlador de Ingress NGINX basado en NGINX Open Source o el controlador de Ingress NGINX basado en NGINX Plus) reenvió solicitudes a aplicaciones de back-end implementadas en pods de Kubernetes y devolvió la respuesta generada por las aplicaciones al cliente. Generamos un flujo constante de tráfico de clientes para probar el estrés de los controladores de Ingress y recopilamos las siguientes métricas de rendimiento:
Para todas las pruebas, utilizamos la utilidad wrk2
que se ejecuta en una máquina cliente en AWS para generar solicitudes. El cliente de AWS se conectó a la dirección IP externa del controlador de Ingress, que se implementó como un Kubernetes DaemonSet en GKE-node-1 en un entorno de Google Kubernetes Engine (GKE). El controlador de ingreso se configuró para la terminación SSL (haciendo referencia a un secreto de Kubernetes) y enrutamiento de capa 7, y se expuso a través de un servicio de Kubernetes de tipo
LoadBalancer
. La aplicación de backend se ejecutó como una implementación de Kubernetes en GKE-node-2 .
Para obtener detalles completos sobre los tipos de máquinas en la nube y las configuraciones de software, consulte el Apéndice .
Ejecutamos el siguiente script wrk2
(versión 4.0.0) en la máquina cliente de AWS. Genera 2 subprocesos wrk
que juntos establecen 1000 conexiones al controlador de Ingress implementado en GKE. Durante cada ejecución de prueba de 3 minutos, el script genera 30 000 solicitudes por segundo (RPS), lo que consideramos una buena simulación de la carga en un controlador Ingress en un entorno de producción.
wrk -t2 -c1000 -d180s -L -R30000 https://app.example.com:443/
DÓNDE:
-t
– Establece el número de subprocesos (2)-c
– Establece el número de conexiones TCP (1000)-d
– Establece la duración de la ejecución de la prueba en segundos (180 o 3 minutos)-L
– Genera información detallada del percentil de latencia para exportar a herramientas de análisis-R
– Establece el número de RPS (30 000)Para el cifrado TLS, utilizamos RSA con un tamaño de clave de 2048 bits y Perfect Forward Secrecy.
Cada respuesta de la aplicación back-end (a la que se accede en https://app.ejemplo.com:443) consta de aproximadamente 1 KB de metadatos básicos del servidor, junto con la 200
DE ACUERDO
Código de estado HTTP.
Realizamos pruebas con una implementación estática y dinámica de la aplicación back-end.
En la implementación estática, había cinco réplicas de Pod y no se aplicaron cambios mediante la API de Kubernetes.
Para la implementación dinámica, utilizamos el siguiente script para escalar periódicamente la implementación de nginx de backend desde cinco réplicas de Pod hasta siete, y luego nuevamente a cinco. Esto emula un entorno dinámico de Kubernetes y prueba la eficacia con la que el controlador de Ingress se adapta a los cambios de los puntos finales.
while [ 1 -eq 1 ]
do
kubectl scale deployment nginx --replicas=5
sleep 12
kubectl scale deployment nginx --replicas=7
sleep 10
done
Como se indica en el gráfico, los tres controladores Ingress lograron un rendimiento similar con una implementación estática de la aplicación back-end. Esto tiene sentido dado que todos se basan en NGINX de código abierto y la implementación estática no requiere reconfiguración del controlador Ingress.
El gráfico muestra la latencia incurrida por cada controlador de Ingress en una implementación dinámica donde periódicamente escalamos la aplicación back-end desde cinco pods de réplica hasta siete y viceversa (consulte Implementación de aplicación back-end para obtener más detalles).
Está claro que solo el controlador Ingress basado en NGINX Plus funciona bien en este entorno, y prácticamente no sufre latencia hasta el percentil 99,99. Tanto la comunidad como los controladores Ingress basados en código abierto NGINX experimentan una latencia notable en percentiles bastante bajos, aunque con un patrón bastante diferente. Para el controlador de Ingress de la comunidad, la latencia aumenta de manera suave pero constante hasta el percentil 99, donde se estabiliza en aproximadamente 5000 ms (5 segundos). Para el controlador Ingress basado en código abierto NGINX, la latencia aumenta drásticamente a aproximadamente 32 segundos en el percentil 99, y nuevamente a 60 segundos en el percentil 99,99.
Como analizamos más detalladamente en Resultados de errores y tiempos de espera para la implementación dinámica , la latencia experimentada con los controladores de ingreso basados en código abierto NGINX y la comunidad se debe a errores y tiempos de espera que ocurren después de que se actualiza y se vuelve a cargar la configuración de NGINX en respuesta a los puntos finales cambiantes de la aplicación back-end.
Aquí se muestra una vista más detallada de los resultados para el controlador Ingress comunitario y el controlador Ingress basado en NGINX Plus en la misma condición de prueba que el gráfico anterior. El controlador Ingress basado en NGINX Plus prácticamente no introduce latencia hasta el percentil 99,99, donde comienza a subir hacia los 254 ms en el percentil 99,9999. El patrón de latencia del controlador de Ingress de la comunidad crece de manera constante hasta alcanzar una latencia de 5000 ms en el percentil 99, punto en el cual la latencia se estabiliza.
Esta tabla muestra la causa de los resultados de latencia con mayor detalle.
NGINX Open Source | COMUNIDAD | NGINX Plus | |
---|---|---|---|
Errores de conexión | 33365 | 0 | 0 |
Tiempos de espera de conexión | 309 | 8809 | 0 |
Errores de lectura | 4650 | 0 | 0 |
Con el controlador Ingress basado en código abierto NGINX, la necesidad de actualizar y recargar la configuración de NGINX para cada cambio en los puntos finales de la aplicación back-end provoca muchos errores de conexión, tiempos de espera de conexión y errores de lectura. Los errores de conexión/socket ocurren durante el breve tiempo que tarda NGINX en recargarse, cuando los clientes intentan conectarse a un socket que ya no está asignado al proceso NGINX. Los tiempos de espera de conexión se producen cuando los clientes han establecido una conexión con el controlador de Ingress, pero el punto final del backend ya no está disponible. Tanto los errores como los tiempos de espera afectan gravemente la latencia, con picos de hasta 32 segundos en el percentil 99 y nuevamente a 60 segundos en el percentil 99,99.
Con el controlador de Ingress de la comunidad, hubo 8,809 tiempos de espera de conexión debido a los cambios en los puntos finales a medida que la aplicación de back-end escalaba hacia arriba y hacia abajo. El controlador de Ingress de la comunidad utiliza código Lua para evitar recargas de configuración cuando cambian los puntos finales . Los resultados muestran que ejecutar un controlador Lua dentro de NGINX para detectar cambios en los puntos finales aborda algunas de las limitaciones de rendimiento de la versión de código abierto de NGINX, que resultan de su requisito de recargar la configuración después de cada cambio en los puntos finales. Sin embargo, todavía se producen tiempos de espera de conexión que generan una latencia significativa en percentiles más altos.
Con el controlador Ingress basado en NGINX Plus no hubo errores ni tiempos de espera: el entorno dinámico prácticamente no tuvo efecto en el rendimiento. Esto se debe a que utiliza la API NGINX Plus para actualizar dinámicamente la configuración de NGINX cuando cambian los puntos finales. Como se mencionó, la latencia más alta fue de 254 ms y ocurrió solo en el percentil 99,9999.
Los resultados de rendimiento muestran que, para eliminar por completo los tiempos de espera y los errores en un entorno de nube Kubernetes dinámico, el controlador de Ingress debe ajustarse dinámicamente a los cambios en los puntos finales del back-end sin controladores de eventos ni recargas de configuración. Basándonos en los resultados, podemos decir que la API NGINX Plus es la solución óptima para reconfigurar dinámicamente NGINX en un entorno dinámico. En nuestras pruebas, solo el controlador Ingress basado en NGINX Plus logró el rendimiento impecable en entornos de Kubernetes altamente dinámicos que necesita para mantener satisfechos a sus usuarios.
Máquina | Cloud Provider | Tipo de máquina |
---|---|---|
el cliente | AWS | m5a.4xgrande |
Nodo GKE 1 | GCP | e2-estándar-32 |
Nodo GKE 2 | GCP | e2-estándar-32 |
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:
nginx-plus
se ajustaron según fuera necesario en la configuración para NGINX Open Source.gcr.io/nginx-demos/nap-ingress:edge
), pero estaba deshabilitado (se omitió el indicador -enable-app-protect
).kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-config
namespace: nginx-ingress
data:
worker-connections: "10000"
worker-rlimit-nofile: "10240"
keepalive: "100"
keepalive-requests: "100000000"
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
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"
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
---
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 :)';}}"
---
apiVersion: v1
kind: Service
metadata:
name: app-svc
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: nginx
---
"Esta publicación de blog puede hacer referencia a productos que ya no están disponibles o que ya no reciben soporte. Para obtener la información más actualizada sobre los productos y soluciones F5 NGINX disponibles, explore nuestra familia de productos NGINX . NGINX ahora es parte de F5. Todos los enlaces anteriores de NGINX.com redirigirán a contenido similar de NGINX en F5.com.