Как стать автором
Обновить
105.94
Cloud.ru
Провайдер облачных сервисов и AI-технологий

Зачем я использую контейнеры как виртуалки: опыт python-разработчика

Время на прочтение12 мин
Количество просмотров8.5K

Привет, я Денис, 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 тоже мне не подошли. Программы имеют схожий функционал — помогают упорядочить сессии и задать стартовые команды. Но все окружения работают до первой перезагрузки программы, поэтому опять-таки не надежны. Кроме того, большинство из них не являются кроссплатформенными, а это еще один ограничивающий фактор — работать вне зависимости от условий и набора ПО уже не получится. Еще недавно попробовал кросс-платформенный SSH-менеджер PortX, но в нем нет возможности задать команду при входе на хост, а значит, сразу попасть в готовое окружение не получится.

Было время, после продвинутых курсов по Python я начал создавать свои окружения прямо в Jupyter-блокнотах — это было похоже на работу в сервисе Google Colab. Мне понравилась переносимость окружений, поскольку в блокноте сохраняется результат вывода команд, а также заготовки команд находятся всегда под рукой. Там я выполнял и bash-скрипты, и ansible-плейбуки, но позже я перестал использовать этот способ — при больших объемах скриптов и разнообразной работе управлять блокнотами становится сложно. Так я понял, что подходящих моим запросам инструментов нет, поэтому пришлось создавать комплексное окружение самостоятельно.

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

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

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

Неловко оказаться в ситуации, когда нет подходящего контейнера. Исправим это.
Неловко оказаться в ситуации, когда нет подходящего контейнера. Исправим это.

На самом деле все не так сложно и страшно. Нужно только преодолеть несколько технических ограничений.

Как создать такие же контейнеры, как у меня

Расскажу про свой стек работы с контейнерами:

  1. Docker и кастомные сети с доменными именами. Можно было бы использовать Podman, но у него хуже совместимость с Portainer.

  2. Squid в контейнере — благодаря нему можно заходить на сервисы в контейнерах через веб-браузер и по доменному имени.

  3. SSH-агент в контейнере, проброшенный с помощью команд и переменных окружения.

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

  5. FoxyProxy для работы через веб-браузер — помогает в настройке URL-паттернов через прокси-сервер Squid.

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

А вот такие среды я использую, и каждую — с конкретной целью:

  • работа с командной строкой OpenStack — под каждый стенд я создаю отдельный контейнер. Контейнеры отличаются переменными окружения и набором Python-пакетов;

  • работа с Git — в этой среде я держу готовые команды для отправки кода в репозиторий;

  • разработка в IDE code-server (Microsoft Visual Studio Code) — таких сред я использую несколько, чтобы параллельно отлаживать распределенные сервисы OpenStack;

  • работа с базой данных — иногда я шучу, что могу администрировать весь OpenStack только лишь запросами в БД;

  • работа с RPC-вызовами — я запускаю контейнер с кодом OpenStack и минимальным конфигом, консольный Python и отправляю RPC-вызовы напрямую в сервисы, минуя API;

Расскажу подробнее про основные компоненты моего решения — как они устроены и как их воспроизвести.

Docker-сети с доменным именем

Удобно, что в Docker-сетях есть DNS-сервер на хосте 127.0.0.11. Контейнеры могут обращаться друг к другу по имени или полному доменному имени, которое содержит имя сети. По умолчанию имя сети — docker, поэтому к контейнерам можно обращаться по FQDN. Например, у контейнера «container1» в сети docker будет имя «container1.docker».

Поскольку я планировал использовать веб-браузер для доступа к развернутым средам, то выбрал реалистичное доменное имя. Я называю сети по имени стенда, в котором работаю. Для этого вот так просто я создаю сеть az103.ru:

docker network create az103.ru

Благодаря этому у всех контейнеров теперь корректные (с точки зрения корневых доменов) имена.

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

Сборка образа:

cd squid
sudo docker build -t ubuntu/squid:6.6-24.04_edge-openssl .

После этого я запускаю контейнер командой, указывая путь до конфигурации squid, которая расположена в моем домашнем каталоге, открываю порт 443, а также указываю созданную сеть az103.ru:

sudo docker run -d --restart=unless-stopped --name squid -v ./squid:/etc/squid/ -p 443:443 --network az103.ru ubuntu/squid:6.6-24.04_edge-openssl

SSH-агент в контейнере

Часто в работе нужно подключиться по SSH к какому-то хосту используя свой SSH ключ. Этот ключ предоставляет SSH-агент, но когда запускаю контейнер, то доступа к агенту у него нет. Когда пользователь подключается к хосту с включенным перенаправлением SSH-агента опцией ForwardAgent=yes в сессии пользователя появляется переменная SSH_AUTH_SOCK. Она указывает на путь UNIX-сокета SSH-агента обычно в папке /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}

Этот вариант понравился мне больше — в таком случае в контейнер я могу прокинуть папку с UNIX-сокетом, в которой он находится. При новом подключении команда socat будет удалять старый сокет, но контейнер увидит изменения файлов внутри прокинутой папки /tmp/sshagent.

При запуске контейнера нужно будет добавить переменную окружения для подключения по SSH с использованием постоянного пути UNIX-сокета:

Переменная

Значение

SSH_AUTH_SOCK

/tmp/sshagent/myssh.sock

Если в дополнение к предыдущим шагам в контейнер прокинуть файл authorized_keys и запустить SSH-сервер командой sshd, то и на сам контейнер можно будет зайти по его IP-адресу как на виртуалку.

Portainer для контейнеров

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

В Portainer по умолчанию нестандартные веб-порты. Чтобы использовать 80 и 443, я запускаю контейнер с кастомными командами:

mkdir portainer
sudo docker run -d --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v ./portainer:/data --network az103.ru portainer/portainer-ce:2.20.3 --bind 0.0.0.0:80 --bind-https 0.0.0.0:443

Вот так в итоге выглядит список моих контейнеров — и это только небольшая их часть. На скриншоте контейнеры для развертывания и работы со стендами, для работы с кодом, добавления пользователей и другие. Работаю в них прямо через веб-браузер, открывая вкладки с консолью через соответствующую кнопку:

Так выглядят контейнеры, которые я ежедневно использую в работе
Так выглядят контейнеры, которые я ежедневно использую в работе
А так я работаю в консоли контейнеров — прямо через веб-браузер
А так я работаю в консоли контейнеров — прямо через веб-браузер
Еще примеры
Запуск плейбука с клонированием репозитория
Запуск плейбука с клонированием репозитория
Работа с базой данных
Работа с базой данных
Работа с git
Работа с git

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

Параметр

Значение

Working Directory

Папка, в которую я хочу попадать, заходя в контейнер

User

Например, 1038:1038 — идентификатор моего пользователя и группы, что позволяет использовать одинаковые права на хосте и внутри контейнера

Console

Interactive & TTY

Volumes

/etc/shadow:/etc/shadow:ro
/etc/group:/etc/group:ro
/etc/passwd:/etc/passwd:ro
/etc/sudoers:/etc/sudoers:ro
/etc/sudoers.d:/etc/sudoers.d:ro
/etc/localtime:/etc/localtime:ro
/tmp/sshagent:/tmp/sshagent:ro
/etc/hosts:/etc/hosts:ro
/home/myname/.bashrc:/home/myname/.bashrc:ro
/home/myname/folder1:/home/myname/folder1
/home/myname/code:/home/myname/code

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

Visual Studio Code в контейнере

Один из инструментов, который часто в работе — контейнер code-server — он позволяет использовать полноценные среды разработки (IDE) в веб-браузере. Я задействую несколько таких настроенных сред, благодаря чему легко переключаться между задачами. Контейнер собрал с использованием официального скрипта установки и в качестве примера взял базовый образ ubuntu:jammy.

/etc/code/Dockerfile

FROM ubuntu:jammy
RUN curl -fsSL https://code-server.dev/install.sh | sh || true
CMD bash

Собранный образ контейнера удобно запустить в веб-интерфейсе Portainer и задать нужные параметры, но можно это сделать и другим способом. Последнее время я часто работаю прямо на самих виртуалках стенда разработки, поэтому для отладки кода удобнее запускать code-server командой. А еще на стендах я использую podman вместо docker, но запускаются они одинаково. Для запуска контейнера сделал удобную заготовку (слегка отредактировал ее, чтобы привести в качестве примера):

args=(
  -it -d
  --ip 10.89.0.11
  --name nova-compute-dev
  --hostname nova-compute-dev.az103.ru
  --network az103.ru
  -e GEVENT_SUPPORT=1
  -e SSH_AUTH_SOCK=/ssh.sock
  -e TOX_CONSTRAINTS_FILE=https://mycompany.ru/artifactory/openstack/unstable/2024.1/requirements/upper-constraints.txt
  -e TWINE_REPOSITORY_URL=https://mycompany.ru/artifactory/api/pypi/openstack-unstable
  -e PIP_CONSTRAINT=https://mycompany.ru/artifactory/openstack/unstable/2024.1/requirements/upper-constraints.txt
  -e PIP_INDEX_URL=https://mycompany.ru/artifactory/api/pypi/openstack-unstable/simple
  -e PIP_PRE="1"
  -v /root/.ssh/config:/root/.ssh/config
  -v /tmp/sshagent:/tmp/sshagent
  -v /etc/codebase/mycompany.ru/myname/toolbox/code-server/extensions:/root/.local/share/code-server/extensions
  -v /etc/codebase/mycompany.ru/myname/toolbox/code-server/User:/root/.local/share/code-server/User
  -v /etc/codebase/:/codebase
  -v /etc/resolv.conf:/etc/resolv.conf:ro
  -v /etc/kolla/:/etc/kolla/
  -v libvirtd:/var/lib/libvirt
  -v nova_compute:/var/lib/nova/
  -v /lib/modules:/lib/modules
  -v /run/libvirt:/run/libvirt
  f1f4be05cce
  code-server --bind-addr 0.0.0.0:80 --auth none
)
sudo podman run "${args[@]}"

Почти такую же я использую в работе очень часто. Среди подключаемых папок перечисляю директорию extensions с расширениями и директорию User с настройками темы, форматирования Black и другими. Названия контейнерам даю по имени задачи, разрабатываемого сервиса или универсальные — code1.az103.ru, code2.az103.ru и т. д.

Делаю отладку юнит-тестов Nova в контейнере code2.az103.ru
Делаю отладку юнит-тестов Nova в контейнере code2.az103.ru

FoxyProxy для работы через веб-браузер

Через веб-браузер я работаю с контейнерами в Portainer, а также с кодом — в Visual Studio Code. Доступ к ссылкам обеспечивает прямой HTTPS-прокси Squid, о настройке которого рассказывал выше. Чтобы ссылки открывались, делаю небольшие настройки для веб-браузера. Я захожу на адрес прокси, доверяю подключению и экспортирую самоподписанный TLS-сертификат. Этот сертификат нужно добавить в доверенные в системе, где открыт веб-браузер (macOS, Windows, Linux).

Затем настраиваю плагин FoxyProxy, чтобы ссылки portainer.az103.ru, code1.az103.ru и code2.az103.ru в моем примере, а также другие открывались через прокси. В настройках плагина на вкладке «Прокси» я выбираю «Добавить», указываю название, IP адрес и порт прокси 443, а еще всего два URL-паттерна — они будут вести к ссылкам с доменом az103.ru по протоколам HTTP/HTTPS и WebSocket/WebSocketSecure через прямой прокси. После заполнения остальных URL-паттернов в иконке плагина станет доступен выбор режима «Прокси из шаблона»:

Действие

Тип прокси

Шаблон

Включить

Reg Exp

https?:\/\/.*((az103\.ru)).*

Включить

Reg Exp

wss?:\/\/.*((az103\.ru)).*

Пример настроек
Добавление прокси в плагин FoxyProxy
Добавление прокси в плагин FoxyProxy

Выводы или продолжение следует…

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

Как новый подход повлиял на мою работу:

  • Решение помогло по максимуму раскрыть возможности работы с OpenStack.

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

  • Я преодолел ограничение на единственную среду разработки.

Что я пока не продумал в получившемся рабочем окружении, так это бэкапы: сейчас не очевидно, как забэкапить параметры запуска контейнеров с переменными окружения. Обычно я переносил и бэкапил диск виртуалки целиком — к файловому бэкапу я пока что не готов. В моих планах попробовать Kubernetes. Это еще один инструмент работы с контейнерами, который имеет гораздо больше возможностей. Например, я хотел бы хранить постоянные данные в отдельном репозитории, а в Kubernetes как раз есть драйверы для подключения удаленных папок. А еще параметры запуска контейнеров в нем описываются декларативным способом в YAML-файлах, поэтому забэкапить их будет легко.

Делитесь в комментариях, какими способами и инструментами пользуетесь для разделения своей работы? Буду рад узнать про ваши лайфхаки и решения.

Теги:
Хабы:
Всего голосов 9: ↑8 и ↓1+12
Комментарии11

Публикации

Информация

Сайт
cloud.ru
Дата регистрации
Дата основания
2019
Численность
1 001–5 000 человек
Местоположение
Россия
Представитель
Елизавета