Dit artikel is het vervolg van het artikel uit Linux Magazine #3 van pagina 26-27. 

Met docker images vraag je allereerst de lijst met images op die op dat moment beschikbaar zijn op de host. In deze kersverse Docker-installatie zitten nog geen images. Met docker ps vraag je vervolgens de lijst op met draaiende containers. Er zijn geen images, en daarmee logischerwijs ook nog geen containers. Met docker run start je daarna een nginx webserver. Dit commando doet een aantal dingen. Ten eerste kijkt de Docker-engine of er een image met de naam nginx reeds beschikbaar is op de host. Zoals blijkt uit het voorbeeld is dit niet het geval, en besluit Docker om de image te downloaden in de Docker Hub. Dit downloaden wordt een pull genoemd. Nadat de image is gedownload start Docker de image. Door de opties –d en –P mee te geven stuurt Docker de draaiende container naar de achtergrond, en stelt de netwerkpoorten van nginx beschikbaar op willekeurige vrije netwerkpoorten van de host. 

Kijk je met docker ps naar de lijst met draaiende containers, dan zie je de draaiende nginx-container. Hier is ook te zien welke netwerkpoorten op de host zijn gekoppeld aan nginx-poorten. Zo is poort 80 van nginx te vinden op poort 32769 van de host, en poort 32768 van de host verwijst door naar poort 443 van nginx. Start je een browser, en kijk je met een browser op poort 32769 van de host, dan zie je het volgende resultaat:

De grote kracht van Docker is dat je in circa 30 seconden een complete nginx-webserver hebt gedownload, gestart en beschikbaar hebt gesteld.

 Layers

Bekijk je de output van het commando docker run, dan valt je misschien op dat er bij het downloaden van de image meerdere bestanden worden gedownload. Een Docker image bestaat voornamelijk uit het bestandssysteem met meerdere lagen (layers). De volgende illustratie toont hoe deze lagen werken. 

Iedere laag is een eigen image, gebaseerd op de onderliggende laag. Een laag is echter geen volledige kopie van de onderliggende laag. Een laag bevat slechts informatie over de wijzigingen ten opzichte van de onderliggende laag. In de afbeelding wordt de onderste laag base genoemd. In de laag daar bovenop (java) zie je dat bestand C is aangepast, en dat er een nieuw bestand (D) is toegevoegd. De java-laag bevat virtueel de bestanden B, C, D en E, terwijl de laag in werkelijkheid slechts bestanden C en D bevat. In de tomcat-laag is bestand D weer verwijderd, is bestand A toegevoegd en is er wederom een wijziging op bestand E. De tomcat-laag bevat dus virtueel bestanden A, B, C en E. Door het gebruik van overerving (inheritance), overschrijven (override), ‘uitgrijzen’ (whiteout) en copy-on-write worden Docker-images zo klein en efficiënt mogelijk gehouden. Het gebruik van lagen, ofwel de stapeling van images is noodzakelijk voor het maken van een nieuwe image.

Copy-on-write

Copy-on-write is het principe dat pas wanneer een proces in een Docker-container een bestand wijzigt, er een kopie van het bestand wordt gemaakt in de huidige laag. Deze kopie bevat alle wijzigingen die de container maakt.

 Op de achtergrond maakt Docker iedere keer wanneer een container wordt gestart automatisch een nieuwe toplaag aan. Deze laag is gebaseerd op de Docker-image waarmee de container werd gestart. Wanneer de container wordt gestopt verdwijnt deze laag, tenzij je de laag expliciet bewaart als image. Dit kan met het docker commit commando.

Een eigen image

Een Docker-image maken kan op twee manieren. Je kunt ten eerste de interactieve manier gebruiken. Omdat Docker-images gebaseerd zijn op Linux, kan je een interactieve shell starten in een container. Stel dat je bijvoorbeeld de extra package genaamd ssl-cert zou willen installeren in de image met nginx:

Hier start de container op een andere manier dan in het vorige voorbeeld. In plaats van –d zie je de optie –it. Hiermee start de container op de voorgrond in interactieve modus. Verder zie je de naam van de gewenste image, en het commando waarmee de container moet starten. Nu heb je een bash shell waarin je zaken kan doen zoals op alle Linuxsystemen. Omdat de base image van de nginx-container een Ubuntu-image is gebruiken we hier apt-get. Een container die op de voorgrond is gestart, stopt zodra het proces eindigt waarmee de container is gestart. Met de exit uit de bash shell is de container ook gestopt. Je kan de container die heeft gedraaid vervolgens weer vinden en opslaan als nieuwe image:


Het commando docker ps kan je de optie –a meegeven zodat je niet alleen de nu draaiende containers ziet, maar ook de containers die tot dan toe hebben gedraaid. Met behulp van het container-ID kan je de betreffende container met de opdracht docker commit opslaan als image. In het voorbeeld krijgt de image de naam ‘linuxmag/nginx’. Met het commando docker images kan je controleren of de image daadwerkelijk is gemaakt.

 De tweede manier waarop je een image kunt maken is door middel van een Dockerfile. Dit is een tekstbestand met daarin instructies. De opdracht docker build begrijpt deze file, en kan de instructies uitvoeren. Het resultaat is een nieuwe image. Het eindresultaat van het vorige voorbeeld kan worden gereproduceerd met de volgende Dockerfile:


De eerste instructie is altijd de FROM instructie. Een image bestaat uit lagen, en iedere laag is ook weer een image. Op het laagste niveau is er de zogenaamde base image. Dit zijn de meest elementaire images die vaak zijn gebaseerd op een Linuxdistributie. Zo is er bijvoorbeeld een Ubuntu en een CentOS base image. In het voorbeeld zie je dat de nieuwe image is gebaseerd op de nginx image uit Docker Hub. Op de tweede regel zie je een RUN instructie. Deze werkt net zoals een reguliere Linux shell, zoals je die ook zelf uit kunt voeren. Met dit bestand kan je vervolgens de nieuwe image bouwen door middel van het commando docker build:


Het resultaat is een wederom een nieuwe image waarin ook alle lagen van de onderliggende images zitten. In de praktijk wordt een Docker-build het meeste gebruikt. Het is namelijk zeer gemakkelijk om de Dockerfile samen met je broncode in een versiebeheersysteem te bewaren. Het bouwproces van de applicatie kan eenvoudig zo gemaakt worden dat de gebouwde applicatie automatisch in een image wordt gestopt.

 Stateful versus Stateless

Omdat images stateless zijn lenen stateful applicaties zich minder makkelijk voor container-based deployments. Wanneer een container stopt, is de staat van de applicatie verdwenen. Wat overblijft is een image. Docker biedt echter wel voorzieningen die een applicatie kan gebruiken om zijn staat op te slaan. Je kan bijvoorbeeld in een Docker-container het bestandssysteem van de host delen, gebruiken, en daar data opslaan. Echter een modern datacentrum heeft duizenden servers, waar applicaties ten behoeve van hoge beschikbaarheid meervoudig zijn uitgevoerd. Dat leidt tot uitdagingen. Je moet dan nadenken hoe je deze data tussen de verschillende instanties van je applicatie, en de verschillende Docker hosts, gaat delen. Om deze uitdagingen (deels) uit de weg te gaan is het een actuele trend in software ontwikkeling om zogenaamde microservices te bouwen. Deze services zijn web applicaties die slechts éénspecifieke taak hebben en stateless zijn. Dit is een toepassing die uitermate geschikt is voor containers.

Microservices

Er is geen formele definitie van wat een microservice is. Microservices zijn eigenlijk onderdeel van een stijl van software ontwikkelen waarin bepaalde eigenschappen centraal staan. Microservices zijn componenten in een groter geheel die één specifieke functie of taak hebben. Daarnaast moet het mogelijk zijn een microservice onafhankelijk te vernieuwen of verwijderen zonder dat omringende services of systemen daar hinder van ondervinden. Dit betekent in de praktijk dat de microservice zelf volledig stateless moet zijn, of zijn staat kwijt mag raken.

 Verder typeren microservices zich door het feit dat ze een duidelijke goed omschreven interface hebben, die gebaseerd is op webtechnologie (met name HTTP). Een voorbeeld van een microservice zou een service kunnen zijn die een PDF-document genereert en teruggeeft uit een stuk tekst dat door middel van een HTTP POST naar de service wordt verzonden.

 Microservices zijn interessant voor cloudtechnologie omdat ze zich door deze eigenschappen bij uitstek lenen voor horizontaal schalen (veel simpele kleine instanties) in plaats van verticaal schalen (enkele complexe grote instanties).

Er is veel mogelijk met Docker, maar je moet wel bekijken of de toepassing die je wilt gebruiken zich goed leent voor containers. Het Docker-‘ecosysteem’ ontwikkelt zich zeer snel. Toepassingen die nu minder goed geschikt zijn voor containers passen volgende week misschien wel. Crate bijvoorbeeld, is een database-applicatie die speciaal gemaakt is om juist in containers te kunnen draaien (https://crate.io). Dat terwijl een database toch een typisch voorbeeld is van een stateful applicatie.

Het juiste gereedschap

De populariteit van Docker is met name te danken aan het open platform, de standaardcontainer, het gebruiksgemak en de flexibiliteit. Je kunt met Docker zeer snel een nieuwe applicatie in productie nemen op een eenvoudige, voorspelbare en herhaalbare manier. Alhoewel met Docker veel mogelijk is, moet je je beseffen dat niet iedere toepassing zich even goed leent voor Docker. Met de huidige ontwikkeling van toepassingen in de cloud, bijvoorbeeld op basis van microservices, is Docker het juiste gereedschap en niet meer weg te denken.

Je kunt Docker eenvoudig zelf online uitproberen. Ga daarvoor naar de website van Docker