docker tworzenie obrazow banner

Kurs Docker #02: Instalacja i tworzenie obrazów

Podziel się z innymi!

W tej części kursu przyjrzymy się głównie zagadnieniom związanym z obrazami dla kontenerów. Pokaże Ci, że tworzenie obrazów Docker jest stosunkowo proste. Jeżeli jeszcze nie miałeś okazji to polecam rzucić okiem na pierwszą część kursu, dostępną tutaj –> Kurs Docker #01: Wstęp i Architektura

Jeśli chcesz być na bieżąco i jako pierwszy dowiedzieć się o kolejnych częściach darmowego Kursu Dockerzapisz się do Newsletter’a!


Instalacja Docker

Zanim zaczniemy mówić o obrazach koniecznym jest zainstalowanie samego Docker! Tak jak już wspominałem w poprzedniej części kursu, potrzebujemy narzędzi klienckich i samego silnika konteneryzacji.

Aktualną matryce kompatybilności systemów operacyjnych i architektur znajdziesz w oficjalnej dokumentacji Docker –> Install Docker Engine. Jako że najczęściej w dużych infrastrukturach IT stosuje się OS’y z rodziny RHEL to skupię się na instalacji na CentOS 😉

Aby Zainstalować Docker możemy przy pomocy jednej z trzech metod:

  • z repozytorium YUM
  • z pakietu RPM bezpośrednio – dla maszyn bez dostępu do repozytorium
  • przy pomocy skryptu dostarczanego przez Docker Inc. – instalacja wersji innych niż stable – nie zalecana na produkcji

Maszyna na której chcemy wykonać instalacje, musi spełniać nastepujące warunki:

  • na dzień pisania tego artykułu – CentOS powinien być w wersji 7
  • katalog /var/lib/docker jeżeli istnieje musi być pusty
  • repozytorium centos-extras musi być aktywne

Skupmy się na metodzie pierwszej:

  • Instalujemy pakiet zawierający yum-config-manager
[root@docker ~]# yum install -y yum-utils
  • Dodajemy repozytorium YUM dla Docker Community Edition
[root@docker ~]# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
  • Instalujemy silnik Docker wraz z narzędziami klienckimi
[root@docker ~]#  yum install docker-ce docker-ce-cli containerd.io

Poprawny GPG Key: 060A 61C5 1B55 8A7F 742B 77AA C52F EB6B 621E 9F35 🙂

  • Uruchamiamy daemon’a Docker i włączamy autostart wraz z startem OS
[root@docker ~]# systemctl start docker
[root@docker ~]# systemctl enable docker

Docker zainstalowany! To teraz sprawdźmy czy daemon wystartował i jest nam w stanie podać swój numer wersji:

[root@docker ~]# docker version
Client: Docker Engine - Community
 Version:           19.03.13
 API version:       1.40
 Go version:        go1.13.15
 Git commit:        4484c46d9d
 Built:             Wed Sep 16 17:03:45 2020
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.13
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       4484c46d9d
  Built:            Wed Sep 16 17:02:21 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.3.7
  GitCommit:        8fba4e9a7d01810a393d5d25a3621dc101981175
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

Jak widzisz zainstalowaliśmy wersje Community Edition 19.03.13.

Komenda docker version zwraca nam wersje dla narzędzi klienckich i serwera. Co oznacza, że daemon działa i możemy już zacząć omawiać tworzenie obrazów docker!


Koncepcja obrazu kontenera

Każdy kontener który odpalisz na Docker lub Kubernetes będzie korzystał z obrazu. Świetną analogią, aby zrozumieć zależność między kontenerem a obrazem, jest porównanie do klasy i obiektów znanych z języków programowania. Obraz zawiera w sobie to, co ma znaleść się w kontenerze z niego uruchomionym. Podczas tworzenia obrazu to Ty określasz jakie pliki będą wchodzić w skład środowiska uruchomieniowego którym jest kontener.

Każdy obraz Docker składa się z wielu warstw. Przykładowo:

  • pierwsza warstwa to pliki OS
  • druga warstwa to pliki binarne Javy
  • trzecia to już pliki samej aplikacji którą chcemy uruchomić w kontenerze

Jak zapewne zauważyłeś obraz zawiera niezbędne minimum żeby dana aplikacja mogła działać. Jest to jedna z głównych zasad tworzenia dobrych obrazów Docker – w obrazie zawsze umieszczamy tylko niezbędne do działania aplikacji pliki, im mniejszy obraz tym lepiej! Świetnym przykładem jest oficjalny obraz Alpine Linux, który ma wielkość około 4MB i często używany jest jako obraz bazowy.

Czym jest obraz bazowy? Jest to obraz na podstawie którego budujesz swój obraz. W naszym przykładzie obraz bazowy zawierał pliki systemu operacyjnego. I tu ciekawostka…. każdy obraz może być użyty jako bazowy dla innych obrazów.

Odwołując się do naszego przykładu, moglibyśmy jako obraz bazowy wybrać gotowy obraz z Java w jakiejś konkretnej wersji i do niego dorzucić tylko naszą aplikacje. Ktoś inny z kolei może wsiąść nasz obraz z aplikacją i na jego podstawie zbudować swój, który będzie bogatszy na przykład o jakieś dodatkowe skrypty. Miedzy innymi dla tego obrazy mają budowę warstwową.

Z pojedynczego obrazu może być uruchomione wiele kontenerów. Sam obraz jest Read-Only(RO). Każdy kontener kiedy startuje tworzy dodatkową warstwę Read-Write(RW) – do której ma dostęp tylko on. Dzięki takiemu podejściu możesz tworzyć i modyfikować pliki w uruchomionym kontenerze, nie wpływając jednocześnie na sam obraz. Współdzielenie obrazu z kolei przekłada się choćby na oszczędność miejsca na dyskach.


Tworzenie obrazu Docker

Aby zbudować własny obraz trzeba stworzyć plik Dockerfile. Plik ten jest zbiorem instrukcji, które mają zostać wykonane w procesie budowania obrazu.

Dockerfile

Najważniejsze instrukcje dostępne w Dockerfile

InstrukcjaOpisSkładnia
FROMWskazanie obrazu bazowegoFROM [–platform=<platform>] <image>[:<tag>] [AS <name>]
ARGZmienna dostępna tylko przy budowaniu obrazu. ARG <name>[=<default value>]
ENVZdefiniowanie runtime variable, standardowa zmienna która jest dostępna dla działającego kontenera. ENV <key> <value>
LABELDodanie własnych metadanych do obrazuLABEL <key>=<value> <key>=<value>
RUNWykonanie komendy w obrazie. Składnia pierwsza wykonuje w domyślnym shell systemu. Składnia druga wykonuje komendę bez wywołania shell. Komenda tworzy zawsze nową warstwę!RUN <command>
RUN [„exec”,”param1″,”param2″]
COPYDodanie do obrazu plików.Komenda tworzy zawsze nową warstwę!ADD [–chown=<user>:<group>] <src>… <dest>
ADDDodanie do obrazu plików – komenda powoli wycofywana – umie kopiować pliki po sieci. Komenda tworzy zawsze nową warstwę!COPY [–chown=<user>:<group>] <src>… <dest>
VOLUMEDodanie punktu montowania pod wolumen zewnętrznyVOLUME [„<path>”]
ENTRYPOINTWskazanie pliku binarnego lub skryptu który wystartuje jako pierwszy po starcie konteneraENTRYPOINT [„executable”, „param1”, „param2”]
COMMANDUstawienie wartości domyślnych dla parametrów pliku binarnego z ENTRYPOINT.COMMAND [ „param1”, „param2”]
EXPOSEWystawienie portu, instrukcja nic nie robi, pełni role dokumentacyjną 😉EXPOSE <port> [<port>/<protocol>]
USERUstawienie użytkownika z którego będą uruchamiane instrukcje RUN, COMMAND, ENTRYPOINTUSER <user>[:<group>]
WORKDIRUstawienie katalogu bieżącego dla instrukcji RUN, COMMAND, ENTRYPOINT, ADD i COPYWORKDIR <path>
ONBUILDDodanie dodatkowych instrukcji które zostaną wykonane w przypadku użycia obrazu jako obrazu bazowego.ONBUILD <dockerfile_command>

Sztuczka z ARG i ENV

Instrukcja ARG definiuje zmienną która dostępna jest tylko w czasie procesu budowania obrazu. A co jeśli chciałbyś, aby informacja w niej zawarta była dostępna także w działającym kontenerze?

Na przykład kiedy chcesz z jednego pliku Dockerfile budować obrazy z różnymi wersjami silnika bazodanowego PostgreSQL, a przekazany przy budowie numer wersji jest Ci potrzebny jako zmienna systemowa?

Wystarczy wykonać prostą sztuczkę – definiujemy zmienne typu ARG i ENV o identycznych nazwach, a następnie do zmiennych ENV przypisujemy zmienne ARG(opcjonalnie podając wartości default – ciąg znaków po :- )

ARG PG_MAJOR
ARG PG_MINOR

ENV PG_MAJOR=${PG_MAJOR:-11}
ENV PG_MINOR=${PG_MINOR:-5}

Przykładowy Dockerfile

FROM centos:7
LABEL ADMIN=Lukas ADMINMAIL=lukas@lukas.int

ARG HTTPD_VERSION
ENV HTTPD_VERSION=${HTTPD_VERSION:-2.4.6}

RUN yum update -y; \
    yum -y install \
    yum install httpd-${HTTPD_VERSION}

COPY –chown=apache:apache index.html /var/www/html/

ENTRYPOINT ["/usr/sbin/httpd","-D", "FOREGROUND"]

EXPOSE 80

Co robi nasz Dockerfile?

  • jako obraz bazowy wybiera centos:7
  • dodaje metadane zawierające informacje o budującym obraz administratorze
  • tworzy zmienną na czas budowania – pozwalająca wybrać przy budowaniu obrazu jaka wersja Apache znajdzie się w obrazie
  • robi trick ARG/ENV – aby wybrana wersja była dostępna w kontenerze w zmiennej
  • aktualizuje system operacyjny i instaluje wybraną wersje HTTPD
  • kopiuje plik z przykładową stroną internetową, ustawiając na nim odpowiedniego właściciela i grupę
  • wskazuje plik binarny który ma być uruchamiany po starcie kontenera

No i mamy wyszczególniony port na którym będzie działać Apache 😀

Tak jak wspominałem EXPOSE pełni role dokumentacji, możemy go pominąć i nic się złego nie stanie.


Dobre praktyki przy tworzeniu obrazów

Tworzenie obrazów Docker wymaga trzymania się pewnych zasad. Dobry obraz to taki który:

  • posiada minimalną liczbę warstw – należy łączyć komendy w ciągi po kilka, gdy piszemy instrukcje RUN
  • ma minimalny możliwy rozmiar – pozbawiony jest zbędnych pakietów i plików
  • posiada jasno określony wąski cel – nie wrzucamy kilku aplikacji do obrazu, jeden obraz = jedna funkcja
  • najbardziej zmienne pakiety/pliki powinny być dodawane w Dockerfile najpóźniej, aby maksymalnie wykorzystać build cache

Build Context

Pierwszym o czym powinniśmy powiedzieć w temacie budowania obrazu to tzw. build context. Można by próbować tutaj przytoczyć jakąś skomplikowaną definicje, ale nie uważam żeby miało sens utrudnianie prostych pojęć 😉 Build context to porostu katalog, z którego budujesz swój obraz. To w nim właśnie komenda docker build szuka pliku Dockerfile, którego instrukcje będzie wykonywać. Także z niego pobieranepliki które instrukcje ADD i COPY wrzucają do gotowego obrazu.

Z build context wiąże się jedna istotne rzecz! W momencie gdy wydajesz komendę docker build wywołujesz narzędzia klienckie, mogą one się łączyć do lokalnego(tak jak u Nas w przykładzie) ale i zdalnego daemon Docker. To właśnie daemon buduje obraz. Docker build w pierwszej kolejności bierze zawartość całego build context i wysyła do daemona który będzie budował obraz! Co za tym idzie, dbaj zawsze o to żeby w katalogu używanym jako build context mieć tylko niezbędne pliki! Posiadanie bałaganu w build context wydłuża czas budowy obrazu.

Czas utworzyć build context – nazwa katalogu dowolna:

[root@docker ~]# mkdir build-dir

W katalogu tworzę plik Dockerfile z wcześniej pokazaną zawartością i dodatkowy plik index.html który zawiera stronę internetową 😉

[root@docker ~]# cd build-dir
[root@docker build-dir]# ls -lah
razem 16K
drwxr-xr-x 2 root root 4,0K 10-10 16:54 .
dr-xr-x--- 4 root root 4,0K 10-09 20:39 ..
-rw-r--r-- 1 root root  276 10-09 20:53 Dockerfile
-rw-r--r-- 1 root root   13 10-09 20:51 index.html

Doświadczeni administratorzy Linux/Unix zapewne zwrócą uwagę że korzystam z konta root na OS. Robie to dla wygody, aby nie wprowadzać zamętu. Oczywiście w środowisku komercyjnym warto było by wykonywać tego typu komendy z konta imiennego. Jeżeli chcesz mieć dostęp do daemon docker z konta niego niż root, musisz dodać to konto do sudoers i wywoływać polecenia docker poprzez sudo docker …


Polecenie docker build

[root@docker build-dir]# docker build --build-arg HTTPD_VERSION=2.4.6 -t webapp:1.0 .

Przyjrzyjmy się komendzie do budowania.

Wykorzystujemy –build-arg w celu podania zmiennej HTTPD_VERSION. Zmienna ta wykorzystywana jest w komendzie yum install – krótko mówiąc parametryzujemy sobie wersje HTTPD która będzie w obrazie zainstalowana. Przy pomocy jednego Dockerfile, możesz zbudować wiele obrazów z różnymi wersjami serwera Apache!

Kolejny parametr czyli -t służy do podania nazwy obrazu w formacie <repository>/<name>:<tag> . Na tą chwile podałem jedynie nazwę i tag. Jak nazywać obrazy? Najlepiej podawać nazwę aplikacji, a w tagu jej wersje. Np. oficjalny obraz Ubuntu 20.04, znajdziesz w Docker Hub pod nazwą: ubuntu:20.04, zauważ że tak samo było z CentOS 7(centos:7) którego użyliśmy jako obrazu bazowego 😉 O tagowaniu i repozytoriach dowiesz się więcej w kolejnych częściach kursu!

Na końcu polecenia pojawia się magiczna kropka 😉 Wskazuje ona bieżący katalog w którym budujemy, czyli nasz build context.

Zachęcam do głębszego zapoznania się z możliwościami polecenia docker build, poprzez przełącznik -h. Dostępne jest wiele opcji, których możemy użyć przy tworzeniu obrazów Docker.


Budowanie obrazów

[root@docker build-dir]# docker build --build-arg HTTPD_VERSION=2.4.6 -t webapp:1.0 .

Sending build context to Docker daemon  3.072kB
Step 1/8 : FROM centos:7
 ---> 7e6257c9f8d8
Step 2/8 : LABEL ADMIN=Lukas ADMINMAIL=lukas@lukas.int
 ---> Running in 8398f9fec2e4
Removing intermediate container 8398f9fec2e4
 ---> aa5fec9bf6e6
Step 3/8 : ARG HTTPD_VERSION
 ---> Running in 9fb53bf94c72
Removing intermediate container 9fb53bf94c72
 ---> 10bf518ffe84
Step 4/8 : ENV HTTPD_VERSION=${HTTPD_VERSION:-2.4.6}
 ---> Running in 9ff670951e93
Removing intermediate container 9ff670951e93
 ---> 0459f782ad84
Step 5/8 : RUN yum update -y;     yum -y install     yum install httpd-${HTTPD_VERSION}
 ---> Running in 4623ec838bd1
[...]
Installed:
  httpd.x86_64 0:2.4.6-93.el7.centos

Dependency Installed:
  apr.x86_64 0:1.4.8-5.el7
  apr-util.x86_64 0:1.5.2-6.el7
  centos-logos.noarch 0:70.0.6-3.el7.centos
  httpd-tools.x86_64 0:2.4.6-93.el7.centos
  mailcap.noarch 0:2.1.41-2.el7

Complete!
Removing intermediate container 4623ec838bd1
 ---> 13ce90e9466e
Step 6/8 : COPY index.html /var/www/html/
 ---> 38ddf2e15730
Step 7/8 : ENTRYPOINT ["/usr/sbin/httpd","-D", "FOREGROUND"]
 ---> Running in 78480d1c9b33
Removing intermediate container 78480d1c9b33
 ---> 8e9fd8cdfe2f
Step 8/8 : EXPOSE 80
 ---> Running in 15f998bd9412
Removing intermediate container 15f998bd9412
 ---> f6200c9752d8
Successfully built f6200c9752d8
Successfully tagged webapp:1.0

Powyżej znajduje się output z budowy naszego obrazu.

Tak jak już wspominałem w pierwszej kolejności cały build context jest wysyłany. U Nas znajduje się on lokalnie, sytuacja nie była by problematyczna nawet gdyby nasz katalog budowania był duży.

Proces budowania realizuje kolejne instrukcje z Dockerfile. Istotną rzeczą jest, że w kulisach do każdej instrukcji takiej jak np. RUN yum install powoływany do życia jest kontener tymczasowy(intermediate container). Dane polecenie wykonywane jest wewnątrz tego kontenera, pliki wygenerowane w trakcie kopiowane są do obrazu, a sam kontener tymczasowy zostaje skasowany.

Na sam koniec obraz zostaje otagowany, wybraną przez nas nazwą. Jak widać tworzenie obrazów docker nie jest szczególnie trudne 🙂

W kolejnych częściach kursu porozmawiamy o repozytoriach, a także uruchomimy pierwszy kontener!


Podziel się z innymi!

docker mockupNewsletter!

Co zyskujesz?

Dostajesz zupełnie za darmo ściągę z poleceniami Docker!

Jesteś na bieżąco z nowymi artykułami!

Dowiesz się pierwszy kiedy pojawi się nowy kurs!