Docker: sfaturi nevinovate

În comentariile la articolul meu Docker: sfaturi dăunătoare au fost multe cereri de a explica de ce Dockerfile-ul descris în el este atât de groaznic.

Rezumatul episodului anterior: doi dezvoltatori care se află sub un termen limită strict compun un Dockerfile. În proces, la ei vine Ops Igor Ivanovici. Dockerfile-ul final este atât de prost încât Igor Ivanovici ajunge la limita infarctului.

Docker: sfaturi nevinovate

Acum vom analiza ce nu este în regulă cu acest Dockerfile.

Deci, a trecut o săptămână.

Dezvoltatorul Petya se întâlnește la cantină cu Ops Igor Ivanovici pentru o ceașcă de cafea.

P: Igor Ivanovici, sunteți foarte ocupat? Aș dori să înțeleg unde am greșit.

II: Este bine, nu întâlnești des dezvoltatori interesați de exploatare.
Pentru început, să ne punem de acord asupra unor lucruri:

  1. Ideologia Docker: un container — un proces.
  2. Cu cât containerul este mai mic, cu atât mai bine.
  3. Cu cât se folosește mai mult din cache, cu atât mai bine.

P: Dar de ce în fiecare container ar trebui să fie un singur proces?

II: Docker monitorizează starea procesului cu pid 1 atunci când pornește containerul. Dacă procesul moare, Docker încearcă să repornească containerul. Să spunem că ai mai multe aplicații rulate în container sau aplicația principală nu este rulată cu pid 1. Dacă procesul moare, Docker nu va ști despre asta.

Dacă nu mai sunt întrebări, arată-mi Dockerfile-ul tău.

Și Petya a arătat:

FROM ubuntu:latest

# Copiem codul sursă
COPY .\/ \/app
WORKDIR \/app

# Actualizăm lista de pachete
RUN apt-get update 

# Actualizăm pachetele
RUN apt-get upgrade

# Instalăm pachetele necesare
RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor

# Instalăm bundler
RUN gem install bundler

# Instalăm nodejs care este folosit pentru construirea staticelor
RUN curl -sL https:\/\/deb.nodesource.com\/setup_9.x | sudo bash -
RUN apt-get install -y nodejs

# Instalăm dependențele
RUN bundle install --without development test --path vendor\/bundle

# Curățăm cache-urile
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
# Rulăm scriptul la pornirea containerului, care va lansa tot restul.
CMD ["\/app\/init.sh"]

II: Oh, hai să analizăm pe rând. Să începem cu prima linie:

FROM ubuntu:latest

Ieșiți cu eticheta latest. Utilizarea etichetei latest duce la consecințe imprevizibile. Imaginează-ți, mentinatorul imaginii construiește o nouă versiune a imaginii cu o listă diferită de software, acea imagine primește eticheta latest. Și containerul tău, în cel mai bun caz, încetează să se construiască, iar în cel mai rău caz, te confrunți cu bug-uri care anterior nu existau.

Ia iei o imagine cu un sistem de operare complet, plin de software inutil, ceea ce umflă volumul containerului. Cu cât mai mult software, cu atât mai multe vulnerabilități și breșe de securitate.

În plus, cu cât imaginea este mai mare, cu atât ocupă mai mult spațiu pe gazdă și în registry (undeva trebuie să stochezi imaginile, nu-i așa?).

P: Da, desigur, avem registry, tu l-ai și configurat.

I: Așa, despre ce vorbeam?.. Ah, da, volumele… De asemenea, crește și încărcarea rețelei. Pentru o singură imagine, asta trece neobservat, dar când avem o construire continuă, teste și implementări, devine simțit. Iar dacă nu ai modul divin pe AWS, vei primi și o factură astronomică.

De aceea, trebuie să alegi imaginea cea mai potrivită, cu versiunea exactă și minimul de software. De exemplu, ia: FROM ruby:2.5.5-stretch

P: Oh, înțeleg. Dar cum și unde pot vedea imaginile disponibile? Cum pot ști ce am nevoie?

I: De obicei, imaginile provin de pe Docker Hub, nu confunda cu Pornhub :). De obicei, pentru o imagine există mai multe build-uri:
Alpine: imaginile sunt construite pe o imagine Linux minimalistă, cu doar 5 MB. Dezavantajul este că este construit cu o implementare proprie a libc, iar pachetele standard nu funcționează. Găsirea și instalarea pachetului necesar va dura ceva timp.
Scratch: imaginea de bază, care nu este folosită pentru a construi alte imagini. Este destinată exclusiv pentru a rula binare, date pregătite. Se potrivește perfect pentru a rula aplicații binare care includ tot ce este necesar, cum ar fi aplicațiile go.
Pe baza unei anumite distribuții de sistem de operare, cum ar fi Ubuntu sau Debian. Ei bine, cred că nu trebuie să explic mai mult.

I: Acum trebuie să instalăm toate pachetele suplimentare și să curățăm cache-urile. Și putem elimina imediat apt-get upgrade. Altfel, la fiecare construit, în ciuda etichetei fixe a imaginii de bază, vor rezulta imagini diferite. Actualizarea pachetelor în imagine reprezintă o sarcină pentru menținator, însoțită de modificarea etichetei.

P: Da, am încercat să fac asta, iar rezultatul meu a fost:

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

I: Nu este rău, dar mai este loc de îmbunătățiri. Uite, această comandă:

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

… nu șterge datele din imaginea finală, ci doar creează un strat suplimentar fără aceste date. Corect ar fi:

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

Dar asta nu e tot. Ce ai acolo, Ruby? Atunci nu trebuie să copiezi întregul proiect de la început. E suficient să copiezi Gemfile și Gemfile.lock.

Cu acest abord, bundle install nu va fi executat la fiecare modificare a surselor, ci doar dacă Gemfile sau Gemfile.lock s-au schimbat.

Aceleași metode funcționează și pentru alte limbaje cu manageri de dependențe, cum ar fi npm, pip, composer și alte bazate pe un fișier cu lista de dependențe.

Și în cele din urmă, îți amintești, la început, am vorbit despre ideologia Docker „un container — un proces”? Asta înseamnă că supervisorul nu e necesar. De asemenea, nu trebuie să instalezi systemd, din aceleași motive. Practic, Docker este deja un supervisor. Și când încerci să rulezi mai multe procese în el, este ca și cum ai rula mai multe aplicații într-un singur proces supervisor.
În timpul construirii, vei face o singură imagine, iar apoi vei lansa numărul dorit de containere pentru ca fiecare să ruleze un singur proces.

Dar despre asta mai târziu.

Î: Se pare că am înțeles. Uite ce iese:

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

Dar redarea demonilor va fi redefinită la pornirea containerului?

AI: Da, exact. Apropo, poți folosi atât CMD, cât și ENTRYPOINT. A înțelege care este diferența, e tema ta pentru acasă. Despre asta există un articol bun pe Habr. articol.

Așa, hai să continuăm. Descarci fișierul pentru instalarea node, dar nu există nicio garanție că acesta va conține ceea ce ai nevoie. Trebuie să adaugi validare. De exemplu, așa:

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

Prin suma de control poți verifica că ai descărcat fișierul corect.

P: Dar dacă fișierul se schimbă, compilarea nu va reuși.

AI: Da, și e curios că acesta este un avantaj. Vei ști că fișierul s-a schimbat și vei putea verifica ce s-a modificat. Poate au adăugat, de exemplu, un script care șterge tot ce atinge sau creează un backdoor.

P: Mulțumesc. Deci, Dockerfile-ul final va arăta așa:

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 Ivanovici, mulțumesc pentru ajutor. Trebuie să plec, trebuie să fac încă 10 commit-uri azi.

Igor Ivanovici, oprind cu privirea colegul grăbit, ia o înghițitură din cafeaua tare. După câteva secunde de gândire asupra SLA-ului de 99.9% și codului fără bug-uri, pune o întrebare.

AI: Unde stocați log-urile?

P: Desigur, în production.log. Apropo, cum obținem acces la ele fără SSH?

AI: Dacă le lăsați în fișiere, s-a gândit deja la o soluție pentru voi. Comanda docker exec permite executarea oricărei comenzi în container. De exemplu, puteți folosi cat pentru log-uri. Și folosind cheia -it și rulând bash (dacă este instalat în container), veți obține acces interactiv la container.

Dar nu ar trebui să stocați log-urile în fișiere. Cel puțin asta duce la o creștere necontrolată a containerului, care nu rotește log-urile. Toate log-urile trebuie să meargă în stdout. Acolo le puteți verifica cu comanda docker logs.

P: Igor Ivanovici, poate să mut log-urile într-un director montat, pe nodul fizic, ca datele utilizatorilor?

AI: Bine că nu ai uitat să muți datele încărcate pe disc pe nod. La log-uri se poate face la fel, dar nu uita să configurezi rotirea.
Totul, poți pleca.

P: Igor Ivanov, poți să-mi recomanzi ce să citesc?

II: Începe cu recomandările dezvoltatorilor Docker, cu greu cineva cunoaște Docker mai bine decât ei.

Dacă vrei să faci practică, mergi la intensiv. Teoria fără practică este moartă.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster