In de commentaren op mijn artikel waren er veel verzoeken om uit te leggen waarom de beschreven Dockerfile zo vreselijk is.
Korte samenvatting van de vorige aflevering: twee ontwikkelaars werken onder een strakke deadline aan de Dockerfile. Tijdens het proces komt Ops Igor Ivanovich binnen. De uiteindelijke Dockerfile is zo slecht dat de AI bijna een hartaanval krijgt.

Laten we nu bekijken wat er mis is met deze Dockerfile.
Dus, er is een week verstreken.
Dev Petya ontmoet Ops Igor Ivanovich in de kantine voor een kop koffie.
P: Igor Ivanovich, ben je erg druk? Ik zou graag willen begrijpen waar we fout zijn gegaan.
II: Dat is goed, je ontmoet niet vaak ontwikkelaars die geïnteresseerd zijn in de exploitatie.
Laten we beginnen met een paar afspraken:
- Ideologie van Docker: één container — één proces.
- Hoe kleiner de container, hoe beter.
- Hoe meer er uit de cache wordt gehaald, hoe beter.
P: Maar waarom zou er één proces in één container moeten zijn?
II: Docker houdt bij het starten van een container de status van het proces met pid 1 in de gaten. Als het proces sterft, probeert Docker de container opnieuw te starten. Stel dat je meerdere applicaties in de container hebt draaien of dat de belangrijkste applicatie niet met pid 1 draait. Als het proces sterft, verneemt Docker daar niets van.
Als er verder geen vragen zijn, laat me jullie Dockerfile zien.
En Petya liet zien:
FROM ubuntu:latest
# Kopieer de broncode
COPY . /app
WORKDIR /app
# Werk de lijst van pakketten bij
RUN apt-get update
# Werk de pakketten bij
RUN apt-get upgrade
# Installeer de benodigde pakketten
RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor
# Installeer bundler
RUN gem install bundler
# Installeer nodejs, dat gebruikt wordt voor het bouwen van statische bestanden
RUN curl -sL https://deb.nodesource.com/setup_9.x | sudo bash -
RUN apt-get install -y nodejs
# Installeer afhankelijkheden
RUN bundle install --without development test --path vendor/bundle
# Maak de caches schoon
RUN rm -rf /usr/local/bundle/cache/*.gem
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN rake assets:precompile
# Voer het script uit bij het opstarten van de container dat alles andere zal starten.
CMD ["/app/init.sh"]II: Oh, laten we de zaken stap voor stap bekijken. Laten we beginnen met de eerste regel:
FROM ubuntu:latestJe pakt de tag latest. Het gebruik van deze tag latest kan onvoorspelbare gevolgen hebben. Stel je voor, de maintainer van de afbeelding bouwt een nieuwe versie van de afbeelding met een andere softwarelijst, deze afbeelding krijgt de tag latest. En je container stopt in het beste geval met bouwen, of in het ergste geval krijg je bugs die er vroeger niet waren.
Je neemt een afbeelding met een volledig besturingssysteem met veel onnodige software, wat het volume van de container vergroot. En hoe meer software, hoe meer gaten en kwetsbaarheden.
Bovendien, hoe groter de afbeelding, hoe meer ruimte deze op de host en in de registry in beslag neemt (je slaat de afbeeldingen ergens op, toch)?
P: Ja, natuurlijk, we hebben een registry, dat heb jij ingesteld.
AI: Waar was ik?.. Ah ja, volumes… De belasting op het netwerk groeit ook. Voor een enkele afbeelding is dit onopgemerkt, maar wanneer er continu gebouwd, getest en gedeployed wordt, is het merkbaar. En als je geen God’s mode op AWS hebt, krijg je ook nog een astronomische rekening.
Daarom moet je de meest geschikte afbeelding kiezen, met de exacte versie en minimaal aantal software. Neem bijvoorbeeld: FROM ruby:2.5.5-stretch
P: O, begrijpelijk. Hoe en waar kan ik de beschikbare afbeeldingen bekijken? Hoe weet ik welke ik nodig heb?
AI: Gewoonlijk worden afbeeldingen genomen van , verwarren met Pornhub :). Voor een afbeelding zijn meestal verschillende builds beschikbaar:
Alpine: afbeeldingen zijn gebouwd op een minimalistische Linux-afbeelding, slechts 5 MB. Het nadeel is: het is gebouwd met een eigen implementatie van libc, standaardpakketten werken er niet. Het zal enige tijd duren om het benodigde pakket te zoeken en te installeren.
Scratch: basisafbeelding, wordt niet gebruikt voor het bouwen van andere afbeeldingen. Het is uitsluitend bedoeld om binaire, voorbereide gegevens uit te voeren. Ideaal voor het uitvoeren van binaire applicaties die alles nodig hebben, zoals Go-applicaties.
Op basis van een of ander besturingssysteem, zoals Ubuntu of Debian. Hier hoeft, denk ik, niet verder op ingegaan te worden.
AI: Nu moeten we alle extra pakketten installeren en de caches opruimen. En we kunnen meteen apt-get upgradeweggooien. Anders zullen er bij elke build, ondanks de vaste tag van de basisafbeelding, verschillende afbeeldingen worden gemaakt. Het bijwerken van pakketten in een afbeelding is de verantwoordelijkheid van de maintainer, dit gaat gepaard met wijziging van de tag.
P: Ja, ik heb geprobeerd dit te doen, en zo kreeg ik het voor elkaar:
WORKDIR /app
COPY ./ /app
RUN curl -sL https://deb.nodesource.com/setup_9.x | bash -
&& apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
RUN rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*AI: Niet slecht, maar er is ook hier werk aan de winkel. Kijk, deze opdracht:
RUN rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* … verwijdert geen gegevens uit de eindafbeelding, maar creëert slechts een extra laag zonder deze gegevens. Het is zo correct:
RUN curl -sL https://deb.nodesource.com/setup_9.x | bash -
&& apt-get -y install libpq-dev imagemagick gsfonts nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
&& rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* Maar dat is nog niet alles. Wat heb je daar, Ruby? Dan hoef je niet aan het begin het hele project te kopiëren. Het is voldoende om Gemfile en Gemfile.lock te kopiëren.
Met deze aanpak wordt bundle install niet uitgevoerd bij elke wijziging in de brondocumenten, maar alleen als de Gemfile of Gemfile.lock is veranderd.
Dezelfde methodes werken ook voor andere talen met afhankelijkheidsbeheerders, zoals npm, pip, composer en andere die gebaseerd zijn op een bestand met een lijst van afhankelijkheden.
En tot slot, herinner je je dat ik in het begin sprak over de Docker-ideologie ‘één container — één proces’? Dit betekent dat supervisor niet nodig is. Je hoeft ook geen systemd te installeren, om dezelfde redenen. In feite is Docker zelf al een supervisor. En wanneer je probeert meerdere processen erin te starten, is dat alsof je meerdere applicaties in één supervisor-proces probeert te draaien.
Bij het bouwen maak je één afbeelding, en daarna start je het benodigde aantal containers zodat er in elk één proces draait.
Maar daarover later meer.
V: Ik denk dat ik het begrijp. Kijk eens wat we hebben:
FROM ruby:2.5.5-stretch
WORKDIR /app
COPY Gemfile* /app
RUN curl -sL https://deb.nodesource.com/setup_9.x | bash -
&& apt-get -y install libpq-dev imagemagick gsfonts nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
&& rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY . /app
RUN rake assets:precompile
CMD ["bundle”, “exec”, “passenger”, “start"]En we zullen de demonfunctie opnieuw definiëren bij het starten van de container?
AI: Ja, dat klopt. Overigens, je kunt zowel CMD als ENTRYPOINT gebruiken. En om te begrijpen wat het verschil is, is dat jouw huiswerk. Daarover is er een goed artikel op Habr. .
Goed, laten we verder gaan. Je downloadt een bestand voor de installatie van node, maar er is geen garantie dat het bevat wat je nodig hebt. We moeten validatie toevoegen. Bijvoorbeeld zo:
RUN curl -sL https://deb.nodesource.com/setup_9.x > setup_9.x
&& echo "958c9a95c4974c918dca773edf6d18b1d1a41434 setup_9.x" | sha1sum -c -
&& bash setup_9.x
&& rm -rf setup_9.x
&& apt-get -y install libpq-dev imagemagick gsfonts nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
&& rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* Met de controlesom kun je verifiëren dat je het juiste bestand hebt gedownload.
P: Maar als het bestand verandert, dan zal de bouw niet slagen.
AI: Ja, en vreemd genoeg is dat ook een voordeel. Je weet dat het bestand is veranderd, en je kunt bekijken wat er is aangepast. Wie weet, misschien hebben ze een script toegevoegd dat alles verwijdert waar het toegang toe heeft, of een backdoor maakt.
P: Dank je. Dus het uiteindelijke Dockerfile ziet er zo uit:
FROM ruby:2.5.5-stretch
WORKDIR /app
COPY Gemfile* /app
RUN curl -sL https://deb.nodesource.com/setup_9.x > setup_9.x
&& echo "958c9a95c4974c918dca773edf6d18b1d1a41434 setup_9.x" | sha1sum -c -
&& bash setup_9.x
&& rm -rf setup_9.x
&& apt-get -y install libpq-dev imagemagick gsfonts nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
&& rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY . /app
RUN rake assets:precompile
CMD ["bundle", "exec", "passenger", "start"]P: Igor Ivanovich, dank voor de hulp. Ik moet nu gaan, ik moet vandaag nog 10 commits doen.
Igor Ivanovich, die zijn haastige collega met een blik stopt, neemt een slok sterke koffie. Na enkele seconden nagedacht te hebben over de SLA van 99,9% en code zonder fouten, stelt hij een vraag.
AI: Waar slaat u de logs op?
P: Natuurlijk, in production.log. Trouwens, hoe krijgen we zonder ssh toegang tot hen?
AI: Als u ze in bestanden laat, is er al een oplossing voor u bedacht. De docker exec-opdracht stelt je in staat om elke opdracht in de container uit te voeren. Bijvoorbeeld, je kunt cat voor de logs gebruiken. En als je de sleutel -it en bash start (als het in de container is geïnstalleerd), krijg je interactieve toegang tot de container.
Maar het is niet verstandig om logs in bestanden op te slaan. Op zijn minst leidt het tot ongecontroleerde groei van de container, de logs worden immers niet geroteerd. Alle logs moeten naar stdout worden gestuurd. Daar kun je ze bekijken met de opdracht docker logs.
P: Igor Ivanovich, kunnen we de logs misschien naar een gemonteerde directory op de fysieke node verplaatsen, net als gebruikersgegevens?
AI: Goed dat je niet bent vergeten om de gegevens die naar de schijf van de node zijn geladen weg te halen. Met logs kan dat ook, maar vergeet niet om de rotatie in te stellen.
Dat is het, je kunt gaan.
P: Igor Ivanovich, wat raad je aan om te lezen?
AI: Begin met het lezen van , niemand weet waarschijnlijk beter dan zij.
En als je praktijkervaring wilt opdoen, ga naar de . Immers, theorie zonder praktijk is dood.
Bron: habr.com
