Helm: el gestor de paquetes para Kubernetes

Helm (timón en español) es un administrador de paquetes de recursos para aplicaciones en Kubernetes. Permite definir, instalar, actualizar y hacer rollback de las aplicaciones desplegadas a través de este gestor. Este administra los recursos que necesita a través de los llamados Charts, que en español significa «cartas de navegación».

Helm se compone de dos partes:

  • El servidor llamado Tiller: interactúa con la API de Kubernetes para instalar, actualizar, listar y eliminar recursos del clúster.
  • El cliente: es responsable de hablar con el servidor Tiller para llevar a cabo las acciones.

En este artículo vamos a ver cómo instalar Helm, cómo trabajar con charts de terceros y, además, crearemos uno propio.

Instalar Helm

Para instalar Helm primero necesitas tener un clúster de Kubernetes. En este artículo voy a utilizar Minikube, del que ya te hablé. Si utilizas Mac Os, puedes instalar Helm con Homebrew:

brew install helm

En Windows la forma más sencilla es a través del gestor Chocolatey:

choco install kubernetes-helm

Charts

Los charts son colecciones de archivos organizados en una estructura específica. Cada vez que se ejecuta una instancia de un chart se llama release. Existen repositorios públicos de charts que puedes usar, como Kubeapps. Para verlo con un ejemplo, vamos a lanzar un chart que nos genere un entorno de WordPress, el cual está compuesto del CMS y una base de datos MariaDB.

#Update helm repo
helm repo update

#Install WordPress using helm
helm install wp stable/wordpress --set wordpressSkipInstall=false,service.type=NodePort

El resultado será el siguiente:

Giselas-iMac:~ gis$ helm install wp stable/wordpress --set wordpressSkipInstall=false,service.type=NodePort
NAME: wp
LAST DEPLOYED: Mon Jan  6 11:28:21 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the WordPress URL:

  export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services wp-wordpress)
  export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}")
  echo "WordPress URL: http://$NODE_IP:$NODE_PORT/"
  echo "WordPress Admin URL: http://$NODE_IP:$NODE_PORT/admin"

2. Login with the following credentials to see your blog

  echo Username: user
  echo Password: $(kubectl get secret --namespace default wp-wordpress -o jsonpath="{.data.wordpress-password}" | base64 --decode)

Como puedes ver, este comando genera mucha información de salida. El nombre de la release es wp y alojará los objetos en el namespace default. A partir de este momento, crea todos los recursos necesarios para esta aplicación en concreto. Por último, te da indicaciones de cómo acceder a tu nuevo WordPress. Si estás usando Minikube como yo, la IP del balanceador se quedará como pending, ya que en este entorno no es posible generar IPs de manera dinámica. Sin embargo, a través de set, he modificado el tipo de servicio que genera este chart a NodePort para poder acceder de esta otra manera. Por supuesto, puedes comprobar el estado de los pods como siempre:

kubectl get pods

Una vez que estos estén en estado ready, puedes acceder a tu nuevo WordPress y comprobar que todo funciona perfectamente. Para ello, localiza el nombre del servicio de WordPress a través de kubectl get services y pregúntale a minikube por sus URLs.

minikube service wp-wordpress --url

Este comando devolverá dos URLs: una para el puerto 80 y otra para el 443. Debes acceder a través del puerto 80, por lo que elige aquella que el puerto esté mapeado a este. Si accedes a través del navegador verás que tu WordPress funciona correctamente.

WordPress ejecutándose gracias a Helm

Para ver las releases que tienes desplegadas de diferentes charts utiliza la opción list:

helm list

Si quieres eliminar alguna release utiliza este otro:

helm delete NOMBRE_DE_LA_RELEASE

Crear tu propio Chart

Ahora que has visto cómo funciona Helm, ha llegado el momento de crear tu propio chart. Este va a ser un ejemplo sencillo, con un único contenedor, pero así será más fácil el hacerte una idea de cómo funciona.

Para crear la estructura de carpetas que te mencionaba antes solo tienes que lanzar este comando:

helm create my-first-chart

Esto creará una carpeta llamada my-first-chart con la siguiente estructura en su interior:

Estructura de un chart de Helm
  • Chart.yaml es el archivo principal, que contiene la información sobre tu chart (es como el package.json en Node.js, donde se describe el paquete).
  • values.yaml es el que contiene los valores por defecto de nuestro chart. Si no se especifica ninguno a través de –set, como hice con el chart de WordPress, estos son los valores que se usarán.
  • En templates te encontrarás los recursos de Kubernetes (services, deployments, ingress) creados como plantillas. Ahora veremos de qué se trata.
  • El directorio charts es opcional y es para incluir otros charts dentro de este.
  • .helmignore es similar a .gitignore o .dockerignore. Es útil para definir qué archivos o directorios no entrarán en el paquete final.

Ahora que tenemos la estructura creada, vamos a modificarla para un despliegue de Kubernetes que contendrá una aplicación en ASP.NET Core. Lo primero que vamos a modificar es el archivo values.yaml, donde vamos a definir qué imagen quiero utilizar para mi aplicación web, he añadido un nuevo valor para el tag de la imagen (tag: aspnetapp), además del tipo de servicio, que será NodePort:

# Default values for my-first-chart.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1

image:
  repository: mcr.microsoft.com/dotnet/core/samples
  tag: aspnetapp
  pullPolicy: IfNotPresent

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

serviceAccount:
  # Specifies whether a service account should be created
  create: true
  # The name of the service account to use.
  # If not set and create is true, a name is generated using the fullname template
  name:

podSecurityContext: {}
  # fsGroup: 2000

securityContext: {}
  # capabilities:
  #   drop:
  #   - ALL
  # readOnlyRootFilesystem: true
  # runAsNonRoot: true
  # runAsUser: 1000

service:
  type: NodePort
  port: 80

ingress:
  enabled: false
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  hosts:
    - host: chart-example.local
      paths: []
  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local

resources: {}
  # We usually recommend not to specify default resources and to leave this as a conscious
  # choice for the user. This also increases chances charts run on environments with little
  # resources, such as Minikube. If you do want to specify resources, uncomment the following
  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
  # limits:
  #   cpu: 100m
  #   memory: 128Mi
  # requests:
  #   cpu: 100m
  #   memory: 128Mi

nodeSelector: {}

tolerations: []

affinity: {}

En el archivo deployment.yaml he modificado el valor de image para que recupere el tag que he especificado en values.yaml, cambiando .Chart.AppVersion por .Values.image.tag:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-first-chart.fullname" . }}
  labels:
    {{- include "my-first-chart.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "my-first-chart.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "my-first-chart.selectorLabels" . | nindent 8 }}
    spec:
    {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
    {{- end }}
      serviceAccountName: {{ include "my-first-chart.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.podSecurityContext | nindent 8 }}
      containers:
        - name: {{ .Chart.Name }}
          securityContext:
            {{- toYaml .Values.securityContext | nindent 12 }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
    {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
    {{- end }}
    {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
    {{- end }}

Como ves, este archivo se corresponde con el YAML que hemos utilizado en artículos anteriores para generar un deployment, pero contiene algunos valores entre dobles llaves que son variables en Helm. Estas son definidas en el archivo values.yaml, que vimos antes que este.

El archivo service.yaml podemos dejarlo tal cual está e ingress.yaml no es utilizado en este caso, ya que en el archivo values.yaml está deshabilitado.

Lo siguiente que podemos hacer es comprobar que nuestro chart está bien formado a través de este comando:

helm lint ./my-first-chart

Como resultado, nos mostrará si todo lo que hemos editado, eliminado o añadido es correcto.

helm lint my-first-chart

Otro comando interesante es el que nos permite ver cómo quedarán las plantillas, dentro de templates, una vez se hayan mapeado los valores en los apartados con llaves:

helm template ./my-first-chart

Por último, para instalar este chart podemos hacerlo de la misma manera que con los charts de terceros:

helm install my-first-chart ./my-first-chart

Al igual que en el caso anterior, puedo utilizar minikube para recuperar la URL del servicio y comprobar que todo funciona correctamente:

ASP.NET Core web app generada desde un chart de Helm

Si modificas el chart y quieres actualizar la release que acabas de lanzar puedes utilizar la opción upgrade.

helm upgrade my-first-chart ./my-first-chart

Por otro lado, si lo que necesitas es hacer rollback a una versión anterior, existe otra opción, que es la de rollback.

helm rollback my-first-chart 1

El 1 es el número de la revisión (se puede ver con helm list, en qué revisión estás).

El número de la revisión en Helm

¡Saludos!