Docker: Nepatřičné rady

V komentářích k mému článku Docker: škodlivé rady bylo mnoho žádostí vysvětlit, čím je tak hrozný popsaný Dockerfile.

Krátké shrnutí předchozího dílu: dva vývojáři mají tvrdý termín a sestavují Dockerfile. V procesu k nim přijde Ops Igor Ivanovič. Konečný Dockerfile je tak špatný, že se Igor Ivanovič dostává na pokraj infarktu.

Docker: Nepatřičné rady

Teď si posvítíme na to, co je s tímto Dockerfile špatně.

Takže, uběhl týden.

Dev Petya se potkává v jídelně u šálku kávy s Ops Igorem Ivanovičem.

P: Igore Ivanoviči, jste velmi zaneprázdněn? Rád bych se podíval, kde jsme udělali chybu.

II: To je dobře, ne často potkáte vývojáře, které zajímá provoz.
Pro začátek si ujasněme několik věcí:

  1. Ideologie Dockeru: jeden kontejner — jeden proces.
  2. Čím menší kontejner, tím lépe.
  3. Čím více se bere z cache, tím lépe.

P: Proč by měl být v jednom kontejneru jen jeden proces?

II: Docker při spuštění kontejneru sleduje stav procesu s pid 1. Pokud proces umře, Docker se pokusí kontejner restartovat. Předpokládejme, že máte v kontejneru spuštěno několik aplikací nebo hlavní aplikace není spuštěna s pid 1. Pokud proces umře, Docker o tom neví.

Pokud nejsou žádné další otázky, ukazujte svůj Dockerfile.

A Petya ukázal:

FROM ubuntu:latest

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

# Aktualizujeme seznam balíčků
RUN apt-get update 

# Aktualizujeme balíčky
RUN apt-get upgrade

# Instalujeme potřebné balíčky
RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor

# Instalujeme bundler
RUN gem install bundler

# Instalujeme nodejs používaný pro sestavení statiky
RUN curl -sL https:\/\/deb.nodesource.com\/setup_9.x | sudo bash -
RUN apt-get install -y nodejs

# Instalujeme závislosti
RUN bundle install --without development test --path vendor\/bundle

# Čistíme 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
# Spouštíme skript při startu kontejneru, který spustí všechno ostatní.
CMD ["\/app\/init.sh"]

II: Ach, tak se pojďme podívat postupně. Začneme prvním řádkem:

FROM ubuntu:latest

Berete tag latest. Použití tagu latest může vést k nepředvídatelným následkům. Představte si, že maintainer obrazu sestaví novou verzi obrazu s jiným seznamem programů, tento obraz dostane tag latest. A váš kontejner se v lepším případě přestane skládat, v horším případě chytíte chyby, které zde dříve nebyly.

Berete obraz s plnohodnotným operačním systémem s velkým množstvím zbytečného softwaru, což zbytečně zvětšuje objem kontejneru. A čím více softwaru, tím více otvorů a zranitelností.

Navíc čím větší je obraz, tím více místa zabírá na hostiteli a v registru (přece někde uchováváte obrazy)?

P: Ano, samozřejmě, máme registr, vy jste ho přece nastavovali.

AI: Tak, o čem to mluvím?.. Ach ano, objemy… Roste také zatížení sítě. Pro jednotlivý obraz to není znatelné, ale když probíhá neustálá sestava, testování a nasazení, je to patrné. A pokud nemáte God’s mode na AWS, ještě vám přijde tučné vyúčtování.

Proto je potřeba vybírat co nejvhodnější obraz, s přesnou verzí a minimem softwaru. Například, vezměte: FROM ruby:2.5.5-stretch

P: Aha, chápu. Jak a kde se podívat na dostupné obrazy? Jak zjistit, který potřebuji?

AI: Obvykle se obrazy berou z Docker Hubu, nemi­ljte to s Pornhubem :). Pro obraz obvykle existuje několik sestavení:
Alpine: obrazy jsou postaveny na minimalistickém obraze Linuxu, pouze 5 MB. Jeho nevýhoda: je založen na vlastní implementaci libc, standardní balíčky v něm nefungují. Hledání a instalace potřebného balíčku vám zabere spoustu času.
Scratch: základní obraz, nepoužívá se pro sestavování jiných obrazů. Je určen výhradně pro spouštění binárních, připravených dat. Ideálně se hodí pro spuštění binárních aplikací, které zahrnují vše potřebné, například aplikace v Go.
Na základě nějakého operačního systému, například Ubuntu nebo Debian. No, tady snad není potřeba vysvětlovat.

AI: Teď je potřeba nainstalovat všechny dodatečné balíčky a uklidit keš. A můžeme rovnou vyhodit apt-get upgrade. Jinak při každé sestavě, navzdory pevně danému tagu základního obrazu, budou vznikat různé obrazy. Aktualizace balíčků v obraze je úkolem údržbáře, je spojena se změnou tagu.

P: Ano, zkoušel jsem to udělat, podařilo se mi 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/*

AI: Nešpatně, ale je tu také co zlepšovat. Podívej se, tato příkaz:

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

… neodstraňuje data z výsledného obrazu, ale jen vytváří dodatečnou vrstvu bez těchto dat. Správně 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 není všechno. Co máte tam, Ruby? Takže není třeba kopírovat celý projekt na začátku. Stačí zkopírovat Gemfile a Gemfile.lock.

Při tomto přístupu se bundle install nevykoná při každé změně zdrojáků, ale pouze pokud se změnil Gemfile nebo Gemfile.lock.

Tyto metody fungují i pro jiné jazyky s manažery závislostí, jako jsou npm, pip, composer a další poháněné souborovou přílohou se seznamem závislostí.

A konečně, pamatuješ, jak jsem na začátku mluvil o ideologii Dockeru 'jeden kontejner - jeden proces'? To znamená, že supervisor není potřeba. Také není nutné instalovat systemd, z těchto stejných důvodů. V podstatě je Docker sám supervisor. A když se snažíš spustit několik procesů v něm, je to jako spouštět několik aplikací v jednom procesu supervizora.
Při sestavování vytvoříte jeden obraz a poté spustíte požadovaný počet kontejnerů, aby v každém běžel jeden proces.

Ale o tom později.

Otázka: Zdá se, že rozumím. Podívejte se, co vychází:

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 přepsání démonů provedeme při spuštění kontejneru?

AI: Ano, to je správně. Mimochodem, lze použít jak CMD, tak ENTRYPOINT. A zjistit, v čem je rozdíl, to je tvoje domácí úkol. Na toto téma je na Habru dobrý článek. neaktuální statistika.

Tak, jdeme dál. Stahuješ instalační soubor pro node, ale přitom není žádná záruka, že v něm bude to, co potřebuješ. Je třeba přidat validaci. Napří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/* 

Pomocí kontrolní součtu můžeš zkontrolovat, že jsi stáhl správný soubor.

P: Ale pokud se soubor změní, pak sestavení neproběhne.

AI: Ano, a jak podivné, to je také výhoda. Zjistíš, že se soubor změnil, a můžeš se podívat, co se tam změnilo. Kdo ví, třeba přidali skript, který smaže vše, co se dotkne, nebo udělá backdoor.

P: Děkuji. Takže konečný Dockerfile bude vypadat 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: Igore Ivanoviči, děkuji za pomoc. Už musím běžet, potřebuji ještě udělat 10 commitů dnes.

Igor Ivanovič, zastavující spěchajícího kolegu pohledem, si dá lok silného kávy. Po několika sekundách přemýšlení o SLA 99.9% a kódu bez chyb se ptá.

AI: A kde ukládáte logy?

P: Samozřejmě, v production.log. Mimochodem, jak se k nim dostaneme bez ssh?

AI: Pokud je necháte v souborech, už pro vás existuje řešení. Příkaz docker exec umožňuje spustit jakýkoli příkaz v kontejneru. Například můžete použít cat pro logy. A pokud použijete klíč -it a spustíte bash (pokud je nainstalován v kontejneru), získáte interaktivní přístup k kontejneru.

Ale není dobré uchovávat logy v souborech. Minimálně to vede k nekontrolovanému růstu kontejneru, logy nikdo nerotuje. Všechny logy by měly být posílány do stdout. Tam je můžete již zkontrolovat pomocí příkazu docker logs.

P: Igore Ivanoviči, co kdybychom vyexportovali logy do připojeného adresáře na fyzickou uzel, jak data uživatelů?

AI: Dobře, že jsi nezapomněl na export dat uložených na disku uzlu. S logy to lze také, jen nezapomeň nastavit rotaci.
Můžeš běžet.

P: Igore Ivanoviči, co byste doporučil k přečtení?

II: Na začátek si přečti doporučení od vývojářů Dockeru, stěží někdo zná Docker lépe než oni.

A pokud chceš absolvovat praxi, zajdi na intenzivní kurz. Vždyť teorie bez praxe je mrtvá.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster