Vagrant 101 - Utilizando Infraestrutura como Código para estudo e desenvolvimento.

Vagrant é uma ferramenta de automação da Hashicorp na qual podemos utilizar para subir de servidores até uma infraestrutura e até mesmo configurar estes servidores.
O Vagrant utiliza a linguagem HCL (Hashicorp Configuration Language) que é de fácil entendimento (baseada em Ruby) e permite que possamos definir recursos da máquina virtual tais como hostname, ip, cpu, memória, disco, quantidade de máquinas a serem criadas e scripts a serem executados.
O Vagrant precisa de um Provider , que geralmente é um Hypervisor (Virtualbox, KVM, HyperV, VMWare, etc...), que será utilizado como base para fornecer os recursos de virtualização, podemos dizer que o Vagrant é como se fosse um frontend em CLI que utiliza os hypervisors como um backend para executar suas tarefas .
Para executar as ações o Vagrant trabalha com dois papéis distintos: Provisioner e Provider
Provider
O Provedor (provider) é responsável pela criação da instância dos ambientes, o Vagrant vem por padrão com suporte ao VirtualBox, Hyper-V e Docker, porém vários outros providers podem ser utilizados.
Privisioners
O Provisionador (provisioner) é o responsável pelas tarefas a serem executadas de forma automatizada, como por exemplo a instalação de pacotes e a configuração do sistema, o Vagrant oferece suporte a diversos provisioners como por exemplo Ansible, Chef, Puppet, Shell, File, etc...
Vagrant Box
O vagrant constroi suas instâncias através das Vagrant Boxes, que podem ser encontradas fácilmente na Vagrant Cloud.
Opnião Pessoal: Não recomendo a utilização do vagrant para ambientes de produção a não ser que o usuário crie suas próprias boxes, as boxes que se encontram no Vagrant Cloud são criadas por usuários comuns e como todo sistema tem suas vulnerabilidades. Ao criar uma box própria você pode tomar suas próprias precauções de segurança. para isto pode-se utilizar o Packer também da Hashicorp.
Vagrantfile
O Vagrantfile é o arquivo responsável por descrever nossa infraestrutura e subir a aplicação diretamente no Vagrant. O arquivo tem sua estrutura HCL porém é possivel utilizar todos os recursos da linguagem Ruby para descrever o mesmo.
É importante que o arquivo tenha o nome de Vagrantfile (case-sensitive) ou o comando do vagrant não irá localiza-lo
Instalando o Vagrant
Para instalar o vagrant devemos acessar a página oficial de downloads e buscar o pacote referente ao seu sistema operacional, no caso estarei fazendo a instalação em um sistema Debian-Based.
$ cd /tmp
$ curl https://releases.hashicorp.com/vagrant/2.2.7/vagrant_2.2.7_x86_64.deb -o vagrant.deb
$ sudo dpkg -i vagrant.deb
Podemos executar o comando vagrant --version
para verificar se o vagrant foi instalado corretamente.

Definindo as Máquinas Virtuais
Em nosso post, estaremos utilizando o VirtualBox 6.1, para isto basta efetuar o download do virtualbox através da página oficial de downloads e efetuar a instalação de acordo com seu sistema operacional.
Para definir nossa máquina virtual utilizaremos o Vagrantfile.
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
vb.cpus = "2"
end
end
No exemplo acima estamos definindo uma infraestrutura de apenas uma máquina com a box do Ubuntu Bionic 64bits, a máquina possuirá 1024 MiB de RAM e 2 vCPUs.
Toda estrutura do Vagrantfile começa informando a versão (Major Version) que irá utilizar do Vagrant e termina com a palavra end.
Como nosso vagrant está na versão 2.2.7, a major version é a 2, então o arquivo começará com: Vagrant.configure("2") do |config|
e como segue a estrutura do ruby, terminará com um end
.
O próximo passo é informar qual a box que iremos utilizar nessa instância, nesse caso utilizaremos uma imagem chamada ubuntu/bionic64 : config.vm.box = "ubuntu/bionic64"
.
Por último criamos uma infraestrutura onde informamos qual o provedor da nossa instância e suas configurações como cpu, memória, hostname, disco, etc... config.vm.provider "virtualbox" do |vb|
Criando nossa primeira instância
Podemos utilizar o comando vagrant init <box>
para que seja criado o arquivo do Vagrantfile. Crie um diretório para armazenar os arquivos do nosso tutorial.
$ mkdir ~/vagrant-101/
$ cd ~/vagrant-101/
$ vagrant init -m ubuntu/bionic64
Ao executar o comandovagrant init <box>
um arquivo Vagrantfile é criado com diversas opções já preenchidas, porém comentadas. Para quem está começando a trabalhar com o vagrant, talvez seja a melhor opção, caso contrário podemos passar o parâmetro-m
ou--minimal
para remover todas as linhas comentadas e deixar somente o necessário.
Edite o arquivo para que fique igual a nosso exemplo
$ vim Vagrantfile
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
vb.cpus = "2"
end
end
Para verificar se a sintaxe está correta, podemos utilizar o comando vagrant validate
. Agora vamos construir o ambiente de acordo com o que se encontra parametrizado no Vagrantfile:
$ vagrant up
Agora que temos nosso ambiente disponível, podemos acessar o mesmo através do comando vagrant ssh
, conecte-se a máquina e desconecte-se em seguida
$ vagrant ssh
$ exit
Note também diretamente no Virtualbox que a máquina foi criada.

Quando utilizamos o comando vagrant ssh
o que acontece "por trás dos panos" é simplesmente um comando SSH comum, onde a chave utilizada esta armazenada no diretório oculto .vagrant localizado no mesmo diretório do Vagrantfile.

Podemos verificar a fundo os parâmetros utilizados pelo vagrant ssh
através do comando vagrant ssh-config
.

Para desligar a máquina podemos utilizar o comando vagrant halt
e para destruir o ambiente podemos executar o comando vagrant destroy
e digitar y
para confirmar a destruição do ambiente.
$ vagrant halt
$ vagrant destroy
Construindo Múltiplas Instâncias
Modifique agora o arquivo para adicionarmos uma segunda instância para nossa infraestrutura
$ vim Vagrantfile
Vagrant.configure("2") do |config|
config.vm.define "server1" do |server1|
server1.vm.box = "ubuntu/bionic64"
end
config.vm.define "server2" do |server2|
server2.vm.box = "centos/7"
end
end
Vamos construir nossa infraestrutura novamente e verificar os status da mesma.
$ vagrant up
$ vagrant status

Para acessar cada máquina podemos utilizar o comando vagrant ssh <machine_name>
, podemos também verificar o sistema operacional através do comando cat /etc/*release
.
$ vagrant ssh server1
$ cat /etc/*release
$ exit
$ vagrant ssh server2
$ cat /etc/*release
$ exit
Podemos também executar um comando diretamente através do parâmetro -c
no vagrant ssh
$ vagrant ssh server1 -c "cat /etc/*release"
$ vagrant ssh server2 -c "cat /etc/*release"

Vamos destruir nosso ambiente.
$ vagrant destroy -f
O parâmetro -f
força o comando executado sem solicitar confirmação.
Configurando Interfaces de Rede
Vamos editar agora nosso Vagrantfile para definir o endereço IP e as configurações das interfaces de rede através do parâmetro vm.network
$ vim Vagrantfile
Vagrant.configure("2") do |config|
config.vm.define "server1" do |server1|
server1.vm.box = "ubuntu/bionic64"
server1.vm.network "private_network", ip: "10.10.10.11"
end
config.vm.define "server2" do |server2|
server2.vm.box = "centos/7"
server2.vm.network "private_network", ip: "10.10.10.12"
end
end
Vamos construir o ambiente e verificar o endereço IP das máquinas
$ vagrant up
$ vagrant ssh server1 -c "ip -c a show enp0s8"
$ vagrant ssh server2 -c "ip -c a show eth1"
Após isto, vamos destruir novamente nosso ambiente
$ vagrant destroy -f
Utilizando Provisioners
Vamos utilizar os provisioners para executar ações após a máquina ter sido criada pelo Provider.
Iremos utilizar o provisioner shell para instalar o nginx na máquina ubuntu.
$ vim Vagrantfie
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.network "private_network", ip: "10.10.10.11"
config.vm.provision "shell", inline: "sudo apt update && sudo apt install nginx -y"
end
Vamos construir nosso ambiente e verificar se o nginx foi instalado com sucesso.
$ vagrant up
$ curl 10.10.10.11
Podemos também verificar diretamente no navegador digitando o endereço do servidor

Vamos destruir nosso ambiente novamente.
$ vagrant destroy -f
Podemos alterar a maneira na qual nosso script é chamado, uma das maneiras é declarando o script no início do arquivo. E novamente verificar se o nginx foi instalado corretamente.
$ vim Vagrantfile
$script = <<-EOF
sudo apt update
sudo apt install nginx -y
EOF
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.network "private_network", ip: "10.10.10.11"
config.vm.provision "shell", inline: $script
end
$ vagrant up
$ curl 10.10.10.11
Vamos destruir nosso ambiente novamente.
$ vagrant destroy -f
BONUS: Laboratório de Docker
Vamos criar um Dockerfile no qual criará três servidores com docker e docker-compose instalado e configurará um cluster de Docker Swarm para que possa ser utilizado como ambiente de estudos para o Docker.
Deixei o script de uma maneira que sua manutenção seja feita de maneira fácil, sendo assim podendo ser aproveitado com pequenas modificações pra diversas outras aplicações.
$ vim Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :
machines = {
"master" => {"memory" => "1024", "cpu" => "2", "ip" => "100", "image" => "ubuntu/bionic64"},
"node01" => {"memory" => "1024", "cpu" => "2", "ip" => "101", "image" => "ubuntu/bionic64"},
"node02" => {"memory" => "1024", "cpu" => "2", "ip" => "102", "image" => "centos/7"}
}
Vagrant.configure("2") do |config|
machines.each do |name, conf|
config.vm.define "#{name}" do |machine|
machine.vm.box = "#{conf["image"]}"
machine.vm.hostname = "#{name}.caiodelgado.dev"
machine.vm.network "private_network", ip: "10.10.10.#{conf["ip"]}"
machine.vm.provider "virtualbox" do |vb|
vb.name = "#{name}"
vb.memory = conf["memory"]
vb.cpus = conf["cpu"]
vb.customize ["modifyvm", :id, "--groups", "/Docker-Lab"]
end
machine.vm.provision "shell", path: "docker.sh"
if "#{conf["image"]}" == "centos/7"
machine.vm.provision "shell", inline: "sudo systemctl start docker && sudo systemctl enable docker"
end
if "#{name}" == "master"
machine.vm.provision "shell", path: "master.sh"
else
machine.vm.provision "shell", path: "worker.sh"
end
end
end
end
Criaremos também um script para instalação do docker, chamado docker.sh
, um arquivo vazio chamado worker.sh
que receberá o comando de join do cluster e um script para configurar o master chamado master.sh
.
$ vim docker.sh
#!/bin/bash
curl -fsSL https://get.docker.com | sudo bash
sudo curl -fsSL "https://github.com/docker/compose/releases/download/1.25.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo usermod -aG docker vagrant
$ touch worker.sh
$ vim master.sh
#!/bin/bash
sudo docker swarm init --advertise-addr=10.10.10.100
sudo docker swarm join-token worker | grep docker > /vagrant/worker.sh
Após tudo isto basta executar o comando vagrant up
que todo o ambiente estará disponível em questão de minutos.
Podemos verificar estado do cluster através do comando
$ vagrant ssh master -c "docker node ls"

O código deste post encontra-se no repositório: https://github.com/caiodelgadonew/blog-vagrant-101
Ficamos por aqui com esse post e nos vemos em uma próxima!