Werf: GitOps totalmente customizable

Miguel Fontanilla
Caminos a la nubes
En este post se presenta y analiza una herramienta para la integración continua y el despliegue continuo en Kubernetes:

Werf: GitOps totalmente customizable 🛥️⚙️

Bienvenido al tercer post en la serie de artículos sobre herramientas de GitOps. En este post se presenta y analiza una herramienta para la integración continua y el despliegue continuo en Kubernetes: Werf. Si te perdiste los artículos anteriores a cerca de GitOps, échales un ojo, ya que te ayudarán a entender mejor la idea general. ArgoCD y FluxCD, fueron las herramientas que se analizaron en los artículos previos.

Al igual que para dichas herramientas, se ha preparado un repositorio con código de ejemplo para que puedas probar Werf. Puedes encontrarlo en aquí.

GitOps y Werf ⛵

GitOps se define como una estrategia para gestionar la infraestructura de Kubernetes y sus aplicaciones en la que la definición declarativa de lo que debe estar corriendo en el clúster se almacena en un repositorio de Git. El repositorio de Git se considera como la la única fuente de verdad en el modelo de GitOps. Con esta estrategia, cualquier cambio en la definición de las aplicaciones (código), se refleja en la infraestructura desplegada y en las aplicaciones. Además, permite detectar diferencias y desviaciones entre el código de la aplicación y las versiones desplegadas.

GitOps incrementa la frecuencia de entregas de software, ya que en el momento en el que el código se encuentre en la rama productiva, también estará presente en el clúster en cuestión de segundos, incrementando también la productividad. Este modelo incrementa también la fiabilidad y robustez, ya que como todas las aplicaciones y los despliegues se encuentran bajo el control de versiones de Git, los rollbacks y procedimientos de recuperación son más sencillos. Además, GitOps permite mejorar la estabilidad y la consistencia de las aplicaciones, al automatizar y unificar el método de despliegue.

Werf es una herramienta de GitOps de código abierto, cuyo objetivo es acelerar la entrega de software automatizando y simplificando el ciclo de vida completo del desarrollo de una aplicación. Para conseguirlo, Werf, construye y publica imágenes, despliega aplicaciones en clústers de Kubernetes y elimina las imágenes que ya no se usan en base a políticas y reglas definidas en el repositorio de Git.

Werf puede construir imágenes Docker utilizando Dockerfiles o su propio constructor alternativo, que emplea una sintaxis propia, que soporta Ansible así como construcciones incrementales basadas en el histórico de Git.

Werf puede desplegar aplicaciones en Kubernetes utilizando Charts de Helm pero también añade customizaciones más avanzadas a los mismos. Además incluye mecanismos de control de rollouts, detección de errores y monitorización para gestionar las releases de las aplicaciones. La lógica que controla dichos mecanismos puede ser modificada mediante el empleo de annotations de Kubernetes.

Werf se distribuye como un CLI desarrollado en Go, por lo que prácticamente, puede correr en cualquier SO moderno y se puede integrar con cualquier plataforma de CI/CD existente, ya que ha sido diseñado con dicho fin.

Instalando Werf 💻

Para instalar el CLI de Werf, en primer lugar es necesario instalar Docker y Git en tu máquina. Si no los tienes instalados todavía, puedes encontrar las instrucciones para instalar Docker en aquí, y para instalar Git en aquí. Una vez que ambas herramientas se encuentren instaladas, podemos instalar Werf.

Werf recomienda instalar su CLI utilizando la herramienta multiwerf , que se asegura de que la versión del CLI de Werf esté siempre actualizada. Ejecuta los siguientes comandos para instalar multiwerf en tu máquina.

Selecciona la versión de Werf a utilizar y configura tu sesión de terminal para que ejecute dicha versión ejecutando el siguiente comando. Werf proporciona 5 niveles de estabilidad entre los que elegir, de forma que puedas elegir el nivel adecuado en función de las necesidades de cada entorno.

En caso de que no quieras utilizar multiwerf y sus funcionalidades, puedes utilizar el 'modo tradicional', escogiendo una versión específica del binario del CLI de Werf en aquí.

Estructura del Proyecto📁

Werf trabaja con una estructura específica para los ficheros dentro del repositorio de código. La siguiente imagen muestra la estructura del repositorio de ejemplo, que contiene una página web sencilla (en /web) que se empaqueta en una imagen Docker de nginx y se despliega en un clúster de Kubernetes usando Helm.

En la raíz del repositorio puede encontrarse el fichero werf.yaml, un fichero de configuración en YAML que define como construir las imágenes y contiene metadatos y directivas que permiten modificar el funcionamiento de Werf. Dentro de este fichero, los diferentes apartados se separan con tres guiones. Werf soporta el empleo de templates de Go dentro del fichero werf.yaml , como se puede observar en el siguiente ejemplo, lo que hace el fichero de configuración mucho más flexible. Además, al incluir diferentes templates, el fichero werf.yaml se puede dividir en diferentes partes, para gestionar mejor aquellos escenarios que requieren configuraciones complejas.

Al mismo nivel puedes encontrar un Dockerfileque se emplea para construir la imagen a desplegar. Este Dockerfile puede referenciarse directamente desde werf.yaml para utilizarlo. Sin embargo, Werf soporta una sintaxis propia para construir imágenes, especificada directamente desde el fichero werf.yaml como podrás ver en los siguientes ejemplos.

.helm contiene los charts de Helm que utilizará Werf para desplegar la aplicación, organizados siguiendo la estructura para los charts de Helm. El fichero Chart.yaml no se incluye en el directorio, ya que no es necesario para Werf.

Construyendo Imágenes 📦

En primer lugar, clona el repositorio de ejemplos para poder empezar.

Construir imágenes desde un Dockerfile es la manera más sencilla de empezar a utilizar Werf sobre un proyecto ya existente. Para hacerlo, solo necesitas añadir la directiva dockerfile junto con el nombre del Dockerfile (Dockerfile en este caso) en el apartado de configuración de la imagen en el fichero werf.yaml .

El siguiente código muestra el contenido de dicho Dockerfile. Es bastante sencillo, ya que solo añade la página web y el logo a la imagen Docker y expone el puerto 80 para que el servidor nginx pueda escuchar en el mismo.

Sin embargo, Werf proporciona funcionalidades avanzadas para la construcción de imágenes, eliminando la necesidad de los Dockerfiles, aunque estos puede combinarse también con la propia sintaxis de Werf. Además, se pueden construir tantas imágenes como sean necesarias utilizando un único fichero de configuración de werf (multi-image build). El siguiente código muestra el contenido del fichero werf.yaml del repositorio de ejemplo.

En este caso, la imagen se construye utilizando diferentes directivas de Werf. En primer lugar, se emplea la sintaxis de Ansible mediante la directiva ansible, para modificar permisos y crear algunos directorios. Los comandos de shell pueden ejecutarse también durante la construcción de la imagen utilizando la directiva shell , no obstante, cuando se utiliza la directiva ansible, la directiva shell no puede emplearse al mismo tiempo y viceversa. Puedes observar un ejemplo de la directiva shell en el código anterior (comentado).

Los bloques dentro de las directivas ansible, y shell son conocidos como Werf assembly instructions. Werf proporciona cuatro fases (user stages) para la ejecución de dichas instrucciones: beforeInstall, install, beforeSetup, y setup. Las fases se ejecutan respectivamente en orden, una detrás de la otra. Si quieres indagar un poco más a cerca de estas fases y las posibilidades que ofrecen, échale un ojo a la documentación oficial.

La directiva git se emplea para añadir ficheros a las imágenes desde el repositorio Git local (desde el que se invoca a Werf) o desde repositorios remotos, permitiendo especificar el commit, la rama o el tag a utilizar, así como filtros basados en el path. Además, la directiva puede utilizarse para desencadenar la construcción de imágenes cuando se hacen cambios específicos dentro del repositorio. Para poder conocer en detalle su funcionamiento, aquí tienes la documentación oficial.

Por último, la directiva docker se utiliza para construir la imagen, siguiendo una sintaxis similar a la de los Dockerfiles, pero con la identación propia de YAML. Para más información acerca de la directiva, consulta aquí.

Para construir la imagen, solo necesitas ejecutar el siguiente comando en la raíz del repositorio, para que el CLI pueda encontrar el fichero werf.yaml . El flag –stages-storage indica a Werf donde almacenar las stages, que son las capas intermedias que se emplean para generar la imagen. En este caso, utilizando el valor local para el flag, Werf almacenará las imágenes utilizando el runtime local de Docker.

Durante el proceso de construcción, Werf ejecuta las diferentes fases y las salidas se muestran en el terminal.

Una vez que la imagen ha sido construida, ya podemos subirla a un repositorio de imágenes. Para este ejemplo, puedes utilizar un repo de Dockerhub por simplicidad. Si no tienes una cuenta todavía, regístrate y crea un repositorio público. Una vez que esté listo, genera un access token para que Werf pueda subir las imágenes al repositorio. Para subir las imágenes, ejecuta el siguiente comando.

TRUQUILLO: Si prefieres utilizar un repositorio de imágenes local, ejecuta el siguiente comando y añade –images-repo localhost:5000/nombre-de-tu-repo cuando ejecutes el comando werf publish .

TRUQUILLO 2: Puedes construir y publicar tu imagen con un sólo comando de Werf.

Werf y los secretos🔐

Werf es también bastante útil para gestionar valores sensibles y secretos dentro de los templates de Helm. Werf permite encriptar los ficheros de values para que cuando se suban a un repositorio de código, nadie a excepción de Werf pueda obtener los valores en claro. Para hacerlo, Werf utiliza una clave secreta, que se especifica con la variable WERF_SECRET_KEY o con el fichero .werf_secret_key en la raíz del proyecto.

Para generar una clave y configurarla como variable de entorno en la sesión de terminal, ejecuta el siguiente comando.

Utiliza Werf para generar el fichero de values secreto. El siguiente comando abrirá un editor de texto para que puedas crear o modificar los valores en claro.

Pega los siguiente valores de ejemplo en el editor y guarda tus cambios.

Si intentas leer los contenidos del fichero .helm/secret-values.yaml puedes ver que los valores han sido encriptados, por lo que ahora pueden almacenarse en un repositorio sin riesgo.

Los valores que hemos creado pueden referenciarse como cualquier otro set de valores dentro de un template de Helm. Para este ejemplo se inyectan en el contenedor como variables de entorno.

Desplegando en K8s ☸️

Como se comentó anteriormente, Werf puede utilizar templates de Helm para desplegar aplicaciones en clústers de Kuberetes. Werf busca los templates en .helm/templates y los renderiza tal y como hace Helm normalmente. Además, Werf extiende las funcionalidades de Helm incluyendo three-way merge patches así como resource tracking annotations.

El siguiente código muestra el contenido de .helm/templates/deployment.yaml, que utiliza la imagen previamente construida para desplegar un servidor web de tres réplicas en Kubernetes.

Fíjate en como se utilizan las Go template functions werf_container_image y werf_container_env en el manifiesto del deployment. Estas funciones son evaluadas al renderizar el chart y generan las keys image y imagePullPolicy en base a la estrategia de tageado, y a la variable de entorno DOCKER_IMAGE_ID, respectivamente.

Al igual que Helm, Werf utiliza el fichero .helm/requirements.yaml para definir las dependencias del chart, como el nginx ingress controller en este caso, para exponer el servidor web. Para construir las dependencias, ejecuta el siguiente comando.

Una vez que las dependencias hayan sido resueltas, despliega la aplicación en tu clúster. Werf utiliza el kubeconfig por defecto del sistema, definido en la variable de entorno KUBECONFIG, pero puedes utilizar cualquier otro kubeconfig mediante el flag –kube-config. El siguiente comando lleva a cabo el proceso de despliegue.

Mientras que Werf lleva a cabo el despliegue de la aplicación a través de Helm, el progreso se va mostrando en el terminal para todos aquellos componentes que se despliegan en el clúster.

Una vez que el despliegue ha acabado, Werf muestra un resumen de la release desplegada.

Si has desplegado la aplicación en un clúster local utilizando Minikube o Docker desktop, acuérdate de ejecutar el siguiente comando para que puedas acceder al servidor web a través del ingress.

Si todo ha funcionado tal y como debe, deberías poder acceder a la página de ejemplo, que muestra un logo muy molón 😏  .

Por cierto, ¿recuerdas aquellos valores súper secretos que se inyectaron como variables de entorno? Si compruebas las variables de entorno dentro del contenedor de cualquiera de los pods, podrás ver como los valores se importaron el claro.

TRUQUILLO 3: El comando converge combina los comandos werf build, werf publish y werf deploy. Fíjate en que el flag –custom-tag no se utiliza en este comando, ya que el tag de la imagen es generado por Werf y es renderizado utilizando las funciones de Go embebidas en el template.

Desmontando el chiringuito🎪

Una vez que hayas terminado de jugar con esta aplicación de prueba, toca eliminar la release de Helm y los recursos asociados. Ejecutando el siguiente comando, la release de Helm es eliminada del clúster.

Para eliminar las imágenes subidas al repositorio así como las imágenes intermedias generadas (stages), ejecuta el siguiente comando. Es importante tener en cuenta que para los repos de Dockerhub, es necesario especificar usuario y contraseña para poder borrar las imágenes.

TRUQUILLO 4: El comando cleanup puede utilizarse para eliminar los stages e imágenes que no están en uso. Es una buena práctica habitual el ejecutar este comando de forma diaria para almacenar exclusivamente las imágenes que están en uso.

Integraciones para CI/CD 🐙🐱

Como has podido ver, Werf es una herramienta muy poderosa, y por ello, tiene bastante sentido el integrarla en entornos de CI/CD para mejorar la automatización del ciclo de vida de las aplicaciones. Integrar Werf en cualquier sistema es fácil si se hace de forma 'manual' ya que se distribuye como un CLI, cuyo comportamiento puede configurarse por medio de variables de entorno. Además, Werf proporciona un comando, ci-env, que detecta la configuración del sistema de CI/CD y la almacena en las variables de entorno que utiliza Werf, haciéndolo incluso más fácil.

A parte de esto, se han desarrollado integraciones nativas para GitLab CI y GitHub Actions. El siguiente código muestra un ejemplo utilizando GitHub actions desarrolladas por Werf para desplegar aplicaciones en un clúster de Kubernetes cuando se añaden cambios en la rama deploy. La action werf/actions/converge@master ejecuta el comando werf converge , construyendo las imágenes y desplegando la aplicación en el clúster al que apunta el kubeconfig, y que se almacena como un secret en KUBE_CONFIG_BASE64_DATA.


Si quieres probar esta integración, puedes hacer fork del proyecto y utilizar la rama deploy, que está configurada para funcionar con GitHub actions. En este caso, si no se especifica un registry, Werf utilizará GitHub packages como repositorio para almacenar las stages y las imágenes.

Para permitir que tu clúster de Kubernetes pueda hacer pull de las imágenes de este registry, necesitarás crear unas credenciales de Docker como secret, especificando tu nombre de usuario y un Personal Access Tokende GitHub, y posteriormente añadirlo a la service account a utilizar (default en este caso).

Si todo funciona como debe, para cuando el job acabe, Werf habrá desplegado la aplicación en el clúster.

TRUQUILLO 5: Para obtener tu kubeconfig en base64 puedes utilizar el siguiente comando (asumiendo que tu kubeconfig local apunte al cluster remoto que quieres utilizar).

¡Gracias a estas actions previamente desarrolladas por el equipo de Werf, integrarlo en tu entorno de CI/CD puede ser mucho más fácil de lo que pensabas!

Sigue aprendiendo👩‍💻👨‍💻

Si te ha gustado Werf, tómate tu tiempo para investigar su documentación oficial, ya que te ayudará a entender mejor todos los comandos y las diferentes opciones que ofrece el CLI, así como todas las integraciones para CI/CD que soporta.

Además, la documentación presenta casos de uso avanzados que son realmente interesantes, ya que plantean escenarios complejos bastante cercanos a entornos productivos reales. Algunos de los más interesantes son:

Miguel Fontanilla

DevOps/Cloud Engineer at Orange. Cloud Architecture, Virtualization, Services Orchestration, Automation and Optimization. Always learning.

Related Posts

Únete a nuestra Newsletter

Lidera la Conversación en la Nube