Docker: ne štetni saveti

V komentarjih k mojemu članku Docker: вредные советы je bilo veliko prošenj, da pojasnim, zakaj je opisan Dockerfile tako grozljiv.

Kratka vsebina prejšnje epizode: dva razvijalca v napetem roku sestavljata Dockerfile. Med procesom jima pride na pomoč Ops Igor Ivanovič. Končni Dockerfile je tako slab, da je Igor Ivanovič na robu infarkta.

Docker: ne štetni saveti

Zdaj bomo razjasnili, kaj je narobe s tem Dockerfile-om.

Torej, minil je teden dni.

Razvijalec Peter se v jedilnici sreča s Ops Igorjem Ivanovičem ob skodelici kave.

P: Igor Ivanovič, ali ste zelo zaposleni? Rad bi se pogovoril, kje smo zagrešili napake.

II: To je dobro, redko srečaš razvijalce, ki jih zanima delovanje.
Za začetek se dogovorimo o nekaterih stvareh:

  1. Ideologija Dockera: en kontejner — en proces.
  2. Manjši kot je kontejner, bolje je.
  3. Več kot se vzame iz predpomnilnika, bolje je.

P: Zakaj mora biti v enem kontejnerju en proces?

II: Docker ob zagonu kontejnerja spremlja stanje procesa s pid 1. Če proces umre, Docker poskuša znova zagnati kontejner. Recimo, da imate v kontejnerju zagnanih več aplikacij ali glavna aplikacija ni zagnana s pid 1. Če proces umre, Docker o tem ne izve.

Če ni več vprašanj, pokažite vaš Dockerfile.

In Peter je pokazal:

FROM ubuntu:latest

# Kopiramo izvorno kodo
COPY .\/ \/app
WORKDIR \/app

# Posodobimo seznam paketov
RUN apt-get update 

# Posodobimo pakete
RUN apt-get upgrade

# Namestimo potrebne pakete
RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor

# Namestimo bundler
RUN gem install bundler

# Namestimo nodejs, ki se uporablja za gradnjo statike
RUN curl -sL https:\/\/deb.nodesource.com\/setup_9.x | sudo bash -
RUN apt-get install -y nodejs

# Namestimo odvisnosti
RUN bundle install --without development test --path vendor\/bundle

# Očistimo predpomnilnike
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
# Zagnamo skripto ob zagonu kontejnerja, ki bo zagnala vse ostalo.
CMD ["\/app\/init.sh"]

II: Ooh, pojdimo korak za korakom. Začnimo z prvo vrstico:

FROM ubuntu:latest

Vzeli ste oznako latest. Uporaba oznake latest privede do nepredvidljivih posledic. Predstavljajte si, da vzdrževalec slike gradi novo različico slike z drugim seznamom programske opreme in ta slika dobi oznako latest. In vaš kontejner se v najboljšem primeru preneha graditi, v najslabšem pa dobite napake, ki jih prej niste imeli.

Zberete sliko s celotnim operacijskim sistemom z velikim številom nepotrebne programske opreme, kar povečuje velikost kontejnerja. In več kot je programske opreme, večje so luknje in ranljivosti.

Poleg tega, večja kot je slika, več prostora zaseda na gostitelju in v registru (kjer pa shranjuješ slike)?

P: Da, seveda, imamo register, pa si ga nastavil ti.

AI: Tako, o čem sem to govoril?.. Ah ja, prostornine… Tako narašča obremenitev omrežja. Za posamezno sliko je to neopazno, vendar ko poteka neprekinjen postopek sestavljanja, testiranja in namestitve, to postane opazno. In če nimaš Božanskega načina na AWS, te dodatno čaka še račun v vesoljskih številkah.

Zato je treba izbrati najustreznejšo sliko, z natančno različico in minimalno programsko opremo. Na primer, vzemi: FROM ruby:2.5.5-stretch

P: O, razumem. Kako in kje lahko pogledam obstoječe slike? Kako razumeti, katera je potrebna?

AI: Običajno slike pridejo iz dockerhub-a, ne zamenjaj s porn hubom :). Za sliko običajno obstaja več zbirk:
Alpine: slike so zbrane na minimalistični različici Linuxa, le 5 MB. Njena slabost: zgrajena je s собственной реализацией libc, standardni paketi v njej ne delujejo. Iskanje in namestitev potrebnega paketa bo vzelo kar nekaj časa.
Scratch: osnovna slika, ne uporablja se za sestavljanje drugih slik. Namenjena je izključno za zagon binarnih, pripravljenih podatkov. Idealna je za zagon binarnih aplikacij, ki vključujejo vse potrebno, na primer go-aplikacije.
Na osnovi kakršnegakoli operacijskega sistema, na primer Ubuntu ali Debian. Tukaj mislim, da ni treba dodatno razlagati.

AI: Zdaj moramo namestiti vse dodatne pakete in očistiti predpomnilnike. In takoj lahko odstranimo apt-get upgrade. V nasprotnem primeru, pri vsakem sestavljanju, kljub fiksnemu označevanju osnovne slike, pridobimo različne slike. Posodabljanje paketov v sliki je naloga skrbnika, kar spremlja sprememba oznake.

P: Da, sem poskušal to narediti, in mi je uspelo takole:

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: Ni slabo, vendar je tudi tukaj še veliko prostora za izboljšave. Poglej, ta ukaz:

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

… ne briše podatke iz konačne slike, nego samo stvara dodatni sloj bez tih podataka. Ispravno je:

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

Ali to nije sve. Što imaš, Ruby? Tada ne trebaš kopirati ceo projekt na početku. Dovoljno je kopirati Gemfile i Gemfile.lock.

Sa ovim pristupom, bundle install neće se izvršavati pri svakoj promeni izvornih datoteka, već samo ako se promenio Gemfile ili Gemfile.lock.

Iste metode rade i za druge jezike sa menadžerima zavisnosti, kao što su npm, pip, composer i druge zasnovane na datoteci sa spiskom zavisnosti.

I konačno, sećaš se, na početku sam govorio o ideologiji Dockera 'jedan kontejner — jedan proces'? To znači da supervisor nije potreban. Takođe, ne treba instalirati systemd, iz istih razloga. U suštini, Docker sam jeste supervisor. Kada pokušavaš da pokreneš više procesa u njemu, to je kao da u jednom procesu supervisor pokrećeš više aplikacija.
Kada kreiraš, napravićeš jedinstvenu sliku, a zatim pokrenuti željeni broj kontejnera, tako da svaki ima jedan proces.

Ali o tome kasnije.

P: Čini se da sam razumeo. Pogledajte šta se dešava:

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 da li ćemo prepraviti pokretanje demona prilikom pokretanja kontejnera?

AI: Da, sve je tačno. Uostalom, možeš koristiti i CMD i ENTRYPOINT. A da shvatiš u čemu je razlika, to ti je domaći zadatak. Na tu temu na Habr-u postoji dobar članek.

Dakle, idemo dalje. Preuzimaš datoteku za instalaciju node-a, ali pritom nema nikakve garancije da će u njoj biti ono što ti treba. Treba dodati validaciju. Na primer, ovako:

ПУСК 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/* 

По контрольной сумме вы можете проверить, что скачали правильный файл.

П: Но если файл изменится, сборка не пройдет.

ИИ: Да, странно, но это тоже плюс. Вы узнаете, что файл изменился, и сможете посмотреть, что там изменилось. Возможно, добавили, например, скрипт, который удаляет все, до чего дотянется, или создает бэкдор.

П: Спасибо. Получается, конечный Dockerfile будет выглядеть так:

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

П: Игорь Иванович, спасибо за помощь. Мне уже пора бежать, надо еще 10 коммитов за сегодня сделать.

Игорь Иванович, остановив взглядом торопливого коллегу, делает глоток крепкого кофе. Поразмыслив несколько секунд об SLA 99.9% и коде без багов, он задает вопрос.

ИИ: А где вы храните логи?

П: Конечно, в production.log. Кстати, а как мы без ssh получим к ним доступ?

ИИ: Если вы оставите логи в файлах, решение уже придумано. Команда docker exec позволяет выполнить любую команду в контейнере. Например, вы можете сделать cat для логов. А используя ключ -it и запустив bash (если он установлен в контейнере), вы получите интерактивный доступ к контейнеру.

Но хранить логи в файлах не стоит. Как минимум это приводит к бесконтрольному росту контейнера, логи же никто не ротирует. Все логи нужно отправлять в stdout. Их уже можно посмотреть с помощью команды docker logs.

П: Игорь Иванович, может, вынести логи в смонтированную директорию, на физическую ноду, как данные пользователей?

ИИ: Хорошо, что вы не забыли вынести данные, загруженные на диск ноды. С логами так тоже можно, только не забудьте настроить ротирование.
Все, можете бежать.

P: Igor Ivanovič, prosim vas, priporočite, kaj naj preberem?

II: Za začetek preberi priporočila razvijalcev Dockerja, malo verjetno, da kdo ve o Dockru bolje kot oni.

Če pa želiš opraviti prakso, pojdi na intenzivni tečaj. Teorija brez prakse je mrtva.

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster