Error 400 on SERVER: Could not find class “foo” for “node” on “node”

Alweer een Puppetprobleem. Opeens zegt de puppet-agent dat hij een bepaalde module niet kan vinden op de master. Maar, zoals in alle slechte verhalen, de module was er wel. En ze stond op de juiste plaats.

Zowat alles werd onderzocht. De module stond er, was correct, vertoonde geen syntaxfouten; de server had rechten om te lezen en uit te voeren; kortom, er was geen enkele reden waarom het niet zou werken.

En toch

Error: Could not retrieve catalog from remote server: Error 400 on SERVER: Could not find class

Frustrerend.

Na nog wat extra google-fu werd de oplossing gevonden in een ondergesneeuwd antwoord op deze vraag. Was de metadata.json wel correct? En jawel. Ik had de parameters “requirements” en “dependencies” omgedraaid. requirements verwijst naar de verwachte puppetversie en is niet verplicht. De puppetmodules die nodig zijn voor de module staan in dependencies. En helaas bleek ik die te hebben omgewisseld.

Op zich geen probleem (afhankelijkheden worden niet gecontroleerd in onze setup), maar ik had enkel “requirements” en niet “dependencies“. En dependencies is verplicht, en als een verplichte parameter ontbreekt, dan weigert puppet dienst. Met een wel heel verwarrende foutmelding tot gevolg.

PXE-boot voor Ubuntu 16.04 op Ubuntu 14.04

Jaren geleden (2014) heb ik een tweede- of derdehands Dell rackmount-server gekocht voor wanneer “dat eens van pas zou komen”. En dat moment is nu gekomen; het wordt een monitoringserver voor de lokale radio, om te kunnen zien of een bepaald cruciaal onderdeel is uitgevallen vooraleer de radio off-air gaat.

Maar dan moet er natuurlijk een OS op. En zo’n server kan enkel PXE-booten, maar dat kan mijn netwerk dan weer niet zomaar. Dus, om een lang verhaal kort te maken, enkele aanpassingen drongen zich op: de thuis-manusje-van-alles-server moet ook nog eens PXE en TFTP ondersteunen, terwijl de router dat moet kunnen delegeren naar de server.

Netwerk

Te beginnen met het netwerk: de router is een Mikrotik met RouterOS v6.41. En die kan dat. Maar niet op de geijkte manier (met DHCP-options 66 en 67), maar wel zo:

/ip dhcp-server network
add address=192.168.0.0/24 boot-file-name=pxelinux.0 comment=dhcp-lan dhcp-option="" dns-server=192.168.0.1 gateway=192.168.0.1 netmask=25 next-server=192.168.0.200 ntp-server=192.168.0.200

(waarbij 192.168.0.200 het adres is van de server met PXE/TFTP)

Server

En dan nu de server. Op het systeem (serapeum) staat Ubuntu 14.04 LTS, maar uiteraard wil ik Ubuntu 16.04 LTS (de volgende LTS) (eigenlijk wou ik CentOS 7, maar dat is een beetje te veel van het goede).

Vooraleer te beginnen, open de firewall op poort 69 voor udp-verkeer en maak een map aan om de installatiebestanden op te slaan (bijvoorbeeld /srv/tftp).

Installeer dan een paar packages:

apt-get install tftpd-hpa inetutils-inetd syslinux

Geen dhcp-server of nfs-server; DHCP is iets voor de router en NFS gebruiken we niet.

Inetd zal gebruikt worden om de tftp-service te starten, dus moeten er aanpassingen gebeuren in /etc/initetd.conf:

tftp dgram udp wait root /usr/sbin/in.tftpd /usr/sbin/in.tftpd -s /srv/tftp

Volgende werk is de configuratie van tftp (in dit geval tftpd-hpa) zelf. In /etc/default/tftpd-hpa:

TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="--secure"
RUN_DAEMON="yes"
OPTIONS="-l -s /srv/tftp"

/srv/tftp is de map die in het begin is aangemaakt om de installatiebestanden te bevatten. De rest zijn min of meer de standaardwaarden.

Uiteraard moet de service nog ingeschakeld worden en moet ook starten bij het opstarten, voor het gemak.

update-initd --enable BOOT
service tftpd-hpa start

De voorlaatste stap is de configuratie van de bootloader. We (of beter, tftp en PXE) gebruiken hiervoor Syslinux.

Begin met in srv/tftp de map pxelinux.cfg aan te maken, en daarna vesamenu.c32 en pxelinux.0 uit /usr/lib/syslinux naar /srv/tftp te kopiëren. De bestanden uit /usr/lib/syslinux zijn nodig voor de bootloader, terwijl in pxelinux.cfg de configuratie voor de bootloader komt.

Het configuratiebestand van de bootloader heet, zeer creatief, default en bevindt zich in pxelinux.cfg. Het is niet zo ingewikkeld; maar een beetje “klassiek”. Meer informatie is te vinden op de wiki van syslinux, dat dient als bootloader voor PXE.

DEFAULT vesamenu.c32
TIMEOUT 100
PROMPT 0
MENU INCLUDE pxelinux.cfg/PXE.conf
NOESCAPE 1
LABEL Install Ubuntu 16.04 Server
MENU LABEL Install Ubuntu 16.04 Server
kernel Ubuntu/16.04/amd64/linux
append initrd=Ubuntu/16.04/amd64/initrd.gz vga=788
ENDTEXT

Ook ik ben niet zo creatief geweest. Wat tussen LABEL en ENDTEXT staat zijn de menu-entries. Ik heb er maar één, Install Ubuntu 16.04 Server. kernel verwijst naar de Linux-kernel; append voegt parameters toe aan de kernel. initrd verwijst naar de initial ramdisk en vga naar de vga-modus. initrd en kernel zijn bestanden in Ubuntu/16.04/amd64 en zijn gekopieerd van de Ubuntu 16.04 netinstall-images.

Als je meer items in het menu wil kan dat door extra LABELENDTEXT-blokken toe te voegen.

Naast default moet er in pxelinux.cfg ook nog pxe.conf staan, waar we in default ook naar verwijzen (MENU INCLUDE pxelinux.cfg/PXE.conf). Het moge duidelijk zijn dat die entry niet case-sensitive is.

pxe.conf bevat niet veel interessants:

MENU TITLE PXE Server
NOESCAPE 1
ALLOWOPTIONS 1
PROMPT 0
MENU WIDTH 80
MENU ROWS 14
MENU TABMSGROW 24
MENU MARGIN 10
MENU COLOR border 30;44 #ffffffff #00000000 std

Volgende (en laatste) stap is het kopiëren van de kernel en initrd van de minimal-installation-image van Ubuntu 16.04. Download de ISO, mount hem en kopieer linux en initrd.gz vanuit de root van de CD naar de installatiemappen. Zorg ervoor dat de locatie dezelfde is als die in pxelinux.cfg/default, anders verwijst dat bestand naar iets wat niet bestaat. En dan zal het niet werken.

Finale

Dat was het eigenlijk. Als alles goed geïnstalleerd en geconfigureerd is, de firewall open staat en de computer waar je iets op wil installeren PXE-booting ondersteunt, dan zou het moeten werken. Veel plezier!

(Een echte systeembeheerder zou natuurlijk Kickstart en/of preseed configureren naast PXE-boot, maar ik ben niet meteen van plan om een installatiefarm op te zetten.)

mod_rewrite, Let’s Encrypt en een vervelend probleem

Ik ben een fervent aanhanger van Let’s Encrypt, en van het versleutelen van verkeer in het algemeen. Al mijn publieke en private sites hebben dus een certificaat, dat bij voorkeur automatisch vernieuwd wordt.

Maar al een tijdje is er een probleem met één van die sites (een Mattermostserver achter een Apache reverse proxy), waarbij het hernieuwen van het certificaat (via cron) om de één of andere reden faalt. Op zich was er met DNS en de configuratie van de webserver niets mis: die was identiek aan alle andere systemen.

Het enige verschil was dat er een groot aantal RewriteRule‘s geconfigureerd stonden, met daarin een aantal catch-all-regels. Nu is het zo dat ik altijd een RewriteRule toevoeg voor Let’s Encrypt, om alle aanvragen voor de acme-challenge-token op een centrale plaats te bewaren:

RewriteRule ^/\.well-known/acme-challenge/(.*)$ /var/letsencrypt/.well-known/acme-challenge/$1

Door een te snelle lezing van de documentatie was ik vergeten dat Apache alle regels die matchen uitvoert, niet alleen de eerste. Hierdoor werden alle aanvragen doorgestuurd naar de achterliggende Mattermostserver, die, terecht, antwoordde met 403 Forbidden. En dan werkt Let’s Encrypt natuurlijk niet.

De oplossing staat gelukkig ook in de documentatie:  de [L]-flag zorgt ervoor dat de huidige regel meteen ook de laatste regel is die wordt geïnterpreteerd.

RewriteRule ^/\.well-known/acme-challenge/(.*)$ /var/letsencrypt/.well-known/acme-challenge/$1 [L]

Et voilà, alle requests van Let’s Encrypt worden meteen correct doorgestuurd, en de andere regels worden zelfs niet meer bekeken. En ik heb mijn certificaten terug!

Environment variables in wsgi-applicaties

Soms heb je een environment variable nodig in een wsgi-applicatie, zoals bijvoorbeeld LD_LIBRARY_PATH omdat een bepaalde C-bibliotheek niet beschikbaar is.

De normale manier om dat te doen is via mod_env en SetEnv in de VirtualHost-definitie, maar voor wsgi-applicaties werkt dat niet, omdat die gestart worden voor de eerste request. SetEnv wordt enkel uitgevoerd bij de eerste request naar een site.

Je moet die variables meegeven aan het apache (httpd)-proces zelf, met dien verstande dat ze dan voor alle websites en webapplicaties beschikbaar zijn (wat misschien niet de bedoeling is). Op Ubuntu-gebaseerde systemen moet dat in /etc/apache2/envvars, op RedHat-gebaseerde systemen gebruiken /etc/sysconfig/httpd:

LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/opt/apa/lib"

Een procesherstart later zou de variable moeten beschikbaar zijn.

ObjectId & PyMongo

Het is een beetje weggestopt in de documentatie, maar het staat er gelukkig wel in. Wanneer je een document uit een mongodatabase wil halen op basis van zijn _id, dan moet je dat id eerst omzetten naar een ObjectId, anders krijg je onbegrijpelijke “Document not found“-foutmeldingen.

Alweer een uur gespaard!

Puppetmodules in RubyMine

In RubyMine, een uitstekende IDE uit de Jetbrainsstal, kan je Puppetmodules ontwikkelen (de taal is ondersteund), maar je moet enkele dingen in het achterhoofd houden.

Zo eentje heb ik vandaag ontdekt. Opdat RubyMine automatisch de naam van variabelen, functies en klassen uit jouw module zou aanvullen, moet je ervoor zorgen dat ze op één of andere manier door heeft dat je een module aan het ontwikkelen bent.

De oplossing is eenvoudig en elegant: voeg een metadata.json-bestand toe en zorg ervoor dat de structuur van de module overeen komt met de documentatie.

Een kleine moeite, een wereld van verschil.

mysql op Ubuntu 16.04

Groot was de (mijn) consternatie toen ik probeerde om op een blauwe maandag (het zal eerder een zaterdag geweest zijn, maar goed) een database aan te maken voor één of ander project. Normaal is dat niet zo moeilijk: inloggen als root, database aanmaken et voilà, Bob’s your uncle!

Maar niet dus. Hoewel ik het mij niet kon herinneren, bleek de mysql server al geïnstalleerd te zijn en, nog vreemder, er was een rootwachtwoord ingesteld. Nu ben ik, als goede systeembeheerder, nogal paranoïde, en was het dus niet onmogelijk dat ik in het verleden het wachtwoord toch had ingesteld.

Maar een goede systeembeheerder documenteert, en het ingestelde wachtwoord kwam niet overeen met het door mij gedocumenteerde mysqlwachtwoord (ik gebruik Keepassxc als wachtwoordbeheerder). Allemaal wreed vreemd.

Een mysqlwachtwoord opnieuw instellen is niet zo moeilijk, in principe, maar in dit geval wou het maar niet werken. Na een lange avond zoeken (u wil het echt niet weten), bleek deze lijn in /var/log/syslog (en niet in /var/log/mysql/error.log trouwens) het antwoord te bevatten:

[Warning] 'user' entry 'root@localhost' has both a password and an authentication plugin specified. The password will be ignored.

Tussen Ubuntu 14.04 (mijn vorige versie) en Ubuntu 16.04 (de huidige) is de standaardauthenticatieplugin van MariaDB op Ubuntu veranderd naar unix_socket. En daarom lukte het niet om het wachtwoord te wijzigen, of in te loggen met het nieuwe wachtwoord.

Om alsnog toegang te krijgen tot de server, moet u simpelweg als root (de Linuxgebruiker, niet de mysqlgebruiker) mysql -u root uitvoeren:

sudo mysql -u root

Je kan, en ik citeer, niet meer inloggen als een andere gebruiker wanneer die plugin geactiveerd is voor een bepaalde gebruiker; dus enkel root kan inloggen als root.

U moet het maar weten. Of de release notes lezen natuurlijk …