Puppet es una herramienta de gestión de configuración declarativa: describes en manifiestos cómo debe quedar cada servidor y un agente lo aplica periódicamente, corrigiendo cualquier desviación. En este tutorial instalarás un servidor Puppet y un agente en Ubuntu 24.04 usando OpenVox, la distribución de código abierto de Puppet mantenida por la comunidad Vox Pupuli, firmarás el certificado del agente y aplicarás un módulo propio con datos en Hiera.

Requisitos previos

Necesitas:

  • Dos servidores con Ubuntu 24.04 LTS, por ejemplo dos VPS de CubePath:
    • Servidor Puppet: al menos 2 vCPU y 4 GB de RAM (Puppet Server corre sobre la JVM).
    • Agente: cualquier tamaño.
  • Un usuario no root con privilegios sudo en ambos.
  • El puerto TCP 8140 del servidor accesible desde el agente.

Valores de ejemplo usados en la guía:

RolFQDNIP
Servidorpuppet.example.comserver_ip
Agenteweb01.example.comagent_ip

Paso 1: Configurar nombres de host

Puppet identifica cada máquina por su certificado, que se emite para su nombre (certname). Ambos equipos deben resolver los nombres del otro. Si no tienes DNS interno, añade las dos entradas en /etc/hosts en los dos servidores:

sudo nano /etc/hosts
server_ip  puppet.example.com  puppet
agent_ip   web01.example.com   web01

Fija el hostname de cada máquina. En el servidor:

sudo hostnamectl set-hostname puppet.example.com

En el agente:

sudo hostnamectl set-hostname web01.example.com

Comprueba que cada uno resuelve al otro:

ping -c 1 puppet.example.com

Paso 2: Añadir el repositorio de OpenVox

Vox Pupuli publica un paquete que instala la definición del repositorio y su clave de firma. Hazlo en los dos servidores:

wget https://apt.voxpupuli.org/openvox8-release-ubuntu24.04.deb
sudo dpkg -i openvox8-release-ubuntu24.04.deb
sudo apt update

Comprueba que los paquetes están disponibles:

apt-cache policy openvox-server openvox-agent

La salida debe mostrar un candidato para cada paquete procedente de apt.voxpupuli.org. Si el nombre del archivo .deb cambia, consulta el listado en https://apt.voxpupuli.org/.

Paso 3: Instalar y arrancar Puppet Server

En el servidor, instala el paquete:

sudo apt install openvox-server

Por defecto Puppet Server reserva 2 GB de heap para la JVM. En un servidor de 4 GB está bien; si tienes menos memoria, ajústalo en /etc/default/puppetserver:

sudo nano /etc/default/puppetserver
JAVA_ARGS="-Xms1g -Xmx1g"

Indica al servidor su propio nombre en la configuración principal. Los binarios se instalan en /opt/puppetlabs/bin, que no está en el PATH de sudo, así que usa la ruta completa:

sudo /opt/puppetlabs/bin/puppet config set server puppet.example.com --section main
sudo /opt/puppetlabs/bin/puppet config set certname puppet.example.com --section main

Arranca el servicio. El primer arranque genera la autoridad de certificación (CA) y tarda uno o dos minutos:

sudo systemctl enable --now puppetserver
sudo systemctl status puppetserver
● puppetserver.service - puppetserver Service
     Loaded: loaded (/usr/lib/systemd/system/puppetserver.service; enabled; preset: enabled)
     Active: active (running) since ...

Permite el puerto 8140 solo desde el agente:

sudo ufw allow from agent_ip to any port 8140 proto tcp

Paso 4: Instalar el agente y solicitar el certificado

En el agente, instala el paquete:

sudo apt install openvox-agent

Apunta el agente al servidor:

sudo /opt/puppetlabs/bin/puppet config set server puppet.example.com --section main

Lanza una primera ejecución manual. El agente genera su par de claves, envía una petición de firma (CSR) al servidor y se queda esperando:

sudo /opt/puppetlabs/bin/puppet agent --test --waitforcert 60
Info: Creating a new SSL key for web01.example.com
Info: Creating a new SSL certificate request for web01.example.com
Info: Certificate Request fingerprint (SHA256): 8A:3F:...
Info: Certificate for web01.example.com has not been signed yet

Anota la huella (fingerprint) que aparece en la salida y deja el comando corriendo.

Paso 5: Firmar el certificado en el servidor

En el servidor, lista las peticiones pendientes:

sudo /opt/puppetlabs/bin/puppetserver ca list
Requested Certificates:
    web01.example.com   (SHA256)  8A:3F:...

Si la huella coincide con la que mostró el agente, fírmala:

sudo /opt/puppetlabs/bin/puppetserver ca sign --certname web01.example.com

En el agente, el comando que dejaste esperando descarga el certificado, pide su catálogo (todavía vacío) y termina con:

Notice: Applied catalog in 0.02 seconds

A partir de aquí el agente se conecta al servidor con TLS mutuo. No uses la autofirma de certificados salvo en redes completamente cerradas.

Paso 6: Crear un módulo

El código de Puppet vive en entornos. El entorno por defecto, production, está en /etc/puppetlabs/code/environments/production, con los módulos en modules/ y el punto de entrada en manifests/site.pp. En el servidor, crea un módulo miweb que instale Nginx y publique una página:

sudo mkdir -p /etc/puppetlabs/code/environments/production/modules/miweb/{manifests,templates}
sudo nano /etc/puppetlabs/code/environments/production/modules/miweb/manifests/init.pp
class miweb (
  String $site_name,
) {
  package { 'nginx':
    ensure => installed,
  }

  file { '/var/www/html/index.html':
    ensure  => file,
    owner   => 'root',
    group   => 'root',
    mode    => '0644',
    content => epp('miweb/index.html.epp', {
      'site_name' => $site_name,
    }),
    require => Package['nginx'],
  }

  service { 'nginx':
    ensure    => running,
    enable    => true,
    subscribe => File['/var/www/html/index.html'],
  }
}

La plantilla usa EPP, el formato de plantillas nativo de Puppet:

sudo nano /etc/puppetlabs/code/environments/production/modules/miweb/templates/index.html.epp
<%- | String $site_name | -%>
<h1><%= $site_name %></h1>
<p>Servidor: <%= $facts['networking']['fqdn'] %></p>

Valida la sintaxis antes de seguir:

sudo /opt/puppetlabs/bin/puppet parser validate /etc/puppetlabs/code/environments/production/modules/miweb/manifests/init.pp

Si no imprime nada, la sintaxis es correcta.

Paso 7: Asignar el módulo y sus datos con Hiera

La clase tiene un parámetro obligatorio, site_name. En lugar de escribir el valor en el código, sepáralo en Hiera, el sistema de datos jerárquico de Puppet. El entorno production ya incluye un hiera.yaml que busca primero en data/nodes/<certname>.yaml y después en data/common.yaml. Crea el dato común:

sudo mkdir -p /etc/puppetlabs/code/environments/production/data
sudo nano /etc/puppetlabs/code/environments/production/data/common.yaml
---
miweb::site_name: 'Mi web gestionada con Puppet'

Puppet asigna automáticamente la clave miweb::site_name al parámetro site_name de la clase miweb. Ahora declara qué nodos reciben la clase en site.pp:

sudo nano /etc/puppetlabs/code/environments/production/manifests/site.pp
node 'web01.example.com' {
  include miweb
}

node default {
}

Comprueba que Hiera resuelve el dato (al estar en common.yaml, vale para cualquier nodo):

sudo /opt/puppetlabs/bin/puppet lookup miweb::site_name
--- Mi web gestionada con Puppet

Paso 8: Aplicar la configuración en el agente

En el agente, primero haz una ejecución en seco con --noop, que muestra los cambios sin aplicarlos:

sudo /opt/puppetlabs/bin/puppet agent --test --noop

Si todo cuadra, aplícala:

sudo /opt/puppetlabs/bin/puppet agent --test
Notice: /Stage[main]/Miweb/Package[nginx]/ensure: created
Notice: /Stage[main]/Miweb/File[/var/www/html/index.html]/content: content changed ...
Notice: /Stage[main]/Miweb/Service[nginx]: Triggered 'refresh' from 1 event
Notice: Applied catalog in 12.41 seconds

Comprueba el resultado:

curl -s http://localhost
<h1>Mi web gestionada con Puppet</h1>
<p>Servidor: web01.example.com</p>

Una segunda ejecución de puppet agent --test no debe mostrar cambios.

Finalmente, habilita el servicio del agente para que aplique el catálogo automáticamente, por defecto cada 30 minutos:

sudo systemctl enable --now puppet

Solución de problemas

  • Connection refused - connect(2) for "puppet.example.com" port 8140: Puppet Server no ha terminado de arrancar o el firewall bloquea el puerto. Revisa sudo journalctl -u puppetserver y la regla de UFW.
  • Puppet Server no arranca por memoria: el heap de JAVA_ARGS es mayor que la RAM disponible. Redúcelo en /etc/default/puppetserver.
  • certificate verify failed o certificados que no coinciden tras reinstalar un agente: revoca y borra el certificado antiguo en el servidor con sudo /opt/puppetlabs/bin/puppetserver ca clean --certname web01.example.com, borra /etc/puppetlabs/puppet/ssl en el agente y repite el paso 4.
  • Could not find class miweb: el directorio del módulo no se llama igual que la clase o está fuera de environments/production/modules.
  • Class[Miweb]: expects a value for parameter 'site_name': Hiera no encuentra el dato. Revísalo con puppet lookup ... --explain.

Conclusión

Tienes un servidor Puppet con su propia CA, un agente autenticado por certificado y un módulo que separa código y datos con Hiera. Como siguientes pasos, puedes versionar el directorio de código con Git y desplegarlo con r10k, reutilizar módulos del Puppet Forge y añadir datos por nodo en data/nodes/.