Salt (SaltStack) es una herramienta de gestión de configuración y ejecución remota con arquitectura master/minion: un servidor central (el master) envía órdenes y estados a los agentes (los minions) a través de un bus de mensajes muy rápido. En este tutorial instalarás un Salt master y un minion en Ubuntu 24.04 desde el repositorio oficial del proyecto, aceptarás la clave del minion, ejecutarás comandos remotos y aplicarás un state que instala y configura Nginx usando datos de pillar.

Requisitos previos

Necesitas:

  • Dos servidores con Ubuntu 24.04 LTS, por ejemplo dos VPS de CubePath: uno hará de master y otro de minion. Con 1 GB de RAM por servidor es suficiente para este tutorial.
  • Un usuario no root con privilegios sudo en ambos.
  • Conectividad privada o pública entre ellos. El minion debe poder llegar al master por los puertos TCP 4505 y 4506.

En la guía se usan estos valores de ejemplo; sustitúyelos por los tuyos:

RolHostnameIP
Mastersalt-mastermaster_ip
Minionweb01minion_ip

Paso 1: Añadir el repositorio oficial de Salt

Los paquetes de Salt que trae Ubuntu están desactualizados. El proyecto Salt publica sus paquetes (compilación "onedir", con su propio Python) en un repositorio de Broadcom. Repite este paso en los dos servidores.

Crea el directorio de claves y descarga la clave pública del repositorio:

sudo mkdir -m 755 -p /etc/apt/keyrings
curl -fsSL https://packages.broadcom.com/artifactory/api/security/keypair/SaltProjectKey/public | sudo tee /etc/apt/keyrings/salt-archive-keyring.pgp > /dev/null

Descarga la definición del repositorio que mantiene el proyecto:

curl -fsSL https://github.com/saltstack/salt-install-guide/releases/latest/download/salt.sources | sudo tee /etc/apt/sources.list.d/salt.sources

La salida muestra el contenido del archivo, que apunta a packages.broadcom.com y usa la clave que acabas de descargar en Signed-By. Actualiza el índice y comprueba que los paquetes vienen de ese repositorio:

sudo apt update
apt-cache policy salt-master
salt-master:
  Installed: (none)
  Candidate: <versión>
  Version table:
     <versión> 500
        500 https://packages.broadcom.com/artifactory/saltproject-deb stable/main amd64 Packages

La versión concreta cambiará con el tiempo; lo importante es que el origen sea packages.broadcom.com.

Paso 2: Instalar y configurar el master

En el servidor master, instala el paquete:

sudo apt install salt-master

Crea los directorios donde vivirán los states y los datos de pillar. /srv/salt y /srv/pillar son las rutas por defecto de file_roots y pillar_roots, así que no hace falta tocar /etc/salt/master:

sudo mkdir -p /srv/salt /srv/pillar

Habilita e inicia el servicio:

sudo systemctl enable --now salt-master
sudo systemctl status salt-master
● salt-master.service - The Salt Master Server
     Loaded: loaded (/usr/lib/systemd/system/salt-master.service; enabled; preset: enabled)
     Active: active (running) since ...

Comprueba que escucha en los dos puertos que usa Salt (4505 para publicar órdenes y 4506 para recibir respuestas):

sudo ss -tlnp | grep -E '4505|4506'

Si usas UFW, permite esos puertos solo desde la IP del minion:

sudo ufw allow from minion_ip to any port 4505:4506 proto tcp

Para terminar este paso, anota la huella de la clave pública del master. La usarás en el minion para verificar que se conecta al master correcto:

sudo salt-key -F master
Local Keys:
master.pem:  ...
master.pub:  4d:1a:9c:...

Paso 3: Instalar y configurar el minion

En el servidor minion, instala el paquete:

sudo apt install salt-minion

El paquete arranca el servicio al instalarse, pero todavía no sabe dónde está el master. En lugar de editar /etc/salt/minion, crea un archivo propio en el directorio minion.d, que Salt lee automáticamente:

sudo nano /etc/salt/minion.d/master.conf
master: master_ip
master_finger: '4d:1a:9c:...'

master_finger es la huella de master.pub que anotaste en el paso anterior; con ella el minion rechaza cualquier master cuya clave no coincida.

Fija el ID del minion. Por defecto Salt usa el hostname, pero escribirlo explícitamente evita que la clave se registre con un nombre inesperado:

echo "web01" | sudo tee /etc/salt/minion_id

Reinicia el servicio para que lea la nueva configuración:

sudo systemctl restart salt-minion
sudo systemctl enable salt-minion

Revisa el registro. Es normal ver que el minion espera a que el master acepte su clave:

sudo journalctl -u salt-minion -n 20 --no-pager

Paso 4: Aceptar la clave del minion

Salt autentica cada minion con un par de claves. El master no confía en un minion hasta que aceptas su clave. En el master, lista las claves:

sudo salt-key -L
Accepted Keys:
Denied Keys:
Unaccepted Keys:
web01
Rejected Keys:

Antes de aceptarla, compara las huellas. En el master:

sudo salt-key -f web01

Y en el minion:

sudo salt-call --local key.finger

Si las dos huellas coinciden, acepta la clave en el master:

sudo salt-key -a web01

Confirma con y. Ahora prueba la comunicación:

sudo salt 'web01' test.ping
web01:
    True

Paso 5: Ejecutar comandos remotos

Con la clave aceptada ya puedes lanzar módulos de ejecución desde el master. El primer argumento es el objetivo ('*' son todos los minions) y después va la función:

sudo salt '*' cmd.run 'uptime'
sudo salt '*' grains.item os osrelease
sudo salt '*' pkg.version openssl
web01:
    ----------
    os:
        Ubuntu
    osrelease:
        24.04

Los grains son datos que cada minion reporta sobre sí mismo (SO, CPU, red). Puedes usarlos para apuntar a grupos de máquinas, por ejemplo solo a los minions Ubuntu:

sudo salt -G 'os:Ubuntu' test.ping

Paso 6: Escribir tu primer state

Los comandos remotos son útiles, pero la gestión de configuración se hace con states: archivos .sls en YAML que describen el estado deseado y que Salt aplica de forma idempotente. En el master, crea un state que instale Nginx, publique una página y mantenga el servicio en marcha:

sudo mkdir -p /srv/salt/nginx
sudo nano /srv/salt/nginx/init.sls
nginx:
  pkg.installed: []
  service.running:
    - enable: True
    - require:
      - pkg: nginx

/var/www/html/index.html:
  file.managed:
    - source: salt://nginx/files/index.html.jinja
    - template: jinja
    - user: root
    - group: root
    - mode: '0644'
    - require:
      - pkg: nginx

Crea la plantilla de la página. Salt la renderiza con Jinja en el minion, así que puede usar grains y pillar:

sudo mkdir -p /srv/salt/nginx/files
sudo nano /srv/salt/nginx/files/index.html.jinja
<h1>{{ salt['pillar.get']('web:site_name', 'Sitio sin nombre') }}</h1>
<p>Servidor: {{ grains['id'] }} ({{ grains['osfinger'] }})</p>

Ahora el top file, que decide qué states recibe cada minion:

sudo nano /srv/salt/top.sls
base:
  'web*':
    - nginx

Paso 7: Añadir datos con pillar

Pillar guarda datos por minion (nombres, puertos, credenciales) en el master y solo los envía a los minions a los que van destinados. Crea el archivo de datos:

sudo nano /srv/pillar/web.sls
web:
  site_name: Mi web gestionada con Salt

Y el top file de pillar, con la misma sintaxis que el de states:

sudo nano /srv/pillar/top.sls
base:
  'web*':
    - web

Refresca el pillar en los minions y comprueba que el dato llega:

sudo salt '*' saltutil.refresh_pillar
sudo salt 'web01' pillar.get web:site_name
web01:
    Mi web gestionada con Salt

Paso 8: Aplicar el state

Antes de cambiar nada, haz una ejecución en seco con test=True. Salt muestra lo que cambiaría sin aplicarlo:

sudo salt 'web01' state.apply test=True

Si el resultado es el esperado, aplícalo de verdad. Sin argumentos, state.apply ejecuta el "highstate", es decir, todo lo que el top file asigna al minion:

sudo salt 'web01' state.apply
Summary for web01
------------
Succeeded: 3 (changed=3)
Failed:    0
------------
Total states run:     3

Compruébalo desde el propio minion (o desde el master con curl http://minion_ip):

sudo salt 'web01' cmd.run 'curl -s http://localhost'
web01:
    <h1>Mi web gestionada con Salt</h1>
    <p>Servidor: web01 (Ubuntu-24.04)</p>

Si vuelves a ejecutar state.apply, verás changed=0: el estado ya coincide con lo declarado. Esa idempotencia es lo que permite aplicar los states de forma periódica sin miedo.

Solución de problemas

  • Minion did not return. [No response]: el minion está caído o no llega al master. Revisa sudo journalctl -u salt-minion en el minion y comprueba con nc -zv master_ip 4506 que el puerto está abierto.
  • La clave no aparece en salt-key -L: el minion no apunta al master correcto. Revisa /etc/salt/minion.d/master.conf y reinicia salt-minion.
  • El minion cambió de hostname o se reinstaló: el master rechazará la clave nueva con el mismo ID. Borra la antigua con sudo salt-key -d web01 y acepta la nueva.
  • No Top file or master_tops data matches found: el patrón del top file no coincide con el ID del minion. Compruébalo con sudo salt 'web*' test.ping.
  • Errores de renderizado en un .sls: casi siempre son de indentación YAML. Muestra el resultado renderizado con sudo salt 'web01' state.show_sls nginx.

Conclusión

Tienes un Salt master que controla un minion con autenticación por claves, ejecuta comandos remotos y aplica un state idempotente alimentado con datos de pillar. Como siguientes pasos, puedes guardar /srv/salt y /srv/pillar en un repositorio Git, cifrar los secretos de pillar con el renderer GPG de Salt y programar la aplicación periódica del highstate con el scheduler de Salt.