Ansible: server sotto controllo

Se gestite un datacenter, sia esso di poche decine di macchine o di centinaia, vi troverete sicuramente di fronte al problema di tenere allineate e coerenti le configurazioni delle vostre macchine.

Tipicamente vorrete la stessa versione di Mysql su tutte le macchine di database, la stessa versione di java, tomcat o altro nella rete applicativa e apache, php o simili nella fascia di frontend.
A questo si sommano altri pacchetti standard, come le utility da riga di comando, eventuali agent di monitoraggio e chi più ne ha più ne metta.

Gestire la problematica manualmente è semplicemente illudersi di non avere un grosso problema: in breve le vostre macchine saranno disallineate, alcune saranno mancanti di alcuni pacchetti ed altre avranno addirittura versioni incompatibili o in conflitto tra loro.

Ansible risolve efficacemente questo problema, permettendo di generare veri e propri raggruppamenti di hosts in base alle funzionalità o alla rete di appartenenza (come nell’esempio precedente: database, app o frontend) e di allineare con un solo comando e in parallelo tutte le macchine dello stesso gruppo.

Vediamo quindi come fare.
Raggruppiamo innanzitutto le nostre macchine in un file di hosts secondo la sintassi di ansible, fornendo anche alcuni parametri per la connessioni quali la porta e l’utente da usare per la connessione.

[database]
192.168.2.30 ansible_ssh_port=22 ansible_ssh_user=deployuser
192.168.2.31 ansible_ssh_port=22 ansible_ssh_user=deployuser
192.168.2.32 ansible_ssh_port=22 ansible_ssh_user=deployuser

[application]
192.168.1.20 ansible_ssh_port=22 ansible_ssh_user=deployuser
192.168.1.21 ansible_ssh_port=22 ansible_ssh_user=deployuser
192.168.1.23 ansible_ssh_port=22 ansible_ssh_user=deployuser

[frontend]
192.168.0.10 ansible_ssh_port=22 ansible_ssh_user=deployuser
192.168.0.11 ansible_ssh_port=22 ansible_ssh_user=deployuser
192.168.0.12 ansible_ssh_port=22 ansible_ssh_user=deployuser

Su ciascuna macchina l’unica attività necessaria sarà quindi creare l’utente “deployuser” e fornirlo della chiave pubblica ssh della macchina orchestator (quella da cui lanceremo ansible) in modo da garantire la connessione senza bisogno di fornire la password ogni volta.
Se lavorate in un ambiente virtualizzato questa attività può essere inserita in un template da riutilizzare alla creazione di ogni nuovo server per azzerare anche questo effort.

A questo punto decidiamo quali sono i pacchetti-base che vogliamo installare su ciascun gruppo di server.
Supponiamo di voler installare i pacchetti base per poter monitorare il server da centreon (quindi demone SNMP e client NRPE ), i pacchetti necessari per poter utilizzare tutte le funzionalità di Ansible al meglio (rsync, zip, unzip e python-mysql) e un server apache munito di mod_proxy.

Per complicarci un poco la vita supponiamo che le macchine non siano tutte dello stesso tipo, ma mischiamo almeno un po di distribuzioni RPM-based come Centos e altre Deb-based come Ubuntu.
Le differenze tra le due non sono molte, ma basta solo cambiare i nomi dei pacchetti per causare crisi di schizofrenia acuta anche nei sistemisti più pazienti durante le installazioni “miste”.

Facciamo quindi un po di ricerca preventiva e istanziamo due variabili con le liste dei pacchetti in sintassi Yaml:

vars:
pkg_list_deb:
– nagios-nrpe-plugin
– nagios-nrpe-server
– snmp
– snmpd
– python-mysqldb
– openssh-client
– rsync
– zip
– unzip
– apache2
– libapache2-mod-proxy-html
pkg_list_rpm:
– nagios-plugins-nrpe
– nrpe
– net-snmp
– net-snmp-utils
– MySQL-python
– openssh-clients
– rsync
– zip
– unzip
– httpd
– mod_proxy_html

Come potete vedere anche nei pacchetti più comuni le differenze saltano subito all’occhio, ma dopo che avremo una procedura automatica che fa il lavoro sporco per noi tutto sarà molto più semplice.

Ora che la nostra lista della spesa è pronta passiamo all’installazione vera e propria: innanzitutto il nostro playbook dovrà essere informato su quale host operare e del fatto che avrà bisogno di utilizzare sudo per poter installare i pacchetti.
L’apertura del nostro script sarà quindi la seguente (ometto le due liste al punto precedente per sintetizzare)

– hosts: ${deploy_host}
sudo: yes
vars:
pkg_list_deb:
– …
pkg_list_rpm:
– …

Dichiarando l’utilizzo di sudo nell’intestazione lo script sarà informato che tutte le operazioni elencate andranno eseguite tramite superuser,mentre l’host su cui lavorare sarà passato da riga di comando come variabile all’esecuzione del playbook.
Qualora solo alcune operazioni necessitassero di tali privilegi sarebbe comunque stato possibile dichiarare la direttiva sudo puntualmente sui singoli tasks.

Veniamo ora all’esecuzione dei task veri e propri: la prima cosa da fare è far determinare ad Ansible la famiglia di sistema operativo della macchina host e per nostra comodità mostrarcela tramite log.
Niente di più facile, Ansible lo fa già gratis per noi:

– name : Check Family
debug: msg=”Familiy ${ansible_os_family}”

La variabile ansible_os_family è infatti valorizzata da Ansible nel momento stesso in cui si collega alla macchina e esegue il task automatico di “Gathering Facts”.
In questa fase, che vedrete all’inizio dell’esecuzione di qualsiasi playbook (a meno che non decidiate di disattivarla), Ansible scansiona l’host prima di iniziare l’esecuzione dei task veri e propri e mette a disposizione dei passi successivi diverse utili variabili.

Una volta stabilito il nostro sistema operativo, vogliamo che la procedura installi tutti i pacchetti di una delle due liste liste, utilizzando YUM o APT-GET a seconda dei casi.
Per far questo creeremo due blocchi, uno eseguito solo nel caso che la famiglia sia “Debian” l’altro solo in caso di “RedHat”.

– name : install packages – Debian way
apt: pkg=${item} state=present install_recommends=no
with_items: ${pkg_list_deb}
when: ansible_os_family == “Debian”

– name : install packages – RedHat way
yum: name=${item} state=present
with_items: ${pkg_list_rpm}
when: ansible_os_family == “RedHat”

Ciascun task itererà la procedura per ogni elemento della sua lista di competenza, fino al completamento dell’installazione di tutti i pacchetti.

Per concludere la procedura vogliamo che i demoni di snmp e nrpe vengano correttamente riavviati.
Nel primo caso il nome del servizio coincide su RH/Deb, mentre per nrpe dovremo ripetere la doppia procedura condizionale come per l’installazione dei pacchetti:

– name : stop NRPE – Debian way
service: name=nagios-nrpe-server state=stopped
when: ansible_os_family == “Debian”
– name : start NRPE – Debian way
service: name=nagios-nrpe-server state=started
when: ansible_os_family == “Debian”

– name : stop NRPE – RedHat way
service: name=nrpe state=stopped
when: ansible_os_family == “RedHat”
– name : start NRPE – RedHat way
service: name=nrpe state=started
when: ansible_os_family == “RedHat”

– name : stop SNMPD
service: name=snmpd state=stopped
– name : start SNMPD
service: name=snmpd state=started

Il nostro script è ora pronto per essere eseguito, lanciamolo con il seguente comando:

ansible-playbook /etc/ansible/playbooks/servers.yml -e “deploy_host=*” -K   -i /etc/ansible/all-hosts –forks=10

In questo modo diremo ad ansible di eseguire il playbook servers.yml su tutti ( deploy_host=* ) gli hosts presenti nel nostro file di inventory creato precedentemente (-i /etc/ansible/all-hosts).
Per ciascuno di essi la procedura utilizzerà sudo per guadagnare i privilegi di superuser, ma la password ci verrà richiesta una sola volta all’avvio dello script (opzione -K ).
Infine dichiariamo che vogliamo eseguire il processo con un parallelismo di 10 hosts in contemporanea ( –forks=10 ) .

L’output dovrebbe quindi essere qualcosa di questo genere:

# ansible-playbook /etc/ansible/playbooks/servers.yml -e “deploy_host=*” -K -i /etc/ansible/all-hosts
sudo password:

PLAY [*] **********************************************************************

GATHERING FACTS ***************************************************************
ok: [192.168.2.30]
ok: [192.168.2.31]
ok: [192.168.2.32]
ok: [192.168.1.20]
ok: [192.168.1.21]
ok: [192.168.1.23]
ok: [192.168.0.10]
ok: [192.168.0.11]
ok: [192.168.0.12]

TASK: [Check Family] **********************************************************
ok: [192.168.2.30] => {“msg”: “Familiy Debian”}
ok: [192.168.2.31] => {“msg”: “Familiy Debian”}
ok: [192.168.2.32] => {“msg”: “Familiy Debian”}
ok: [192.168.1.20] => {“msg”: “Familiy Debian”}
ok: [192.168.1.21] => {“msg”: “Familiy Debian”}
ok: [192.168.1.23] => {“msg”: “Familiy Debian”}
ok: [192.168.0.10] => {“msg”: “Familiy RedHat”}
ok: [192.168.0.11] => {“msg”: “Familiy RedHat”}
ok: [192.168.0.12] => {“msg”: “Familiy RedHat”}

TASK: [install packages – Debian way] ****************************************
ok: [192.168.2.30] => (item=nagios-nrpe-plugin,nagios-nrpe-server,snmp,snmpd,python-mysqldb,openssh-client,rsync,zip,unzip,apache2,libapache2-mod-proxy-html)
ok: [192.168.2.31] => (item=nagios-nrpe-plugin,nagios-nrpe-server,snmp,snmpd,python-mysqldb,openssh-client,rsync,zip,unzip,apache2,libapache2-mod-proxy-html)
ok: [192.168.2.32] => (item=nagios-nrpe-plugin,nagios-nrpe-server,snmp,snmpd,python-mysqldb,openssh-client,rsync,zip,unzip,apache2,libapache2-mod-proxy-html)
changed: [192.168.1.20] => (item=nagios-nrpe-plugin,nagios-nrpe-server,snmp,snmpd,python-mysqldb,openssh-client,rsync,zip,unzip,apache2,libapache2-mod-proxy-html)
changed: [192.168.1.21] => (item=nagios-nrpe-plugin,nagios-nrpe-server,snmp,snmpd,python-mysqldb,openssh-client,rsync,zip,unzip,apache2,libapache2-mod-proxy-html)
changed: [192.168.1.23] => (item=nagios-nrpe-plugin,nagios-nrpe-server,snmp,snmpd,python-mysqldb,openssh-client,rsync,zip,unzip,apache2,libapache2-mod-proxy-html)
skipping: [192.168.0.10] => (item=nagios-nrpe-plugin,nagios-nrpe-server,snmp,snmpd,python-mysqldb,openssh-client,rsync,zip,unzip,apache2,libapache2-mod-proxy-html)
skipping: [192.168.0.11] => (item=nagios-nrpe-plugin,nagios-nrpe-server,snmp,snmpd,python-mysqldb,openssh-client,rsync,zip,unzip,apache2,libapache2-mod-proxy-html)
skipping: [192.168.0.12] => (item=nagios-nrpe-plugin,nagios-nrpe-server,snmp,snmpd,python-mysqldb,openssh-client,rsync,zip,unzip,apache2,libapache2-mod-proxy-html)

TASK: [install packages – RedHat way] ****************************************
skipping: [192.168.2.30] => (item=nagios-plugins-nrpe,nrpe,net-snmp,net-snmp-utils,MySQL-python,openssh-clients,rsync,zip,unzip,httpd,mod_proxy_html)
skipping: [192.168.2.31] => (item=nagios-plugins-nrpe,nrpe,net-snmp,net-snmp-utils,MySQL-python,openssh-clients,rsync,zip,unzip,httpd,mod_proxy_html)
skipping: [192.168.2.32] => (item=nagios-plugins-nrpe,nrpe,net-snmp,net-snmp-utils,MySQL-python,openssh-clients,rsync,zip,unzip,httpd,mod_proxy_html)
skipping: [192.168.1.20] => (item=nagios-plugins-nrpe,nrpe,net-snmp,net-snmp-utils,MySQL-python,openssh-clients,rsync,zip,unzip,httpd,mod_proxy_html)
skipping: [192.168.1.21] => (item=nagios-plugins-nrpe,nrpe,net-snmp,net-snmp-utils,MySQL-python,openssh-clients,rsync,zip,unzip,httpd,mod_proxy_html)
skipping: [192.168.1.23] => (item=nagios-plugins-nrpe,nrpe,net-snmp,net-snmp-utils,MySQL-python,openssh-clients,rsync,zip,unzip,httpd,mod_proxy_html)
ok: [192.168.0.10] => (item=nagios-plugins-nrpe,nrpe,net-snmp,net-snmp-utils,MySQL-python,openssh-clients,rsync,zip,unzip,httpd,mod_proxy_html)
changed: [192.168.0.11] => (item=nagios-plugins-nrpe,nrpe,net-snmp,net-snmp-utils,MySQL-python,openssh-clients,rsync,zip,unzip,httpd,mod_proxy_html)
ok: [192.168.0.12] => (item=nagios-plugins-nrpe,nrpe,net-snmp,net-snmp-utils,MySQL-python,openssh-clients,rsync,zip,unzip,httpd,mod_proxy_html)

TASK: [stop NRPE – Debian way] ************************************************
changed: [192.168.2.30]
changed: [192.168.2.31]
changed: [192.168.2.32]
changed: [192.168.1.20]
changed: [192.168.1.21]
changed: [192.168.1.23]
skipping: [192.168.0.10]
skipping: [192.168.0.11]
skipping: [192.168.0.12]
TASK: [start NRPE – Debian way] ***********************************************
skipping: [192.168.2.30]
skipping: [192.168.2.31]
skipping: [192.168.2.32]
skipping: [192.168.1.20]
skipping: [192.168.1.21]
skipping: [192.168.1.23]
changed: [192.168.0.10]
changed: [192.168.0.11]
changed: [192.168.0.12]

PLAY RECAP ********************************************************************
to retry, use: –limit @/var/tmp/ansible/servers.retry

192.168.2.30 : ok=7 changed=3 unreachable=0 failed=0
192.168.2.31 : ok=7 changed=5 unreachable=0 failed=0
192.168.2.32 : ok=7 changed=4 unreachable=0 failed=0
192.168.1.10 : ok=7 changed=3 unreachable=0 failed=0
192.168.1.21 : ok=7 changed=5 unreachable=0 failed=0
192.168.1.23 : ok=7 changed=4 unreachable=0 failed=0
192.168.0.10 : ok=7 changed=3 unreachable=0 failed=0
192.168.0.11 : ok=7 changed=5 unreachable=0 failed=0
192.168.0.12 : ok=7 changed=4 unreachable=0 failed=0

Come vedete la procedura ha correttamente distinto le due famiglie di sistemi operativi, installando la lista corretta e riavviano i servizi voluti, il tutto a tappeto su tutte le macchine del nostro inventario e in parallelo.
In poche righe di script abbiamo quindi automatizzato un processo noioso, ripetitivo e a rischio di errori lasciano al sistemista di turno l’unico onere di dover mantenere allineate la lista dei pacchetti necessari e schiacciare un pulsante per vederla propagata su tutto il datacenter.
Non male 🙂

Per ora ci fermiamo qui con questo primo esempio, nel prossimo post cercheremo di creare effettivamente delle liste separate e ragionate a seconda delle tre tipologie di hosts che abbiamo configurato nel nostro file di inventory, facendo in modo che sia Ansible a distinguere le diverse appartenenze e modificare il set di pacchetti da installare. Cercheremo anche di gestire qualche eccezione locale per alcuni server che vadano differenziati dalla procedura comune senza dover riscrivere un nuovo playbook solo per loro.

Pubblicità

Un pensiero su “Ansible: server sotto controllo

  1. Pingback: Ansible: introduzione | Around the Code

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...