Docker: nezávadné rady

V komentároch k môjmu článku Docker: nezdravé rady bolo veľa žiadostí vysvetliť, čo je také hrozné na v ňom popísanom Dockerfile.

Kratšie zhrnutie predchádzajúcej časti: dvaja vývojári vo vypätej lehote zostavujú Dockerfile. Počas tohto procesu k nim prichádza Ops Igor Ivanovič. Výsledný Dockerfile je tak zlý, že Igor Ivanovič takmer dostáva infarkt.

Docker: nezávadné rady

Teraz si vysvetlíme, čo nie je v poriadku s týmto Dockerfile.

Takže prešla týždeň.

Dev Peťo sa stretáva v jedálni pri šálke kávy s Ops Igorom Ivanovičom.

P: Igor Ivanovič, ste veľmi zaneprázdnený? Chcel by som sa dozvedieť, kde sme urobili chybu.

I: To je dobré, nie často sa stretám s vývojármi, ktorí sa zaujímajú o prevádzku.
Na začiatok si dohodnime niektoré veci:

  1. Ideológia Docker: jeden kontajner — jeden proces.
  2. Čím menší kontajner, tým lepšie.
  3. Čím viac sa berie z cache, tým lepšie.

P: A prečo by mal byť v jednom kontajneri len jeden proces?

I: Docker pri spúšťaní kontajnera sleduje stav procesu s pid 1. Ak proces umrie, Docker sa pokúsi kontajner reštartovať. Predpokladajme, že v kontajneri je spustených niekoľko aplikácií alebo že hlavná aplikácia nie je spustená s pid 1. Ak proces umrie, Docker o tom nebude vedieť.

Ak nemáte viac otázok, ukážte váš Dockerfile.

A Peťo ukázal:

FROM ubuntu:latest

# Kopírujeme zdrojový kód
COPY . /app
WORKDIR /app

# Aktualizujeme zoznam balíkov
RUN apt-get update 

# Aktualizujeme balíky
RUN apt-get upgrade

# Inštalujeme potrebné balíky
RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor

# Inštalujeme bundler
RUN gem install bundler

# Inštalujeme nodejs, ktorý sa používa na zostavovanie statiky
RUN curl -sL https://deb.nodesource.com/setup_9.x | sudo bash -
RUN apt-get install -y nodejs

# Inštalujeme závislosti
RUN bundle install --without development test --path vendor/bundle

# Čistíme si za sebou cache
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
# Spúšťame skript pri štarte kontajnera, ktorý spustí všetko ostatné.
CMD ["/app/init.sh"]

I: Och, poďme to rozobrať po poriadku. Začneme od prvého riadku:

FROM ubuntu:latest

Beriete tag latest. Použitie tagu latest vedie k nepredvídateľným následkom. Predstavte si, že maintainer obrazu zostavuje novú verziu obrazu s iným zoznamom softvéru, tento obraz dostane tag latest. A váš kontajner v najlepšom prípade prestane zostavovať, a v najhoršom prípade narazíte na chyby, ktoré predtým neexistovali.

Beriete obraz s plnohodnotným OS s množstvom nepotrebného softvéru, čo zväčšuje objem kontajnera. A čím viac softvéru, tým viac bezpečnostných dier a zraniteľností.

Navyše, čím väčší obraz, tým viac miesta zaberá na hostiteľovi a v registry (predsa niekde uchovávaš obrazy)?

P: Áno, samozrejme, máme registry, veď si ho aj nastavoval.

IA: Tak, o čom som to hovoril?.. Aha, objemy… Takisto rastie záťaž na sieti. Pre jediný obraz to nie je viditeľné, ale keď ide o nepretržitú zostavu, testy a nasadenie, je to cítiť. A ak nemáš God’s mode na AWS, dostaneš ešte aj astronomický účet.

Preto je potrebné vybrať najvhodnejší obraz, s presnou verziou a minimom softvéru. Napríklad, vezmi: FROM ruby:2.5.5-stretch

P: O, chápem. A ako a kde sa dajú pozrieť existujúce obrazy? Ako zistím, ktorý potrebujem?

IA: Zvyčajne sa obrazy berú z dockerhubu, nemieň to s Pornhubom :). Pre obraz obvykle existuje niekoľko zostáv:
Alpine: obrazy sú zostavené na minimalistickom obraze Linux, len 5 MB. Jeho nevýhoda: je zostavený s vlastnou realizáciou libc, štandardné balíčky v ňom nefungujú. Na hľadanie a inštaláciu potrebného balíčka pôjde dosť času.
Scratch: základný obraz, ktorý sa nepoužíva na zostavovanie iných obrazov. Je určený výhradne na spúšťanie binárnych, pripravených dát. Ideálne sa hodí na spúšťanie binárnych aplikácií, ktoré obsahujú všetko potrebné, napríklad go-aplikácií.
Na báze nejakej OS, napríklad Ubuntu alebo Debian. No, tu si myslím, že nie je potrebné vysvetľovať.

IA: Teraz musíme nainštalovať všetky doplnkové balíčky a vyčistiť si za sebou keš. A hneď môžeme vyhodiť apt-get upgrade. Inak pri každej zostave, bez ohľadu na fixovaný tag základného obrazu, budú vzniknúť rôzne obrazy. Aktualizácia balíčkov v obraze je úlohou maintenera, je sprevádzaná zmenou tagu.

P: Áno, snažil som sa to urobiť, mám to takto:

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/*

IA: Nie zlé, ale aj tu sú veci, na ktorých treba zapracovať. Pozri, táto príkaz:

RUN rm -rf /usr/local/bundle/cache/*.gem 
    && apt-get clean 
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*  

… neodstraňuje údaje z výsledného obrazu, ale len vytvára ďalšiu vrstvu bez týchto údajov. Správne je to takto:

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/* 

Ale to ešte nie je všetko. Čo tam máš, Ruby? Potom na začiatku netreba kopírovať celý projekt. Stačí skopírovať Gemfile a Gemfile.lock.

Pri tomto prístupe nebude bundle install vykonávaný pri každej zmene zdrojového kódu, ale iba ak sa zmení Gemfile alebo Gemfile.lock.

Tieto metódy fungujú aj pre iné jazyky s manažérom závislostí, ako sú npm, pip, composer a ďalšie založené na súbore so zoznamom závislostí.

A nakoniec, pamätáš, na začiatku som hovoril o ideológii Dockera „jeden kontajner — jeden proces“? To znamená, že supervisora netreba. Tak isto netreba inštalovať systemd, z rovnakých dôvodov. V podstate, Docker sám je supervisor. A keď sa pokúšaš spúšťať v ňom niekoľko procesov, je to ako spúšťať niekoľko aplikácií v jednom procese supervisora.
Pri zostavení vytvoríte jediný obraz, a potom spustíte potrebný počet kontajnerov, aby v každom bežal jeden proces.

Ale o tom neskôr.

P: Zdá sa, že som pochopil. Pozrite, čo to robí:

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"]

A spustenie démonov predefiníme pri spustení kontajnera?

AI: Áno, úplne správne. Mimochodom, môžeš použiť CMD aj ENTRYPOINT. A zistiť, v čom je rozdiel, to je tvoje domáce zadanie. Na túto tému je na Habre dobrá článok.

Tak, poďme ďalej. Sťahuješ inštalačný súbor pre node, ale nie je žiadna záruka, že obsahuje to, čo potrebuješ. Je potrebné pridať validáciu. Napríklad takto:

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/* 

Kontrolná suma ti umožní skontrolovať, či si stiahol správny súbor.

P: Ale ak sa súbor zmení, tak kompilácia neprebehne.

AI: Áno, a prekvapivo, to je tiež výhoda. Zistíš, že sa súbor zmenil, a budeš si môcť pozrieť, čo sa tam zmenilo. Kto vie, možno tam pridali skript, ktorý odstraňuje všetko, na čo natrafí, alebo vytvára backdoor.

P: Ďakujem. Takže konečný Dockerfile bude vyzerať takto:

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 Ivanovič, ďakujem za pomoc. Už musím bežať, potrebujem ešte spraviť 10 commitov za dnes.

Igor Ivanovič, zastavujúc ponáhľajúceho sa kolegu, si dáva dúšok silnej kávy. Po zamyslení nad SLA 99.9% a kódom bez chýb sa pýta.

AI: A kde uchovávate logy?

P: Samozrejme, v production.log. Mimochodom, ako získame prístup bez ssh?

AI: Ak ich necháte v súboroch, riešenie už bolo pre vás vymyslené. Príkaz docker exec umožňuje vykonať akýkoľvek príkaz v kontejnery. Napríklad, môžete urobiť cat pre logy. A ak použijete kľúč -it a spustíte bash (ak je nainštalovaný v kontejnery), získate interaktívny prístup k kontejnery.

Ale uchovávať logy v súboroch nestojí za to. Minimálne to vedie k nekontrolovateľnému rastu kontajnera, logy nikto nerotuje. Všetky logy treba posielať do stdout. Tam ich už môžete pozrieť pomocou príkazu docker logs.

P: Igor Ivanovič, a čo keby sme previedli logy do pripojenej adresáre na fyzickej node, ako údaje používateľov?

AI: Dobre, že si nezabudol presunúť údaje uložené na disku node. S logmi je to tiež možné, len nezabudni nastaviť rotáciu.
Všetko, môžeš bežať.

P: Igor Ivanovič, poraďte, čo si mám prečítať?

II: Na začiatok si prečítaj odporúčania od vývojárov Dockeru, ťažko by niekto poznal Docker lepšie ako oni.

A ak chceš zožiť prax, choď na intenzívny kurz. Veď teória bez praxe je mŕtva.

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster