Capítulo 1. Presentación de Helm

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

Helm es el gestor de paquetes para Kubernetes. Así es como los desarrolladores de Helm han descrito Helm desde los primeros commits al repositorio Git. Y esa frase es el tema de este capítulo.

En este capítulo, empezaremos con una mirada conceptual al ecosistema nativo de la nube, en el que Kubernetes es una tecnología clave. Daremos un nuevo vistazo a lo que ofrece Kubernetes para preparar el terreno para describir Helm.

A continuación, veremos los problemas que Helm pretende resolver. En esta sección, veremos el concepto de gestión de paquetes y por qué hemos modelado Helm de esta manera. También visitaremos algunas de las facetas únicas de la instalación de paquetes en una herramienta de gestión de clústeres como Kubernetes.

Por último, terminaremos el capítulo con una visión de alto nivel de la arquitectura de Helm, centrándonos en los conceptos de gráficos, plantillas y versiones. Al final del capítulo, comprenderás cómo encaja Helm en el ecosistema más amplio de herramientas, y estarás familiarizado con la terminología y los conceptos que utilizaremos a lo largo de este libro.

El ecosistema nativo de la nube

La aparición de las tecnologías en la nube ha cambiado claramente la forma en que el sector considera el hardware, la gestión de sistemas, las redes físicas, etc. Las máquinas virtuales sustituyeron a los servidores físicos, los servicios de almacenamiento desplazaron a los discos duros y las herramientas de automatización ganaron protagonismo. Esto fue quizás un cambio temprano en la forma en que la industria conceptualizaba la nube. Pero a medida que se fueron aclarando los puntos fuertes y débiles de este nuevo enfoque, también empezaron a cambiar las prácticas de diseño de aplicaciones y servicios.

Los desarrolladores y operadores empezaron a cuestionarse la práctica de crear grandes aplicaciones de un solo binario que se ejecutaban en hardware de gran potencia. Reconocieron la dificultad de compartir datos entre distintas aplicaciones conservando la integridad de los datos. El bloqueo distribuido, el almacenamiento y el almacenamiento en caché se convirtieron en problemas corrientes en lugar de puntos de interés académico. Los grandes paquetes de software se dividieron en pequeños ejecutables discretos. Y, como suele decir el fundador de Kubernetes, Brendan Burns, "la informática distribuida pasó de ser un tema avanzado a Ciencias de la Computación 101".

El término nativo de la nube capta este cambio cognitivo en lo que podríamos llamar nuestra visión arquitectónica de la nube. Cuando diseñamos nuestros sistemas en torno a las capacidades y limitaciones de la nube, estamos diseñando sistemas nativos de la nube.

Contenedores y Microservicios

En el corazón mismo de la computación nativa en la nube está esta perspectiva filosófica de que los servicios independientes discretos más pequeños son preferibles a los grandes servicios monolíticos que lo hacen todo. En lugar de escribir una sola aplicación grande que lo maneje todo, desde generar la interfaz de usuario hasta procesar colas de tareas e interactuar con bases de datos y cachés, el enfoque nativo de la nube consiste en escribir una serie de servicios más pequeños, cada uno con un propósito relativamente especial, y luego unir estos servicios para que sirvan a un propósito de nivel superior. En un modelo así, un servicio podría ser el único usuario de una base de datos relacional. Los servicios que deseen acceder a los datos se pondrán en contacto con ese servicio a través de (normalmente) una API de transferencia de estado representacional (REST) . Y, utilizando la Notación de Objetos JavaScript (JSON) sobre HTTP, estos otros servicios consultarán y actualizarán los datos.

Este desglose permite a los desarrolladores ocultar la implementación de bajo nivel y, en su lugar, ofrecer un conjunto de funciones específicas de la lógica empresarial de la aplicación más amplia.

Microservicios

Donde antes una aplicación consistía en un único ejecutable que hacía todo el trabajo, las aplicaciones nativas de la nube son aplicaciones distribuidas. Mientras que los programas separados asumen cada uno la responsabilidad de una o dos tareas discretas, todos juntos forman una única aplicación lógica.

Con toda esta teoría, un ejemplo sencillo puede explicar mejor cómo funciona. Imagina un sitio web de comercio electrónico. Podemos pensar en varias tareas que componen conjuntamente este tipo de sitio web. Hay un catálogo de productos, cuentas de usuario y carros de la compra, un procesador de pagos que gestiona el proceso de las transacciones monetarias, sensible a la seguridad, y un frontend a través del cual los clientes ven los artículos y seleccionan sus compras. También hay una interfaz administrativa en la que los propietarios de la tienda gestionan el inventario y tramitan los pedidos.

Históricamente, las aplicaciones de este tipo se construían como un único programa. El código responsable de cada una de estas unidades de trabajo se compilaba en un gran ejecutable, que a menudo se ejecutaba en una única pieza de hardware de gran tamaño.

Sin embargo, el enfoque nativo en la nube para una aplicación de este tipo consiste en dividir esta aplicación de comercio electrónico en varias piezas. Una gestiona las transacciones de pago. Otra gestiona el catálogo de productos. Otra proporciona la administración, y así sucesivamente. Estos servicios se comunican entre sí a través de la red mediante API REST bien definidas.

Llevado al extremo, una aplicación se descompone en sus partes constituyentes más pequeñas, y cada parte es un programa. Esto es la arquitectura de microservicios. En el extremo opuesto del espectro de una aplicación monolítica, un microservicio es responsable de gestionar sólo una pequeña parte del procesamiento global de la aplicación.

El concepto de microservicio ha influido enormemente en la evolución de la computación nativa en la nube. Y en ninguna parte es esto más evidente que en la aparición de la informática de contenedores.

Contenedores

Es habitual comparar y contrastar un contenedor y una máquina virtual. Una máquina virtual ejecuta un sistema operativo completo en un entorno aislado en una máquina anfitriona. Un contenedor, en cambio, tiene su propio sistema de archivos, pero se ejecuta en el mismo núcleo del sistema operativo que el anfitrión.

Pero hay una segunda forma de conceptualizar el contenedor, que puede resultar más beneficiosa para el presente debate. Como su nombre indica, un contenedor es una forma útil de empaquetar el entorno de ejecución de un único programa, de forma que se garantice que el ejecutable satisface todas sus dependencias cuando se traslada de una máquina a otra.

Este es un enfoque más filosófico, quizás, porque impone algunas restricciones no técnicas a un contenedor. Por ejemplo, se podría empaquetar una docena de programas diferentes en un solo contenedor y ejecutarlos todos al mismo tiempo. Pero los contenedores, al menos tal y como fueron diseñados por Docker, se concibieron como un vehículo para un programa de nivel superior.

Nota

Cuando aquí hablamos de programas, en realidad estamos pensando en un nivel de abstracción superior al de "un binario". La mayoría de los contenedores Docker tienen al menos unos cuantos ejecutables que están ahí simplemente para ayudar al programa principal. Pero estos ejecutables son auxiliares de la función principal del contenedor. Por ejemplo, un servidor web puede necesitar algunas otras utilidades locales para arrancar o realizar tareas de bajo nivel (Apache, por ejemplo, tiene herramientas para módulos), pero es el propio servidor web el programa principal.

Los contenedores y los microservicios son, por su diseño, una combinación perfecta. Los pequeños programas discretos pueden empaquetarse, junto con todas sus dependencias, en esbeltos contenedores. Y esos contenedores pueden moverse de un host a otro. Cuando se ejecuta un contenedor, no es necesario que el anfitrión disponga de todas las herramientas necesarias para ejecutar el programa, porque todas esas herramientas están empaquetadas dentro del contenedor. El anfitrión sólo debe tener la capacidad de ejecutar contenedores.

Por ejemplo, si un programa se construye en Python 3, el anfitrión no necesita instalar Python, configurarlo y luego instalar todas las bibliotecas que requiere el programa. Todo eso está empaquetado en el contenedor. Cuando el anfitrión ejecuta el contenedor, la versión correcta de Python 3 y cada una de las bibliotecas necesarias ya están almacenadas en el contenedor.

Llevando esto un paso más allá, un host puede ejecutar libremente contenedores con requisitos en competencia. Un programa Python 2 en contenedor puede ejecutarse en el mismo anfitrión que un requisito Python 3 en contenedor, ¡y los administradores del anfitrión no necesitan hacer ningún trabajo especial para configurar estos requisitos contrapuestos!

Estos ejemplos ilustran una de las características del ecosistema nativo de la nube: los administradores, operadores e ingenieros de fiabilidad del sitio (SRE) de ya no se dedican a gestionar las dependencias de los programas. En su lugar, son libres de centrarse en un nivel superior de asignación de recursos. En lugar de preocuparse por qué versiones de Python, Ruby y Node se ejecutan en distintos servidores, los operadores pueden centrarse en si los recursos de red, almacenamiento y CPU están correctamente asignados para estas cargas de trabajo en contenedores.

Ejecutar un programa completamente aislado a veces es útil. Pero lo más frecuente es que queramos exponer algunos aspectos de este contenedor al mundo exterior. Queremos darle acceso al almacenamiento. Queremos permitirle responder a conexiones de red. Y queremos inyectar fragmentos de configuración en el contenedor en función de nuestras necesidades actuales. Todas estas tareas (y más aún) las proporciona el tiempo de ejecución del contenedor. Cuando un contenedor declara que tiene un servicio que escucha internamente en el puerto 8080, el tiempo de ejecución del contenedor puede concederle acceso en el puerto 8000 del host. Así, cuando el host recibe una petición de red en el puerto 8000, el contenedor lo ve como una petición en su puerto 8080. Del mismo modo, un anfitrión puede montar un sistema de archivos en el contenedor, o establecer variables de entorno específicas dentro del contenedor. De este modo, un contenedor puede participar en el entorno más amplio que le rodea, incluyendo no sólo otros contenedores de ese host, sino servicios remotos de la red local o incluso de Internet.

Imágenes y registros de contenedores

La tecnología de contenedores es un espacio sofisticado y fascinante por derecho propio. Pero para nuestros propósitos, sólo necesitamos entender algunas cosas más sobre cómo funcionan los contenedores antes de poder pasar a la siguiente capa de la pila nativa de la nube.

Como hemos comentado en la sección anterior, un contenedor es un programa junto con sus dependencias y entorno. Todo esto puede empaquetarse junto en una representación portátil llamada imagen de contenedor (a menudo denominada simplemente imagen). Las imágenes no se empaquetan en un gran binario, sino en capas discretas, cada una de las cuales tiene su propio identificador único. Cuando se mueven las imágenes, se mueven como una colección de capas, lo que supone una gran ventaja. Si una máquina tiene una imagen con cinco capas y otra necesita la misma imagen, sólo tendrá que buscar las capas que no tenga ya. Así, si ya tiene dos de las cinco capas, sólo tendrá que recuperar tres capas para reconstruir todo el contenedor.

Hay una pieza crucial de tecnología que proporciona la capacidad de mover imágenes de contenedores de un lado a otro. Un registro de imágenes es una pieza especializada de tecnología de almacenamiento que aloja contenedores, poniéndolos a disposición de los anfitriones. Un anfitrión puede empujar una imagen de contenedor a un registro, que transfiere las capas al registro. Y luego otro anfitrión puede extraer la imagen del registro al entorno del anfitrión, tras lo cual el anfitrión puede ejecutar el contenedor.

El registro gestiona las capas. Cuando un servidor solicita una imagen, el registro le informa de qué capas la componen. El anfitrión puede entonces determinar qué capas (si las hay) faltan y, posteriormente, descargar sólo esas capas delregistro.

Un registro utiliza hasta tres datos para identificar una imagen concreta:

Nombre

Un nombre de imagen puede ir de simple a complejo, dependiendo del registro que almacene la imagen: nginx servers/nginx , o example.com/servers/nginx.

Etiqueta

La etiqueta suele referirse a la versión del software instalado (v1.2.3), aunque las etiquetas son en realidad cadenas arbitrarias. Las etiquetas latest y stable suelen utilizarse para indicar "la versión más reciente" y "la versión lista para producción más reciente", respectivamente.

Digiere

A veces es importante extraer una versión muy concreta de una imagen. Como las etiquetas son mutables, no hay garantía de que en un momento dado una etiqueta se refiera exactamente a una versión concreta del software. Por eso, los registros admiten la obtención de imágenes por compendio, que es un compendio SHA-256 o SHA-512 de la información de la capa de la imagen.

A lo largo de este libro, veremos imágenes referenciadas utilizando los tres datos anteriores. El formato canónico para combinarlas es name:tag@digest, donde sólo se requiere name. Así, example.com/servers/nginx:latest dice "dame la etiqueta latest para la imagen llamada example.com/servers/nginx." Y

example.com/my/app@sha256:
a428de44a9059feee59237a5881c2d2cffa93757d99026156e4ea544577ab7f3

dice "dame example.com/my/app con el resumen exacto que se da aquí".

Aunque hay mucho más que aprender sobre imágenes y contenedores, ahora tenemos suficientes conocimientos para pasar al siguiente tema importante: los programadores. Y en esa sección, descubriremos Kubernetes.

Horarios y Kubernetes

En la sección anterior vimos cómo los contenedores encapsulan programas individuales y su entorno necesario. Los contenedores pueden ejecutarse localmente en estaciones de trabajo o remotamente en servidores.

Cuando los desarrolladores empezaron a empaquetar sus aplicaciones en contenedores y los operadores empezaron a utilizar los contenedores como artefacto para la implementación, surgió un nuevo conjunto de preguntas. ¿Cuál es la mejor manera de ejecutar montones de contenedores? ¿Cuál es la mejor manera de facilitar una arquitectura de microservicios en la que muchos contenedores tienen que trabajar juntos? ¿Cómo compartimos juiciosamente el acceso a elementos como el almacenamiento conectado a la red, los equilibradores de carga y las pasarelas? ¿Cómo gestionamos la inyección de información de configuración en muchos contenedores? Y quizás lo más importante, ¿cómo gestionamos recursos como la memoria, la CPU, el ancho de banda de la red y el espacio de almacenamiento?

Yendo incluso un nivel más allá, la gente empezó a preguntarse (basándose en sus experiencias con máquinas virtuales) cómo se podría gestionar la distribución de contenedores entre varios hosts, repartiendo la carga equitativamente sin dejar de utilizar juiciosamente los recursos. O, más sencillamente, ¿cómo hacer funcionar el menor número posible de máquinas y, al mismo tiempo, tantos contenedores como necesitemos?

En 2015, era el momento adecuado: Los contenedores Docker se estaban abriendo paso en la empresa. Y había una clara necesidad de una herramienta que pudiera gestionar la programación de contenedores y la gestión de recursos entre hosts. Múltiples tecnologías aparecieron en escena: Mesos introdujo Marathon; Docker creó Swarm; Hashicorp lanzó Nomad; y Google creó un hermano de código abierto para su plataforma interna Borg, y llamó a esta tecnología Kubernetes (la palabra griega para el capitán de un barco).

Todos estos proyectos proporcionaban una implementación de un sistema de gestión de contenedores en clúster que podía programar contenedores y conectarlos para alojar sofisticadas aplicaciones distribuidas similares a microservicios.

Cada uno de estos programadores tenía puntos fuertes y débiles. Pero Kubernetes introdujo dos conceptos que lo diferenciaron del resto: la infraestructura declarativa y el bucle de reconciliación.

Infraestructura declarativa

Considera el caso de la implementación de un contenedor. El proceso de implementación de un contenedor podría ser el siguiente: Creo el contenedor. Abro un puerto para que escuche, y luego adjunto algo de almacenamiento en este lugar concreto del sistema de archivos. Luego espero a que todo se inicialice. Luego pruebo si el contenedor está listo. Después lo marco como disponible.

En este enfoque, estamos pensando procedimentalmente al centrarnos en el proceso de configuración de un contenedor. Pero el diseño de Kubernetes es que pensemos declarativamente. Le decimos al programador (Kubernetes) cuál es nuestro estado deseado, y Kubernetes se encarga de convertir esa declaración en sus propios procedimientos internos.

Instalar un contenedor en Kubernetes, por tanto, es más una cuestión de decir: "Quiero que este contenedor se ejecute en este puerto con esta cantidad de CPU y algo de almacenamiento montado en esta ubicación del sistema de archivos". Kubernetes trabaja entre bastidores para cablearlo todo de acuerdo con nuestra declaración de lo que queremos.

El bucle de reconciliación

¿Cómo funciona Kubernetes entre bastidores para hacer todo esto? Cuando veíamos las cosas de forma procedimental, había un cierto orden de operaciones. ¿Cómo conoce Kubernetes el orden? Aquí es donde entra en juego la idea del bucle de reconciliación.

En un bucle de reconciliación, el programador dice "aquí está el estado deseado por el usuario. Aquí está el estado actual. No son iguales, así que tomaré medidas para reconciliarlos". El usuario quiere almacenamiento para el contenedor. Actualmente no hay almacenamiento adjunto. Así que Kubernetes crea una unidad de almacenamiento y la adjunta al contenedor. El contenedor necesita una dirección de red pública. No existe ninguna. Así que se adjunta una nueva dirección al contenedor. Los distintos subsistemas de Kubernetes trabajan para cumplir su parte individual de la declaración general del estado deseado por el usuario.

Finalmente, Kubernetes conseguirá crear el entorno deseado por el usuario o llegará a la conclusión de que no puede realizar los deseos del usuario. Mientras tanto, el usuario adopta un papel pasivo observando el clúster de Kubernetes y esperando a que alcance el éxito o marque la instalación como fallida.

Desde contenedores a pods, servicios, implementaciones, etc.

Aunque conciso, el ejemplo anterior es un poco engañoso. Kubernetes no trata necesariamente al contenedor como la unidad de trabajo. En su lugar, Kubernetes introduce una abstracción de nivel superior denominada pod. Un pod es una envoltura abstracta que describe una unidad discreta de trabajo. Un pod describe no sólo un contenedor, sino uno o más contenedores (así como su configuración y requisitos) que juntos realizan una unidad de trabajo:

apiVersion: v1 1
kind: Pod
metadata:
    name: example-pod
spec:
    containers: 2
    - image: "nginx:latest"
      name: example-nginx
1

Las dos primeras líneas definen el tipo de Kubernetes (v1 Pod).

2

Una vaina puede tener uno o varios contenedores.

Lo más frecuente es que un pod sólo tenga un contenedor. Pero a veces tienen contenedores que realizan alguna configuración previa para el contenedor principal, saliendo antes de que éste se conecte. Son los llamados contenedores init. Otras veces, hay contenedores que se ejecutan junto al contenedor principal y proporcionan servicios auxiliares. Son los llamados contenedores sidecar. Todos ellos se consideran parte del mismo pod.

Nota

En el código anterior, hemos escrito una definición de un recurso Kubernetes Pod. Estas definiciones, cuando se expresan como YAML o JSON, se denominan manifiestos. Un manifiesto puede contener uno o varios recursos Kubernetes (también llamados objetos o definiciones de recursos). Cada recurso está asociado a uno de los tipos de Kubernetes, como Pod oDeployment. En este libro, solemos utilizar recurso porque la palabra objeto está sobrecargada: YAML define la palabra objeto para referirse a una estructura clave/valor con nombre.

Un Pod describe qué configuración necesita el contenedor o contenedores (como puertos de red o puntos de montaje del sistema de archivos). La información de configuración en Kubernetes puede almacenarse en ConfigMaps o, para información sensible, en Secretos. Y la definición de Pod's puede entonces relacionar esos ConfigMaps y Secrets con variables de entorno o archivos dentro de cada contenedor. Cuando Kubernetes vea esas relaciones, intentará adjuntar y configurar los datos de configuración tal y como se describen en la definición de Pod:

apiVersion: v1 1
kind: ConfigMap
metadata:
    name: configuration-data
data: 2
    backgroundColor: blue
    title: Learning Helm
1

En este caso, hemos declarado un objeto v1 ConfigMap.

2

Dentro de data, declaramos algunos pares nombre/valor arbitrarios.

Un Secret es estructuralmente similar a un ConfigMap, excepto en que los valores de la sección data deben estar codificados en Base64.

Pods están vinculados a objetos de configuración (como ConfigMap o Secret) mediante volúmenes. En este ejemplo, tomamos el ejemplo anterior Pod y adjuntamos el Secret anterior:

apiVersion: v1
kind: Pod
metadata:
    name: example-pod
spec:
    volumes: 1
    - name: my-configuration
      configMap:
        name: configuration-data 2
    containers:
    - image: "nginx:latest"
      name: example-nginx
      env: 3
        - name: BACKGROUND_COLOR 4
          valueFrom:
            configMapKeyRef:
                name: configuration-data 5
                key: backgroundColor 6
1

La sección volumes indica a Kubernetes qué fuentes de almacenamiento necesita este pod.

2

El nombre configuration-data es el nombre de nuestro ConfigMap que creamos en el ejemplo anterior.

3

La sección env inyecta variables de entorno en el contenedor.

4

La variable de entorno se llamará BACKGROUND_COLOR dentro delcontenedor.

5

Este es el nombre del ConfigMap que utilizará. Este mapa debe estar en volumes si queremos utilizarlo como volumen del sistema de archivos.

6

Este es el nombre de la clave dentro de la sección data de ConfigMap.

Un pod es la descripción "primitiva" de una unidad de trabajo ejecutable, con contenedores como parte de ese pod. Pero Kubernetes introduce conceptos de orden superior.

Considera una aplicación web. Puede que no queramos ejecutar sólo una instancia de esta aplicación web. Si ejecutáramos sólo una, y fallara, nuestro sitio se caería. Y si quisiéramos actualizarla, tendríamos que averiguar cómo hacerlo sin que se cayera todo el sitio. Así, Kubernetes introdujo el concepto de Implementaciones. Un Deployment describe una aplicación como una colección de pods idénticos. El Deployment se compone de algunos datos de configuración de nivel superior, así como de una plantilla sobre cómo construir un pod de réplica.

Con un Deployment, podemos decirle a Kubernetes que cree nuestra aplicación con un único pod. Luego podemos escalarla hasta cinco pods. Y de nuevo a tres. Podemos adjuntar un HorizontalPodAutoscaler (otro tipo de Kubernetes) y configurarlo para que escale nuestro pod en función del uso de recursos. Y cuando actualicemos la aplicación, el Deployment puede emplear varias estrategias para actualizar incrementalmente pods individuales sin que se caiga toda nuestra aplicación:

apiVersion: apps/v1 1
kind: Deployment
metadata:
    name: example-deployment
    labels:
        app: my-deployment
spec:
    replicas: 3 2
    selector:
        matchLabels:
            app: my-deployment
    template: 3
        metadata:
            labels:
                app: my-deployment
        spec:
            containers:
            - image: "nginx:latest"
              name: example-nginx
1

Se trata de un objeto apps/v1 Deployment.

2

Dentro de la especificación, pedimos tres réplicas de la siguiente template.

3

La plantilla especifica el aspecto que debe tener cada vaina de réplica.

Cuando se trata de adjuntar una aplicación Kubernetes a otras cosas en la red, Kubernetes proporciona definiciones de Servicio. Un Service es un recurso de red persistente (algo así como una IP estática) que persiste incluso si el pod o los pods conectados a él desaparecen. De este modo, los Podde Kubernetes pueden ir y venir, mientras que la capa de red puede seguir dirigiendo el tráfico al mismo punto final Service. Aunque Service es un concepto abstracto de Kubernetes, entre bastidores puede implementarse como cualquier cosa, desde una regla de enrutamiento hasta un equilibrador de carga externo:

apiVersion: v1 1
kind: Service
metadata:
  name: example-service
spec:
  selector:
    app: my-deployment 2
  ports:
    - protocol: TCP 3
      port: 80
      targetPort: 8080
1

El tipo es v1 Service.

2

Este Service se dirigirá a los pods con la etiqueta app: my-deployment.

3

El tráfico TCP al puerto 80 de este Service se dirigirá al puerto 8080 de los pods que coincidan con la etiqueta app: my-deployment.

El Service descrito dirigirá el tráfico al Deployment que hemos creado antes.

Hemos presentado algunos de los muchos tipos de Kubernetes. Hay docenas más que podríamos cubrir, pero los más utilizados con diferencia son Pod, Deployment, ConfigMap, Secret, y Service. En el próximo capítulo empezaremos a trabajar con estos conceptos más directamente. Pero por ahora, armados con algo de información genérica, podemos presentar Helm.

Objetivos de Helm

Hasta este punto, nos hemos centrado en el ecosistema nativo de la nube más amplio y en el papel de Kubernetes dentro de ese ecosistema. En esta sección, cambiaremos el enfoque hacia Helm.

En la sección anterior, vimos varios recursos distintos de Kubernetes: A Pod, aConfigMap, un Deployment, y un Service. Cada uno de ellos desempeña alguna función discreta. Pero una aplicación suele necesitar más de uno de ellos.

Por ejemplo, el sistema CMS WordPress puede ejecutarse dentro de Kubernetes. Pero normalmente necesitaría al menos un Deployment (para el servidor de WordPress), un ConfigMap para la configuración y probablemente un Secret (para guardar las contraseñas), unos cuantos objetos Service, un StatefulSet que ejecute una base de datos y unas cuantas reglas de control de acceso basado en roles (RBAC). Ya una descripción en Kubernetes de un sitio básico de WordPress abarcaría miles de líneas de YAML. En el núcleo mismo de Helm está la idea de que todos esos objetos pueden empaquetarse para instalarse, actualizarse y eliminarse juntos.

Cuando escribimos Helm, teníamos tres objetivos principales:

  1. Facilitar el paso de "cero a Kubernetes"

  2. Proporcionar un sistema de gestión de paquetes como tienen los sistemas operativos

  3. Hacer hincapié en la seguridad y la configurabilidad para la implementación de aplicaciones en Kubernetes

Analizaremos cada uno de estos tres objetivos, y luego echaremos un vistazo a otro aspecto del uso de Helm: su participación en la historia de la gestión del ciclo de vida.

De Cero a Kubernetes

El proyecto Helm comenzó en 2015, unos meses antes de la KubeCon inaugural. Kubernetes era difícil de configurar, y a menudo requería que los nuevos usuarios compilaran el código fuente de Kubernetes y luego utilizaran algunos scripts de shell para ponerlo en marcha. Y una vez que el clúster estaba en marcha, se esperaba que los nuevos usuarios escribieran YAML (como hicimos en secciones anteriores) desde cero. Había pocos ejemplos básicos y ningún ejemplo listo para la producción.

Queríamos invertir el ciclo de aprendizaje: en lugar de exigir a los usuarios que empezaran con ejemplos básicos e intentaran construir sus propias aplicaciones, queríamos proporcionar a los usuarios ejemplos listos para la producción. Los usuarios podían instalar esos ejemplos, verlos en acción y luego aprender cómo funcionaba Kubernetes.

Esa era, y sigue siendo a día de hoy, nuestra primera prioridad con Helm: facilitar la puesta en marcha con Kubernetes. En nuestra opinión, un nuevo usuario de Helm con un clúster de Kubernetes existente debería poder pasar de la descarga a una aplicación instalada en cinco minutos o menos.

Pero Helm no es sólo una herramienta de aprendizaje. Es un gestor de paquetes.

Gestión de paquetes

Kubernetes es como un sistema operativo. En su base, un sistema operativo proporciona un entorno para ejecutar programas. Proporciona las herramientas necesarias para almacenar, ejecutar y monitorizar el ciclo de vida de un programa.

En lugar de ejecutar programas, ejecuta contenedores. Pero, al igual que un sistema operativo, proporciona las herramientas necesarias para almacenar, ejecutar y monitorizar esos contenedores.

La mayoría de los sistemas operativos cuentan con un gestor de paquetes. El trabajo de del gestor de paquetes es facilitar la búsqueda, instalación, actualización y eliminación de los programas de un sistema operativo. Los gestores de paquetes proporcionan una semántica para agrupar programas en aplicaciones instalables, y proporcionan un esquema para almacenar y recuperar paquetes, así como para instalarlos y gestionarlos.

Al concebir Kubernetes como un sistema operativo, vimos rápidamente la necesidad de un gestor de paquetes Kubernetes. Desde el primer commit al repositorio de código fuente de Helm, hemos aplicado sistemáticamente la metáfora de la gestión de paquetes a Helm:

  • Helm proporciona repositorios de paquetes y capacidades de búsqueda para encontrar qué aplicaciones Kubernetes están disponibles.

  • Helm dispone de los conocidos comandos de instalación, actualización y eliminación.

  • Helm define un método para configurar los paquetes antes de instalarlos.

  • Además, Helm tiene herramientas para ver lo que ya está instalado y cómo está configurado.

Inicialmente modelamos Helm a partir de Homebrew (un gestor de paquetes para macOS) y Apt (el gestor de paquetes para Debian). Pero a medida que Helm ha ido madurando, hemos intentado aprender de tantos gestores de paquetes diferentes como hemos podido.

Existen algunas diferencias entre los sistemas operativos típicos y Kubernetes. Una de ellas es que Kubernetes permite ejecutar muchas instancias de la misma aplicación. Mientras que yo puedo instalar la base de datos MariaDB una sola vez en mi estación de trabajo, un clúster de Kubernetes podría estar ejecutando decenas, cientos o incluso miles deinstalaciones deMariaDB, cada unacon una configuración diferente o incluso con una versión distinta.

Otra noción poco frecuente en los sistemas operativos típicos, pero fundamental en Kubernetes, es la idea de espacio de nombres. En Kubernetes, un espacio de nombres es un mecanismo de agrupación arbitrario que define un límite entre las cosas que están dentro del espacio de nombres y las que están fuera. Hay muchas formas diferentes de organizar los recursos con espacios de nombres, pero a menudo se utilizan como un accesorio al que se adjunta la seguridad de . Por ejemplo, quizá sólo determinados usuarios puedan acceder a los recursos dentro de un espacio de nombres.

Éstas son sólo algunas de las diferencias entre Kubernetes y los sistemas operativos tradicionales. Estas y otras diferencias han planteado retos en el diseño de Helm. Hemos tenido que construir Helm para aprovechar estas diferencias, pero sin renunciar a nuestra metáfora de gestión de paquetes.

Por ejemplo, el comando de instalación de Helm requiere no sólo el nombre del paquete, sino también un nombre facilitado por el usuario con el que se hará referencia a la versión instalada de ese paquete. En el próximo capítulo veremos ejemplos de esto.

Del mismo modo, las operaciones en Helm son sensibles al espacio de nombres. Se puede instalar la misma aplicación en dos espacios de nombres diferentes, y Helm proporciona herramientas para gestionar estas diferentes instancias de la aplicación.

Al final, sin embargo, Helm sigue perteneciendo firmemente a la clase de herramientas de gestión de paquetes.

Seguridad, reutilización y configurabilidad

Nuestro tercer objetivo con Helm era centrarnos en tres elementos "imprescindibles" para gestionar aplicaciones en un clúster:

  1. Seguridad

  2. Reutilización

  3. Configurabilidad

En resumen, queríamos que Helm conociera estos principios lo suficiente como para que los usuarios de Helm pudieran confiar en los paquetes que utilizan. Un usuario debe poder verificar que un paquete procede de una fuente fiable (y que no ha sido manipulado), reutilizar el mismo paquete varias veces y configurarlo para que se ajuste a sus necesidades.

Mientras que los desarrolladores de Helm tienen control directo sobre los dos objetivos de diseño anteriores, éste es único: Helm sólo puede proporcionar las herramientas adecuadas a los autores de paquetes y esperar que estos creadores decidan hacer realidad estos tres "imprescindibles".

Seguridad

La seguridad es una categoría amplia. En este contexto, sin embargo, nos referimos a la idea de que cuando un usuario examina un paquete, tiene la capacidad de verificar ciertas cosas sobre él:

  • El paquete procede de una fuente de confianza.

  • La conexión de red por la que se tira del paquete está protegida.

  • El paquete no ha sido manipulado.

  • El paquete puede inspeccionarse fácilmente para que el usuario pueda ver lo que hace.

  • El usuario puede ver qué configuración tiene el paquete, y ver cómo influyen las distintas entradas en la salida de un paquete.

A lo largo de este libro, y especialmente en el Capítulo 6, trataremos la seguridad con más detalle. Pero estas cinco capacidades son cosas que creemos haber proporcionado con Helm.

Helm proporciona una función de procedencia para establecer la verificación sobre el origen, el autor y la integridad de un paquete . Helm soporta Secure Sockets Layer/Transport Layer Security (SSL/TLS) para enviar datos de forma segura a través de la red. Y Helm proporciona comandos dry-run, template y linting para examinar paquetes y sus posiblespermutaciones.

Reutilización

Una virtud de la gestión de paquetes es su capacidad para instalar lo mismo de forma repetida y predecible. Con Helm, esta idea se amplía ligeramente: podemos querer incluso instalar la misma cosa (repetida y previsiblemente) en el mismo clúster o incluso en el mismo espacio de nombres de un clúster.

Los gráficos de Helm son la clave de la reutilización. Un gráfico proporciona un patrón para producir en los mismos manifiestos de Kubernetes. Pero los gráficos también permiten a los usuarios proporcionar configuración adicional (de la que hablaremos en el próximo capítulo). Así que Helm proporciona patrones para almacenar la configuración, de modo que la combinación de un gráfico más su configuración pueda hacerse incluso repetidamente.

De este modo, Helm anima a los usuarios de Kubernetes a empaquetar su YAML en gráficos para que estas descripciones puedan reutilizarse.

En el mundo Linux, cada distribución Linux tiene su propio gestor de paquetes y repositorios. Esto no ocurre en el mundo de Kubernetes. Helm se construyó para que todas las distribuciones de Kubernetes pudieran compartir el mismo gestor de paquetes, y (con muy, muy pocas excepciones) también los mismos paquetes. Cuando hay diferencias entre dos distribuciones distintas de Kubernetes, los gráficos pueden acomodar esto utilizando plantillas (tratadas más a fondo en el Capítulo 5) junto con la configuración.

Configurabilidad

Helm proporciona patrones para tomar un gráfico de Helm y luego suministrarle alguna configuración adicional. Por ejemplo, puede que instale un sitio web con Helm, pero quiera establecer (en el momento de la instalación) el nombre de ese sitio web. Helm proporciona herramientas para configurar los paquetes en el momento de la instalación, y para reconfigurar las instalaciones durante las actualizaciones. Pero conviene hacer una advertencia.

Helm es un gestor de paquetes. Otra clase de software se ocupa de la gestión de la configuración. Esta clase de software, tipificada por Puppet, Ansible y Chef, se centra en cómo se configura específicamente una determinada pieza de software (a menudo empaquetada) para su entorno anfitrión. Su responsabilidad es gestionar los cambios de configuración a lo largo del tiempo.

Helm no se diseñó para ser una herramienta de gestión de la configuración, aunque existe al menos cierto solapamiento entre la gestión de paquetes y la gestión de la configuración.

La gestión de paquetes suele limitarse a la aplicación de tres verbos: instalar, actualizar y eliminar. La gestión de la configuración es un concepto de orden superior que se centra en la gestión de una aplicación o aplicaciones a lo largo del tiempo. A veces se denomina "operaciones de un día para otro".

Aunque Helm no se propuso ser una herramienta de gestión de la configuración, a veces se utiliza como tal. Las organizaciones confían en Helm no sólo para instalar, actualizar y eliminar, sino también para realizar un seguimiento de los cambios a lo largo del tiempo, para realizar un seguimiento de la configuración y para determinar si una aplicación en su conjunto se está ejecutando. Helm puede estirarse de esta manera, pero si quieres una solución sólida de gestión de la configuración, quizá quieras aprovechar otras herramientas del ecosistema Helm. Muchas herramientas como Helmfile, Flux y Reckoner han completado detalles en la historia más amplia de la gestión de la configuración.

Nota

La comunidad Helm ha creado una gran cantidad de herramientas que interoperan con Helm o lo aumentan. El proyecto Helm mantiene una lista de esas herramientas en la documentación oficial.

Uno de los temas comunes que observarás en los gráficos de Helm es que las opciones de configuración suelen estar configuradas de modo que puedas tomar el mismo gráfico y lanzar una versión mínima del mismo en tu entorno de desarrollo, o (con diferentes opciones de configuración) una versión sofisticada en tu entorno de producción.

Arquitectura de Helm

En la sección final de este capítulo, abordaremos brevemente la arquitectura de alto nivel de Helm. Además de completar el debate conceptual sobre las aplicaciones Kubernetes nativas de la nube y la gestión de paquetes, esta sección prepara el camino para el Capítulo 2, donde nos sumergiremos en el uso de Helm.

Recursos de Kubernetes

Hemos echado un vistazo a varios tipos de recursos de Kubernetes. Hemos visto un par de definiciones Pod, un ConfigMap, un Deployment, y un Service. Hay docenas más proporcionadas por Kubernetes. Incluso puedes utilizar definiciones de recursos personalizadas (CRD) para definir tus propios tipos de recursos personalizados. La documentación principal de Kubernetes proporciona tanto guías accesibles como documentación detallada de la API de cada tipo.

A lo largo de este libro, utilizaremos muchos tipos diferentes de recursos Kubernetes. Aunque hablaremos de ellos en su contexto, puede que te resulte útil hojear el documento principal de Kubernetes cuando te encuentres con nuevas definiciones de recursos.

Como hemos comentado antes, las definiciones de recursos son declarativas. Tú, el usuario, describes para Kubernetes el estado deseado de un recurso. Por ejemplo, puedes leer la definición de Pod que creamos antes en el capítulo como una declaración: "Quiero que Kubernetes me haga un Pod que tenga estas características". Depende de Kubernetes averiguar cómo configurar y ejecutar un pod de acuerdo con tu especificación.

Todas las definiciones de recursos de Kubernetes comparten un subconjunto común de elementos. El siguiente manifiesto utiliza un Deployment para ilustrar los principales elementos estructurales de una definición de recurso:

apiVersion: apps/v1 1
kind: Deployment 2
metadata: 3
    name: example-deployment 4
    labels: 5
        some-name: some-value
    annotations: 6
        some-name: some-value
# resource-specific YAML
1

La familia y versión de la API de este recurso.

2

El tipo de recurso. Combinado con apiVersion, obtenemos el "tipo de recurso".

3

La sección metadata contiene datos de nivel superior sobre el recurso.

4

Se necesita un name para casi todos los tipos de recursos.

5

Las etiquetas se utilizan para dar a Kubernetes "asas" consultables para tus recursos.

6

Las anotaciones permiten a los autores adjuntar sus propias claves y valores a un recurso.

En particular, un tipo de recurso en Kubernetes se compone de tres elementos deinformación:

Grupo API (o familia)

Varios tipos de recursos base como Pod y ConfigMap omiten este nombre.

Versión API

Se expresa como v, seguido de una versión principal y un marcador de estabilidad opcional. Por ejemplo, v1 es una "versión 1" estable, mientras que v1alpha indica una "versión 1 alfa 1" inestable.

Tipo de recurso

El nombre (en mayúsculas) del recurso específico dentro del grupo API.

Nota

Mientras que un nombre completo de tipo de recurso es algo así como apps/v1 Deployment o v1 Pod (para tipos de núcleo), los usuarios de Kubernetes omitirán a menudo el grupo y la versión cuando hablen o escriban sobre tipos bien conocidos. Por ejemplo, en este libro escribimos simplemente Deployment en lugar de apps/v1 Deployment. Los nombres totalmente cualificados se utilizan cuando se especifica una versión exacta o cuando se habla de un tipo de recurso definido en un CRD.

Así, apps/v1 Deployment indica que el grupo de API "apps" tiene un tipo de recurso "versión 1" (estable) llamado "Implementación".

Kubernetes admite dos formatos principales para declarar los recursos que desees: JSON y YAML. En sentido estricto, YAML es un superconjunto de JSON. Todos los documentos JSON son YAML válidos, pero YAML añade una serie de características adicionales.

En este libro, nos ceñimos al formato YAML. Nos parece más fácil de leer y escribir, y casi todos los usuarios de Helm eligen YAML en lugar de JSON. Sin embargo, si tus preferencias difieren, tanto Kubernetes como Helm admiten JSON plano.

Antes hemos introducido el término manifiesto. Un manifiesto no es más que un recurso Kubernetes serializado a su formato JSON o YAML. Sería justo llamar a cada uno de nuestros ejemplos anteriores Pod, ConfigMap, Deployment, y Service un manifiesto de Kubernetes, ya que son recursos expresados en YAML.

Gráficos

Ya hemos hablado de los paquetes Helm en este capítulo. En el vocabulario de Helm, un paquete se denomina carta. El nombre es un juego de palabras entre la naturaleza náutica de Kubernetes (que significa "capitán del barco" en griego) y Helm (que es el mecanismo de gobierno de un barco). Una carta traza la forma en que debe instalarse una aplicación Kubernetes.

Un gráfico es un conjunto de archivos y directorios que se adhieren a la especificación del gráfico para describir los recursos que se instalarán en Kubernetes. El Capítulo 4 explica en detalle la estructura de la carta, pero aquí introduciremos algunos conceptos de alto nivel.

Un gráfico contiene un archivo llamado Gráfico.yaml que describe el gráfico. Contiene información sobre la versión del gráfico, el nombre y la descripción del gráfico y su autor.

Un gráfico también contiene plantillas. Se trata de manifiestos de Kubernetes (como hemos visto antes en este capítulo) que están potencialmente anotados con directivas de plantillas. Las veremos en detalle en el Capítulo 5.

Un gráfico también puede contener un archivo values.yaml que proporciona la configuración por defecto. Este archivo contiene parámetros que puedes anular durante la instalación y la actualización.

Éstas son las cosas básicas que encontrarás en un gráfico de Helm, aunque hay otras que trataremos en el Capítulo 4. Sin embargo, cuando veas un gráfico de Helm, puede presentarse sin empaquetar o empaquetado.

Un gráfico Helm desempaquetado no es más que un directorio. Dentro, tendrá un Chart.yaml, unvalues.yaml, un directorio templates/, y quizás también otras cosas. Un gráfico de Helm empaquetado contiene la misma información que uno desempaquetado, pero está comprimido en un único archivo.

Un gráfico descomprimido se representa mediante un directorio con el nombre del gráfico. Por ejemplo, el gráfico llamado migráfico se descomprimirá en un directorio llamado migráfico/. En cambio, un gráfico empaquetado tiene el nombre y la versión del gráfico, así como el sufijo tgz: mychart-1.2.3.tgz.

Las cartas se almacenan en repositorios de cartas, que veremos en el Capítulo 7. Helm sabe cómo descargar e instalar gráficos de los repositorios.

Recursos, instalaciones y lanzamientos

Para unir la terminología introducida en esta sección, cuando se instala un gráfico de Helm en Kubernetes, esto es lo que ocurre:

  1. Helm lee el gráfico (descargándolo si es necesario).

  2. Envía los valores a las plantillas, generando manifiestos Kubernetes.

  3. Los manifiestos se envían a Kubernetes.

  4. Kubernetes crea los recursos solicitados dentro del clúster.

Cuando se instala un gráfico de Helm, Helm generará tantas definiciones de recursos como necesite. Algunas pueden crear una o dos, otras pueden crear cientos. Cuando Kubernetes reciba estas definiciones, creará recursos para ellas.

Un gráfico de Helm puede tener muchas definiciones de recursos. Kubernetes ve cada uno de ellos como algo discreto. Pero desde el punto de vista de Helm, todos los recursos definidos por un gráfico están relacionados. Por ejemplo, mi aplicación WordPress puede tener un Deployment, un ConfigMap, unServicey así sucesivamente. Pero todos forman parte de un mismo gráfico. Y cuando los instalo, todos forman parte de la misma instalación. El mismo gráfico puede instalarse más de una vez (con un nombre distinto cada vez). Por tanto, puedo tener varias instalaciones del mismo gráfico, igual que puedo tener varios recursos del mismo tipo de recurso de Kubernetes.

Y esto nos lleva a un último término. Una vez que instalamos nuestro gráfico de WordPress, tenemos una instalación de ese gráfico. Luego actualizamos ese gráfico utilizando helm upgrade. Ahora, esa instalación tiene dos versiones. Se crea una nueva versión de una instalación cada vez que utilizamos Helm para modificar la instalación.

Una versión se crea cuando instalamos una nueva versión de WordPress. Pero también se crea una versión cuando simplemente cambiamos la configuración de una instalación, o cuando revertimos una instalación. Esta es una característica importante de Helm que volveremos a ver en el Capítulo 7.

Breve nota sobre Helm 2

Quienes estén familiarizados con Helm 2 pueden notar que faltan ciertos conceptos en este libro. No se mencionan ni Tiller ni gRPC. Estas cosas se eliminaron de Helm 3, que es el tema del presente libro. Además, esta versión del libro se centra en los gráficos de Helm de la versión 2. Aunque resulte confuso, la versión de los gráficos de Helm se incrementa por separado de la versión de Helm. Así, Helm v2 utilizaba los gráficos de Helm v1, y Helm v3 utiliza los gráficos de Helm v2. Éstos difieren en algunos aspectos importantes de los gráficos de Helm versión 1, sobre todo en la forma de declarar las dependencias. Helm 2 y Helm Charts v1 se consideran obsoletos.

Conclusión

Este material debería prepararte para los próximos capítulos. Pero esperamos que también te haya servido para comprender por qué hemos creado Helm como lo hemos hecho. Helm sólo tiene éxito si hace que Kubernetes sea más utilizable, tanto para los que lo usan por primera vez como para los equipos de operaciones y SREs que lo utilizan a diario desde hace tiempo. El resto de este libro está dedicado a explicar (con muchos ejemplos) cómo sacar el máximo partido a Helm, y cómo hacerlo de forma segura e idiomática.

Get Timón de aprendizaje now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.