pestsov-v / docker-shorts

Detailed description of commands and a structured description of all the main features of Docker in one repository

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Выжимка по работе с Docker

Подноготная структуры Docker и подробно о всех основных функциях собрана в одном месте

Оглавление

Архитектура Docker

Отличие Docker от Виртуальных машин

Простыми словами ключевое отличие это отстутсвие у Docker гостевой OS. Docker ничего общего не имеет с виртуальными машинами, разве что только изолированность.

Docker-vs-VM.jpg

Вся суть в процессах работы Linux. К примеру при запуске работы приложения на Nodejs первым делом запускается новый процесс с помощью внутреннего вызова комманды fork, который создаст новый процесс, при этом скоппировав предидущий процесс. Поэтому будет Bash потом fork потом второй Bash после этого подменяется новым процессом. Это делается с помощью комманды execv это заменит текущий процесс замененого Bash на текущий процесс ноды. У этого процесса появится свой process ID и он будет уже функционировать уже в рамках этого process ID.

Этот процесс он при этом не является изолированным. Поскольку если другой процесс занимает ту же часть диска, то удалит текущий процесс. Эта проблема изорилованности процессов была до тех пор, пока в Linux не появилась команда chroot - эта комманда позволяет получить прообраз изоляции с помощью изменения root директории. В Docker используется конечно же не chroot, там используются namespaces. Но это некая аналогия, которая показывает как работает Docker. Запускается процесс, потом ему изменяют root директорию на какую-то кастомную и он не будет видеть никакие другие соответствующие бинарники, папки и прочее внутри своего изолированного пространства. Он также может скопировать необходимые другие бинарники и запускатся уже с ними.

Docker-container-namespace.jpg

Когда запускается новый процесс, то Docker открывает новый namespace. Фактически, когда мы запускаем контейнер, мы запускаем новый Namespace. Он имеет несколько наборов, так званные СGroups. Которые управляют ограничениями по памяти. IPC - это управления процесами, которые позволяют создавать изолированость и общение в namespace. Свой network. Mount который говорит какие директории доступны, которые доступны и какие нет. Process ID, который может повторятся с process ID текущего, то есть при запуске на хосте, process ID является уникальным, то в namespace process ID может быть свой. User, который может быть своим по аналогии с process ID.

Кроме этого в Docker существуют некоторые обвязки, которые обвязывают все эти параметры namespace и непосредственно какие-то Vоlume и т.д.

Поэтому по сути контейнер это некая изолированная часть, изолированный namespace, который запускается на ядре хостовой машины и функционирует полностью изолировано кроме того, что оно получает доступ к ресурсам этой машины. Благодаря этой изоляции мы получаем полностью изолированное пространство в котором мы можем делать ряд различных вещей и кроме того можно запускать разные библиотеки, тем самым на одном ядре может например находится Gem Linux, а на другом namespace - Ubuntu.

Поэтому контейнер это не виртуальная машина, это изолированный namespace с дополнительными обвязками Docker в котором у нас запускается приложение, запускаются различные библиотеки с соответствующим ядром и после этого этот контейнер стартует и позволяет с ним работать с помощью удобного API.

Docker-Engine.jpg

Сам Docker не имеет ядра и поэтому мы не можем запускать процессы для одной архитектуре и запускать на другой архитектуре. То есть Docker не позволяет делать эмуляцию между процессами, поэтому если контейнер был запущен на архитектуре х64, то он успешно откроется только на такой архитектуре, а не на армовской или ещё какой-либо другой.

Внутри Docker состоит из трёх частей и когда, например, вводится комманда Docker ps то это работа не с самим docker'ром, а с клиентом внутри docker'а. Это просто удобная СLI утилита.

Подробная схема работы

image.jpg

У нас есть клиент - тот самый клиент из которого вводятся комманды по типу docker рs и всё что угодно. После этого делается запрос к API к хосту, на этом хосте крутится Docker daemon, Docker daemon проверяет есть ли у нас такой image внутри, в наличии локально. Если такой image нету, то daemon пошел скачивать его из общего регистра, например пошел на Docker hub. После этого он скачивает нужный image и запускать новый контейнер. На самом деле всё что он делает это создаёт новый namespace и передаёт туда этот Image. Кладёт туда нужные либы и распаковывает этот image, чтобы всё запустить.

Управление контейнерами

Docker container - это сущность отвечающая за работу с контейнерами.
Но название container можно опускать, поскольку в большинстве комманд он по умолчанию устроен под container, поскольку при работе с Docker очень приходится работать именно с container и поэтому команды работы с контейнерами вынесены на верхний уровень. Из-за этого следующие команды равны по своей сущности:
docker start - docker container start
docker stop - docker container stop

Жизненный цикл контейнера

Когда мы делаем docker run у нас скачивается image. Этот Image запускается и превращается в контейнер. После того как мы запустили контейнер у нас появляется жизненный цикл контейнера:

image.jpg

Когда мы запустили контейнер, у него появляется жизненный этап running. Запускается наше приложение и контейнер работает. Когда нужно остановить контейнер то необходимо использовать команду docker stop, которая приводит контейнер в состояние - stopped. Когда нужно контейнер уничтожить контейнер, то необходимо использовать команду docker kill. Когда нужно перезагрузить контейнер, то нужна команда docker restart. Когда нужно поставить контейнер на паузу, то нужна команда docker pause. Если нужно удалить контейнер, то используется команда docker rm Если нужно удалить контейнер, даже если он запущен, тогда необходимо применить команду docker rm -f.

Сигналы процессам

Каждая команда сопровождается сигналом, который позволяет нам завершить процесс:

Комманда Docker Сигнал Пояснение
docker stop SIGTERM
SIGKILL
При введении команды, Docker'у посылается команда SIGTERM, которая завершает процесс, но если в течении какого-то времени контейнер не останавливается, то через какое-то время Docker с помощью сигнала SIGNKILL убьёт этот контейнер. То есть даже если контейнер повис, Docker всё равно его убьёт
docker pause SIGNSTOP Ставит контейнер на паузу
docker kill SIGNKILL Убивает контейнер, даже если он повис

Основные команды Docker

Комманда Синтаксис Пояснение
run --name docker run --name <container_name> Создание контейнера с указанным именем
start docker container start <container_name> Запуск контейнера с указанным именем
stop docker container stop <container_name> Остановка контейнера с указанным именем
ps -a docker ps -a Вывести все (как запущенные так и остановленные) контейнеры
ps docker ps Вывести все запущенные контейнеры
remove docker container remove <container_name> Удалить контейнера с указанным именем
prune docker container prune Удалить все остановленные контейнеры
rename docker remae <old_container_name> <new_container_name> Переименовать контейнер с указанным именем на новое имя
stats docker stats Выводит статистику по всем контейнерам в реальном времени таких параметров как: ЦПУ, использование памяти, выход и выход сети, ID процесса и т.д.
inspect docker inspect <container_name> Получить всю подробную информацию о контейнере в формате JSON, в том числе State, ID процесс или Image`и с которых был сделан контейнер
inspect -s docker inspect -s <container_name> Получить размер контейнера
inspect -f "{{.field}}" docker inspect -f "{{.field.field}}" <container_name> Получить детали конкретной строчки из JSON

Логи Docker

Контейнер на протяжении своего жизненного цикла создаёт логи. Чтобы получить все логи конкретного контейнера необходимо ввести комманду:

docker logs <container_name>

Поскольку лента логов достаточно длинная и поэтому слабо читабельная, то уже с помощью команд Linux есть возможность витянуть из логов что-то конкретное. Для этого необходимо использовать оператор пайпа ( | ), чтобы передать результаты предидущей команды в следующую команды, а также использовать функцию grep. Функция grep позволяет вытащить необходимый кусок текста описывая регулярные выражения в RegExp внутри запроса.

Синтаксис Пояснение
docker logs <container_name> \ grep id Выводит все строки содержащие id
docker logs <container_name> \ grep id -A 10 Выводит 10 строк после нахождения строки содержащую id
docker logs <container_name> \ grep id -В 15 Выводит 15 строк до нахождения строки содержащую id
docker logs <container_name> \ grep id -m 2 Выводит 2 первых строки содержащие id
docker logs <container_name> text.txt Сохранить логи, которые будут выведены в файл text.txt

Команды внутри контейнера

Порой необходимо войти в контейнер руками, чтобы что-нибудь сделать, подправить или посмотреть. Синтаксис docker команда для контейнера:

docker exec [параметры] <container_name> [комманда]

При этом параметры могут быть следующими:

Параметр Пояснение
-i итерактивное
-t псевдо tty
-d запуск в фоне
-e переменная окружения
-u пользователь
-w рабочая директория

А комманды:

Комманда Пояснение
bash исполнить код
pwd выводит текущую рабочую директорию

Любое исполнение команд сработает, если у контейнера статус running, другими словами – контейнер запущен.

Docker Image

Подробно про Image

Image из себя представляет некий список слоёв. Каждый из этих слоёв имеет свой уникальный идентификатор. Это поможет сильно экономить пространство на диске.

Image.jpg

У нас есть некий контейнер, который (впрочем как и все) состоит из слоёв. Каждый слой на самом деле это тоже Image. Это некий слепок, который содержит некую информацию. Все слои, которые содержит наш Image доступны нам только на чтение. То есть когда строится наш контейнер, он сформировывает 6 слоёв. После того как создастся наш контейнер, создастся ещё один, тонкий слой и вот этот слой доступен на запись. Поэтому сколько бы мы одинаковых контейнеров не запускали, они все будут базироватся на одном Image таким образом сильно экономя пространство.

image.jpg

Каждый Image имеет конкретный этап сборки и при запуске двух одинаковых контейнеров, будет накладыватся лишь один, отличающийся слой, при этом та часть, что доступна для чтения будет как раз общая.

Комманды работы с Image

Все комманды можно получить с помощью комманды --help

Команда Описание Пример комманды
pull Выкачать Image с Docker Hub docker pull <image_name>
images Просмотреть все Image'и docker images
save Сохранить Image на диск docker save --output <image_name_archive> <image_name>
history Просмотреть как был собран образ docker history <image_name>
build Построить image из dockerfile docker build
import Развернуть образ из архива docker image import <image_archive_name>
inspect Получить подробную информацию по самому образу: когда был создан, его ID, название контейнеров и так далее docker inspect <image_name>
ls Вывести все текущие образы, которые есть. Также работает динамическая строка форматирования docker image ls
prune Чистит неактивные образы или образы у которых отсутствует тэг docker image prune
push Пушит сбилденный образ в registry docker push <image_name>
rm Удаляет конкретный образ docker image rm <image_name>
save Сохранить образ в некоторый архив, чтобы потом куда-нибудь его перекинуть docker image save <image_name>
tag Создать тэг для контейнера docker tag <tag_name>

Dockerfile

Dockerfile состоит из строк, где каждая строка начинается из какой-то команды и её аргументов. Например WORKDIR /opt/app означает:

  • WORKDIR комманда
  • /opt/app - аргумент

Dockerfile.jpg

Каждый раз когда добавляется строка, добавляется новый слой. Поэтому необходимо оптимизировать Dockerfile. При этом есть ограничение количества строк в размере 127. Помимо постройки image с dockerfile есть ещё такое понятие как контекст. Контекст это путь выполнения команды docker build со всеми вложенными директориями. Поэтому если указать начальную папку как корневую, то контекстом будет вся операционная система это будет медленно и неефективно. Поэтому важно понимать, где начинается контекст. Там где производится docker build там и контекст.

При этом мы не можем выйти вверх. Поэтому если нам нужны какие-то файлы из верха, то нам нужно начинать строить приложение из этого верха. Также из контекста можно что-то удалить с помощью файла .dockerignore например node_modules как например в .gitignore

Команды dockerfile

dockerfile.jpg

Подробнее о командах dockerfile:

  • Аргументы это дополнительные параметры, которые можно передать при сборке. Аргументы никогда не остаются после того, как был собран image. Это аргументы, которые распростроняются во время билда. В финальном сборке их не будет. Это для передачи из вне. Это полезно например, когда билдится какой-нибудь нодовский модуль и нужен приватный репозиторий и туда передать какой-то токен, который должен существовать только в рамках билда и в рамках этого билда для того, чтобы установить зависимости.
  • FROM это то на чём базируется image. Он должен быть обязателен при каждой сборке. Также этому image можно задать алиас, чтобы сделать multistage билдинг.
  • ONBUILD - это полезно только тогда, когда строится какой-то image, который базируется на другом образе.
  • LABEL - это мета информация. Это та информация, которая останется в финальной сборке и её можно будет посмотреть.
  • USER и WORKDIR это информация о пользователе, который будет иметь доступ к рабочей папки. Всё что до строчки с USER выполняется с дефолтным пользователем в корневой папке, а всё что ниже - выполняется уже с привязанным пользователем.
  • Команда ADD чаще используется когда нужно добавить что-то из хостовой машины в контейнер сборки. Например это может быть гит репозиторий. Первым аргументом он принимает то, что нужно скопировать, а вторым аргументом куда это нужно скопировать. В отличии от COPY способен делать дополнительные фичи, такие как разархивация архива или например скачать архив с URL.
  • Команда COPY в отлчии от ADD позволяет нам коппировать инфорацию из других image при multistage build.

Dockerfilee.jpg

  • SHELL - позволяет также как и USER и WORKDIR установить какой shell будет использоваться после этой строки.
  • RUN - позволяет выполнить какую-то комманду в SHELL например установка каких-то зависимостей из репозитория.
  • ENV - позволяет объявить и использовать их несколько раз. Если нам нужно передать какие-то переменные, но при этом чтобы эти переменные не передались в финальную сборку, нужно передать эти команды перед самой командой.
  • STOPSIGNAL - позволяет отправить специализированный сигнал.
  • EXPOSE - это просто некоторая документация для тех, кто будет использовать этот image о том, что можно будет прокинуть порт 80 по TCP и в него чем-то там дёрнуть и будет получено то что там спрятано.
  • # - комментарии.

Multistage build

Исходный код сохраняется в финальном образе и это является проблемой. Плюс также в production версии нам нужен лишь наш код и только node_modules, которые используются исключительно в production версии.

В multistage build мы можем скрыть наши секреты, установив их в первом образе, который зашифрует их в последующих образах.

Сети Docker

Сетями Docker управляет библиотека libnetwork которая позволяет управлять сетями внутри docker'а. При этом важно отметить, что libnetwork испольузет стандартые элементы, которые есть в Linux ядре.

Docker.jpg

Libnetwork использует в себе:

  • Network Namespace - это namespace, которые использует контейнер.
  • Linux Bridge - это некоторые виртуальный мост, который позволяет передать пакеты из одного входа к другому.
  • Virtual Ethernet Devices - это виртуальная эмуляция, будто контейнер имеет какой-то ethernet выход и мы можем к нему подключится и что-то от него получить.
  • IP tables - это управление портами.

Работа сетей Docker

ocker.jpg

Когда у нас поднимается контейнер, он по умолчанию подключается к сети Bridge. Как только он подключается к сети внутри контейнера создаётся виртуальный ethernet адаптер, который и позволяет взаемодействовать с этой сетью. За этим мостом распологается реальный ethernet хостовой машины, который может быть как физический так и виртуальный, но который имеет реальный ethernet адрес.

Поэтому каждый контейнер имеет условную розетку (veth) при этом каждый контейнер может иметь несколько розеток, чтобы подключатся к различным сетям. Потому что один контейнер может теоритически быть подключённым к различным сетям.

Типы сетевых драйверов Docker

Сетевой драйвер это вариант работы с сетью. Всего их в суме по умолчанию в суме 5:

  • bridge - это самый часто используемый контейнер. По сути это некоторая изолированная сеть между контейнерами.
  • host - это сетевой драйвер позволяет убрать слой изоляции и тем самым, позволяет работать напрямую с файловой системой хостовой машины.
  • overlay - это тип сетей, который позволяет соединить множество хост машин. Объеденить одной сетью множество контейнеров, чтобы те могли находить друг друга. Чаще всего используется в swarm.
  • macvlan - используется крайне редко. Он позволяет создать уникальный адрес для контейнера и прокинуть его наружу. По сути в сети появляется новое устройство со своей сетью. Необходимо использовать крайне акуратно поскольку большое количество таких сетей может сказыватся на производительности всех сетей в целом.
  • null - это без сети. Когда нужно, чтобы контейнер не имел никакого доступа в какую-либо сеть.

Комманды по управлению сетями

Команда Описание Пример комманды
connect Подключить контейнер к сети docker network connect <network_name>
create Создать сеть docker network create <network_name>
disconnect Отключить контейнер от сети docker network disconnect <network_name>
inspect Параметры сети docker network inspect <network_name>
ls Получить весь список сетей docker network ls
prune Удалить все отключённые сети docker network prune
rm Удалить конкретную сеть docker network rm <network_name>

Сетевой драйвер типа Bridge

Драйвер Bridge необходим, когда мы хотим соединить в рамках одной сети несколько контейнеров и одного хоста. Он обеспечивает локальный service discovery, чтобы один контейнер мог обращаться к другому сервису по имени. Часто он используется для локальной розработки, например чтобы поднять фул стэк.

Bridge.jpg

При подключении контейнера к сетевому драйверу Bridge у него появляется доступ к общей сети Bridge Network. Он также получает IP в рамках этой сети. Когда контейнеров несколько в одной сети, то каждый из них получает свой IP адрес, в рамках этой сети.

image.jpg

Когда же у нас, например три контейнера и когда конейнеры 1 и 2 подключены к 1 сети, а 2 и 3 к другой, тогда у контейнера 2 будет возможность подключится к 1 и к 3 контейнеру, но при этом контейнеры 1 и 3 не смогут между собой общаться. Благодаря такой схеме можно изолировать контейнеры по разным сетям при этом объеденив их одним контейнером. Такой подход часто практикуется в микросервисной архитектуре, когда у нас есть одна общая шина приёма событий, но при этом каждый сервис реагирует лишь на свой тип этих событий.

Проброс портов

Когда же нам нужен доступ к контейнеру с хостовой машины. Тогда вступает в роботу Port Mapping.

Port-Mapping.jpg

Port Mapping позволяет сказать контейнеру, что в рамках этой сети если стучатся на порт 80, то переадрисуй нас на опрт 8080.

Сетевой драйвер типа Host

Хост сеть позволяет нам убрать слой абстракции, которые предоставляют Docker network и получать доступ непосредственно к файловой системе хост машины. Доступ получается только с точки зрения к сети.

Host.jpg

Это полезно когда есть одна база данных и она развёрнута на одном из хосте специально выделенным под эту базу данных и дополнительный слой абстракции не нужен, потому что контейнер всего один.

Сетевой драйвер типа Null

Null сеть необходима в том случае, когда нужно, чтобы вообще не было никакого доступа ни к какой сети.

Null.jpg

Такое может быть полезно, когда например нужно чтобы контейнер сделал какую-либо одну операцию над файлами. Тогда этому контейнеру не нужна никакая сеть и он не должен никуда подключатся. Тако позволяет сэкономить ресурсы. Самый популярный вариант это контейнеры которые поднимаются, выполняет какую-то задачу и убивается, например генерация и выдача каких-то данных.

Docker volumes

Все данные и состояние контейнера хранятся до того момента, пока контейнер не удалили. Поэтому когда мы останавливаем контейнер, в реальности данные этого контейнера больше не доступны, до момента когда мы не запустим контейнер. Вторая проблема в том, что если мы удалим контейнер, то все данные будут утеряны. Это плохо потому что можно обновлять базу данных, но данные базы данных в любом случае необходимо сохранять.

Поэтому есть Volume. Volume работают как в swarm режиме так и в обычном. Преимущественно они используются в обычном режиме, поскольку в swarm нельзя предугадать на какой ноде поднимется контейнер и там нужно специальный тип хранилища.

Volume.jpg

Есть три различных типа, как мы можем подключить Volume к нашему контейнеру:

  • Volume - это основной способ рекомендованый, который позволяет подключить область хостовой машины к контейнеру. Внутри Docker создаётся специальная область, по простому - папка. Она биндится на папку внутри контейнера. Всё что попадём в папку, попадает напрямую в контейнер Docker'а.
  • Bind mounts - это когда биндится не в область внутри Docker, а в любую папку файловой системы хост машины.
  • tempfs - это когда данные не биндятся к определенной папке, а просто хранятся в памяти. Это может быть полезно для временного хранилища каких либо секретных данных.

Команды работы с Volume

Команда Описание Пример комманды
ls Получить список всех Volume docker volume ls
create Создать Volume docker volume create <volume_name>
inspect Получить детали определенного Volume docker volume inspect <volume_name>
rm Удалить Volume. Может быть исполнено, если все связанные контейнеры уже удалены docker volume rm <volume_name>
remove -f Удаляет Volume в любом случае docker volume rm <volume_name> -f

Хранилище типа Volumes

Если заинспектить любой из Volume, то можно найти поле: Mountpoint - это то место, где распологается данные этого Volume на хостовой машине и все данные, что будут складоваться, будут накапливатся в данной папке.

Чтобы добавить данные необходимо ввести следующую команду:

docker run —name <volume_name> -d -v <volume_name>:<docker_vowkdir>/<endpoint> <image_name>
docker run —name volume-1 -d -v demo:opt/app/data deno4:latest

или если прокидываются порты:

docker run —name <volume_name> -d -v <volume_name>:<docker_vowkdir>/<endpoint> -p <output_port>:<input_port> <image_name>
docker run —name volume-1 -d -v demo:opt/app/data -р 3000:3000 deno4:latest

Чтобы подключить ещё один контейнер в эту же папку:

docker run —name <volume_name_2> -d -v <volume_name>:<docker_vowkdir>/<endpoint> -p <output_port>:<input_port> <image_name>
docker run —name volume-2 -d -v demo:opt/app/data -р 3001:3000 deno4:latest

После, чтобы зайти в папку необходимо зайти по локальной сети на нужный порт с вызовом нужного маршрута:

curl "127.0.01:<port>/<endpoint>
curl "127.0.01:3001/get

Хранилище типа Bind mountes

Cоздание с подтягиванием корневого пути:

docker run —name <volume_name> -d -p <output_port>:<input_port> -v <path>:<container_folder_path> <image_name> 
docker run —name volume-1 -d -p 3000:3000 -v /home/vladislav/data:/app/data demo_image 

Если папки на которую биндится нету – то она создастся. После выполнения команды в Volume ничего не будет. В таком случае было просто расширено хостовая машина вместе с Docker. Это нужно, когда необходимо прокинуть какой-то config. Например положить рядом с запуском приложения какой-то config и его нужно подсунуть вместе с запуском контейнера. C помощью Bind Mount можно биндить не только папки, но и конкретные файлы. То есть в по сути это не перекладывание файла в папку, а связывания файла с Volume. Если прибиндить папку, а потом этот контейнер куда-то уйдет, то уже не будет известно, что ушли какие-то данные и они не будут захломлять систему. Если с Volumes можно сделать только volume prune, то с bind mount придется искать руками где он был примонтирован и соответсвенно ремувать его.

Хранилище типа TempFS

TempFS это по сути монтирования маленького кусочка кода внутрь памяти. Точнее это хранение кусочка файловой системы внутри памяти. При этом у этого хранения есть свои особенности:

  • не работает в swarm режиме.
  • нельзя шарить между контейнерами.
  • удаляются после остановки контейнера.

Это может понадобится, для временного хранения данных, которые б в дальнейшем нельзя было бы найти. То есть если в контейнере были сохранены какие-то данные, то даже если нет Volume, то можно зайти в контейнер и посмотреть в writable-слой данные, где эти данные сохранены.

docker run —name <container_name> -d -p 3000:3000 —tmpfs <container_path_folder> <image_name> 

Docker-compose

Docker registry

About

Detailed description of commands and a structured description of all the main features of Docker in one repository