Wat is Docker: een korte excursie naar geschiedenis en fundamentele abstracties

Gestart op 10 augustus in Slurm Docker-videocursus, waarin we het volledig analyseren - van basisabstracties tot netwerkparameters.

In dit artikel zullen we het hebben over de geschiedenis van Docker en zijn belangrijkste abstracties: Image, Cli, Dockerfile. De lezing is bedoeld voor beginners, dus het is onwaarschijnlijk dat deze interessant zal zijn voor ervaren gebruikers. Er zal geen bloed, blindedarm of diepe onderdompeling zijn. De basisprincipes.

Wat is Docker: een korte excursie naar geschiedenis en fundamentele abstracties

Wat is Docker

Laten we eens kijken naar de definitie van Docker van Wikipedia.

Docker is software voor het automatiseren van de implementatie en het beheer van applicaties in containeromgevingen.

Niets is duidelijk uit deze definitie. Het is vooral onduidelijk wat “in omgevingen die containerisatie ondersteunen” betekent. Om daar achter te komen, gaan we terug in de tijd. Laten we beginnen met het tijdperk dat ik gewoonlijk het ‘monolithische tijdperk’ noem.

Monolithisch tijdperk

Het monolithische tijdperk is het begin van de jaren 2000, toen alle applicaties monolithisch waren, met een heleboel afhankelijkheden. De ontwikkeling heeft lang geduurd. Tegelijkertijd waren er niet veel servers; we kenden ze allemaal bij naam en hielden ze in de gaten. Er is zo'n grappige vergelijking:

Huisdieren zijn huisdieren. In het monolithische tijdperk behandelden we onze servers als huisdieren, verzorgd en gekoesterd, waarbij we stofdeeltjes wegbliezen. En voor een beter resourcebeheer hebben we gebruik gemaakt van virtualisatie: we hebben een server genomen en deze in verschillende virtuele machines opgedeeld, waardoor de isolatie van de omgeving werd gewaarborgd.

Op hypervisor gebaseerde virtualisatiesystemen

Iedereen heeft waarschijnlijk wel eens gehoord van virtualisatiesystemen: VMware, VirtualBox, Hyper-V, Qemu KVM, enz. Ze bieden applicatie-isolatie en resourcebeheer, maar ze hebben ook nadelen. Voor virtualisatie heb je een hypervisor nodig. En de hypervisor is een resource-overhead. En de virtuele machine zelf is meestal een hele kolos: een zware image met daarin een besturingssysteem, Nginx, Apache en mogelijk MySQL. De afbeelding is groot en de virtuele machine is lastig te bedienen. Als gevolg hiervan kan het werken met virtuele machines traag zijn. Om dit probleem op te lossen werden virtualisatiesystemen op kernelniveau gecreëerd.

Virtualisatiesystemen op kernelniveau

Virtualisatie op kernelniveau wordt ondersteund door OpenVZ-, Systemd-nspawn- en LXC-systemen. Een sprekend voorbeeld van een dergelijke virtualisatie is LXC (Linux Containers).

LXC is een virtualisatiesysteem op besturingssysteemniveau voor het uitvoeren van meerdere geïsoleerde exemplaren van het Linux-besturingssysteem op één knooppunt. LXC maakt geen gebruik van virtuele machines, maar creëert een virtuele omgeving met een eigen procesruimte en netwerkstack.

In wezen creëert LXC containers. Wat is het verschil tussen virtuele machines en containers?

Wat is Docker: een korte excursie naar geschiedenis en fundamentele abstracties

De container is niet geschikt voor het isoleren van processen: er worden kwetsbaarheden aangetroffen in virtualisatiesystemen op kernelniveau waardoor ze uit de container naar de host kunnen ontsnappen. Als u iets wilt isoleren, is het daarom beter om een ​​virtuele machine te gebruiken.

De verschillen tussen virtualisatie en containerisatie zijn te zien in het diagram.
Er zijn hardware-hypervisors, hypervisors bovenop het besturingssysteem en containers.

Wat is Docker: een korte excursie naar geschiedenis en fundamentele abstracties

Hardware-hypervisors zijn cool als je echt iets wilt isoleren. Omdat het mogelijk is om te isoleren op het niveau van geheugenpagina's en processors.

Er zijn hypervisors als programma, en er zijn containers, en we zullen er verder over praten. Containerisatiesystemen beschikken niet over een hypervisor, maar er is wel een Container Engine die containers aanmaakt en beheert. Dit ding is lichter, dus door het werken met de kern is er minder of helemaal geen overhead.

Wat wordt gebruikt voor containerisatie op kernelniveau

De belangrijkste technologieën waarmee u een container kunt maken die geïsoleerd is van andere processen, zijn naamruimten en stuurgroepen.

Naamruimten: PID, Netwerken, Mount en Gebruiker. Er zijn er nog meer, maar voor een beter begrip zullen we ons hier op concentreren.

PID-naamruimte beperkt processen. Wanneer we bijvoorbeeld een PID-naamruimte maken en daar een proces plaatsen, wordt dit PID 1. Meestal is PID 1 in systemen systemd of init. Wanneer we een proces in een nieuwe naamruimte plaatsen, ontvangt het dus ook PID 1.

Met netwerknaamruimte kunt u het netwerk beperken/isoleren en uw eigen interfaces erin plaatsen. Mount is een beperking van het bestandssysteem. Gebruiker: beperking voor gebruikers.

Controlegroepen: geheugen, CPU, IOPS, netwerk - in totaal ongeveer 12 instellingen. Anders worden ze ook wel Cgroepen (“C-groepen”) genoemd.

Stuurgroepen beheren bronnen voor een container. Via controlegroepen kunnen we zeggen dat de container niet meer dan een bepaalde hoeveelheid hulpbronnen mag verbruiken.

Om containerisatie volledig te laten werken, worden aanvullende technologieën gebruikt: mogelijkheden, Copy-on-write en andere.

Capabilities zijn wanneer we een proces vertellen wat het wel en niet kan doen. Op kernelniveau zijn dit eenvoudigweg bitmaps met veel parameters. De rootgebruiker heeft bijvoorbeeld volledige rechten en kan alles doen. De tijdserver kan de systeemtijd wijzigen: hij heeft mogelijkheden op de Time Capsule, en dat is alles. Met behulp van privileges kunt u flexibel beperkingen voor processen configureren en uzelf daardoor beschermen.

Dankzij het Copy-on-write-systeem kunnen we met Docker-images werken en deze efficiënter gebruiken.

Docker heeft momenteel compatibiliteitsproblemen met Cgroups v2, dus dit artikel richt zich specifiek op Cgroups v1.

Maar laten we teruggaan naar de geschiedenis.

Toen virtualisatiesystemen op kernelniveau verschenen, begonnen ze actief te worden gebruikt. De overhead op de hypervisor verdween, maar er bleven enkele problemen bestaan:

  • grote afbeeldingen: ze duwen een besturingssysteem, bibliotheken en een heleboel verschillende software in dezelfde OpenVZ, en uiteindelijk blijkt de afbeelding nog steeds behoorlijk groot te zijn;
  • Er is geen normale standaard voor verpakking en levering, dus het probleem van afhankelijkheden blijft bestaan. Er zijn situaties waarin twee stukjes code dezelfde bibliotheek gebruiken, maar met verschillende versies. Er kan een conflict tussen hen bestaan.

Om al deze problemen op te lossen is het volgende tijdperk aangebroken.

Containertijdperk

Toen het tijdperk van de containers aanbrak, veranderde de filosofie om met hen samen te werken:

  • Eén proces - één container.
  • Wij leveren alle afhankelijkheden die het proces nodig heeft in de container. Dit vereist het opdelen van monolieten in microservices.
  • Hoe kleiner de afbeelding, hoe beter: er zijn minder mogelijke kwetsbaarheden, de afbeelding wordt sneller uitgerold, enzovoort.
  • Instanties worden vluchtig.

Weet je nog wat ik zei over huisdieren versus vee? Vroeger waren exemplaren als huisdieren, maar nu zijn ze als vee geworden. Voorheen was er een monoliet - één applicatie. Nu zijn het 100 microservices, 100 containers. Sommige containers kunnen 2-3 replica's hebben. Het wordt voor ons minder belangrijk om elke container te controleren. Wat voor ons belangrijker is, is de beschikbaarheid van de dienst zelf: wat deze set containers doet. Dit verandert de aanpak van monitoring.

In 2014-2015 bloeide Docker - de technologie waar we het nu over zullen hebben.

Docker veranderde de filosofie en standaardiseerde de applicatieverpakking. Met Docker kunnen we een applicatie verpakken, naar een repository sturen, daar downloaden en implementeren.

We stoppen alles wat we nodig hebben in de Docker-container, zodat het afhankelijkheidsprobleem is opgelost. Docker garandeert reproduceerbaarheid. Ik denk dat veel mensen met onreproduceerbaarheid te maken hebben gehad: alles werkt voor je, je zet het in productie, en daar stopt het met werken. Met Docker verdwijnt dit probleem. Als uw Docker-container start en doet wat hij moet doen, dan zal hij met grote waarschijnlijkheid in productie gaan en daar hetzelfde doen.

Uitweiding over overhead

Er zijn altijd geschillen over overheadkosten. Sommige mensen zijn van mening dat Docker geen extra belasting met zich meebrengt, omdat het de Linux-kernel en al zijn processen gebruikt die nodig zijn voor containerisatie. Zoals: "als je zegt dat Docker overhead is, dan is de Linux-kernel overhead."

Aan de andere kant, als je dieper gaat, zijn er inderdaad verschillende dingen in Docker waarvan je, met enige rek, kunt zeggen dat ze boven het hoofd liggen.

De eerste is de PID-naamruimte. Wanneer we een proces in een naamruimte plaatsen, krijgt het PID 1 toegewezen. Tegelijkertijd heeft dit proces een andere PID, die zich op de hostnaamruimte bevindt, buiten de container. We lanceerden bijvoorbeeld Nginx in een container, het werd PID 1 (masterproces). En op de host heeft het PID 12623. En het is moeilijk te zeggen hoeveel overhead het is.

Het tweede ding is Cgroups. Laten we Cgroups op geheugen nemen, dat wil zeggen de mogelijkheid om het geheugen van een container te beperken. Als het is ingeschakeld, worden tellers en geheugenaccounting geactiveerd: de kernel moet begrijpen hoeveel pagina's zijn toegewezen en hoeveel er nog vrij zijn voor deze container. Dit is mogelijk een overhead, maar ik heb geen precieze onderzoeken gezien over hoe dit de prestaties beïnvloedt. En ik merkte zelf niet dat de applicatie die in Docker draaide plotseling een groot prestatieverlies ervoer.

En nog een opmerking over de prestaties. Sommige kernelparameters worden doorgegeven van de host naar de container. In het bijzonder enkele netwerkparameters. Als u daarom iets met hoge prestaties in Docker wilt uitvoeren, bijvoorbeeld iets dat actief gebruik maakt van het netwerk, dan moet u op zijn minst deze parameters aanpassen. Sommige nf_conntrack, bijvoorbeeld.

Over het Docker-concept

Docker bestaat uit verschillende componenten:

  1. Docker Daemon is dezelfde Container Engine; lanceert containers.
  2. Docker CII is een Docker-beheerhulpprogramma.
  3. Dockerfile - instructies voor het bouwen van een afbeelding.
  4. Afbeelding — de afbeelding waaruit de container wordt uitgerold.
  5. container.
  6. Docker-register is een opslagplaats voor afbeeldingen.

Schematisch ziet het er ongeveer zo uit:

Wat is Docker: een korte excursie naar geschiedenis en fundamentele abstracties

Docker-daemon draait op Docker_host en lanceert containers. Er is een client die opdrachten verzendt: bouw de afbeelding, download de afbeelding, start de container. Docker-daemon gaat naar het register en voert ze uit. De Docker-client heeft zowel lokaal (naar een Unix-socket) als via TCP toegang vanaf een externe host.

Laten we elk onderdeel doornemen.

Docker-daemon - dit is het servergedeelte, het werkt op de hostmachine: downloadt afbeeldingen en start er containers van, creëert een netwerk tussen containers, verzamelt logs. Als we zeggen ‘maak een beeld’, doet de demon dat ook.

Docker-CLI — Docker-clientgedeelte, consolehulpprogramma voor het werken met de daemon. Ik herhaal: het kan niet alleen lokaal werken, maar ook via het netwerk.

Basiscommando's:

docker ps - toon containers die momenteel op de Docker-host draaien.
docker-afbeeldingen - toon afbeeldingen die lokaal zijn gedownload.
docker search <> - zoek naar een afbeelding in het register.
docker pull <> - download een afbeelding van het register naar de machine.
docker-build < > - verzamel de afbeelding.
docker run <> - start de container.
docker rm <> - verwijder de container.
docker-logboeken <> - containerlogboeken
docker start/stop/herstart <> - werken met de container

Als u deze opdrachten onder de knie heeft en er vertrouwen in heeft ze te gebruiken, beschouw uzelf dan als 70% bedreven in Docker op gebruikersniveau.

Dockerfile - instructies voor het maken van een afbeelding. Bijna elk instructiecommando is een nieuwe laag. Laten we eens kijken naar een voorbeeld.

Wat is Docker: een korte excursie naar geschiedenis en fundamentele abstracties

Zo ziet het Dockerbestand eruit: opdrachten aan de linkerkant, argumenten aan de rechterkant. Elke opdracht die hier staat (en doorgaans in het Dockerbestand wordt geschreven) creëert een nieuwe laag in Image.

Zelfs als je naar de linkerkant kijkt, kun je grofweg begrijpen wat er gebeurt. We zeggen: "maak een map voor ons" - dit is één laag. “Laat de map werken” is een andere laag, enzovoort. Layer cake maakt het leven gemakkelijker. Als ik nog een Dockerfile maak en iets in de laatste regel verander - ik voer iets anders uit dan "python" "main.py", of installeer afhankelijkheden uit een ander bestand - dan worden de voorgaande lagen hergebruikt als cache.

Beeld - dit is containerverpakking; containers worden gelanceerd vanuit de afbeelding. Als we Docker bekijken vanuit het standpunt van een pakketbeheerder (alsof we met deb- of rpm-pakketten werken), dan is image in wezen een rpm-pakket. Via yum install kunnen we de applicatie installeren, verwijderen, in de repository vinden en downloaden. Hier is het ongeveer hetzelfde: containers worden gestart vanaf de afbeelding, ze worden opgeslagen in het Docker-register (vergelijkbaar met yum, in een repository) en elke afbeelding heeft een SHA-256-hash, een naam en een tag.

De afbeelding is gebouwd volgens de instructies uit de Dockerfile. Elke instructie uit het Dockerbestand creëert een nieuwe laag. Lagen kunnen worden hergebruikt.

Docker-register is een Docker-imagerepository. Net als het besturingssysteem heeft Docker een openbaar standaardregister: dockerhub. Maar u kunt uw eigen repository bouwen, uw eigen Docker-register.

Containers - wat er vanuit de afbeelding wordt gelanceerd. We hebben een afbeelding gebouwd volgens de instructies uit het Dockerfile en vervolgens starten we deze vanuit deze afbeelding. Deze container is geïsoleerd van andere containers en moet alles bevatten wat nodig is om de applicatie te laten functioneren. In dit geval is één container één proces. Het komt voor dat je twee processen moet doen, maar dit is enigszins in strijd met de Docker-ideologie.

De vereiste "één container, één proces" heeft betrekking op de PID-naamruimte. Wanneer een proces met PID 1 in Naamruimte start en het plotseling sterft, sterft de hele container ook. Als daar twee processen draaien: de ene leeft en de andere is dood, dan blijft de container nog steeds leven. Maar dit is een kwestie van Best Practices, we zullen erover praten in andere materialen.

Om de kenmerken en het volledige programma van de cursus in meer detail te bekijken, volgt u de link: “Docker-videocursus.

Auteur: Marcel Ibraev, gecertificeerd Kubernetes-beheerder, praktiserend ingenieur bij Southbridge, spreker en ontwikkelaar van Slurm-cursussen.

Bron: www.habr.com

Voeg een reactie