Docker-containers voor home- en webservers
Docker-containers voor thuis- en webservers
Containers zijn zo cool dat Microsoft ze in Windows ondersteunt en via zijn cloud aanbiedt. Ze zijn een uitkomst voor ontwikkelaars en cloud-exploitanten, maar heb je ook voordelen van deze techniek als je een home- of webserver hebt? Een doe-het-zelf-experiment ...
Dankzij virtualisatie kun je meerdere besturingssystemen en servers op één rootserver hosten. Met programma's zoals VMware, User Mode Linux, Xen, KVM en Hyper-V maak je virtuele machines waarop je bijvoorbeeld de firewall van je thuisnetwerk, een webserver of een ftp-server laat draaien. Wanneer je al met BSDJails, OpenVZ of in chroot-environments werkt, ben je al bekend met het concept van containers. In dit artikel geven we praktijkvoorbeelden van Docker en zijn containers.
Zodra je meer over Docker wilt weten, kom je allerlei vakjargon tegen met termen als layers, rollback, orchestration en deploy, die vooral voor toepassingen in het groot gelden. Maar wanneer je praktijk- en detailvoorbeelden zoekt die je helpen met het op kleine schaal opzetten van een home- of een webserver, moet je de informatie bijeenschrapen en overal vandaan halen. Daarom proberen we in dit artikel een evenwicht te vinden tussen een praktische inleiding en een begrijpelijke uitleg. Dit is niet meer dan een kennismaking met Docker. Als je Docker productief wilt gebruiken, ontkom je er op den duur niet aan online extra informatie te lezen.
Als basis voor de eerste stappen met Docker gebruiken we een rootserver waarop meerdere virtuele Apache-machines voor onder andere een blog, Owncloud, Git met webinterface en een ftp-server voor het laden van webcontent staan. Al die systemen hebben een SSL-certificaat van Let's Encrypt.
Wanneer je volgens goed Docker-gebruik één service per container aanhoudt, zadel je jezelf op met extra werk. Want waar vroeger een VirtualHost-bestand en een paar muisklikken voldoende waren voor de certificaten, moet je nu meerdere containers onderhouden. Maar wat je aan de ene kant aan extra werk en tijd investeert, win je dankzij kant-en-klare containers weer terug. Op Docker Hub staan namelijk containers met blogsoftware, met Gitweb, Owncloud en zelfs ftp-servers. Je hoeft geen tar-bestanden uit te pakken, geen rechten in te stellen of een installatiehandleiding van de ontwikkelaar te lezen. Omdat de maintainers van de images dat al allemaal voor je hebben gedaan, kom je als
het ware in een gespreid bed. Bovendien is de daadwerkelijke applicatie in de image meestal moderner dan de versie die een distributie als pakket zou hebben.
In werking zetten
Voordat je met images aan de slag kunt, moet je de Docker-omgeving eerst installeren en starten. Dat kan op meerdere manieren, bijvoorbeeld met installatiepakketten voor Windows of macOS. In dit artikel leggen we het uit aan de hand van een Linux-systeem met Debian 9 (Stretch). Omdat Docker geen bijzondere eisen aan de processor stelt, mag de distributie ook in een virtuele machine of op een virtuele server draaien.
Voeg de volgende regel aan de package-repository's in /etc/apt/sources.list toe:
deb https://download.docker.com/linux/— debian stretch stable
Installeer daarna de ondersteuning voor HTTPS-verbindingen in de package-manager, download de openbare PGP-sleutel en voeg die toe aan de lijst van vertrouwde sleutels en installeer Docker tot slot.
apt-get install apt-transport-https apt-get install curl curl -fsSL https://download.docker.com/—
linux/debian/gpg | apt-key add - apt-get update apt-get install docker-ce
Met het volgende commando kom je erachter of Docker werkt:
docker run hello-world
Daarmee start je een minimale container die de tekst 'Hello from Docker!' met wat aanvullende informatie op het scherm toont. Voordat Docker start, downloadt die de image 'hello-world' van de Docker Hub, waar dan een container van gemaakt wordt. Je kunt images dus als een template beschouwen en containers als instantie. Docker geeft elke container die het van een image maakt een kunstmatige naam. Welke dat is, zie je in de laatste kolom van de tabel als je het commando docker ps -a uitvoert, bijvoorbeeld 'zen_khorana'. Dat commando toont alle bestaande containers. Wanneer je de optie '-a' weglaat, worden alleen de actieve containers getoond.
Oriëntatie
Met docker image ls of docker images krijg je een overzicht van alle images die je binnengehaald hebt. In de laatste kolom 'VIRTUAL SIZES' zie je hoeveel schijfruimte elke image lokaal inneemt. De image van het zojuist gestarte hello-world is slechts twee kilobyte groot en bevat alleen een ELF-programma dat de tekst weergeeft. In een image kan meer staan, maar dat hoeft zeker geen volledige Linux runtime-omgeving te zijn. Voor het programma 'helloworld' zijn de kernelinterfaces genoeg.
Er zijn meerdere mogelijkheden om uit te vinden wat er in een image zit. Je kunt hem met docker image save > /tmp/ hello.tar als TAR-bestand opslaan, waarvan je de inhoud – wederom TAR-bestanden – als een ui met lagen kunt uitpakken. Je kunt ook op de naam van een image googelen. Als je naar 'library/hello-world' zoekt, staan op de eerste pagina met resultaten al de belangrijke domeinen hub. docker.com en github.com (aangevuld met de prefix 'docker-library').
De website van Docker Hub (https:// hub.docker.com/_/hello-world) beschrijft de image die je met de docker-run-opdracht hebt opgehaald. Bij dergelijke beschrijvingen staan meestal aanwijzingen voor het gebruik van de image, zoals het instellen van een wachtwoord voor de services die in de image aangeboden worden. Verder staat er op de Docker Hub-site van een image meestal nog een verwijzing naar het projectplatform GitHub, waar je de build-instructies, een bugtracker en meer informatie vindt. Vaak heb je die gedetailleerde informatie later nodig. Officiële images hebben 'library' of 'docker-library' als prefix voor hun naam. 'Officieel' betekent in deze context niet dat de images van Docker zelf komen, maar alleen dat het team gesponsord wordt dat de images naar de Docker Hub uploadt. Het team moet aan de hand van goede voorbeelden tonen wat de bestpractices bij het bouwen van images zijn, de images goed documenteren en eventuele veiligheidsupdates snel in de images verwerken. Maar garanties zijn er niet.
De Docker Hub biedt een speciale service voor geregistreerde gebruikers. Voor die gratis registratie is alleen een e-mailadres nodig en we raden dat ook echt aan. De service controleert regelmatig of de 'officiële' images bekende veiligheidslekken hebben. Alleen teams en ontwikkelaars die voor hun eigen repository op Docker Hub betalen, kunnen die service inkopen. Dat geeft de gebruiker toch meer vertrouwen in hun images.
Nog een laatste opmerking over de bouwplannen op GitHub: daarin kun je zien hoe de image gemaakt is. Het voor het maken van een image centrale bestand Dockerfile beschrijft niet alleen hoe de image in elkaar gezet is, maar ook welke externe bronnen ervoor nodig zijn. Die informatie is niet, of slechts met heel veel moeite, uit een voltooide image te herleiden. Naast de informatie uit Dockerfile en de overige documentatie is de bugtracker een grote bron van kennis. Daarin worden
veelgestelde vragen en problemen beantwoord en opgelost.
Eerste service
Het commando docker search gitweb op de commandline is voldoende om op Docker Hub naar de eenvoudige webserver Gitweb te zoeken waarmee je met een webbrowser in Git-repository's kunt bladeren. Die zoekopdracht levert minstens zeventien resultaten op, maar geen enkele daarvan is een 'officiële' image. Het overzicht heeft drie hulpvolle kolommen. Stars geeft aan hoe andere gebruikers een image beoordelen. Als er OK in Official staat, gaat het om een 'officiële' image en Automated geeft aan of een image automatisch opnieuw gemaakt wordt wanneer de maintainer hem updatet.
Op Docker Hub zie je hoe vaak een bepaalde image gebruikt werd (Pulls). Als je het overzicht nauwkeurig bekijkt, kom je images tegen waarvan de GitHub-pagina's niet meer bestaan of waarvan de inhoud al jaren niet bijgewerkt is. Omdat je die bij voorkeur níet moet gebruiken, houd je een lijstje over van favoriete images die je kunt uitproberen. Want zo snel je ze aan het draaien krijgt, zo snel kom je er ook weer vanaf.
We kiezen de minimale aanpak en gebruiken 'fraoustin/gitweb'. Met docker pull fraoustin/gitweb download je de image. Met docker run -d --name gitweb fraoustin/gitweb maak en start je daar een container van die je de naam 'gitweb' geeft. De webserver in die container werkt standaard met de poorten 80 en 443 voor een webbrowser of Git-client.
Voordat je een verbinding met de webserver kunt maken, moet je eerst een gebruikersaccount en een repository maken. Daar heeft de maintainer van de image eenvoudige scripts voor geschreven die je met Docker aanroept. Met
docker exec gitweb addauth test test_ww
maak je de gebruiker 'test' met het wachtwoord 'test_ww' aan en met
docker exec gitweb addrepos test
maak je een repository met de naam 'test'. Om erachter te komen hoe je de webserver in de container kunt bereiken, gebruik je het commando docker inspect gitweb | more, dat de configuratie van de container gedetailleerd weergeeft. Bijna aan het eind van dat overzicht staan bij Network de netwerkgegevens inclusief het ip-adres dat docker aan de container gegeven heeft.
Zonder verdere tussenkomst geeft Docker private ip-adressen aan alle gestarte containers en koppelt hij ze aan de bridge 'docker0' die tijdens het starten van de Docker-daemon ingericht werd. De Docker-host waarop de Docker-deamon draait, krijgt meestal het eerste ip-adres van het netwerk, bijvoorbeeld 172.17.0.1. De eerste container krijgt dan 172.17.0.2. Je kunt dat adres direct in een webbrowser op de host intypen, als die een grafische gebruikersinterface heeft. Heb je geen grafische interface nodig, dan maak je een SSH-verbinding via een SOCKSproxy (zie het kader 'Bruggen bouwen met SSH en SOCKS-proxy' hiernaast).
Wegwerpmaatschappij
Indien de services van buiten het hostsysteem toegankelijk moeten zijn, moet je de instructies daarvoor al bij het maken van de container meegeven. Je kunt de instellingen daarvoor niet achteraf bij een draaiende container uitvoeren omdat dat tegen de basisprincipes van containers indruist. Containers zijn in principe zonder waarde. Gooi de Gitweb-container weg en start hem opnieuw met
docker rm -f gitweb docker run -d --name gitweb fraoustin/gitweb
Als je met de browser dan weer verbinding met de webserver maakt, leer je een belangrijke les: het lijkt alsof Gitweb de configuratiebestanden vergeten is, want de gebruiker en de repository zijn weg. Dat is geen fout, maar juist de bedoeling. Je moet vóór het starten van een container namelijk aangeven welke gegevens hij permanent moet opslaan en Docker dan laten weten wáár die data opgeslagen moeten worden.
Doe je dat niet, dan maakt de Docker-daemon tijdens de eerste start automatisch één of meerdere volumes voor dergelijke data aan en geeft die dan een naam die uit willekeurige tekens bestaat. Zolang de container niet verwijderd wordt, blijft die de volumes ook na een herstart gebruiken. Door het verwijderen van de container wordt de koppeling verbroken, met als resultaat dat een nieuwe container van dezelfde image een vers volume bevat. Met docker inspect gitweb kom je erach-
ter waar de volumes van een container zich bevinden. Bij een standaardinstallatie staan onder Mount en Source in de map /var/lib/docker/volumes één of meerdere submappen die eindigen op '_data'. Met docker volume ls --filter dangling=true zie je de overbodig geworden volumes en met docker volume prune verwijder je ze.
Om er al voor het starten van een container achter te komen waar de gegevens staan die permanent opgeslagen moeten worden, voer je docker inspect fraoustin/ gitweb uit. Dus niet voor de container, maar voor de image. Zoek dan naar Volumes. In dit voorbeeld zul je zien dat het '/var/lib/ git' is. Als je dat weet, start je Gitweb met een volume dat je zelf een naam geeft:
docker run -d --name gitweb -v gitwebrepos:/var/lib/git fraoustin/gitweb
Docker maakt dan automatisch een volume met de naam gitwebrepos aan door op de host de map var/lib/docker/gitwebrepos te creëren.
In dit voorbeeld is dat nog steeds niet voldoende voor de image fraoustin/gitweb om de toegangsgegevens te herstellen die je via docker exec gitweb addauth had aangemaakt. Die staan in het bestand /etc/nginx/.htpasswd, dat de maker van de image helaas niet op de juiste wijze gedeclareerd heeft. Als je de oplossing daarvoor wilt weten, moet je de bronnen bestuderen en dingen uitproberen. Dan kom je erachter dat je het probleem met dit configuratiebestand eenvoudig oplost door het bestand al voor het aanmaken van de container te maken:
mkdir -p /usr/local/share/docker;touch /usr/local/share/docker/.gitweb_—
htpasswd
Bij het starten voeg je het bestand als optie toe. Het gehele commando is dan:
docker run -d --name gitweb -v gitwebrepos:/var/lib/git -v /usr/local/share/docker/—
.gitweb_htpasswd:/etc/nginx/.htpasswd fraoustin/gitweb
Wanneer je de Gitweb-containers altijd met die opties aanmaakt, gaan er geen gegevens verloren.
Communiceren met de wereld
Met die basiskennis kun je andere images draaien. We raden je aan zo lang met dokker run en docker rm te oefenen tot je zeker weet dat je daarbij geen gegevens verliest. Het verschilt per container hoe je ze configureert. Behalve de werkwijze met scripts in Gitweb worden de gegevens ook vaak als omgevingsvariabelen meegegeven. Webapplicaties verzamelen hun gegevens meestal via configuratiepagina's die verschijnen wanneer je de applicatie voor de eerste keer in een browser opent.
Als je een shell-sessie in een draaiende container wilt starten, kan dat met docker exec -it gitweb /bin/sh. Daarna kun je de container vanaf de commandline onderzoeken en configureren. Onthoud dat eventuele wijzigingen alleen permanent zijn bij bestanden die buiten de draaiende container opgeslagen worden, dus op volumes of in een externe database. Met het commando docker commit verander je een container weer in een image, maar die moet je niet te vaak gebruiken. Daarmee maak je alleen images zonder bouwplan (Dockerfile) die moeilijk te onderhouden zijn.
Zonder verdere configuratie blijven de services van de container alleen toegankelijk vanaf het hostsysteem. Dat komt doordat Docker elke container in een private netwerk zet, wat vergelijkbaar is met de situatie van een thuisnetwerk achter een router die verbinding met internet maakt. Er is geen portforwarding die het verkeer naar een poort van de host doorstuurt naar een proces in de container. Docker kan zo'n portforwarding inrichten als je dat tijdens het starten van de container aangeeft. De extra parameter die je daarvoor nodig hebt is bijvoorbeeld -p 8001:80. Die verbindt poort 80 van de Gitweb-webserver met poort 8001 van de host.
Voor webservices zou dat snel een onwerkbare situatie opleveren, zeker als meerdere containers als virtuele hosts via één officieel ip-adres op de standaard webpoorten 80 en 443 bereikbaar moeten zijn. Dat probleem los je op met de image jwilder/nginx-proxy, die een vooraf geconfigureerde nginx-webserver als automatische reverse-proxy bevat.
De reverse-proxyserver luistert aan een speciale Unix-socket waar de Docker zijn API aanbiedt (/var/run/docker.sock). Omdat je (hacker)aanvallen op de host niet kunt uitsluiten, geef je alleen betrouwbare containers toegang tot die socket. Zodra je een nieuwe container start, krijgt de proxy dat via de socket te horen. Wanneer die container een specifieke omgevingsvariabele in de vorm van de parameter -e VIRTUAL_HOST=gitweb.example.com, heeft, maakt de proxy een bijpassend configuratiebestand dat in de nginx-server geladen wordt. Daardoor kunnen verzoeken van de