Docker: Вступ
Стаття розповідає про проблеми, які вирішує 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
.
Не можна відкрити порт назовні, якщо він вже зайнятий іншим процесом(службою).
Якщо контейнер вже слухає зовні 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
Тепер ми вміємо упаковувати
свої власні програми в докер образи :)
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
на хост машину.
Висновок
Основна мета статті, показати проблеми повсякденної розробки та як їх вирішує докер.