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
sudoen 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:
| Rol | Hostname | IP |
|---|---|---|
| Master | salt-master | master_ip |
| Minion | web01 | minion_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.
Notael repositorio contiene varias ramas (LTS y STS) y apt instala la más reciente. Si quieres quedarte en una rama concreta, fíjala con un archivo en
/etc/apt/preferences.d/antes de instalar, tal como indica la guía de instalación oficial de Salt. Master y minion deben usar la misma versión o el master una más nueva.
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. Revisasudo journalctl -u salt-minionen el minion y comprueba connc -zv master_ip 4506que 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.confy reiniciasalt-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 web01y 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 consudo salt 'web*' test.ping.- Errores de renderizado en un
.sls: casi siempre son de indentación YAML. Muestra el resultado renderizado consudo 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.
