Puppet (Segunda parte: Manifiesto y Módulos)

Antes de completar el ejercicio vamos a definir dos conceptos principales en Puppet: los módulos y manifiestos.

Manifiesto: Puppet es  un sistema declarativo, en donde las operaciones son aplicadas y no ejecutadas, estas operaciones son definidas en lenguage puppet dentro de archivos de texto denominados manifiestos. Por cada servidor puppet existe un manifiesto central, definido en /etc/puppet/manifests/site.pp, donde se configuran todos los sitios (clientes), mediante un nodo para cada uno de ellos.

Módulo: Un módulo es una colección de recursos, clases, archivos, definiciones y plantillas fácilmente redistribuible usado para configurar Dnsmasq o Apache o un sitio de reportes o una aplicación en Python determinada.

Cada uno de estos se escribe en lenguaje Puppet, un lenguaje declarativo bastante sencillo basado en Ruby en el cuál podemos definir toda la implementación para nuestros clientes. Les recomiendo ampliamente que se den una vuelta por la documentación oficial sobre este lenguaje.

Vamos a definir nuestro módulo para Dnsmasq, que incluirá como recursos al paquete, el archivo dnsmasq.conf y el servicio dnsmasq.

Los módulos se encuentran dentro de /etc/puppet/modules, teniendo la siguiente organización interna:

MODULE_PATH/
   downcased_module_name/
      files/
      manifests/
         init.pp
      lib/
         puppet/
             parser/
                functions
             provider/
             type/
         facter/
      templates/
      README

El archivo manifests/init.pp es donde crearemos nuestra clase principal para definir al módulo que utilizaremos. Primero retomaremos las necesidades expuestas en el post anterior:

  • Paquete Dnsmasq
  • Archivo de configuración de Dnsmasq, que provee también servicios de DHCP
  • Servicio Dnsmasq, el cuál será reiniciado al detectar cambios en la configuración.

Podemos visualizarlo en el siguiente esquema

Módulo Dnsmasq

Ahora simplemente transferiremos el esquema anterior al lenguaje puppet, haciendo uso de tres recursos: package, file y service. También utilizaremos un módulo para definición de los parámetros (oficialmente le denominan clase parametrizada), así podremos hacer transparentes los nombres del paquete y la ubicación del archivo de configuración especificando cuál es el valor adecuado para cada distribución, aunque en este caso en concreto Dnsmasq se encuentra con el mismo nombre por lo menos en Debian, Fedora y OpenSuse, además, claro, de hacerlo reusable, extensible, etc, etc.

Como se mencionó anteriormente, el lenguaje puppet es bastante sencillo, y si alguna vez en tu vida has leído sobre programación orientada a objetos esto es pancito.

Primero definimos el módulo que crearemos a continuación en …/modules/dnsmasq/Modulefile, donde incluimos el nombre y la versión.

name 'dnsmasq'
version '0.0.1'

El nombre de nuestra clase es dnsmasq, así que nuestro módulo para parámetros se define a continuación en …/dnsmasq/manifests/params.pp

class dnsmasq::params {

  #Definiendo el nombre del paquete
  $packagename = $operatingsystem ? {
    default => "dnsmasq",
  }
  #Definiendo la ruta del archivo de configuración
  $config_file_path = $operatingsystem ? {
    default => "/etc/dnsmasq.conf",
  }
}

En el código anterior asignamos nuestros parámetros para el nombre del paquete: dnsmasq y para la ruta del archivo de configuración: /etc/dnsmasq.conf. Como pueden ver, la asignación comienza con una condición que toma el valor en $operatingsystem, una variable construida por puppet mismo para indicar qué sistema operativo está corriendo el cliente, por supuesto, es un switch y ahora, ya que nuestro paquete tiene el mismo nombre y el archivo de configuración la misma ruta, estamos usando el caso por default.

Para efectos demostrativos supongamos que Fedora decide que ahora el paquete, en su distribución, se llamará dnsmsq y que el archivo de configuración estará en /etc/dnsmsq/dnsmsq.conf, procedemos a ajustar nuestros parametros

 

class dnsmasq::params {
  #Definiendo el nombre del paquete
  $packagename = $operatingsystem ? {
    fedora => "dnsmsq",
    default => "dnsmasq",
  }
  #Definiendo la ruta del archivo de configuración
  $config_file_path = $operatingsystem ? {
    fedora => "/etc/dnsmsq/dnsmsq.conf"
    default => "/etc/dnsmasq.conf",
  }
}

Definiendo así que sólo para Fedora tenemos un caso especial.

Ahora sí construimos la clase principal en …/dnsmasq/manifests/init.pp

 

class dnsmasq {
      require dnsmasq::params
      package { dnsmasq:
      	      name => "${dnsmasq::params::packagename}",
	      ensure => present,
      }

      file { "dnsmasq.conf":
       path => "${dnsmasq::params::config_file_path}",
       mode => "644",
       owner => "root",
       group => "root",
       ensure => present,
       content => template("dnsmasq/dnsmasq.conf.erb"),
       }

       service { "dnsmasq" :
               enable => true,
               ensure => running,
               require => File["${dnsmasq::params::config_file_path}"],
	       subscribe => File["${dnsmasq::params::config_file_path}"],
               name => "${dnsmasq::params::packagename}"

          }

}

En el código anterior definimos los recursos. Primero el recurso package, de nombre dnsmasq (sí, en serio) con los siguientes parámetros:

  • name: es el nombre del paquete obtenido desde nuestro módulo de parámetros dependiendo de la distribución en la que corre el cliente.
  • ensure: se le pide a puppet que se asegure que el paquete está presente, también puede ser absent por si queremos desinstalarlo.

El recurso file de nombre dnsmasq.conf cuenta con los siguientes parámetros:

  • path: la ruta donde el archivo será creado. Este valor también es obtenido de nuestros parámetros.
  • mode: los permisos del archivo, asignados mediante chmod.
  • owner: el usuario propietario del archivo.
  • group: el grupo propietario del archivo.
  • ensure: también en este caso se le pide a puppet que se asegure que el archivo está presente.
  • content: es el contenido del archivo definido mediante una plantilla (template), de la cuál hablaremos más adelante.

El recurso service de nombre (sí, adivinaron) dnsmasq define lo siguiente:

  • enable: define si el servicio debe ser iniciado en el arranque o no.
  • ensure: define si el servicio debe ser ejecutado o no, en este caso lo queremos siempre corriendo (running)
  • require: define una dependencia del servicio, en este caso se trata del archivo de configuración de dnsmasq. Un requerimiento podría ser también un paquete.
  • subscribe: indica que el servicio está suscrito a los cambios realizados en el archivo de configuración, y por tanto debe ser reiniciado cada vez que dichos cambios existan. De igual forma podrá también estar suscrito a los cambios en la caché de paquetes.
  • name: el nombre del servicio en /etc/init.d .

Como se mencionó anteriormente, el contenido del archivo de configuración está definido mediante una plantilla. Todas las plantillas de nuestro módulo deben ser almacenadas con extensión .erb, para que puedan ser evaluadas, dentro de la carpeta templates o en /var/puppet/templates si las queremos accesibles para todos. A continuación se muestra una plantilla sencilla para este archivo de configuración (…/dnsmasq/templates/dnsmasq.conf.erb)

bogus-priv
address=/miservidor.dominio.com/10.1.0.1
address=/miotroservidor.dominio.com/10.1.0.2
no-hosts
dhcp-range=b-phot.org,<%= dhcp_prefix %>.16.1,<%= dhcp_prefix %>.16.254,12h
dhcp-option=option:router,<%= dhcp_prefix %>.0.1
dhcp-host= aa:bb:cc:dd:ee:ff,usuario1,<%= dhcp_prefix %>.1.1
dhcp-host= bb:cc:dd:ee:ff:aa,usuario2,<%= dhcp_prefix %>.1.9
dhcp-host= cc:dd:ee:ff:aa:bb,usuario3,<%= dhcp_prefix %>.1.17

Este archivo introduce un nuevo concepto: variables de plantilla. En este caso tenemos a la variable dhcp_prefix, que será obtenida desde la configuración del nodo donde dnsmasq sea requerido, aquí es una simple sustitución, pero en las plantillas también podemos introducir iteraciones y condicionales.

Nota: Lean la documentación de dnsmasq si quieren conocer más opciones para el archivo de configuración.

Por fin llegamos a definir nuestros nodos clientes, si recuerdan todavía, estos se encuentran en /etc/puppet/manifests/site.pp. Cada uno de los nodos es definido por la palabra reservada node seguida por el FQDN. Para nuestro ejercicio los clientes son servidores DNS y DHCP detrás de diferentes redes, así que para cada uno de ellos tenemos un prefijo para direcciones IP diferente.

node 'cliente0.dominio.com' {
     $dhcp_prefix = "10.1"
     include dnsmasq
}

node 'cliente1.dominio.com' {
     $dhcp_prefix = "10.3"
     include dnsmasq
}

node 'cliente2.dominio.com' {
     $dhcp_prefix = "10.2"
     include dnsmasq
}

Finalmente tendremos algo funcionando…