Asegurar recursos en Cuba

imagen


La gestión de recursos de clúster es siempre un tema complejo. ¿Cómo explicar la necesidad de configurar recursos para el usuario que implementa sus aplicaciones en el clúster? ¿Quizás es más fácil automatizar esto?


Descripción del problema


La gestión de recursos es una tarea importante en el contexto de la administración de clústeres de Kubernetes. Pero, ¿por qué es importante si Kubernetes hace todo el trabajo duro por usted? Porque no lo es. Kubernetes le proporciona herramientas convenientes para resolver muchos problemas ... si usa estas herramientas. Para cada pod en su clúster, puede especificar los recursos necesarios para sus contenedores. Y Kubernetes utilizará esta información para distribuir instancias de su aplicación en nodos de clúster.


Pocas personas toman en serio la gestión de recursos en Kubernetes. Esto es normal para un clúster ligeramente cargado con un par de aplicaciones estáticas. Pero, ¿qué pasa si tienes un clúster muy dinámico? ¿Dónde van y vienen las aplicaciones, dónde se crea y elimina el espacio de nombres todo el tiempo? ¿Un clúster con una gran cantidad de usuarios que pueden crear su propio espacio de nombres e implementar aplicaciones? Bueno, en este caso, en lugar de una orquestación estable y predecible, tendrás un montón de bloqueos aleatorios en las aplicaciones, ¡y a veces incluso en los componentes de Kubernetes!


Aquí hay un ejemplo de tal grupo:


imagen


Usted ve 3 hogares en el estado "Terminando". Pero esta no es la eliminación habitual de los hogares: están atascados en este estado porque el demonio contenedor en su nodo fue golpeado por algo muy hambriento de recursos.


Dichos problemas se pueden resolver manejando adecuadamente la falta de recursos, pero este no es el tema de este artículo (hay un buen artículo ), ni tampoco una bala de plata para resolver todos los problemas con los recursos.


La razón principal de tales problemas es incorrecta o la falta de gestión de recursos en el clúster. Y si este tipo de problema no es un desastre para las implementaciones, ya que crearán fácilmente una nueva en funcionamiento, entonces, para entidades como DaemonSet, o incluso más para StatefulSet, tales bloqueos serán fatales y requerirán intervención manual.


Puede tener un gran clúster con mucha CPU y memoria. Cuando ejecuta muchas aplicaciones en él sin la configuración de recursos adecuada, existe la posibilidad de que todos los pods de uso intensivo de recursos se coloquen en el mismo nodo. Lucharán por los recursos, incluso si los nodos restantes del clúster permanecen prácticamente libres.


También a menudo puede ver casos menos críticos en los que algunas de las aplicaciones se ven afectadas por sus vecinos. Incluso si estas aplicaciones "inocentes" tenían sus recursos configurados correctamente, podría venir uno errante y matarlos. Un ejemplo de tal escenario:


  1. Su aplicación solicita 4 GB de memoria, pero inicialmente solo toma 1 GB.
  2. Un vagar por debajo, sin una configuración de recursos, se asigna al mismo nodo.
  3. Vagar por debajo consume toda la memoria disponible.
  4. Su aplicación está tratando de asignar más memoria y fallas porque no hay más.

Otro caso bastante popular es la revaluación. Algunos desarrolladores hacen grandes solicitudes en manifiestos "por si acaso" y nunca usan estos recursos. El resultado es una pérdida de dinero.


Teoría de la decisión


Horror! Derecho?
Afortunadamente, Kubernetes ofrece una manera de imponer algunas restricciones en los pods al especificar configuraciones de recursos predeterminadas, así como valores mínimos y máximos. Esto se implementa utilizando el objeto LimitRange . LimitRange es una herramienta muy conveniente cuando tiene un número limitado de espacios de nombres o un control total sobre el proceso de creación. Incluso sin la configuración adecuada de los recursos, sus aplicaciones tendrán un uso limitado. Los hogares “inocentes”, debidamente sintonizados, estarán seguros y protegidos de vecinos dañinos. Si alguien implementa una aplicación codiciosa sin una configuración de recursos, esta aplicación recibirá valores predeterminados y probablemente se bloqueará. ¡Y eso es todo! La aplicación ya no arrastrará a nadie.


Por lo tanto, tenemos una herramienta para controlar y forzar la configuración de recursos para hogares, ahora parece que estamos a salvo. Entonces? En realidad no El hecho es que, como describimos anteriormente, nuestros espacios de nombres pueden ser creados por los usuarios y, por lo tanto, LimitRange puede no estar presente en dichos espacios de nombres, ya que debe crearse en cada espacio de nombres por separado. Por lo tanto, necesitamos algo no solo a nivel de espacio de nombres, sino también a nivel de clúster. Pero todavía no existe tal función en Kubernetes.


Por eso decidí escribir mi solución a este problema. Déjame presentarte - Operador de límite. Este es un operador creado sobre la base del marco Operator SDK , que utiliza el recurso personalizado ClusterLimit y ayuda a proteger todas las aplicaciones "inocentes" en el clúster. Con este operador, puede controlar los valores predeterminados y los límites de recursos para todos los espacios de nombres utilizando la cantidad mínima de configuración. También le permite elegir exactamente dónde aplicar la configuración utilizando namespaceSelector.


Ejemplo de ClusterLimit
apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: default-limit spec: namespaceSelector: matchLabels: limit: "limited" limitRange: limits: - type: Container max: cpu: "800m" memory: "1Gi" min: cpu: "100m" memory: "99Mi" default: cpu: "700m" memory: "900Mi" defaultRequest: cpu: "110m" memory: "111Mi" - type: Pod max: cpu: "2" memory: "2Gi" 

Con esta configuración, el operador creará un LimitRange solo en el espacio de nombres con el limit: limited etiqueta limit: limited . Esto será útil para proporcionar restricciones más estrictas en un grupo específico de espacios de nombres. Si namespaceSelector no se especifica, el operador aplicará un LimitRange a todos los espacios de nombres. Si desea configurar LimitRange manualmente para un espacio de nombres específico, puede usar la anotación "limit.myafq.com/unlimited": true esto le indicará al operador que omita este espacio de nombres y no cree LimitRange automáticamente.


Script de ejemplo para usar el operador:


  • Cree ClusterLimit predeterminado con restricciones liberales y sin espacio de nombresSelector: se aplicará en todas partes.
  • Para un conjunto de espacios de nombres con aplicaciones livianas, cree un ClusterLimit adicional, más riguroso, con namespaceSelector. Coloque etiquetas en estos espacios de nombres en consecuencia.
  • En un espacio de nombres con aplicaciones muy intensivas en recursos, coloque la anotación "limit.myafq.com/unlimited": verdadero y configure LimitRange manualmente con límites mucho más amplios que los especificados en el ClusteLimit predeterminado.

Lo importante que debe saber sobre varios LimitRange en un espacio de nombres:
Cuando se crea un sub en un espacio de nombres con varios LimitRange, se tomarán los valores predeterminados más grandes para configurar sus recursos. Pero los valores máximos y mínimos se verificarán de acuerdo con el límite más estricto.

Ejemplo práctico


El operador rastreará todos los cambios en todos los espacios de nombres, ClusterLimits, child LimitRanges e iniciará la coordinación del estado del clúster con cualquier cambio en los objetos monitoreados. Veamos cómo funciona en la práctica.


Para comenzar, cree bajo sin ninguna restricción:


kubectl ejecutar / obtener salida
 ❯() kubectl run --generator=run-pod/v1 --image=bash bash pod/bash created ❯() kubectl get pod bash -o yaml apiVersion: v1 kind: Pod metadata: labels: run: bash name: bash namespace: default spec: containers: - image: bash name: bash resources: {} 

Nota: parte de la salida del comando se ha omitido para simplificar el ejemplo.


Como puede ver, el campo "recursos" está vacío, lo que significa que este sub se puede iniciar en cualquier lugar.
Ahora crearemos el ClusterLimit predeterminado para todo el clúster con valores bastante liberales:


default-limit.yaml
 apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: default-limit spec: limitRange: limits: - type: Container max: cpu: "4" memory: "5Gi" default: cpu: "700m" memory: "900Mi" defaultRequest: cpu: "500m" memory: "512Mi" 

Y también más estricto para un subconjunto de espacios de nombres:


restrictive-limit.yaml
 apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: restrictive-limit spec: namespaceSelector: matchLabels: limit: "restrictive" limitRange: limits: - type: Container max: cpu: "800m" memory: "1Gi" default: cpu: "100m" memory: "128Mi" defaultRequest: cpu: "50m" memory: "64Mi" - type: Pod max: cpu: "2" memory: "2Gi" 

Luego cree los espacios de nombres y las vainas en ellos para ver cómo funciona.
Espacio de nombres normal con restricción predeterminada:


 apiVersion: v1 kind: Namespace metadata: name: regular 

Y un espacio de nombres un poco más limitado, según la leyenda, para aplicaciones ligeras:


 apiVersion: v1 kind: Namespace metadata: labels: limit: "restrictive" name: lightweight 

Si observa los registros del operador inmediatamente después de crear el espacio de nombres, puede encontrar algo así debajo del spoiler:


registros de operador
 {...,"msg":"Reconciling ClusterLimit","Triggered by":"/regular"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"regular","LimitRange":"default-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"regular","Name":"default-limit"} {...,"msg":"Reconciling ClusterLimit","Triggered by":"/lightweight"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"lightweight","LimitRange":"default-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"lightweight","Name":"default-limit"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"lightweight","LimitRange":"restrictive-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"lightweight","Name":"restrictive-limit"} 

La parte faltante del registro contiene 3 campos más que no son relevantes en este momento.


Como puede ver, la creación de cada espacio de nombres comenzó la creación de un nuevo LimitRange. Un espacio de nombres más limitado tiene dos LimitRange: predeterminado y más estricto.


Ahora intentemos crear un par de hogares en estos espacios de nombres.


kubectl ejecutar / obtener salida
 ❯() kubectl run --generator=run-pod/v1 --image=bash bash -n regular pod/bash created ❯() kubectl get pod bash -o yaml -n regular apiVersion: v1 kind: Pod metadata: annotations: kubernetes.io/limit-ranger: 'LimitRanger plugin set: cpu, memory request for container bash; cpu, memory limit for container bash' labels: run: bash name: bash namespace: regular spec: containers: - image: bash name: bash resources: limits: cpu: 700m memory: 900Mi requests: cpu: 500m memory: 512Mi 

Como puede ver, a pesar del hecho de que no hemos cambiado la forma en que se crea el pod, el campo de recursos ahora está lleno. También puede notar la anotación creada automáticamente por LimitRanger.


Ahora cree bajo en un espacio de nombres ligero:


kubectl ejecutar / obtener salida
 ❯() kubectl run --generator=run-pod/v1 --image=bash bash -n lightweight pod/bash created ❯() kubectl get pods -n lightweight bash -o yaml apiVersion: v1 kind: Pod metadata: annotations: kubernetes.io/limit-ranger: 'LimitRanger plugin set: cpu, memory request for container bash; cpu, memory limit for container bash' labels: run: bash name: bash namespace: lightweight spec: containers: - image: bash name: bash resources: limits: cpu: 700m memory: 900Mi requests: cpu: 500m memory: 512Mi 

Tenga en cuenta que los recursos en el hogar son los mismos que en el ejemplo anterior. Esto se debe a que, en el caso de varios LimitRange, se utilizarán valores predeterminados menos estrictos al crear pods. Pero, ¿por qué entonces necesitamos un LimitRange más limitado? Se utilizará para verificar los valores máximos y mínimos de los recursos. Para demostrarlo, haremos que nuestro ClusterLimit limitado sea aún más limitado:


restrictive-limit.yaml
 apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: restrictive-limit spec: namespaceSelector: matchLabels: limit: "restrictive" limitRange: limits: - type: Container max: cpu: "200m" memory: "250Mi" default: cpu: "100m" memory: "128Mi" defaultRequest: cpu: "50m" memory: "64Mi" - type: Pod max: cpu: "2" memory: "2Gi" 

Presta atención a la sección:


 - type: Container max: cpu: "200m" memory: "250Mi" 

Ahora hemos configurado 200m de CPU y 250Mi de memoria como máximo para el contenedor en el hogar. Y ahora nuevamente, intente crear debajo de:


 ❯() kubectl run --generator=run-pod/v1 --image=bash bash -n lightweight Error from server (Forbidden): pods "bash" is forbidden: [maximum cpu usage per Container is 200m, but limit is 700m., maximum memory usage per Container is 250Mi, but limit is 900Mi.] 

Nuestro sub tiene valores grandes establecidos por el LimitRange predeterminado y no pudo iniciarse porque no pasó la verificación de recursos máximos permitidos.




Este fue un ejemplo del uso del operador de límite. Pruébelo usted mismo y juegue con ClusterLimit en su instancia local de Kubernetes.


En el repositorio del operador de límite de GitHub , puede encontrar el manifiesto para la implementación del operador, así como el código fuente. Si desea expandir la funcionalidad del operador, ¡las misiones de extracción y funciones son bienvenidas!


Conclusión


La gestión de recursos en Kubernetes es crítica para la estabilidad y confiabilidad de sus aplicaciones. Personalice sus recursos de hogar siempre que sea posible. Y use LimitRange para asegurarse contra casos cuando no es posible. Automatice la creación de LimitRange utilizando el operador de límite.


Siga estos consejos, y su clúster siempre estará a salvo del caos sin recursos de hogares extraviados.

Source: https://habr.com/ru/post/473908/


All Articles