# Onderwerpen van de super demo
* Mini-omschrijving XS4ALL voice platform
* Waarom LXD?
* Doel van de demo: LXD + Ansible + Galera cluster
* Installatie VM's voor de demo
* Netwerktopologie
* LXD clustering vs LXD remotes
* De Ansible container
* Ansible-vault voor opslag secrets
* Dynamische inventory
* Het gebruik van facts
* Playbook roles structuur and handige aliases
* Container bootstrapping vanuit Ansible
* Installatie van het Galera cluster
* Rolling upgrade Galera cluster
# Mini-omschrijving XS4ALL voice platform
* een aantal hardware LXD hosts, met tig geheugen en tig CPU
* verspreid over meerdere lokaties
* alle applicaties draaien in LXD containers op deze hosts
* naasts deze hosts zijn er nog een paar SBC clusters, die zorgen
voor goede en veilige interconnectie met externe partijen
(onze uplink naar het KPN netwerk en alle connecties met
de klantapparatuur / softphones)
Configuration management is een hybride setup:
* puppet beheert een stuk van de LXD host configuratie
* Ansible beheert andere stukken van de LXD host configuratie,
plus alle LXD containers in volledigheid (dus geen 2 kapiteins
op 1 schip, maar een strakke verdeling in taken)
De op te lossen vraagstukken voor Ansible waren vooral:
* Hoe kunnen we geautomatiseerd LXD containers optrekken?
* Hoe beheren we het OS op al die LXD containers vanuit 1 Ansible omgeving?
* Hoe kunnen we development en acceptatie handig inrichten?
Verschillende soorten inzet van Ansible:
* Nieuwe LXD containers optrekken indien nodig (one-shot runs)
* Configuratiemanagement LXD containers en LXD hosts (one-shot runs)
* Onderhoudstaken op regelmatige basis (periodieke runs in cron)
# Waarom LXD?
* Het platform bestaat uit heel veel verschillende applicaties
* Allemaal in het verleden gebouwd als Ubuntu hosts (hardware en VM's)
* Te veel niet gebruikte resources, containerization was een logische keuze
* Veel van de applicaties hebben ook een veelheid aan scheduled jobs en support scripts
* Statisch netwerk, omdat SBC's geen dynamische config kennen
* Daardoor ook geen hippe autoscaling behoefte
* Migratie naar LXD binnen XS4ALL was strak tijdgebonden (gekoppeld aan einde Edutel)
Doordat de beschikbare tijd gelimiteerd was en LXD de mogelijkheid bood om de
bestaande omgeving vrijwel 1-op-1 na te bouwen op basis van LXD systeemcontainers,
was de keuze voor virtualisatie snel gemaakt.
Het platform omzetten naar bijvoorbeeld Docker was ongetwijfeld mogelijk geweest,
maar dat zou heel veel extra werk hebben opgeleverd en voor extra complexiteit
op de netwerklaag hebben gezorgd.
Jammer? Nee! Met LXD en Ansible hebben we een mooi evenwicht bereikt tussen de
bekende cattle en pets analogie. Het voice platform heeft diverse pets in zich,
maar die kunnen zonder blikken of blozen naar de slachtbank.
# Installatie VM's voor de demo
* 2 VM's: sidn-demo-01, sidn-demo-02
* virtual machine met 2 KVM netwerken gekoppeld:
* NAT netwerk: 192.168.56.0/24, gateway/DNS 192.168.56.1
* Host-only netwerk: 10.0.0.0/24
* Ubuntu 18.04 LTS
#### Host-only netwerk
Het NAT netwerk is standaard beschikbaar, maar het host-only netwerk moest ik
nog met de hand aanmaken.
```text
# sudo apt-get install bridge-utils
# vi /etc/network/interfaces (onderstaande daaraan toegevoegd)
auto br-host
iface br-host inet static
address 10.0.0.1
netmask 255.255.255.0
pre-up brctl addbr br-host
post-down brctl delbr br-host
# echo 'host-only' > /tmp/net.xml
# virsh net-define /tmp/net.xml
# virsh net-start host-only
# virsh net-autostart host-only
# virsh net-list --all (om te controleren of het er goed uitziet)
Name State Autostart Persistent
------------------------------------------------
nat active yes yes
host-only active yes yes
```
#### Upgrade na installatie
```text
sidn-demo-0X# apt update && apt upgrade -y
```
#### LXD snap-versie installeren ipv apt-versie
Standaard gebruikt 18.04 een apt-package voor LXD. Deze is prima bruikbaar,
maar voor een meer up-to-date feature set is het beter om de snap
installatie te gebruiken. Snap is op dit moment de aanbevolen method
voor het draaien van LXD.
```
sidn-demo-0X# snap refresh
sidn-demo-0X# snap install lxd
sidn-demo-0X# lxd.migrate ("no" voor automatisch verwijderen LXD)
sidn-demo-0X# apt remove --purge lxd lxd-client (want zo zie ik wel hoe ver deze is)
```
Om te zorgen dat alle tooling lxc goed kan vinden:
```
sidn-demo-0X# ln -s /snap/bin/lxc /usr/bin/lxc
```
#### LXD initialiseren
Voor deze demo:
```text
sidn-demo-0X# lxd init
> clustering: no
> storage pool: yes, default, dir
> connect to MAAS: no
> network bridge: no
> available over the network: yes, all interfaces, port 8443, password
```
Op productie doen we het (uiteraard) anders:
* ZFS als filesystem
* "Available over network" alleen op het management VLAN
# Netwerktoplogie
Belangrijke keuze qua netwerk: elk netwerk interface in de LXD host
is ondergebracht in een bridge interface.
* Elke container kan simpel op elk benodigd netwerk "ingeplugd" worden
door een interface van de container aan de bridge toe te voegen.
* Elke container is daarmee ook direct exposed op dat netwerk.
* Containers kunnen ook containers op een andere LXD host bereiken,
mits het layer 2 netwerk doorgetrokken is uiteraard.
* Containers kunnen eenvoudig naar een andere LXD host worden verplaatst,
mits de nieuwe host dezelfde netwerkbridges heeft geconfigureerd.
__Praktisch gezien lijkt dit op hoe je het netwerk met hardware hosts en
switches zou regelen__
#### Netwerk bridges op de LXD hosts
Tijdens lxd init maken we niet de standaard lxdbr0 bridge aan.
Die bridge is alleen voor intern gebruik binnen de host. Wanneer
je services beschikbaar wilt stellen aan andere systemen, dan moeten
daar trucs voor uitgehaald worden (vgl. met Docker/Traefik).
In plaats daarvan worden de bridges door onszelf geconfigureerd in Netplan.
Netplan-config op productie ziet er ongeveer zo uit:
```yaml
network:
version: 2
ethernets:
eno1: {} # management network (untagged)
eno2: {} # SIP network (SIP signalling & services, tagged)
eno3: {} # public interface (untagged)
vlans:
# SIP core: proxy & SBC signalling
vlan.6:
id: 6
link: eno2
# SIP applications (voicemail, mediaserver, class5 services)
vlan.9:
id: 9
link: eno2
bridges:
br-mgmt:
dhcp4: no
dhcp6: no
interfaces: ["eno1"]
addresses: ["172.17.4.10/24"]
br-public:
dhcp4: no
dhcp6: no
interfaces: ["eno3"]
gateway4: 194.109.16.129
addresses: ["194.109.16.132/26"]
nameservers:
addresses: ["194.109.6.66", "194.109.9.99"]
br-sipcore:
dhcp4: no
dhcp6: no
interfaces: ["vlan.6"]
addresses: ["10.160.64.132/24"]
br-sipapp:
dhcp4: no
dhcp6: no
interfaces: ["vlan.9"]
addresses: ["10.116.1.132/24"]
```
Met een dergelijke setup is het heel eenvoudig om de LXD containers
beschikbaar te maken binnen bepaalde netwerksegmenten. De containers kunnen
een of meer netwerk interfaces krijgen, die verbonden kunnen worden met de
benodigde netwerken. Dat zien er dan bijvoorbeeld zo uit (afwijkende
bridge-namen ivm de ruimte):
```text
GATEWAY
|
|
+-------------------------+------+----------public--+
| | |
| +-------------------------+----------SIP------------+
| | | | | |
| | +-------------------------+---mgmt------------------+
| | | | | | | | |
| | | | | | | | |
+---O------O------O----+ +---O------O------O----+ +---O------O------O----+
| eno3 eno2 eno1 | | eno3 eno2 eno1 | | eno3 eno2 eno1 |
| | | | | | | | | | | | | | |
| | | | | | | | | | | | | | |
| br3 br2 br1 | | br3 br2 br1 | | br3 br2 br1 |
| | | \ | | | | | | | | | |
| | | | | | | \ | | | | | |
| CONTAINER1 | | | | `--CONTAINER3 | | | CONTAINER4 |
| | | | | | | | |
| CONTAINER2 | | | | CONTAINER5 |
| | | | | |
+----------------------+ +----------------------+ +----------------------+
LXD Host 1 LXD Host 2 LXD Host ... n
```
Hier is bijvoorbeeld CONTAINER1 aangesloten op het public en het SIP netwerk, terwijl
CONTAINER2 aangesloten is op SIP en mgmt. CONTAINER4 is alleen aangesloten op het
SIP netwerk, en kan via dat netwerk op layer 2 CONTAINER1 en CONTAINER2 ook bereiken.
__Voor de demo gebruiken we een iets simpeler opzet:__
```yaml
network:
version: 2
ethernets:
enp1s0: {}
enp6s0: {}
bridges:
br-public:
dhcp4: no
dhcp6: no
interfaces: ["enp1s0"]
addresses: ["192.168.56.150/24"] # en 151 op de andere host
gateway4: "192.168.56.1"
nameservers:
addresses: ["8.8.8.8", "9.9.9.9"]
br-demo:
dhcp4: no
dhcp6: no
interfaces: ["enp6s0"]
addresses: ["10.0.0.150/24"] # en 151 op de andere host
```
Bridge br-public is aangesloten op een bridge die NAT netwerk verzorgt op
de VM host en daarmee gebruikt kan worden om via NAT naar het internet te
komen. Bridge br-demo zit aangesloten op een host-only bridge op de VM host
en heeft dus de rol van een losse, niet-connected netwerkswitch.
In een plaatje:
```text
KVM host
192.168.56.1 (gateway + DHCP + DNS)
|
|
+------192.168.56.0/24-----------+
| |
| private |
| +----bridge----+ |
| | 10.0.0.0/24 | |
| | | |
+------O------O--------+ +--O----------O--------+
| enp1s0 enp6s0 | | enp6s0 enp1s0 |
| | | | | | | |
| br-public br-demo | | br-demo br-public |
| | \ / | | | | \ / | |
| | \/ | | | | \/ | |
| | /\ | | | | /\ | |
| ansible-01 \ | | | galera-02 \ | |
| | | | | \ | |
| galera-01 | | galera-03 |
+----------------------+ +----------------------+
KVM guest: sidn-demo-01 KVM guest: sidn-demo-02
```
#### Netwerk interfaces op de LXD containers
Met bovenstaande setup is het dus mogelijk om een LXD container netwerk
interfaces te geven die "ingeplugd" worden in een of meer van de br-\* bridges.
Een SIPproxy server bijvoorbeeld, krijgt drie netwerk interfaces in drie
verschillende bridges:
* br-mgmt: voor host management, o.a. Ansible aansturing
* br-public: op een SIPproxy voor SSH toegang en OS updates
* br-sipcore: voor SIP signalling verkeer richting SBC's
Om binnen een container het herkennen van de netwerk interfaces zo simpel
mogelijk te houden, wordt de naamgeving van de bridges gevolg. Bijvoorbeeld
een interface dat in bridge "br-aap" wordt geplugd, heet dan "if-aap".
Veel handiger dan "eth0" o.i.d.
Op de SIPproxy zul je dan ook de interfaces if-mgmt, if-public en if-sipcore
terug kunnen vinden.
__Interfaces vanuit LXD profielen__
Om bij het optrekken van een LXD container de benodigde netwerk interfaces
aan te maken, wordt gebruik gemaakt van profielen. Er zijn verschillende
profielen gemaakt voor verschillende netwerkbehoeftes:
```text
# lxc profile list
+---------------+---------+
| NAME | USED BY |
+---------------+---------+
| ansible-acc | 1 |
+---------------+---------+
| default | 11 |
+---------------+---------+
| sipproxy | 2 |
+---------------+---------+
| voicemail | 2 |
+---------------+---------+
| voiceservices | 2 |
+---------------+---------+
```
Hier het stuk van het profiel wat voor het netwerk van de hierboven beschreven
SIPproxy container wordt gebruikt:
```text
productie# lxc profile show sipproxy
config:
---8<-------
devices:
if-mgmt:
name: if-mgmt
nictype: bridged
parent: br-mgmt
type: nic
if-public:
name: if-public
nictype: bridged
parent: br-public
type: nic
if-sipcore:
name: if-sipcore
nictype: bridged
parent: br-sipcore
type: nic
---8<-------
```
Noot:
Voor het voice platform is dit een handige setup. Uiteraard kan elke mogelijk
setup worden gemaakt op basis van de behoefte. Als de behoefte alleen maar
een lokaal werkende bridge met private IP space was, dan zou ik het geheel nog
steeds wel handmatig inrichten zoals hierboven, alleen dan met een loopback
bridge met een duidelijk naam als "br-local" of "br-private".
#### Gebruik van LXD profiles voor de netwerk device configuratie
Op basis van de gevolgde lxd init methode, is er al een default profiel aangemaakt
voor LXD. Deze is heel erg basaal, omdat we geen netwerk bridge hebben geconfigureerd:
```text
sidn-demo-0X# lxc profile show default
config: {}
description: Default LXD profile
devices:
root:
path: /
pool: default
type: disk
name: default
used_by: []
```
Het beheer van profielen regelen we primair vanuit Ansible, maar op dit punt
van de demo maak ik met de hand een profiel aan, om te laten zien wat
Ansible hier onder water doet.
```text
sidn-demo-0X# lxc profile create demo
sidn-demo-0X# lxc profile edit demo
config:
user.user-data: |
# cloud-config
package_upgrade: true
packages:
- python3
timezone: Europe/Amsterdam
description: SIDN demo profile
devices:
if-public:
name: if-public
nictype: bridged
parent: br-public
type: nic
if-demo:
name: if-demo
nictype: bridged
parent: br-demo
type: nic
root:
path: /
pool: default
type: disk
sidn-demo-0X# lxc profile show demo
```
In deze config zijn wat cloud-init statements toegevoegd om te laten zien dat
cloud-init te gebruiken is in een profiel. De installatie van Python is handig,
omdat daarmee de container klaar is voor Ansible. Echter, in het voice platform
wordt dit niet zo gedaan. Daar worden wat bootstrapping Ansible recipes gebruikt
om Python op een vers systeem te krijgen. Voordeel is dat de Ansible-methode
ook voor oudere versies van Ubuntu en voor niet cloud-init images te gebruiken is.
Noot:
In de praktijk hebben wij tot op heden alleen maar timezone en netwerkconfiguratie
gepusht met de cloud-init configuratie. En zelfs de netwerkconfguratie slechts
deels, omdat demodeze met de cloud-init van Ubuntu 14.04 nog niet mogelijk was.
# LXD clustering vs LXD remotes
Wij gebruiken __geen__ LXD clustering.
Een cluster zorgt er voornamelijk voor dat je een aantal LXD hosts aan elkaar
koppelt, die onderling gaan uitwisselen welke containers er op de hosts draaien.
Daarna krijg je met bijvoorbeeld "lxc list" niet alleen inzicht in de containers
die op de lokale host draaien, maar ook in alle containers die op alle cluster
hosts draaien.
demo
Het klinkt heel goed: LXD clustering. Functioneel heb ik er echter nog
weinig heil in gevonden voor onze setup. Bovendien heb ik op clustersystemen
regelmatig problemen gezien met het goed werkend houden van het cluster,
door bijvoorbeeld het uit sync raken van de quorum database (gebaseerd op dqlite,
wat een distributed sqlite database is.)
Voordat clustering bestond in LXD liepen wel al wel tegen een probleem aan
dat clustering probeert op te lossen: het werken met containers, verspreid
over meerdere LXD hosts.
Onze aanpak:
* De LXD daemon laten luisteren op het netwerk (zoals bij lxd init al aangezet)
* Je kunt vanuit de ene LXD host een andere LXD host als "remote" toevoegen
onder een bepaalde naam.
* Vanaf dan kunnen lxc commando's de naam van de remote gebruiken om
operaties op de andere host te doen.
* We hebben alle LXD hosts op deze manier kruislings gekoppeld, zodat vanuit
elke host elke andere host aangesproken kan worden.
Om dit op te zetten, zorg dan eerst dat alle LXD hosts remote access actief hebben,
mocht dat nog niet geconfigureerd zijn vanuit lxd init. Doe het volgende op elke host:
```
sidn-demo-0X# lxc config set core.https_address [::]:8443
sidn-demo-0X# lxc config set core.trust_password SuperGeheimGoedWachtwoordHorseStaple
```
Nu kunnen de remotes op elke LXD host worden aangemaakt.
Merk op dat we ook voor de lokale LXD daemon een remote aanmaken.
Dat maakt een aantal zaken vanuit Ansible een stuk simpeler, omdat we dan
vanaf elke willekeurige host bijv `lxc list sidn-demo-01:` kunnen doen en niet
op die bestreffende host `lxc list local:` hoeven te gebruiken
Als wachtwoord gebruiken we uiteraard het bovenstaande wachtwoord.
```
sidn-demo-0X# lxc remote add --accept-certificate sidn-demo-01 192.168.56.150
sidn-demo-0X# lxc remote add --accept-certificate sidn-demo-02 192.168.56.151
```
Vanaf nu kun je vanaf elk van de twee LXD hosts commando's uitvoeren op alle
LXD hosts, bijv:
```
sidn-demo-0X# lxc list -cns sidn-demo-01:
sidn-demo-0X# lxc list -cns sidn-demo-02:
sidn-demo-0X# lxc launch ubuntu:18.04 sidn-demo-02:test
```
__Ja, maar, hoe wordt dit gebruikt dan?__
Het belangrijkste gebruik hiervan, is dat er vanuit cron op elke LXD host een job draait
die een `lxc list` uitvoert op alle remotes. Daarmee wordt een lijst gemaakt waarin
de containers en hun LXD hosts staan. Vanuit die lijst wordt een bestand gemaakt, met
daarin een serie bash aliases voor `lxc exec : bash`.
Als ik wil inloggen op bijvoorbeeld de container `tel-prd-sipproxy-02`, dan kan ik na
het sourcen van het gegenereerde aliassen bestand simpelweg `tel-prd-sipproxy-02` als
commando uitvoeren op een willekeurige LXD host, waarna ik binnenval op een shell op
die container.
# De Ansible container
De Ansible management host is een container op het voice platform zelf. Vanwege de
kip/ei problematiek en het feit dat deze container toch niet steeds opnieuw gebouwd
gaat worden, bootstrappen we deze container met de hand. Hier kan natuurlijk ook een
playbook van worden gemaakt, maar vooralsnog is het minder werk om het zo te doen.
De handigste manier voor het leveren van de netwerkconfiguratie van deze container,
is gebruikmaken van een `user.network-config` in de container configuratie.
```text
sidn-demo-01# vi /tmp/config
version: 1
config:
- type: physical
name: if-public
subnets:
- type: static
address: 192.168.56.160
netmask: 255.255.255.0
gateway: 192.168.56.1
dns_nameservers: [192.168.56.1]
- type: physical
name: if-demo
subnets:
- type: static
address: 10.0.0.160
netmask: 255.255.255.0
```
Bouw de container op en start hem:
```text
sidn-demo-01# H=ansible
sidn-demo-01# lxc init --storage default --no-profiles ubuntu:18.04 $H
sidn-demo-01# lxc config device add $H if-demo nic name=if-demo nictype=bridged parent=br-demo
sidn-demo-01# lxc config set $H volatile.if-demo.hwaddr 00:16:3e:00:00:a0
sidn-demo-01# lxc config set $H user.network-config - < /tmp/config
sidn-demo-01# lxc config show $H
sidn-demo-01# lxc start $H
sidn-demo-01# lxc exec $H bash
ansible# apt update && apt upgrade -y && apt autoclean -y && apt autoremove -y
```
Opmerkingen:
* Voor deze setup zou ook het eerder aangemaakte "demo" profiel gebruikt kunnen
worden. Echter, op het voice platform doen we het op deze manier, omdat we
de profielen vanuit Ansible opzetten en het benodigde profiel daarom niet
noodzakelijkerwijs al bestaat op de LXD host.
* Er wordt een hard MAC-adres gezet. Dat is vooral nodig als je gebruik maakt
van VirtualBox voor het maken van de VM's. De bridge in VirtualBox kan
problemen geven wanneer je eenzelfde host met hetzelfde IP-adres maar een
nieuw MAC-adres opnieuw optrekt. In andere omgeving zou dat MAC-adres
ook weggelaten kunnen worden.
Op de container is nu te zien dat het layer 2 netwerk via de netwerk bridges
ontsloten is vanuit de container:
```
ansible# ip a (laat zien dat er nu een if-demo interface bestaat)
ansible# ping 192.168.56.150 (een ping naar de eigen LXD host)
ansible# ping 192.168.56.151 (een ping naar de andere LXD host)
ansible# ping 10.0.0.150 (een ping naar de eigen LXD host)
ansible# ping 10.0.0.151 (een ping naar de andere LXD host)
```
#### Connectie Ansible -> managed hosts
Even een uitstapje naar de manier waarop we vanuit Ansible verbinding
gaan maken naar de LXD hosts en de LXD containers, voor het uitvoeren
van de nodige commando's.
__Cool, lxc support binnen Ansible!__
In Ansible zit support voor het gebruik van lxc in plaats van ssh voor
het verbinden met de te configureren hosts. Aanvankelijk zijn we daar
100% voor gegaan, door op de Ansible host alle LXD hosts als remotes
toe te voegen en Ansible voor elk uit te voeren commando o een container
lxc te laten gebruken.
__meh__
Helaas bleek dat dit een behoorlijke impact op de snelheid had. Met een
SSH koppeling, kan de SSH verbinding open worden gehouden en kunnen
commando's in rap tempo's naar een host worden gestuurd. De lxc connector
_doet dit helemaal niet_. Voor elk commando wordt een nieuwe connectie opgezet.
Om de zaken te versnellen, gebruiken we daarom inmiddels een hybride setup:
* Management van de LXD hosts wordt volledig met SSH gedaan vanuit
de Ansible container (dit is de standaard methode voor Ansible).
* Het bootstrappen van nieuwe containers en het configureren van het
netwerk van containers worden gedaan door met SSH naar een LXD host
te gaan (middels Ansible delegate). Vervolgens worden van daaruit met
lxc commando's (met name `lxc exec` en `lxc file`) de containers
opgetrokken en geconfigureerd tot het niveau dat de Ansible container
er met SSH bij kan.
* Het verder configureren van containers wordt volledig met de standaard
SSH methode gedaan, direct vanuit de Ansible container.
Het mooie aan deze opzet, is dat we:
* Containers uit het niet automatisch kunnen bootstrappen.
* Kapotte netwerkconfiguratie ook altijd weer kunnen repareren, omdat
de `lxc` commando's geen containernetwerk nodig hebben.
* Missende SSH keys voor Ansible toegang kunnen plaatsen.
* Voor de bulk van de configuratie commando's snel via SSH kunnen versturen.
#### Ansible SSH toegang Ansible -> LXD hosts
Om de LXD hosts te kunnen beheren met SSH, is het nodig dat er een SSH key
wordt gegenereerd en dat deze naar de LXD hosts wordt gekopieerd.
```text
ansible# ssh-keygen -b 2048 -N '' -f /root/.ssh/id_rsa
sidn-demo-0X# mkdir -p /root/.ssh
sidn-demo-0X# chmod 700 /root/.ssh
sidn-demo-0X# touch /root/.ssh/authorized_keys
sidn-demo-0X# chmod 600 /root/.ssh/authorized_keys
sidn-demo-0X# lxc exec sidn-demo-01:ansible cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys
```
Hierna moet het volgende werken:
```bash
ansible# ssh 10.0.0.150 touch .hushlogin
ansible# ssh 10.0.0.151 touch .hushlogin
```
#### Installatie van de Ansible software
De basis infrastructuur staat. Nu kan de benodigde software op de
Ansible host worden geïnstalleerd.
Op het voiceplatform wordt overal gebruik gemaakt van Python3 voor Ansible.
```text
ansible# apt install -y python3 python3-pip
ansible# pip3 install ansible
```
De Ansible configuratie staat in een git repository. Go get!
```text
ansible# apt install -y git
ansible# REPO=https://git.makaay.nl/mauricem/sidn-lxd-ansible-demo.git
ansible# git clone $REPO /etc/ansible
```
De configuratie van Ansible zelf is redelijk eenvoudig:
```text
ansible# vi /root/.ansible.cfg
[defaults]
inventory = /etc/ansible/environments/demo/hosts
vault_password_file = /root/.ansible-vault-password
jinja2_extensions = jinja2.ext.do
[ssh_connection]
pipelining = True
```
# Ansible vault voor opslag secrets
De bovengenoemde `vault_password_file` wordt gebruikt als password file voor
Ansible vault. Met vault is het mogelijk om geheime informatie (normaliter: wachtwoorden)
op te slaan in gecrypte vorm. Het te gebruiken symmetrische wachtwoord is opgeslagen in
de password file.
Bijvoorbeeld:
```text
ansible# echo -n "My very secret 1337 super passw0rd##" > /root/.ansible-vault-password
ansible# chmod 600 /root/.ansible-vault-password
```
Hierna is het mogelijk om met het `ansible-vault` commando bestanden of losse strings
te encrypten. Ansible herkent automatisch dit soort crypted informatie bij het lezen
van de recipe yaml bestanden en decrypt ze automatisch voor gebruik in een run.
Wij maken alleen gebruik van de losse string encryptie op de volgende manier:
```text
# ansible-vault encrypt_string 'SECRET!'
Reading plaintext input from stdin. (ctrl-d to end input)
!vault |
$ANSIBLE_VAULT;1.1;AES256
64663430316333633466353834343736333634666137653034323538316536376435616339313539
3536373866656238393031663665353364346530313933610a616233386234323363346266643262
37613963346134313461396130643939353037323835383132663864636266623736393361393636
3533613531353763610a303633323961366361333165393339343464336335653162663963393837
646
```
De complete string vanaf `!vault ...` kun je in een yaml value plakken, bijv:
```yaml
---
user:
username: john
password: !vault |
$ANSIBLE_VAULT;1.1;AES256
64663430316333633466353834343736333634666137653034323538316536376435616339313539
3536373866656238393031663665353364346530313933610a616233386234323363346266643262
37613963346134313461396130643939353037323835383132663864636266623736393361393636
3533613531353763610a303633323961366361333165393339343464336335653162663963393837
646
```
Om wachtwoorden te genereren voor onze systemen, gebruiken we pwgen op deze manier:
```text
# ansible-vault encrypt_string $(pwgen 32 -c -n -1)
```
Hiermee genereren we direct een crypted string en het plain text wachtwoord is
zelfs onbekend, omdat het nergens op het scherm verschijnt. Als je wilt weten
welk wachtwoord het is geworden, dan is dat uiteindelijk natuurlijk wel binnen
Ansible recipes opvraagbaar.
# Dynamische inventory
Ansible maakt gebruik van een inventory, waarin alle te beheren hosts, een
groepen-indeling en extra attributen voor hosts / groepen zijn oipgenomen.
Waar we al heel snel tegenaan liepen bij het inrichten van de Ansible omgeving
voor het voice platform, was dat de standaard manieren om een inventory in
te richten vrij beperkt waren. Bovendien kriebelde steeds het gevoel dat we
een laag van abstractie misten, omdat alle gegevens door elkaar in bestanden
terecht kwamen, wat het overzicht om zeep hielp.
De oplossing hiervoor was het implementeren van een dynamische inventory.
Het principe is heel simpel: op de plek waar je normaal gesproken het `hosts`
bestand voor Ansible neerzet, zet je nu een script neer dat een inventory
bestand op STDOUT uitpoept. Hiermee ben je in het script volledig in control
voor de manier waarop de inventory wordt gebouwd.
In het voice platform gebruiken we een aantal losse yaml bestanden (node_types,
nodes, networks, lxd, credentials), die elk hun eigen type informatie bevatten
en die door het `hosts` script worden gecombineerd tot een inventory met
zeer verrijkte host entries.
Dezelfde strategie kan natuurlijk ook worden gebruikt om de Ansible inventory
op te bouwen vanuit een database, een webservice die de data aan kan leveren,
etc.
# Het gebruik van facts
Simpel: wij gebruiken geen facts in het voice platform.
Facts zijn leuk, maar het verzamelen van facts voor een Ansible run kost
aardig wat tijd (en ik hou niet van wachten). Bovendien suggereert het
dat ik de nodes in het platform niet volledig zelf onder controle zou
hebben (maar ik ben een control freak).