Kubernetes: Kubelet / Kubernetes debería funcionar con Swap Enabled

Creado en 6 oct. 2017  ·  94Comentarios  ·  Fuente: kubernetes/kubernetes

¿Es este un INFORME DE ERROR o una SOLICITUD DE FUNCIÓN? :

Descomente solo uno, déjelo en su propia línea:

/ tipo de error
/ tipo de característica

Que paso :

Kubelet / Kubernetes 1.8 no funciona con Swap habilitado en máquinas Linux.

Encontré este problema original https://github.com/kubernetes/kubernetes/issues/31676
Este PR https://github.com/kubernetes/kubernetes/pull/31996
y el último cambio que lo habilitó por defecto https://github.com/kubernetes/kubernetes/commit/71e8c8eba43a0fade6e4edfc739b331ba3cc658a

Si Kubernetes no sabe cómo manejar el desalojo de memoria cuando Swap está habilitado, debería encontrar una manera de hacerlo, pero sin pedir que se deshaga del swap.

Siga el Capítulo 11 de kernel.org

El lector casual puede pensar que con una cantidad suficiente de memoria, el intercambio es innecesario, pero esto nos lleva a la segunda razón. Una cantidad significativa de las páginas a las que hace referencia un proceso al principio de su vida solo se pueden usar para la inicialización y luego nunca se vuelven a usar. Es mejor intercambiar esas páginas y crear más búferes de disco que dejarlas residentes y sin usar.

En caso de ejecutar muchas aplicaciones de nodo / java, he visto que siempre se intercambian muchas páginas, solo porque ya no se usan.

Qué esperabas que sucediera :

Kubelet / Kubernetes debería funcionar con Swap habilitado. Creo que en lugar de deshabilitar el intercambio y no dar a los usuarios opciones, kubernetes debería admitir más casos de uso y varias cargas de trabajo, algunas de ellas pueden ser aplicaciones que pueden depender de cachés.

No estoy seguro de cómo Kubernetes decidió qué matar con el desalojo de memoria, pero considerando que Linux tiene esta capacidad, ¿tal vez debería alinearse con cómo Linux hace eso? https://www.kernel.org/doc/gorman/html/understand/understand016.html

Sugeriría revertir el cambio por fallar cuando el intercambio está habilitado y revisar cómo funciona el desalojo de memoria actualmente en kubernetes. El intercambio puede ser importante para algunas cargas de trabajo.

Cómo reproducirlo (de la forma más mínima y precisa posible) :

Ejecute kubernetes / kublet con la configuración predeterminada en linux box

¿Algo más que necesitemos saber? :

Medio ambiente :

  • Versión de Kubernetes (use kubectl version ):
  • Proveedor de nube o configuración de hardware **:
  • SO (por ejemplo, de / etc / os-release):
  • Kernel (por ejemplo, uname -a ):
  • Instalar herramientas:
  • Otros:

/ sig nodo
cc @mtaufen @vishh @derekwaynecarr @dims

kinfeature sinode

Comentario más útil

¿No admite el intercambio de forma predeterminada? Me sorprendió escuchar esto: ¿pensé que Kubernetes estaba listo para el horario de máxima audiencia? Swap es una de esas características.

Esto no es realmente opcional en la mayoría de los casos de uso abiertos, es la forma en que el ecosistema Unix está diseñado para ejecutarse, con VMM cambiando las páginas inactivas.

Si la opción es sin intercambio o sin límites de memoria, elegiré mantener el intercambio cualquier día y simplemente activar más hosts cuando empiece a paginar, y aún así saldré ahorrando dinero.

¿Alguien puede aclarar - el problema con el desalojo de la memoria es solo un problema si está usando límites de memoria en la definición de pod, pero por lo demás, está bien?

Sería bueno trabajar en un mundo en el que tengo control sobre la forma en que funciona la memoria de una aplicación para no tener que preocuparme por el mal uso de la memoria, pero la mayoría de las aplicaciones tienen mucho espacio de memoria inactiva.

Honestamente, creo que este movimiento reciente para ejecutar servidores sin intercambio es impulsado por los proveedores de PaaS que intentan coaccionar a las personas a instancias de memoria más grandes, sin tener en cuenta ~ 40 años de diseño de administración de memoria. La realidad es que el kernel es realmente bueno para saber qué páginas de memoria están activas o no, déjelo hacer su trabajo.

Todos 94 comentarios

El soporte para el intercambio no es trivial. Las cápsulas garantizadas nunca deberían requerir intercambio. Los pods expandibles deben tener sus solicitudes satisfechas sin necesidad de intercambio. Las cápsulas BestEffort no tienen garantía. El kubelet en este momento carece de la inteligencia para proporcionar la cantidad correcta de comportamiento predecible aquí en todos los pods.

Discutimos este tema en la administración de recursos cara a cara a principios de este año. No estamos muy interesados ​​en abordar esto a corto plazo en relación con las ganancias que podría obtener. Preferiríamos mejorar la confiabilidad en la detección de presión y optimizar los problemas relacionados con la latencia antes de intentar optimizar para el intercambio, pero si esta es una prioridad más alta para usted, nos encantaría su ayuda.

/ tipo de característica

@derekwaynecarr ¡ gracias por la explicación! Fue difícil obtener información / documentación sobre por qué el intercambio debería estar deshabilitado para kubernetes. Esta fue la razón principal por la que abrí este tema. En este punto, no tengo una alta prioridad para este tema, solo quería estar seguro de que tenemos un lugar donde se puede discutir.

Hay más contexto en la discusión aquí: https://github.com/kubernetes/kubernetes/issues/7294 : tener un intercambio disponible tiene interacciones muy extrañas y malas con los límites de memoria. Por ejemplo, un contenedor que alcanza su límite de memoria _entonces_ comenzaría a extenderse hacia el intercambio (esto parece estar arreglado ya que f4edaf2b8c32463d6485e2c12b7fd776aef948bc - no se les permitirá usar ningún intercambio, ya sea que esté allí o no).

Este es un caso de uso crítico para nosotros también. Tenemos un trabajo cron que ocasionalmente se ejecuta en un alto uso de memoria (> 30 GB) y no queremos asignar de forma permanente nodos de más de 40 GB. Además, dado que corremos en tres zonas (GKE), esto asignará 3 de tales máquinas (1 en cada zona). Y esta configuración debe repetirse en más de 3 instancias de producción y más de 10 instancias de prueba, lo que hace que este K8 sea muy caro de usar. Nos vemos obligados a tener más de 25 nodos de 48 GB, lo que supone un coste enorme.
¡Habilite el intercambio !.

Una solución para aquellos que realmente quieren un intercambio. Si tu

  • iniciar kubelet con --fail-swap-on=false
  • agregue swap a sus nodos
  • Los contenedores que no especifiquen un requisito de memoria podrán utilizar de forma predeterminada toda la memoria de la máquina, incluido el intercambio.

Eso es lo que estamos haciendo. O al menos, estoy bastante seguro de que lo es, en realidad no lo implementé personalmente, pero eso es lo que deduzco.

Esto solo podría ser una estrategia viable si ninguno de sus contenedores especifica alguna vez un requisito de memoria explícito ...

Ejecutamos en GKE y no conozco una forma de configurar esas opciones.

Estaría abierto a considerar la adopción de zswap si alguien puede evaluar las implicaciones de los desalojos de memoria en kubelet.

Estoy ejecutando Kubernetes en mi computadora portátil Ubuntu local y con cada reinicio tengo que apagar el intercambio. También tengo que preocuparme por no acercarme al límite de memoria ya que el intercambio está desactivado.

¿Hay alguna manera de que con cada reinicio no tenga que desactivar el intercambio como un cambio de archivo de configuración en la instalación existente?

No necesito intercambio en los nodos que se ejecutan en el clúster.

Son solo otras aplicaciones en mi computadora portátil, además del clúster de desarrollo local de Kubernetes, que necesitan intercambio para encenderse.

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"9", GitVersion:"v1.9.2", GitCommit:"5fa2db2bd46ac79e5e00a4e6ed24191080aa463b", GitTreeState:"clean", BuildDate:"2018-01-18T10:09:24Z", GoVersion:"go1.9.2", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"9", GitVersion:"v1.9.2", GitCommit:"5fa2db2bd46ac79e5e00a4e6ed24191080aa463b", GitTreeState:"clean", BuildDate:"2018-01-18T09:42:01Z", GoVersion:"go1.9.2", Compiler:"gc", Platform:"linux/amd64"}

Ahora mismo la bandera no funciona.

# systemctl restart kubelet --fail-swap-on=false
systemctl: unrecognized option '--fail-swap-on=false'

Establezca la siguiente marca de Kubelet: --fail-swap-on=false

El martes 30 de enero de 2018 a la 1:59 p.m., icewheel [email protected] escribió:

Estoy ejecutando Kubernetes en mi computadora portátil Ubuntu local y con cada reinicio
tengo que desviar swap. También tengo que preocuparme por no acercarme a la memoria
límite si se cambia si está desactivado.

¿Hay alguna forma con cada reinicio de que no tenga que desactivar el intercambio como algunos
cambio de archivo de configuración en la instalación existente?

No necesito intercambio en los nodos que se ejecutan en el clúster.

Son solo otras aplicaciones en mi computadora portátil además de Kubernetes Local Dev
clúster que necesita intercambio para activarse.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/kubernetes/kubernetes/issues/53533#issuecomment-361748518 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AA3JwQdj2skL2dSqEVyV46iCllzT-sOVks5tP5DSgaJpZM4PwnD5
.

-
Michael Taufen
Google SWE

gracias @mtaufen

Para los sistemas que inician el clúster por usted (como terraform), es posible que deba modificar el archivo de servicio

Esto funcionó para mi

sudo sed -i '/kubelet-wrapper/a \ --fail-swap-on=false \\\' /etc/systemd/system/kubelet.service

¿No admite el intercambio de forma predeterminada? Me sorprendió escuchar esto: ¿pensé que Kubernetes estaba listo para el horario de máxima audiencia? Swap es una de esas características.

Esto no es realmente opcional en la mayoría de los casos de uso abiertos, es la forma en que el ecosistema Unix está diseñado para ejecutarse, con VMM cambiando las páginas inactivas.

Si la opción es sin intercambio o sin límites de memoria, elegiré mantener el intercambio cualquier día y simplemente activar más hosts cuando empiece a paginar, y aún así saldré ahorrando dinero.

¿Alguien puede aclarar - el problema con el desalojo de la memoria es solo un problema si está usando límites de memoria en la definición de pod, pero por lo demás, está bien?

Sería bueno trabajar en un mundo en el que tengo control sobre la forma en que funciona la memoria de una aplicación para no tener que preocuparme por el mal uso de la memoria, pero la mayoría de las aplicaciones tienen mucho espacio de memoria inactiva.

Honestamente, creo que este movimiento reciente para ejecutar servidores sin intercambio es impulsado por los proveedores de PaaS que intentan coaccionar a las personas a instancias de memoria más grandes, sin tener en cuenta ~ 40 años de diseño de administración de memoria. La realidad es que el kernel es realmente bueno para saber qué páginas de memoria están activas o no, déjelo hacer su trabajo.

Esto también tiene el efecto de que si la memoria se agota en el nodo, potencialmente se bloqueará por completo, requiriendo un reinicio del nodo, en lugar de simplemente ralentizarse y recuperarse un poco más tarde.

Los problemas se vuelven obsoletos después de 90 días de inactividad.
Marque el problema como nuevo con /remove-lifecycle stale .
Los problemas obsoletos se pudren después de 30 días adicionales de inactividad y finalmente se cierran.

Si es seguro cerrar este problema ahora, hágalo con /close .

Envíe sus comentarios a sig-testing, kubernetes / test-infra y / o fejta .
/ ciclo de vida obsoleto

Tengo una gran cantidad de lecturas de disco en los nodos de mi clúster ( K8s Version - v1.11.2 ). ¿Puede deberse a la desactivación de la memoria de intercambio?

https://stackoverflow.com/questions/51988566/high-number-of-disk-reads-in-kubernetes-nodes

@srevenant En el mundo de los clústeres, la RAM del otro nodo es el nuevo intercambio. Dicho esto, ejecuto dos instancias K8 de un nodo donde el intercambio tiene sentido. Pero este no es el caso de uso típico de los K8.

@srevenant Estoy completamente de acuerdo contigo, SWAP se usa en Unix y Linux por defecto desde que nacieron, creo que no vi una aplicación durante 15 años de trabajo en Linux que solicite que SWAP esté apagado.
El problema SWAP siempre está activado de forma predeterminada cuando instalamos cualquier distribución de Linux, por lo que debo activarlo antes de instalar K8 y eso fue una sorpresa.
El kernel de Linux sabe bien cómo administrar SWAP para aumentar el rendimiento de los servidores, especialmente temporalmente cuando el servidor está a punto de alcanzar el límite de RAM.
¿Significa esto que debo apagar SWAP para que los K8 funcionen bien?

Tengo interés en hacer que esto funcione, y tengo las habilidades y una serie de máquinas para probar. Si quisiera contribuir, ¿cuál es el mejor lugar para comenzar?

@superdave, por favor,

Estoy a favor de habilitar el intercambio en las vainas de Kubernete correctamente. Realmente no tiene sentido no tener intercambio, ya que casi todos los contenedores son instancias personalizadas de Linux y, por lo tanto, admiten el intercambio de forma predeterminada.
Es comprensible que la función sea compleja de implementar, pero ¿desde cuándo eso nos impidió seguir adelante?

Debo estar de acuerdo en que los problemas de intercambio deben resolverse en Kubernetes, ya que deshabilitar el intercambio provoca una falla en el nodo cuando se agota la memoria en el nodo. Por ejemplo, si tiene 3 nodos de trabajo (20 GB de RAM cada uno) y un nodo se cae porque se alcanza el límite de RAM, otros 2 nodos de trabajo también bajarán después de transferirles todos los pods en ese tiempo.

Puede evitar esto configurando las solicitudes de memoria de acuerdo con la
necesidad de la aplicación.

Si un tercio de la memoria de su aplicación está en 2 órdenes de magnitud
almacenamiento más lento, ¿podrá realizar algún trabajo útil?

El miércoles 26 de septiembre de 2018 a las 6:51 a.m., vasicvuk [email protected] escribió:

Debo estar de acuerdo en que los problemas de intercambio deben resolverse en Kubernetes ya que
deshabilitar el intercambio provoca una falla en el nodo cuando se agota la memoria en el nodo. Para
ejemplo, si tiene 3 nodos de trabajo (20 GB de RAM cada uno) y un nodo va
hacia abajo porque se alcanza el límite de RAM 2 otros nodos trabajadores también irán
hacia abajo después de transferirles todas las vainas en ese tiempo.

-
Estás recibiendo esto porque hiciste un comentario.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/kubernetes/kubernetes/issues/53533#issuecomment-424604731 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AAICBqZApBscFl5aNA4IcYvlxcvPA88Tks5ueyPlgaJpZM4PwnD5
.

@matthiasr Puede hacerlo cuando tenga entre 10 y 50 servicios. Pero cuando tiene Cluster ejecutando más de 200 servicios y la mitad de ellos se implementan utilizando gráficos oficiales de Helm sin ninguna solicitud de memoria en ellos, sus manos están en marea.

Pero entonces, ¿la memoria faltante no requiere que se aborde el problema?

@matthiasr en muchos casos, la memoria una vez asignada al proceso solo se usó una vez o nunca se usó realmente. Esos son casos válidos y no son pérdidas de memoria. Cuando haya intercambiado, esas páginas finalmente se intercambian y es posible que nunca se intercambien nuevamente, sin embargo, libera la memoria RAM rápida para un mejor uso.

Desactivar el intercambio tampoco es una buena forma de garantizar la capacidad de respuesta. A menos que fije archivos en la memoria (una capacidad que los K8 deberían tener para los ejecutables, al menos), el kernel aún intercambiará todas y cada una de las páginas respaldadas por archivos en respuesta a la presión de la memoria, o incluso simplemente a la falta de uso.

Tener el intercambio habilitado no cambia notablemente el comportamiento del kernel. Todo lo que hace es proporcionar un espacio para intercambiar páginas anónimas o páginas modificadas cargadas desde archivos mapeados con COW.

No puede desactivar el intercambio por completo, por lo que K8 debe sobrevivir a su existencia, esté o no habilitado el caso especial de intercambio de memoria anónimo.

Eso hace que esto sea un error: no es compatible con una función del kernel que en realidad no se puede desactivar.

@Baughn

el kernel seguirá intercambiando todas y cada una de las páginas respaldadas por archivos en respuesta a la presión de la memoria, o incluso simplemente a la falta de uso. Tener el intercambio habilitado no cambia notablemente el comportamiento del kernel.

No puede desactivar el intercambio por completo,

¿Puede proporcionar alguna referencia sobre esto para que yo pueda educarme?

A menos que ancle archivos en la memoria (una capacidad que los K8 deberían tener para ejecutables, al menos),

¿Cuál es la capacidad que desea que utilice el k8s? Si un binario es estático, simplemente copiarlo en un tmpfs en el pod debería ayudar con la latencia de paginación.

@adityakali ¿ alguna idea sobre el impacto del intercambio en el kernel cuando el intercambio está desactivado?

¿Puede proporcionar alguna referencia sobre esto para que yo pueda educarme?

Como todos los sistemas operativos de memoria virtual modernos, Linux demanda páginas ejecutables del disco a la memoria. Bajo presión de memoria, el kernel intercambia el código ejecutable real de su programa hacia / desde el disco como cualquier otra página de memoria (el "intercambio" es simplemente un descarte porque es de solo lectura, pero el mecanismo es el mismo), y lo harán volver a buscar si es necesario. Lo mismo ocurre con cosas como las constantes de cadena, que normalmente están mapeadas como de solo lectura de otras secciones del archivo ejecutable. Otros archivos mmapped (comunes para cargas de trabajo de tipo base de datos) también se intercambian dentro y fuera de sus archivos de respaldo relevantes (que requieren una escritura real si se han modificado) en respuesta a la presión de la memoria. El _único_ intercambio que deshabilita al "deshabilitar el intercambio" es "memoria anónima" - memoria que _no_ está asociada con un archivo (los mejores ejemplos son las estructuras de datos "pila" y "montón").

Hay muchos detalles que estoy omitiendo en la descripción anterior, por supuesto. En particular, los ejecutables pueden "bloquear" porciones de su espacio de memoria en la memoria RAM usando la familia de llamadas al sistema mlock , hacer cosas inteligentes a través de madvise() , se vuelve complicado cuando las mismas páginas son compartidas por múltiples procesos (por ejemplo, libc.so), etc. Me temo que no tengo un puntero más útil para leer más que esas páginas de manual, o cosas generales como libros de texto o fuentes del núcleo de Linux / docs / mailing-list.

Entonces, un efecto práctico de lo anterior es que a medida que su proceso se acerca a su límite de memoria, el kernel se verá obligado a desalojar porciones de _code_ y constantes de RAM. La próxima vez que se requiera ese bit de código o valor constante, el programa se detendrá, esperando recuperarlo del disco (y desalojar algo más). Por lo tanto, incluso con "intercambio deshabilitado", todavía obtiene la misma degradación cuando su conjunto de trabajo excede la memoria disponible.

Antes de que la gente lea lo anterior y comience a llamar para bloquear todo en la memoria o copiar todo en una unidad de memoria ram como parte de la caza de brujas anti-swap, me gustaría repetir que el recurso real de interés aquí es el tamaño del conjunto de trabajo, no el tamaño total. . Un programa que trabaja linealmente a través de gigabytes de datos en RAM puede que solo funcione en una ventana estrecha de esos datos a la vez. Este programa hipotético funcionaría bien con una gran cantidad de intercambio y un límite de RAM pequeño, y sería terriblemente ineficiente bloquearlo todo en una RAM real. Como ha aprendido de la explicación anterior, esto es exactamente lo mismo que un programa que tiene una gran cantidad de _code_ pero solo ejecuta una pequeña cantidad en un momento particular.

Mi último ejemplo personal del mundo real de algo así es vincular los ejecutables de kubernetes. Actualmente (irónicamente) no puedo compilar kubernetes en mi clúster de kubernetes porque la etapa de enlace de go requiere varios gigabytes de memoria virtual (anónima), aunque el conjunto de trabajo es mucho más pequeño.

Para explicar realmente el punto de "se trata de un conjunto de trabajo, no de memoria virtual", considere un programa que hace muchas E / S de archivos regulares y nada que ver con mmap. Si tiene suficiente memoria RAM, el kernel almacenará en memoria caché las estructuras de directorio y los datos de archivo utilizados repetidamente en la memoria RAM y evitará ir al disco, y permitirá que las escrituras estallen en la memoria RAM temporalmente para optimizar la escritura del disco. Incluso un programa "ingenuo" como este se degradará de velocidades de RAM a velocidades de disco dependiendo del tamaño del conjunto de trabajo frente a la RAM disponible. Cuando anclas algo en la memoria RAM innecesariamente (por ejemplo, usando mlock o deshabilitando el intercambio), evitas que el kernel use esa página de la memoria RAM física para algo realmente útil y (si no tienes suficiente memoria RAM para el conjunto de trabajo) simplemente movió la E / S del disco a un lugar aún más caro.

@superdave : Yo también estoy interesado en mejorar el status quo aquí. Por favor, inclúyanme si quieren que otro par de ojos revise un documento o las manos en un teclado.

Mi último ejemplo personal del mundo real de algo así es vincular los ejecutables de kubernetes. Actualmente (irónicamente) no puedo compilar kubernetes en mi clúster de kubernetes porque la etapa de enlace de go requiere varios gigabytes de memoria virtual (anónima), aunque el conjunto de trabajo es mucho más pequeño.

@superdave : Yo también estoy interesado en mejorar el status quo aquí. Por favor, inclúyanme si quieren que otro par de ojos revise un documento o las manos en un teclado.

¡Buen resumen del tema que nos ocupa! La paliza de intercambio es el problema clave aquí, de hecho; es algo que espero abordar de manera racional. Me parece, aunque realmente no he tenido tiempo para pensarlo lo suficiente, que algún tipo de métrica de actividad de intercambio (entradas / salidas de página en un período de tiempo determinado, tal vez con una regla de percentil para evitar saltar demasiado ansioso si un proceso repentinamente aumenta temporalmente) podría ser una buena manera de evaluar las cosas para el desalojo cuando el intercambio está habilitado. Hay una serie de métricas, y sospecho que nos gustaría ofrecer muchas perillas para jugar y evaluar cuidadosamente los casos de uso probables. También sospecho que poder instrumentar pods para interacciones de memoria virtual debería ayudar a las personas a sintonizar mejor; No estoy lo suficientemente familiarizado con lo que ya se ofrece para decir lo que hay ahora, pero sospecho que lo voy a averiguar.

Tampoco estoy lo suficientemente familiarizado con los controles que tenemos para saber qué tan bien podemos controlar el comportamiento de intercambio en vainas / contenedores individuales; Sería útil poder "renizar" las cosas para su retención o intercambio, pero los desarrolladores obviamente siempre son libres de intentar mlock () cuando es absolutamente necesario garantizar que las cosas serán residentes de todos modos.

En cualquier caso, sí, absolutamente quiero avanzar en esto. Últimamente he estado abrumado en el trabajo (manejando algunos problemas de OOM con nuestros propios microservicios bajo k8s que se habrían beneficiado de poder intercambiar bajo carga porque el 99% de las veces no necesitan gigas de RAM a menos que alguien haga una cantidad desaconsejadamente grande solicitud), pero siéntase libre de seguir conmigo al respecto. Nunca antes había participado en el proceso KEP, así que voy a ser bastante ecológico, pero en estos días trabajo mucho mejor con interrupciones que con una encuesta. :-)

Me gustaría señalar que zram funciona a cuestas en intercambios. Si no hay intercambios en k8, entonces no hay compresión de memoria, que es algo que la mayoría de los sistemas operativos que no son Linux han habilitado de forma predeterminada (cue Windows, MacOS).

Tenemos una instancia de Ubuntu en k8 que ejecuta un gran trabajo por lotes todas las noches, lo que consume mucha memoria. Como la carga de trabajo no está predeterminada, nos vemos obligados a asignar (costosamente) 16 GB al nodo independientemente de su consumo de memoria real para evitar OOM. Con la compresión de memoria en nuestro servidor de desarrollo local, el trabajo alcanza un máximo de solo 3 GB. De lo contrario, durante el día, solo ocupa 1 GB de memoria. Prohibir los intercambios y, por lo tanto, la compresión de la memoria es un movimiento bastante tonto.

Creo que la principal preocupación aquí es probablemente el aislamiento. Una máquina típica puede albergar una tonelada de pods, y si la memoria se agota, podrían comenzar a intercambiarse y destruir por completo el rendimiento entre sí. Si no hay intercambio, el aislamiento es mucho más fácil.

Creo que la principal preocupación aquí es probablemente el aislamiento. Una máquina típica puede albergar una tonelada de pods, y si la memoria se agota, podrían comenzar a intercambiarse y destruir por completo el rendimiento entre sí. Si no hay intercambio, el aislamiento es mucho más fácil.

Pero como se explicó anteriormente, deshabilitar el intercambio no nos compra nada aquí. De hecho, ya que aumenta la presión de memoria en general, puede forzar al núcleo descarte partes del conjunto de trabajo cuando de otro modo podría haber intercambiado datos no utilizados - por lo que empeora la situación.

Permitir el intercambio, por sí solo, debería mejorar el aislamiento.

Pero te compra mucho, si ejecutas las cosas de la manera que se supone que debes hacerlo (y la forma en que Google ejecuta las cosas en Borg): todos los contenedores deben especificar el límite de memoria superior. Borg se aprovecha de la infraestructura de Google y aprende los límites si así lo desea (a partir del uso pasado de recursos y el comportamiento OOM), pero no obstante, existen límites.

De hecho, estoy un poco desconcertado de que la gente de K8S haya permitido que el límite de memoria sea opcional. A diferencia de la CPU, la presión de la memoria tiene un efecto muy no lineal en el rendimiento del sistema, como atestiguará cualquiera que haya visto un sistema bloqueado por completo debido al intercambio. Realmente debería requerirlo de forma predeterminada y le dará una advertencia si elige deshabilitarlo.

Pero te compra mucho, si ejecutas las cosas de la manera que se supone que debes hacerlo (y la forma en que Google ejecuta las cosas en Borg): todos los contenedores deben especificar el límite de memoria superior. Borg se aprovecha de la infraestructura de Google y aprende los límites si así lo desea (a partir del uso pasado de recursos y el comportamiento OOM), pero no obstante, existen límites.

De hecho, estoy un poco desconcertado de que la gente de K8S haya permitido que el límite de memoria sea opcional. A diferencia de la CPU, la presión de la memoria tiene un efecto muy no lineal en el rendimiento del sistema, como atestiguará cualquiera que haya visto un sistema bloqueado por completo debido al intercambio. Realmente debería requerirlo de forma predeterminada y le dará una advertencia si elige deshabilitarlo.

Creo que el problema que esto no aborda es que el límite superior es variable y no siempre se conoce para algunos procesos. El problema que estoy tratando se centra específicamente en el uso de k8s para administrar los nodos del renderizador de modelos 3D. Dependiendo de los activos para el modelo y la escena que se está renderizando, la cantidad de RAM requerida puede variar bastante, y aunque la mayoría de los renderizados serán pequeños, el hecho de que _algunos_ puedan ser enormes significa que nuestras solicitudes y límites tienen que reservar mucha más memoria. de lo que realmente necesitamos el 90% del tiempo para evitar OOM, en lugar de que el pod exceda ocasionalmente el límite configurado y pueda extenderse al espacio de intercambio.

Sí, y en ese caso, establecería su límite superior en "Ninguno" o algo por el estilo. Mi punto es que no debería ser el predeterminado. Ponerlo en cero derrota por completo cualquier tipo de programación inteligente de carga de trabajo, ya que el maestro simplemente no sabe el tamaño del "elemento" (trabajo) del que se trata de poner en una "mochila" (kubelet).

El problema aquí no es que su trabajo se desbordará para intercambiar, es que todos los demás trabajos que se ejecutan en ese nodo también lo serán. Y a algunos (¿la mayoría?) No les gustará en absoluto.

Los programas en Borg están escritos para ser interrumpibles (eliminables, para aquellos que no estén familiarizados con la jerga) en cualquier momento sin ningún efecto sobre la integridad de los datos. En realidad, esto es algo que no veo mucho fuera de Google, y reconocer la posible mortalidad repentina de su programa conduce a escribir un software mucho más confiable. Pero yo divago.

Los sistemas construidos con tales programas preferirían que esos programas murieran y se reencarnaran en otra parte de la célula (grupo Borg), en lugar de continuar sufriendo en un nodo con exceso de suscripción. De lo contrario, la latencia de cola puede ser realmente problemática, especialmente cuando la cantidad de tareas en un trabajo es grande.

No me malinterpretes, no estoy diciendo que esta sea la única forma "correcta" de ejecutar las cosas. Solo estoy tratando de dilucidar la posible justificación del diseño.

Descargo de responsabilidad: soy un ex Googler que usó Borg para ejecutar varios servicios muy grandes, por lo que lo conozco bastante bien, y ese conocimiento se traduce en gran medida a Kubernetes. Actualmente no estoy con Google, y lo que escribo aquí son mis propios pensamientos.

@ 1e100 : está combinando el tamaño de la "VM total" con el tamaño del "conjunto de trabajo". La carga de trabajo debe programarse según el tamaño del _ conjunto de trabajo_, y el programa se degradará una vez que se exceda el tamaño del _ conjunto de trabajo_ (suponiendo que haya suficiente intercambio total disponible). El razonamiento que ha indicado anteriormente también se basa en la suposición incorrecta de que el intercambio (y otras compensaciones de degradación de E / S ram <->) no sucederá solo porque el intercambio está deshabilitado.

(También soy ex-Google-SRE, y estoy de acuerdo en que estos mitos comunes de Google son casi con certeza lo que influyó en la decisión de que estaba bien (o incluso deseable) deshabilitar el intercambio en k8s también. Vi a varios equipos de Google ir a través del aprendizaje de que deshabilitar el intercambio no deshabilita el intercambio, y el desperdicio de memoria agregada que se deriva de solo describir un límite "duro" (oom-kill) para la memoria: estas son precisamente algunas de las cosas que me gustaría mejorar con k8s. Hay una serie de botones ajustables de cgroup / swap disponibles ahora que no teníamos cuando se diseñó inicialmente el modelo de recursos de borg, y estoy convencido de que podemos lograr los resultados deseados sin un enfoque de tirar al bebé con agua de baño. También señalaré que la compensación de Google es _ a menudo_ ser menos eficiente en promedio para lograr un mejor / conocido tiempo en el peor de los casos (es decir, comportamiento en tiempo real) y que esta a menudo _no_ es la opción deseada fuera de Google - más pequeño / menos hosts, SLO más relajados, presupuestos más bajos, más mala definición trabajos por lotes ed, más uso de lenguajes ineficientes de pila no compilados, etc.)

El intercambio de memoria anónima no ocurrirá. Todo lo que se asigne a la memoria (incluido el código del programa y los datos) puede y seguirá intercambiando si hay presión de memoria, por eso sugerí que los límites de RAM deberían ser necesarios de forma predeterminada: para que sea menos probable que haya presión de memoria en primer lugar. Para cargas de trabajo que necesitan garantías aún más estrictas, también hay un valor de mlockall() y un swappiness bajo.

Como ex SRE de Google, no puede argumentar que no especificar el límite superior de RAM o permitir que las tareas intercambien lo que quieran por capricho es algo bueno, a menos que solo quiera oponerse. El intercambio de archivos mapeados en memoria ya es bastante malo, por lo que introducir aún más obstáculos de rendimiento potenciales en la mezcla no es algo bueno.

Estos son entornos compartidos por diseño, y desea eliminar las formas en que los programas pueden hacer impredecible el rendimiento de los demás, no agregar otros nuevos. Como dicen las SRE de Google, "la esperanza no es una estrategia". Swap thrashing es la forma más fácil que conozco de conseguir que una máquina Linux se bloquee de forma completa e irrecuperable, incluso si está cambiando a SSD. Eso no puede ser bueno incluso si solo está ejecutando una carga de trabajo en la máquina, y mucho menos un par de docenas. Las fallas correlacionadas pueden ser especialmente dolorosas en grupos más pequeños con pocas tareas / pods.

Uno puede ignorar el cheque de intercambio incluso hoy si lo desea, pero con el entendimiento explícito de que todas las apuestas están canceladas en ese caso.

Sí, estoy totalmente de acuerdo en que necesitamos tener un "tamaño" que usemos para programar y evitar (involuntariamente) compromisos excesivos. Y también queremos evitar el thrash global de la máquina virtual, porque Linux tiene un mal tiempo para recuperarse de eso. Lo que _hacemos_es que el kernel sea _capaz_ de intercambiar memoria anónima y reutilizar esa memoria RAM por otra cosa, cuando eso tenga sentido, ya que esto es estrictamente superior a un sistema que no puede hacer eso. Idealmente, queremos permitir que los contenedores individuales puedan administrar las compensaciones de RAM / disco y enfrentar las consecuencias de sus propias opciones de recursos (sobre / subasignadas) con un efecto mínimo en otros contenedores.

Solo para mostrar a dónde voy con esto, mi propuesta actual de hombre de paja es:

  • La metáfora es que cada contenedor se comporta como si estuviera en una máquina por sí mismo con una cierta cantidad de RAM (dada por limits.memory ) e intercambio.
  • Igual que otros recursos: programar según requests.memory , imponer límite según limits.memory . "Memoria" en este caso significa "RAM"; el uso de intercambio es gratuito.
  • Específicamente k8s requests.memory -> cgroup memory.low (reducido por cualquier factor de compromiso excesivo); k8s limits.memory -> cgroup memory.high .
  • Si un contenedor excede su límite de memoria configurado, entonces comienza a intercambiar, _independientemente_ de la cantidad de RAM libre disponible. Gracias a cgroups, esto _no_ es solo el uso de VM, sino que también incluye caché de bloques, búfer de sockets, etc., atribuibles al contenedor. Esto evita que ejerzamos presión sobre la memoria en otros contenedores (o procesos del host). Al buscar una página para desalojar, el kernel buscará contenedores que excedan el tamaño de su solicitud de memoria.
  • Introduzca un límite flexible de uso de intercambio total de kubelet donde k8s dejará de programar nuevos pods en el host (exactamente como otros "sistemas de archivos compartidos" como imagefs).
  • Si alcanzamos el límite estricto de uso de intercambio total, entonces comience a desalojar los pods en función de la clase qos / prioridad y el tamaño de la máquina virtual por encima de las "solicitudes" (exactamente igual que otros "sistemas de archivos compartidos" como imagefs).
  • Si un contenedor excede en gran medida su conjunto de trabajo ( requests.memory ), entonces puede fallar (si también excede limits.memory o no hay suficiente RAM disponible en el host). Explícitamente _no_ hacemos nada al respecto a través del mecanismo de recursos. Si el contenedor es swap-thrashing, entonces (presumiblemente) fallará las comprobaciones de la sonda de disponibilidad / disponibilidad y será eliminado a través de ese mecanismo (es decir, swap-thrashing está bien si no tenemos SLA de respuesta configurados).

El resultado final es que el administrador es responsable de configurar el intercambio "suficiente" en cada sistema. Las aplicaciones deben configurar limits.memory con el _max_ ram que siempre quieran usar, y requests.memory con su conjunto de trabajo previsto (incluidos los búferes del kernel, etc.). Al igual que con otros recursos, las clases qos garantizadas (límite == solicitud), ampliable (límite indefinido o! = Solicitud), mejor esfuerzo (sin límite o solicitud) aún se aplican. En particular, esto alienta a los procesos con ráfagas a declararse cerca de su conjunto de trabajo previsto (sin un gran búfer de seguridad), lo que permite una asignación eficiente (idealmente exactamente el 100% del ariete asignado para el conjunto de trabajo) y proporciona una degradación suave del rendimiento cuando los contenedores superan eso: al igual que otros recursos "indulgentes" como cpu.

Creo que esto se puede implementar dentro de los cgroups de Linux, aborda las preocupaciones de aislamiento, continúa los precedentes conceptuales establecidos por otros recursos de k8s y se degrada al comportamiento existente cuando el intercambio está deshabilitado (lo que facilita la migración). La única pregunta abierta que tengo es si esto es _ya_ lo que está implementado (menos el límite suave / estricto de kubelet de "swapfs"). Necesito leer el código real de kubelet / CRI cgroups antes de poder escribir una propuesta concreta y elementos de acción. .

Los comentarios / discusiones sobre lo anterior probablemente no sean apropiados en este problema de github (es un foro de discusión deficiente). Si hay algo terriblemente mal con lo anterior, agradecería recibir comentarios sobre la holgura de k8s, de lo contrario, escribiré un documento adecuado y pasaré por la discusión de diseño habitual en algún momento.

Recomiendo redactar un documento formal para que podamos tener un mejor foro de discusión.

Acordado. Estoy feliz de ayudar a escribir un KEP, porque tengo algunas ideas definidas para esto, pero nunca antes había hecho uno y preferiría tener una mano más experimentada en el timón.

Pero, además, no tengo el ancho de banda para estar al día con un canal de Slack en mi tiempo libre; si hay un método de coordinación más asincrónico, avíseme.

Solo para mantener las cosas vivas: todavía estoy muy interesado en trabajar en un KEP y / o implementación para esto; Una vez que las cosas se calmen (tengo un taller que preparar para el próximo fin de semana), intentaré unirme al canal de Slack.

Hola, ¿hay algún debate público sobre este problema actualmente? (La holgura de k8s no está abierta para todos en este momento, y supongo que no lo estará por algún tiempo).

@leonaves no hay discusión sobre la holgura actualmente AFAIK. el último comentario de @guslees es el último de la discusión. Tenga en cuenta que tendrá que haber un KEP con detalles en el repositorio de kubernetes / mejoras para comenzar y probablemente también en las listas de correo.

Parece haber un final del túnel para la reapertura de holgura también muy pronto. cruzo los dedos.

Resulta que todavía no tengo el ancho de banda mental para unirme a otro canal de Slack. Todavía estaría dispuesto a colaborar en esto por correo electrónico.

Una solución para aquellos que realmente quieren un intercambio. Si tu

  • iniciar kubelet con --fail-swap-on=false
  • agregue swap a sus nodos
  • los contenedores que _no_ especifiquen un requisito de memoria podrán utilizar de forma predeterminada toda la memoria de la máquina, incluido el intercambio.

Eso es lo que estamos haciendo. O al menos, estoy bastante seguro de que lo es, en realidad no lo implementé personalmente, pero eso es lo que deduzco.

Esto solo podría ser una estrategia viable si ninguno de sus contenedores especifica alguna vez un requisito de memoria explícito ...

¿¡Este método ya no funciona !? Enciendo el intercambio e implemento un pod sin configuración de memoria, y obtuve este contenedor

$ docker inspect <dockerID> | grep Memory
            "Memory": 0,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": -1

MemorySwap es "0", lo que significa que este contenedor no puede acceder al intercambio :(

Los problemas se vuelven obsoletos después de 90 días de inactividad.
Marque el problema como nuevo con /remove-lifecycle stale .
Los problemas obsoletos se pudren después de 30 días adicionales de inactividad y finalmente se cierran.

Si es seguro cerrar este problema ahora, hágalo con /close .

Envíe sus comentarios a sig-testing, kubernetes / test-infra y / o fejta .
/ ciclo de vida obsoleto

/ remove-lifecycle stale.

/ remove-lifecycle stale

Voy a dejar esto aquí como otra referencia para los lectores de este número: https://chrisdown.name/2018/01/02/in-defence-of-swap.html

Los problemas se vuelven obsoletos después de 90 días de inactividad.
Marque el problema como nuevo con /remove-lifecycle stale .
Los problemas obsoletos se pudren después de 30 días adicionales de inactividad y finalmente se cierran.

Si es seguro cerrar este problema ahora, hágalo con /close .

Envíe sus comentarios a sig-testing, kubernetes / test-infra y / o fejta .
/ ciclo de vida obsoleto

/ remove-lifecycle stale

esta característica es realmente necesaria en algunos casos de uso. Actualmente estamos usando k8s para el aprendizaje automático y, a veces, necesitamos cargar modelos grandes en la memoria (¡en nuestro caso, a veces, 500 MB por solicitud de API!), y los límites de la memoria física están causando problemas graves. El escalado horizontal funcionaría desde el punto de vista técnico, pero los costos se dispararían. Si tuviéramos la memoria virtual como una opción, sería genial.

¿Alguna posibilidad de que este boleto vuelva a la hoja de ruta?

Suena como un caso para mmap.

También estoy muy interesado en esta función. ¿Hay alguna novedad sobre esto?

Estaría feliz de comenzar a investigar esto cuando tenga tiempo, que es escaso en este momento, pero sería bueno tener uno o dos casos canónicos que exacerben el problema para que se pueda caracterizar más completamente (más allá de " comienza a golpear y todo se va al infierno ") y el enfoque definitivo y la corrección validada.

Cualquier solución prospectiva también debe considerar las implicaciones de seguridad del swap. Esto, obviamente, es cierto para cualquier cosa que se ejecute en un entorno Unix, pero si la gente se ha acostumbrado a ejecutar pods k8s con una pseudogarantía de no intercambio y se ha vuelto perezosa con la disciplina de la memoria, esto podría ser una sorpresa bastante desagradable si está habilitado por defecto.

Sería bueno tener uno o dos casos canónicos que exacerben el problema para poder caracterizarlo más completamente.

Eso suena a KEP .

Cualquier solución prospectiva también debe considerar las implicaciones de seguridad del swap. Esto, obviamente, es cierto para cualquier cosa que se ejecute en un entorno Unix, pero si la gente se ha acostumbrado a ejecutar pods k8s con una pseudogarantía de no intercambio y se ha vuelto perezosa con la disciplina de la memoria, esto podría ser una sorpresa bastante desagradable si está habilitado por defecto.

Esa lógica se aplicaría a cualquier proceso que se ejecute en una combinación de contenedores, independientemente de si Kubernetes está en uso o no.

¡Acordado! Pero Docker ya admite explícitamente la ejecución con swap. Kubernetes explícitamente no lo hace (aunque puede forzarlo). Mi argumento es que al menos debería mencionarse, porque no está en el modelo de amenazas de todos, especialmente si no han tenido que pensar en ello previamente.

También sí, @sftim , lo hace. :-) Creo que lo que estoy diciendo es que me gustaría escribir / contribuir a un KEP, pero me gustaría ver uno o dos casos de prueba mínimos que ejerciten de manera confiable el problema en un sistema de prueba dado antes de aventurarme. que podemos estar seguros de que estamos resolviendo los problemas correctos.

@superdave ¿qué tipo de caso de prueba tienes en mente?

Aquí hay una prueba trivial:

  1. Configure un clúster con 1 nodo, 16 GiB de RAM y 64GiB de archivo de paginación.
  2. Intente programar 20 Pods, cada uno con una solicitud de memoria de 1GiB y un límite de memoria de 1GiB.
  3. Observe que no todo es horario.

Aquí está otro:

  1. Configure 6 máquinas, cada una con 16 GiB de RAM y 64GiB de archivo de paginación.
  2. Intente usar kubeadm con las opciones predeterminadas para configurar estas máquinas como un clúster de Kubernetes.
  3. Observe que kubeadm no está contento de que el intercambio esté en uso.

Ahora hay un gran cambio para SSD en las plataformas en la nube más respetables y considerando que Linux tiene optimizaciones dedicadas para el intercambio en SSD https://lwn.net/Articles/704478/ con posibilidad adicional de compresión, esta situación brinda una oportunidad completamente nueva para utilizar el intercambio como Recurso predecible y rápido para RAM adicional en caso de presión de memoria.
El intercambio deshabilitado se convierte en un recurso desperdiciado de la misma manera que la RAM no utilizada se desperdicia si no se usa para búferes de E / S.

@superdave

¡Acordado! Pero Docker ya admite explícitamente la ejecución con swap. Kubernetes explícitamente no lo hace (aunque puede forzarlo). Mi argumento es que al menos debería mencionarse, porque no está en el modelo de amenazas de todos, especialmente si no han tenido que pensar en ello previamente.

En ese caso, sería justo asumir que kubelet tendrá mlock() su espacio de memoria y establecer la prioridad de eliminación de OOM baja para evitar ser intercambiado o eliminado, y ejecutar todos los contenedores en cgroups con swapiness establecido en 0 de forma predeterminada. Si alguien quiere beneficiarse del intercambio de formularios, puede optar por usar una opción, es decir, enableSwapiness: 50 para contenedores particulares en el pod.
Sin sorpresas, pilas incluidas.

@sftim Aquellos demostrarían que a) Kubelet no quiere programar los contenedores yb) Kubelet no se ejecutará con el intercambio activado de forma predeterminada. Lo que estoy buscando ejercitar son las situaciones en la parte superior del hilo, por @derekwaynecarr :

El soporte para el intercambio no es trivial. Las cápsulas garantizadas nunca deberían requerir intercambio. Los pods expandibles deben tener sus solicitudes satisfechas sin necesidad de intercambio. Las cápsulas BestEffort no tienen garantía. El kubelet en este momento carece de la inteligencia para proporcionar la cantidad correcta de comportamiento predecible aquí en todos los pods.

Discutimos este tema en la administración de recursos cara a cara a principios de este año. No estamos muy interesados ​​en abordar esto a corto plazo en relación con las ganancias que podría obtener. Preferiríamos mejorar la confiabilidad en la detección de presión y optimizar los problemas relacionados con la latencia antes de intentar optimizar para el intercambio, pero si esta es una prioridad más alta para usted, nos encantaría su ayuda.

Y también justo debajo de él, de @matthiasr :

Hay más contexto en la discusión aquí: # 7294 - tener intercambio disponible tiene interacciones muy extrañas y malas con los límites de memoria. Por ejemplo, un contenedor que alcanza su límite de memoria _entonces_ comenzaría a extenderse hacia el intercambio (esto parece estar arreglado desde f4edaf2 ; no se les permitirá usar ningún intercambio, ya sea que esté allí o no).

Ambos ofrecen una buena visión de los problemas que ya se han visto, pero sería bueno tener una idea de los escenarios conocidos y reproducibles que podrían exacerbar los problemas. Puedo inventarlos yo mismo, pero si alguien más ya lo ha hecho, es una rueda que no me importaría no reinventar.

@superdave

¡Acordado! Pero Docker ya admite explícitamente la ejecución con swap. Kubernetes explícitamente no lo hace (aunque puede forzarlo). Mi argumento es que al menos debería mencionarse, porque no está en el modelo de amenazas de todos, especialmente si no han tenido que pensar en ello previamente.

En ese caso, sería justo asumir que kubelet tendrá mlock() su espacio de memoria y establecer la prioridad de eliminación de OOM baja para evitar ser intercambiado o eliminado, y ejecutar todos los contenedores en cgroups con swapiness establecido en 0 de forma predeterminada. Si alguien quiere beneficiarse del intercambio de formularios, puede optar por usar una opción, es decir, enableSwapiness: 50 para contenedores particulares en el pod.
Sin sorpresas, pilas incluidas.

Creo que estoy de acuerdo con todo aquí. Por defecto al comportamiento actual para evitar sorpresas desagradables.

Aquí un ejemplo de cómo podría verse una aplicación simple, donde por alguna razón se asigna una gran parte de la memoria pero nunca se vuelve a acceder. Una vez que se llena toda la memoria disponible, la aplicación se cuelga o cae en un bucle sin fin que básicamente bloquea los recursos u obliga al asesino de memoria agotada:

#include <iostream>
#include <vector>
#include <unistd.h>
int main() {
  std::vector<int> data;
  try
    {
        while(true) { data.resize(data.size() + 200); };
    }
    catch (const std::bad_alloc& ex)
    {
        std::cerr << "Now we filled up memory, so assume we never access that stuff again and just moved on, or we're stuck in an endless loop of some sort...";
        while(true) { usleep(20000); };
    }
  return 0;
}

Una solución para aquellos que realmente quieren un intercambio. Si tu

  • iniciar kubelet con --fail-swap-on=false
  • agregue swap a sus nodos
  • los contenedores que _no_ especifiquen un requisito de memoria podrán utilizar de forma predeterminada toda la memoria de la máquina, incluido el intercambio.

Eso es lo que estamos haciendo. O al menos, estoy bastante seguro de que lo es, en realidad no lo implementé personalmente, pero eso es lo que deduzco.

Esto solo podría ser una estrategia viable si ninguno de sus contenedores especifica alguna vez un requisito de memoria explícito ...

Hola @hjwp , gracias por tu información. ¡Eso realmente ayuda mucho!

¿Puedo hacer una pregunta después de esto?

Después de configurar todo como dijiste, ¿hay alguna manera de limitar el uso de memoria de intercambio por parte de los contenedores?

Estaba pensando en configurar --memory-swap params de Docker
https://docs.docker.com/config/containers/resource_constraints/# --memory-swap-details
Actualmente, mi contenedor no tiene límite en el uso de intercambio ( "MemorySwap": -1 )

sudo docker inspect 482d70f73c7c | grep Memory
            "Memory": 671088640,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": -1,
            "MemorySwappiness": null,

Pero no pude encontrar este parámetro expuesto en k8s.

Por cierto, ¿el límite de la memoria del módulo también restringirá el uso del intercambio?

Mi configuración relacionada con vm

vm.overcommit_kbytes = 0
vm.overcommit_memory = 1
vm.overcommit_ratio = 50
vm.swappiness = 20
vm.vfs_cache_pressure = 1000

¡Gracias!

@ pai911 No creo que eso sea posible,

actualmente, CRI no admite eso, vea esto , no hay una opción como --memory-swap en la ventana acoplable

Sin embargo, esta es una limitación de CRI, la especificación OCI admite esta opción, pero no se exporta a la capa de CRI

Una posible solución (teóricamente) es crear un DaemonSet privilegiado que, a su vez, lea los datos de anotación de los pods, y luego el DaemonSet edite el valor de cgroup manualmente

cgroup

Hola @ win-t,

¡Gracias por los comentarios!

Entonces, por ahora, ¿esta opción es solo para uso interno?

¿Sabe qué valor de cgroup está asignado a esta opción --memory-swap?

Entonces, por ahora, ¿esta opción es solo para uso interno?

Sí, no puede configurar esta opción, ya que no están expuestos en k8s

por cierto, MemorySwap en la inspección de la ventana acoplable debería ser lo mismo que Memory acuerdo con esto , no sé cómo puede obtener -1 en su inspección de la ventana acoplable

¿Sabe qué valor de cgroup está asignado a esta opción --memory-swap?

  • --memory en el mapa de la ventana acoplable a memory.limit_in_bytes en cgroup v1
  • --memory-swap en el mapa de la ventana acoplable a memory.memsw.limit_in_bytes en cgroup v1

@ win-t ¡Muchas gracias!

Estoy usando la siguiente versión

Client Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.5", GitCommit:"20c265fef0741dd71a66480e35bd69f18351daea", GitTreeState:"clean", BuildDate:"2019-10-15T19:16:51Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.10", GitCommit:"1bea6c00a7055edef03f1d4bb58b773fa8917f11", GitTreeState:"clean", BuildDate:"2020-02-11T20:05:26Z", GoVersion:"go1.12.12", Compiler:"gc", Platform:"linux/amd64"}

y miré la historia. parece que la solución se agregó en esta confirmación

¿Quizás no esté incluido en la versión que estoy ejecutando?

Entonces, por ahora, ¿esta opción es solo para uso interno?

Sí, no puede configurar esta opción, ya que no están expuestos en k8s

por cierto, MemorySwap en la inspección de la ventana acoplable debería ser lo mismo que Memory acuerdo con esto , no sé cómo puede obtener -1 en su inspección de la ventana acoplable

¿Sabe qué valor de cgroup está asignado a esta opción --memory-swap?

  • --memory en el mapa de la ventana acoplable a memory.limit_in_bytes en cgroup v1
  • --memory-swap en el mapa de la ventana acoplable a memory.memsw.limit_in_bytes en cgroup v1

Esto es raro.

Estaba usando kops + Debian, y la inspección de Docker muestra que no hay límite en la memoria de intercambio
(El Docker inspecciona la información que publiqué anteriormente)

Pero luego cambié a la imagen de Amazon Linux, y esto es lo que obtuve

            "Memory": 671088640,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": 671088640,
            "MemorySwappiness": null,

Investigaré un poco más y veré si esto es un error.

Entonces, por ahora, ¿esta opción es solo para uso interno?

Sí, no puede configurar esta opción, ya que no están expuestos en k8s
por cierto, MemorySwap en la inspección de la ventana acoplable debería ser lo mismo que Memory acuerdo con esto , no sé cómo puede obtener -1 en su inspección de la ventana acoplable

¿Sabe qué valor de cgroup está asignado a esta opción --memory-swap?

  • --memory en el mapa de la ventana acoplable a memory.limit_in_bytes en cgroup v1
  • --memory-swap en el mapa de la ventana acoplable a memory.memsw.limit_in_bytes en cgroup v1

Esto es raro.

Estaba usando kops + Debian, y la inspección de Docker muestra que no hay límite en la memoria de intercambio
(El Docker inspecciona la información que publiqué anteriormente)

Pero luego cambié a la imagen de Amazon Linux, y esto es lo que obtuve

            "Memory": 671088640,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": 671088640,
            "MemorySwappiness": null,

Investigaré un poco más y veré si esto es un error.

Ahora puedo reproducir el problema existente en la imagen oficial de Debian por kops

Parece que esta imagen oficial de kops hará que la memoria de intercambio sea ilimitada
kope.io/k8s-1.15-debian-stretch-amd64-hvm-ebs-2020-01-17

Pasos de reproducción:

Mi grupo de instancias de kops se define de la siguiente manera:

apiVersion: kops.k8s.io/v1alpha2
kind: InstanceGroup
metadata:
  creationTimestamp: "2020-03-12T06:33:09Z"
  generation: 5
  labels:
    kops.k8s.io/cluster: solrcluster.k8s.local
  name: node-2
spec:
  additionalUserData:
  - content: |
      #!/bin/sh
      sudo cp /etc/fstab /etc/fstab.bak
      sudo mkfs -t ext4 /dev/nvme1n1
      sudo mkdir /data
      sudo mount /dev/nvme1n1 /data
      echo '/dev/nvme1n1       /data   ext4    defaults,nofail        0       2' | sudo tee -a /etc/fstab
      sudo fallocate -l 2G /data/swapfile
      sudo chmod 600 /data/swapfile
      sudo mkswap /data/swapfile
      sudo swapon /data/swapfile
      echo '/data/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
      sudo sysctl vm.swappiness=10
      sudo sysctl vm.overcommit_memory=1
      echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
      echo 'vm.overcommit_memory=1' | sudo tee -a /etc/sysctl.conf
    name: myscript.sh
    type: text/x-shellscript
  image: kope.io/k8s-1.15-debian-stretch-amd64-hvm-ebs-2020-01-17
  instanceProtection: true
  kubelet:
    failSwapOn: false
  machineType: t3.micro

Pasos:

  1. Después de que el clúster esté en funcionamiento.

  2. Implemente Solr Helm Chart con la siguiente configuración de recursos

resources:
  limits:
    cpu: "1"
    memory: 640Mi
  requests:
    cpu: 100m
    memory: 256Mi

** Cualquier otro Pod también debería funcionar

  1. Enumere los contenedores para encontrar una identificación de contenedor
    sudo docker container ls

  2. Inspeccionar los parámetros de memoria de un contenedor
    sudo docker inspect d67a72bba427 | grep Memory

            "Memory": 671088640,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": -1,
            "MemorySwappiness": null,

¿Debo enviar el problema a alguna parte? k8s o kops?

Pasos:

  1. Después de que el clúster esté en funcionamiento.
  2. Implemente Solr Helm Chart con la siguiente configuración de recursos
resources:
  limits:
    cpu: "1"
    memory: 640Mi
  requests:
    cpu: 100m
    memory: 256Mi

** Cualquier otro Pod también debería funcionar

  1. Enumere los contenedores para encontrar una identificación de contenedor
    sudo docker container ls
  2. Inspeccionar los parámetros de memoria de un contenedor
    sudo docker inspect d67a72bba427 | grep Memory
            "Memory": 671088640,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": -1,
            "MemorySwappiness": null,

¿Debo enviar el problema a alguna parte? k8s o kops?

Puedo confirmar que solo puedo ver el comportamiento correcto en Amazon Linux
ami-0cbc6aae997c6538a : amzn2-ami-hvm-2.0.20200304.0-x86_64-gp2

            "Memory": 671088640,
            "CpusetMems": "",
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": 671088640,
            "MemorySwappiness": null,

Es decir: "MemorySwap" == "Memory"

Las otras dos imágenes tienen la misma configuración: "MemorySwap": -1 , lo que conduce a un uso de intercambio ilimitado.

  • Debian

    • ami-075e61ad77b1269a7 : k8s-1.15-debian-stretch-amd64-hvm-ebs-2020-01-17

  • Ubuntu

    • ami-09a4a9ce71ff3f20b : ubuntu / imágenes / hvm-ssd / ubuntu-bionic-18.04-amd64-server-20200112

¿Entonces creo que podría ser un problema de k8s?

Historias de usuarios:

(1) El programa proporcionado por mi proveedor utiliza un tiempo de ejecución de lenguaje que exige el acceso a la fuente del código del programa. Debido a esto, durante la inicialización, todo el código fuente del programa se organiza en una arena de memoria separada. Una vez que el programa se inicializa y el contenedor está listo, no se accederá a esta memoria (no puede probar esto, pero no lo hará). Además, el programa asigna algunas páginas para reservar para el manejo personalizado de OOM. Esta memoria se puede intercambiar. No quiero que esta "memoria muerta" desplace a otras aplicaciones de clúster. Puedo calcular con precisión la cantidad de memoria que quedará muerta y listarla como una solicitud de intercambio en la especificación de Pod.

(2) Estoy ejecutando una carga de trabajo de análisis de datos o aprendizaje automático en la que el uso de la memoria puede aumentar y retraerse repentinamente. Está bien si estas aplicaciones se ralentizan, siempre y cuando no terminen y finalmente terminen. Quiero aprovisionar el clúster con swap para mitigar los desalojos cuando ocurren estos globos de memoria. Estos pods tendrían pocas solicitudes de memoria e intercambio, un límite de memoria moderado, tal vez suficiente para la línea de base + un conjunto de trabajo, y un gran límite de intercambio.

(3) Estoy ejecutando un servidor web en un intérprete (por ejemplo, Ruby on Rails) que ocasionalmente necesita fork + exec. La contabilidad estricta de la memoria da como resultado fallas en las bifurcaciones, que son inaceptables. Quiero aprovisionar swap para que el kernel tenga el margen de memoria garantizado para cubrir el comportamiento del proceso entre las llamadas a la bifurcación y al ejecutivo. El valor de vm.swappiness se puede configurar para desalentar extremadamente el intercambio, y configuro alertas para notificar a las operaciones si el intercambio se usa realmente durante la producción. La especificación de pod establecería la solicitud de intercambio y el límite en el mismo valor.

Recientemente, intentamos migrar todos nuestros servicios basados ​​en Docker a Kubernetes, pero tuvimos que abandonar el proyecto debido a que el intercambio no era compatible.

Descubrimos que habríamos necesitado aprovisionar 3 veces la cantidad de hardware para admitir exactamente la misma cantidad de contenedores que estábamos ejecutando actualmente con el intercambio habilitado.

El problema principal es que nuestra carga de trabajo consta de varios contenedores que pueden usar hasta 1 Gb de memoria (o intercambio), pero normalmente usan alrededor de 50 Mb cuando funcionan normalmente.

No poder intercambiar significaba que teníamos que diseñar todo para la carga más grande posible que pudiera necesitar, en lugar de tener un bloque de intercambio disponible cuando se necesitaran grandes 'trabajos'.

Terminamos abandonando nuestra migración a Kubernetes y hemos movido temporalmente todo a Swarm por el momento con la esperanza de que el intercambio sea compatible en el futuro.

El problema principal es que nuestra carga de trabajo consta de varios contenedores que pueden usar hasta 1 Gb de memoria (o intercambio), pero normalmente usan alrededor de 50 Mb cuando funcionan normalmente.

Uno podría aventurarse a decir que las aplicaciones que se ejecutan en esos contenedores están escritas increíblemente mal.

Uno podría aventurarse a decir que las aplicaciones que se ejecutan en esos contenedores están escritas increíblemente mal.

Esto es algo irrelevante, y señalar con el dedo rara vez es constructivo. El ecosistema de Kubernetes está diseñado para admitir una amplia gama de perfiles de aplicaciones, y destacar uno de ellos de esta manera no tiene mucho sentido.

El problema principal es que nuestra carga de trabajo consta de varios contenedores que pueden usar hasta 1 Gb de memoria (o intercambio), pero normalmente usan alrededor de 50 Mb cuando funcionan normalmente.

Uno podría aventurarse a decir que las aplicaciones que se ejecutan en esos contenedores están escritas increíblemente mal.

lol, esta es una característica del kernel, una aplicación puede usar madvise(2) en el archivo shm, y no bloqueamos madvise syscall,
por lo que es elegible para que el usuario aproveche esta función en su diseño, no se puede decir "están escritas increíblemente mal",

El problema principal es que nuestra carga de trabajo consta de varios contenedores que pueden usar hasta 1 Gb de memoria (o intercambio), pero normalmente usan alrededor de 50 Mb cuando funcionan normalmente.

Uno podría aventurarse a decir que las aplicaciones que se ejecutan en esos contenedores están escritas increíblemente mal.

Su respuesta indica que no comprende las cargas de trabajo con las que trabajan muchos desarrolladores.

La carga de trabajo que mencioné trata con conjuntos de datos de diferentes tamaños proporcionados por los usuarios, de ahí la gran variedad de posibles requisitos de recursos.

Claro, _podríamos_ usar archivos mapeados en memoria para mantener el uso de la memoria consistente. Luego, tendríamos que reescribir las bibliotecas para usar el mapeo de memoria, lo que incluiría más o menos todas las bibliotecas existentes ... siempre.

Pero entonces habríamos creado lo que es esencialmente un archivo de paginación específico de la aplicación, que casi con certeza funcionaría peor que uno administrado por el kernel.

Algunas implementaciones de Kubernetes necesitan intercambio

Tengo un caso de uso válido: estoy desarrollando un producto local, la distribución de Linux, incluido con kubeadm. sin escala horizontal por diseño. Para sobrevivir a los picos de memoria oportunistas y seguir funcionando (pero lento), definitivamente necesito un intercambio .

Para instalar kubeadm con intercambio habilitado

  1. Crea un archivo en /etc/systemd/system/kubelet.service.d/20-allow-swap.conf con el contenido:

    [Service]
    Environment="KUBELET_EXTRA_ARGS=--fail-swap-on=false"
    
  2. Correr

    sudo systemctl daemon-reload
    
  3. Inicializar kubeadm con la bandera --ignore-preflight-errors=Swap

    kubeadm init --ignore-preflight-errors=Swap
    

https://stackoverflow.com/a/62158455/3191896

Como desarrollador de software ingenuo, me parece perfectamente razonable que los módulos de sistema con cargas de trabajo sensibles al tiempo soliciten un comportamiento de no intercambio, y otras cargas de trabajo (de forma predeterminada) se coloquen en una categoría de mejor esfuerzo. ¿No resolvería eso todas las preocupaciones?

Para mis propias necesidades, muchas de mis aplicaciones se benefician de los cachés. Dicho esto, si una aplicación necesita repentinamente un montón de memoria, sería preferible enviar la caché más antigua al disco si esas aplicaciones no responden a una solicitud para reducir la presión de la memoria que dejar que la nueva carga de trabajo se quede sin memoria o tenga para respaldar la ráfaga con memoria física + más para implementaciones continuas + más para una posible falla del nodo.

@metatick dijo:

Claro, podríamos usar archivos mapeados en memoria para mantener un uso constante de la memoria. Luego, tendríamos que reescribir las bibliotecas para usar el mapeo de memoria, lo que incluiría más o menos todas las bibliotecas existentes ... siempre.

La biblioteca C estándar de Linux está diseñada para reemplazar el asignador de memoria; los malloc , realloc y free se llaman a través de punteros para ese propósito. Entonces, podría simplemente LD_PRELOAD una biblioteca que los anularía para asignar desde un archivo mmapped.

Pero entonces habríamos creado lo que es esencialmente un archivo de paginación específico de la aplicación, que casi con certeza funcionaría peor que uno administrado por el kernel.

Realmente funcionaría exactamente como un intercambio normal, porque sería administrado por el mismo código en el kernel. La única diferencia sería la falta de parámetro de intercambio para ajustar su prioridad.

La única pregunta abierta que tengo es si esto ya está implementado (menos el límite suave / duro de kubelet de "swapfs"). Necesito leer el código de cgroups de kubelet / CRI real antes de poder escribir una propuesta concreta y elementos de acción. .

@anguslees ,
¿Alguna vez te has movido para comprobar el comportamiento? Si es así, ¿puede agregar alguna resolución o un enlace a uno, por favor?

Gracias,
ene

¿Alguna vez te has movido para comprobar el comportamiento? Si es así, ¿puede agregar alguna resolución o un enlace a uno, por favor?

No lo hice. (Busqué un poco en el código de la ventana acoplable, pero ya lo olvidé por completo y tendría que comenzar de nuevo)

¡Otros voluntarios son bienvenidos! Espero no haberle robado el oxígeno a alguien diciendo que trabajaría en esto y luego no lo cumplí :(

Para agregar a la historia de @metatick :

Actualmente uso Gigalixir como mi host, ejecutándose sobre Kubernetes. Es una aplicación web. A veces, los clientes cargan un lote de fotos, por lo que mi aplicación activa un montón de (uf) procesos de ImageMagick para cambiar su tamaño. El uso de memoria aumenta, el asesino de OOM se activa, mi aplicación se cae (brevemente) y la carga se arruina.

Termino teniendo que pagar toneladas más a Gigalixir de lo que debería solo por el uso espinoso. Como han mencionado otros.

Puede que no le guste el intercambio desde la perspectiva del diseño, pero su decisión le está costando dinero a los empresarios ... y es un desperdicio.

Por favor, arregla. :)

Este también es un problema muy importante para mí. En mi caso de uso, necesitaría ejecutar pods que usen ~ 100 MB la mayor parte del tiempo, pero de vez en cuando, cuando un usuario activa eventos específicos, puede explotar hasta 2 GB de RAM durante unos minutos antes de retroceder (y no, no es porque esté mal escrito, es la realidad de la carga de trabajo).
Ejecuto casi un centenar de estas cargas de trabajo a la vez en máquinas de 16 GB con intercambio. Simplemente no puedo mover esa carga de trabajo a Kubernetes porque eso no funcionaría en absoluto. Entonces, en este momento, tengo mi propio orquestador que ejecuta estas cargas de trabajo en sistemas que no son de Kubernetes mientras mi aplicación principal se ejecuta en Kubernetes, y frustra el propósito de mi migración a k8s. Sin intercambio, o se mata, o siempre necesito desperdiciar mucha RAM disponible durante los pocos minutos que las aplicaciones pueden (o no) explotar.

Si puede establecer un límite de CPU, que acelera la CPU de un pod, debería poder establecer un límite de memoria que acelera la memoria utilizada por un pod. Matar un pod cuando alcanza el límite de memoria es tan ridículo como matar uno si usa más recursos de CPU que el límite de CPU que se estableció (lo siento, pero no todos los pod son una réplica que se puede eliminar sin consecuencias).

Kubernetes no puede funcionar con el conjunto de intercambio en el nodo porque puede afectar todo el rendimiento de todo el clúster, está bien (aunque no creo que sea un argumento válido). Idealmente, lo que debería suceder es que el propio pod tenga un archivo de intercambio de nivel de pod en el que solo se intercambien los procesos dentro de esos contenedores. En teoría, esto limitaría el uso y el rendimiento de la RAM (debido al intercambio) de los pods que exceden sus límites de memoria, al igual que los límites de la CPU los acelera.
Desafortunadamente, no parece que cgroups pueda especificar un archivo de intercambio, solo su intercambio, y no puede decirle al kernel que "intercambie si el uso de mem está por encima de este límite", ya que, en cambio, parece decidir cuándo intercambiar según el último acceso y otras métricas.

Pero mientras tanto, ¿por qué no dejar que el intercambio exista en un nodo, establecer el intercambio en 0 para los pods que no tienen un límite establecido, y cuando se establece un límite (o algún otro campo de especificación para decir "swapInsteadOfKill") establecer el intercambio a un valor distinto de cero?

Además de la discusión sobre "intercambiar o no intercambiar", me da curiosidad que el equipo de k8s no haya abordado más el comportamiento descrito por

Puedo confirmar que kubelet parece comportarse de manera diferente (y en algunos sistemas operativos no de acuerdo con el código recortado mencionado anteriormente) con respecto a la configuración de la memoria docker deamon. Nuestros clústeres se ejecutan en SUSE linux y estamos experimentando el mismo uso de intercambio ilimitado mencionado en https://github.com/kubernetes/kubernetes/issues/53533#issuecomment -598056151

Detalles del SO: SUSE Linux Enterprise Server 12 SP4 - Linux 4.12.14-95.45-default

Sin el soporte adecuado para Swap en k8s de todos modos, al menos desearía que kubelet manejara la configuración de la memoria de Docker de manera consistente, independientemente del sistema operativo subyacente.

Puse el intercambio en la agenda para ver si hay apetito de la comunidad o voluntarios para ayudar a impulsar esto en 1.21. No tengo ninguna objeción a admitir el intercambio como señalé en 2017, solo debe asegurarse de no confundir el desalojo de kubelet, la prioridad de la cápsula, la calidad del servicio de la cápsula y, lo que es más importante, las cápsulas deben poder decir si toleran el intercambio o no. Todas estas cosas son importantes para garantizar que las cápsulas sean portátiles.

Últimamente se ha centrado mucha energía en hacer que funcionen cosas como la memoria alineada con NUMA, pero si hay personas que son menos sensibles al rendimiento y están igualmente motivadas para ayudar a hacer avanzar este espacio, nos encantaría que nos ayudaran a tener una ventaja en el diseño de KEP detallado en este espacio.

Últimamente no he seguido el proceso de la comunidad terriblemente bien, ya que las cosas han estado muy ocupadas para mí últimamente, sin embargo, parece que deberían calmarse un poco pronto. ¿Hay alguna forma de participar sin tener que unirme a un canal de Slack?

¿Fue útil esta página
0 / 5 - 0 calificaciones