### 1. Чем хорош Docker? - Замена виртуальных машин - Прототипирование программного обеспечения - Упаковка программного обеспечения - Возможность для архитектуры микросервисов - Моделирование сетей - Возможность производительности полного стека в автономном режиме - Сокращение неизбежных расходов на отладку - Документирование зависимостей программного обеспечения и точки взаимодействия - Возможность непрерывной доставки #### 1.1 Ключевые концепции Прежде чем запускать команды Docker, лучше всего разобраться с поняти- ями образов, контейнеров и слоев. Говоря кратко, контейнеры запускают системы, определенные образами. Эти образы состоят из одного или нескольких слоев (или наборов различий) плюс некоторые метаданные Docker. Если вы знакомы с принципами объектно-ориентированного программи- рования, еще один способ взглянуть на образы и контейнеры – это рассма- тривать образы как классы, а контейнеры – как объекты. Точно так же, как объекты представляют собой конкретные экземпляры классов, контейнеры являются экземплярами образов. Вы можете создать несколько контейнеров из одного образа, и все они будут изолированы друг от друга так же, как и объекты. Что бы вы ни изменили в объекте, это не повлияет на определение класса – это принципиально разные вещи. Dockerfile – это простая последовательность ограниченного набора команд, выполняемых в строгом порядке. ### 1.2 Слои Docker Слои Docker помогают справиться с большой проблемой, которая возника- ет, когда вы используете контейнеры в широком масштабе. Представьте себе, что произойдет, если вы запустите сотни или даже тысячи приложений, и каждому из них потребуется копия файлов для хранения в каком-либо месте. По умолчанию Docker внутренне использует механизм копирования при записи, чтобы уменьшить объем требуемого дискового пространства. Всякий раз, когда работающему контейнеру необходимо выполнить запись в файл, он записывает изменение, копируя элемент в но- вую область диска. После выполнения фиксации новая область диска замораживается и записывается как слой со своим собственным идентификатором. ### 2.1. Архитектура Docker Docker на вашем хост-компьютере разделен на две части: демон с прикладным программным интерфейсом RESTful и клиент, который общается с демоном. Вы вызываете Docker-клиент, чтобы получить информацию или дать ин- струкции демону. Демон – это сервер, который получает запросы и возвращает ответы от клиента по протоколу HTTP. В свою очередь, он будет отправлять запросы в другие службы для отправки и получения образов, также используя протокол HTTP. Сервер будет принимать запросы от клиента командной строки или любого, кто авторизован для подключения. Демон также отвечает за заботу о ваших образах и контейнерах за кулисами, тогда как клиент выступает в качестве посредника между вами и интерфейсом RESTful. ### 3. Запуск контейнеров в качестве демонов Используйте флаг -d в команде docker run и связанные флаги управления контейнерами для определения характеристик службы. ### Различия между виртуальными машинами и контейнерами Docker * Docker ориентирован на приложения, в то время как виртуальные машины - на операционные системы; * контейнеры Docker совместно используют операционную систему вместе с другими контейнерами Docker. Напротив,у каждой виртуальной машины есть собственная операционная система, управляемая гипервизором; * контейнеры Docker предназначены для запуска одного основного процесса, а не для управления несколькими наборами процессов. ### 3.2 Разделение системы на микросервисные контейнеры Docker, как правило, рекомендуется разделять систему как можно больше, пока у вас не будет запущена одна служба для каждого контейнера, а все контейнеры не будут связаны сетями. Основной причиной использования одного сервиса на контейнер является более легкое разделение интересов по принципу единственной ответственности. Если у вас есть контейнер, выполняющий одну работу, легче провести его через жизненный цикл разработки программного обеспечения, включая разработку, тестирование и производство, не беспокоясь о его взаимодействии с другими компонентами. ## Использование socat для мониторинга трафика Docker API Нам нужно вставить доменный сокет Unix между запросом и сокетом сервера, чтобы посмотреть, что проходит через него. Для создания этого прокси вы будете использовать socat. ``` socat -v UNIX-LISTEN:/tmp/dockerapi.sock,fork UNIX-CONNECT:/var/run/docker.sock & ``` UNIX-LISTEN говорит socat прослушивать сокет Unix, fork гарантирует, что socat не завершит работу после первого запроса, а UNIX-CONNECT сообщает socat подключиться к Unix-сокету Docker. Чтобы посмотреть запросы от Docker, надо ввести команду ``` docker -H unix:///tmp/dockerapi.sock ps -a ``` Socat может прослушивать внешний порт с помощью TCP-LISTEN: 2375, fork. ## Использование портов для подключения к контейнерам Используйте флаг -p для отображения порта контейнера в свой хост-компьютер. ``` docker run -d -p 10001:80 --name blog1 tutum/wordpress ``` Команда docker run запускает контейнер как демон (-d) с флагом (-p). Она идентифицирует порт хоста (10001) для отображения в порт контейнера (80) и присваивает контейнеру имя, чтобы его идентифицировать (-- name blog1 tutum/wordpress). При использовании флага -p порты передаются в следующем порядке host_ port: container_port). Чтобы удалить контейнеры выполняем команду: ``` $ docker rm -f blog1 blog2 ``` ### 4 Основы Docker #### Архитектура Docker Главные компоненты Docker 1. демон Docker (Docker daemon), ответственный за создание, запуск и контроль работы контейнеров, а также за создание и хранение образов. 2. клиент Docker. Используется для диалога с демоном Docker по протоколу HTTP. По умолчанию это соединение устанавливается через сокет домена Unix, но также может использоваться TCP-сокет для поддержки соединений с удаленными клиентами или дескриптор файла для сокетов, управляемых systemd. Так как все операции обмена данными выполняются по протоколу HTTP, можно без затруднений организовать соединение с удаленными демонами Docker и разработать привязки (bindings) к нужному языку программирования, но при этом следует учитывать особенности реализации этих возможностей, например обязательное наличие контекста создания (building context), описанного в соответствующем разделе данной книги. Интерфейсы прикладного программирования, используемые для организации обмена данными с демоном, четко определены и подробно документированы, что позволяет разработчикам писать программы, взаимодействующие напрямую с демоном, без использования клиента Docker. Клиент и демон Docker распространяются как отдельные независимые бинарные файлы. 3. Реестры Docker используются для хранения и распространения образов. Реестром, выбираемым по умолчанию, явялется Docker Hub, на котором хранятся тысячи общедоступных образов, а также управляемые «официальные» образы. ### Базовые технологии Демон Docker использует «драйвер выполнения» (execution driver) для создания контейнеров. По умолчанию выбирается собственный драйвер Docker runc. Драйвер runc очень тесно связан со следующими механизмами ядра: 1. cgroups – механизм, отвечающий за управление ресурсами, используемыми контейнером (процессор, оперативная память и т. д.). Механизм cgroups также обеспечивает выполнение операций «замораживания» (freezing) и «размораживания» (unfreezing) контейнеров как поддержку функциональности команды docker pause. 2. пространства имен (namespaces) отвечают за изоляцию контейнеров, гарантируют, что файловая система, имя хоста, пользователи, сетевая среда и процессы любого контейнера полностью отделены от остальной части системы. Еще одной основополагающей технологией для Docker является файловая система с каскадно-объединенным монтированием (Union File System – UnionFS), обеспечивающая хранение уровней для контейнеров. Функциональность UnionFS обеспечивается одним из нескольких драйверов файловой системы: AUFS, devicemapper, BTRFS или Overlay. #### Сопровождающие технологии 1. Swarm – решение задачи кластеризации от Docker. Swarm позволяет сгруппировать несколько Docker-хостов, после чего пользователь может работать с этой группой как с единым ресурсом. 2. Docker Compose – инструмент для создания и выполнения приложений, скомпонованных из нескольких Docker-контейнеров. Такие компоновки используются главным образом при разработке и тестировании, но гораздо реже в производственной среде. 3. Docker Machine устанавливает и конфигурирует Docker-хосты на локальных и удаленных ресурсах. Кроме того, Machine конфигурирует клиента Docker, упрощая процедуру переключения между средами. 4. Kitematic представляет собой графический пользовательский интерфейс для операционных систем Mac OS и Windows, обеспечивающий запуск и управление контейнеров Docker. 5. Docker Trusted Registry – локально устанавливаемое программное решение для хранения и управления образами Docker. В действительности это локальная версия реестра Docker Hub, которую можно объединить с существующей инфраструктурой обеспечения безопасности и согласовать с правилами хранения и обеспечения защиты данных, принятых в конкретной организации. Все функциональные возможности локального реестра, в том числе различные метрики, управление доступом на основе ролей (Role-based access control – RBAC) и регистрационные журналы, контролируются через административную консоль. #### Установление связи контейнеров с внешним миром Допустим, вы запустили веб-сервер внутри контейнера. Но как обеспечить связь сервера с внешним миром? Ответ прост – открыть нужные порты для общего доступа с помощью аргументов -p или -P в команде запуска. Такая команда перенаправляет порты хоста в контейнер. Например: ``` docker run -d -p 8000:80 nginx ``` Аргумент -p 8000:80 сообщил механизму Docker о необходимости перенаправления порта 8000 хоста на порт 80 в контейнере. ### Инструкции. Dockerfile 1. ADD Копирует файлы из контекста создания или из удаленных URL-ссылок в созда- ваемый образ. Если архивный файл добавляется из локального пути, то он будет автоматически распакован. Так как диапазон функциональности инструкции ADDдостаточно велик, в общем случае лучше воспользоваться более простой ко- мандой COPY для копирования файлов и каталогов в локальном контексте соз- дания или инструкциями RUN с запуском curl или wget для загрузки удаленных ресурсов (с сохранением возможности обработки и удаления результатов за- грузки в той же самой инструкции). 2. CMD Запускает заданную инструкцию во время инициализации контейнера. Если была определена инструкция ENTRYPOINT, то заданная здесь инструкция будет интерпретироваться как аргумент для ENTRY POINT (в этом случае необходимо использовать формат exec). Инструкция CMD замещается любыми аргументами, указанными в командеdockerrunпосле имени образа. В действительности вы- полняется только самая последняя инструкция CMD, а все предыдущие инструк- ции CMD будут отменены (в том числе и содержащиеся в основных образах). 3. COPY Используется для копирования файлов из контекста создания в образ. Име- ет два формата:COPYисточникцельиCOPY["источник","цель"]– оба копируют файл или каталог из «источник» в контексте создания в «цель» внутри контей- нера. Формат JSON-массива обязателен, если путь содержит пробелы. Можно использовать шаблонные символы для определения нескольких файлов или каталогов. Следует обратить особое внимание на невозможность указания пу- тей «источника», расположенных вне пределов контекста создания (например, нельзя указать для копирования файл ../another_dir/myfile). 4. ENTRYPOINT Определяет выполняемый файл (программу) (и аргументы по умолчанию), запускаемый при инициализации контейнера. В эту выполняемую программу пе- редаются как аргументы любые инструкции CMD или аргументы команды docker run, записанные после имени образа. Инструкции ENTRYPOINT часто используют- ся для организации скриптов запуска, которые инициализируют переменные и сервисы перед обработкой всех передаваемых в образ аргументов. 5. ENV Определяет переменные среды внутри образа. На эти переменные можно ссы- латься в последующих инструкциях. ```` ENV MY_VERSION 1.3 RUN apt-get install -y mypackage=$MY_VERSION ``` 6. EXPOSE Сообщает механизму Docker о том, что в данном контейнере будет существо- вать процесс, прослушивающий заданный порт или несколько портов. Меха- низм Docker использует эту информацию при установлении соединения между контейнерами (см. раздел «Соединение между контейнерами» ниже) или при открытии портов для общего доступа при помощи аргумента-Pв командеdocker run. Но сама по себе инструкция EXPOSE не оказывает никакого воздействия на сетевую среду. 7. FROM Определяет основной образ для файла Dockerfile. Все последующие ин- струкции выполняют операции создания поверх заданного образа. Основной образ определяется в форме IMAGE:TAG (например, debian:wheezy). При отсут- ствии тега по умолчанию полагается latest, но я настоятельно рекомендую всегда явно указывать тег конкретной версии, чтобы избежать неприятных неожиданностей. Эта инструкция обязательно должна быть самой первой в Dockerfile. 8. MAINTAINER Определяет метаданные об авторе «Author» для создаваемого образа в задан- ной строке. Извлечь эти метаданные можно с помощью командыdockerinspect -f{{.Author}}IMAGE. Обычно используется для записи имени автора образа и его контактных данных. 9. ONBUILD Определяет инструкцию, которая должна выполняться позже, когда данный образ будет использоваться как основной уровень для другого образа. Это мо- жет оказаться полезным при обработке данных, добавляемых в образ-потомок (например, это может быть инструкция копирования дополнительного кода из заданного каталога и запуска скрипта сборки, обрабатывающего скопирован- ные данные). 10. RUN Запускает заданную инструкцию внутри контейнера и сохраняет результат. 11. USER Задает пользователя (по имени или по идентификатору UID) для использова- ния во всех последующих инструкциях RUN, CMD, ENTRYPOINT. Отметим, что иден- тификаторы UID одинаковы на хосте и в контейнере, но имена пользователей могут присваиваться различным идентификаторам UID, что может приводить к затруднениям при установке прав доступа. 12. VOLUME Объявляет заданный файл или каталог как том. Если такой файл или каталог уже существует в образе, то он копируется в том при запуске контейнера. Если задано несколько аргументов, то они интерпретируются как определение не- скольких томов. Из соображений обеспечения безопасности и сохранения пере- носимости нельзя определить каталог хоста как том внутри файла Dockerfile. 13. WORKDIR Определяет рабочий каталог для всех последующих инструкций RUN, CMD, ENTRY- POINT, ADD, COPY. Инструкцию можно использовать несколько раз. Допускается указание относительных путей, при этом итоговый путь определяется относи тельно ранее указанного рабочего каталога WORKDIR. #### Соединение между контейнерами Соединения (links) механизма Docker – простейший способ обеспечения обмена информацией между контейнерами на одном хосте. При использовании принятой по умолчанию сетевой модели Docker обмен данными между контейнерами будет происходить во внутренней сети Docker, то есть все коммуникационные операции останутся невидимыми из сети хоста. Соединения инициализируются с помощью аргумента `--link CONTAINER:ALIAS` в команде `docker run`, где `CONTAINER` – имя контейнера-адресата (link container), а `ALIAS` – локальное имя, используемое внутри управляющего контейнера для обращения к контейнеру-адресату. Кроме того, при использовании соединений Docker внутреннее имя и идентификатор контейнера-адресата будут добавлены в файл /etc/hosts в управляющем контейнере, что позволит обращаться по этому имени к контейнеру-адресату из управляющего контейнера. Далее Docker создает в управляющем контейнере набор переменных среды, предназначенных для упрощения диалога с контейнером-адресатом. Например, при создании контейнера Redis и установления соединения с ним: ``` $ docker run -d --name myredis redis bc2506d6538487f0eb6b9dbf9ee71f2d0eb666be028a5ff0ca26a4a3561b36cb $ docker run --link myredis:redis debian env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=13e3c3ba0a5d REDIS_PORT=tcp://172.17.0.2:6379 REDIS_PORT_6379_TCP=tcp://172.17.0.2:6379 REDIS_PORT_6379_TCP_ADDR=172.17.0.2 REDIS_PORT_6379_TCP_PORT=6379 REDIS_PORT_6379_TCP_PROTO=tcp REDIS_NAME=/suspicious_gagarin/redis REDIS_ENV_GOSU_VERSION=1.14 REDIS_ENV_REDIS_VERSION=7.0.0 REDIS_ENV_REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-7.0.0.tar.gz REDIS_ENV_REDIS_DOWNLOAD_SHA=284d8bd1fd85d6a55a05ee4e7c31c31977ad56cbf344ed83790beeb148baa720 HOME=/root ``` Мы можем видеть, что Docker создает переменные среды с префиксом REDIS_PORT, содержащие информацию, необходимую для установления соединения с контейнером Redis. Некоторые значения кажутся избыточными, поскольку нужная информация уже содержится в имени переменной. Тем не менее все переменные и их значения в любом случае полезны, хотя бы как своеобразная форма документации. #### Управление данными с помощью томов и контейнеров данных тома (volumes) Docker – это каталоги, которые не являются частью файловой системы UnionFS конкретного контейнера а представляют собой обычные каталоги в файловой системе хоста, но могут быть смонтированы как отдельные файловые системы (bind mounting) внутри контейнера. Существуют три различных способа инициализации томов. Важно хорошо понимать различия между этими способами. Во-первых, можно объявить том при запуске контейнера с помощью флага -v: ``` $ docker run -it --name container-test -h CONTAINER -v /data debian /bin/bash ``` Здесь каталог /data внутри контейнера станет томом. Любые файлы, которые данный образ сохранил в каталоге /data, копируются на этот том. Мы можем про- верить место расположения данного тома в файловой системе хоста, выполнив командуdockerinspectна хосте из новой командной оболочки: ``` $ docker inspect -f {{.Mounts}} container-test ``` Второй способ – объявление тома с помощью инструкции VOLUME в файле Dockerfile: ``` FROM debian:wheezy VOLUME /data ``` Результат будет в точности тот же самый, что при использовании ключа -v в команде docker run. ### Запуск нескольких процессов внутри контейнера В подавляющем большинстве контейнеров запускается только один процесс. Когда необходимо организовать работу нескольких процессов, наилучшим решением являются запуск нескольких контейнеров и установление соединений между ними, как показано в примере, рассматриваемом в текущем разделе. Тем не менее иногда без нескольких процессов в одном контейнере обойтись невозможно. В таких случаях лучше всего воспользоваться менеджером процессов, например supervisord (http://supervisord.org/) или runit (http://smarden.org/runit/), для управления запуском и контроля выполнения процессов. Кроме того, можно написать простой скрипт инициализации требуемых процессов, но при этом следует помнить, что в этом случае только на вас возлагается ответственность за корректный останов и удаление процессов, а также за правильную обработку и перенаправление всех сигналов.