Documentos de Académico
Documentos de Profesional
Documentos de Cultura
INTEGRACIÓN DE VAGRANT
CON ANSIBLE
Página | 1
Contents
Objetivos y Justificación de este proyecto ............................................................................. 3
Contexto ................................................................................................................................. 3
Vagrant ................................................................................................................................... 4
Ansible .................................................................................................................................... 4
SSH ........................................................................................................................................ 4
Recordatorio sobre las llaves pública-privada....................................................................... 4
Dos posibles escenarios ........................................................................................................ 5
Herramientas que se han utilizado para la realización de este proyecto: ............................ 6
Nuestro primer servidor con Vagrant utilizando Windows .................................................... 6
Comprobación de las máquinas Virtuales creadas con Vagrant ........................................ 15
Servicio SMB gestionado por Vagrant ................................................................................. 17
Reconfigurar nuestras máquinas Virtuales con Vagrant ..................................................... 20
Emplear emuladores de terminales para agilizar el trabajo ................................................ 21
Destruir una máquina virtual con Vagrant ........................................................................... 21
Aprovisionar máquinas virtuales creadas con Vagrant con Shell Scripts ........................... 22
Vagrant junto a otras tecnologías ........................................................................................ 26
SSH en Ansible .................................................................................................................... 26
Generar un par de llaves pública-privada en nuestro controlador. ..................................... 26
Configurar el servidor SSH .................................................................................................. 28
Exportar nuestra llave pública.............................................................................................. 28
Desactivar las conexiones SSH por contraseña ................................................................. 29
Ansible y el papel de los inventarios.................................................................................... 30
Utilizando comandos ad-hoc de Ansible ............................................................................. 32
Volver a levantar nuestras máquinas virtuales con Vagrant ............................................... 34
Automatización de Tareas con Ansible utilizando Playbooks ............................................. 35
Playbook alternativo para crear usuarios y grupos en diferentes equipos ......................... 48
Configuración de ~/.ssh/config y /etc/hosts ......................................................................... 50
Servidor NFS y Cliente......................................................................................................... 52
Actualizar las Host Keys de cada nodo ............................................................................... 54
Servidor NTP con Ansible .................................................................................................... 60
Conclusiones ........................................................................................................................ 64
Página | 2
Objetivos y Justificación de este proyecto
Con este proyecto pretendo dar a conocer las ventajas que ofrece Ansible para la
automatización de tareas, como puede ser el aprovisionamiento y configuración de algunos
servicios para servidores en un entorno virtualizado gestionado por Vagrant.
Escogí este tema por su esencia netamente administrativa, que es la tarea que más hemos
realizado a lo largo del módulo.
A medida que iba investigando sobre Ansible, nacido en el año 2012, había otra palabra que,
en ocasiones, se mencionaba para levantar los nodos que iban a ser gestionados después
por él. Era Vagrant. En sí mismo, Vagrant da para la creación de un proyecto en solitario,
debido a todo lo que nos puede ofrecer combinándolo con otras tecnologías como Ansible
(este es el caso), Shell Scripts, Chef, Puppet o Docker. Y básicamente eso es lo que he
hecho, un proyecto que demuestra lo bien que se pueden compenetrar estas dos tecnologías.
Mi intención con este proyecto es explicaros el funcionamiento de estas dos tecnologías y
mostraros el código que he tenido que realizar para el levantamiento de una infraestructura
informática con Vagrant y su aprovisionamiento y gestión de tareas de forma automatizada
utilizando Ansible. Utilizar Ansible y Vagrant se resume en crear scripts y en eso se apoya
este proyecto.
Cabe mencionar también, que ambas tecnologías utilizan SSH para su funcionamiento, por
lo que me ha tocado profundizar conocimientos sobre este protocolo.
Durante el proceso de creación de este proyecto me he quedado estancado en muchas
partes, recibiendo diversos errores en la terminal, pero que con el tiempo los he ido
solventando y mi intención es reproducir esos mismos errores a lo largo de este documento
y exponer la solución.
Contexto
En el pasado los administradores de sistemas solían conectarse a los servidores que
gestionaban a través una conexión segura empleando para ello SSH, pero de uno en uno. A
medida que Internet creció, la solicitud de servicios a través de la World Wide Web también.
Los pocos servidores que gestionaban empezaron a multiplicarse. ¿Qué pasaba si querías
replicar la infraestructura de servidores que ya tenías montada, con todo el software y la
configuración editada a tus necesidades? Reproducir cada nodo tomaría demasiado tiempo
sin mencionar el coste.
Página | 3
Vagrant
Vagrant es una tecnología que puede levantar máquinas virtuales a través de un fichero
escrito en lenguaje Ruby. Sin embargo, no hace falta ningún conocimiento previo de Ruby
para utilizarlo. Como he dicho en líneas anteriores Vagrant se puede combinar con otras
tecnologías para abastecer a las máquinas creadas con el software y los servicios de Red e
Internet que queramos, entre otras cosas. Sin embargo, Vagrant necesita un hipervisor para
trabajar. De forma nativa emplea VirtualBox, al ser un software de código abierto y libre, pero
puede emplear otros hipervisores como KVM (GNU-LINUX), VMware o Hyper-V de Microsoft.
Ansible
Ansible es una herramienta para la automatización de tareas en nodos, ya sean servidores o
dispositivos de red. Empleando Ansible puedes gestionar diferentes nodos a través del
protocolo SSH y ejecutar una tarea o lista de tareas en los nodos especificados (nodos que
crearemos con Vagrant). Así pues, desde un solo equipo podemos gestionar diez, veinte o
doscientos equipos a los que nos podamos conectar a través de SSH.
Ansible puede convertir un script complejo en un playbook fácil de leer y codificar escrito en
YAML. Y ese es el motivo por el que Ansible se ha convertido en una de las herramientas
más utilizadas en el mundo de los servidores. Es fácil de aprender a diferencia de otras
tecnologías que se crearon para su mismo cometido. No necesitas tener instalado ningún tipo
de software en los nodos que van a ser controlados, pues Python y OpenSSH Server (los
únicos requisitos) ya vienen instalados por defecto en la mayoría de distribuciones
GNU/Linux.
En ansible el equipo que gestiona al resto de equipos se le conoce como controlador y a los
equipos controlados se le conoce como nodos.
SSH
A través de un fichero llamado ‘Vagrantfile’ crearemos nuestras máquinas virtuales con
Vagrant. Vagrant creará en cada una de ellas un usuario con el nombre Vagrant y contraseña
Vagrant. Dicho usuario está configurado para que pueda utilizar el comando sudo sin
contraseña con privilegios de superusuario. Sin embargo, no necesitaremos utilizar la
aplicación de VirtualBox para acceder a nuestras nuevas máquinas virtuales dado que desde
el mismo terminal podremos conectarnos a ellas a través de una sesión de SSH.
Por defecto, Vagrant crea un par de llaves/claves pública-privada para facilitarnos la conexión
SSH con llave pública. Sin embargo, dado que utilizaremos Ansible, y este también utiliza
SSH para realizar tareas en nuestros equipos creados (nodos), lo mejor que podemos hacer
-y por mejorar la seguridad- es crear nuestro propio par de llaves pública-privada.
Página | 4
La clave privada se quedará en mi ordenador (/home/luis/.ssh/id_rsa) y la clave pública
(/home/luis/.ssh/id_rsa.pub) la podré enviar a cualquier ordenador/servidor al que me quiera
conectar (tendremos que añadir nuestra clave pública a este fichero para que el servidor nos
pueda autenticar /home/maquinaVirtual/.ssh/authorized_keys).
1
What is the Windows Subsystem for Linux?
2
Are WSL 2 and VirtualBox currently mutually exclusive?
Página | 5
Escenario uno y dos respectivamente.
- Windows 10 y Powershell
- Manjaro y la terminal Bash en entornos Linux
- Cualquier emulador de terminal que pueda dividir la terminal en varias partes y así
facilitarnos el trabajo: Tilix en Linux y Cmder en Windows.
- Visual Studio Code con complementos para YAML y VIM como IDEs
- Virtualbox
- Vagrant
- Ansible
Página | 6
Empecemos por crear un directorio donde se va alojar todo nuestro proyecto. En este
directorio crearemos nuestro fichero de Vagrant para configurar nuestras máquinas
virtuales.
Comprobemos que tenemos instalado Vagrant en nuestro equipo con el siguiente comando:
Vagrant nos avisa que ya hemos creado nuestro fichero y que ya está listo para levantar la
máquina. Cosa que es mentira. Vagrant solo ha creado un fichero básico que hay que
configurarlo más a fondo. Para abrir el fichero que Vagrant acaba de crear y si tienes instalado
el Visual Studio Code escribe el siguiente comando:
Página | 7
Esta es una plantilla bien documentada por Vagrant con la mayoría de opciones que
podemos configurar. Y esta es una tabla con los parámetros que quiero que Vagrant cree
las máquinas virtuales.
Máquinas Virtuales
IP 192.168.50.10 192.168.50.20
CPU asignado 1 1
Página | 8
Este sería el script sin los comentarios el cual vamos a personalizar a nuestras
necesidades.
Vagrant.configure("2") do |config|
config.vm.box = "base"
end
Vagrant.configure("2") do |config|
end
Dado que vamos a configurar dos máquinas virtuales las vamos a definir del siguiente
modo:
Vagrant.configure("2") do |config|
config.vm.define "controlador" do |cont|
end
config.vm.define "nodo" do |nodo|
end
end
Vagrant.configure("2") do |config|
# configuración general
config.vm.box = "debian/buster64"
# configuración controlador
config.vm.define "controlador" do |cont|
end
# configuración nodo
config.vm.define "nodo" do |nodo|
end
end
Página | 9
config.vm.box = “debian/buster” hace referencia a la Box que vamos a utilizar para
nuestras dos máquinas. Un box es una máquina virtual utilizada por Vagrant muy optimizada
como punto de partida para customizar sobre ella nuestra máquina virtual. Con esto me refiero
a que, si deseamos crear un Debian, debemos utilizar un box con la versión del Debian que
queramos levantar. Los nombres de estos boxes las podemos conseguir desde el catálogo
de Vagrant. 3 En dicho catálogo buscaremos el nombre del sistema operativo o distribución
de GNU/Linux que queramos utilizar. Ojo, no confundamos un box con una ISO de un sistema
operativo (que es lo que utilizamos normalmente cuando queremos levantar una máquina en
virtualbox).
Vamos a configurar otros parámetros en nuestro Vagrantfile como por ejemplo el hostname,
el tipo de red y la IP que utilizarán nuestras máquinas virtuales.
Vagrant.configure("2") do |config|
# configuración general
config.vm.box = "debian/buster64"
# configuración controlador
config.vm.define "controlador" do |cont|
cont.vm.hostname = "controlador"
cont.vm.network "private_network", ip: "192.168.50.10",
virtualbox__intnet: "asir"
3
Vagrant Cloud
Página | 10
end
# configuración nodo
config.vm.define "nodo" do |nodo|
nodo.vm.hostname = "controlador"
nodo.vm.network "private_network", ip: "192.168.50.20",
virtualbox__intnet: "asir"
end
end
Se podría configurar el tipo de red como pública con “public_network” y así nuestras
máquinas virtuales serían un equipo más en nuestro segmento de red. También podríamos
dejar que un servidor DHCP sea el que asigne automáticamente las IPs a nuestras máquinas
virtuales. Para ello deberíamos reemplazar la línea donde configuramos la red a por esta:
Vagrant.configure("2") do |config|
# configuración general
config.vm.box = "debian/buster64"
# configuración controlador
config.vm.define "controlador" do |cont|
cont.vm.hostname = "controlador"
cont.vm.network "private_network", ip: "192.168.50.10",
virtualbox__intnet: "asir"
cont.vm.network "private_network", type: "dhcp"
cont.vm.synced_folder ".", "/home/vagrant/proyecto", type: "smb"
end
# configuración nodo
config.vm.define "nodo" do |nodo|
nodo.vm.hostname = "controlador"
nodo.vm.network "private_network", ip: "192.168.50.20",
virtualbox__intnet: "asir"
end
end
Página | 11
Al momento de levantar nuestras máquinas Vagrant solicitará permisos a Windows para
instalar un servicio SMB pidiéndonos usuario y contraseña de administrador.
Nos queda por configurar opciones como la memoria, la CPU y el nombre que tendrán las
máquinas en Virtualbox.
Vagrant.configure("2") do |config|
# configuración general
config.vm.box = "debian/buster64"
# configuración controlador
config.vm.define "controlador" do |cont|
cont.vm.hostname = "controlador"
cont.vm.network "private_network", ip: "192.168.50.10",
virtualbox__intnet: "asir"
cont.vm.synced_folder ".", "/home/vagrant/proyecto", type: "smb"
cont.vm.provider "virtualbox" do |vb|
vb.name = "controlador(50.10)"
vb.cpus = 1
vb.memory = 512
end
end
# configuración nodo
config.vm.define "nodo" do |nodo|
nodo.vm.hostname = "controlador"
nodo.vm.network "private_network", ip: "192.168.50.20",
virtualbox__intnet: "asir"
nodo.vm.provider "virtualbox" do |vb|
vb.name = "nodo(50.20)"
vb.cpus = 1
vb.memory = 512
end
end
end
Bien. Ya tenemos nuestro Vagrantfile con sintaxis en Ruby que va a levantar el controlador y
el nodo. Para comprobar que no hemos cometido ningún fallo de sintaxis guardamos el fichero
y ejecutamos el siguiente comando en la terminal:
Página | 12
Para levantar las dos máquinas ejecutamos el siguiente comando:
Vagrant empezará a descargar la Box si es la primera vez que la utilizamos en nuestro equipo.
Como dije anteriormente. Vagrant montará un servicio de compartición de ficheros SMB entre
la máquina real (Windows) y el controlador. Para ello antes nos solicitará permisos de
administrador.
Vagrant empezará a configurar las máquinas con los parámetros que establecimos en nuestro
fichero Vagrantfile.
Página | 13
Vagrant ha creado exitosamente nuestras dos máquinas virtuales. Para comprobarlo
ejecutaremos el siguiente comando:
Página | 14
Comprobación de las máquinas Virtuales creadas con Vagrant
Podemos comprobar que las especificaciones de las máquinas virtuales son las que
establecimos desde nuestro Vagrantfile desde la aplicación VirtualBox
Página | 15
Podemos cerrar VirtualBox porque no lo utilizaremos más ya que podemos ejecutar las
conexiones hacia nuestras máquinas virtuales a través de SSH. Para acceder a nuestra
máquina controladora ejecutaremos el siguiente comando:
Página | 16
Servicio SMB gestionado por Vagrant
Comprobemos que nuestro sistema de compartición de ficheros funciona correctamente
creando algunos documentos.
Al parecer todo funciona correctamente. Con esta herramienta (SMB) podríamos estar
trabajando perfectamente a través de Windows y todo el trabajo quedaría también reflejado
en nuestro Debian. Vagrant puede utilizar también Rsync y NFS para entornos Linux. Es bien
conocido que utilizar el sistema de compartición de carpetas y ficheros por defecto de
VirtualBox puede dar muchos dolores de cabeza porque solo funciona cuando le apetece
(esto lo he experimentado en clase muchas veces) 4.
Comprobación de la red:
4
NFS, rsync, and shared folder performance in Vagrant VMs
Página | 17
vagrant@controlador:~/proyecto$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group
default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP
group default qlen 1000
link/ether 08:00:27:8d:c0:4d brd ff:ff:ff:ff:ff:ff
inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic eth0
valid_lft 84921sec preferred_lft 84921sec
inet6 fe80::a00:27ff:fe8d:c04d/64 scope link
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP
group default qlen 1000
link/ether 08:00:27:ec:75:5c brd ff:ff:ff:ff:ff:ff
inet 192.168.50.10/24 brd 192.168.50.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:feec:755c/64 scope link
valid_lft forever preferred_lft forever
La primera es la interfaz loopback. La segunda es la tarjeta de red con la que nos podemos
comunicar con nuestro equipo Windows y salir a internet (configurada en NAT). La tercera
tarjeta de red virtual es la que utilizamos para generar la red privada virtual (configurada en
Red Interna). Cuya IP, efectivamente, es: 192.168.50.10
La puerta de enlace
Página | 18
Vagrant utiliza SSH para que la máquina anfitriona se pueda comunicar desde la terminal con
los diferentes hosts que vaya creando. Podemos comprobar que nuestra llave pública (que
creó Vagrant) se encuentra en el fichero ‘authorized_keys’ del equipo remoto al que nos
queremos conectar.
Vagrant crea la cuenta ‘vagrant’ con privilegios de root a través de sudo. Vagrant ha
configurado sudoers para que el usuario realice operaciones con sudo sin tener que
especificar ninguna contraseña. Comprobación:
vagrant@controlador:~$ groups
vagrant cdrom floppy sudo audio dip video plugdev netdev
Página | 19
Acceder al nodo a través de SSH (sin utilizar comandos de Vagrant)
Hostname…
Página | 20
Guardamos nuestro fichero y ejecutamos el siguiente comando para que Vagrant reconozca
la nueva configuración.
5
Tilix
6
Cmder
Página | 21
También se puede especificar como parámetro el id asignado por Vagrant a la máquina
virtual. También se le puede pasar más de un parámetro al comando:
Para que Vagrant no nos pida confirmación al momento de eliminar las máquinas virtuales se
debe añadir la opción -f
#!/bin/bash
sudo apt update
sudo apt install vim git python3 ansible -y
Vagrant.configure("2") do |config|
# configuración general
config.vm.box = "debian/buster64"
# configuración controlador
config.vm.define "controlador" do |cont|
cont.vm.hostname = "controlador"
cont.vm.network "private_network", ip: "192.168.50.10",
virtualbox__intnet: "asir"
cont.vm.synced_folder ".", "/home/vagrant/proyecto", type: "smb"
cont.vm.provider "virtualbox" do |vb|
vb.name = "controlador(50.10)"
vb.cpus = 1
vb.memory = 512
end
Página | 22
cont.vm.provision "shell", path: "controlador.sh"
end
# configuración nodo
config.vm.define "nodo" do |nodo|
nodo.vm.hostname = "nodo"
nodo.vm.network "private_network", ip: "192.168.50.20",
virtualbox__intnet: "asir"
nodo.vm.provider "virtualbox" do |vb|
vb.name = "nodo(50.20)"
vb.cpus = 1
vb.memory = 512
end
end
end
Podemos aprovechar para crear otro shell script que actualice los repositorios del nodo y que
a su vez instale Python, que es un requisito para que Ansible pueda controlarlo (nodo.sh)
#!/bin/bash
sudo apt update
sudo apt install vim git python3 -y
Vagrant.configure("2") do |config|
# configuración general
config.vm.box = "debian/buster64"
# configuración controlador
config.vm.define "controlador" do |cont|
cont.vm.hostname = "controlador"
cont.vm.network "private_network", ip: "192.168.50.10",
virtualbox__intnet: "asir"
cont.vm.synced_folder ".", "/home/vagrant/proyecto", type: "smb"
cont.vm.provider "virtualbox" do |vb|
vb.name = "controlador(50.10)"
vb.cpus = 1
vb.memory = 512
end
cont.vm.provision "shell", path: "controlador.sh"
end
# configuración nodo
config.vm.define "nodo" do |nodo|
nodo.vm.hostname = "nodo"
Página | 23
nodo.vm.network "private_network", ip: "192.168.50.20",
virtualbox__intnet: "asir"
nodo.vm.provider "virtualbox" do |vb|
vb.name = "nodo(50.20)"
vb.cpus = 1
vb.memory = 512
end
nodo.vm.provision "shell", path: "nodo.sh"
end
end
Vagrant empezará a actualizar los repositorios de las dos máquinas virtuales e instalará
Ansible en el controlador (junto a git y vim). Además, debe instalar la versión 3 de Python en
el nodo. Vamos a comprobarlo.
Página | 24
vagrant@controlador:~$ ansible --version
ansible 2.7.7
config file = /etc/ansible/ansible.cfg
configured module search path = ['/home/vagrant/.ansible/plugins/modules',
'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
executable location = /usr/bin/ansible
python version = 3.7.3 (default, Dec 20 2019, 18:57:59) [GCC 8.3.0]
vagrant@controlador:~$
Podríamos actualizar los paquetes con ‘sudo apt upgrade’ pero no nos hará falta para lo que
vamos a hacer. Salgamos de la sesión y comprobemos que todo esté bien en el nodo.
Página | 25
Vagrant junto a otras tecnologías
Vale hasta aquí ha sido comprobada la esencia de Vagrant. ¿De qué me sirve virtualizar de
forma automática dos, diez, veinte máquinas virtuales si no podré después automatizar el
aprovisionamiento? Pues para eso combinamos Vagrant con otras tecnologías. Si ya sabes
programar Shell scripts te sentirás muy cómodo escribiendo playbooks de Ansible. Con
Ansible puedes escribir scripts (playbooks) de forma sencilla sin tener que escribir Shell
scripts complejos que intenten satisfacer la misma tarea. Una vez aprovisionado todo nuestro
entorno podemos dejar del lado Vagrant para poder utilizar toda la fuerza que tiene Ansible.
¿Quieres configurar firewalls en 20 nodos con un solo script? ¿Quieres instalar servicios como
un stack completo de LAMP de forma sencilla en los nodos que haga falta? No hace falta que
te conectes manualmente a tus servidores uno a uno para actualizar al último parche de
seguridad. Todo eso lo puedes hacer desde un simple playbook o utilizando comandos ad-
hoc de Ansible.
SSH en Ansible
Ansible también utiliza SSH para realizar todas las tareas que queramos realizar. Por lo que
debemos generar nuestro par de llaves pública/privada y exportar nuestra llave pública a los
nodos que queramos controlar. Se puede utilizar contraseñas para una conexión SSH, pero
no es recomendable por temas de seguridad.7
7
5 Linux SSH Security Best Practices To Secure Your Systems
Página | 26
La passphrase la dejaremos en blanco. RSA es el tipo de algoritmo que vamos a utilizar (se
podría utilizar otros). Se puede especificar el número de bits que utilizará el algoritmo para la
encriptación, pero con los parámetros del comando anterior son más que suficientes para
nuestra prueba. Nuestra llave privada generada se ha guardado en la ruta ~/.ssh/id_rsa.pub.
Es esta llave la que debemos exportar a nuestro nodo. Para ello utilizaremos el siguiente
comando:
Sin embargo, al intentar exportar nuestra llave pública nos topamos con un error en la salida
del comando.
Página | 27
Configurar el servidor SSH
Permiso denegado. Para solucionar esto debemos configurar antes el fichero de
configuración del servidor SSH del nodo. Cerremos la conexión y accedamos al nodo para
poder configurar el SSH.
Página | 28
Ingresemos la contraseña del usuario con el que deseamos acceder a través de ssh (vagrant).
Con todo esto preparado tenemos casi nuestro entorno preparado para poder realizar algunas
pruebas con Ansible. Si quisiéramos utilizar Ansible contra nuestro propio controlador
tendríamos que importar nuestra llave pública creada a nuestro fichero ‘authorized_keys’. Al
ejecutar el comando ‘ssh-copy-id 192.168.50.10’ nos toparemos otra vez con el error que
experimentamos con el nodo. Para solventar esto tendríamos que editar nuestro fichero de
configuración del servidor SSH, reiniciar el servicio SSH y volver a ejecutar el comando.
Página | 29
Ansible y el papel de los inventarios
Bien. Ya podemos empezar a utilizar Ansible. La forma con la que le decimos de la existencia
de nuestros nodos a Ansible es a través de un inventario (inventory). Este inventario es un
fichero sin formato en el que anotaremos los nodos (por dominio o IP o ambos). Podemos
crear grupos de nodos e inclusive almacenar variables relacionadas con los nodos o grupos.
Aquí pongo un ejemplo de un inventario.
[controlador]
192.168.50.10
[basesDeDatos]
192.168.50.20
192.168.50.30
[servidoresWeb]
192.168.50.40
192.168.50.50
[servidoresCorreo]
192.168.50.60
192.168.50.70
[debian:children]
controlador
basesDeDatos
basesDeDatos
[centos:children]
servidoresWeb
servidoresCorreo
[debian:vars]
ansible_user=vagrant
ansible_port=2222 # puerto personalizado para realizar la conexión SSH
Los nodos se agrupan a través de las cabeceras entre corchetes. Si quisiéramos añadir
variables a un nodo o a un grupo en específico habría que añadir :vars a la cabecera. Para
agrupar grupos utilizamos :children.
/etc/ansible/hosts
Sin embargo, podemos crear nuestro propio inventario y decirle explícitamente a Ansible que
lo utilice en vez del inventario general.
Vamos a crear nuestro propio inventario. Le podemos poner el nombre que queramos, pero
yo le llamaré hosts. Lo guardaré en el mismo directorio donde se encuentra nuestro
Vagrantfile.
Página | 30
La variable ‘ansible_user:vagrant’ especifica el usuario con el que queremos hacer la
conexión SSH.
Bien ahora necesitamos decirle a Ansible que utilice este inventario. Para ello utilizamos el
fichero de configuración que se encuentra en la ruta: /etc/ansible/ansible.cfg
Sin embargo, dado que estamos en un entorno de pruebas, dejemos ese fichero en paz y
creemos nuestro propio fichero de configuración. Este debe estar en el directorio de nuestro
proyecto con el nombre ansible.cfg
Página | 31
Utilizando comandos ad-hoc de Ansible
Si tenemos todo bien configurado hasta el momento (conexiones ssh y los ficheros hosts y
ansible.cfg) ya podemos utilizar comandos ad-hocs de ansible. Para comprobar la conexión
con nuestro nodo ejecutaremos el siguiente comando (ping):
Podemos utilizar el parámetro all para seleccionar todos los nodos que se encuentren en el
inventario (incluyendo el controlador)
Con -m le estamos diciendo a Ansible que vamos a utilizar un módulo y ping al nombre del
módulo.
Estos son ejemplos de algunos comandos ad-hocs:
Ver la memoria de todos los nodos (incluido en controlador)
Página | 32
Aquí estamos utilizando el módulo shell, que es eso, ejecutar comandos shell con Ansible.
con la opción -a especificamos los parámetros del módulo shell (free -h)
Como vemos no hace falta especificar -m shell. Ansible ya sabe que queremos ejecutar
comandos shell.
Con la opción -b (become) le decimos a Ansible que el siguiente comando lo ejecute con
privilegios de superusuario (sudo) y empleamos el módulo apt (gestor de paquetes de
Debian). Luego le pasamos los parámetros con -a. En ‘name’ especificamos el paquete que
queremos configurar (htop) y en ‘state’ especificamos qué queremos hacer con el paquete
(present para que se instale). Si htop estuviese instalado ya, podríamos cambiar el estado a
por ‘absent’ para desinstalarlo de ambos nodos. En la salida del comando veremos que el
programa ha sido instalado en ambos nodos. Comprobémoslo:
Página | 33
Vista desde el nodo (utilizando Cmder)
Vamos viendo la esencia de Ansible. Podemos controlar a través de SSH diferentes nodos
según como hayamos configurado nuestro inventario. Esto nos elimina el trabajo engorroso
de conectarnos nodo por nodo para realizar nuestras operaciones en ellos. En nuestro caso
solo tenemos dos nodos configurados (controlador y nodo) pero esto podría no ser así y que
algún día tengamos que administrar una docena de servidores. Ansible reduce este trabajo
permitiéndonos administrarlos desde un solo equipo.
Página | 34
El estado de nuestras máquinas es de ‘running’. Cualquiera pudiera decir de que están
activas. Pero si intentamos conectarnos a través de SSH…
Debemos volver a utilizar el comando ‘vagrant up’ para que nuestras máquinas estén
operativas otra vez.
Hemos avanzado ya cierto trecho de este proyecto. Ahora quedaría por ver la utilidad de los
playbooks (así se llaman los scripts en Ansible). Un playbook es un fichero con formato .yml
o .yaml escrito en lenguaje YAML. En dicho playbook se escriben ‘tasks’ o ‘plays’ que
básicamente son operaciones que se realizarán en nuestros nodos.
Página | 35
En este ejercicio veremos la automatización de creación de usuarios y grupos en dos nodos
distintos de dos empresas ficticias. Aparte de esto vamos a ver cómo podríamos subir
ficheros/directorios con la documentación de la empresa en cada carpeta personal de cada
usuario con los permisos correspondientes. Aquí un esquema con la información de los
grupos y usuarios de cada nodo:
Creemos un directorio para nuestro nuevo proyecto, nos posicionamos en su ruta. Ahora
creemos nuestro nuevo Vagrantfile:
Vagrant.configure("2") do |config|
config.vm.box = "debian/buster64"
config.vm.synced_folder '.', '/vagrant', disabled: true
config.ssh.insert_key = false
config.ssh.forward_agent = true
config.ssh.private_key_path = ["~/.vagrant.d/insecure_private_key",
"~/.ssh/id_rsa"]
config.vm.provision "file", source: "~/.ssh/id_rsa.pub", destination:
"~/.ssh/authorized_keys"
Página | 36
boxes = [
{ :name => "nodo1", :ip => "192.168.52.10" },
{ :name => "nodo2", :ip => "192.168.52.20" },
]
boxes.each do |opts|
config.vm.define opts[:name] do |config|
config.vm.hostname = opts[:name]
config.vm.network :private_network, ip: opts[:ip]
if opts[:name] == "nodo2"
config.vm.provision "ansible" do |ansible|
ansible.playbook = "playbooks/grupos_usuarios.yml"
ansible.inventory_path = "inventory"
ansible.limit = "nodos"
end
end
end
end
end
config.vm.box = "debian/buster64"
Con esta línea le decimos a Ansible que nuestros nodos utilizarán un Debian versión Buster.
Recordemos que los boxes las encontramos en el catálogo de Vagrant Cloud. 8
Esta línea nos permite exportar un fichero o directorio desde la máquina local hacia nuestros
nodos. En este caso exportando mi llave pública a cada uno de nuestros Debian.
boxes = [
{ :name => "nodo1", :ip => "192.168.52.10" },
{ :name => "nodo2", :ip => "192.168.52.20" },
]
8
Hashicorp Vagrant Cloud
Página | 37
A través de este array podemos crear los nodos que queramos y asignarle los atributos
correspondientes a cada nodo. Es una forma más limpia de crear nodos comparado con
nuestro primer ejercicio.
if opts[:name] == "nodo2"
config.vm.provision "ansible" do |ansible|
ansible.playbook = "playbooks/grupos_usuarios.yml"
ansible.inventory_path = "inventory"
ansible.limit = "nodos"
end
end
Aquí utilizamos un condicional para aprovisionar nuestros nodos con un playbook llamado
“grupos_usuarios.yml”. Hasta que nuestro último nodo no esté en funcionamiento Vagrant
no aprovisionará nada con Ansible. Es necesario decirle explícitamente a Vagrant la ruta
donde se encuentra nuestro inventario. La palabra clave ‘limit’ se emplea para decirle a
Vagrant a que nodo / grupo-de-nodos queremos que se aplique este aprovisionamiento.
En el mismo directorio de nuestro proyecto creamos un fichero nuestro inventario. Agrupamos
nuestros nodos en un grupo llamado ‘nodos’ y creamos una cabecera ‘nodos:vars’ para
almacenar variables relacionadas con nuestro grupo. Con la última variable le decimos a
Ansible la ruta de nuestra llave privada para hacer las conexiones SSH. Este fichero tendrá
el nombre de ‘inventory’.
[nodos]
192.168.52.10
192.168.52.20
[nodos:vars]
ansible_user=vagrant
ansible_ssh_private_key_file=~/.ssh/id_rsa
[defaults]
inventory=inventory
deprecation_warnings=False
Ahora nos queda crear nuestro script de Ansible, conocido como playbook. Allí escribiremos
los ‘plays’ o ‘tasks’ que se realizarán en nuestros diferentes nodos para aprovisionarlos. Es
importante la forma en que usamos los espacios (o sangrías) en YAML9.
9
Yaml Tutorial | Learn YAML in 18 mins
Página | 38
---
- hosts: nodos
become: true
vars:
Nodo1Grupos: ["Gestion", "Marketing"]
Nodo2Grupos: ["Programadores", "Sistemas"]
Gestion: ['Ana','Maia','Lorenzo','Ricardo']
Marketing: ['Jose','Laura']
Programadores: ['Jon','Luis','Sara']
Sistemas: ['Alejandro','Oscar','Cristian']
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
tasks:
- name: crear Grupos nodo1
group:
state: present
name: "{{ item }}"
with_items: "{{ Nodo1Grupos }}"
when: ansible_facts.hostname == "nodo1"
- name: GESTION usuarios
user:
state: present
shell: /bin/bash
create_home: true
name: "{{ item }}"
group: Gestion
comment: Creado con Ansible
# la contraseña se puede crear con el programa mkpasswd
password:
$6$BsSaanpDOmQNTrw2$Krvyq7NhVe2qtghwJCcHBbuKpeij.eEdrSdLGN3Z6wSrSxEJ/0
u3eZ6JHGenbTXt8NCodmxxeChCv8HpFlds61
# hash de la contraseña luis (mkpasswd --method=SHA512)
with_items: "{{ Gestion }}"
when: ansible_facts.hostname == "nodo1"
- name: Marketing usuarios
user:
state: present
shell: /bin/bash
create_home: true
name: "{{ item }}"
group: Marketing
with_items: "{{ Marketing }}"
when: ansible_facts.hostname == "nodo1"
Página | 39
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
- name: crear Grupos nodo2
group:
state: present
name: "{{ item }}"
with_items: "{{ Nodo2Grupos }}"
when: ansible_facts.hostname == "nodo2"
- name: PROGRAMADORES usuarios
user:
state: present
shell: /bin/bash
create_home: true
name: "{{ item }}"
group: Programadores
with_items: "{{ Programadores }}"
when: ansible_facts.hostname == "nodo2"
- name: SISTEMAS usuarios
user:
state: present
shell: /bin/bash
create_home: true
name: "{{ item }}"
group: Sistemas
with_items: "{{ Sistemas }}"
when: ansible_facts.hostname == "nodo2"
---
Significa el inicio de un fichero escrito en YML.
- hosts: nodos
Especifica el nodo o grupo de nodos configurado en nuestro inventario.
vars:
Bloque donde se especificarán las variables. Para un playbook más limpio y organizado se
podría especificar un fichero externo que contenga dichas variables.
En este bloque estamos especificando una lista de grupos para cada nodo. Gestión y Marketing
para el nodo1 y Programadores y Administradores para el segundo nodo.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
Las líneas comentadas empiezan con un # y Ansible no las tomará en cuenta.
Página | 40
tasks:
Con esta sección se definen las tareas que Ansible irá ejecutando en cada uno de los nodos. Se
podría usar roles de Ansible, que es la estructura de ficheros y directorios recomendada por
Ansible para tener así un playbook principal que llame a otros playbooks y obtener así un fichero
limpio y organizado. Esto es parecido a escribir un programa en cualquier lenguaje de
programación. Para tener un orden de nuestro programa lo escribimos en diferentes ficheros los
cuales llamaremos desde un fichero principal. Sin embargo, dado que estamos solamente en un
ejemplo, tengo todo el código en un solo fichero para facilitar el entendimiento del funcionamiento
de este playbook. No usaré roles.
group:
state: present
name: "{{ item }}"
with_items: "{{ Nodo1Grupos }}"
when: ansible_facts.hostname == "nodo1"
Empieza un nuevo play. Este creará los usuarios con el módulo user.12
Queda demostrado la singularidad de YAML. Es un lenguaje fácil de leer. No hace falta explicar
nada más acerca de lo que hace este playbook. Si quisiéramos encriptar nuestra contraseña
podríamos emplear Ansible Vault.13
“Ansible Vault encrypts variables and files so you can protect sensitive content such as passwords or keys rather
than leaving it visible as plaintext in playbooks or roles. To use Ansible Vault you need one or more passwords to
encrypt and decrypt content. If you store your vault passwords in a third-party tool such as a secret manager, you
10
Ansible.builtin.group – Add or remove groups
11
Discovering variables: facts and magic variables
12
Ansible.builtin.user – Manage user accounts
13
Ansible Vault
Página | 41
need a script to access them. Use the passwords with the ansible-vault command-line tool to create and view
encrypted variables, create encrypted files, encrypt existing files, or edit, re-key, or decrypt files. You can then
place encrypted content under source control and share it more safely.”
Bien, ahora solo nos queda incluir información acerca de la empresa en cada directorio ‘home’ de
cada usuario. Podríamos incluir esta tarea en este mismo playbook, añadiendo más líneas. Pero
lo haré de otra forma más adelante. Aquí un vistazo de la estructura de nuestro proyecto.
‘info_empleados.yml’ es otro playbook que utilizaremos después. Los otros ficheros con relación
a la información a la empresa se utilizarán también en el siguiente playbook.
Para levantar toda nuestra infraestructura y crear los usuarios y grupos con tan solo un comando
ejecutaremos lo siguiente:
Página | 42
Página | 43
Página | 44
Página | 45
Nos quedaría aprovisionar los nodos con la información de los empleados en el directorio personal
de cada usuario. Para ello vamos a crear un nuevo playbook:
---
- hosts: nodos
become: true
vars:
Gestion: ['Ana','Maia','Lorenzo','Ricardo']
Marketing: ['Jose','Laura']
Programadores: ['Jon','Luis','Sara']
Sistemas: ['Alejandro','Oscar','Cristian']
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
tasks:
- name: Copiar información de los trabajadores al grupo Gestion
copy:
src: "../files/"
dest: "/home/{{ item }}"
mode: 0644
owner: "{{ item }}"
group: Gestion
with_items: "{{ Gestion }}"
when: ansible_facts.hostname == "nodo1"
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
- name: Copiar información de los trabajadores al grupo Marketing
copy:
src: "../files/"
dest: "/home/{{ item }}"
mode: 0644
owner: "{{ item }}"
group: Marketing
with_items: "{{ Marketing }}"
when: ansible_facts.hostname == "nodo1"
Solo aprovisionaré al nodo uno. Para aprovisionar el segundo con Ansible solo bastaría con editar
este playbook (copiar y pegar) y cambiar los valores correspondientes en los grupos y usuarios.
Página | 46
Página | 47
Playbook alternativo para crear usuarios y grupos en diferentes
equipos
Este playbook hace la misma función de crear usuarios y grupos, pero sin declarar variables.
---
- hosts: all
become: true
tasks:
- name: crear grupos
group:
name: "{{ item }}"
state: present
with_items:
- programadores
- administradores
when: ansible_facts.hostname == "nodo"
- name: crear usuarios programadores
user:
state: present
shell: /bin/bash
group: programadores
name: "{{ item }}"
Página | 48
with_items:
- Sofia
- Carla
- Rodolfo
when: ansible_facts.hostname == "nodo"
- name: crear usuarios administradores
user:
state: present
shell: /bin/bash
group: administradores
name: "{{ item }}"
with_items:
- Ricardo
- Jose
- Ansible
when: ansible_facts.hostname == "nodo"
Página | 49
Las líneas en azul (skipping) son nodos a los que el task no le afecta.
Página | 50
El resultado:
Página | 51
Servidor NFS y Cliente
En este ejercicio vamos a configurar un nodo para que sirva de servidor NFS. Un servidor NFS
nos permite compartir ficheros a través de la red. Configuraré dos nodos CentOS, uno funcionará
como servidor NFS y el otro como cliente. En esta práctica veremos el funcionamiento de nuevos
módulos de Ansible.
En nuestro primer nodo instalaremos los paquetes que sean necesarios para que funcione como
servidor NFS, crearemos el directorio a compartir y le asignaremos los permisos pertinentes,
activaremos los servicios que hagan falta para el funcionamiento de esta práctica, copiaremos el
archivo de configuración NFS desde nuestro equipo local al servidor. Una vez la configuración
esté realizada reiniciaremos el servicio.
En nuestro segundo nodo habrá que instalar los paquetes que hagan falta para que le permitan
dar uso de un servidor NFS, crearemos un punto de montaje de un directorio que utilizará para
acceder a los recursos compartidos y lo montaremos en el sistema.
Página | 52
Creamos un nuevo directorio para nuestro proyecto y nos posicionamos en él.
El código:
boxes = [
{ :name => "nodo1", :ip => "192.168.52.10" },
{ :name => "nodo2", :ip => "192.168.52.20" },
]
boxes.each do |opts|
Página | 53
config.vm.define opts[:name] do |config|
config.vm.hostname = opts[:name]
config.vm.network :private_network, ip: opts[:ip]
end
end
end
Página | 54
[luis@luis-manjaro nfs_servidor_cliente]$ ssh-keygen -R nodo1
# Host nodo1 found: line 3
/home/luis/.ssh/known_hosts updated.
Original contents retained as /home/luis/.ssh/known_hosts.old
Página | 55
Así quedaría nuestro inventario. Nodo1 se podría cambiar por ServidorNFS y nodo2 como
ClienteNFS.
---
- hosts: nodo1
become: true
tasks:
- name: Instalar Nfs-utils
yum:
name: nfs-utils
state: latest
- name: Crear directorio a compartir
file:
path: /home/vagrant/nfs_test
state: directory
owner: vagrant
group: vagrant
mode: '0775'
- name: Habilitar los servicios rpcbind nfslock nfs
service:
name: "{{ item }}"
enabled: true
with_items:
- rpcbind
- nfslock
- nfs
- name: Exportar fichero exports.j2
template:
src: exports.j2 # localizado en el directorio ./templates del
proyecto
dest: /etc/exports
owner: root
group: root
mode: '0644'
- name: Aplicar cambios NFS
shell: systemctl restart nfs; exportfs -a
Página | 56
Creamos un playbook que configure el cliente NFS
---
- hosts: nodo2
become: true
tasks:
- name: Instalar Nfs-utils
yum:
name: nfs-utils
state: latest
- name: Crear directorio que se sincronizará con el Servidor
file:
path: /mnt/compartido
state: directory
owner: vagrant
group: vagrant
mode: '0755'
- name: Montar el directorio del nodo1 en el nodo2
shell: mount 192.168.52.10:/home/vagrant/nfs_test
/mnt/compartido
Este es el contenido del fichero template.j2 que se encuentra alojado en un directorio llamado
‘templates’ dentro de nuestro proyecto. Es fichero de configuración del servidor NFS que se va
alojar en la ruta: /etc/exports
/home/vagrant/nfs_test 192.168.52.0/24(rw,sync,no_root_squash)
Página | 57
Levantemos nuestras máquinas:
Página | 58
Ejecutamos el playbook que solo afecta al cliente
Página | 59
Comprobaciones:
Generemos ficheros
Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :
API_VERSION = "2"
BOX_NAME = "centos/7"
BOX_IP = "192.168.50.4"
DOMAIN = "nip.io"
PRIVATE_KEY = "~/.ssh/id_rsa"
PUBLIC_KEY = '~/.ssh/id_rsa.pub'
Vagrant.configure(API_VERSION) do |config|
config.vm.box = BOX_NAME
config.vm.network "private_network", ip: BOX_IP
config.vm.host_name = BOX_IP + '.' + DOMAIN
config.ssh.insert_key = false
Página | 60
config.ssh.private_key_path = [PRIVATE_KEY, "~/.vagrant.d/insecure_private
_key"]
config.vm.provision "file", source: PUBLIC_KEY, destination: "~/.ssh/autho
rized_keys"
end
Inventario
[boxes]
192.168.50.4
[boxes:vars]
ansible_connection=ssh
ansible_user=vagrant
ansible_private_key_file=~/.ssh/id_rsa
Configuración NTP
# {{ ansible_managed }}
driftfile /var/lib/ntp/drift
restrict default nomodify notrap nopeer noquery
restrict 127.0.0.1
restrict ::1
{% for item in ntp_servers %}
server {{ item }} iburst
{% endfor %}
includefile /etc/ntp/crypto/pw
keys /etc/ntp/keys
disable monitor
Página | 61
Playbook
---
- hosts: boxes
gather_facts: true
become: true
become_method: sudo
vars:
ntp_servers:
- "0.centos.pool.ntp.org"
- "1.centos.pool.ntp.org"
- "2.centos.pool.ntp.org"
- "3.centos.pool.ntp.org"
handlers:
- name: reiniciar ntp
service:
name: ntpd
state: restarted
tasks:
- debug:
msg: "Me estoy conectando a {{ ansible_nodename }} el cual
corre sobre {{ ansible_distribution }} {{
ansible_distribution_version }}"
- yum:
name: ["ntp","ntpdate"]
state: installed
- template:
src: ./ntp.conf.j2
dest: /etc/ntp.conf
notify: reinciar ntp
Comprobación:
Página | 62
Con la opción -i indicamos explícitamente la ruta del fichero inventario (hosts) que vamos a utilizar.
Los handlers se utilizan para lanzar tareas al final de un playbook. Sin embargo, necesitan una
condición para ejecutarse: que haya habido algún cambio en el nodo (instalación de algún
paquete/servicio, creación de usuarios/grupos, subir o eliminar algún fichero). Empleamos la
palabra clave ‘notify’ más el nombre del ‘handler’ para llamarlo.
Página | 63
Conclusiones
Más que un proyecto, esto ha sido, relativamente, un manual del funcionamiento de Vagrant y
Ansible donde he puesto en práctica casi todo el conocimiento que he adquirido sobre estas dos
tecnologías a través de determinados scripts. He expuesto la solución a alguno de los problemas
con los que me iba topando en el curso de la realización de este trabajo. No puedes aprender sin
antes equivocarte y es por eso que resaltar mis errores -o mi ignorancia en aquel entonces- me
parece algo sumamente importante.
Ansible es una tecnología que se demanda cada vez más en el sector IT de entorno GNU Linux y
es de obligatorio conocimiento si deseas obtener algún certificado Linux de Red Hat14.
Gracias a Ansible he reforzado conocimientos de Seguridad a través del empleo de protocolos
como SSH, la administración de sistemas GNU-Linux con sus módulos correspondientes, la
programación de scripts para el despliegue de una infraestructura de servidores-clientes y la
gestión de los mismos con playbooks de Ansible, modificando el comportamiento de la
ejecución del código en función del valor de determinadas variables.
En GitHub podemos encontrar muchos repositorios con proyectos de Ansible muy útiles como
por ejemplo levantar un LAMP stack o un Wordpress con un simple comando.
Aunque en este proyecto me haya centrado en nodos Linux también existen módulos de
Ansible para controlar nodos Windows.
En cuanto Vagrant está bien para el desarrollo de un entorno de pruebas, pero para un entorno
más real se debe emplear otras tecnologías -dado las limitaciones de VirtualBox- como la nube
que nos ofrece DigitalOcean.
Vuelvo a enfatizar la importancia de Ansible. No hay casi ningún libro sobre entornos GNU Linux
moderno que no le dediquen al menos unas páginas dada su relevancia que tiene hoy en día.
14
Ofertas de trabajo que requieren Ansible
Página | 64