Continuous Delivery: Scripting para balanceador F5

F5 es una empresa que provee un conjunto de productos relacionados a networking: firewall, balanceador, etc. Es común encontrarse con balanceador F5 en ambientes productivos de alta carga para repartir carga entre varios nodos.

Al mismo tiempo, en aplicaciones de cierta criticidad, es común que las actualizaciones se hagan siguiendo alguna estrategia tipo «canary», esto es:

  • «Se saca» un nodo del balanceador
  • Se le instala la nueva versión de la aplicación
  • Se verifica que la aplicación funcione correctamente
  • «Se restaura» el nodo en el balanceador
  • Se repite el proceso con cada uno de los restantes nodos

Cuando pretendemos trabajar en un esquema de continuous delivery es imprescindible que este proceso se haga de forma automatizada y para ello es necesario poder interactuar con balanceador programáticamente.

Dicho esto, he aquí un fragmento de código Python para interactuar un F5 Big-IP:

# este script depende de varias variables:
# * f5_login_url (url del balanceador para obtener el token)
# * f5_user (usuario de f5 para obtener el api token)
# * f5_pass (password de f5 para obtener el api token)
# * f5_self_service_url (url de la api del balanceador)
# * node_name (nombre del nodo sobre el que queremos operar)
# * node_id (id del nodo sobre el que queremos operar)
# * pool_id (id del pool del balanceador en el que está el nodo)


import requests
import json
import sys

# primero hay que loguearse y obtener un token
login_request = '{ "username":' + f5_user + ', "password":' + f5_pass }'
response = requests.post(f5_login_url, login_request)
if response.status_code == 200:
  response_json = json.loads(response.text)
  token = response_json["token"]["token"]
else:
  token = {}
  print('No pudo obtener token', file=sys.stderr)
  exit(1)

# sacamos el nodo
headers = { 'X-F5-Auth-Token': token }
disable_request = '{"name":"Self-Service_' + node_name + '", "resourceReference":{' \
  + '"link":"https://localhost/mgmt/cm/adc-core/working-config/ltm/pool/' + pool_id \
  + '/members/' + node_id + '"},"operation":"force-offline"}'
response = requests.post(f5_self_service_url, data=disable_request, headers=headers)

# restauramos el nodo
enable_request = '{"name":"Self-Service_' + node_name + '", "resourceReference":{' \
  + '"link":"https://localhost/mgmt/cm/adc-core/working-config/ltm/pool/' + pool_id \
  + '/members/' + node_id + '"},"operation":"enable"}'
response = requests.post(f5_self_service_url, data=disable_request, headers=headers)

Espero les resulte útil.

El mayor impedimento para una iniciativa DevOps

Si bien el término DevOps no lo explicita, toda iniciativa DevOps requiere del apoyo e involucramiento de negocio, pues la razón central de DevOps es poder dar respuesta rápida y consistente a las necesidades del negocio.

Si el negocio no se sube al barco de la iniciativa DevOps las probabilidades de éxito y sus beneficios estarán fuertemente limitados. Toda iniciativa DevOps requiere de cierta inversión, ya sea a nivel de capacitación, compra de herramientas o simplemente horas-hombre. Si el negocio ve a IT como un centro de costos difícilmente estará dispuesta a hacer la inversión en DevOps.

Desarrollo y Operaciones pueden tener las mejores intenciones, pueden colaborar de forma muy fluida, pueden incluso ser un mismo equipo, pero nada de eso alcanza si el negocio sigue viendo IT como un centro de costos.

Las empresas que logran implementaciones exitosas DevOps son aquellas que ven IT como una área estratégica, capaz de habilitar oportunidades de negocio, capaz de impactar en la performance del negocio/organización y de proveer un valor diferencial de cara al mercado.

En más de una ocasión me he encontrado trabajando con áreas de IT, tratando de armonizar la relación entre desarrollo y operaciones, pero sin contar con el apoyo suficiente del negocio. Así y todo es posible implementar algunas mejoras, pero difícilmente se logren los plenos beneficios de una cultura DevOps.

Charla: DevOps sin DevOps

Esta es la charla que di ayer en el contexto de la conferencia Agile en Español 1 iteración. Hubo alrededor de 90 personas conectadas pero en las actividades interactivas participó aproximadamente la mitad.

En un momento de la charla pedí a los participantes que compartieran por escrito las nuevas responsabilidades/habilidades necesarias para poder trabajar con un mindset DevOps. La siguiente imagen es el resultado de esta actividad.

Hay algunos de los términos que que surgieron que por distintos motivos me llamaron poderosamente la atención. Por ejemplo, cuestiones como integración continua, cobertura de código y tdd tienen más de 20 años, sin embargo parece que hay gente/organizaciones que recién tomaron conciencia de ellas con el auge de DevOps.

No llegamos a cubrir todos los temas que tenia en mente y por eso agendé un meetup para el este sábado 29 de mayo para continuar la charla, debatir, compartir experiencias e inquietudes. Los interesados pueden registrarse aquí y recibirán el link de conexión.

Aquí están disponibles los slides de la charla:

DevOps sin DevOps

Este es título de la charla que estaré dando en el contexto de la primera conferencia de la Agile Alliance en Español. Esta conferencia será en modalidad online los días 27 y 28 de mayo. En particular mi charla será el jueves 27 de mayo a las 18 hs (GMT-3). Les comparto aquí el resumen de la charla.

Sin bien llevamos varios años hablando de DevOps, sigue habiendo mucho ruido y confusión respecto a su significado y estrategia de implementación. Muchos creen que DevOps implica tener un área o gente especializada con dicho nombre/rol, lo cual es completamente discutible y hasta en un punto contrario a lo que muestran varios casos de éxito.
En esta sesión repasaremos los pilares fundamentales de esta corriente junto algunos casos exitosos de implementación de DevOps sin “Ingenieros DevOps” ni un área especializada en DevOps.

Consultores DevOps: entre purismo, pragmatismo, ética profesional y complicidades

La semana pasada comencé a trabajar en la segunda etapa de una iniciativa «DevOps». Luego de la reunión del kick-off un colega que está trabajando conmigo en este proyecto pero que no había participado de la primera etapa me consulta: «¿qué onda esto de armar pipelines de deployment de aplicaciones sin ningún tests automatizado?»

¿Qué onda? Riesgoso. Lo hablé con el cliente y fui muy explícito:

«Por este camino más que DevOps vamos a hacer DevOoooops!».

Habiendo hecho la advertencia del caso y siendo conscientes que el término DevOps queda grande para lo que estamos haciendo, mantuve mi ética profesional. Dejé mi purismo de lado y me convertí en complice de la situación con el único objeto de lograr una mejora en el proceso de software delivery. Concretamente lo que estamos haciendo es automatizar el proceso de build y deployment.

Previo a nuestra intervención varios proyectos generaban sus binarios en la máquina de un developer y luego los hacían llegar al área encargada de deployments via un sistema de tickets. Algunos otros equipos tenían un Jenkins, pero solo para compilación. Los encargados del deployment ejecutaban entonces varias tareas manual para desplegar el binario pedido en el ambiente especificado. En este este contexto estamos buscando que:

  • Todo el código esté almacenado en la herramienta oficial de versionado de la organización (GitLab)
  • Todos los binarios se generen en un servidor (GitLab-CI) sin depender de la máquina de ningún developer
  • Los binarios generados sean subidos automáticamente a un repositorio de binarios (Nexus)
  • El proceso de deployment comience descargando el binario a desplegar desde el repositorio de binario y a continuación ejecute un script parametrizado de forma de utilizar el mismo script para despliegue a todo ambiente.
  • El proceso de deployment sea ejecutado por una herramienta de CI/CD (GitLab-CI)

¿Esto es hacer DevOps? Esto solo no lo es. Sin duda una estrategia DevOps debería incluir versionado y automatización, pero también muchas otras cuestiones como colaboración entre negocio-desarrollo-operaciones, entrega frecuente, manejo de infraestructura como código, integración continua y pruebas automatizadas (como bien observaba mi colega).

Personalmente suelo hacer mucho hincapié en todas esta cuestiones y las aplico en todos los proyectos en los que trabajo como team member. Sin embargo, cuando hago trabajo de consultoría, no está en mí decidir qué prácticas aplicar. Claro que sugiero (con mucho énfasis en general) pero la priorización y decisión está en manos del cliente. Lo que suele ocurrir es que dentro de todo el set de prácticas DevOps, una de las que ofrece mayor visibilidad y retorno temprano de inversión es la automatización de deployments. Entonces se suele comenzar automatizando los deployments de una aplicación y a continuación surge el dilema, ¿avanzamos en ancho o en profundidad?. O sea: ¿seguimos trabajando sobre esa aplicación para agregarle pruebas automatizadas (y demás prácticas)? o ¿nos movemos al equipo de al lado para seguir automatizando deployments? En general mis clientes suelen optar por lo segundo, avanzar en ancho y dejar en manos del cada equipo el avance en profundidad (pero esto no siempre ocurre). Obviamente que esto no es lo mismo en todos los clientes, esta descripción corresponde a una organización muy grande con cientos de equipos de equipos de desarrollo y varios cientos de aplicaciones.

En fin, esta es la vida del «consultor responsable», bailar el fino equilibro entre purismo y pragmatismo honrando siempre la ética profesional.

Dos estrategias para la adopción de DevOps

Partiendo de la premisa que DevOps viene a intentar mejorar el flujo de software delivery, reduciendo las fricciones entre desarrollo y operaciones, intentando incluso derribar los silos, me he encontrado con distintas estrategias de implementación. De forma muy simplificada he logrado identificar dos patrones recurrentes cuando una organización adopta una estrategia DevOps. A falta de creatividad en este artículo las denominaré «Desarrollo Empoderado» y «Operaciones Serviciales«.

Desarrollo Empoderado

Esta estrategia implica empoderar a los equipos de desarrollo para tomar responsabilidad sobre todo el proceso de delivery. Esto tiene dos implicancias fuertes. Por un lado el equipo de desarrollo incorpora más responsabilidades y habilidades, se involucra con la infraestructura, el pipeline de delivery, etc. Por el otro el equipo de operaciones «suelta» un poquito, comparte más con los equipos de desarrollo a partir de involucrarse en el día a día del proyecto, etc. Incluso en algunos casos una persona de operaciones se suma como un team member más al equipo de desarrollo.

Operaciones Serviciales

En esta estrategia operaciones sigue manteniendo cierta distancia con desarrollo pero a partir de un fuerte trabajo de automatización facilita el día a día del equipo de desarrollo proveyendo una experiencia que podríamos denominar «operaciones como servicio». En estos casos la visión de operaciones es: «que desarrollo se concentre en las user stories y hagamosle la vida lo más simple posible y que con un par de clicks puedan tener acceso los recursos que necesiten«. Esto requiere obviamente, incorporar habilidades, herramientas y sobre todo proactividad, colaboración y vocación de servicio.

En cierto modo estas dos estrategias, descriptas aquí de forma muy simplificada, pueden ubicarse en dos extremos de un continuo de estrategias que incorporan elementos de cada una.

Personalmente, trabajando en desarrollo, me siento mucho más cómodo con la estrategia Desarrollo Empoderado, pero soy consciente que puede no ser así para todos los desarrolladores y también que en algunas organizaciones puede ser más conveniente una estrategia del estilo Operaciones Serviciales.

Kubernetes: logs y sidecar containers

Al trabajar con Kubernetes es posible acceder a los logs de nuestras aplicaciones/contenedores utilizando la herramienta kubectl. Para la etapa de desarrollo (por ejemplo cuando estamos trabajando con Minikube) esto puede estar bien, pero para un ambiente de test/producción esta solución se queda corta por múltiples motivos. Principalmente no resulta seguro ni cómodo andar conectandose con kubectl a un cluster productivo. Una solución bastante habitual para esta problemática es utilizar alguna solución de agregación de logs. Estas soluciones consisten básicamente en concentrar todos los mensajes de log en un almacenamiento centralizado y explotarlo con alguna de visualización. Para concentrar los mensajes de logs se suele hacer lo siguiente: junto con el contenedor de nuestra aplicación desplegamos desplegamos en el mismo pod un contenedor sidecar. Este contenedor sidecar colecta los mensaje de logs generados por nuestra aplicación y los envía un concentrador. El siguiente gráfico muestra un esquema de esta solución

Existen varias herramientas para implementar esta estrategia. En el contexto de la arquitectura de referencia que estamos armando para el trabajo práctico final de MeMo2@fiuba vamos a utilizar la propuesta de Sumologic. Para esto utilizamos un container de Sumologic, desplegado como sidecar, que recolecta los logs de nuestra aplicación y los envía a Sumologic que almacena los mensajes, los procesa y los pone disponibles via una interface web.

El siguiente fragmento de código muestra un posible descriptor de deployment para implementar esta solución.

De acuerdo a esta configuración, tenemos los dos contenedores compartiendo un volumen (logs-data). El bot (la aplicación en cuestión) escribe los mensajes de log en ese volumen de donde son leídos por el colector de Sumologic. Adicionalmente el colector de Sumologic tiene un segundo volumen (config-data) de donde lee su configuración. Al mismo tiempo ambos contenedores reciben como variables de ambiente algunos secrets (el telegram token, la key de sumologic, etc) y demás parámetros de configuración (el log level por ejemplo).

Nota 1: el uso de sidecar containers es un patrón muy popular que se utiliza para algunas otras cuestiones más allá de la recolección de logs.
Nota 2: por cuestiones de foco hay fragmentos de código del deployment que fueron removidos para dejar solamente el código relevante para este artículo

Deploy de contenedores a Heroku con GitLab

En mi opinión gran parte de la popularidad que alcanzó Heroku se debió a la facilidad con la que era posible desplegar una aplicación: git push. Básicamente teniendo el código fuente en un repositorio Git, solo basta con agregar un nuevo remote provisto por Heroku y hacer push al mismo. Cuando Heroku recibe el código fuente ejecuta un build-pack que básicamente «preparar el ambiente» para poder ejecutar el código recibido. Dependiendo del lenguaje ese build-pack puede instalar dependencias, compilar e incluso ejecutar migrations. Esta es seguramente la estrategia más utilizada al usar Heroku pero no es la única. Otra opción es ejecutar directamente un contenedor Docker. Esta opción trae un poco más de complejidad pero al mismo tiempo trae algunos beneficios interesantes.

La opción de utilizar un contenedor nos da más «libertad»/flexbilidad. Con el modelo de ejecución tradicional nuestra aplicación está restringida a lo establecido por el build-pack (aún cuando es posible crear build-packs, eso ya tiene otro costo), mientras que al correr un contenedor podemos poner lo que querramos dentro. Al mismo tiempo, al poder especificar el Dockerfile tenemos la posibilidad de ajustar ciertos aspectos del runtime. Finalmente, el correr un contenedor nos también la libertad de salir de Heroku a un costo más bajo.

En el contexto de MeMo2 @ Fiuba, utilizamos Heroku en modo «tradicional» durante el primer trabajo grupal pero para el segundo TP grupal tenemos la intención de utilizar un modelo basado en contenedores ya que queremos estudiar algunas de las implicancias de este modelo en términos de configuración management y deployment pipelines.

Explicada la motivación veamos entonces algunas cuestiones de implementación. En primer lugar debemos crear la imagen del contenedor que querramos ejecutar, para esto podemos delegar la creación de la imagen en Heroku o bien podemos crearla nosotros mismo y luego darsela a Heroku. Una vez que la imagen está construida y subida Heroku, basta una invocación a API rest para desplegarla (también es posible haciendo uso del heroku-cli). Al mismo tiempo, si es necesario ejecutar algún tipo de inicialización antes de levantar el contenedor Heroku nos da posibilidad de ejecutar un esa inicialización con otra imagen. Esto eso, debemos crear una imagen llamada «release», subirla a Heroku y cada vez que disparemos un deploy Heroku, se ejecutar primero la imagen release para hacer las tareas de incialización y luego se pondrá a correr el contenedor de nuestra aplicación. Todo esto está explicado con bastante detalle la documentación oficial de Heroku.

Llevando todo esto a nuestro escenario de MeMo2, vamos construir nuestra imagen Docker como parte nuestro pipeline de GitLab y vamos a almacenarla en la propia registry de Gitlab. Luego en el paso de deploy haremos un push de la imagen a Heroku y dispararemos el deploy via la API Rest. A continuación comparto algunos snippets de código de nuestro pipeline que puede resultar útiles para quienes pretendan implementar una solución similar con Heroku y Gitlab.

El siguiente fragmento de código corresponde a job de GitLab que crea la imagen Docker y la publica en la registry de GitLab

package_job:
  stage: package
  image: docker:stable
  before_script:
    - echo -n $CI_JOB_TOKEN | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY
  script:
    - VERSION=$(cat VERSION.txt)
    - docker build -f Dockerfile.prod -t $REGISTRY_URL/$APP_NAME:$VERSION .
    - docker tag $REGISTRY_URL/$APP_NAME:$VERSION $REGISTRY_URL/$APP_NAME:latest
    - docker push $REGISTRY_URL/$APP_NAME:$VERSION
    - docker push $REGISTRY_URL/$APP_NAME:latest

A continuación tenemos el fragmento de código correspondiente al job de deploy el cual descarga la imagen a desplegar de la registry de Gitlab y la sube Heroku. Finalmente invoca a script de deploy que interactua con la API Rest de Heroku.

deploy_dev_job:
  stage: deploy
  image: docker:stable
  before_script:
    - apk add curl
    - echo -n $CI_JOB_TOKEN | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY
    - docker login -u _ -p $HEROKU_TOKEN registry.heroku.com
  script:
    - VERSION=$(cat VERSION.txt)
    - docker pull $REGISTRY_URL/$APP_NAME:$VERSION
    - docker tag $REGISTRY_URL/$APP_NAME:$VERSION registry.heroku.com/$HEROKU_APP/web
    - docker push registry.heroku.com/$HEROKU_APP/web
    - export IMAGE_ID=`docker inspect registry.heroku.com/$HEROKU_APP/web --format={{.Id}}`
    - ./scripts/deploy_app.sh

Finalmente este es el script que invoca a la API de Heroku (este script es algo que definitivamente puede mejorarse)

#!/bin/sh
set -x 

DATA='{ "updates": [ { "type": "web", "docker_image":"'
DATA="$DATA$IMAGE_ID"
DATA=$DATA'" } ] }'

curl -X PATCH https://api.heroku.com/apps/$HEROKU_APP/formation --header "Content-Type: application/json" --header "Accept: application/vnd.heroku+json; version=3.docker-releases" --header "Authorization: Bearer ${HEROKU_TOKEN}" --data "$DATA" --fail

Cierro con algunos comentarios:

  • Estos fragmentos de código requieren del uso de un API token de Heroku que debe ser configurado en las variables del ambiente del pipeline.
  • Ambos jobs del Gitlab asumen la existencia del un archivo VERSION.txt que contiene el tag correspondiente a la imagen docker que construye/publica/despliega. Típicamente ese archivo se genera en el build y se lo propaga por el pipeline o bien está en el repositorio de código fuente.
  • Estos fragmentos son ejemplos que pueden ser mejorados y/o ajustados para contextos más específicos. De hecho es muy posible que en los próximos días les aplique algunos ajustes.

DevOops! tal vez igual sirve

Hace un tiempo escribí sobre algunos malentendidos de DevOps en la práctica, hoy quiero compartir algunas otras situaciones a la luz de una definición formal.

De acuerdo a Len Bass y sus colegas del SEI DevOps tiene 5 pilares fundamentales:

  1. Operaciones como ciudadano de primera categoría en el proceso de software delivery
  2. Involucramiento de los desarrolladores en los incidentes productivos
  3. Un proceso formal de deployment
  4. Continuous Delivery
  5. Infrastructure as Code

Pero en la práctica veo que muchas organizaciones que:

  • Pasan completamente por alto los dos primeros puntos, son cuestiones casi «culturales» que no son triviales de cambiar.
  • Van directo al punto 5, incorporan herramientas para la automatización de infraestructura para lo cual contratan algunas nuevas personas, típicamente bajo del rol de «Ingenieros DevOps». Hacer esto es muy más simple que pretender cambiar una estructura organizacional o un mindset.
  • El punto 4 lo toman solo parcialmente, invierten en la automatización de pipelines de despliegue (tarea que hacen los recientemente incorporados «Ingenieros DevOps») pero ni noticia de la automatización de pruebas (claro, los DevOps no automatizan pruebas)
  • El punto 3 lo logran como un efecto colateral de la automatización de los despliegues.

El resultado dista bastante de la idea de DevOps de Bass, pero no es tan malo porque a pesar de ello representa una mejora radical respecto de la situación previa.

#HistoriaDeTrinchera: equipo completo

Al cabo de 6 iteraciones hemos completado el equipo. Resulta que al comenzar la sexta iteración se sumó el miembro faltante. Tenemos ahora cubiertos todos los roles/habilidades que consideramos necesarios para llevar adelante el proyecto y al mismo tiempo hemos alcanzado, a mi criterio (no estoy seguro que mis colegas compartan) el número máximo de personas que el equipo puede tener.

Nuestra expectativa es poder tener dentro del equipo todas las habilidades (y autorizaciones) para operar en un esquema de entrega continua dentro de ciertas restricciones organizacionales que por el momento no parecen estar abiertas a negociación (tema de otro post). En este sentido tenemos los siguientes roles/habilidades:

  • Developer
  • Tester
  • Diseñador de UI/UX
  • Facilitador
  • Product Owner
  • Infraestructura

Estos roles/habilidades son ocupados por personas que están en el día a día del proyecto y que como tal participan diariamente de las reuniones de sincronización (daily standups). Algunas aclaraciones que pueden resultar relevantes para el lector:

  • Developers: apuntamos a que puedan desarrollar una funcionalidad de punta a punta, lo cual implica tanto hacer tanto front (angular) como back (netCore) y tocar algunas cuestiones de infra como pipeline de CI/CD y generación de los correspondientes contenedores.
  • Testers: en el sentido de Agile, trabajan muy de la mano de la gente de negocio y con una visión funcional. Colaboran en la identificación/definición de casos y también en su automatización.
  • Diseñadores de UI/UX: diseñan/validan la experiencia del usuario y definen las pantallas. Trabajan muy de cerca con la gente del negocio y los usuarios. No codean, su trabajo llega hasta el maquetado con alguna herramienta tipo Miro, Marvel y Zeplin.
  • Facilitadores: llamados a veces Scrum Masters o coaches, ayudan a que el equipo mantenga el foco en el proceso y la entrega de valor, destraban impedimentos y mantienen los diálogos abiertos con otros equipos/áreas.
  • Product Owners: es gente con perfil de negocio que define el norte del producto, concentra los pedidos de los usuarios y los prioriza.
  • Infraestructura: más allá del control que tenga el equipo sobre la infraestructura es fundamental que el equipo entienda (y pueda definir y discutir) ciertas cuestiones de infraestructura porque pueden impactar en la implementación de algunas características/funcionalidades de la aplicación. En este caso particular, la organización tiene un área de DevOps con la cual nosotros como equipo de desarrollo trabajamos muy cerca en un ida y vuelta constante. Luego ese equipo de DevOps se encarga de la interacción con el resto de los sectores de infraestructura como seguridad, networking, etc.
    (este es un punto que puede resultar polémico y por ello le dedicaré un post aparte)

Todo este conjunto de roles/habilidades está repartido entre 12 personas, varias de ellas con asignación parcial (yo entre ellas). Para mi gusto ya son demasiadas personas y como algunos colegas coinciden con esto, es muy posible que en el corto/mediano plazo sumemos más gente y partamos el equipo en 2.