¿Cómo el APISIX Ingress Controller soporta miles de réplicas de Pods?
Xin Rong
October 21, 2022
1,000+ Pods en Kubernetes
Los Pods son los objetos más pequeños que se pueden implementar en Kubernetes. Usamos un Pod para ejecutar una única instancia de una aplicación, por lo que solo asignamos recursos limitados al Pod debido a la escalabilidad. Sin embargo, si nos encontramos con escenarios de alto tráfico, usaremos el escalado horizontal para manejarlos.
Por ejemplo, para los minoristas en línea, el Black Friday provocará un rápido aumento del tráfico. Para manejar este escenario, debemos escalar automáticamente los servicios para manejar más tráfico. Implementaríamos más réplicas basadas en la estrategia de escalado automático, lo que resultaría en más Pods.
Cada Pod tiene una dirección IP dinámica. La API de Endpoints ha proporcionado una forma sencilla de rastrear los puntos finales de la red en Kubernetes para que podamos lograr el equilibrio de carga monitoreando oportunamente los cambios de la IP del Pod. Sin embargo, a medida que los clústeres y los Servicios de Kubernetes han crecido para manejar más tráfico hacia más Pods, por ejemplo, el escenario de Black Friday mencionado anteriormente, el número de Pods sigue aumentando y la API de Endpoints se vuelve más grande. En consecuencia, las limitaciones de la API de Endpoints se han vuelto más visibles e incluso se han convertido en el cuello de botella del rendimiento.
Para resolver el problema de limitación de la API de Endpoints, Kubernetes comienza a admitir la API de EndpointSlice en la versión v1.21. La API de EndpointSlice ayuda a resolver el problema de rendimiento de manejar enormes puntos finales de red en la API de Endpoints y tiene una excelente escalabilidad y extensibilidad.
Podemos ver directamente las diferencias entre ellos en el siguiente diagrama:
- Los cambios de Endpoints durante un pico de tráfico
- Los cambios de Endpointslices durante un pico de tráfico
En Kubernetes, ¿cómo se comunican las aplicaciones entre sí? ¿Cuáles son las diferencias específicas entre Endpoints y EndpointSlice? ¿Cuál es la relación entre Pod y Endpoints/EndpointSlice? ¿Cómo APISIX admite estas características y cómo instalarlas y usarlas? Nos enfocaremos en estas preguntas en este artículo.
Cómo acceder a aplicaciones en Kubernetes
A través de Service
Cada Pod tiene su propia dirección IP única en Kubernetes. Normalmente, Service establecerá conexiones con Pod usando selector
, proporcionará el mismo subdominio DNS y logrará el equilibrio de carga. Además, las aplicaciones dentro del clúster de Kubernetes pueden usar DNS para comunicarse entre sí.
Cuando se crea un Service, Kubernetes conectará el Service con un recurso Endpoints. Sin embargo, si el Service no ha especificado ningún selector, Kubernetes no creará automáticamente Endpoints para el Service.
¿Qué es Endpoints y cuál es su relación con Pod?
Endpoints es un objeto de recurso en Kubernetes, almacenado en etcd, e incluye referencias a un conjunto de direcciones de acceso de Pod que coinciden con un Service. Por lo tanto, cada Service solo puede tener un recurso Endpoints. El recurso Endpoints monitoreará los clústeres de Pods y se actualizará sincrónicamente una vez que cualquier Pod en el Service cambie.
- Implementar 3 réplicas de
httpbin
y verificar el estado de los Pods, incluida la información de IP
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
httpbin-deployment-fdd7d8dfb-8sxxq 1/1 Running 0 49m 10.1.36.133 docker-desktop <none> <none>
httpbin-deployment-fdd7d8dfb-bjw99 1/1 Running 4 (5h39m ago) 23d 10.1.36.125 docker-desktop <none> <none>
httpbin-deployment-fdd7d8dfb-r5nf9 1/1 Running 0 49m 10.1.36.131 docker-desktop <none> <none>
- Crear el servicio
httpbin
y verificar la condición de los endpoints de Endpoints
$ kubectl get endpoints httpbin
NAME ENDPOINTS AGE
httpbin 10.1.36.125:80,10.1.36.131:80,10.1.36.133:80 23d
De los dos ejemplos anteriores, podemos ver que cada punto final de red del recurso httpbin
en Endpoints coincide con una dirección IP de un Pod.
Desventajas de Endpoints
- Endpoints tiene un límite de almacenamiento; si cualquier recurso Endpoints tiene más de 1000 puntos finales, el controlador de Endpoints truncará los puntos finales a 1000.
- Un Service solo puede tener un recurso Endpoints, lo que significa que el recurso Endpoints necesita almacenar direcciones IP y otra información de red para cada Pod que respalda el Service correspondiente. Como resultado, el recurso de la API de Endpoints se vuelve enorme y debe actualizarse cuando un solo punto final de red en el recurso cambia. Cuando el negocio necesita cambios frecuentes de puntos finales, un recurso de API enorme se enviará entre sí, lo que afecta el rendimiento de los componentes de Kubernetes.
¿Qué es Endpointslices?
Endpointslices es una alternativa más escalable y extensible a Endpoints y ayuda a manejar el problema de rendimiento causado por el procesamiento de enormes puntos finales de red. También ayuda a proporcionar una plataforma extensible para características adicionales, como el enrutamiento topológico. Esta característica es compatible con Kubernetes v1.21.
La API de EndpointSlice fue diseñada para abordar este problema con un enfoque similar al sharding. En lugar de rastrear todas las IPs de Pods para un Service con un solo recurso Endpoints, las dividimos en múltiples EndpointSlices más pequeños.
Por defecto, el plano de control crea y gestiona EndpointSlices para que no tengan más de 100 puntos finales cada uno. Puedes configurar esto con la bandera --max-endpoints-per-slice
del kube-controller-manager, hasta 1000.
¿Por qué lo necesitamos?
Considera un escenario
Supongamos que hay un Service respaldado por 2000 Pods, lo que podría resultar en un recurso Endpoints de 1.0 MB. En el entorno de producción, si este Service tiene actualizaciones continuas o migraciones de puntos finales, los recursos de Endpoints se actualizarán con frecuencia. Piensa en las actualizaciones continuas que causarán que todos los Pods sean reemplazados debido al límite de tamaño máximo de cualquier solicitud en etcd. Kubernetes ha establecido un límite máximo de 1000 puntos finales para Endpoints. Si hay más de 1000 puntos finales, el recurso Endpoints no tendrá referencias sobre puntos finales de red adicionales.
Supongamos que el Service necesita múltiples actualizaciones continuas debido a algunas necesidades especiales; en ese caso, un enorme objeto de recurso de API se transferirá entre los componentes de Kubernetes, lo que afecta significativamente el rendimiento de los componentes de Kubernetes.
¿Qué pasa si usamos EndpointSlice?
Supongamos que hay un Service respaldado por 2000 Pods, y asignamos 100 puntos finales a cada Endpointslices en la configuración, entonces terminaremos con 20 Endpointslices. Ahora, cuando se agrega o elimina un Pod, solo un pequeño EndpointSlice necesita ser actualizado. Obviamente, es una mejora notable en la escalabilidad y la extensibilidad de la red. Supongamos que cada Service tiene un requisito de escalado automático. En ese caso, el Service implementará más Pods, y el recurso Endpoints se actualizará con frecuencia para manejar el tráfico creciente cuando haya un pico de tráfico, y la diferencia se volverá más notable. Más importante aún, ahora que todas las IPs de Pods para un Service no necesitan almacenarse en un solo recurso, no tenemos que preocuparnos por el límite de tamaño de los objetos almacenados en etcd.
Conclusión de Endpoints vs EndpointSlice
Dado que EndpointSlice es compatible desde Kubernetes v1.21, todas las conclusiones se refieren a Kubernetes v1.21.
Casos de uso de Endpoints:
- Hay una necesidad de escalado automático, pero el número de Pods es relativamente pequeño, y las transferencias de recursos no causarán un gran tráfico de red y necesidades adicionales de manejo.
- No hay necesidad de escalado automático, y el número de Pods no será enorme. Sin embargo, aunque el número de Pods sea fijo, el Service no podría omitir las actualizaciones continuas y las fallas.
Casos de uso de EndpointSlice:
- Hay una necesidad de escalado automático, y el número de Pods es enorme (cientos de Pods).
- El número de Pods es enorme (cientos de Pods) debido al límite máximo de puntos finales de Endpoints siendo 1000; cualquier Pod que tenga más de 1000 puntos finales debe usar EndpointSlice.
Práctica en APISIX Ingress Controller
APISIX Ingress Controller logra el equilibrio de carga y las comprobaciones de salud observando los cambios en el recurso Endpoints o EndpointSlice. Para admitir Kubernetes v1.16+, APISIX Ingress Controller usará Endpoints durante la instalación por defecto.
Si la versión de tu clúster es Kubernetes v1.21+, entonces necesitas especificar la bandera watchEndpointSlice=true
para admitir la característica de EndpointSlice durante la instalación de APISIX Ingress Controller.
Nota: En los clústeres con Kubernetes v1.21+, recomendamos usar la característica de Endpointslice; de lo contrario, cuando el número de Pods exceda el valor de configuración de la bandera
--max-endpoints-per-slice
, la configuración se perderá debido a que APISIX Ingress Controller está observando el objeto de recurso Endpoints.
Crear un Service respaldado con 20 réplicas de Pods
Configurar el Service de la aplicación httpbin
en Kubernetes y crear 20 réplicas de Pods.
- kubectl apply -f httpbin-deploy.yaml
# htppbin-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin-deployment
spec:
replicas: 20
selector:
matchLabels:
app: httpbin-deployment
strategy:
rollingUpdate:
maxSurge: 50%
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
labels:
app: httpbin-deployment
spec:
terminationGracePeriodSeconds: 0
containers:
- livenessProbe:
failureThreshold: 3
initialDelaySeconds: 2
periodSeconds: 5
successThreshold: 1
tcpSocket:
port: 80
timeoutSeconds: 2
readinessProbe:
failureThreshold: 3
initialDelaySeconds: 2
periodSeconds: 5
successThreshold: 1
tcpSocket:
port: 80
timeoutSeconds: 2
image: "kennethreitz/httpbin:latest"
imagePullPolicy: IfNotPresent
name: httpbin-deployment
ports:
- containerPort: 80
name: "http"
protocol: "TCP"
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
spec:
selector:
app: httpbin-deployment
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
type: ClusterIP
Proxy a través de APISIX Ingress
- Usar Helm para instalar APISIX Ingress Controller
Usar --set ingress-controller.config.kubernetes.watchEndpointSlice=true
para habilitar la compatibilidad con la característica de EndpointSlice.
helm repo add apisix https://charts.apiseven.com
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
kubectl create ns ingress-apisix
helm install apisix apisix/apisix \
--set gateway.type=NodePort \
--set ingress-controller.enabled=true \
--namespace ingress-apisix \
--set ingress-controller.config.apisix.serviceNamespace=ingress-apisix \
--set ingress-controller.config.kubernetes.watchEndpointSlice=true
- Usar el recurso CRD para hacer proxy
Los usuarios no notarán la compatibilidad con las características de Endpoints y EndpointSlice en APISIX Ingress Controller, y sus configuraciones son las mismas.
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: httpbin-route
spec:
http:
- name: rule
match:
hosts:
- httpbin.org
paths:
- /get
backends:
- serviceName: httpbin
servicePort: 80
- Al verificar el Pod de APISIX, podemos ver que el campo
nodes
del objeto upstream de APISIX contiene las direcciones IP de 20 Pods.
kubectl exec -it ${Pod for APISIX} -n ingress-apisix -- curl "http://127.0.0.1:9180/apisix/admin/upstreams" -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1'
{
"action": "get",
"count": 1,
"node": {
"key": "\/apisix\/upstreams",
"nodes": [
{
"value": {
"hash_on": "vars",
"desc": "Created by apisix-ingress-controller, DO NOT modify it manually",
"pass_host": "pass",
"nodes": [
{
"weight": 100,
"host": "10.1.36.100",
"priority": 0,
"port": 80
},
{
"weight": 100,
"host": "10.1.36.101",
"priority": 0,
"port": 80
},
{
"weight": 100,
"host": "10.1.36.102",
"priority": 0,
"port": 80
},
{
"weight": 100,
"host": "10.1.36.103",
"priority": 0,
"port": 80
},
{
"weight": 100,
"host": "10.1.36.104",
"priority": 0,
"port": 80
},
{
"weight": 100,
"host": "10.1.36.109",
"priority": 0,
"port": 80
},
{
"weight": 100,
"host": "10.1.36.92",
"priority": 0,
"port": 80
}
... // ignorar 13 nodos a continuación
// 10.1.36.118
// 10.1.36.115
// 10.1.36.116
// 10.1.36.106
// 10.1.36.113
// 10.1.36.111
// 10.1.36.108
// 10.1.36.114
// 10.1.36.107
// 10.1.36.110
// 10.1.36.105
// 10.1.36.112
// 10.1.36.117
],
"labels": {
"managed-by": "apisix-ingress-controller"
},
"type": "roundrobin",
"name": "default_httpbin_80",
"scheme": "http"
},
"key": "\/apisix\/upstreams\/5ce57b8e"
}
],
"dir": true
}
}
- Coincidir con los puntos finales de red de EndpointSlice
addressType: IPv4
apiVersion: discovery.k8s.io/v1
endpoints:
- addresses:
- 10.1.36.92
...
- addresses:
- 10.1.36.100
...
- addresses:
- 10.1.36.104
...
- addresses:
- 10.1.36.102
...
- addresses:
- 10.1.36.101
...
- addresses:
- 10.1.36.103
...
- addresses:
- 10.1.36.109
...
- addresses:
- 10.1.36.118
...
- addresses:
- 10.1.36.115
...
- addresses:
- 10.1.36.116
...
- addresses:
- 10.1.36.106
...
- addresses:
- 10.1.36.113
...
- addresses:
- 10.1.36.111
...
- addresses:
- 10.1.36.108
...
- addresses:
- 10.1.36.114
...
- addresses:
- 10.1.36.107
...
- addresses:
- 10.1.36.110
...
- addresses:
- 10.1.36.105
...
- addresses:
- 10.1.36.112
...
- addresses:
- 10.1.36.117
...
kind: EndpointSlice
metadata:
labels:
endpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.io
kubernetes.io/service-name: httpbin
name: httpbin-dkvtr
namespace: default
ports:
- name: http
port: 80
protocol: TCP
Conclusión
Este artículo presenta escenarios en los que Kubernetes necesita implementar una gran cantidad de Pods y los problemas que encontramos. También compara las diferencias entre Endpoints y EndpointSlice, e introduce la forma de habilitar la característica de EndpointSlice durante la instalación de APISIX Ingress Controller. Si la versión de tu clúster es Kubernetes v1.21+, recomendamos habilitar la característica de EndpointSlice durante la instalación de APISIX Ingress Controller. Por lo tanto, se puede evitar la pérdida de configuración y no tenemos que preocuparnos por el valor de configuración de la bandera --max-endpoints-per-slice
.