V komentářích k mému článku 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.

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í:
- Ideologie Dockeru: jeden kontejner — jeden proces.
- Čím menší kontejner, tím lépe.
- Čí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:latestBerete 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 , nemiljte 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. .
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 , stěží někdo zná Docker lépe než oni.
A pokud chceš absolvovat praxi, zajdi na . Vždyť teorie bez praxe je mrtvá.
Zdroj: habr.com
