Enseñando prácticas DevOps

La semana pasada presenté en la conferencia ARGENCON 2022, IEEE Biennial Congress of Argentina un artículo formal (experience report) que describe la forma en que abordamos las cuestiones relacionadas a DevOps en el contexto de MeMo2

Este artículo junto con los publicados por Sergio Villagra (Teaching software engineering: an active learning experience) y Carlos Fontela (Learning Communication Skills in Software Development Management Courses: An Active Learning Experience), resume de forma bastante acabada el núcleo de Ingeniería de Software de la carrera de Licenciatura en Análisis de Sistemas de Universidad de Buenos Aires. En un par de semanas el artículo estará disponible en IEEE Explore.

Ingeniería de Software en la Era DevOps

Este el título de la charla/tutorial que dí la semana pasada en el contexto de CIbSE. En Zoom hubo unas 80 personas conectadas pero de las actividades interactivas que propuse, participaron alrededor de 30, un buen número de todas formas.

El punto central de mi de charla fue el hecho de que los escenarios que enfrentamos actualmente en la entrega de software nos llevan a tener que lidiar con ciertas cuestiones que tradicionalmente la ingeniería de software no ha atendido presentes. Al mismo tiempo, dichas cuestiones son centrales dentro del movimiento DevOps. Esto plantea un dilema: ¿es DevOps una disciplina distinta a la Ingeniería de Software? Pues yo creo que no. A mi parecer la Ingeniería de Software debe incluir DevOps. De hecho algunas de prácticas DevOps no son nuevas, sino que han sido parte de la Ingeniería de Software desde hace mucho tiempo. Ejemplo: Integración Continua.

En línea con esta idea, durante mi disertación mencioné varios libros que deberíamos tener presentes a la hora de plantear una Ingeniería de Software que incluya la temática DevOps:

Actualización: ya está disponible el video de la sesión, aquí.

Logs centralizados para aplicaciones Kubernetizadas

Cuando corremos una aplicación en Kubernetes (y en términos más generales en un arquitectura distribuida) donde potencialmente existe más de una instancia de nuestra aplicación resulta conveniente (o incluso imprescindible) poder acceder a los logs de nuestra aplicación en forma centralizada. O sea, en lugar de acceder a cada servidor/nodo donde corre nuestra aplicación, deberíamos poder acceder a una única ubicación y ver ahí mismo los logs de todas las instancias de nuestra aplicación.

Para implementar una solución de este tipo cuando nuestra aplicación corre en Kubernetes existen diferentes estrategias posibles. Voy a referir a continuación 3 estrategias que a mi parecer son bastante habituales, pero antes veamos algunas generalidades.

El tener un log centralizado típicamente implica dos cuestiones: por un lado recolectar los mensajes de log para enviarlos a un lugar central y por otro lado poder acceder a esos mensajes de una forma práctica. Adicionalmente podríamos mencionar el procesamiento de esos mensajes para poder consultarlos de forma más práctica/específica o incluso para accionar ante ciertos mensaje. Esta problemática es tan habitual en la actualidad que existen múltiples productos y servicios para implementarla. Entre las soluciones más populares podemos mencionar New Relic, Datadog, Sumologic y ELK. En general estas soluciones son mucho más amplias que el manejo de logs, son soluciones de monitoreo que incluyen los logs como una funcionalidad particular. Volviendo al foco de este artículo, nos vamos a concentrar puntualmente en el primer paso de este flujo: la recolección de los mensajes de log.

Estrategia Sidecar

Esta estrategia consisten en desplegar dentro cada uno de nuestros pods, a la par del contenedor que corre nuestra aplicación, un contener sidecar que tome los mensajes de log de nuestra aplicación y los envíe a la fuente central. Una form típica de implementar esto es haciendo que nuestra aplicación escriba el log en un archivo de un volumen compartido del cual luego serán leídos por el colector de logs que corre en el contenedor sidecar.

Esta estrategia resulta bastante transparente para la aplicación pero requiere montar el sidecar en cada manifiesto de deployment.

A modo de ejemplo, si utilizamos el servicio de logs centralizados de Sumologic, podemos utilizar esta imagen Docker para montar nuestro contenedor sidecar.

Estrategia Daemonset

En el contexto de kubernetes un daemonset es un pod que corre en cada nodo del cluster de Kubenetes. Entonces podemos desplegar un daemonset que colecte los logs leyendo el stdout de todos contenedores corriendo en el nodo.

Esta estrategia resulta muy económica de implementar, basta hacer que nuestra aplicación escriba los mensajes de log al stdout y desplegar el daemonset. Luego al agregado de nuevas aplicaciones y nodos es completamente transparente.

A modo de ejemplo, si utilizamos el servicio de log de Papertrail, podemos utilizar este daemonset para enviar los logs.

Estrategia Logger con handler/appender remoto

Esta estrategia no tiene nada que ver con Kubernetes y consiste en hacer que sea nuestra aplicación la que directamente envíe los mensajes de log al log central. La forma prolija de implementar esto es usando alguna biblioteca log como Log4J, Log4Net, etc y configurar esta biblioteca con un handler que envíe la información al log central.

Lo interesante de esta solución es que resulta muy simple de implementar. Pero al mismo tiempo tiene un potencial costo de performance que hace que esta estrategia no sea conveniente para algunos escenarios, o sea: si tenemos una aplicación web atendiendo pedidos de nuestros usuarios y en ese contexto el logger van a enviar los mensajes de log via HTTP dentro del mismo proceso podemos tener algún issue de performance. Sin embargo, si nuestra aplicación es un job que corre en background, puede que este costo del log no sea relevante.

A modo de ejemplo, si nuestra aplicación está construída en Ruby podemos utilizar la gema SemanticLogger con un appender http para enviar los Sumologic o Papertrail.

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