Marco Carrozzo
Posted on November 11, 2020
[EN_version_TBD]
Da circa un anno a questa parte ricopro il ruolo di DevOps. O, come ironicamente ci piace soprannominarci nel settore, YAML engineer.
Che poi è una definizione che mi sta stretta: è solo quando la mia giornata lavorativa va male, che passo 8 ore al giorno ad arrovellarmi tra una ventina di docker-compose(.yml) e, da poco, anche sui deployment di Kubernetes. Ma, a dirla tutta, preferisco i TOML. Che sono utilizzati poco, molto poco. Quasi niente.
Se qualcuno ancora si stesse chiedendo quali benefici può portare la figura del Devops ad una ipotetica azienda a conduzione familiare, risponderei: automatizza tutt'cos!
O, pensandoci su:
la persona o il team che traghetta quell'incubo di applicazione, uscita da un film dell'orrore, nel mondo delle best practices. Un mondo fatto di: ti automatizzo la build, ti segnalo eventuali bug di sicurezza o quantomeno ti faccio rilasciare un codice
manut...guard...capibile., ti standardizzo i test e ti faccio rilasciare il software in produzione con due click.
In poche parole: il massimo risultato con il minimo sforzo. Il sogno bagnato di ogni sfaticato. Incluso me.
La giornata tipo di un devops non è fatta solo di YAML e di Kubernetes, però. Ma anche di passione, emozioni (negative) e automatismi. E come ogni professionista del settore IT, anche noi abbiamo un coltellino cinessvizzero con 40 funzionalità.
A molti di noi, in ufficio, vengono assegnati dei portatili su cui gira Windows. Nel nostro lavoro, però, automatizzare e rilasciare è una cosa che preferiamo delegare al buon vecchio linux con le sue primitive posix e bash, che tra una find, un awk ed un zcat macina anche i sassi.
Microsoft ha finalmente capito quale sia il lato corretto della corrente da seguire, e finalmente ci ha deliziato con strumenti comodi e pratici: VScode, WSL2, Windows Terminal, per citarne alcuni.
I Devops, come si è capito, sono svogliati, non si fidano dei Dev e quindi gli piace automatizzare. Ma sopratutto, gli piace che una cosa possa essere configurata, installata e riutilizzata. Spesso gli ambienti di dev (e non solo quelli) diventano così confusi e pieni di monnezza, che l'unica soluzione sensata sembra essere il formattone riparatore.
4-5 anni fa, uno sviluppatore smart alle prime armi nell'industria del software avrebbe potuto pensare: ok, installo Vbox, scarico una immagine da osboxes, spendo qualche giornata a vedere tutorial su come configurare il maledetto bridge tra vbox e windows, e dopo aver ripetuto manualmente le configurazioni 15 volte sono pronto per lavorare.
Il tempo passa e Microsoft gli va incontro: Ora può semplicemente installare una ricca Ubuntu dallo store di Windows. Questa gira su WSL, ha le cartelle condivise che può usare da Windows, sta sullo stesso network di windows e pertanto non deve impazzire a configurarlo, installa npm/maven/go/sarcazzo e inizia a lavorare. C'è solo la shell. Pazienza, deve fare solo mv, cp e lanciare la build e l'esecuzione, se gli va proprio male.
6 mesi dopo, scopre che ha instalato tanta di quella roba su WSL, che gli pesa 45gb e ha bisogno di un esorcismo per compilare un pacchetto senza fallire. La VM? reinstallata altre 15 volte, con i soliti problemi di rete e la pesantezza che tutto un DE di linux può portare sul notebook di un consulente messo in croce.
Storia già vista.
Ma un giorno, il tuo nuovo Senior ti chiama e ti dice "Installa vagrant e lancialo con questo file di configurazione. Così puoi avere una macchina virtuale pronta per l'uso. La lanci e la fermi da riga di comando e uando non ti serve o non funziona o devi farci altro, la cancelli e ne crei una nuova."
In quel momento capisci di aver compreso il senso dell'universo, e pensi che vorresti ringraziare quei santi di Hashicorp uno ad uno, perché finalmente puoi fare una configurazione, una sola volta e riusarla all'infinito se qualcosa non va. E fai tutto dalla riga di comando. Animale strano che inizi ad apprezzare sempre di più.
La cosa figa è che con uno schiocco di dita è possibile (ram permettendo) tirare su un cluster di macchine virtuali. Già.
Cos'è Vagrant
Vagrant è uno degli attrezzi nel vostro nuovo coltellino svi multiattrezzo.
Vagrant è la risposta ad una domanda che nessuno forse si era posto: come faccio ad avere un ambiente linux, pronto con quello che dico io, senza perderci tempo?
Idempotenza
In informatica, in matematica, e in particolare in algebra, l'idempotenza è una proprietà delle funzioni per la quale applicando molteplici volte una funzione data, il risultato ottenuto è uguale a quello derivante dall'applicazione della funzione un'unica volta.
Non importa quante volte voi eseguiate il provisioning di una vm con vagrant machine. Se scriverete bene il file di configurazione, otterrete sempre lo stesso risultato. L'idempotenza è uno dei principi cardine del DevOps.
Pre-requisiti
- Vbox
- Vagrant
- editor a scelta (VScode)
- terminale a scelta (Windows Terminal)
- un ssd (lavorate pure su un disco meccanico, se avete voglia di soffrire)
Visto che sono particolarmente sfaticato, vi dico che a me piace usare Choco per gestire i miei robi di lavoro:
choco install virtualbox vagrant editor-a-scelta
Al lettore si rimanda la dimostrazione del teorema, l'installazione di Chocolatey, l'installazione dei pacchetti, la creazione di un nuovo repo git chiamato "ubuntu-cluster" ed un paio di riavvii del pc.
Le basi, cazzo
guest
è la macchina virtuale che create.
host
è la macchina fisica, che ospiterà le vm.
qui trovate le immagini "bento" per le vostre vm. Le bento sono create da Chef (altra grossa corp che lavora in ambito DevOps/Operations), sono ottimizzate i nostri scopi di dev e sono opensource
digressione: come vengono create le vm di vagrant? usando un altro programma di Hashicorp, Packer.
i comandi fondamentali:
vagrant init ubuntu/focal64
inizializza un progetto per una vm ubuntu 20. crea un Vagrantfile con le direttive di base.
I vagrantfile sono scritti in Ruby. Quindi sapete già che se volete diventare dei pro, vi tocca imparare quelle 4 direttive per farlo girare.
vagrant up
crea una nuova vm a partire da un Vagrantfile. Oppure avvia la vm, se già esistente nel path in cui lanciate il comando
vagrant ssh
apre una shell ssh nella vm.
Se la vostra vagrantbox condivide un'interfaccia di rete con il suo host, potrete eseguire direttamente un ssh vagrant@ip_guest, password: vagrant. Si risparmia qualche secondo di attesa, il risultato è il medesimo.
vagrant halt
spegne la vm
Comandi un po meno di base
vagrant validate
"interpreta" il Vagrantfile e mostra eventuali errori.
vagrant reload
esegue nuovamente il provision del guest. Non perderete dati, tranne quelli che verranno impattati direttamente dal file di provision scritto da voi.
vagrant suspend
mette in sospensione la vm. Occuperà un po di preziosa memoria sul disco, ma manterrà lo stato delle vostre applicazioni.
vagrant resume
"resuscita" dalla sospensione una vm.
vagrant destroy -f
il potere di uccidere un guest ( -f ) e di eliminare qualsiasi sua traccia dal disco
Playground, struttura
Ora creiamo una vm (o un cluster) con cui poter lavorare. Per Vagrant è sufficiente usare un init o un Vagrantfile per definire una vm. Quando le cose iniziano a richiedere un pelo di operazioni in più, è meglio disaccoppiare gli elementi.
il progetto richiederà poco lavoro
boxes.yml
Il file che conterrà le conf. di base della/e vm
provision.sh
Uno script che verrà eseguito al primo avvio o al reload della/e vm
Vagrantfile
Il file che Vagrant usa per lanciare il provision
Boxes.yml
Definiamo una struttura dati arbitraria che contenga la configurazione delle nostre macchine. Vagrant, o per meglio dire Ruby, vi da la possibilità di leggere un qualsiasi tipo di file dal disco. Ovviamente sceglieremo uno Yaml, perché è facilmente leggibile dall'essere umano. Lo Yaml verrà interpretato in Ruby come una mappa.
---
- hostname: worker1
role: worker1
box: bento/ubuntu-20.04
box_version: 202008.16.0
cpus: 4
memory: 2048
ip: 192.168.57.101
provision: provision.sh
shares:
- host: C:/Users/ozeta/ubuntu-runner/share
guest: /home/vagrant/share
- host: C:/Users/ozeta/ubuntu-runner/work
guest: /home/vagrant/work
autostart: true
- hostname: worker2
role: worker2
box: bento/ubuntu-20.04
box_version: 202008.16.0
cpus: 4
memory: 2048
ip: 192.168.57.102
provision: provision.sh
shares:
- host: C:/Users/ozeta/ubuntu-runner/share
guest: /home/vagrant/share
- host: C:/Users/ozeta/ubuntu-runner/work
guest: /home/vagrant/work
autostart: true
cosa succede?
Nella radice del documento ho dichiarato un array contenente 2 oggetti anonimi. Ogni oggetto corrisponde ad una vm che verrà creata sul nostro host. Per lo scopo di questo esercizio, le vm saranno identiche tranne che per l'indirizzo IP, ruolo e hostname. L'oggetto contiene i seguenti campi:
hostname
è l'hostname che verrà assegnato al guest.
role
è un attributo che potremo riutilizzare nel nostro Vagrantfile.
box
è l'immagine che useremo per creare le nostre vm.
box_version
identifica la versione dell'immagine.
cpus
assegneremo ad ogni vm 4 cpu virtuali.
memory
assegneremo 2 gb di ram ad ogni macchina. Il vostro host dovrà quindi avere 4gb liberi o, nel caso sia necessario, dovete ridurre le dimensioni assegnate.
ip
assegneremo un ip alla macchina, a cui potremo connetterci
provision
una volta che la vm sarà inizializzata, verrà eseguito questo script.
shares
Vagrant configura automaticamente delle cartelle condivise tra host e guest. In questa sezione dichiaro un array, di dimensione arbitraria, di cartelle che voglio condividere tra gli ambienti.
Provision.sh
Questa dimostrazione è una semplice POC, quindi ci basta qualcosa di facile: stampiamo a video una variabile recuperata da Boxes.yml ed installiamo Python3 e pip3
Tutti i comandi vengono eseguiti come root, quindi non sarà necessario usare "sudo" prima dei comandi.
Quando sarà lanciato il provisioner shell, non avremo la possibilità di interagire tramite stdin con la nostra console. quindi dovremo fare molta attenzione a come eseguire i nostri comandi.
apt-get richiede la conferma di installazione dei pacchetti. l'opzione -y disabilita la conferma.
#!/bin/bash
echo "Guest VM IP: ${1}"
apt-get update
apt-get install -y python python3-pip # occhio al -y!
echo "Ciao!"
Vagrantfile
Siamo arrivati finalmente al cuore pulsante della nostra configurazione. Nel vagrantfile faremo una serie di operazioni di routine.
In particolare, verrà dichiarata una private_network. Alla vm guest assegnerò un indirizzo ip statico arbitrario (ad esempio 192.168.57.101) per poter accedere comodamente ai server che in futuro vorrò esporre sulle porte della vm. Verrà creata una interfaccia di rete virtuale.
Sarà possibile raggiungere la vm solo dal pc host, che sarà convinto che la macchina in questione sia un qualsiasi pc sulla rete.
Al contrario, per mettere su rete pubblica la vm, si utilizza una public_network. Usando una rete pubblica sarà necessario, durante il bootstrap della vm, scegliere l'interfaccia di rete fisica. Nel caso si scelga di usare un ip statico, stavolta questo dovrà essere coerente con la nostra configurazione di rete.
Lo ammetto, la sintassi di vagrant è da mal di testa. Ma funziona.
Tentando di essere chiaro prima di leggere il nostro Vagrantfile, questo è quello che ho intenzione di fare:
- Carico il file boxes.yml in una lista
- Per ogni vm contenuta nella lista:
2.1. Verrà creatà una vm usando le specifiche (host, cpu, ram) lette dal file.
2.2. verrà creato una interfaccia di rete privata
2.3. Per ogni cartella "shares", creo una synced_folder
2.4. eseguo il file di provision, passandogli un array di
argomenti (in questo caso contiene solo l'indirizzo ip)
# -*- mode: ruby -*-
# vi: set ft=ruby :
#vagrant 2.2.10
require 'yaml'
boxes = YAML.load_file('./boxes.yml')
Vagrant.configure("2") do |config|
boxes.each do |box|
config.vm.define box['hostname'] do |host|
host.vm.box = box['box']
host.vm.box_version = box['box_version']
host.vm.hostname = box['hostname']
host.vm.provider "virtualbox" do |vb|
vb.memory = box['memory']
vb.cpus = box['cpus']
end
host.vm.network "private_network", ip: box['ip']
box['shares'].each do |share|
host.vm.synced_folder share['host'], share['guest'], type: 'virtualbox' , create: true
end
host.vm.provision "shell", path: box['provision'], :args => [box['ip']]
end
end
end
Tentiamo di capire che succede....
require 'yaml' # importo il modulo yaml
boxes = YAML.load_file('./boxes.yml') # carico il file
Vagrant.configure("2") do |config| # creo un oggetto config con cui opererò i guest
boxes.each do |box| ## ciclo nell'array dei box
...
end
end
Fin qui tutto facile. Avvio il configuratore di Vagrant, che per motivi mistici è alla versione "2". Questo mi alloca un oggetto config, che verrà impiegato più tardi. Fermiamoci un istante per apprezzare la sintassi di Ruby.
Ciclo for sull'array, terzo giorno di scuola.
Entriamo nel dettaglio:
Adesso userò le api di vagrant per una configurazione multi machine e per la configurazione di virtual box
A questo punto posso lavorare sulla singola macchina.
Configurazioni di base, poi rete, poi shared folders, ed infine il lancio del provisioner shell.
Vagrant mette a disposizione una vasta gamma di provisioner
config.vm.define box['hostname'] do |host| # definisco una nuova vm, a cui do come nome il nostro hostname
host.vm.box = box['box'] # indico l'immagine da usare
host.vm.box_version = box['box_version'] # la versione specifica
host.vm.hostname = box['hostname'] # l'hostname
host.vm.provider "virtualbox" do |vb| # qui assegno le risorse fisiche
vb.memory = box['memory']
vb.cpus = box['cpus']
end
host.vm.network "private_network", ip: box['ip'] # imposto il network privato, con ip statico
box['shares'].each do |share| # ciclo sugli shares definiti nel box.yml
host.vm.synced_folder share['host'], share['guest'], type: 'virtualbox' , create: true
end
host.vm.provision "shell", path: box['provision'], :args => [box['ip']] # lancio un provisioner shell, passo un array che contiene l'ip della macchina
end
end
A questo punto non ci resta che provare il tutto.
Un rapido check:
PS C:\Users\ozeta\OneDrive\Desktop\ubuntu-runners> vagrant validate
==> vagrant: A new version of Vagrant is available: 2.2.13 (installed version: 2.2.10)!
==> vagrant: To upgrade visit: https://www.vagrantup.com/downloads.html
Lanciamo il provision.
Possiamo notare come vagrant identifichi la macchina su cui sta lavorando:
PS C:\Users\ozeta\OneDrive\Desktop\ubuntu-runners> vagrant up
Bringing machine 'worker1' up with 'virtualbox' provider...
Bringing machine 'worker2' up with 'virtualbox' provider...
==> worker1: Importing base box 'bento/ubuntu-20.04'...
==> worker1: Matching MAC address for NAT networking...
==> worker1: Checking if box 'bento/ubuntu-20.04' version '202008.16.0' is up to date...
==> worker1: Setting the name of the VM: ubuntu-runners_worker1_1605136965674_39052
==> worker1: Clearing any previously set network interfaces...
==> worker1: Preparing network interfaces based on configuration...
worker1: Adapter 1: nat
worker1: Adapter 2: hostonly
==> worker1: Forwarding ports...
worker1: 22 (guest) => 2222 (host) (adapter 1)
==> worker1: Running 'pre-boot' VM customizations...
==> worker1: Booting VM...
==> worker1: Waiting for machine to boot. This may take a few minutes...
Ad un certo punto sarà loggata l'attività del nostro provisioner shell:
==> worker1: Running provisioner: shell...
worker1: Running: C:/Users/ozeta/AppData/Local/Temp/vagrant-shell20201112-16244-140103d.sh
worker1: Guest VM IP: 192.168.57.101
worker1: Hit:1 http://archive.ubuntu.com/ubuntu focal InRelease
worker1: Get:2 http://security.ubuntu.com/ubuntu focal-security InRelease [107 kB]
worker1: Get:3 http://archive.ubuntu.com/ubuntu focal-updates InRelease [111 kB]
worker1: Get:4 http://archive.ubuntu.com/ubuntu focal-backports InRelease [98.3 kB]
worker1: Get:5 http://security.ubuntu.com/ubuntu focal-security/main i386 Packages [153 kB]
worker1: Get:6 http://security.ubuntu.com/ubuntu focal-security/main amd64 Packages [367 kB]
...
worker1: Ciao!
...
worker2: Setting up gcc-9 (9.3.0-17ubuntu1~20.04) ...
worker2: Setting up libpython3-dev:amd64 (3.8.2-0ubuntu2) ...
worker2: Setting up libstdc++-9-dev:amd64 (9.3.0-17ubuntu1~20.04) ...
worker2: Setting up gcc (4:9.3.0-1ubuntu2) ...
worker2: Setting up python3-dev (3.8.2-0ubuntu2) ...
worker2: Setting up g++-9 (9.3.0-17ubuntu1~20.04) ...
worker2: Setting up g++ (4:9.3.0-1ubuntu2) ...
worker2: update-alternatives:
worker2: using /usr/bin/g++ to provide /usr/bin/c++ (c++) in auto mode
worker2: Setting up build-essential (12.8ubuntu1.1) ...
worker2: Processing triggers for libc-bin (2.31-0ubuntu9) ...
worker2: Processing triggers for man-db (2.9.1-1) ...
worker2: Processing triggers for mime-support (3.64ubuntu1) ...
worker2: Ciao!
Risultato
Adesso abbiamo creato 2 nuove vm, che possiamo pilotare sia nella maniera tradizionale, dal pannello di Vbox, che tramite i comandi di vagrant.
Lanciando vagrant ssh
, vagrant non saprà su quale dei 2 nodi avviare la console. dovremo quindi aggiungere la destinazione:
vagrant ssh worker1
PS C:\Users\ozeta\OneDrive\Desktop\ubuntu-runners> vagrant ssh worker1
Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-42-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Wed 11 Nov 2020 11:33:32 PM UTC
System load: 0.0 Processes: 131
Usage of /: 2.4% of 61.31GB Users logged in: 0
Memory usage: 8% IPv4 address for eth0: 10.0.2.15
Swap usage: 0% IPv4 address for eth1: 192.168.57.101
121 updates can be installed immediately.
54 of these updates are security updates.
To see these additional updates run: apt list --upgradable
This system is built by the Bento project by Chef Software
More information can be found at https://github.com/chef/bento
vagrant@worker1:~$
Enjoy the ride, e alla prossima puntata 🤠
Posted on November 11, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.