Ansible
Ansible é uno strumento di automazione.
Si utilizza per amministrare server: é stato creato da sviluppatori e amministratori di sistema, per automatizzare procedure in maniera affidabile, semplice ed automatica, compiti originariamente fatti “a mano” dagli amministratori. Questo é anche dovuto dall’esigenza di avere un’architettura altamente scalabile, non solo fisicamente ma anche virtualmente.
Ansible aiuta ad automatizzare la configurazione di server tramite file in formato YAML.
Di default, utilizza OpenSSH.
Compendio di nozioni utili
Qui di seguito si riportano delle nozioni che é bene conoscere.
-vvv
: ogni qualvolta che si esegue un comando Ansible, é possibile visualizzare il log attraverso il flag-v
. Piúv
ci sono, maggiore sará la verbositá del log (verbose mode)- per fare input utente:
vars_prompt:
- name: username
prompt: "What is your username?"
export ANSIBLE_HOST_KEY_CHECKING=False
→ usi questo nella bash per fare in modo che Ansible non controlli la key ogni volta
Vagrant e Virtualbox
L’utilizzo di Vagrant e Virtualbox fanno sí che si possa andare a creare un’ambiente di test locale in maniera molto agevole.
Vagrant fa da tramite tra la riga di comando e il provisioner (nell’esempio Virtualbox): é un intermediario, ed aiuta a creare ambienti di test in maniera piú rapida.
Tutte le macchine di test (locali) sono descritte dal file di configurazione Vagrantfile
.
Per gestire i server virtuali da riga di comando con Vagrant:
vagrant up
: accende le VMvagrant halt
: spegne le VMvagrant destroy
: elimina le VM (fisicamente!)vagrant provision
: lancia il provisionervagrant rsync
: sincronizza la cartella specificata nel Vagrantfile, comune quindi sia alle VM che al localhost
Esempio di Vagrantfile:
Inventory
Ansible utilizza gli inventory per tenere traccia degli host dell’infrastruttura, attraverso i loro indirizzi ip.
Tendenzialmente sono file .ini
, posso anche usare dei file .yaml
.
Si possono specificare dei gruppi, denotandoli con []
Ad esempio:
Per convenzione il nome dei file hosts é hosts.ini
.
Negli inventory, per ogni singolo host possono essere definite delle variabili:
ansible_ssh_port
: per connessioni SSH diverse dalla porta standard di SSH
É buona prassi seguire la regola un inventory per ogni ambiente di produzione (ad esempio):
servercheck/
inventories/
inventory-prod.ini
inventory-cert.ini
inventory-dev.ini
playbook.yml
Struttura se ho modificato il file hosts della macchina locale
Struttura (inventory_test
) se ho modificato il file hosts:
Invece nell’hosts
:
Usare nomi simbolici senza dover modificare il file /etc/hosts
Piu comodo, non bisogna farlo ogni volta che si aggiorna un host… insomma, basta usare questo.
Playbooks
I playbook sono un’insieme di azioni (tasks) da eseguire in sequenza.
Sono scritti in YAML: un linguaggio semplice che é sia human che machine-readable, ancora piú del JSON.
Ogni qualvolta che si esegue un playbook, Ansible raccoglie dei facts sugli host, cioé dei dati utili come che OS stanno usando, memoria libera in quel momento ecc.
Lo fa sempre all’inizio di tutti i playbook. Per velocizzare l’esecuzione del playbook e disabilitarlo: set gather_facts: no
nel playbook principale.
Ad esempio per installare un pacchetto possiamo usare il builtin di Ansible, apt:
Nomi e commenti sono facoltativi, ma é buona prassi metterli in modo tale da rendersi conto cosa succede in fase di debug del playbook.
Puoi importare task da altri file usando import_tasks: nomefile.yml
Puoi importare altri playbook da altri file usando import_playbooks: nomefile.yml
.
Puoi importare tutto quello che vuoi.
Esistono dei task molto basilari chiamati builtin
.
Tutti i builtin dalla docs di Ansible. Includono operazioni come copia, spostamento file, rinomina, crea nuovo utente o gruppo…
Esecuzione
-
in generale:
ansible-playbook playbook.yml -i hosts.ini
-
limitare l’esecuzione di un playbook su un host specifico:
ansible-playbook playbook.yml --limit xyz.example.com
- vedere la lista degli host sul quale andresti a runnare il playbook:
ansible-playbook playbook.yml --list-hosts
- eseguire un playbook con chiave:
- lanciare un playbook sulla macchina locale
# dentro hosts.ini
localhost ansible_connection=local
# da riga di comando
ansible-playbook playbook.yml -i hosts.ini
oppure
ansible-playbook playbook.yml --connection=local
- lanciare un playbook come root
ansible-playbook playbook.yml -b -i hosts.ini
oppure
# inserire password manualmente / richiedere password manualmente
ansible-playbook playbook.yml --connection=local -K
oppure specificare nel playbook become: yes
.
É buona prassi andare a fare un --list-hosts
prima di eseguire un playbook.
SSH
Per usare un playbook devo essere autenticato con SSH, tramite private key.
Per farlo, la private key viene data come parametro a ansible-playbook oppure nell’inventory viene messo il percorso della chiave.
In quest’ultimo caso, l’Inventory risulterá in questo modo:
Facts
Informazioni reperite dai server host remoti e utilizzati come variabili negli ansible playbook.
Tramite il comando:
ci connettiamo all’host e otteniamo una serie di facts in json, di informazioni utili!
Per disabilitare la raccolta di facts: all’inizio del playbook: settare gather_facts: no
all’inizio.
Facts piu utilizzati:
"ansible_os_family":"Debian"
- per usarlo in un playbook o ovunque dovro usare
{{ ansible_os_family }}
: proprio come fosse una variabile. Infatti troveremo ‘Debian’. ansible_default_ipv4['address']
Pre e post tasks
pre_tasks
: operazioni che devono essere eseguite PRIMA ANCORA di tutto il playbook, a prescindere (come unupdate
della cache dei pacchetti o il fetch di chiavi gpg)
Handlers
Gli handlers sono delle operazioni che vengono eseguite a seguito di alcuni eventi.
Funzionano usando il paradigma “notify”: al termine di un’operazione si “risveglia” e si chiama un evento.
Hanno queste caratteristiche:
- sono atomici: vengono eseguiti una e una sola volta alla fine del playbook
- sono condizionali: vengono eseguiti solamente se qualcuno li richiama
- sono diversamente ineluttabili: se il playbook fallisce per una qualsiasi ragione, non vengono eseguiti
- per ovviare a questo problema, si puó usare
--force-handlers
a patto che gli host non vadano offline
- per ovviare a questo problema, si puó usare
Variabili
Come qualsiasi linguaggio, Ansible offre la possibilitá di definire delle variabili.
Essendo i file scritti in yml:
---
variabile: "VALORE"
Niente punti, trattini alti e caratteri speciali, o numeri all’inizio delle variabili e per convenzione si scrivono tutte in minuscolo, niente camelcase.
Tipicamente si trovano nel file vars.yml
, ma possono essere anche definite nei seguenti modi:
- attraverso il
.bash_profile
aggiungendo le linee che servono. Soloshell
peró sará in grado di leggerle, quindi non é una buona prassi:
- name: Add an environment variable to the remote user's shell.
lineinfile:
dest: ~/.bash_profile
regexp: '^ENV_VAR='
line: "ENV_VAR=value"
- name: Get the value of the environment variable we just added.
shell: 'source ~/.bash_profile && echo $ENV_VAR'
register: foo
- name: Print the value of the environment variable.
debug:
msg: "The variable is {{ foo.stdout }}"
- usando la riga di comando usando il parametro
--extra-vars
:
ansible-playbook example.yml --extra-vars "foo=bar"
- usando la sezione vars nei playbook, sia nel caso di variabili singole che di file:
vars:
foo: bar
o
vars_files:
- vars.yml
- tramite inventory
# Host-specific variables (defined inline).
[washington]
app1.example.com proxy_state=present
app2.example.com proxy_state=absent
# Variables defined for the entire group.
[washington:vars]
cdn_host=washington.static.example.com
api_version=3.0.1
Per registrare l’output delle variabili si puó usare la direttiva register
: l’output di un comando andrá a finire nella variabile specificata. Es.
- name: Run a shell command and register its output as a variable
ansible.builtin.shell: /usr/bin/foo
register: foo_result
ignore_errors: true
Per usare una variabile "{{ }}"
(le virgolette sono importanti)
Condizionali: when, register, tags
I task possono essere eseguiti a seguito di particolari condizioni:
-
condizionali classici: `if/then/when
- Es.
when: ansible_facts['os_family'] == "Debian"
- Es.
-
changed_when
andfailed_when
: di solito si usano con gli output (es.changed_when: 'Not found' in out
‘) -
tags: delle stringhe che etichettano un playbook, un role o qualsiasi altra cosa
Precedenza delle variabili
--extra-vars
passed in via the command line (these always win, no matter what).- Task-level vars (in a task block).
- Block-level vars (for all tasks in a block).
- Role vars (e.g. [role]/vars/main.yml) and vars from include_vars module.
- Vars set via set_facts modules.
- Vars set via register in a task.
- Individual play-level vars: 1. vars_files 2. vars_prompt 3. vars
- Host facts.
- Playbook host_vars.
- Playbook group_vars.
- Inventory: 1. host_vars 2. group_vars 3. vars
- Role default vars (e.g. [role]/defaults/main.yml).
Variabili magiche
Variabili speciali, che richiamandole danno accesso a delle informazioni.
Sono comode.
Per approfondire: https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html
Le piu importanti:
{{ hostvars }}
: tutti i server/host presenti e le variabili assegnate a loro{{ inventory_name }}
: su chi sto eseguendo la ricetta? host{{ groups }}
: tutti i gruppi presenti nell’inventory e server/host sotto di loro{{ group_names }}
: tutti i gruppi a cui appartiene host per cui si sta eseguendo il playbook
Cicli
Servono per eseguire task piu volte.
Deve essere usato item, se no, non funziona nulla e il corpo del loop non viene eseguito.
Concetto di foreach (array semplice):
Concetto di foreach (array):
Group vars e host vars
Di default Ansible cerca due cartelle nominate group_vars
e host_vars
:
- nella stessa cartella dell’inventory
- /etc/ansible se usi l’inventory predefinito
group_vars
: è utilizzata per definire le variabili che si applicano a un gruppo specifico di host.
host_vars
: è utilizzata per definire le variabili che si applicano a host specifici. Ogni host ha il suo file di variabili all’interno della directory host_vars
, dove è possibile definire variabili personalizzate.
Es.
Dati sensibili
Password e altri dati sensibili devono essere conservati in maniera adeguata. Per questo si utilizza Ansible Vault, un tool che cripta i file usando la cifratura AES 256.
Funziona in questo modo:
- In un file YAML si definiscono i dati sensibili
- Si criptano i dati con Ansible Vault utilizzando una chiave (password o file)
- Si conserva la chiave in un luogo sicuro e al di fuori della repo
- Ogni qualvolta che bisogna eseguire un playbook, si usa la chiave e si decripta il vault
Criptare un file (ansible-vault chiederá una password):
ansible-vault encrypt vars/api_key.yml
Criptare un file utilizzando un file:
ansible-playbook encrypt main.yml --vault-password-file .vault.pwd
Ruoli
Pacchetti di configurazioni ad-hoc per qualcosa.
Solitamente é consigliabile farli altamente customizzabili per evitare di rimetterci le mani ogni volta.
Includono playbook perlopiú.
Solitamente hanno:
tasks/main.yml
: il playbook principaledefaults/main.yml
: valori di default- Handlers, Files and Templates per quel ruolo
Per dirla con una metafora: é una specie di grande script (main.yml
) che fa qualcosa (es. settare web server)
Prima di riscrivere un role, sempre controllare che gia’ ne esiste uno.
Ruoli cross platform
- se servono uno o due sistemi operativi:
when: ansible_os_family == 'Debian' ...
- in generale vale la regola un playbook per ogni piattaforma
---
- name: Include OS-specific variables.
include_vars: "{{ ansible_os_family }}.yml"
- name: Include OS-specific setup tasks.
include_tasks: setup-{{ ansible_os_family }}.yml
Usare un ruolo
Collection
Una collection é un insieme di ruoli, moduli, plugin e playbook.
Vengono installate per default:
∼/.ansible/collections
/usr/share/ansible/collections
Usare una collection
Moduli
I moduli sono librerie Python che fanno in modo di eseguire determinate operazioni su un qualche nodo.
Quelli di ansible.builtin fanno parte della collezione predefinita.
Builtin importanti
I piu importanti:
systemd
copy
pip
command
import_task
: importa i task da un altro file
Ansible Galaxy
Un servizio dove le persone condividono ruoli e collection.
Per installare qualcosa:
ansible-galaxy role install geerlingguy.apache
Con il classico requirements.yml:
ansible-galaxy
install -r requirements.yml
Sicurezza
- Utilizzare comunicazioni sicure e criptate.
- Disattivare l’utente root e usare sudo.
- Rimuovere il software non utilizzato e aprire solo le porte necessarie.
- Utilizzare il principio del minimo privilegio: permessi corretti su file e cartelle.
- Aggiornare il sistema operativo e il software installato.
- Utilizzare un firewall correttamente configurato.
- Assicurarsi che i file di log siano popolati e ruotati.
- Monitorare i login e bloccare gli indirizzi IP sospetti: fail2ban.
- SeLinux
Consigli e best practises
- usare commenti e nomi
- un playbook non dovrebbe MAI superare le 100 righe
- la modularitá é importante
- prediligere approcci con
import_tasks
oinclude_tasks
oinclude_playbooks
→ importare da altri file - prediligere l’utilizzo di ruoli
- prediligere approcci con
- essere YAML-compliant quando si scrivono file yaml
- ci sono due modi per scrivere un task in Ansible:
- one-line: da preferire in caso di cose rapide che non richiedono una particolare configurazione
- name: Install Nginx dnf: name=nginx state=present
- strutturata: da preferire in generale
- name: Copy ssh. template: src: "files/ssh/authorized_keys" dest: "/home/{{ host_user }}/.ssh" owner: "{{ host_user }}" group: "{{ host_user }}" mode: 0755
- preferire un ambiente di test, anziché la propria macchina
- configurare l’ansible cfg in questo modo:
# ansible.cfg [defaults] nocows = True collections_paths = ./collections roles_path = ./roles
- usare il file
requirements.yml
per definire i requisiti - valutare bene quando si usa un ruolo della community
- ultimo aggiornamento
- quanto viene utilizzato
- semplice da sistemare
Gestione degli errori
Chiavi speciali che modificano l’esecuzione del playbook.
-
default
: se errore, blocca la macchina che da errore, il resto vai -
any_errors_fatal: true
: se si verifica un errore, ferma tutto il parco macchine. Non si mette all’interno dei task. -
ignore_errors: true
se il playbook ha un errorcode diverso da 0, anche se c’e’ un errore lo ignora. Si mette all’interno dei task. -
failed_when
: a una specifica condizione il playbook si ferma