Привет, я Денис, python-разработчик в Cloud.ru. Последние три года я работаю с продуктами на базе компонентов OpenStack — для этого нужны разнообразные навыки и знания способов администрирования и разработки в среде Linux. За это время я убедился — мне не хватает уже существующих способов отладки, доставки кода, подключения к prod- и dev-стендам. Поэтому решил придумать свой.
Сейчас никого не удивишь контейнерами — они есть у большинства провайдеров, многие используют их для размещения приложений. Я же хотел попробовать другое применение, и, кажется, у меня все получилось. Теперь я ежедневно работаю в контейнерах — почти как в виртуалках. В статье расскажу, зачем это делаю, поделюсь своим подходом и стеком технологий, а также покажу, как настроить такое же решение у себя.
На какой бы позиции я не работал — в поддержке, эксплуатации или разработке, я всегда стремился улучшить свои навыки и инструменты работы. А работа у меня достаточно активная: параллельно нужно следить за развертыванием, делать запросы к стенду, работать с базой данных, читать логи на виртуалках, работать со скриптами и т. д. Поэтому я хотел найти такой способ создания сред работы, который бы позволял сегментировать работу, а еще легко воспроизводился и переносился.
Работа в консоли для меня — основной способ взаимодействия с виртуалками. Часто приходится решать многоплановые задачи, где нужно держать под рукой несколько вкладок с консолью. Каждая из вкладок может быть открыта на той же виртуалке, но в другой папке, с другими переменными окружения или с другой виртуалкой. Я пробовал разные способы — утилиты tmux, screen, SSH-клиентов, Jupyter, но всего этого мне не хватало.
Почему screen, tmux, iTerm2, SecureCRT, Jupyter и MobaXterm мне не подошли
Работа разработчиков и инженеров многоплановая — нужно взаимодействовать с разными компонентами, подстраиваться под меняющиеся инструменты и способы диагностики, искать инструменты самостоятельно, если их нет. Со временем рабочее пространство становится похоже на комнату в состоянии переезда, когда много разной мебели и вещей, сваленных в кучу, ожидают своей участи.
А еще так не хочется каждый раз заново искать инструменты, заходить в папки, активировать переменные окружения… Хочется переиспользовать скрипты и применять их сразу на нескольких стендах. Поэтому я стал искать инструмент, который покрыл бы все аспекты многоплановой работы и помог поддерживать порядок.
Начнем с утилит screen и tmux. По функционалу они для меня почти не отличаются — помогают сегментировать работу через создание сессий и использовать разные переменные окружения в разных сессиях. Также помогают продолжить работу после разрыва соединения — очень нужная опция. Но оба инструмента не задержались в моем «райдере»:
я обнаружил конфликты с горячими клавишами (например, Ctrl+A для перевода в начало строки);
оказалось, довольно просто случайно выйти из окружения так, что оно безвозвратно исчезает — и это совершенно ненадежно.
SSH-клиенты iTerm2, MobaXterm, XShell, SecureCRT тоже мне не подошли. Программы имеют схожий функционал — помогают упорядочить сессии и задать стартовые команды. Но все окружения работают до первой перезагрузки программы, поэтому опять-таки не надежны. Кроме того, большинство из них не являются кроссплатформенными, а это еще один ограничивающий фактор — работать вне зависимости от условий и набора ПО уже не получится.
Было время, после продвинутых курсов по Python я начал создавать свои окружения прямо в Jupyter-блокнотах. Мне понравилась переносимость окружений, поскольку в блокноте сохраняется результат вывода команд, а также заготовки команд находятся всегда под рукой — так я выполнял и bash-скрипты, и ansible-плейбуки. Но позже я перестал использовать этот способ. При больших объемах скриптов и разнообразной работе управлять блокнотами становится сложно.
Так я понял, что подходящих моим запросам инструментов нет, поэтому пришлось создавать комплексное окружение самостоятельно. Итак, мое решение, чтобы разделить рабочее окружение — использовать контейнеры для всего в работе. С контейнеризацией я познакомился давно, еще до начала активной работы в Linux. Уже тогда я понимал, как работает проброс SSH-портов, но только в рамках администрирования, а не в качестве рабочего окружения. Теперь я работаю в контейнерах ежедневно.
Есть мнение, что не стоит использовать контейнеры как виртуалки, ведь это противоречит принципу один контейнер — один сервис. Но, как и во многих сферах, идти не по стандарту можно, когда знаешь зачем. На мой взгляд контейнеры — это путь к расширению и комфорту. Как будто все вещи из той самой комнатки перевезли в коттедж и сразу появился простор для работы. Теперь есть много разных комнат, в каждой из которых только нужные и любимые предметы — все лежит по местам и всегда готово к использованию.
Статья могла бы закончиться на этой воодушевляющей ноте, если бы не одно но — а как начать использовать это все в работе? Ведь контейнеры ориентированы на запуск сервисов, а не на то, чтобы запускать их как виртуалки.
На самом деле все не так сложно и страшно. Нужно только преодолеть несколько технических ограничений.
Как создать такие же контейнеры, как у меня
Расскажу про свой стек работы с контейнерами:
Docker и кастомные сети с доменными именами. Можно было бы использовать Podman, но у него хуже совместимость с Portainer.
Squid в контейнере — благодаря нему можно заходить на сервисы в контейнерах через веб-браузер и по доменному имени.
SSH-агент в контейнере, проброшенный с помощью команд и переменных окружения.
Portainer — позволяет легко редактировать биндинги, волюмы, переменные окружения, работать в консоли контейнеров.
FoxyProxy для работы через веб-браузер — помогает в настройке URL-паттернов через прокси-сервер Squid.
Все скрипты, код и инструменты я разместил в домашней директории моего пользователя виртуалки. Свою домашнюю директорию я прокидываю почти в каждый контейнер, который запускаю, но не полностью — только те файлы или папки, которые нужны для конкретной среды.
А вот такие среды я использую, и каждую — с конкретной целью:
работа с CLI OpenStack — под каждый стенд я создаю отдельный контейнер. Контейнеры отличаются переменными окружения и набором Python-пакетов;
работа с git. В этой среде у меня настроены git-hooks для работы с репозиториями и алгоритмом управляемого добавления пайплайна: в мой локальный репозиторий — для тестов и в репозиторий компании — для работы с итоговым решением.
разработка в code-server (Microsoft Visual Studio Code) — таких сред я использую несколько, чтобы параллельно отлаживать распределенные сервисы OpenStack;
доставка кода на мой стенд разработки. Поскольку в Python не обязательно компилировать код, удобно подкинуть его на виртуалки, склонировав плейбуком из репозитория, а затем подключить в контейнеры сервисов через волюмы;
работа с базой данных — иногда я шучу, что могу администрировать весь OpenStack только лишь запросами в БД;
работа с RPC-вызовами — мое недавнее увлечение. Я запускаю контейнер с кодом OpenStack и минимальным конфигом, консольный Python 3 и отправляю RPC-вызовы напрямую в сервисы, минуя API;
развертывание SSH-ключей — чтобы в любой момент можно было открыть доступ на стенд себе или кому-то другому;
Gitea и Gitea Act Runner — для своих пайплайнов и тестирования;
httpd — для сбора PyPi-артефактов;
httpd — для сбора отчетов tox coverage.
Расскажу подробнее про основные компоненты моего решения — как они устроены и как их воспроизвести.
Docker-сети с доменным именем
Удобно, что в Docker-сетях есть резолвер на хосте 127.0.0.11. Контейнеры могут обращаться друг к другу по имени или полному доменному имени, которое содержит имя сети. По умолчанию имя сети — docker, поэтому к контейнерам можно обращаться по FQDN. Например, у контейнера «container1» в сети docker будет имя «container1.docker».
Поскольку я планировал использовать веб-браузер для доступа к развернутым средам, то выбрал реалистичное доменное имя. Для этого вот так просто я создал сеть tbox.tech:
docker network create tbox.tech
Благодаря этому у всех контейнеров теперь корректные (с точки зрения корневых доменов) имена.
Squid для резолвинга и веб-доступа
Для доступа к контейнерам через веб-браузер использовал прямой прокси. Раньше пользовался SSH-туннелированием, но вариант с прокси позволяет лучше управлять политикой доступа — не нужно открывать SSH-туннель ко всей виртуалке. Прямой HTTPS-прокси я защитил самоподписанным TLS-сертификатом, который выпустил заранее. Поскольку у моей виртуалки нет доменного имени, я выпустил TLS-сертификат на IP-адрес:
squid/openssl.conf
[ req ]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[ req_distinguished_name ]
CN = 192.168.109.136
[ v3_req ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = 192.168.109.136
Команда для выпуска самоподписанного TLS-сертификата для Squid:
openssl req -x509 -newkey rsa:4096 -keyout squid/squid.key -out squid/squid.crt -sha256 -days 3650 -nodes -config squid/openssl.conf
Для настройки Squid использовал минимальный конфиг. Указал в нем HTTPS-порт, TLS-сертификат, резолвер Docker, разрешенные порты целевых подключений и сети-источники, с которых я захожу на виртуалку:
squid/squid.conf
https_port 443 tls-cert=/etc/squid/squid.crt tls-key=/etc/squid/squid.key tls-cafile=/etc/squid/squid.crt
dns_nameservers 127.0.0.11
acl safe_ports port 80
acl safe_ports port 443
acl arm src 172.16.1.0/24
acl arm src 172.16.2.0/24
http_access allow arm
http_access deny !safe_ports
Для запуска в такой конфигурации нужен контейнер Squid с библиотеками OpenSSL. Я не нашел готовый контейнер, поэтому написал Dockerfile и собрал свой образ:
squid/Dockerfile
FROM ubuntu/squid:6.6-24.04_edge
RUN apt update -y
RUN apt install squid-openssl -y
Сборка образа:
docker build -t ubuntu/squid:6.6-24.04_edge-openssl .
После этого я запускаю контейнер командой, указывая путь до конфигурации squid, которая расположена в моем домашнем каталоге, открываю порт 443, а также указываю созданную сеть tbox.tech:
docker run -d --restart=unless-stopped --name squid -v ./squid/squid.conf:/etc/squid/squid.conf -p 443:443 --network tbox.tech ubuntu/squid:6.6-24.04_edge-openssl
SSH-агент в контейнере
Если подключаться по SSH со включенным форвардингом SSH-агента, то в сессии пользователя будет переменная SSH_AUTH_SOCK. Обычно она указывает на сокет в директории /tmp и меняется при каждом переподключении. Чтобы контейнеры получили доступ к этому агенту, можно назначить постоянный путь. Для этого есть два способа — оба с socat.
Первый способ — прокинуть на хосте UNIX-сокет с агентом к TCP-сокету, а в контейнере прокинуть этот TCP-сокет к новому UNIX-сокету, который имеет постоянный путь.
На хосте:
socat TCP-LISTEN:2222,bind=192.168.254.1,fork UNIX-CLIENT:${SSH_AUTH_SOCK}
В контейнере:
socat UNIX-LISTEN:/tmp/sshagent/myssh.sock,unlink-early,mode=777,fork TCP:192.168.254.1:2222
Важно, чтобы IP-адрес был доступен из контейнера. Поэтому лучше выбрать один из Docker-интерфейсов или создать отдельный dummy-интерфейс. В примере я выбрал адрес 192.168.254.1 dummy-интерфейса виртуалки. Недостаток способа в том, что внутри контейнера приходится выполнять socat.
Второй способ — прокинуть на хосте UNIX-сокет к другому UNIX-сокету:
socat UNIX-LISTEN:/tmp/sshagent/myssh.sock,unlink-early,fork UNIX-CLIENT:${SSH_AUTH_SOCK}
Этот вариант понравился мне больше — в контейнер прокидывается не сам сокет, а папка, в которой он находится. При переподключении socat будет удалять старый сокет, поэтому контейнер должен отслеживать изменения файлов внутри папки /tmp/sshagent.
Теперь в контейнере используем переменные окружения для подключения по SSH с использованием текущей сессии пользователя:
Переменная | Значение |
---|---|
SSH_AUTH_SOCK | /tmp/sshagent/myssh.sock |
Кстати, после этих шагов стало проще запустить SSH-сервер в контейнере: теперь можно добавить строку запуска в стартовую команду, прокинуть authorized_keys и заходить в контейнер как на виртуалку.
Portainer для контейнеров
Веб-интерфейс Portainer удобно использовать для редактирования переменных окружения, переподключения биндингов и волюмов, а еще для пересоздания контейнеров и работы в консоли. Благодаря этому удобнее работать с контейнерами.
В Portainer по умолчанию нестандартные веб-порты. Чтобы использовать 80 и 443, я запускаю контейнер с кастомными командами:
docker run -d --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v ./portainer:/data --network tbox.tech portainer/portainer-ce:2.20.3 --bind 0.0.0.0:80 --bind-https 0.0.0.0:443
Вот так в итоге выглядит список моих контейнеров — и это только небольшая их часть. На скриншоте контейнеры для развертывания и работы со стендами, для работы с кодом, добавления пользователей и т. д. Работаю в них прямо через веб-браузер, открывая вкладки с консолью через соответствующую кнопку:
Еще примеры
При создании контейнеров для рабочих сред обычно я прописываю такие параметры:
Параметр | Значение |
---|---|
Working Directory | Директория, в которую я хочу попадать, заходя в контейнер |
User | Например, 1038:1038 — идентификатор моего пользователя и группы, что позволяет использовать одинаковые права на хосте и внутри контейнера |
Console | Interactive & TTY |
Volumes | /etc/shadow:/etc/shadow:ro |
Среди волюмов я здесь как раз указываю только те, которые мне нужны. Получается, вид файловой системы контейнера повторяет вид файловой системы моей виртуалки, но скомпонован под конкретную задачу.
FoxyProxy для работы через веб-браузер
Веб-доступ к контейнерам, в которых развернуты веб-приложения Visual Studio Code или Gitea, обеспечивает прямой HTTPS-прокси Squid, о настройке которого рассказывал выше. Для доступа в веб-браузер через прокси я использую плагин FoxyProxy. Добавляю прокси в настройках плагина — указываю его IP-адрес и порт. В настройках прокси задаю всего два URL-паттерна — они будут вести к URL с доменом tbox.tech по протоколам HTTP/HTTPS и WebSocket/WebSocketSecure через мой прямой прокси. После заполнения паттернов в иконке плагина станет доступен режим «Прокси из шаблона»:
Действие | Тип прокси | Шаблон |
---|---|---|
Включить | Reg Exp |
|
Включить | Reg Exp |
|
Выводы или продолжение следует…
Я поделился приемами, которые использую в работе ежедневно. Для меня этот подход похож на ящик с инструментами, который можно взять с собой в любой момент и в любое место, использовать долгое время и для разного типа задач.
Как новый подход повлиял на мою работу:
Решение помогло по максимуму раскрыть возможности работы с OpenStack.
Мне удалось снизить ежедневную когнитивную нагрузку — теперь не нужно держать в голове абсолютное расположение всех файлов и сред, достаточно запомнить относительные пути в каждой из них.
Я преодолел ограничение на единственную среду разработки.
Теперь можно одновременно задействовать сразу несколько навыков работы — с SSH, прокси и TLS-сертификатами.
Что я пока не продумал в получившемся рабочем окружении, так это переносимость: сейчас не очевидно, как забэкапить и перенести развернутые в Portainer контейнеры с переменными окружения. Но я и раньше переносил и бэкапил у своей виртуалки весь диск целиком — к файловому бэкапу я все еще не готов. Так что, на мой взгляд, это единственный недостаток моего решения. А еще хочется попробовать Kubernetes — в нем проще управлять конфигами контейнеров и Portainer с ним совместим.
В следующих статьях я расскажу о том, как мне удалось настроить параллельную отладку кода распределенных сервисов, запущенных в контейнерах. И для чего я написал git-hook скрипт с правилами отправки моего пайплайна в несколько git-репозиториев.
А пока делитесь в комментариях, какие любимые инструменты у вас? Буду рад узнать про ваши лайфхаки и решения. И приходите на конференцию GoCloud Tech, которая пройдет 24 октября онлайн и офлайн в Москве. Сможете послушать доклады про внутрянку облачных решений и в неформальной обстановке обсудить технологические тренды.
Что еще почитать в блоге: