Notas Mentales de Un SysAdmin

blog sobre tecnologías para sysadmin y devops

Year: 2020

Comandos de bolsillo para gestionar rabbitmq

La entrada de hoy es una pura nota mental, de estas cosas que son muy útiles tener apuntadas en algún lado, pero que no se suelen usar con frecuencia una vez la configuración inicial está funcionando bien. Para ello, voy a tratar de describir, de la manera mas breve y concisa prosible, los principales componentes de RabbitMQ y cómo crearlos.

Introducción

RabbitMQ es un broker de mensajería multiprotocolo. En otras palabras, es un software que permite enviar y recibir mensajes dentro de un sistema. En este caso, la flexibilidad de rabbitmq soporta el ser implementado con facilidad tanto desde un sistema standalone como en un contenedor.

Se suele utilizar con frecuencia en infraestructuras de tipo docker y kubernetes, ya que es una alternativa sencilla para permitir la comunicación utilizando mensajes, las colas y los exchanges.

Los principales protocolos soportados son:

  • AMPQ: Es el protocolo nativo y el más utilizado. Es el que mayor cantidad de plugins tiene desarrollados y resulta fácil de implementar.
  • STOPM: Es un protocolo binario, diseñado principalmente para mensajes ligeros de publicación / suscripción.
  • HTTP and WebSockets: No es realmente un protocolo, pero es posible trabajar con él utilizando plugins específicos.

Para más info, aquí la documentación oficial.

Herramienta de comandos de línea

La herramienta de comandos que permite la gestión del nodo servidor de rabbitmq es rabbitmqctl. Lleva a cabo todas las acciones mediante la conexión al puerto del nodo RabbitMQ indicado. Para su autenticación, utiliza una contraseña secreta compartida, también conocida como archivo cookie.

Comandos para la gestión de usuarios

Por defecto, cuando desplegamos rabbitmq, crea el usuario guest / guest. Este usuario viene con una restricción de seguridad, la cual impide que sea utilizado fuera del ámbito «localhost».

Por ello y por seguridad, siempre es recomendable crear uno o varios usuarios, en función de las necesidades y de la exposición del servicio. Especificar que los roles aquí detallados, se utilizarán con el plugin de consola de gestión, accesible por defecto mediante interfaz web en el puerto 15672.

En la siguiente lista, podemos ver los diferentes roles que pueden ser asignados:

  • Ninguno (None): El usuario tendrá acceso vía protocolos, pero no a la interfaz gráfica de gestión.
  • management: Un usuario de lectura. Podrá ver colas, exchanges, virtual host, canales, estadísticas globales… siempre que él sea el propietario.
  • policymaker: Podrá hacer todo lo que puede hacer management, además de ver, crear y borrar políticas y parámetros de los virtual host a los cuales pueda hacer login vía AMQP.
  • monitoring: Podrá listar y leer virtual host, conexiones, canales, estadísticas, uso de memoria, clustering del nodo… aunque no sea el propietario.
  • administrator: Permisos máximos. Puede gestionarlo todo.

Con toda esta info, la sintaxis para crear un usuario sería:

rabbitmqctl add_user [nombre_usuario] [contraseña]

Dónde:

  • [nombre_usuario] es el nombre que queremos dar al nuevo usuario.
  • [contraseña] es la contraseña en plano que queremos asignarle al mismo.

Y para añadirlo a un determinado rol:

rabbitmqctl set_user_tags [nombre_usuario] [rol]

Dónde:

  • [rol] es una de las opciones descritas anteriormente

Para darle permisos totales sobre un determinado virtual host:

rabbitmqctl set_permissions -p [nombre_vhost] [nombre_usuario] [configure] [write] [read]

Dónde:

  • [nombre_vhost]: Es el nombre del virtual host sobre el cual queremos darle privilegios.
  • [configure]: Equivale a los permisos «Configure regexp».
  • [write]: Equivale a los permisos «Write regexp».
  • [read]: Equivale a los permisos «Read regexp».

La forma común de configurarla, es asignarles el valor «.*». Un ejemplo:

rabbitmqctl set permissions -p vhost usuario ".*" ".*" ".*"

Para asignar permisos sobre el topic de un virtual host, al sintaxis es parecida al anterior:

rabbitmqctl set_topic_permissions -p [nombre_vhost] [nombre_usuario] [nombre_exchange] [write] [read]

Dónde:

  • [nombre_exchange]: Es el objeto sobre el que se le da permisos. Es habitual darle permisos sobre (AMQP default).

Virtual Host

Los virtual host son grupos lógicos, que sirven para segmentar la información, de manera similar a como se haría en un apache. Para crearlos:

rabbitmqctl add_vhost [nombre_vhost]

Dónde:

[nombre_vhost]: Es el nombre que queremos darle a este grupo.

También es posible establecer el límite máximo de conexiones:

rabbitmqctl set_vhost_limits -p [nombre_vhost] '{"max-connections": [value]}'

Dónde:

[value]: Será un número entero entre 0 y 256.

Exchanges

Los mensajes de RabbitMQ no se envían directamente a una cola. En su lugar, se envían a un exchange. El exchange es el responsable de enrutar los mensajes a diferentes colas con la ayuda de enlaces y claves de enrutamiento.

Hay 4 tipos de exchanges diferentes:

  • direct: Enruta los mensajes con una clave de enrutamiento igual a la clave de enrutamiento declarada por la cola de enlace.
  • fanout: Enruta los mensajes a todas las colas vinculadas indiscriminadamente. Si se le proporciona una clave de enrutamiento, simplemente se ignorará.
  • topic: Enruta los mensajes a las colas cuya clave de enrutamiento coincide con todos o una parte de una clave de enrutamiento.
  • headers: Enruta los mensajes en función de una coincidencia de encabezados de mensaje con los encabezados esperados especificados por la cola de enlace.

Si os pica la curiosidad, he aquí un post en inglés bastante completo e ilustrativo. Pero vayamos al grano. Para listar todos los exchanges:

rabbitmqadmin -V test list exchanges

Y para crear uno:

rabbitmqadmin declare exchange name=[nombre_exchange] type=[tipo]

Dónde:

  • [nombre_exchange]: Es el nombre con el que vamos a identificar a este objeto.
  • [tipo]: Es uno de los valores descritos anteriormente.

Colas

En RabbitMQ, las colas son el objeto productor y consumidor de mensajes. Tienen multiples usos, como distribuir mensajes, balanceo de cargas, etc. Aunque su función principal es almacenar los mensajes para su posterior distribución.

Para listar las colas con toda la información disponible:

rabbitmqadmin -f long -d 3 list queues

Ejemplo para envíar un mensaje a una cola:

rabbitmqadmin publish exchange=amq.default routing_key=test payload="hola, mundo"

Ejemplo para recuperar el mensaje de una cola:

rabbitmqadmin get queue=test ackmode=ack_requeue_false

Conclusión

RabbitMQ es una herramienta altamente funcional y configurable. Una de las mayores ventajas es que va a simplificar nuestra manera de comunicarnos dentro de una infraestructura.

He querido llamar a esta entrada «de bolsillo», puesto que considero que apenas he rascado la superficie del producto, y que los comandos aquí descritos son básicos mínimos para poder empezar a trabajar. Para más info, RabbitMQ tiene una documentación extensa, detallada y con muchos ejemplos, que podréis encontrar aquí.

Problema resuelto!

Ejecutar comandos en una imagen docker contenida en un pod tras su arranque

Cuanto más conozco de Kubernetes, GCP y docker, más consciente soy de que es todo un universo paralelo en continuo cambio. Universo, porque a nivel de posibilidades, flexibilidad, configuraciones… las opciones son muy diversas pero no por ello excluyentes. La problemática que se me planteaba era la siguiente:

Tenía un despliegue (deployment), que generaba a partir de un yaml sencillo, el cual contenía básicamente un par de imágenes y los puertos expuestos. En una de esas imágenes, había que crear por seguridad un nuevo usuario con sus respectivos permisos y accesos.

En casos normales, hubiera bastado con levantar el deployment y asignarle almacenamiento persistente. El problema era que la configuración de este servicio se guardaba vinculada al nombre del pod. La situación era tal que así:

config@pod1
config@pod2

Mientras el pod estuviera vivo, no había problema, pero si el pod moría y se levantaba otro distinto, se perdía esa configuración. Y seamos realistas, Kubernetes está diseñado para que los pods se mueran en cuanto dejan de funcionar como se espera.

Solución: Crear ese usuario a la vez que el pod, ejecutando comandos de consola específicos. Así, cuando se levantase el pod con el nombre que se levantase, ese usuario existiría y funcionaría.

Ejemplo de lanzamiento de comando tras la creación del contenedor

apiVersion: v1
kind: Deployment
metadata:
  ...
spec:
  replicas: 1
  template:
    metadata:
        ...
    spec:
      containers:
        - name: auth
          image: [imagen-del-servicio]
          env:
          ports:
            - containerPort: 3000
          lifecycle:
            postStart:
              exec:
                command: ["/bin/sh", "-c", "[cmd]"]

Donde:

  • [imagen-del-servicio] es el nombre de la imagen que estamos utilizando para crear el contenedor dentro del pod.
  • [cmd] es el comando/s que queremos lanzar.
  • command: Sus corchetes son obligatorios.

A grandes rasgos, la configuración para lanzar los comandos viene definida dentro de la etiqueta lifecycle. Para más info, podéis consultar la documentación oficial de Kubernetes sobre cómo adjuntar controladores a eventos de ciclo de vida del contenedor.

Ojo! Es posible que la imagen de aplicación que estáis desplegando tarde unos segundos antes de poder aceptar comandos. Recomiendo siempre usar un sleep antes de lanzar ningún otro comando, donde [TIEMPO] es el valor en segundos que queremos darle.

command: ["/bin/sh", "-c", "sleep [TIEMPO]"]

Los comandos se pueden concatenar de la misma manera que lo haríamos en consola. Por ejemplo:

command: ["/bin/sh", "-c", "sleep 60 && echo 'Hola Mundo'"]

Problema resuelto!

Subir imagen docker local al Container Registry de GCP

En mis primeras semanas de andanzas con la plataforma de Google Cloud, me metieron de lleno a los leones. «Ves buscando información sobre docker y Kubernetes, a ver como podemos montar ahí cosas».

Uno de los fundamentales para poder empezar a trabajar de manera cómoda con el entorno GKE es utilizar el Container Registry. Y aunque la documentación de Google es copiosa y viene con muchos ejemplos, etc. me llevó sus días aprender a funcionar de manera ágil.

¿Qué es el Container Registry?

Empecemos por el principio. El Conainer Registry es la heramienta que necesitas utilizar si quieres gestionar imágenes Docker y poder empezar a trabajar con Kubernetes.

Entre las muchas funcionalidades que brinda, está la capacidad de realizar análisis de vulnerabilidades, además de la gestión de los accesos, integración con herramientas tipo Jenkins de CI/CD…

Todo esto esta muy bien Laura, pero… ¡Yo lo que quiero es poder subir una imágen para ir desplegar mi primer pod!

Requisitos previos

Lo más cómodo en GCP es tener descargada su herramienta de consola SDK. No solo para la siguiente implementación, sino para implementaciones sucesivas. Para ello necesitarás:

Si ya has instalado el SDK de Cloud, recuerda actualizarlo para tener siempre la última versión. Para ello, puedes lanzar desde la consola SDK el siguiente comando:

gcloud components update
  • Habilitar la API Cloud BuildAPI
  • Tener creado un proyecto en la plataforma. Si aún no lo tienes, aquí te dejo un enlace a la documentación oficial.
  • Tener instalado docker en local.

Autentícate en SDK

Antes de poder subir nada poder subir nada, necesitamos autenticarnos.

gcloud auth login

Lo siguiente que tenemos que hacer es seleccionar el proyecto con el que empezar a trabajar.

gcloud config set project [PROJECT_ID]

Donde [PROJECT_ID] es tu ID del proyecto de GCP.

Procedimiento

1. Abrir la herramienta de comandos favorita (en mi caso, PowerShell)
2. Re-etiquetar la imágen con el formato adecuado. La sintaxis es la siguiente, dependiendo si le añadís o no la etiqueta:

docker tag [IMAGEN_LOCAL] [REPOSITORIO]/[ID-PROYECTO]/[IMAGEN] 
docker tag [IMAGEN_LOCAL] [REPOSITORIO]/[ID-PROYECTO]/[IMAGEN]:[ETIQUETA]

Los repositorios disponibles para Container Registry son:

  • gcr.io
  • us.gcr.io
  • eu.gcr.io
  • asia.gcr.io.

Ejemplo

docker tag mongo gcr.io/mi-proyecto/mongo

3. Enviar la imagen (push) al Container Registry.

docker push [HOSTNAME]/[PROJECT-ID]/[IMAGE]
docker push [HOSTNAME]/[PROJECT-ID]/[IMAGE]:[TAG]

Ejemplo

docker push gcr.io/mi-proyecto/mongo

4. Comprobar que la subida. Si todo ha ido bien, veremos una nueva entrada en la consola web.

Problema resuelto!

Instalar JDK 8 en Debian 10

Esta nota mental viene con historia.

Mi segunda semana en mi nuevo trabajo. Me piden que levante una máquina virtual en Google Cloud Platform. Los requisitos no eran descabellados, última versión de Debian, JDK8 y poco más. La instalación clásica no funciona. Hago una búsqueda en caché y tampoco encuentro nada. Que raro.

Aviso a mi compañera, super fan de linux para que me echara un cable en mi atasco. Estuvimos toda una mañana consultando diferentes documentaciones hasta que al fin dimos con el verdadero problema: A la versión 10 de Debian han decidido no ponerle los paquetes de JDK8, solo instala la versión más reciente.

La solución que encontramos y aplicamos, básicamente, consistía en añadirle un repositorio principal donde si se encontrara ese paquete.

sudo nano /etc/apt/sources.list

Añadimos la siguiente linea al fichero de configuración

deb http://deb.debian.org/debian/ sid main

Lanzamos update+upgrade:

sudo apt-get update
sudo apt-get upgrade

Y por último ya instalamos la versión deseada de jdk.

sudo apt-get install -y openjdk-8-jdk

Siempre podemos y debemos comprobar que la versión instalada está bien.

java -version

Resultado:

openjdk version "1.8.0_252"
OpenJDK Runtime Environment (build 1.8.0_252-8u252-b09-1-b09)
OpenJDK 64-Bit Server VM (build 25.252-b09, mixed mode)

Problema resuelto!

Scroll hacia arriba