Написание кода в docker окружении
В компании, где я работаю — большинство сервисов запускаются и работают в docker-контейнерах.
В связи с этим, у моих коллег-новичков-в-докере часто возникает вопрос —, а как писать код и запускать его в этом чёртовом контейнере???
Для человека, написавшего около сотни docker-образов и запускающего их несколько раз в день — такой вопрос уже не стоит, но когда я разбирался с докером в давние времена — мысль «Как же писать код в докере? Это же сверхнеудобно!» долго была актуальной.
В статье я опишу свои практики работы с образами docker, которые позволяют писать код «как у себя в home», и даже лучше.
Итак, что такое готовый docker-образ?
Это слепок готового сервиса, который настраивается небольшим числом переменных окружения и готов к работе сразу после старта. С docker-образом не требуется устанавливать зависимости приложения и библиотеки разработчика себе локально в систему, замусоривая её.
Запуск готового образа
Для начала разберём, как запускать образ. Предполагается, что название образа нам известно.
- название может представлять собой имя образа на hub.docker.com: kaktuss/clickhouse-udp-proxy;
- название может содержать в себе имя приватного docker registry (репозитория docker образов вашей компании): my-private-registry.com/kaktuss/clickhouse-udp-proxy;
- в названии может содержаться версия образа: my-private-registry.com/kaktuss/clickhouse-udp-proxy:0.1.
И это всё — имя образа.
В простейшем случае образ запускается так:
docker run --rm -it kaktuss/clickhouse-udp-proxy
Часто образ нужно сконфигурировать переменными окружения. Откуда их брать?
- нужные значения переменных окружения описаны на docker hub — если это публичный крупный поддерживаемый образ с docker hub;
- в сценариях nomad, docker swarm, kubernetes, приватной документации — если это приватный образ вашей компании;
- иногда, переменные нигде не описаны и о нужных значениях требуется догадываться по их названиям и просмотру docker-образа.
Примеры подсмотренных переменных окружения:
С docker hub
Из сценария nomad
Запускаем контейнер с переменными окружения
docker run --rm -it -e CLICKHOUSE_ADDR=127.0.0.1:9000 kaktuss/clickhouse-udp-proxy
Если переменных несколько
docker run --rm -it -e CONSUL_HTTP_ADDR="consul.query.consul:8500" -e VAULT_ADDR="http://vault.query.consul:8200/" -e DC_NAME="deac" -e SYS_NODE="b1" ...
В итоге, мы получили работающий и сконфигурированный сервис.
Попадаем внутрь контейнера
Для упрощения программирования «внутри» контейнера — нам нужно в него попасть. При описанном выше запуске — запускается сам сервис, но мы сами не попадаем «внутрь».
Для попадания «внутрь» — нужно переопределить команду старта образа.
Это делается указанием имени shell-оболочки после имени образа
docker run --rm -it -e CLICKHOUSE_ADDR=127.0.0.1:9000 kaktuss/clickhouse-udp-proxy ash
Shell-оболочка зависит от дистрибутива, на котором построен образ.
- это может быть ash, как в примере выше;
- bash;
- или даже sh, в самом простом случае.
Попробуйте один из вариантов и не ошибётесь.
После подобного запуска мы оказываемся в консоли внутри контейнера.
Инициализация сервиса
После того, как мы попали в консоль внутри контейнера — мы попали в «голый» образ. Часто образы, после запуска, проводят первичную инициализацию (формируют файлы настроек и т.д.) и после этого запускают сам сервис.
Инициализация делается через команду старта, которую мы выше заменили на shell-оболочку. Поэтому, инициализацию нужно запустить вручную. Для этого, открываем Dockerfile и смотрим содержимое инструкции CMD.
CMD ["/usr/local/bin/entrypoint.sh"]
И именно его и запускаем.
/ # /usr/local/bin/entrypoint.sh
или короче
/ # entrypoint.sh
Сервис инициализировался и запустился, теперь мы можем нажать Ctrl+C и снова попасть в консоль, имея контейнер, готовый к повторному запуску сервиса.
Написание кода внутри контейнера
Когда сервис запускается внутри контейнера — он использует те скрипты/бинарные файлы, которые уже находятся внутри. Как нам их редактировать?
Элементарно. Нужно редактировать их извне, в любимом редакторе, в своей домашней папке, а потом просто скопировать в контейнер и запустить.
Даём доступ контейнеру к своей домашней папке используя опцию
-v ~/:/d
docker run --rm -it -e CLICKHOUSE_ADDR=127.0.0.1:9000 -v ~/:/d kaktuss/clickhouse-udp-proxy ash
После запуска данной команды мы будем находиться в консоли контейнера, сможем неограниченно редактировать свой код извне и копировать его в контейнер. Домашняя папка будет доступна в контейнере в папке /d.
/ # cp /d/my-repo/script.pl /usr/local/bin/script.pl
После копирования обновленного скрипта или бинарного файла — запускаем сервис (способом, описанным в разделе инициализации) и получаем сервис с нашими правками, работающий в исходном окружении.
В зависимости от ваших нужд — скрипты/файлы можно не копировать в контейнер, а запускать их сразу из /d/my-repo.
Граничные случаи и лайфхаки
ENTRYPOINT
Некоторые образы (довольно редко) используют команду старта в виде ENTRYPOINT. Что это такое — можно посмотреть в Dockerfile reference. Нам же нужно только помнить, что перезапись команды старта для таких образов выглядит иначе
docker run --rm -it -e CLICKHOUSE_ADDR=127.0.0.1:9000 -v ~/:/d --entrypoint ash kaktuss/clickhouse-udp-proxy
Точка старта переопределяется опцией --entrypoint.
«Облачные» окружения
Если сервисы работают в Consul, docker swarm, kubernetes окружении, то они могут использовать такие переменные окружения, которые доступны только контейнеру, запущенному в этом облаке и не будут доступны контейнеру, запущенному на компьютере разработчика.
В указанном примере используются «облачные» адреса в переменных CONSUL_HTTP_ADDR и VAULT_ADDR. В таких случаях вам нужно использовать внешние адреса данных сервисов.
Повторные запуски
Писать каждый раз полностью команду docker run — излишне. Всю команду старта с переменными удобно сохранить с sh файл. Который потом достаточно просто запускать.
Переиспользование переменных окружения
Если часть переменных окружения требуется для многих сервисов, то эти переменные можно вынести в отдельный файл и передавать контейнеру специальной опцией
docker run --env-file=~/my_docker_env
Запуск без sudo
В локальной разработке запускать контейнеры с sudo — утомительно. Для исправления — добавляем своего пользователя в группу docker. После этого, вместо
sudo docker run ....
можно писать просто
docker run