Saltar al contenido

Temas Avanzados de Kubernetes: Contenedores de Inicialización

Descubre cómo los Init Containers pueden proporcionar capacidades adicionales a tus cargas de trabajo en Kubernetes

Foto de Rinson Chory en Unsplash

Hay muchos nuevos desafíos que vienen con el nuevo patrón de desarrollo de una manera mucho más distribuida y colaborativa, y cómo gestionamos las dependencias es crucial para nuestro éxito.

Kubernetes y las distribuciones dedicadas se han convertido en el nuevo estándar de implementación para nuestra aplicación nativa de la nube y proporcionan muchas características para gestionar esas dependencias. Pero, por supuesto, el recurso más habitual que utilizarás para hacer eso son las sondas.

Kubernetes proporciona diferentes tipos de sondas que permitirán a la plataforma conocer el estado de tu aplicación. Nos ayudará a saber si nuestra aplicación está «viva» (sonda de vivacidad), ha sido iniciada (sonda de inicio) y si está lista para procesar solicitudes (sonda de preparación).

Ciclo de vida de las sondas de Kubernetes por Andrew Lock (https://andrewlock.net/deploying-asp-net-core-applications-to-kubernetes-part-6-adding-health-checks-with-liveness-readiness-and-startup-probes/)

Las sondas de Kubernetes son la forma estándar de hacerlo, y si has implementado alguna carga de trabajo en un clúster de Kubernetes, probablemente hayas usado una. Pero hay ocasiones en las que esto no es suficiente.

Eso puede ser porque la sonda que te gustaría hacer es demasiado compleja o porque te gustaría crear algún orden de inicio entre tus componentes. Y en esos casos, confías en otra herramienta: los Init Containers.

Los Init Containers son otro tipo de contenedor en el que tienen su propia imagen que puede tener cualquier herramienta que puedas necesitar para establecer los diferentes chequeos o sondas que te gustaría realizar.

Tienen las siguientes características únicas:

  • Los init containers siempre se ejecutan hasta completarse.
  • Cada init container debe completarse con éxito antes de que comience el siguiente.

¿Por qué usarías init containers?

#1 .- Gestionar Dependencias

El primer caso de uso para utilizar init containers es definir una relación de dependencias entre dos componentes como Deployments, y necesitas que uno comience antes que el otro. Imagina la siguiente situación:

Tenemos dos componentes: una aplicación web y una base de datos; ambos se gestionan como contenedores en la plataforma. Así que si los implementas de la manera habitual, ambos intentarán comenzar simultáneamente, o el programador de Kubernetes definirá el orden, por lo que podría ser posible que la aplicación web intente comenzar cuando la base de datos no esté disponible.

Podrías pensar que eso no es un problema porque para eso tienes una sonda de preparación o de vivacidad en tus contenedores, y tienes razón: el Pod no estará listo hasta que la base de datos esté lista, pero hay varias cosas a tener en cuenta aquí:

  • Ambas sondas tienen un límite de intentos; después de eso, entrarás en un escenario de CrashLoopBack, y el pod no intentará comenzar de nuevo hasta que lo reinicies manualmente.
  • El pod de la aplicación web consumirá más recursos de los necesarios cuando sabes que la aplicación no comenzará en absoluto. Así que, al final, estás desperdiciando recursos en el proceso.

Por lo tanto, definir un init container como parte de la implementación de la aplicación web que verifique si la base de datos está disponible, tal vez solo incluyendo un cliente de base de datos para ver rápidamente si la base de datos y todas las tablas están adecuadamente pobladas, será suficiente para resolver ambas situaciones.

#2 .- Optimización de recursos

Una cosa crítica cuando defines tus contenedores es asegurarte de que tienen todo lo que necesitan para realizar su tarea y que no tienen nada que no sea necesario para ese propósito.

Así que en lugar de agregar más herramientas para verificar el comportamiento del componente, especialmente si esto es algo para gestionar en momentos específicos, puedes descargar eso a un init container y mantener el contenedor principal más optimizado en términos de tamaño y recursos utilizados.

#3.- Precarga de datos

A veces necesitas realizar algunas actividades al inicio de tu aplicación, y puedes separar eso del trabajo habitual de tu aplicación, por lo que te gustaría evitar el tipo de lógica para verificar si el componente ha sido inicializado o no.

Usando este patrón, tendrás un init container gestionando todo el trabajo de inicialización y asegurando que todo el trabajo de inicialización se haya realizado cuando se ejecute el contenedor principal.

Ejemplo del proceso de inicialización (https://www.magalix.com/blog/kubernetes-patterns-the-init-container-pattern)

¿Cómo definir un Init Container?

Para poder definir un init container, necesitas usar una sección específica de la sección de especificaciones de tu archivo YAML, como se muestra en la imagen a continuación:

apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
  - name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'echo ¡La aplicación está en ejecución! && sleep 3600']
initContainers:
  - name: init-myservice
image: busybox:1.28
command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo esperando a myservice; sleep 2; done"]
  - name: init-mydb
image: busybox:1.28
command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo esperando a mydb; sleep 2; done"]

Puedes definir tantos Init Containers como necesites. Sin embargo, notarás que todos los init containers se ejecutarán en secuencia como se describe en el archivo YAML, y un init container solo puede ejecutarse si el anterior se ha completado con éxito.

Resumen

Espero que este artículo te haya proporcionado una nueva forma de definir la gestión de tus dependencias entre cargas de trabajo en Kubernetes y, al mismo tiempo, también otros grandes casos de uso donde la capacidad del init container puede aportar valor a tus cargas de trabajo.

Etiquetas:

Kubernetes Advanced Topics: Init Containers

Discover how Init Containers can provide additional capabilities to your workloads in Kubernetes

Photo by Rinson Chory on Unsplash

There are a lot of new challenges that come with the new development pattern in a much more distributed and collaborative way, and how we manage the dependencies is crucial to our success.

Kubernetes and dedicated distributions have become the new standard of deployment for our cloud-native application and provide many features to manage those dependencies. But, of course, the most usual resource you will use to do that is the probes.

Kubernetes provides different kinds of probes that will let the platform know the status of your app. It will help us to tell if our application is “alive” (liveness probe), has been started (startup probe), and if it is ready to process requests (readiness probe).

Kubernetes Probes lifecycle by Andrew Lock (https://andrewlock.net/deploying-asp-net-core-applications-to-kubernetes-part-6-adding-health-checks-with-liveness-readiness-and-startup-probes/)

Kubernetes Probes are the standard way of doing so, and if you have deployed any workload to a Kubernetes cluster, you probably have used one. But there are some times that this is not enough.

That can be because the probe you would like to do is too complex or because you would like to create some startup order between your components. And in that cases, you rely on another tool: Init Containers.

The Init Containers are another kind of container in that they have their image that can have any tool that you could need to establish the different checks or probes that you would like to perform.

They have the following unique characteristics:

  • Init containers always run to completion.
  • Each init container must complete successfully before the next one starts.

Why would you use init containers?

#1 .- Manage Dependencies

The first use-case to use init containers is to define a relationship of dependencies between two components such as Deployments, and you need for one to the other to start. Imagine the following situation:

We have two components: a Web App and a Database; both are managed as containers in the platform. So if you deploy them in the usual way, both of them will try to start simultaneously, or the Kubernetes scheduler will define the order, so it could be possible the situation of the web app will try to start when the database is not available.

You could think that is not an issue because this is why you have a readiness or a liveness probe in your containers, and you are right: Pod will not be ready until the database is ready, but there are several things to note here:

  • Both probes have a limit of attempts; after that, you will enter into a CrashLoopBack scenario, and the pod will not try to start again until you manually restart it.
  • Web App pod will consume more resources than needed when you know the app will not start at all. So, in the end, you are wasting resources on the process.

So defining an init container as part of the web app deployment that checks if the database is available, maybe just including a database client to quickly see if the database and all the tables are appropriately populated, will be enough to solve both situations.

#2 .- Optimizing resources

One critical thing when you define your containers is to ensure that they have everything they need to perform their task and that they don’t have anything that is not required for that purpose.

So instead of adding more tools to check the component’s behavior, especially if this is something to manage at specific times, you can offload that to an init container and keep the main container more optimized in terms of size and resources used.

#3.- Preloading data

Sometimes you need to do some activities at the beginning of your application, and you can separate that for the usual work of your application, so you would like to avoid the kind of logic to check if the component has been initialized or not.

Using this pattern, you will have an init container managing all the initialization work and ensuring that all the initialization work has been performed when the main container is executed.

Initialization process sample (https://www.magalix.com/blog/kubernetes-patterns-the-init-container-pattern)

How to define an Init Container?

To be able to define an init container, you need to use a specific section of the specification section of your YAML file, as it is shown in the picture below:

apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
  - name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
initContainers:
  - name: init-myservice
image: busybox:1.28
command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]
  - name: init-mydb
image: busybox:1.28
command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done"]

You can define as many Init Containers as you need. Still, you will note that all the init containers will run in sequence as described in the YAML file, and one init container can only be executed if the previous one has been completed successfully.

Summary

I hope this article will have provided you with a new way to define the management of your dependencies between workloads in Kubernetes and, at the same time, also other great use-cases where the capability of the init container can provide value to your workloads.