Original article: What is a Helm Chart? A Tutorial for Kubernetes Beginners

Kubernetes es una herramienta muy útil para los desarrolladores nativos de la nube. Pero no cubre todas las bases por sí sola: hay algunas cosas que Kubernetes no puede resolver o que están fuera de su alcance.

Esta es una de las razones por las que los proyectos de código abierto son tan fantásticos. Ayudan a que herramientas increíbles sean aún más increíbles cuando las combinamos con otras herramientas increíbles de código abierto. Y a menudo estas herramientas se desarrollaron con el único propósito de llenar los vacíos. Una de estas herramientas es Helm.

¿Qué es Helm?

Helm es ampliamente conocido como "el gestor de paquetes para Kubernetes". Aunque se presenta así, su alcance va mucho más allá del de un simple gestor de paquetes. Sin embargo, empecemos por el principio.

Helm es un proyecto de código abierto que fue creado originalmente por DeisLabs y donado a CNCF, que ahora lo mantiene. El objetivo original de Helm era proporcionar a los usuarios una forma mejor de gestionar todos los archivos YAML de Kubernetes que creamos en los proyectos de Kubernetes.

El camino que Helm tomó para resolver este problema fue crear Helm Charts. Cada gráfico es un paquete con uno o más manifiestos de Kubernetes: un gráfico puede tener gráficos hijos y gráficos dependientes.

Esto significa que Helm instala todo el árbol de dependencias de un proyecto si ejecuta el comando install para el gráfico de nivel superior. Sólo tiene que ejecutar un único comando para instalar toda su aplicación, en lugar de listar los ficheros a instalar mediante kubectl.

Charts también te permite versionar tus archivos de manifiesto, al igual que hacemos con Node.js o cualquier otro paquete. Esto te permite instalar versiones específicas de gráficos, lo que significa mantener configuraciones específicas para tu infraestructura en forma de código.

Helm también mantiene un historial de versiones de todas las cartas desplegadas, por lo que puede volver a una versión anterior si algo va mal.

Helm soporta Kubernetes de forma nativa, lo que significa que no tienes que escribir ningún archivo de sintaxis complejo ni nada para empezar a usar Helm. Solo tienes que soltar tus archivos de plantilla en un nuevo gráfico y listo.

Pero, ¿por qué deberíamos utilizarlo? La gestión de los manifiestos de aplicación puede hacerse fácilmente con unas pocas combinaciones de comandos.

¿Por qué utilizar Helm?

Helm realmente brilla donde Kubernetes no llegó. Por ejemplo, las plantillas. El alcance del proyecto Kubernetes es tratar con sus contenedores para usted, no sus archivos de plantilla.

Esto dificulta en exceso la creación de archivos realmente genéricos que puedan utilizarse en un equipo o una organización grandes, con muchos parámetros diferentes que deben configurarse para cada archivo.

Y también, ¿cómo versionar información sensible usando Git cuando los archivos de plantilla son texto plano?

La respuesta: Ir a las plantillas. Helm le permite añadir variables y utilizar funciones dentro de sus archivos de plantilla. Esto lo hace perfecto para aplicaciones escalables que eventualmente necesitarán cambiar sus parámetros. Veamos un ejemplo.

Tengo un proyecto de código abierto llamado Zaqar, un microservicio de correo electrónico simple para Node.js que se comunica con SendGrid. El proyecto se compone básicamente de un servicio, un despliegue y un autoescalador.

Tomemos como ejemplo el archivo de despliegue. Tendría algo como esto

apiVersion: apps/v1
kind: Deployment
metadata:
  name: zaqar
  namespace: default
  labels:
    app: zaqar
    version: v1.0.0
    env: production
spec:
  replicas: 1
  selector:
    matchLabels:
      app: zaqar
      env: production
  template:
    metadata:
      labels:
        app: zaqar
        version: v1.0.0
        env: production
    spec:
      containers:
        - name: zaqar
          image: "khaosdoctor/zaqar:v1.0.0"
          imagePullPolicy: IfNotPresent
          env:
            - name: SENDGRID_APIKEY
              value: "MY_SECRET_KEY"
            - name: DEFAULT_FROM_ADDRESS
              value: "my@email.com"
            - name: DEFAULT_FROM_NAME
              value: "Lucas Santos"
          ports:
            - name: http
              containerPort: 3000
              protocol: TCP
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 250m
              memory: 256Mi

Si quisiera usar esta plantilla en un CI pipeline, o publicarla en mi GitHub, necesitaría reemplazar las partes variables por marcadores de posición. Así que podemos reemplazar estos textos con la información requerida.

En este caso, tanto la etiqueta de versión como la etiqueta env y las variables de entorno se sustituirían por marcadores de posición, así:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: zaqar
  namespace: default
  labels:
    app: zaqar
    version: #!VERSION!#
    env: #!ENV!#
spec:
  replicas: 1
  selector:
    matchLabels:
      app: zaqar
      env: #!ENV!#
  template:
    metadata:
      labels:
        app: zaqar
        version: #!VERSION!#
        env: #!ENV!#
    spec:
      containers:
        - name: zaqar
          image: "khaosdoctor/zaqar:#!VERSION!#"
          imagePullPolicy: IfNotPresent
          env:
            - name: SENDGRID_APIKEY
              value: "#!SENDGRID_KEY!#"
            - name: DEFAULT_FROM_ADDRESS
              value: "#!FROM_ADDR!#"
            - name: DEFAULT_FROM_NAME
              value: "#!FROM_NAME!#"
          ports:
            - name: http
              containerPort: 3000
              protocol: TCP
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 250m
              memory: 256Mi

Ahora podemos ejecutar nuestro CI pipeline. Pero antes de hacerlo, tenemos que reemplazar nuestros marcadores de posición con los valores reales.

Para ello podemos utilizar sed y su "super fácil" sed 's/#!PLACEHOLDER!#/replacement/g', y canalizar esto hacia abajo hasta que terminemos todos los marcadores de posición. El comando final sería algo como esto:

cat deploy.yaml | \
    sed 's/#!ENV!#/production/g' | \
    sed 's/#!VERSION!#/v1.0.0/g' | \
    sed 's/#!SENDGRID_KEY!#/MyKey/g' | \
    sed 's/#!FROM_ADDR!#/my@email.com/g' | \
    sed 's/#!FROM_NAME!#/Lucas Santos/g'

Por defecto, sed envía todo al archivostdout, para que podamos añadir otra tubería a kubectl -f, como <all the command from before> | kubectl -f -. Entonces tendremos nuestro despliegue en su lugar. El único problema es que tenemos que hacer lo mismo para todos los demás archivos.

Ahora imagina un proyecto más grande, con muchas otras variables y marcadores de posición. Probablemente, escribirías un script para hacerlo por ti, ¿verdad? Ese script es Helm.

Cuando se crea un Gráfico (más sobre esto más adelante), tenemos un árbol de directorios específico que debemos seguir para que Helm entienda lo que queremos hacer. Dentro del directorio templates podemos añadir nuestros archivos de manifiesto, con plantillas go nativas, de la siguiente manera:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.name }}
  namespace: {{ default .Release.Namespace .Values.namespace }}
  labels:
    app: {{ .Values.name }}
    version: {{ .Values.image.tag }}
    env: {{ .Values.env }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Values.name }}
      env: {{ .Values.env }}
  template:
    metadata:
      labels:
        app: {{ .Values.name }}
        version: {{ .Values.image.tag }}
        env: {{ .Values.env }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "khaosdoctor/zaqar:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          env:
            - name: SENDGRID_APIKEY
              value: {{ required "You must set a valid Sendgrid API key" .Values.environment.SENDGRID_APIKEY | quote }}
            - name: DEFAULT_FROM_ADDRESS
              value: {{ required "You must set a default from address" .Values.environment.DEFAULT_FROM_ADDRESS | quote }}
            - name: DEFAULT_FROM_NAME
              value: {{ required "You must set a default from name" .Values.environment.DEFAULT_FROM_NAME | quote }}
          ports:
            - name: http
              containerPort: 3000
              protocol: TCP
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

Todos esos valores pueden obtenerse a partir de un archivo Values.yaml (para los valores por defecto), o puede configurarlos en la CLI utilizando el comando con su bandera --set <path> value.

Si queremos instalar nuestro gráfico podemos emitir el siguiente comando:

helm upgrade --install --create-namespace myChart ./path/to/my/chart \
  --set image.tag=v1.0.0 \
  --set env=production \
  --set environment.SENDGRID_APIKEY=myKey \
  --set environment.DEFAULT_FROM_ADDRESS="my@email.com" \
  --set environment.DEFAULT_FROM_NAME="Lucas Santos"

Helm también nos permite utilizar funciones dentro de nuestros despliegues. Así que podemos tener default para volver a los valores por defecto si no se rellenan, como el espacio de nombres. O podemos tener required que muestra un mensaje y no instala el gráfico si no se proporciona el valor, que es el caso de nuestras variables de entorno.

Hay muchas otras funciones útiles en sus documentos.

Ahora podemos no sólo gestionar de forma más eficiente los recursos de nuestra aplicación, sino también publicar estos recursos en un sistema de versiones de código abierto sin ningún tipo de molestia ni problema de seguridad.

Cómo crear un cuadro de mando

Es bastante fácil crear un gráfico en Helm. Primero, necesita tener Helm instalado. Luego, simplemente escriba helm create <chart name> y creará un directorio lleno de archivos y otros directorios. Esos archivos son necesarios para que Helm cree un gráfico.

Veamos con más detalle qué aspecto tiene este árbol de archivos y qué archivos contiene:

  • chart.yaml: Aquí es donde pondrás la información relacionada con tu gráfico. Eso incluye la versión del gráfico, el nombre y la descripción para que puedas encontrarlo si lo publicas en un repositorio abierto. También en este archivo podrás establecer dependencias externas usando el comando dependencies llave.
  • values.yaml: Como vimos antes, este es el archivo que contiene los valores por defecto para las variables.
  • templates (dir): Este es el lugar donde pondrás todos tus archivos de manifiesto. Todo lo que haya aquí se transmitirá y creará en Kubernetes.
  • charts:Si su gráfico depende de otro gráfico de su propiedad, o si no quiere depender de la biblioteca por defecto de Helm (el registro por defecto de donde Helm extrae los gráficos), puede traer esta misma estructura dentro de este directorio. Las dependencias de los gráficos se instalan de abajo a arriba, lo que significa que si el gráfico A depende del gráfico B, y B depende de C, el orden de instalación será C ->B ->A.

Hay otros campos, pero estos son los más comunes, y son los obligatorios. Puedes echar un vistazo rápido al repositorio de Zaqar para comprobar cómo podemos publicar gráficos de código abierto.

Una nota rápida: Cuando instale Helm, asegúrese de que está instalando la versión 3. La versión 2 todavía funciona. La versión 2 todavía funciona, pero necesita un componente del lado del servidor llamado Tiller, que ata su instalación helm a un solo clúster. Helm 3 eliminó esta necesidad con la adición de varios CRDs, pero no es compatible con todas las versiones de Kubernetes.

Cómo organizar un cuadro de mando

Vale, has creado tu gráfico, ¿y ahora qué? ¿Tenemos que descargar todo el repositorio para instalar esos gráficos? Helm tiene una biblioteca pública para los gráficos más utilizados, que funciona como Docker Hub.

También puede crear su propio repositorio de gráficos y alojarlo en línea. Helm bebe de la misma fuente que HomeBrew, o Linux. Puede acceder a estos repositorios para descargar los gráficos que contienen.

Dado que un repositorio de gráficos es básicamente un index.yaml servido desde un servidor web estático, prácticamente se puede crear un repositorio de gráficos desde cualquier lugar.

Por ejemplo, Zaqar está alojado en GitHub Pages y es accesible a través de mi dominio. Cuando Helm busca un index.yaml en realidad, está buscando la lista de versiones disponibles de ese gráfico, sus compendios SHA256 y la ubicación del archivo empaquetado .tgz para descargar el gráfico. Esto es más o menos lo que NPM hace bajo el capó (demasiado simplificado).

Esto significa que no necesitas tener tu repositorio clonado para siempre, y tus gráficos también pueden ser privados. Sólo necesitas crear un repositorio de gráficos.

Incluso puedes utilizar servicios alojados como Azure CR para hacer el trabajo, o puedes tener una solución completa llamada Chart Museum, que te permite almacenar tus gráficos y te proporciona una interfaz de usuario ordenada.

Conclusión

Helm está aquí para quedarse. Ha ayudado y ayudará a muchos desarrolladores de Kubernetes durante mucho tiempo.

Si quieres saber cómo usar Helm, puedes consultar su documentación, o puedes seguir este módulo de aprendizaje gratuito sobre cómo desplegar tus aplicaciones en Kubernetes de forma sencilla con Helm.