Прежде чем рассказывать про Docker, нужно сказать несколько слов о технологии контейнеризации.
-
Контейнеры — это способ упаковать приложение и все его зависимости в единый образ. Этот образ запускается в изолированной среде, не влияющей на основную операционную систему. Контейнеры позволяют отделить приложение от инфраструктуры: разработчикам не нужно задумываться, в каком окружении будет работать их приложение, будут ли там нужные настройки и зависимости. Они просто создают приложение, упаковывают все зависимости и настройки в единый образ. Затем этот образ можно запускать на других системах, не беспокоясь, что приложение не запустится.
-
Docker — это платформа для разработки, доставки и запуска контейнерных приложений. Docker позволяет создавать контейнеры, автоматизировать их запуск и развертывание, управляет жизненным циклом. Он позволяет запускать множество контейнеров на одной хост-машине.
Контейнеризация похоже на виртуализацию, но это не одно и то же. Виртуализация работает как отдельный компьютер, со своим виртуальным оборудованием и операционной системой. При этом внутри одной ОС можно запустить другую ОС. В случае контейнеризации виртуальная среда запускается прямо из ядра основной операционной системы и не виртуализирует оборудование. Это означает, что контейнер может работать только в той же ОС, что и основная. При этом так как контейнеры не виртуализируют оборудование, они потребляют намного меньше ресурсов.
- Изоляция запущенных приложений. Можно отделить ваше приложение от ОС хоста и от других приложений;
- Облегчение процесса разработки, тестирования и развертывания приложений;
- Нет необходимости в конфигурации среды для запуска – она поставляется вместе с приложением.
Выделим важные компоненты docker:
- Docker image (докер-образ) - это неизменяемый образ, из которого разворачивается контейнер. Его можно рассматривать как набор файлов, необходимых для запуска и работы приложения на другом хосте. Говоря простыми словами, образ Docker это образ, содержащий внутри себя приложение и все зависимости, необходимые для запуска этого приложения в Docker;
- Docker registry - это репозиторий, в котором хранятся образы. Пример:
DockerHub
, - Dockerfile (докер-файл) - это файл-инструкция для сборки образа;
- Docker container (докер-контейнер) - это запущенный экземпляр образа Docker;
- Docker host (докер-хост) - машина с установленным docker;
- Docker daemon (докер-демон) - Это фоновый процесс, который работает постоянно и ожидает команды. Все операции по управлению контейнерами отправляются именно в демон, например: запустить или остановить контейнер, скачать образ. И уже на основе этих команд демон выполняет необходимые действия с контейнерами и образами. Поэтому докер-демон знает все о контейнерах, запущенных на одном хосте: сколько всего контейнеров, какие из них работают, где хранятся образы и так далее;
- Docker client (докер-клиент) - это клиент, при помощи которого пользователи взаимодействуют с демоном и отправляют ему команды.
Установка Docker (linux)
Перед установкой проверьте архитектуру процессора введя следующую команду:
uname -m
Установим дополнительные пакеты:
sudo apt-get update
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
Добавим официальный ключ GPG от репазитория к установке докера (команду следуюет запустить из под рут пользователя):
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
В зависимости от архитектуры, добавим соответствующий репазиторий в ubuntu (команду следуюет запустить из под рут пользователя):
-
x86_64 / amd64
echo \ "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
-
armhf
echo \ "deb [arch=armhf signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
-
arm64
echo \ "deb [arch=arm64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
-
s390x
echo \ "deb [arch=s390x signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Установка Docker Engine:
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
Проверим установку докера:
$ sudo docker --version
Docker version 20.10.8, build 3967b7d
!Важно:
если не удалось выполнить последнюю команду, скорее всего установка было выполнена не по инструкции. Попробуйте пройти все пути подroot
пользователем:sudo su
.
Добавим нашего пользователя в docker group
(чтобы не использовать команду sudo
):
# Создадим группу
sudo groupadd docker
# Добавим текущего пользователя в группу
sudo usermod -aG docker $USER
Для дальнейшей работы без sudo
, необходимо перезапустить машину.
Установка Docker Compose (linux)
Выполните эту команду, чтобы загрузить текущую стабильную версию Docker Compose:
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
Примените разрешение на запуск бинарного файла (убедитесь, что файл существует):
sudo chmod +x /usr/local/bin/docker-compose
Проверим установку:
$ docker-compose --version
docker-compose version 1.29.2, build 5becea4c
Запустим первый контейнер hello-world
, для этого воспользуемся следующей командой:
docker run hello-world
# docker run - команда для заупска
# hello-world - это название образа, который мы хотим запустить
Опишем что же произошло при запуске команды. Первым делом, docker deamon
получил от нас команду и попытался запустить контейнер. Образ для контейнера не был обнаружен на локальном устройстве (docker host
). Поэтому docker deamon
сначала скачал образ из DockerHub репозитория и затем его запустил.
Проверим список контейнеров:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Видно, что контейнеров нету, дело в том, что текущая команда отображает только запущенные контейнеры. Для отображения всех контейнеров, необходимо воспользоваться другой командой:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9924379be34d hello-world "/hello" 3 minutes ago Exited (0) 2 minutes ago elastic_kilby
Из полученного ответа видно, что статус контейнера Exited 0
. Контейнер запустил внутри себя команду, программа отработала, и контейнер завершил свою работу. Это стандартная работа контейнера. Контейнер находится в активном состоянии только при условии, что запущенное в нем приложение работает.
Попробуем сделать тоже самое с образом Busybox. Для начала, запустите следующую команду:
docker pull busybox
Команда pull скачивает образ busybox из регистра Докера и сохраняет его локально. Можно использовать команду docker images, чтобы посмотреть список образов в системе:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 42b97d3c2ae9 2 weeks ago 1.24MB
hello-world latest d1165f221234 6 months ago 13.3kB
Запустим контейнер:
docker run busybox
Ничего не произошло! Баг? На самом деле - нет. Под капотом произошло много всего. Докер-клиент нашел образ (в нашем случае, busybox
), запустил контейнер и запустил команду внутри этого контейнера. Мы сделали docker run busybox
, но не указали никаких команд, так что контейнер загрузился, запустилась пустая команда и программа завершилась. Передадим при запуске контейнера команду:
$ docker run busybox echo "Hello, TCS!"
Hello, TCS!
Теперь все выглядит корректно. Почему при запуске контейнера hello-world
небыло необходимости в передаче команды? Дело в том, что при создании образа, в контейнерах всегда устанавливается команда запуска. В образе hello-world
использовалась команда, по выводу в консоль сообщения приветствия. В образе же busybox
используется команда sh
(являющаяся также командной оболочкой, вместо нее иногда можно использовать оболочку bash
).
Попробуем зайти внутрь контейнера под sh оболочкой. Для этого, при запуске необходимо выполнить следующую команду:
docker run -it busybox bash
# Передаем в запуск команду sh (она же - командная оболочка)
Попробуем внутри него создать файл:
$ touch test.txt
$ ls -alh
total 44K
drwxr-xr-x 1 root root 4.0K Sep 4 19:57 .
drwxr-xr-x 1 root root 4.0K Sep 4 19:57 ..
-rw-r--r-- 1 root root 0 Sep 4 19:57 test.txt
...
Видим, что файл test.txt
создался. И вместе с этим файлом мы можем наблюдать другие внутренние файлы запущенного контейнера.
Попробуем выйти из контейнера и зайти в него заново. Проверим, остался ли файл.
$ exit
$ docker run -it busybox bash
$ ls -alh
Видим, что файла больше нету. На самом деле, мы не вернулись в прошлый контейнер. Мы создали новый и перешли в него. Предыдущий контейнер также остался в памяти, но он находится в состоянии exit 0
. Мы можем повторно вернуть в консоль предыдущего контейнера выполнив следующую команду:
# Просмотрим список всех контейнеров, и скопируем его id:
$ docker ps -a
# Запустим остановленный контейнер
$ docker start 117fe8525a25
# Проверим список запущенных контейнеров
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
117fe8525a25 busybox "sh" 13 minutes ago Up 12 seconds determined_banach
# Подключимся к консоли контейнера
$ docker attach 117fe8525a25
# Проверим, имеется ли файл в контейнере
$ ls -alh
total 44K
drwxr-xr-x 1 root root 4.0K Sep 4 19:57 .
drwxr-xr-x 1 root root 4.0K Sep 4 19:57 ..
-rw-r--r-- 1 root root 0 Sep 4 19:57 test.txt
...
Что означают новые команды? Для того, чтобы опсать команду start
, необходимо разобраться в команде run
. Команда run
содержит в себе последовательный запуск следующих команд: create
+ start
. Так как контейнер уже был создан, оставалось его только запустить. Команда attach
необходима для того, чтобы подключиться к консоли контейнера. На самом деле, команду run
никогда не используют в отдельности, чаще всего использую run
или другие способы управления контейнером, о котором поговорим чуть позже.
Попробуем теперь удалить контейнер. Для этого необходимо воскользоваться командой:
# запустим контейнер еще раз
$ docker start 117fe8525a25
# остановим контейнер
$ docker kill 117fe8525a25
# удалим контейнер
$ docker rm 117fe8525a25
# удалим образ
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 42b97d3c2ae9 2 weeks ago 1.24MB
$ docker rmi 42b97d3c2ae9
Для задания автоматического удаления контейнера, после его остановки, необходимо указать команду:
docker run --rm busybox echo "Hello, TCS!"
Для упрощения работы с docker существуют много различных инструментов. Одним из популярных является docker-compose
. Docker-compose
- это приложение написанное на python, предназначено для решения задач, связанных с развёртыванием проектов. Он использует заранее подготовленные docker-compose.yml
с описанными инструкциями по запуску контейнеров (контейнеров может быть несколько).
Рассмотрим пример. В качестве примера возьмем существенное приложение, такое как Prometheus.
Перейдем в пустую директорию и создадим в ней docker-compose.yml
файл со следующим содержимым:
version: '3.9'
services:
prometheus:
image: prom/prometheus:latest
restart: always
ports:
- 9090:9090
Что указано в данном файле?
version
- это версия приложенияdocker-compose
;services
- под этим значением передаются списки всех приложений (на текущий момент приложение одно, приложениеprometheus
);image
- образ приложения prometheus (образ публичный, берется из dockerHub репозитория);restart
- политика перезапуска контейнера (always
означает, что если в приложении возникнет ошибка, контейнер перезапустится);ports
- список портов, которые нам необходимо прокинуть из контейнера во вне.
Запустим контейнер с помощью команды:
# необходимо находиться в директории с файлом docker-compose.yml
docker-compose up -d
Перейдем в браузер по адресу http://localhost:9090 и мы увидем интерфейс приложения, запущенного в контейнере.
Попробуем перейти внутрь контейнера, для этого необходимо воспользоваться командой:
docker exec -it b07c21eed688 sh
Теперь вы находитесь внутри контейнера и можете его исследовать. Как и у большинства приложений, prometheus умеет отображать свои логи (перед этим не забудьте выйти из консоли контейнера). Для отображения логов, необходимо воспользоваться командой:
$ docker logs b07c21eed688
Попробуем остановить приложение. Для этого необходимо воспользоваться командой:
docker-compose down
Приложение было остановлено, контейнер был удален (теперь приложение можно восстановить обратно через docker-compose up -d
).
Для обращения к приложению запущенного через docker-compose
можно также обращаться через команды docker
, так как docker-compose
всего лишь внешняя оболочка для работы докером.
В этом разделе мы рассмотрим примеры по написанию своих docker образов. Предположим, у нас имеется приложение написанное на языке python3
и мы хотим запустить его внутри docker
контейнера. Приложение выполняет следующую функцию:
- прослушивает внешние запросы на определенном порту;
- при переходе из браузера на указанный порт должна отобразиться надпись приветствия:
"Hello, TCS!"
.
Создадим пустую директорию и следующие файлы:
- Dockerfile (файл с описание сборки docker образа)
- docker-compose.yml (файл, используемый для заупска docker контейнера)
- requirements.txt (файл со списком предустанавливаемых python библиотек)
- app.py (приложение на python)
Содержимое файлов:
- app.py
from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, TCS!'
- requirements.txt
Flask==2.0.1
- docker-compose.yml
version: '3.9' services: flask-hello: image: flask-hello:latest restart: always ports: - 8080:8080
- Dockerfile
FROM python:3.8-slim-buster WORKDIR /app COPY requirements.txt requirements.txt RUN pip3 install -r requirements.txt COPY . . CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0", "--port=8080"]
Рассмотрим подробнее незнакомые для нас файлы:
requirements.txt
- это стандартный файл используемый python для указания списка скачиваемых внешних библиотек. Для установки библиотек используется команда:pip3 install -r requirements.txt
.Dockerfile
- файл описывающий последовательность для сборки образа. Рассмотрим каждый момент подробнее:FROM python:3.8-slim-buster
- базовый образ, который будет использоваться для сборки нашего. Для сборки уже существует множество образов с предустановленным ПО. Например, в текущем образе уже имеется заранее предустановленныйpython:3.8
.WORKDIR /app
- создается папка/app
внутри образа. Происходит перемещение выполнения команд в данной директории (то есть, все команды теперь запускаются из этой папки. Последующая команда копирования говорит о том, что копирование будет проходить в директорию/app
).COPY requirements.txt requirements.txt
- копирование файла из вне внутрь образа.RUN pip3 install -r requirements.txt
- запуск команды по установке python3 библиотек из файлаrequirements.txt
.COPY . .
- копирование всей файлов из текущей директории в образ (в директорию/app
)CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0", "--port=8080"]
- команда запуска приложения (называетсяENTRYPOINT
). После создания контейнера запустится именно данная команда.
docker-compose.yml
:image: flask-hello:latest
- название образа. Название будет передано в команду по сборке образа (названия должны совпадать!).
Запустим следующую команду по сборке образа:
docker build -t "flask-hello" ./
В консоли видим логи запуска команд описанных в Dockerfile
файле.
Запустим приложение, используя наш собранный образ и перейдем в браузер по адресу http://localhost:8080:
docker-compose up -d
Как и с примером из приложения Prometheus
, мы можем отслеживать логи приложения, для этого необходимо запустить команду:
docker logs 01d595c4d819
Задание: необходимо запустить приложение написанное с помощью библиотеки flask
в docker
. Приложение должно выводить на экране температуру в городе Зеленоград.