Как получить доступ к файлам docker

Обновлено: 07.07.2024

В этой статье будет рассмотрен вопрос управления данными внутри контейнеров Docker, совместное использование данных разными контейнерами, а также резервное копирование этих данных.

Согласно последней документации, в Docker существует два способа организации работы с данными:

  • Docker тома (Docker volumes)
  • Контейнеры Docker томов (Docker volume containers)

Но прежде чем рассказать о них чуть подробнее, необходимо еще раз обратить ваше внимание на то, как работает файловая система в контейнерах Docker. Образы Docker, из которых запускаются контейнеры, представляют собой последовательность неизменяемых (только чтение) слоев. Набор этих слоев, можно посмотреть при помощи команды docker history, например, для контейнера nginx версии latest это делается так:

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

Для того, чтобы иметь возможность сохранять ваши данные, а также использовать эти данные несколькими контейнерами, Docker предоставляем вам возможность использовать так называемые тома (Docker volumes). Если совсем просто, то тома — это обычные директории (или файлы) хостовой файловой системы сервера, на котором работает Docker, монтируемые в любую директорию контейнера.

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

Данная команда запустит контейнер с именем nginx_example именем хоста контейнера (-h) nginx-container из образа nginx в интерактивном режиме работы (-it), передаст управление интерпретатору /bin/bash, предварительно создав безымянный том (-v) и смонтировав его в /opt/static. При этом в директория /opt/static будет создана автоматически, она будет жить вне UnionFS и будет доступна для записи внутри контейнера, т.е. все файлы, которые вы сохраните в этой директории будут помещены внутрь тома. Вы можете определить расположение тома на файловой системе хостового сервера, на котором работает Docker, при помощи команды docker inspect:

В этом примере используется фильтр (-f) всего вывода docker inspect nginx_example, показывающий только часть содержимого, которое относится к блоку Mounts. При этом вывод форматируется утилитой jq.

После того, как мы знаем имя тома (ключ Name), мы можем получить ту же информацию при помощи команды

В обоих случаях вывод показывает вам, что Docker смонтировал /opt/static внутрь контейнера как директорию /var/lib/docker/volumes/e01cb937734c02790c94c14339bc563159a88c025e266d298d48398352ea477d/_data. Чтобы убедиться в этом, создайте, например, файл test на хостовом сервере, на котором запущен Docker, внутри этой директории.

А затем посмотрите на содержимое /opt/static внутри контейнера.

Если вы отключились от интерактивного режима работы с контейнером, выполните команду docker start для запуска контейнера с интерактивным (-ai) к нему подключением:

Вы увидели этот результат только потому что /opt/static — это всего лишь директория на хостовом сервере, которая была смонтирована внутрь контейнера.

Тот же самый эффект будет достигнут при использовании инструкции VOLUME в Dockerfile:

Еще один способ создавать тома — это использовать команду docker volume create:

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

Есть еще один часто используемый способ создания тома, реализуемый только при помощи параметра -v, монтирующий указанную директорию хостового сервера внутрь контейнера. Для этого создайте на хостовом сервере директорию folder_test, внутри этой директории файл test_file, а затем пересоздайте контейнер, указав полный путь до директории folder_test:

Как видите, содержимое директории folder_test стало доступно внутри запущенного контейнера. Этот способ работы с томами очень удобно использовать, например, в процессе разработке приложений. В целях сохранения свойства переносимости контейнеров, директория, использующаяся для тома на хостовом сервере, не может быть указана в Dockerfile (ведь эта директория совершенно не обязательно должна быть доступна во всех контейнерах). В случае использования этой формы работы с томами (монтирование тома при помощи флага -v в процессе создания контейнера) файлы, возможно содержащиеся внутри директории образа, не копируются внутрь тома (в данном случае в директорию на хостовом сервере). Docker не управляет такого рода томами как показано в прошлых примерах, и поэтому такие тома не появятся в выводе команды docker volume ls и никогда не будут удалены демоном Docker.

Эта статья содержит рекомендации по написанию Dockerfile и принципам безопасности контейнеров и некоторые другие связанные темы, например про оптимизацию образов.

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

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

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


Избегать избыточных привилегий

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

1. Rootless контейнеры

Отчет sysdig показал, что 58% образов выполняют процесс в контейнере от root (UID 0). Рекомендуем избегать этого. Существует очень небольшой круг задач, для решения которых нужно запускать контейнер от root, поэтому не забывайте добавлять команду USER и менять UID пользователя на non-root.

Более того ваша среда выполнения контейнеров может по умолчанию блокировать запуск процессов в контейнере от имени root (например, Openshift требует дополнительные SecurityContextConstraints ).

Чтобы настроить non-root контейнер вам потребуется выполнить несколько дополнительных шагов в вашем Dockerfile.

Необходимо убедится, что пользователь, указанный в команде USER существует внутри контейнера.

Предоставить необходимы разрешения на объекты файловой системы, которые процесс читает или записывает.

Вы можете увидеть контейнеры, которые начинаются как root, а затем используют gosu или su-exec для перехода к обычному пользователю.

Если необходимо выполнить команду от имени root, вы можете использовать sudo.

Хотя эти две рекомендации, лучше запуска от имени root, они работают не во всех окружениях, например таких как OpenShift.

2. Не делайте привязку к определенному UID

Запускайте контейнеры без полномочий root, но также не делайте этот UID пользователя обязательным. Почему?

OpenShidt по умолчанию использует произвольные UID при запуске контейнеров.

Принудительное использование определенного UID требует изменения прав при монтировании папок с хостовой системы. Если вы запустите контейнер (параметр -u в докере) с UID хоста, это может нарушить работу службы при попытке чтения или записи из папок в контейнере.

Возникнут проблемы при запуске этого контейнера с UID отличающимся от myuser, так как приложение не сможет записывать в папку /myapp-tmp-dir folder.

Не нужно жестко задавать путь только для пользователя myuser. Вместо этого можно записать временные данные в /tmp (где любой пользователь может писать, благодаря разрешениям sticky bit). Сделайте ресурсы доступными для чтения (0644 вместо 0640) и убедитесь, что все работает, если UID измениться.

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

3. Назначить root владельцем исполняемых файлов и запретить изменять эти файлы

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

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

Следуя этой рекомендации старайтесь избегать подобной ситуации:

В большинстве случаев вы можете не использовать опцию --chown app:app (в том числе запуск команды RUN chown ). Пользователю app требуются только права на выполнение, при этом быть владельцем файла не обязательно.

Уменьшение поверхности атаки

Старайтесь минимизировать размер образа.

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

4. Многоступенчатые сборки

Используйте многоступенчатые сборки (multi-stage builds), чтобы компилировать ваши приложения внутри контейнеров.

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

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

Это безопасно, а также уменьшает размер образа.

Пример многоступенчатой сборки для приложения на go:

В этом Dockerfile на первом этапе мы создаем контейнер из образа golang:1.15, который содержит необходимые инструменты.

Мы можем скопировать исходный код и выполнить компиляцию.

Затем на втором этапе мы создаем новый контейнер основанный на образе Debian distroless (см. следующий совет).

Копируем артефакты, созданные на первом шаге, добавив опцию --from=builder .

Итоговый образ будет содержать оптимальный набор библиотек из образа образе Debian distroless и исполняемый файл приложения. При этом образ не содержит инструментов для компиляции, не содержит исходный код.

5. Distroless, from scratch

Использование минимального базового образа - еще одна рекомендация при создании Dockerfile.

Лучшим вариантом будет создание контейнера с нуля (scratch), но этот вариант подходит только для бинарных файлов, которые на 100% статичны.

Distroless - прекрасная альтернатива. Они разработаны, чтобы содержать только минимальный набор библиотек, необходимых для запуска Go, Python или других фреймворков.

Например, вы используете базовый образ ubuntu:xenial

При проверке образа сканером sysdig inline scanner были обнаружены более 100 уязвимостей. При этом большинство пакетов, содержащих уязвимости, скорее всего вам никогда не потребуются.

Нужен ли вам компилятор gcc или совместимость с systemd в вашем контейнере? Скорее всего нет. То же самое касается dpkg или bash.

Если ваш базовый образ gcr.io/distroless/base-debian10:

Тогда он содержит только базовый набор пакетов, включая библиотеки libc, libssl и openssl.

Для компилируемых приложений, таких как Go, которым не нужна libc, можно использовать более компактный образ:

6. Используйте проверенные базовые образы

Внимательно выбирайте базовые образы.

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

Следуйте приведенным рекомендациям при выборе базового образа:

Официальные и проверенные образы из доверенных репозиториев всегда предпочтительнее образов неизвестного происхождения.

Когда вы используете неофициальный образ проверяйте источник и Dockerfile. Создавайте свои собственные базовые образы. Нет гарантий, что образ из публичного репозитория действительно создан из указанного Dockerfile. Так же нет уверенности, что он обновляется.

Но иногда даже официальные образы могут не подойти из соображений безопасности или большого размера. В качестве примера сравните официальный образ node и bitnami/node. Последний предлагает различные версии поверх дистрибутива minideb. Образы часто обновляются с учетом последних исправлений ошибок, подписываются с помощью Docker Content Trust и проходят сканирование безопасности для отслеживания известных уязвимостей.

7. Своевременно обновляйте образы

Используйте базовые образы, которые регулярно обновляются, так же обновляйте ваши образы, основанные на них.

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

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

Придерживайтесь стабильных или long-term версий поддержки, которые быстро и часто предоставляют исправления безопасности.

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

Кроме того, периодически пересобирайте свои собственные образы используя аналогичную стратегию, чтобы получить последние пакеты из базового дистрибутива, Node, Golang, Python и т. Д. Большинство менеджеров пакетов или зависимостей, таких как npm или go mod, предлагают способы указать диапазоны версий для следите за последними обновлениями безопасности.

8. Открытые порты

Каждый открытый порт в вашем контейнере - это открытая дверь в вашу систему. Оставляйте открытыми только порты, которые действительно нужны вашим приложениям и избегайте таких портов как SSH (22).

Пожалуйста, обратите внимание, что хотя в Dockerfile присутствует команда EXPOSE, эта команда носит скорее информативный характер (не считая docker -P ). Открытие портов не позволяет автоматически подключаться к ним при запуске контейнера (если вы не выполняете команду docker run --publish-all). Вам необходимо указать публикуемые порты при запуске контейнера.

Используйте команду EXPOSE в Dockerfile только чтобы обозначить и задокументировать необходимые порты, затем используйте указанные порты в процессе запуска контейнеров.

Предотвращение утечки конфиденциальных данных

Будьте осторожны с конфиденциальными данными, при работе с контейнерами.

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

9. Учетные данные и конфиденциальность

Никогда не помещайте чувствительные данные или учетные данные в Dockerfile (через переменные окружения, аргументы или жестко заданными в команде).

Будьте очень осторожны при копировании файлов внутрь контейнера. Даже если файл удален в последующих командах Dockerfile, к нему все еще можно получить доступ на предыдущих слоях, поскольку на самом деле он не удаляется, а только «скрывается» в окончательной файловой системе. При создании образа следуйте этим рекомендациям:

Если приложение поддерживает конфигурацию с помощью переменных окружения, используйте их для установки секретов при выполнении (параметр -e в docker run) или используйте Docker secrets, Kubernetes secrets для предоставления значений в качестве переменных среды.

Используйте конфигурационные файлы и монтируйте их в docker или Kubernetes secret

Кроме того, ваши образы не должны содержать конфиденциальную информацию или параметры конфигурации, которые относятся к определенному окружению (например, dev, qa, prod и т. д.).

10. ADD, COPY

Инструкции ADD и COPY предоставляют аналогичные функции в файле Dockerfile. Однако использование COPY является предпочтительным.

Используйте COPY всегда, если вам действительно не нужны возможности ADD, например, для добавления файлов из URL-адреса или из tar-файла. Процесс копирования данных будет более предсказуемым и менее подтверженым ошибкам.

В некоторых случаях предпочтительнее использовать инструкцию RUN вместо ADD, чтобы загрузить пакет с помощью curl или wget, извлечь его, а затем удалить исходный файл за один шаг, уменьшив количество слоев.

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

11. Контекст сборки и dockerignore

Вот типичное выполнение сборки с использованием Docker с Dockerfile по умолчанию и контекстом в текущей папке:

Остерегайтесь!

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

Представьте, что у вас есть следующая команда внутри Dockerfile:

Это скопирует все внутри контекста сборки, что для «.». Например, включает сам Dockerfile.

В соответствии с Dockerfile best practice нужно создать подпапку, содержащую файлы, которые необходимо скопировать внутри контейнера, использовать ее в качестве контекста сборки и, когда это возможно, явно указывать инструкции COPY (избегайте подстановочных знаков). Например:

Кроме того, создайте файл .dockerignore, чтобы явно исключить файлы и каталоги.

Даже если вы будете особенно осторожны с инструкциями COPY, весь контекст сборки отправляется демону докера перед запуском сборки образа. Это означает, что наличие меньшего и ограниченного контекста сборки сделает ваши сборки быстрее.

Прочие рекомендации

12. Порядок слоев

Помните, что порядок в инструкциях Dockerfile очень важен.

Поскольку RUN, COPY, ADD и другие инструкции создают новый слой контейнера, группировка нескольких команд вместе уменьшит количество слоев.

Можно использовать единственную команду RUN:

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

Лучше бы сделать:

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

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

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

13. Metadata labels

Рекомендуется включать Dockerfile метки метаданных при создании образа.

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

14. Тестируйте ваши Dockerfile

Такие инструменты, как Haskell Dockerfile Linter (hadolint), могут обнаруживать ошибки в вашем Dockerfile и даже обнаружить проблемы внутри таких команд как RUN.

Рассмотрите возможность включения такого инструмента в ваши конвейеры CI.

Сканеры образов также способны обнаруживать уязвимости:


Некоторые из ошибок конфигурации, которые вы можете обнаружить - это образы, работающие с правами root, открытые порты, использование инструкции ADD, жестко запрограммированные секреты или нежелательные команды RUN.

15. Локальное сканирование изображений

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

Лучшей практикой безопасности является применение парадигмы «сдвиг влево» путем непосредственного сканирования ваших образов сразу после их создания в конвейерах CI перед отправкой в ​​реестр.

Периодически проверяйте наличие новых уязвимостей.


За пределами сборки образов

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


16. Docker port socket and TCP protection

Докер-сокет - это большая привилегированная дверь в вашу хост-систему, которая, как недавно было замечено, может использоваться для вторжений и использования вредоносного программного обеспечения. Убедитесь, что ваш /var/run/docker.sock имеет правильные разрешения, и если докер доступен через TCP (что вообще не рекомендуется), убедитесь, что он должным образом защищен.

17. Цифровая подпись образов

Использование Docker Content Trust, Docker notary, Harbour notary или аналогичные инструменты для цифровой подписи ваших образов и последующей проверки их во время выполнения - одна из лучших практик Dockerfile.

Включение проверки подписи отличается в каждой среде выполнения. Например, в докере это делается с помощью переменной окружения DOCKER_CONTENT_TRUST: экспорт DOCKER_CONTENT_TRUST = 1

18. Изменение тэгов

Теги являются непостоянной ссылкой на конкретную версию изображения в определенный момент времени и могут измениться неожиданно и в любой момент.


19. Запуск как non-root

Ранее мы говорили об использовании пользователя без полномочий root при создании контейнера. Инструкция USER установит пользователя по умолчанию для контейнера, но за оркестратором или средой выполнения (например, docker run, kubernetes и т. д.) Остается последнее слово в том, кто является пользователем запущенного контейнера.

Избегайте запуска вашей среды от имени пользователя root.

Openshift и некоторые кластеры Kubernetes по умолчанию будут применять ограничительные политики, предотвращая запуск root контейнеров. Избегайте соблазна работать с правами root, чтобы обойти проблемы с разрешениями или владением, и вместо этого устраните реальную проблему.

20. Включить проверки

При использовании Docker или Docker Swarm по возможности включайте инструкцию HEALTHCHECK в свой Dockerfile. Это критически важно для длительно работающих или постоянных служб, чтобы гарантировать их работоспособность и управлять перезапуском службы в противном случае.

Если вы запускаете образы в Kubernetes, используйте конфигурацию livenessProbe внутри определений контейнеров, поскольку инструкция Docker HEALTHCHECK не будет применяться.

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

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

В последних версиях Докер, что - то , как это возможно: docker exec <container> bash . Итак, вы просто открываете оболочку внутри контейнера. запуск bash на контейнере работает, только если bash установлен внутри контейнера Точно так же вы можете сделать: docker exec <container> ls <dir path> и docker exec <container> cat <file path> . Однако для bash добавьте -it параметры. @ ChristopherThomas, точно. Из-за этого я обнаружил, что единственный надежный способ сделать это с помощью, docker image save image_name > image.tar как указано в ответе @ Gaurav24.

ОБНОВЛЕНИЕ
Самый простой метод: использование docker exec

Docker версии 1.3 или новее поддерживает команду, exec которая ведет себя подобно nsenter . Эта команда может запустить новый процесс в уже запущенном контейнере (в контейнере должен быть запущен процесс PID 1). Вы можете запустить, /bin/bash чтобы изучить состояние контейнера:

Альтернативный метод 1
Снимок

Вы можете оценить файловую систему контейнера следующим образом:

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

Позже вы можете удалить снимок, используя (файловая система работающего контейнера не затронута!):

Альтернативный метод 2
SSH

Если вам нужен постоянный доступ, вы можете установить sshd в свой контейнер и запустить демон sshd:

Таким образом, вы можете запустить свое приложение, используя ssh (подключитесь и выполните то, что вы хотите).

ОБНОВЛЕНИЕ: Альтернативный метод 3
nsenter

Короткая версия: с помощью nsenter вы можете поместить оболочку в существующий контейнер, даже если этот контейнер не запускает SSH или какой-либо специальный демон.

Вариант 4 настолько важен, что его нужно переместить наверх и переименовать Option 1 . @JanusTroelsen Если нет оболочки, вы можете установить ее - например, в dockerfile для альпийского Linux (который действительно не имеет оболочки): RUN apk update && apk add bash (размер: по моему опыту, ограничение Docker exec заключается в том, что команда должна быть добавлена ​​в работающий контейнер или как своего рода точка входа. Следовательно, остановленный контейнер выходит за рамки этого метода. Чтобы использовать оболочку Linux для Windows, используйте docker exec -t -i mycontainer /bin/sh

ОБНОВЛЕНИЕ: ИЗУЧЕНИЕ!

Эта команда должна позволить вам исследовать работающий докер-контейнер :

Эквивалентом этого в docker-compose будет:

(web - это имя службы в этом случае, по умолчанию оно имеет tty.)

Как только вы внутри, сделайте:

или любая другая команда bash, такая как:

Эта команда должна позволить вам изучить образ докера :

однажды внутри сделайте:

или любая другая команда bash, такая как:

-it Означает интерактивный . и терминал.

Эта команда должна позволить вам проверить работающий Docker-контейнер или изображение :

docker inspect name-of-container-or-image

Вы можете сделать это и выяснить, есть ли там bash или sh там. Ищите точку входа или cmd в возвращении json.

  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents Loading

Copy raw contents

Copy raw contents

Dockerfile и коммуникация между контейнерами

В прошлой статье мы рассказали, что такое Docker и как с его помощью можно обойти Vendor–lock. В этой статье мы поговорим о Dockerfile как о правильном способе подготовки образов для Docker. Также мы рассмотрим ситуацию, когда контейнерам нужно взаимодействовать друг с другом.



В InfoboxCloud мы сделали готовый образ Ubuntu 14.04 с Docker. Не забудьте поставить галочку «Разрешить управление ядром ОС» при создании сервера, это требуется для работы Docker.

Dockerfile

Подход docker commit, описанный в предыдущей статье, не является рекомендованным для Docker. Его плюс состоит в том, что мы настраиваем контейнер практически так, как привыкли настраивать стандартный сервер.

Вместо этого подхода мы рекомендуем использовать подход Dockerfile и команду docker build. Dockerfile использует обычный DSL с инструкциями для построения образов Docker. После этого выполняется команда docker build для построения нового образа с инструкциями в Dockerfile.

Написание Dockerfile

Давайте создадим простой образ с веб-сервером с помощью Dockerfile. Для начала создадим директорию и сам Dockerfile.

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

Добавим в Dockerfile информацию по построению образа:

Dockerfile содержит набор инструкций с аргументами. Каждая инструкция пишется заглавными буквами (например FROM). Инструкции обрабатываются сверху вниз. Каждая инструкция добавляет новый слой в образ и коммитит изменения. Docker исполняет инструкции, следуя процессу:

  • Запуск контейнера из образа
  • Исполнение инструкции и внесение изменений в контейнер
  • Запуск эквивалента docker commit для записи изменений в новый слой образа
  • Запуск нового контейнера из нового образа
  • Исполнение следующей инструкции в файле и повторение шагов процесса.

Это означает, что если исполнение Dockerfile остановится по какой-то причине (например инструкция не сможет завершиться), вы сможете использовать образ до этой стадии. Это очень полезно при отладке: вы можете запустить контейнер из образа интерактивно и узнать, почему инструкция не выполнилась, используя последний созданный образ.

Первая инструкция в Dockerfile всегда должна быть FROM, указывающая, из какого образа нужно построить образ. В нашем примере мы строим образ из базового образа ubuntu версии 14:04.

Далее мы указываем инструкцию MAINTAINER, сообщающую Docker автора образа и его email. Это полезно, чтобы пользователи образа могли связаться с автором при необходимости.

Инструкция RUN исполняет команду в конкретном образе. В нашем примере с помощью ее мы обновляем APT репозитории и устанавливаем пакет с NGINX, затем создаем файл /usr/share/nginx/html/index.html.

По-умолчанию инструкция RUN исполняется внутри оболочки с использованием обертки команд /bin/sh -c. Если вы запускаете инструкцию на платформе без оболочки или просто хотите выполнить инструкцию без оболочки, вы можете указать формат исполнения:

Мы используем этот формат для указания массива, содержащего команду для исполнения и параметры команды.

Далее мы указываем инструкцию EXPOSE, которая говорит Docker, что приложение в контейнере должно использовать определенный порт в контейнере. Это не означает, что вы можете автоматически получать доступ к сервису, запущенному на порту контейнера (в нашем примере порт 80). По соображениям безопасности Docker не открывает порт автоматически, но ожидает, когда это сделает пользователь в команде docker run. Вы можете указать множество инструкций EXPOSE для указания, какие порты должны быть открыты. Также инструкция EXPOSE полезна для проброса портов между контейнерами.

Строим образ из нашего файла

, где trukhinyuri – название репозитория, где будет храниться образ, nginx – имя образа. Последний параметр — путь к папке с Dockerfile. Если вы не укажете название образа, он автоматически получит название latest. Также вы можете указать git репозиторий, где находится Dockerfile.

В данном примере мы строим образ из Dockerfile, расположенном в корневой директории Docker.

Если в корне билд контекста есть файл .dockerignore – он интерпретируется как список паттернов исключений.

Что произойдет, если инструкция не исполнится?

Давайте переименуем в Dockerfile nginx в ngin и посмотрим.


Мы можем создать контейнер из предпоследнего шага с ID образа 066b799ea548
docker run -i -t 066b799ea548 /bin/bash
и отладить исполнение.

По-умолчанию Docker кеширует каждый шаг и формируя кеш сборок. Чтобы отключить кеш, например для использования последнего apt-get update, используйте флаг --no-cache.

Использования кеша сборок для шаблонизации

Используя кеш сборок можно строить образы из Dockerfile в форме простых шаблонов. Например шаблон для обновления APT-кеша в Ubuntu:

Инструкция ENV устанавливает переменные окружения в образе. В данном случае мы указываем, когда шаблон был обновлен. Когда необходимо обновить построенный образ, просто нужно изменить дату в ENV. Docker сбросит кеш и версии пакетов в образе будут последними.

Инструкции Dockerfile

Давайте рассмотрим и другие инструкции Dockerfile. Полный список можно посмотреть тут.

CMD

Инструкция CMD указывает, какую команду необходимо запустить, когда контейнер запущен. В отличие от команды RUN указанная команда исполняется не во время построения образа, а во время запуска контейнера.

В данном случае мы запускаем bash и передаем ему параметр в виде массива. Если мы задаем команду не в виде массива — она будет исполняться в /bin/sh -c. Важно помнить, что вы можете перегрузить команду CMD, используя docker run.

ENTRYPOINT

Часто команду CMD путают с ENTRYPOINT. Разница в том, что вы не можете перегружать ENTRYPOINT при запуске контейнера.

При запуске контейнера параметры передаются команде, указанной в ENTRYPOINT.

Можно комбинировать ENTRYPOINT и CMD.

В этом случае команда в ENTRYPOINT выполнится в любом случае, а команда в CMD выполнится, если не передано другой команды при запуске контейнера. Если требуется, вы все-таки можете перегрузить команду ENTRYPOINT с помощью флага --entrypoint.

WORKDIR

С помощью WORKDIR можно установить рабочую директорию, откуда будут запускаться команды ENTRYPOINT и CMD.

Вы можете перегрузить рабочую директорию контейнера в рантайме с помощью флага -w.

USER

Специфицирует пользователя, под которым должен быть запущен образ. Мы можем указать имя пользователя или UID и группу или GID.

Вы можете перегрузить эту команду, используя глаг -u при запуске контейнера. Если пользователь не указан, используется root по-умолчанию.

VOLUME

Инструкция VOLUME добавляет тома в образ. Том — папка в одном или более контейнерах или папка хоста, проброшенная через Union File System (UFS).
Тома могут быть расшарены или повторно использованы между контейнерами. Это позволяет добавлять и изменять данные без коммита в образ.

В примере выше создается точка монтирования /opt/project для любого контейнера, созданного из образа. Таким образом вы можете указывать и несколько томов в массиве.

ADD

Инструкция ADD добавляет файлы или папки из нашего билд-окружения в образ, что полезно например при установке приложения.

Источником может быть URL, имя файла или директория.

В последнем примере архив tar.gz будет распакован в /var/www/wordpress. Если путь назначения не указан — будет использован полный путь включая директории.

COPY

Инструкция COPY отличается от ADD тем, что предназначена для копирования локальных файлов из билд-контекста и не поддерживает распаковки файлов:

ONBUILD

Инструкция ONBUILD добавляет триггеры в образы. Триггер исполняется, когда образ используется как базовый для другого образа, например, когда исходный код, нужный для образа еще не доступен, но требует для работы конкретного окружения.

Коммуникация между контейнерами

В предыдущей статье было показано, как запускать изолированные контейнеры Docker и как пробрасывать файловую систему в них. Но что, если приложениям нужно связываться друг с другом. Есть 2 способа: связь через проброс портов и линковку контейнеров.

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

Такой способ связи уже был показан ранее. Посмотрим на варианты проброса портов чуть шире.
Когда мы используем EXPOSE в Dockerfile или параметр -p номер_порта – порт контейнера привязывается к произвольному порту хоста. Посмотреть этот порт можно командой docker ps или docker port имя_контейнера номер_порта_в_контейнере. В момент создания образа мы можем не знать, какой порт будет свободен на машине в момент запуска контейнера.

Указать, на какой конкретный порт хоста мы привяжем порт контейнера можно параметром docker run -p порт_хоста: порт_контейнера
По-умолчанию порт используется на всех интерфейсах машины. Можно, например, привязать к localhost явно:

Можно привязать UDP порты, указав /udp:

Линковка контейнеров

Связь через сетевые порты — лишь один способ коммуникации. Docker предоставляет систему линковки, позволяющую связать множество контейнеров вместе и отправлять информацию о соединении от одного контейнера другому.

Для установки связи нужно использовать имена контейнеров. Как было показано ранее, вы можете дать имя контейнеру при создании с помощью флага --name.

Допустим у вас есть 2 контейнера: web и db. Чтобы создать связь, удалите контейнер web и пересоздайте с использованием команды --link name:alias.

Используя docker -ps можно увидеть связанные контейнеры.

Что на самом деле происходит при линковке? Создается контейнер, который предоставляет информацию о себе контейнеру-получателю. Это происходит двумя способами:

  • Через переменные окружения
  • Через /etc/hosts

Переменные окружения можно посмотреть, выполнив команду env:

Префикс DB_ был взят из alias контейнера.

Можно просто использовать информацию из hosts, например команда ping db (где db – alias) будет работать.

Читайте также: