3 años con Kubernetes en producción: esto es lo que tenemos

Aprox. transl. : en otro artículo de la categoría "lecciones aprendidas", el ingeniero DevOps de la empresa australiana comparte las principales conclusiones del uso a largo plazo de Kubernetes en producción para servicios cargados. El autor cubre Java, CI / CD, redes y la complejidad de K8 en general.



Comenzamos a crear nuestro primer clúster de Kubernetes en 2017 (con la versión K8s 1.9.4). Teníamos dos grupos. Uno trabajó en bare metal, en máquinas virtuales RHEL, el otro en la nube AWS EC2.



Hoy, nuestra infraestructura tiene más de 400 máquinas virtuales distribuidas en varios centros de datos. La plataforma sirve como base para aplicaciones y sistemas de misión crítica de alta disponibilidad que impulsan una enorme red de casi 4 millones de dispositivos activos.



Al final, Kubernetes nos facilitó la vida, pero el camino hacia esto fue espinoso y requirió un cambio de paradigma completo. Ha habido una transformación total no solo del conjunto de habilidades y herramientas, sino también del enfoque del diseño y el pensamiento. Tuvimos que dominar muchas tecnologías nuevas e invertir mucho en desarrollo de infraestructura y desarrollo de equipos.



Estas son las lecciones clave que hemos aprendido del uso de Kubernetes en producción durante tres años.



1. Una historia entretenida con aplicaciones Java



Cuando se trata de microservicios y contenedorización, los ingenieros tienden a alejarse de Java, principalmente debido a su gestión de memoria notoriamente imperfecta. Sin embargo, hoy la situación es diferente y la compatibilidad de Java con los contenedores ha mejorado en los últimos años. Después de todo, incluso los sistemas populares como Apache Kafka y Elasticsearch se ejecutan en Java.



En 2017-2018, algunas de nuestras aplicaciones se ejecutaron en la versión 8 de Java. A menudo se negaban a funcionar en entornos en contenedores como Docker y fallaban debido a problemas con la memoria del montón y recolectores de basura inadecuados. Al final resultó que, estos problemas fueron causados ​​por la incapacidad de JVM para ejecutar los mecanismos de contenedorización de Linux ( cgroupsy namespaces).



Desde entonces, Oracle ha realizado importantes esfuerzos para mejorar la compatibilidad de Java con el mundo de los contenedores. Ya en la versión 8 de Java, los indicadores experimentales de JVM parecían abordar estos problemas: XX:+UnlockExperimentalVMOptionsy, a XX:+UseCGroupMemoryLimitForHeap.



pesar de todas las mejoras, nadie diría que Java todavía tiene una mala reputación por consumir demasiada memoria y ser lento para comenzar en comparación con Python o ir. Esto se debe principalmente a las características específicas de la gestión de memoria en JVM y ClassLoader.



Hoy en día, si tenemos para trabajar con Java, podemos al menos tratar de utilizar la versión 11 o superior. Y nuestros límites de memoria en Kubernetes son 1 GB más altos que el límite máximo de memoria de pila en la JVM (-Xmx) (por si acaso). Es decir, si la JVM usa 8 GB para la memoria del montón, el límite de memoria de Kubernetes para la aplicación se establecerá en 9 GB. Gracias a estas medidas y mejoras, la vida se ha vuelto un poco más fácil.



2. Actualizaciones relacionadas con el ciclo de vida de Kubernetes



La administración del ciclo de vida de Kubernetes (actualizaciones, adiciones) es algo engorroso y difícil, especialmente si el clúster se basa en máquinas virtuales o bare metal . Resultó que para migrar a una nueva versión, es mucho más fácil crear un nuevo clúster y luego transferirle cargas de trabajo. Actualizar los sitios existentes simplemente no es factible, ya que implica un esfuerzo significativo y una planificación cuidadosa.



Esto se debe a que Kubernetes tiene demasiadas partes "móviles" para considerar al actualizar. Para que el clúster funcione, debe recopilar todos estos componentes juntos, desde Docker hasta complementos CNI como Calico o Flannel. Proyectos como Kubespray, KubeOne, kops y kube-aws simplifican un poco el proceso, pero no están exentos de inconvenientes.



Implementamos nuestros clústeres en máquinas virtuales RHEL usando Kubespray. Ha demostrado ser excelente. Kubespray tenía scripts para crear, agregar o eliminar nodos, actualizar una versión y casi todo lo que necesita para trabajar con Kubernetes en producción. Dicho esto, el script de actualización iba acompañado de la advertencia de que ni siquiera las versiones menores deben omitirse. En otras palabras, para llegar a la versión deseada, el usuario tenía que instalar todas las intermedias.



La principal conclusión aquí es que si planea usar o ya está usando Kubernetes, piense en los pasos del ciclo de vida de su K8 y cómo encaja en su solución. A menudo, es más fácil crear y ejecutar un clúster que mantenerlo actualizado.



3. Construir e implementar



Esté preparado para el hecho de que tendrá que revisar las canalizaciones de construcción e implementación. Con la transición a Kubernetes, hemos experimentado una transformación radical de estos procesos. No solo reestructuramos las canalizaciones de Jenkins, sino que, con la ayuda de herramientas como Helm, desarrollamos nuevas estrategias para crear y trabajar con Git, etiquetar imágenes de Docker y crear versiones de gráficos de Helm.



Necesitará una estrategia única para mantener su código, los archivos de implementación de Kubernetes, los archivos Dockerfiles, las imágenes de Docker, los gráficos de Helm y una forma de unirlos todos.



Después de varias iteraciones, nos decidimos por el siguiente diagrama:



  • El código de la aplicación y sus gráficos de Helm se encuentran en diferentes repositorios. Esto nos permite versionarlos independientemente unos de otros ( versionado semántico ).
  • , , . , , app-1.2.0 charts-1.1.0. (values) Helm, patch- (, 1.1.0 1.1.1). (RELEASE.txt) .
  • , Apache Kafka Redis ( ), . , Docker- Helm-. Docker- , .


(. .: Open Source- Kubernetes — werf — , .)



4. Liveness Readiness ( )



Las verificaciones de disponibilidad y vitalidad de Kubernetes son excelentes para tratar de forma autónoma los problemas del sistema. Pueden reiniciar contenedores en caso de fallas y redirigir el tráfico de instancias "en mal estado". Pero en algunas circunstancias, estas comprobaciones pueden convertirse en un arma de doble filo y afectar el inicio y la recuperación de la aplicación (esto es especialmente cierto para aplicaciones con estado, como plataformas de mensajería o bases de datos).



Nuestro Kafka se convirtió en su víctima. Teníamos un conjunto con estado de 3 Brokery 3 Zookeepercon replicationFactor= 3 yminInSyncReplica= 2. El problema ocurrió al reiniciar Kafka después de fallas o fallas aleatorias. Al inicio, Kafka ejecutó scripts adicionales para reparar índices dañados, lo que tomó de 10 a 30 minutos, dependiendo de la gravedad del problema. Este retraso provocó que las pruebas de actividad fallaran continuamente, lo que provocó que Kubernetes "matara" y reiniciara Kafka. Como resultado, Kafka no solo pudo arreglar los índices, sino incluso comenzar.



La única solución en ese momento era ajustar el parámetro initialDelaySecondsen la configuración de la prueba de vida para que las verificaciones se llevaran a cabo solo después de que se lanzara el contenedor. El principal desafío, por supuesto, es decidir qué retraso establecer. Los inicios individuales después de una falla pueden demorar hasta una hora, y esto debe tenerse en cuenta. Por otro lado, cuanto másinitialDelaySeconds, el Kubernetes más lento responderá a fallas durante el inicio del contenedor.



En este caso, el punto initialDelaySecondsóptimo es el valor que mejor se adapta a sus requisitos de resistencia y, al mismo tiempo, le da a la aplicación el tiempo suficiente para iniciarse con éxito en todas las situaciones de falla (fallas de disco, problemas de red, fallas del sistema, etc.)



Actualización : en versiones recientes de Kubernetes, ha aparecido un tercer tipo de prueba llamada sonda de inicio. Está disponible como versión alfa desde la versión 1.16 y como versión beta desde la versión 1.18.



Startup Probe resuelve el problema anterior al deshabilitar las verificaciones de preparación y vitalidad hasta que se inicia el contenedor, lo que permite que la aplicación se inicie normalmente.


5. Trabajar con IP externa



Resulta que el uso de IP externas estáticas para acceder a los servicios ejerce una presión significativa sobre el mecanismo de seguimiento de conexiones del kernel. Si no lo piensa detenidamente, puede "romperse".



En nuestro clúster, utilizamos Calicotanto CNI BGPcomo un protocolo de enrutamiento, así como para interactuar con los enrutadores fronterizos. El modo proxy de Kube está habilitado iptables. Abrimos el acceso a nuestro muy ocupado servicio en Kubernetes (procesa millones de conexiones todos los días) a través de una IP externa. Debido al SNAT y el enmascaramiento que proviene de las redes definidas por software, Kubernetes necesita un mecanismo para realizar un seguimiento de todos estos flujos lógicos. Para este K8s utiliza estas herramientas básicas como onntrackynetfilter... Con su ayuda, gestiona las conexiones externas a una IP estática, que luego se convierte a la IP interna del servicio y finalmente a la dirección IP del pod. Y todo esto se hace usando una tabla conntracke iptables.



Sin embargo, las posibilidades de la mesa no son conntrackilimitadas. Cuando se alcanza el límite, el clúster de Kubernetes (más precisamente, el kernel del sistema operativo en su núcleo) ya no podrá aceptar nuevas conexiones. En RHEL, este límite se puede comprobar de la siguiente manera:



$  sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_maxnet.netfilter.nf_conntrack_count = 167012
net.netfilter.nf_conntrack_max = 262144


Una forma de evitar esta limitación es combinar varios nodos con enrutadores de borde para que las conexiones entrantes a una IP estática se distribuyan en todo el clúster. Si tiene una gran flota de máquinas en su clúster, este enfoque puede aumentar significativamente el tamaño de la tabla conntrackpara manejar una gran cantidad de conexiones entrantes.



Esto nos confundió por completo cuando comenzamos en 2017. Sin embargo, hace relativamente poco tiempo (en abril de 2019), el proyecto Calico publicó un estudio detallado con el título apropiado " Por qué conntrack ya no es tu amigo " (existe una traducción al ruso, aprox. Transl.) .



¿Realmente necesitas Kubernetes?



Han pasado tres años, pero seguimos descubriendo / aprendiendo algo nuevo todos los días. Kubernetes es una plataforma compleja con su propio conjunto de desafíos, especialmente en el área de iniciar el entorno y mantenerlo en funcionamiento. Cambiará su pensamiento, arquitectura, actitud hacia el diseño. Tendrá que lidiar con la ampliación y actualización de los equipos.



Por otro lado, trabajar en la nube y poder usar Kubernetes como un servicio te ahorrará la mayoría de las preocupaciones asociadas con el mantenimiento de la plataforma (como extender el CIDR de la red interna y actualizar Kubernetes).



Hoy hemos llegado a comprender que la principal pregunta que debemos hacernos es realmente¿necesitas Kubernetes? Lo ayudará a evaluar qué tan global es el problema y si Kubernetes lo ayudará a enfrentarlo.



La cuestión es que mudarse a Kubernetes es caro. Por lo tanto, las ventajas de su caso de uso (y cuánto y cómo aprovecha la plataforma) deberían justificar el precio que paga. Si es así, Kubernetes puede mejorar significativamente su productividad.



Recuerde que la tecnología por la tecnología no tiene sentido.



PD del traductor



Lea también en nuestro blog:






All Articles