Een aantal weken geleden hebben we hier op linuxmag.nl uitgelegd hoe je met Puppet pakketten installeert, configuratiebestanden aanpast en services beheert. Tijd voor de volgende stap: je code voorbereiden om uit te rollen naar meerdere servers!

Vorige keer hebben we ons voorbeeld om Samba te installeren, te configureren en te starten bewust eenvoudig gehouden. Op zich is dit wel goede Puppet-code, maar het is eigenlijk téspecifiek geschreven. Dat is geen probleem als je maar één systeem wilt beheren en ook niet onmiddellijk aan uitbreiding denkt. Maar de ware kracht van Puppet komt pas naar boven als je er meerdere systemen mee beheert. En dan volstaat onze voorbeeldcode niet meer. Je wilt toch niet hetzelfde smb.conf-bestand op elke server zetten? En de code om Samba te starten werkt ook enkel op Debian/Ubuntu-systemen, want op Fedora/Red Hat heet die service ‘smb’ en niet ‘samba’. Kortom, er bestaan heel wat verschillen tussen systemen, die we eigenlijk moeten opvangen in onze code.

Facts

Om dat mogelijk te maken, beschikt Puppet over een aantal zogenaamde ‘facts’. Facts zijn de meest kenmerkende eigenschappen van jouw systeem, zowel van het besturingssysteem als van de hardware. Denk bijvoorbeeld aan de geïnstalleerde distributie, de netwerkconfiguratie of de hoeveelheid geheugen. Veel configuratieverschillen zijn immers terug te brengen tot dergelijke kenmerken. Je vraagt de beschikbare facts van jouw systeem op met het commando ‘facter’. Weet je precies welk fact je nodig hebt, geef dan de naam ervan mee als argument. In afbeelding 1 zie je enkele voorbeelden.

 

 

Templates

Facts zijn op meerdere manieren bruikbaar in je Puppet-code, bijvoorbeeld in templates. Templates gebruik je in plaats van statische configuratiebestanden (zoals het smb.conf-bestand in ons voorbeeld) om minimale verschillen tussen systemen op te vangen. Een template is (althans in zijn meest eenvoudige vorm) niet meer dan een configuratiebestand, waarvan je bepaalde delen vervangt door variabelen. De precieze inhoud van die variabelen laat je dan door Puppet invullen aan de hand van facts. Even kort uitleggen hoe dat precies in zijn werk gaat. Verplaats om te beginnen jouw configuratiebestand van /etc/puppet/files naar /etc/puppet/templates. Dat bestand geef je voor de duidelijkheid de extensie .erb.Op deze manier weet je meteen dat dit een template is en geen kant-en-klaar configuratiebestand. In Puppet-templates gebruik je de syntax van ERB, Ruby’s templating-systeem (Puppet zelf is in Ruby geschreven). Laat je niet afschrikken door het feit dat je geen Ruby kent, want de basismogelijkheden van ERB zijn erg eenvoudig te gebruiken.

 

In je definitie van het smb.conf-bestand (in /etc/puppet/manifests/site.pp) vervang je nu de source-optie (met verwijzing naar het bestand in /etc/puppet/files) door de content-optie met de template()-functie.  In het template zelf vervang je vervolgens alle dynamische gedeeltes van het configuratiebestand door de gepaste facts. ERB-code moet je steeds tussen twee tags plaatsen. Om de waarde van facts in te vullen, gebruik je volgende tags: <%= en %>. Ook moet je nog een @ vóór de namen van de facts plaatsen. De tags <% en %> (dus zonder =teken bij de eerste tag) dienen dan weer om geavanceerde Ruby-code uit te voeren (bijvoorbeeld conditional tests, loops, enzovoorts). Dat heb je pas nodig als je grotere gedeeltes van het template dynamisch wilt genereren, dus daar gaan we niet verder op in. Je bent trouwens niet beperkt tot facts voor het invullen van je template. Ook variabelen die je zelf definieert in je Puppet-code mag je gebruiken in templates. Dat kan bijvoorbeeld binnen een node-statement om zo voor verschillende machines andere waardes te gebruiken. In afbeelding 2 zie je de benodigde Puppet-code en in afbeelding 3 het resultaat als we die code toepassen.

 

 

 

Conditional statements

We hadden vorige keer reeds vermeld dat Puppet erg goed is in het abstraheren van de verschillen tussen meerdere systemen. Dat kan gaan om verschillende besturingssystemen, verschillende Linux-distributies of zelfs verschillende versies van één bepaalde distributie. Voor Linux-systemen komen vooral de lsb-facts van pas. In afbeelding 4 zie je de uitvoer daarvan voor een Debian-systeem en voor een Red Hat Enterprise Linux-systeem. Die facts kan je nu op verschillende manieren gebruiken om je Puppet-code aan te passen aan het systeem waarop ze wordt uitgevoerd. Puppet beschikt namelijk over verschillende conditional statements. De eerste manier is de zogenaamde ‘selector’. De selector is weliswaar eenvoudig om in te passen in bestaande code, maar overmatig gebruik ervan maakt je code niet erg overzichtelijk. Daarom raden we je aan om voor elke selector een variabele aan te maken en al die variabelen vóóraan in je Puppet-code te plaatsen (zie afbeelding 5).

 

**** INVOEGEN AFBEELDING 4 ****

 

**** INVOEGEN AFBEELDING 5 ****

 

Naast selectors bevat Puppet ook de gekende if- en case-constructies. Die heb je nodig als je meer wilt doen dan enkel de waarde van één variabele instellen. Bijvoorbeeld: een waarschuwing tonen tijdens het uitvoeren van Puppet of Puppet afbreken, indien de code op een niet-ondersteunde distributie wordt uitgevoerd (zie afbeelding 6).

 

**** INVOEGEN AFBEELDING 6 ****

 

Zoals je ziet in onze voorbeelden zijn selectors en case-constructies onmisbaar om je Puppet-code geschikt te maken voor een heterogene omgeving. Zelfs met alleen RHEL- en Ubuntu-servers moet je al heel wat verschillen opvangen: packages en services hebben andere namen, configuratiebestanden staan op een andere locatie, enzovoorts. Vergeet niet dat conditional statements ook handig zijn om variabelen te bepalen, die je nadien in templates gebruikt. Tot slot vermelden we nog even dat Puppet ook reguliere expressies ondersteunt in conditional statements. Voor meer informatie verwijzen we je naar de online documentatie.

 

Modules

Tot nu toe hebben we alle Puppet-code in één bestand geplaatst: /etc/puppet/manifests/site.pp. In een master/agent-setup (zie verder) is dat namelijk het eerste bestand dat door Puppet wordt ingelezen. Maar je begrijpt ook dat het behoorlijk onhandig is om een ellenlang bestand met Puppet-code te onderhouden. Bovendien is je code op die manier niet erg herbruikbaar. Daarom raden we je aan om zo snel mogelijk je Puppet-code onder te verdelen in zogenaamde modules. De eigenlijke code verhuis je naar aparte bestanden onder /etc/puppet/modules, terwijl je in /etc/puppet/manifests/site.pp enkel nog een node-statement bewaart, waarin de gewenste modules worden ingelezen. Eén module kan meermaals toegepast worden (op verschillende nodes) en uiteraard kun je aan elke node ook meer dan één module toekennen. Een module is dus een verzameling code, die op geen enkele manier afhangt van de rest van je Puppet-code. Denk maar aan aparte modules per service (Apache, Samba, MySQL, enzovoorts), modules voor gebruikersbeheer, firewall-regels, enzovoorts. Het voorbeeld voor Samba vorm je als volgt om tot een module:

 

     Maak een directory /etc/puppet/modules/samba aan met daarin de subdirectories ‘manifests’, ‘files’ en templates’.

     Verplaats de configuratiebestanden naar de files-directory en de templates naar de templates-directory.

     Maak een bestand init.pp aan in de manifests-directory en plaats alle code in één class-blok. Vergeet niet om de paden naar bestanden of templates ook aan te passen in die code. In site.pp vervang je nu alle Samba-code in het node-statement door een verwijzing naar de samba-class.

 

Met parameters pas je het precieze gedrag van modules verder aan. In ons Samba-voorbeeld hadden we de variabele $workgroup gedefinieerd met de waarde “HOME”. In de praktijk zal die waarde niet voor elke machine hetzelfde zijn. Daarom voegen we een parameter toe om die variabele aan te passen. Er zijn twee soorten parameters: verplichte en optionele. Verplichte parameters móet je opnemen wanneer je de module gebruikt of Puppet geeft een foutmelding. Voor optionele parameters geef je een standaardwaarde op in de module zelf. In afbeelding 7 zie je onze Samba-module met de optionele paramater $workgroup. De standaardwaarde ‘HOME’ overschrijven we met ‘WORKGROUP’ voor een specifieke node in site.pp.

 

**** INVOEGEN AFBEELDING 7 ****

 

Grotere modules worden (om verschillende redenen) wel eens verder onderverdeeld in submodules. Soms is het gewoon overzichtelijker om de packages of users en groups van een module in aparte bestanden te plaatsen. In andere gevallen heb je te maken met twee modules, die erg op elkaar lijken, zoals bijvoorbeeld een OpenVPN-client en een OpenVPN-server. Dan maak je gewoon een module openvpn met daarin de gemeenschappelijke packages/configuratiebestanden en twee submodules openvpn::client en openvpn::server. In die submodules hoef je dan enkel de specifieke Puppet-code voor de juiste toepassing (client of server) op te nemen. Tot slot vermelden we nog even de Puppet Forge (forge.puppetlabs.com), waar je een heleboel kant-en-klare Puppet-modules vindt. Niet alleen bespaar je veel tijd uit door bestaande modules te gebruiken, maar het is ook erg leerzaam om te zien hoe ze geschreven zijn.

 

Efficiënte code

Met modules heb je al de eerste stap gezet naar meer efficiënte en flexibele Puppet-code, maar daar stopt het niet. We sommen nog enkele geavanceerde mogelijkheden op, die je zeker moet onderzoeken wanneer je serieus aan de slag gaat met Puppet. Zo is het soms handig om je eigen resource types te definiëren. Denk bijvoorbeeld aan een Apache vhost: onder Debian plaats je daarvoor een bestand onder /etc/apache2/sites-available en een symlink onder /etc/apache2/sites-enabled, terwijl je onder RHEL een bestand plaatst in /etc/httpd/conf.d. Ook de inhoud van zo’n vhost-configuratiebestand is vaak hetzelfde (dit los je op met enkele templates) en de juiste service moet natuurlijk herladen worden (apache2 of httpd). Je eigen vhost-resource abstraheert en vereenvoudigt al die zaken, waardoor een nieuwe vhost toevoegen in Puppet een fluitje van een cent wordt. Dan zijn er nog virtuele resources.Dit zijn resources die je definieert zonder ze meteen toe te passen. Zo definiëren we bijvoorbeeld bij onze eigen vhost-resource in Puppet een configuratiebestand voor AWStats. Maar die bestanden worden pas effectief op het systeem gezet als ook de AWStats-module actief is. Op die manier is het erg eenvoudig om AWStats te installeren op een bestaand system. Puppet maakt dan immers de ontbrekende configuratiebestanden voor elke vhost automatisch aan voor ons.

 

Groter geheel

In onze voorbeelden voerden we de Puppet-code steeds lokaal uit. Dat werkt prima als je maar één systeem wilt beheren, maar de meeste Puppet-installaties omvatten meerdere systemen. Eén centrale server bevat dan alle Puppet-code.Dit is dan de master-server. Alle andere servers halen daar hun configuratie op, dit zijn de puppet agents. De voorbeeldcode uit dit artikel moet je soms nog aanpassen om het correct te laten werken in een master/agent-infrastructuur. Absolute paden naar bestanden of templates werken immers niet meer, aangezien de agents die bestanden via HTTP van de master moeten ophalen. Een pad zoals /etc/puppet/modules/samba/files/smb.conf wordt dan puppet:///modules/samba/smb.conf.

 

Uiteraard gebruik je ook een versiebeheersysteem zoals Git om de wijzigingen aan je Puppet-code netjes te documenteren. Het wordt dan erg eenvoudig om configuratiewijzigingen terug te draaien of verschillende versies van je configuratie in verschillende omgevingen toe te passen (test- of productiesystemen). Met Hiera (een optionele Puppet-component) haal je alle variabele parameters uit Puppets .pp-bestanden en plaats je ze in een aparte structuur van tekstbestanden. Die perfecte scheiding tussen data en code maakt het aanpassen van je Puppet-code achteraf eenvoudiger. En tot slot gebruik je MCollective om de Puppet-agent te starten en je wijzigingen uit te rollen naar tientallen, honderden of misschien zelfs duizenden servers. De officiële website van Puppet bevat een schat aan informatie over al die onderwerpen, dus je weet wat je te doen sta