Написание кода в docker окружении

В компании, где я работаю — большинство сервисов запускаются и работают в docker-контейнерах.


В связи с этим, у моих коллег-новичков-в-докере часто возникает вопрос —, а как писать код и запускать его в этом чёртовом контейнере???


enilotbenmupvxcxaxjdpa4ginc.png


Для человека, написавшего около сотни 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


xitett1tqt_if8ekmsyo5awoszy.png


Из сценария nomad


vujldpo2jiwtam1d7i0teagot1i.png


Запускаем контейнер с переменными окружения


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 окружении, то они могут использовать такие переменные окружения, которые доступны только контейнеру, запущенному в этом облаке и не будут доступны контейнеру, запущенному на компьютере разработчика.


vujldpo2jiwtam1d7i0teagot1i.png


В указанном примере используются «облачные» адреса в переменных CONSUL_HTTP_ADDR и VAULT_ADDR. В таких случаях вам нужно использовать внешние адреса данных сервисов.


Повторные запуски


Писать каждый раз полностью команду docker run — излишне. Всю команду старта с переменными удобно сохранить с sh файл. Который потом достаточно просто запускать.


Переиспользование переменных окружения


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


docker run --env-file=~/my_docker_env


Запуск без sudo


В локальной разработке запускать контейнеры с sudo — утомительно. Для исправления — добавляем своего пользователя в группу docker. После этого, вместо


sudo docker run ....


можно писать просто


docker run

© Habrahabr.ru