Docker base

Vadym Kykalo · May 27, 2022

Docker: Вступ

Docker_logo

Стаття розповідає про проблеми, які вирішує docker, та як використовувати контейнери.


Проблеми

Основна проблема при розробці програмного забезпечення, повторити production environment на develop environment і навпаки. На це дуже багато часу втрачається без необхідної автоматизації.


Для чого використовувати Docker

Docker — програма, що дозволяє операційній системі запускати процеси(докер контейнер) в ізольованому середовищі з урахуванням спеціально створеного образу (image).


Установка Docker

Для роботи необхідно встановити - Docker Engine. На сторінці install доступні посилання для завантаження під усі популярні платформи. Виберіть вашу та встановіть docker.

Для користувачів Ubuntu виконайте всі команди послідовно

sudo apt update
sudo apt install apt-transport-https
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt update
sudo apt install docker-ce
sudo usermod -aG docker $USER

Останній рядок треба прокоментувати. Docker демон може прослуховувати запити API Docker Engine через три різні типи socket: unix, tcp і fd. За замовчуванням Докер працює через unix сокет, тобто Unix (або сокет IPC) створюється в /var/run/docker.sock, що вимагає або права root, або членства в групі docker. Якщо вам потрібно отримати доступ до Docker демона віддалено, вам потрібно ввімкнути tcp socket. От же метою безпеки сокет закритий для користувачів, які не входять до групи docker. І хоча інсталятор додає поточного користувача до цієї групи автоматично, Докер відразу не запрацює. Справа в тому, що якщо користувач змінює групу сам собі, то нічого не зміниться доти, доки користувач не перелогінеться (зробіть перезавантаження комп’ютера для надійності).

Після чого наберіть команду, щоб переконатися що docker демон запущений

$ sudo systemctl status docker
● docker.service - Docker Application Container Engine
     Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2022-05-26 19:24:23 EEST; 2 days ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 1581 (dockerd)
      Tasks: 37
     Memory: 54.3M
     CGroup: /system.slice/docker.service
             ├─  1581 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
lines 1-10...skipping...
● docker.service - Docker Application Container Engine
     Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2022-05-26 19:24:23 EEST; 2 days ago

Active: active (running) означає, що докер демон працює.


Запуск контейнера

Класика hello world, набираємо команду в терміналі

docker run hello-world

Докладніше про те що таке контейнер, образ, Dockerfile з’ясуємо пізніше.

Якщо вперше запустили контейнер, то в терміналі можна побачити наступний stdout

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Already exists 
Digest: sha256:80f31da1ac7b312ba29d65080fddf797dd76acfb870e677f390d5acba9741b17
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

Що відбулося?

При першому виклику docker демон перевірить свій локальний реєстр образів(registry), якщо образ не буде знайдено локально, docker почне завантажувати образ hello-world з офіційного registry (необхідно мати інтернет підключення). Після того, як образ завантажиться, на основі його запуститься контейнер(docker контейнер завжди запускається на основі образу), всередині контейнера запуститься команда CMD команда що була прописана у Dockerfile, в нашому випадку це /hello і результат виконання програми вивівся на термінал через stdout, сам контейнер при цьому завершив свою роботу.

Файл hello, находиться в образі hello-world, на хост машині файлової системи. До речі, при повторному запуску того ж контейнера docker run hello-world сама команда відпрацює набагато швидше чим перший раз, оскільки образ вже завантажений на хост машину.


Особливості ядра

Процес вашої операційної системи, який стартує в ізольованому середовищі завдяки можливостям ядра і є linux-контейнер. Контейнер бачить свій власний список процесів, власний network, свою власну файлову систему(файл hello не знаходиться в нашій файловій системі).

Якщо добре знати Linux і особливості його ядра Cgroups, Namespaces, можна створити власний контейнер без використання API Docker, який буде ізольований та мати обмеженням використання фізичних ресурсів, таких як пам’ять або процесор.


Термінологія

Контейнер — запущений процес операційної системи, що запустився в ізольованому середовищі із підключеною файловою системою з образу.

Контейнер існує поки існує запущений процес в контейнері.

Волюм(volume) — це дисковий простір між хост машиною і контейнером. Волюм — це те що можна змапити, наприклад є каталог в контейнері і є каталог на хост машині, і ви хочете, щоб вони замапились(синхронізувались), тобто каталог на хост машині опинився(прокинувся) в контейнері.

Образ — це шаблон для створення Docker-контейнерів внаслідок якого виникає власна файлова система. Являє собою пакет(файлова система), що містить все необхідне для запуску програми: код, бібліотеки, середовище виконання.

Dockerfile — це файл з набором інструкцій, для створення образу. Як створити влазний образ? Пишемо власний Dockerfile, виконуємо build Dockerfile, після чого створиться образ. На основі образу можна запустити контейнерів стільки необхідно.

Dockerhub — це реєстр докер образів, архів всіх доступних образів(аналог github тільки для образів).

Поки що використовуємо готові образи з офіційного реєстру, потім навчимося створювати самостійно за допомогою написанням власних Dockerfile.


Dockerfile

Приклад Dockerfile hello-world

FROM scratch
COPY hello /
CMD ["/hello"]

Образ - це набір рівнів(read-only шаблон) із якого створюється контейнер. Кожна директива(команда) в Dockerfile створює новий рівень(слой). Докер при створенні образу використовує слоїсту файлову систему AuFS завдяки якій, контейнери можуть спільно використовувати однакові частини файлової системи(слої), доступні тільки на читання(read-only). FROM scratch робить перший базовий слой(рівень) на своїй файловій системі. В основі кожного образу знаходиться базовий образ(в нашому прикладі scratch).

При написанні власних Dockerfile, базовим образом в більшості випадків являються готові образи дистрибутивів linux (fedora, ubuntu,…).

Директорія в якій знаходиться Dockerfile називається білд оточення, в якій докер будує контекст коли викликаємо зборку(білд) образа. Завдяки цьому докер отримує доступ до файлів і каталогів в якому знаходиться білд оточення.

В терміналі набираємо команду: docker images

vkykalo@vkykalo-HP-ProBook-455R-G6:~/workspace/docker$ docker images
REPOSITORY                      TAG                IMAGE ID       CREATED         SIZE
nginx                           latest             de2543b9436b   2 weeks ago     142MB
python                          3.8-slim           1e46b5746c7c   6 months ago    122MB
hello-world                     latest             feb5d9fea6a5   8 months ago    13.3kB
jekyll/jekyll                   4.2.0              61e560f6aee2   10 months ago   680MB
panubo/vsftpd                   latest             dafd22f924fa   2 years ago     107MB
rabbitmq                        3.7.9-management   13ab52135127   3 years ago     212MB
php                             5.6-cli            36c3c974e6ee   3 years ago     344MB
redis                           4.0.6              1e70071f4af4   4 years ago     107MB
postgres                        10.1               ec61d13c8566   4 years ago     287MB

Команда покаже на екран список всіх образів які вже були скачані на ваший комп’ютер. hello-world образ також знаходиться у списку. Тобто ці всі образи які були скачані з докерхаба, або були створені на комп’ютері із допомогою написання власних Dockerfiles. docker images показує верхні слої образів, docker images -a покаже всі рівні(слої).

Є два типи образів, офіційні та неофіційні. Офіційні випускаються докер спільнотою в докерхаб і мають одну назву(nginx,python,hello-world). Перша колонка показує імена образів. Неофіційні образи випускають користувачі, їх неймінг зазвичай виглядає як <vendor_name>/<service_name>, наприклад panubo/vsftpd.

Друга колонка показує TAG. Кожному образу можна дати тег, аналогічно як ви нумеруєте версію релізу на github, тобто коли випускається нова версія, надається новий тег для того, мати систему версіонування. Перший раз коли запустили docker run hello-world ми не вказували тег образу hello-world, докер зробив за нас це автоматично і завантажив latest tag, тобто останню актуальну версію образа.

Оптимізація. Образ php займає 344MB. Якщо на хост машині буде 30 образів які наслідуються від php(FROM директива), якби у докера не було слоїстої файлової системи, то доводилось би завантажувати 30 разів по 344MB. Проте докер лише один раз завантажує образ і всі працюють зі слоями.

Створюємо власний Dockerfile, touch Dockerfile, прописуємо наступні директиви:

FROM ubuntu:18.10

RUN apt update
RUN apt install -y nginx

Коментар

FROM ubuntu:18.10 - вказуємо в нашому Dockerfile опис нашого майбутнього образу. Перша інструкція FROM завжди говорить від якого базового образу будемо наслідуватись (нижні слої системи), в конкретному випадку ubuntu:18.10. Можна поставити будь-яку іншу версію ubuntu, debian тощо. Якщо не вказувати версію, докер завантажить останню latest. Насправді після першого рядку можна було нічого не дописувати більше, інструкції FROM ubuntu:18.10 було б достатньо для того, щоб створити власний образ, проте це був би влсний образ який наслідується від ubuntu:18.10 і все. Для виконання команд на етапі зборки образу існує команда RUN. Кожна команда RUN породжує новий слой. RUN виконує функцію shell. команда, яка буде виконана в рамках оточення, зазначеного у FROM під час збирання образу. Тобто наслідуємось від ubuntu:18.10 і запускаємо sh команди, в нашому випадку ми хочемо в наш образ поставити вебсервер nginx. sudo використовувати не потрібно, оскільки всі команди виконуються від root користувача. Пакетний менеджер apt який вже в нашому базовому образі ubuntu:18.10, при інсталяції nginx система завжди запросить підтвердження на виконання команди тому необхідно написати apt install -y nginx. Ключ -y говорить про те, що потрібно проводити установку без додаткових питань. Тобто RUN в Dockerfile — це фактично ваший термінал(sh), можна ставити пакети, робити http запити тощо.

В нашому Dockerfile записано дві директиви RUN, тобто створиться два нових слоя, кожен слой займає дисковий простір файлової системи. Щоб слоїв не було так багато(в реальних Dockerfile можуть бути десятки директив RUN), існує оптимізація, перепишемо Dockerfile наступним чином

FROM ubuntu:18.10

RUN apt-get update && \
    apt-get install -y nginx

Таким способом ми скоротили на один слой менше. Зменшення кількості слоїв прискорює зборку образів за рахунок зменшення слоїв. Докер активно використовує систему кеширування слоїв у своїй системі які не змінились. Докер вміє перевикористовувати слої із різних образів. Фактично після зборки образу буде створений слой(набір файлів), докер зможе використати даний слой в зборці іншого образу якщо така існує необхідність.

Можемо зібрати образ із нашого Dockerfile, проте він нічого не буде виконувати, в нашому образі буде ОС ubuntu та встановлений вебсервер nginx і все.

Докер - це засіб ізоляції процесів, а не систем. Тому best practice вважається упаковка в докер контейнер одного постійно робочого процесу.

Для того, щоб вказати який процес необхідно запустити в момент запуску контейнера існує директива CMD. Коли ми запускали docker run hello-world, в момент запуску контейнера автоматично виконалась директива CMD. Пригадаємо Dockerfile hello-world.

FROM scratch
COPY hello /
CMD ["/hello"]

CMD використовується лише в тому випадку, якщо контейнер був запущений без указаної команди, інакше директива ігнорується.

Оскільки ми хочемо зібрати власний образ nginx то допишемо директиву CMD яка запустить процес старту вебсервера.

FROM ubuntu:18.04

RUN apt-get update && \
    apt-get install -y nginx

CMD ["nginx", "-g", "deamon off"]

Яка різниця між RUN та CMD ? Команда RUN виконується на етапі зборки контейнера. Команда CMD виконується на етапі запуску контейнера.

Команда nginx -g deamon off запустить основний процес nginx в контейнері.


Зборка образу(build)

Зробимо зборку ось такого Dockerfile:

FROM ubuntu:18.04

RUN apt-get update
RUN apt-get install -y nginx

CMD ["nginx", "-g", "deamon off"]

Якщо ви знаходитесь в каталозі де знаходиться Dockerfile, набираємо команду docker build -t <vendor>:<tag> .. В моєму випадку команда виглядає docker build -t vkykalo/selfnginx:0.0.1 .. В терміналі ви побачите щось схоже

vkykalo@vkykalo-HP-ProBook-455R-G6:~/workspace/docker$ docker build -t vkykalo/selfnginx:0.0.1 .
Sending build context to Docker daemon  13.31kB
Step 1/4 : FROM ubuntu:18.04
 ---> c6ad7e71ba7d
Step 2/4 : RUN apt-get update
 ---> Running in e92dc6afbb81
Get:1 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB]
Get:2 http://security.ubuntu.com/ubuntu bionic-security InRelease [88.7 kB]
Get:3 http://archive.ubuntu.com/ubuntu bionic-updates InRelease [88.7 kB]
...................more......................
invoke-rc.d: policy-rc.d denied execution of start.
Setting up nginx (1.14.0-0ubuntu1.10) ...
Processing triggers for libc-bin (2.27-3ubuntu1.5) ...
Removing intermediate container c3a2cb24dcb6
 ---> d79ca5dd0ea9
Step 4/4 : CMD ["nginx", "-g", "deamon off"]
 ---> Running in 392f69211705
Removing intermediate container 392f69211705
 ---> 16a67dbdcc01
Successfully built 16a67dbdcc01
Successfully tagged vkykalo/selfnginx:0.0.1

docker build виконує зборку образу. Ключ -t для передачі ім’я образу, а також тег. Якщо не вказувати тег, то підставляється останній latest, а . вказує шлях до Dockerfile.

Перевіримо список локальних образів командою docker images

vkykalo@vkykalo-HP-ProBook-455R-G6:~/workspace/docker$ docker images
REPOSITORY                      TAG                IMAGE ID       CREATED          SIZE
vkykalo/selfnginx               0.0.1              16a67dbdcc01   14 minutes ago   164MB
php                             7.4-cli            96324ce6cf40   8 days ago       473MB
nginx                           latest             de2543b9436b   2 weeks ago      142MB
ubuntu                          18.04              c6ad7e71ba7d   5 weeks ago      63.2MB
busybox                         latest             1a80408de790   7 weeks ago      1.24MB
openjdk                         8                  18fbe41f975e   2 months ago     526MB
hello-world                     latest             feb5d9fea6a5   8 months ago     13.3kB
panubo/vsftpd                   latest             dafd22f924fa   2 years ago      107MB
rabbitmq                        3.7.9-management   13ab52135127   3 years ago      212MB
php                             5.6-cli            36c3c974e6ee   3 years ago      344MB
redis                           4.0.6              1e70071f4af4   4 years ago      107MB
postgres                        10.1               ec61d13c8566   4 years ago      287MB

Бачимо наший образ, тепер можна запустити контейнер на основі образу vkykalo/selfnginx Образ ubuntu також знаходиться в реєстрі, ми його використали в директиві FROM.

При необхідності ми тепер можемо опублікувати образ vkykalo/selfnginx dockerhub, але для цього необхідно мати особистий кабінет на dockerhub та заведений власний репозиторий на ньому для публікацій образів. Щоб зробити логін через REST API Docker, необхідно ввести docker login.

Команда docker run не намагається шукати оновлену версію образу з реєстру dockerhub, якщо локально є образ із таким ім’ям та тегом. Наприклад, ви зробили docker run nginx місяць тому, у локальному реєстрі на комп’ютері знаходиться образ nginx з тегом latest, тобто остання версія. Пройшов місяць і розробники зробили новий образ nginx новий тег та опублікували його на реєстрі. Навіть якщо ви після запустите команду знову docker run nginx то докер не завантажить нову версію образу, оскільки локально у вас вже знаходиться nginx. Якщо необхідно запустити контейнер на новій версії nginx, спочатку треба запустити команду docker pull nginx. Команда завжди перевіряє, чи оновився образ для певного тега.

На основі створеного образу vkykalo/selfnginx можна створювати інші образи, тобто брати як базовий.

Щоб видалити образ існує команда docker rmi, видалити можна по імені docker rmi vkykalo/selfnginx або по ідентифікатору docker rmi 16a67dbdcc01.


Read-Write-Layer

Образ - це сутність, яка складається зі стека слоїв які використовуються тільки для читання(read-only). Кожний верхній слой має слой попередника. Тобто образ це іммутабельна(immutable) структура. Кожний контейнер створюється з образу. Контейнер - це сутність, яка складається зі стека слоїв(read-only) образу, але ще контейнер додає свій власний слой, і саме цей слой доступний для запису(Read-Write-Layer). В цей слой ми можемо записувати данні. Це і є головна різниця між образом та контейнером. Тепер щоб контейнер став робочим, необхідно запустити процес в цій сутності.


Кеширування слоїв

Які проблеми можуть виникнути при експлуатації даного Dockerfile надалі?

FROM ubuntu:18.04

RUN apt-get update
RUN apt-get install -y nginx

CMD ["nginx", "-g", "deamon off"]

Припустимо наш контейнер з образу працює вже рік в production і ми вирішуємо встановити fail2ban в наш контейнер. Для цього необхідно змінити Dockerfile, дописати інструкцію RUN, зробити білд Dockerfile з новою версією та запустити новий контейнер(про налаштування fail2ban не говоримо зараз).

Дописуємо подібне щось та виконуємо build

FROM ubuntu:18.04

RUN apt-get update
RUN apt-get install -y nginx

RUN apt-get install -y fail2ban

CMD ["nginx", "-g", "deamon off"]

Запускаємо зборку нового образу docker build -t vkykalo/selfnginx:0.1.1 .

vkykalo@vkykalo-HP-ProBook-455R-G6:~/workspace/docker$ docker build -t vkykalo/selfnginx:0.1.1 .
Sending build context to Docker daemon  13.31kB
Step 1/5 : FROM ubuntu:18.04
 ---> c6ad7e71ba7d
Step 2/5 : RUN apt-get update
 ---> Using cache
 ---> 24397ffd798e
Step 3/5 : RUN apt-get install -y nginx
 ---> Using cache
 ---> d79ca5dd0ea9
Step 4/5 : RUN apt-get install -y fail2ban
 ---> Running in dbd96cf1fdb0
Reading package lists...

........

Removing intermediate container 964f19a5be20
 ---> 5b3b34966a11
Step 5/5 : CMD ["nginx", "-g", "deamon off"]
 ---> Running in 952fea810667
Removing intermediate container 952fea810667
 ---> 1f9e2758a6cf
Successfully built 1f9e2758a6cf
Successfully tagged vkykalo/selfnginx:0.1.1

Прокоментуємо результат зборки.

Step 1/5 : FROM ubuntu:18.04
 ---> c6ad7e71ba7d

Базовий образ який був скачаний раніше, тому директива відпрацювала швидко.


Step 2/5 : RUN apt-get update
 ---> Using cache
 ---> 24397ffd798e

Ми бачимо що слой існував у нашій системі(ми раніше запускали зборку даного Dockerfile), і докер не створив новий слой, а взяв його зі свого кеша 24397ffd798e - ідентифікатор закешируваного до слою.


Step 3/5 : RUN apt-get install -y nginx
 ---> Using cache
 ---> d79ca5dd0ea9

Аналогічна ситуація, оскільки даний слой ми не змінювали, і докер взяв слой із кешу(існує слой в системі де вже встановлений nginx), а не створив новий.


Step 4/5 : RUN apt-get install -y fail2ban
 ---> Running in dbd96cf1fdb0
Reading package lists...

А ось вже новий слой, докер бачить зміни у файлі, і перевіряє чи є у нього слой в кешу. Докер створює новий слой, який може далі використовувати при необхідності.


Step 5/5 : CMD ["nginx", "-g", "deamon off"]
 ---> Running in 952fea810667
Removing intermediate container 952fea810667
 ---> 1f9e2758a6cf
Successfully built 1f9e2758a6cf
Successfully tagged vkykalo/selfnginx:0.1.1

Команда під час запуску контейнера, тегування образу.


Проблема полягає в тому що команда

RUN apt-get update

не зробила нічого, тому що це був окремий слой який був закешований в самий перший раз зборки образу. Якщо ви рік тому запускали команду, то дана команда не виконається зараз. Докер побачив у себе ubuntu:18.04 зі слоєм apt-get update і взяв його із кешу. Якщо зараз в apt-get update щось змінилося(додались нові пакети, зміна в пакетах, версії тощо) то ми про це б не дізнались.

Якщо необхідно запустити apt-get update не з кешу, видаляємо образ docker rmi <image> і виконуємо зборку заново, або можна скористатися ключем --no-cache при зборці docker build --no-cache ..

Як писати правильно Dockerfile можна дізнатися тут.


Створення контейнера

Після етапу зборки образу docker build настав час створити контейнер на основі образу. Нагадаємо, ми створили образ на основі даного Dockerfile

FROM ubuntu:18.04

RUN apt-get update
RUN apt-get install -y nginx

RUN apt-get install -y fail2ban

CMD ["nginx", "-g", "deamon off"]

Якщо повторно запустити команду docker build -t vkykalo/selfnginx:0.1.1 ., то всі слої докер візьме із кешу.

Створення НЕзапущеного контейнера виконується через команду docker create <image_name>.

vkykalo@vkykalo-HP-ProBook-455R-G6:~/workspace/docker$ docker create vkykalo/selfnginx:0.1.1
dcd8082d35680ec81c97077590d0ee33a1934f7f230cf1baba5fb42324b4a802

Щоб побачити список ВСІХ контейнерів існує команда docker ps -a. Щоб побачити список запущених контейнерів існує команда docker ps.

vkykalo@vkykalo-HP-ProBook-455R-G6:~/workspace/docker$ docker ps -a
CONTAINER ID   IMAGE                         COMMAND                  CREATED         STATUS                      PORTS                                         NAMES
dcd8082d3568   vkykalo/selfnginx:0.1.1       "nginx -g 'deamon of…"   2 minutes ago   Created                                                                   youthful_spence
05b3dbb377ff   postgres:12                   "docker-entrypoint.s…"   3 hours ago     Up 3 hours                  0.0.0.0:5488->5432/tcp, :::5488->5432/tcp     lambda_aws_portfolio_postgres_from_1
a76775b97c19   lambda_aws_portfolio_python   "python"                 3 hours ago     Exited (0) 3 hours ago                                                    lambda_aws_portfolio_python_1
1523a2cd5964   postgres:12                   "docker-entrypoint.s…"   3 hours ago     Up 3 hours                  0.0.0.0:5489->5432/tcp, :::5489->5432/tcp     lambda_aws_portfolio_postgres_to_1
834d0dade641   nginx                         "/docker-entrypoint.…"   12 hours ago    Up 12 hours                 0.0.0.0:8181->80/tcp, :::8181->80/tcp         static-blog-page_nginx_1

Як ми бачимо, наш контейнер успішно був створений, і він отримав статус Created. Будь-який контейнер можна створити, запустити, зупинити, видалити.

Ім’я youthful_spence контейнеру докер дав випадково, якщо не вказувати при створенні контейнера. Цією командою, ми створили останній слой(Read-Write-Layer).

Щоб запустити контейнер(помістити процес в контейнері), існує команда docker start <image_name>.

vkykalo@vkykalo-HP-ProBook-455R-G6:~/workspace/docker$ docker start youthful_spence
youthful_spence

Перевіримо статус контейнера.

vkykalo@vkykalo-HP-ProBook-455R-G6:~/workspace/docker$ docker ps -a
CONTAINER ID   IMAGE                     COMMAND                  CREATED              STATUS            PORTS     NAMES
dcd8082d3568   vkykalo/selfnginx:0.1.1   "nginx -g 'daemon of…"   52 seconds ago       Up 2 seconds                youthful_spence
...........

Як бачимо, контейнер запущений та працює(має статус Up).

На практиці команди docker create і docker start не використовуються, оскільки існує команда яка поєднує ці дві операції, і це команда docker run. Ми вже використовували коли запускали docker run hello-world.

create + start = run


Взаємодія з контейнером

Продовжуємо роботу з нашим робочим контейнером. Ми зробили наступний ланцюг:Dockerfile -> image -> container.

Пригадаємо, контейнер це — запущений процес операційної системи, що запустився в ізольованому середовищі із підключеною файловою системою з образу(слої(read-only) + власний слой(read-write)).

Це означає що контейнер має власне середовище, нетворк також свій. Для взаємодії з контейнерорм необхідно прокинути порти назовні на хост машину(ми створили контейнер з вебсервером nginx).

vkykalo@vkykalo-HP-ProBook-455R-G6:~/workspace/docker$ docker ps -a
CONTAINER ID   IMAGE                     COMMAND                  CREATED              STATUS            PORTS     NAMES
dcd8082d3568   vkykalo/selfnginx:0.1.1   "nginx -g 'daemon of…"   52 seconds ago       Up 2 seconds                youthful_spence
...........

Ми бачимо, що в колонці PORTS. Сама програма nginx всередині контейнера слухає 80 порт. Щоб прокинути порти, необхідно вказати ключ -p при запуску контейнера. Формат запису 8181:80 розшифровується так: прокинути порт 8181 зовні контейнера(хост машина) контейнер на порт 80(всередині контейнера). Перезапустимо контейнер з прокидуванням портів та в фоновом режимі(ключ -d) docker run -p 8181:80 -d dcd8082d3568 та перевіримо статус контейнера.

vkykalo@vkykalo-HP-ProBook-455R-G6:~/workspace/docker$ docker ps -a
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS                     PORTS                                   NAMES
dcd8082d3568   51d41e643171   "nginx -g 'daemon of…"   5 seconds ago   Up 4 seconds               0.0.0.0:8181->80/tcp, :::8181->80/tcp   youthful_spence

Тепер в колонці PORTS ми бачимо два записи в форматі IPv4 та IPv6 0.0.0.0:8181->80/tcp, :::8181->80/tcp. Запис 8181->80 означає, що порт на хост машині 8181 прокинутий на 80 порт всередині контейнера. Ми відкрили 80 порт в контейнері, тому що nginx працює на 80 порту. Тепер, якщо відкрити браузер і написати http:/127.0.0.1:8181 нам відкриється сторінка вебсервера nginx. nginx_def

Не можна відкрити порт назовні, якщо він вже зайнятий іншим процесом(службою).

Якщо контейнер вже слухає зовні 8181 порт, при спробі запустити ще один новий контейнер якому ми також прокинемо порт 8181, виникне помилка Bind for 0.0.0.0:8181 failed: port is already allocated. Необхідно, або прокинути інший порт, або зупинити старий контейнер, щоб звільнити порт

Продовжуємо вивчати нові команди при створенні Dockerfile. Змінимо головну сторінку nginx.

Спочатку зупинимо попередній робочий контейнер -> видалимо контейнер -> видалимо образ.

docker stop <container_id> - зупинити контейнер

docker rm <container_id> - видалення контейнера

docker rmi <image_id> - видалення образу

Наш файл зборки виглядав:

FROM ubuntu:18.04

RUN apt-get update
RUN apt-get install -y nginx

RUN apt-get install -y fail2ban

CMD ["nginx", "-g", "daemon off;"]

При цьому структура проекту на хост машині виглядає:

.
└── Dockerfile

0 directories, 1 file

Створимо в корні проекту файл index.html

vkykalo@vkykalo-HP-ProBook-455R-G6:~/workspace/docker$ echo "<h1>HELLO FROM DOCKER_CONTAINER</h1>" > index.html
vkykalo@vkykalo-HP-ProBook-455R-G6:~/workspace/docker$ cat index.html 
<h1>HELLO FROM DOCKER_CONTAINER</h1>
.
├── Dockerfile
└── index.html

0 directories, 2 files

Допишемо дві нові директиви в Dockerfile COPY index.html /var/www/html, EXPOSE 80

FROM ubuntu:18.04

RUN apt-get update
RUN apt-get install -y nginx
RUN apt-get install -y fail2ban

COPY index.html /var/www/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Інструкція EXPOSE інформує Docker про те, що контейнер прослуховує зазначені мережеві порти під час виконання.

Інструкція EXPOSE фактично не публікує порт. Це документація між людиною, яка створює образ, та людиною, яка запускає контейнер, про те, які порти призначені для публікації. Щоб опублікувати порт при запуску контейнера, використовується ключ -p.

Інструкція COPY копіює нові файли або каталоги <src> і додає їх у файлову систему контейнера на шляху <dest>. Копіювати файли із хост машини можна лише ті що знаходяться в поточному каталозі(контекст Dockerfile).

Запускаємо нову зборку та піднімаємо контейнер.

docker build -t vkykalo/selfnginx:0.1.2 . - зборка образу.

docker run -d --name=my_nginx -p 8282:80 vkykalo/selfnginx:0.1.2 - запуск контейнера на 8282 порту хост системи (ключ --name додає ім`я контейнеру).

vkykalo@vkykalo-HP-ProBook-455R-G6:~/workspace/docker$ docker run -d --name=my_nginx -p 8282:80 vkykalo/selfnginx:0.1.2
7bfd93ba67a317113232944e76cc0cb2416cc9e018efa4966bcc30d4b8c0d808
vkykalo@vkykalo-HP-ProBook-455R-G6:~/workspace/docker$ docker ps
CONTAINER ID   IMAGE                     COMMAND                  CREATED         STATUS         PORTS                                   NAMES
7bfd93ba67a3   vkykalo/selfnginx:0.1.2   "nginx -g 'daemon of…"   8 seconds ago   Up 7 seconds   0.0.0.0:8282->80/tcp, :::8282->80/tcp   my_nginx

Відкриваємо сторінку в браузері http://127.0.0.1:8282 nginx_cast

Тепер ми вміємо упаковувати свої власні програми в докер образи :)


Immutable infrastructure

Docker контейнери створені, щоб бути незмінними(immutable). Як результат, можна упакувати свою програму, встановити всі необхідні залежності в докер образ. Запустити docker run <name-image> і програма запуститься у своєму ізольованому середовищі, де буде власна файлова система, власний network, повністю ізольоване середовище. І відтепер, хто б не запускав контейнер на основі образу, на любому іншому комп’ютері/сервері практично із 100% гарантією програма запуститься з успіхом. Запуск програм в докері відбувається як

  • Скачати образ.
  • Запустити контейнер на основі образу.

Використання готових образів

Зробимо файл із розширенням .php та напишемо просту програму яка буде виводити в термінал версію інтерпретатора php.

$ echo "<?php echo 'Версія  PHP: ' . phpversion(). \"\n\";" > test.php

Перевіримо код програми

cat test.php 
<?php echo 'Версія  PHP: ' . phpversion(). "\n";

Запускаємо програму в докер контейнері із використанням офіційного образу php версії 7.4-cli.

$ docker run -it --rm -v "$PWD":/opt -w /opt php:7.4-cli php test.php
Версія  PHP: 7.4.29

Змінюємо тег образу php з версії 7.4-cli на 5.6-cli.

$ docker run -it --rm -v "$PWD":/opt -w /opt php:5.6-cli php test.php
Версія  PHP: 5.6.40

Програма Docker спочатку перевірить локальний реєстр, а саме образ php версії(тег) 7.4-cli, якщо docker не знайшов, то програма завантажить з реєстру необхідний образ та запустить докер контейнер на основі php:7.4-cli. Аналогічну послідовність docker зробив із запуском контейнера, що базується на основі образу php версії 5.6-cli.

Ми запустили одну програму, але з різними версіями інтерпретатора. Запуск відбувався в різних docker контейнерах, при цьому не потрібно більше встановлювати php на хост машину.


Висновок

Основна мета статті, показати проблеми повсякденної розробки та як їх вирішує докер.


Посилання

Twitter, Facebook