Docker: niezła rada

W komentarzach do mojego artykułu Docker: zła rada było wiele próśb o wyjaśnienie, dlaczego opisany w nim plik Dockerfile jest tak okropny.

Podsumowanie poprzedniej serii: Dwóch programistów tworzy plik Dockerfile w krótkim terminie. W trakcie przychodzi do nich ops Igor Iwanowicz. Powstały plik Dockerfile jest tak zły, że sztuczna inteligencja jest o krok od zawału serca.

Docker: niezła rada

Teraz zastanówmy się, co jest nie tak z tym plikiem Dockerfile.

I tak minął tydzień.

Dev Petya spotyka się w jadalni przy filiżance kawy z ops Igorem Iwanowiczem.

P: Igorze Iwanowiczu, czy jesteś bardzo zajęty? Chciałbym się dowiedzieć, gdzie popełniliśmy błąd.

AI: To dobrze, nie często spotyka się programistów zainteresowanych eksploatacją.
Na początek ustalmy kilka rzeczy:

  1. Ideologia Dockera: jeden kontener – jeden proces.
  2. Im mniejszy pojemnik, tym lepiej.
  3. Im więcej zabierzesz ze skrytki, tym lepiej.

P: Dlaczego w jednym kontenerze miałby znajdować się jeden proces?

AI: Docker podczas uruchamiania kontenera monitoruje stan procesu z pid 1. Jeśli proces się zakończy, Docker próbuje zrestartować kontener. Załóżmy, że masz kilka aplikacji uruchomionych w kontenerze lub aplikacja główna nie działa z pid 1. Jeśli proces zakończy się, Docker nie będzie o tym wiedział.

Jeśli nie masz dalszych pytań, pokaż nam swój plik Dockerfile.

A Petya pokazała:

FROM ubuntu:latest

# Копируем исходный код
COPY ./ /app
WORKDIR /app

# Обновляем список пакетов
RUN apt-get update 

# Обновляем пакеты
RUN apt-get upgrade

# Устанавливаем нужные пакеты
RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor

# Устанавливаем bundler
RUN gem install bundler

# Устанавливаем nodejs используется для сборки статики
RUN curl -sL https://deb.nodesource.com/setup_9.x | sudo bash -
RUN apt-get install -y nodejs

# Устанавливаем зависимости
RUN bundle install --without development test --path vendor/bundle

# Чистим за собой кэши
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
# Запускаем скрипт, при старте контейнера, который запустит все остальное.
CMD ["/app/init.sh"]

AI: Och, uporządkujmy to. Zacznijmy od pierwszej linijki:

FROM ubuntu:latest

Bierzesz etykietę latest. Używanie tagu latest prowadzi do nieprzewidywalnych konsekwencji. Wyobraź sobie, że opiekun obrazu tworzy nową wersję obrazu z inną listą oprogramowania, obraz ten otrzymuje najnowszy tag. A Twój pojemnik w najlepszym przypadku przestanie się zbierać, a w najgorszym złapiesz błędy, które wcześniej nie istniały.

Robisz zdjęcie z pełnoprawnym systemem operacyjnym z dużą ilością niepotrzebnego oprogramowania, co zawyża objętość pojemnika. Im więcej oprogramowania, tym więcej dziur i luk w zabezpieczeniach.

Poza tym im większy obraz, tym więcej miejsca zajmuje na hoście i w rejestrze (czy przechowujesz gdzieś obrazy)?

P: Tak, oczywiście, mamy rejestr, ty go konfigurujesz.

AI: Więc o czym mówię?.. O tak, wolumeny... Obciążenie sieci również rośnie. W przypadku pojedynczego obrazu nie jest to zauważalne, ale w przypadku ciągłej kompilacji, testów i wdrażania jest to zauważalne. A jeśli nie masz trybu Bożego na AWS, to też dostaniesz kosmiczny rachunek.

Dlatego musisz wybrać najbardziej odpowiedni obraz, z dokładną wersją i minimalnym oprogramowaniem. Weźmy na przykład: FROM ruby:2.5.5-stretch

P: Och, rozumiem. Jak i gdzie mogę obejrzeć dostępne obrazy? Skąd mam wiedzieć, którego potrzebuję?

AI: Zwykle zdjęcia są pobierane z dockerhub, nie mylić z Pornhubem :). Zwykle istnieje kilka zestawów obrazu:
Alpejski: obrazy są gromadzone na minimalistycznym obrazie systemu Linux, tylko 5 MB. Jego wada: jest skompilowany z własną implementacją libc, standardowe pakiety w nim nie działają. Znalezienie i zainstalowanie wymaganego pakietu zajmie dużo czasu.
Scratch: obraz bazowy, nie używany do tworzenia innych obrazów. Jest przeznaczony wyłącznie do uruchamiania binarnych, przygotowanych danych. Idealny do uruchamiania aplikacji binarnych zawierających wszystko, czego potrzebujesz, takich jak aplikacje GO.
Oparty na dowolnym systemie operacyjnym, takim jak Ubuntu lub Debian. Cóż, myślę, że nie ma potrzeby wyjaśniać.

AI: Teraz musimy zainstalować wszystkie dodatki. pakiety i wyczyść pamięć podręczną. I możesz go od razu wyrzucić Aktualizacja apt-get. W przeciwnym razie przy każdej kompilacji, pomimo stałego znacznika obrazu bazowego, zostaną uzyskane różne obrazy. Aktualizacja pakietów na obrazku jest zadaniem opiekuna i wiąże się ze zmianą tagu.

P: Tak, próbowałem to zrobić, wyszło tak:

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: Nieźle, ale jest też nad czym pracować. Spójrz, oto to polecenie:

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

... nie usuwa danych z ostatecznego obrazu, a jedynie tworzy dodatkową warstwę bez tych danych. Poprawnie tak:

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 nie wszystko. Co tam masz, Ruby? Nie musisz wtedy kopiować całego projektu na początku. Wystarczy skopiować Gemfile i Gemfile.lock.

Przy takim podejściu instalacja pakietu nie będzie wykonywana przy każdej zmianie źródła, ale tylko w przypadku zmiany pliku Gemfile lub Gemfile.lock.

Te same metody działają dla innych języków z menadżerem zależności, jak npm, pip, Composer i inne bazujące na pliku z listą zależności.

I na koniec, pamiętacie, jak mówiłem na początku o ideologii Dockera „jeden kontener – jeden proces”? Oznacza to, że nadzorca nie jest potrzebny. Z tych samych powodów nie powinieneś instalować systemd. Zasadniczo sam Docker jest nadzorcą. A kiedy próbujesz uruchomić w nim wiele procesów, przypomina to uruchamianie wielu aplikacji w jednym procesie nadzorczym.
Podczas budowania utworzysz pojedynczy obraz, a następnie uruchomisz wymaganą liczbę kontenerów, aby w każdym działał jeden proces.

Ale o tym później.

P: Chyba rozumiem. Zobacz, co się stanie:

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

Czy możemy pominąć uruchomienie demonów podczas uruchamiania kontenera?

AI: Tak, zgadza się. Nawiasem mówiąc, możesz używać zarówno CMD, jak i ENTRYPOINT. A odkrycie, na czym polega różnica, jest twoją pracą domową. Na Habré jest dobry artykuł na ten temat artykuł.

Przejdźmy więc dalej. Pobierasz plik, aby zainstalować węzeł, ale nie ma gwarancji, że będzie on zawierał to, czego potrzebujesz. Musimy dodać walidację. Na przykład tak:

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

Korzystając z sumy kontrolnej, możesz sprawdzić, czy pobrałeś właściwy plik.

P: Ale jeśli plik się zmieni, kompilacja zakończy się niepowodzeniem.

AI: Tak i, co dziwne, jest to również plus. Będziesz wiedział, że plik się zmienił i będziesz mógł zobaczyć, co zostało w nim zmienione. Nigdy nie wiadomo, dodali, powiedzmy, skrypt, który usuwa wszystko, do czego może dotrzeć, lub tworzy backdoora.

Dziękuję. Okazuje się, że ostateczny plik Dockerfile będzie wyglądał następująco:

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 Iwanowicz, dziękuję za pomoc. Czas już uciekać, muszę dzisiaj wykonać jeszcze 10 zatwierdzeń.

Igor Iwanowicz, zatrzymując spojrzeniem pośpiesznego kolegę, upija łyk mocnej kawy. Po kilku sekundach zastanowienia się nad umową SLA na poziomie 99.9% i kodem wolnym od błędów, zadaje pytanie.

AI: Gdzie przechowujesz kłody?

P: Oczywiście w pliku Production.log. Swoją drogą tak, ale jak uzyskać do nich dostęp bez ssh?

AI: Jeśli zostawisz je w plikach, rozwiązanie zostało już dla Ciebie wymyślone. Polecenie docker exec umożliwia wykonanie dowolnego polecenia w kontenerze. Na przykład możesz zrobić cat dla kłód. I użycie klucza -to a uruchomienie basha (jeśli jest zainstalowane w kontenerze) zapewni interaktywny dostęp do kontenera.

Ale nie powinieneś przechowywać logów w plikach. Prowadzi to co najmniej do niekontrolowanego wzrostu pojemnika i nikt nie obraca kłód. Wszystkie logi należy wysłać na standardowe wyjście. Tam możesz je już przeglądać za pomocą polecenia dzienniki dokera.

P: Igorze Iwanowicz, może mógłbym umieścić logi w zamontowanym katalogu, na węźle fizycznym, jako dane użytkownika?

AI: Dobrze, że nie zapomniałeś usunąć danych załadowanych na dysk węzła. Możesz to zrobić również z dziennikami, tylko nie zapomnij skonfigurować rotacji.
To wszystko, możesz uciekać.

P: Igorze Iwanowiczu, czy możesz mi doradzić, co czytać?

AI: Najpierw przeczytaj rekomendacje od programistów Dockera, mało kto zna Dockera lepiej niż oni.

A jeśli chcesz odbyć staż, to idź intensywny. W końcu teoria bez praktyki jest martwa.

Źródło: www.habr.com

Dodaj komentarz