Antes de poder provisionar, es decir, configurar los servicios en una máquina o conjunto de máquinas virtuales, es necesario poder crearlas a partir de imágenes de sistemas operativos, en un remedo de arranque tal como se hace en una máquina real. En el espíritu DevOps, tiene que haber una forma de hacerlo automáticamente y de forma reproducible con código, usando este como infraestructura. También es deseable hacerlo de forma que sea totalmente portable de una infraestructura a otra; en general, debe haber un procedimiento para que las infraestructuras como servicio (IaaS) sean controladas programáticamente, aparte de las otras dos formas que suelen ofrecer, a través de la web y a través de los interfaces de línea de órdenes. Trabajar con la web no es escalable y apenas es reproducible, porque solo permite crear infraestructuras una por una o por grupos si es que son similares; la segunda no es portable, porque cada servicio cloud tiene sus propias órdenes como se ha visto en el tema de automatizar servicios cloud. Solo el trabajar con código desde una herramienta que permita trabajar con cualquier servicio cloud permite reproducir configuraciones de un vendedor a otro sin ningún problema.
Estas herramientas se denominan herramientas de orquestación o de gestión de configuraciones, aunque a veces en esta denominación también incluyen a otras de más bajo nivel como Ansible o Chef. En realidad hoy en día la única herramienta existente y en uso amplio es Vagrant. Otras herramientas, como Vortex, parecen abandonadas y otras como Juju o Cobbler funcionan a otro nivel diferente, trabajando para configurar desde cero bare metal o máquinas sin ningún sistema operativo instalado que usan arranque remoto. Terraform, por otro lado, es una herramienta que sí permite tratar la infraestructura como código y se encuentra más o menos en la misma área, aunque Vagrant se centra en gestionar entornos de desarrollo y Terraform es para construir infraestructura; en ese sentido, Vagrant es de más alto nivel que Terraform, aunque se pueden usar de forma complementaria.
La ventaja de Vagrant es que puede trabajar de forma indistinta con máquinas virtuales locales o remotas e incluso, en las últimas versiones, con contenedores, simplemente cambiando los drivers con los que va a trabajar. Lo veremos a continuación.
No vamos a dedicar demasiado tiempo a la creación y configuración de
máquinas virtuales específicas, aunque en el tema adicional de
uso de sistemas en la nube se explica como
trabajar con máquinas virtuales con kvm
y cómo definir, desde la
línea de órdenes, máquinas virtuales en sistemas en cloud como Azure.
A un nivel superior al provisionamiento de máquinas virtuales está la configuración, orquestación y gestión de las mismas, herramientas como Vagrant ayudan a hacerlo, aunque también Puppet e incluso Juju pueden hacer muchas de las funciones de Vagrant, salvo por el hecho de que no pueden trabajar directamente con el hipervisor.
Realmente no hay muchas alternativas a Vagrant. En este hilo de 2016 de YCombinator mencionan algunos, pero ninguno es lo suficientemente amplio o aceptado para hacerle sombra. Únicamente sistemas basados en contenedores pueden acercársele; por ejemplo Kubernetes. Pero ninguno para orquestación de máquinas virtuales.
La ventaja de Vagrant es que permite gestionar el ciclo de vida completo de una máquina virtual, desde la creación hasta su destrucción pasando por el provisionamiento y la monitorización o conexión con la misma. Además, permite trabajar con todo tipo de hipervisores y provisionadores tanto de contenedores, como en cloud, como en local.
Sigue las instrucciones en la web, para instalarte Vagrant, la versión 2.0.1 ha salido en noviembre de 2017. Es una aplicación escrita en Ruby, por lo que tendrás que tener una instalación preparada. Te aconsejamos que uses un gestor de versiones como RVM o RBEnv para poder trabajar con él en espacio de usuario fácilmente.
Tendrás que tener algunas nociones de Ruby para trabajar con Vagrant, que no es sino un DSL (Domain Specific Language) construido sobre él, al menos tendrás que saber como instalar gemas (bibliotecas), que se usarán para los plugin de Vagrant y también cómo trabajar con bucles y variables, que se usarán en el fichero de definición de máquinas virtuales denominado
Vagrantfile
.
Con Vagrant te puedes descargar directamente
una máquina configurada de esta lista o
bien cualquier otra máquina configurada en el formato box
, que es el
que uno de los que usa Vagrant.
Trabajemos con la configuración por omisión añadiendo la siguiente
box
:
vagrant box add centos7 https://github.com/vezzoni/vagrant-vboxes/releases/download/0.0.1/centos-7-x86_64.box
Para conocer todos los comandos de vagrant,
vagrant help
ovagrant list-commands
.
En este caso usamos un subcomando de vagrant box
,
que permite añadir nuevas imágenes a nuestro gestor de máquinas
virtuales; add
añade uno nuevo en este caso usando el gestor por
omisión que es Virtual Box. El formato determinará en qué tipo de hipervisor se puede ejecutar; en
general, Vagrant usa VirtualBox, y los .box
se pueden importar
directamente en esta aplicación; los formatos vienen listados en la
página anterior. Las boxes disponibles se pueden consultar
en Vagrantbox.es; en esa dirección hay
diferentes sistemas operativos en diferentes formatos, aunque
generalmente la mayoría son para VirtualBox.
Las imágenes no son simplemente sistemas operativos instalados, deben de tener una serie de ganchos para que Vagrant pueda trabajar con ellas a través del hipervisor que use. En este tutorial explica los pasos necesarios para crear una imagen, para lo que aconsejan usar Packer.
A continuación
vagrant init centos7
creará un Vagrantfile
en el directorio en el que te encuentres, por
lo que es conveniente que el directorio esté vacío. En este caso crea
este fichero
en el que realmente solo se configura el nombre de la máquina
(centos7
) pero que a base de des-comentar otras líneas se puede
llegar a configurar de forma más completa.
Para comenzar a usar la máquina virtual se usa
vagrant up
En ese momento Virtual Box arrancará la máquina y te podrás conectar a ella usando
vagrant ssh
Si quieres conectar por ssh
desde algún otro programa, por ejemplo,
Ansible, tendrás que fijarte en cómo se configura la conexión y que
puerto se usa. Generalmente, una máquina virtual va a usar el puerto
2222 de ssh y para que accedas desde fuera de Vagrant tendrás además
que copiar tu clave pública, lo que puedes hacer copiando y pegándola
desde un terminal o bien
usando el propio Vagrantfile
añadiendo las dos líneas siguientes:
#Copy public key
ssh_pub_key = File.readlines("#{Dir.home}/.ssh/id_rsa.pub").first.strip
config.vm.provision 'shell', inline: "echo #{ssh_pub_key} >> /home/vagrant/.ssh/authorized_keys", privileged: false
Una vez creada la máquina, para que use estas líneas y se provisione hay que hacer
vagrant provision
Hay que seguir teniendo en cuenta que se usa el puerto 2222, o sea que para conectarte a la máquina (o usar un provisionador de forma externa a Vagrant) tendrás que hacerlo así:
ssh vagrant@127.0.0.1 -p 2222
Lo que también se puede hacer con
vagrant ssh
, claro. El hacerlo así es para que quede claro cómo se hace la conexión directa desde ssh para poder provisionar directamente la máquina virtual sin pasar necesariamente por vagrant.
Para suspender el estado de la máquina virtual y guardarlo se usa
vagrant suspend
Esto dejará la máquina virtual en el estado en el que esté, que se podrá recuperar en la próxima sesión. Cuando quieras deshacerte de ella,
vagrant destroy
Instalar una máquina virtual Debian usando Vagrant y conectar con ella.
Desde Vagrant se pueden configurar, adicionalmente, algunos aspectos de la máquina virtual relacionados con la red, por ejemplo, los puertos o incluso la IP. En general, esto requiere de la instalación de una serie de servicios en el sistema operativo, por lo que lo habitual es que las imágenes estén preparadas específicamente para hacerlo; si no, no se podrá hacer desde Vagrant sino desde alguna otra herramienta (como Ansible o Chef).
Vagrant tiene una serie de drivers para trabajar con los proveedores de cloud más habituales, así como con herramientas libres como OpenStack. Vagrant trabaja con el API de estos servicios, que en la jerga de Vagrant se denominan providers o proveedores. En algunos casos, estos proveedores son extraoficiales, aunque dado que trabajan contra APIs abiertos, funcionan generalmente bien.
Por ejemplo, el driver para Azure se configura como una aplicación cliente de Azure.
Para lo que es conveniente ver el tutorial de uso de Azure desde la línea de órdenes, donde explica entre otras cosas el concepto de Service Principal, usado para configurar el driver.
En general, dado que estos drivers trabajan con el API, hay dos cosas a tener en cuenta a la hora de crear el Vagrantfile
Un Vagrantfile es un programa en Ruby y por tanto se podrá usar el SDK de Ruby para la nube correspondiente para realizar este tipo de tareas. En la práctica, no he visto ningún Vagrantfile que haga este tipo de cosas, porque si se tiene que usar el SDK, finalmente es más práctico que la orquestación se haga también desde el SDK.
En vagrantbox.es
la mayor parte de las
imágenes tienen formato VirtualBox, pero algunas tienen formato
Vagrant-lxc
(para usar en el sistema de virtualización ligera lxc
), VMWare,
KVM, Parallels o libvirt
. Ninguno de estos formatos está instalado
por defecto en Vagrant y para trabajar con ellos habrá que instalar un
plugin.
Instalemos el
plugin de libvirt
,
por ejemplo, siguiendo las instrucciones de su repositorio; de hecho,
para instalar libvirt habrá que seguir también las instrucciones en el
mismo repositorio, que incluyen instalar
qemu
. libVirt es una librería que abstrae
todas las funciones de virtualización, permitiendo trabajar de forma
uniforme con diferentes hipervisores tales como Xen e incluso el
propio lxc
mencionado anteriormente. Una vez instalada esta
biblioteca, no hay que preocuparse tanto por el sistema de
virtualización que tengamos debajo e incluso podemos trabajar con ella
de forma programática para crear y configurar máquinas virtuales sin
tener que recurrir a sistemas de orquestación o provisionamiento.
Simultáneamente a la instalación, podemos descargarnos esta máquina virtual que está en ese formato.
vagrant box add viniciusfs/centos7 https://atlas.hashicorp.com/viniciusfs/boxes/centos7/
A continuación, la inicialización será de la misma forma
vagrant init viniciusfs/centos7
que, igual que en el caso anterior, crea un fichero Vagrantfile
(y
así te lo dice; este fichero será parecido a este que ya sabemos que permite trabajar
y llevar a cabo cualquier configuración adicional.
Podemos añadirle también la clave pública propia si queremos usarlo “desde fuera”, tal como también se ha hecho antes.
Una vez hecho eso ya podemos inicializar la máquina y trabajar con ella (pero antes voy a apagar la máquina Azure que tengo ejecutándose desde que empecé a contar lo anterior)
sudo vagrant up --provider=libvirt
donde la principal diferencia es que le estamos indicando que queremos
usar el proveedor libvirt
, en vez de el que usa por omisión, Virtual
Box. Dado que este proveedor conecta con un daemon que se ejecuta en
modo privilegiado, habrá que usar sudo
en este caso.
Puede que tengas un problema con libvirt con “dhcp_leases: undefined method” o similar comprobad la versión que tenéis. Si estáis usando la 0.0.36 desinstaladla e instalad la 0.0.35 como indican en el mismo issue. Alternativamente, también se puede pasar de este ejemplo, que era simplemente una forma de ilustrar diferentes proveedores aparte del que aparece por defecto.
Usando esta orden (pero solo en este caso), puedes conectarte con la máquina usando
sudo vagrant ssh
Y todos los demás comandos, también con sudo
, por lo indicado anteriormente.
Instalar una máquina virtual ArchLinux o FreeBSD para KVM, otro hipervisor libre, usando Vagrant y conectar con ella.
Una de las capacidades más interesantes de Vagrant es la posibilidad
de orquestar, es decir, configurar varias máquinas simultáneamente
de forma que tengan una configuración común y estén conectadas entre
sí. En esto hay que tener en cuenta que se pueden configurar
diferentes aspectos de las mismas y su conexión, tal como IPs. Por
ejemplo, con este Vagrantfile
:
Vagrant.configure("2") do |config|
config.vm.define 'public' do |public|
public.vm.box = "debian/stretch64"
public.vm.network "private_network", ip: "192.168.0.10"
end
config.vm.define 'db' do |db|
db.vm.box = "fnando/dev-xenial64"
db.vm.network "private_network", ip: "192.168.0.11"
end
end
En este Vagrantfile se muestra como se configuran dos máquinas virtuales unidas a la misma red privada, cada una de ellas con una IP fija. De esta forma puedes configurar los servicios en ellas para que solo escuchen a esa IP las peticiones como medida adicional de seguridad.
Conviene tener en cuenta que para tener esta red privada virtual, el sistema operativo contenido en la imagen tiene que permitirlo. En algunos casos (en concreto, en una imagen basada en Alpine) no lo permitía. Los sabores de Linux habituales como Debian, CentOS o Ubuntu no tendrían que tener ningún problema.
La imagen que se usa en el segundo caso es una que incluye Redis y PostgreSQL, y que por tanto se puede usar como base para cualquier aplicación que las use.
Una vez creada la máquina virtual se puede entrar en ella, configurarla e instalar todo lo necesario desde la línea de órdenes. Pero, por supuesto, sabiendo lo que sabemos sobre provisionamiento por el tema correspondiente, Vagrant permite provisionarla de muchas maneras diferentes. En general, Vagrant usará opciones de configuración diferente dependiendo del provisionador, subirá un fichero a un directorio temporal del mismo y lo ejecutará (tras ejecutar todo lo necesario para el mismo).
La provisión tiene lugar cuando se alza una máquina virtual (con
vagrant up
) o bien explícitamente haciendo vagrant provision
. En
cualquier caso se lee del Vagrantfile y se llevan a cabo las acciones
especificadas en el fichero de configuración.
En general, trabajar con un provisionador requiere especificar de cuál
se trata y luego dar una serie de órdenes específicas. Comenzaremos
por el
shell, que
es el más simple y, en realidad, equivale a entrar en la máquina y dar
las órdenes a mano. Instalaremos, como hemos hecho en otras ocasiones,
el utilísimo editor emacs
usando este
Vagrantfile
:
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "centos7"
config.vm.provision "shell",
inline: "yum install -y python"
end
Recordemos que se trata de un programa en Ruby en el cual configuramos
la máquina virtual. La 4ª línea indica el nombre de la máquina con la
que vamos a trabajar (que puede ser la usada en el caso anterior);
recordemos también que, por omisión, se trabaja con VirtualBox (si se
hiciera con algún otro tipo de hipervisor habría que usar el plugin
correspondiente e inicializar la máquina de alguna otra forma). La
parte en la que efectivamente se hace la provisión va justamente a
continuación. La orden config.vm.provision
indica que se va a usar
el sistema de provisión del shell
, es decir, órdenes de la línea de
comandos; se le pasa un hash en Ruby (variable: valor, tal como en
javascript, separados por comas) en el que la clave inline
indica el
comando que se va a ejecutar, en este caso yum
, el programa para
instalar paquetes en CentOS, y al que se le indica -y
para que
conteste Yes a todas las preguntas sobre la instalación.
Este Vagrantfile no necesita nada especial para ejecutarse: se le
llama directamente cuando se ejecuta vagrant up
o explícitamente
cuando se llama con vagrant provision
. Lo único que hará es instalar
este programa bajándose todas sus dependencias (y tardará un rato).
Crear un script para provisionar de forma básica una máquina virtual para el proyecto que se esté llevando a cabo en la asignatura.
El provisionamiento por shell admite muchas más opciones: se puede usar un fichero externo o incluso alojado en un sitio web (por ejemplo, un Gist alojado en Github). Por ejemplo, este para provisionar nginx y node (no leer hasta después de hacer el ejercicio anterior).
El problema con los guiones de shell
y no sé por qué diablos pongo guiones si pongo shell, podía poner scripts de shell directamente y todo el mundo me entendería, o guiones de la concha y nadie me entendería
es que son específicos de un sistema operativo determinado. Por eso Vagrant permite muchas otras formas de configuración, incluyendo casi todos los sistemas de provisionamiento populares (Chef, Puppet, Ansible, Salt) y también Docker. La ventaja de estos sistemas de más alto nivel es que permiten trabajar independientemente del sistema operativo. Cada uno de ellos tendrá sus opciones específicas, pero veamos cómo se haría lo anterior usando el provisionador chef-solo.
Para empezar, hay que provisionar la máquina virtual para que funcione con chef-solo y hay que hacerlo desde shell o Ansible; este ejemplo que usa este fichero shell puede provisionar, por ejemplo, una máquina CentOS.
Una vez preinstalado Chef
Lo que también podíamos haber hecho con una máquina que ya lo tuviera instalado, de las que hay muchas en
vagrantbox.es
y de hecho es la mejor opción porquechef-solo
no se puede instalar en la versión 6.5 de CentOS fácilmente por no tener una versión actualizada de Ruby)
lo incluimos en el Vagrantfile, tal como este
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "centos7"
config.vm.provision "chef_solo" do |chef|
chef.add_recipe "emacs"
end
end
Este fichero usa un bloque de Ruby para pasarle variables y
simplemente declara que se va a usar la receta emacs
, que
previamente tendremos que haber creado en un subdirectorio cookbooks
que descienda exactamente del mismo directorio y que contenga
simplemente package 'emacs'
que tendrá que estar en un fichero
cookbooks/emacs/recipes/default.rb
Con todo esto se puede configurar emacs. Pero, la verdad, seguro que es más fácil hacerlo en Ansible y/o en otro sistema operativo que no sea CentOS porque yo, por lo pronto, no he logrado instalar chef-solo en ninguna de las máquinas pre-configuradas de VagrantBoxes.
Configurar tu máquina virtual usando vagrant
con el provisionador
ansible
Desde Vagrant se puede crear también una
caja base con lo
mínimo necesario para poder funcionar, incluyendo el soporte para ssh
y provisionadores como Chef o Puppet. Se puede crear directamente en
VirtualBox y usar
vagrant package
para empaquetarla y usarla para su consumo posterior.
Vagrant es sumamente útil cuando se trata de configurar varios sistemas a la vez, por la posibilidad que tiene de trabajar con diferentes proveedores pero también por tratarse de un programa en Ruby que puede simplemente guardar el estado común e implantarlo en las máquinas virtuales que se vayan creando.
A la vez, sistemas operativos como CoreOS son interesantes
precisamente por la facilidad para configurarlos como sistemas
distribuidos, que proviene de su diseño para ser anfitriones de
contenedores pero también a su uso de etcd
, una base de datos
clave-valor distribuida que se usa en este caso principalmente para
guardar las configuraciones.
Veamos en el siguiente ejemplo cómo se
puede
configurar un sistema con varias máquinas virtuales coordinadas usando CoreOS
(originalmente
estaba aquí. Es un fichero
un tanto largo y complicado, pero veamos las partes más
interesantes. Primero, usa un fichero externo de
configuración,
config.rb
. A
pesar de su nombre, no es un fichero de Chef, simplemente un fichero
que se va a incluir en la configuración de Vagrant que se llama así.
# Size of the CoreOS cluster created by Vagrant
$num_instances=3
# Used to fetch a new discovery token for a cluster of size $num_instances
$new_discovery_url="https://discovery.etcd.io/new?size=#{$num_instances}"
# Automatically replace the discovery token on 'vagrant up'
if File.exists?('user-data') && ARGV[0].eql?('up')
require 'open-uri'
require 'yaml'
token = open($new_discovery_url).read
data = YAML.load(IO.readlines('user-data')[1..-1].join)
if data.key? 'coreos' and data['coreos'].key? 'etcd'
data['coreos']['etcd']['discovery'] = token
end
if data.key? 'coreos' and data['coreos'].key? 'etcd2'
data['coreos']['etcd2']['discovery'] = token
end
# Fix for YAML.load() converting reboot-strategy from 'off' to `false`
if data.key? 'coreos' and data['coreos'].key? 'update' and data['coreos']['update'].key? 'reboot-strategy'
if data['coreos']['update']['reboot-strategy'] == false
data['coreos']['update']['reboot-strategy'] = 'off'
end
end
yaml = YAML.dump(data)
File.open('user-data', 'w') { |file| file.write("#cloud-config\n\n#{yaml}") }
end
Este fichero, después de definir el número de máquinas virtuales que
tenemos, busca un fichero llamado user-data
que es privado porque
contiene un token obtenido de https://discovery.etcd.io. Este
token da acceso al registro de todas las instancias de etcd
con
las que vamos a
trabajar. En
la muestra indica
qué es lo que hay que hacer para obtenerla. Por lo demás, lo único
que hay que cambiar es el número de instancias que se desean.
El Vagrantfile
, por otro lado, realiza una serie de adaptaciones de
la máquina virtual usada y crea una red privada virtual que una a las
tres máquinas, de forma que se puedan comunicar de forma segura. Una
vez ejecutado vagrant up
se puede acceder a las máquinas por ssh con
el nombre definido (core-0x
) pero también se pueden usar para
escalar aplicaciones basadas en contenedores en
el
cluster de CoreOS creado,
con el que puedes ejecutar
múltiples copias de tu aplicación
para replicación o escalado automático.
La migración a la nube ha hecho que se creen ciertos sistemas
operativos (o sabores de los mismos) cuyo fin sea servir de soporte
exclusivamente a servicios
en la misma. Uno de ellos es Scotch Box, que
empaqueta una serie de herramientas de cliente servidor para ejecutar
una pila de desarrollo completa, o
bosh-lite
para
BOSH, una herramienta de gestión de sistemas distribuidos.
Otra posibilidad es
una máquina virtual para empezar con ciencia de datos.
Pero una de las más interesantes que podemos usar es RancherOS, otro sistema operativo, como CoreOS, diseñado para ejecutar contenedores. En el repo dice que ya no se apoya esa versión, pero se puede al menos ejecutar y probar si se desea.
También se puede probar NixOps, que aunque está diseñado especialmente para una versión del sistema operativo llamado NixOS, puede usarse también para orquestar máquinas virtuales en una serie de entornos.
Este es el último tema del curso, pero a partir de aquí se puede seguir aprendiendo sobre DevOps en el blog o en IBM. Libros como DevOps for Developers pueden ser también de ayuda.
Herramientas alternativas a Vagrant pueden ser Vagga, aunque está enfocada más bien a entornos de desarrollo, y por supuesto Terraform.
Por otro lado, Kubernetes es el estándar para trabajar con contenedores y orquestarlos, aunque hay otras alternativas. Los contenedores, precisamente, son el objetivo del siguiente tema.