other
July 4

Dev containers: вскрытие

В этой статье хотелось бы рассказать про разработку внутри docker-контейнера: зачем это нужно, что предоставляет для этого замечательная IDE VSCode, и как это работает.

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

Для тех, кто ещё (внезапно!) не знаком с Docker, очень рекомендую сразу пойти и ознакомиться с этой технологией (https://en.wikipedia.org/wiki/Docker_(software)).

Для чего же это нужно?

Давайте рассмотрим существующие проблемы, которые, как правило, возникают при работе над проектом.

Проблема 1

Вы - новый разработчик в компании, Вам дали свежеустановленный ПК, дали доступ в репозиторий проекта, с которым придется иметь дело. Предположим, что этот проект на Rails. Для запуска проекта необходимо:

  • установить системные dev пакеты
  • установить rvm
  • установить правильную версию ruby, bundle
  • установить nvm
  • установить node
  • установить и настроить БД, например, Postgres
  • возможно, что-то ещё, что требует проект (Redis, Consul и т.д.)

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

  • у вас Windows: это сразу +100 к сложности и боли
  • у вас слишком новая версия операционки, а в проекте используются устаревшие библиотеки, и при установке ruby пакетов, либо scss (когда же его наконец перепишут на JS) могут возникнуть ошибки компиляции
  • возможно, необходимы какие-то ещё дополнительные шаги, которые забыли описать в README, например, это может быть установка корневых сертификатов, прописывание каких-то переменных среды

В целом видно, что процесс первого запуска проекта непростой. Скорее всего, его придется повторить, когда захочется поработать с домашнего компьютера, а там как раз больше вероятность встретить Windows =)

Проблема 2

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

Проблема 3

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

Проблема 4

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

Решение

Итак, эти все проблемы решает контейнеризированное окружение разработки. Мы получаем:

  • повторяемость среды разработки
  • быстрое развертывание
  • независимость от хостовой системы, её типа и версии
  • возможность полностью очистить свою систему от ненужных рудиментов старых проектов

Конечно же есть и недостатки:

  • Повышается сложность понимания что происходит. Надо не забывать, что мы находимся внутри контейнера, и некоторые внешние ресурсы могут быть недоступны, такие как файлы, либо сторонние сервисы, запущенные на localhost.
  • Требуется больше системных ресурсов. В основном, это дополнительное пространство на диске под контейнеры и образы docker, а в случае с MacOS ещё и дополнительный расход оперативной памяти под виртуальную машину с докером.
  • Необходимо учитывать, что ID пользователя внутри контейнера может быть другим, и может возникнуть проблема с доступом к файлам проекта. Забегая вперед, скажу, что эта проблема вполне решаемая.

Существует множество вариантов реализовать такое контейнеризированное окружение:

  • написать Dockerfile с нужной версией ОС, установкой всего необходимого, зайти в контейнер и разрабатывать в VIM.
  • можно смонтировать исходники в запущенный контейнер и разрабатывать в своей IDE, а запускать в контейнере. Правда, у IDE скорее всего будут проблемы с дополнением кода.
  • можно установить IDE внутрь контейнера и пробросить X-ы с хостовой машины (работает только с Linux).
  • можно поставить sshd в контейнер и заходить внутрь по ssh из VSCode, IDEA или любой другой IDE, которая поддерживает удалённую разработку.
  • либо воспользоваться специализированным решением, которое нам предлагает VSCode. Предлагаю в этой статье подробно рассмотреть это решение как самое, на мой взгляд, продвинутое из того, что я видел.

VS Code Dev container

Полная документация находится здесь: https://code.visualstudio.com/docs/remote/containers

Вкратце, это работает следующим образом:

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

Вот шаги, которые нужно выполнить для добавления к себе в проект конфигурации:

Для этого открываем палитру команд в VS Code, выбираем пункт “Remote-Containers: Add Development Configuration Files”.

Нам предоставляется выбор:

  1. воспользоваться готовыми конфигурациями (Node.js, Ruby on Rails, C++, Go, PHP и ещё много других)
  2. добавить существующий в проекте Dockerfile или docker-compose.yml

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

Разбираемся в магии

Отдельно хотелось бы рассмотреть, что же делает VS Code с нашим контейнером, прежде чем его открыть.

Существует ряд нюансов, на которые следует обратить внимание:

  • при монтировании исходников в контейнер ID пользователя и ID группы внутри и снаружи должны совпадать, иначе мы получим в своих исходниках новые файлы, созданные от имени другого пользователя (возможно от рута)
  • у Git есть глобальные конфигурационные файлы, в которых задается ряд параметров, таких как имя пользователя, email, тип переноса строк и т.д., желательно, чтобы git внутри контейнера использовал такие же настройки
  • также необходимо скопировать некоторые пользовательские настройки для того, чтобы вы чувствовали себя как дома (.ssh/authorized_hosts, gpg конфигурацию)

Именно это делает VS Code при открытии devcontainer:

  • он изучает версию операционной системы, которая используется в Dockerfile
  • оборачивает ваш Dockerfile своим с дополнительными командами по пробросу UID и GID внутрь контейнера
  • патчит /etc/passwd, синхронизируя UID и GID внутреннего пользователя с хостовым пользователем
  • копирует в него все необходимые конфигурационные файлы из папки пользователя (git, ssh, gpg)
  • устанавливает свой бэкенд на ноде (это порядка 450Мб) внутрь контейнера, который будет связываться с хостовым VSCode по рандомному TCP порту
  • устанавливает внутрь контейнера все необходимые плагины для VS Code, прописанные в .devcontainer/devcontainer.json#extensions
  • монтирует исходники проекта в папку /workspaces

Вуаля! Проект открыт, и можно кодить как обычно.

В итоге, чтобы открыть любой проект с поддержкой devcontainer и начать быстро кодить, Вам нужна лишь машина на Linux, Windows, MacOS с установленным Docker и VS Code.

Уже не терпится? Попробуем в деле!

Я заготовил пример проекта, который можно открыть в VSCode и посмотреть, как это работает.

Для этого я форкнул первый найденный проект “Блог” на Rails (https://github.com/navrocky/rails-blog-sample-devcontainers ).

Как нельзя кстати оказалось, что он уже немного “подзасох”, последний коммит 5 лет назад. Соответственно, в те годы люди сидели на Ruby 2.3, Debian 8 (Jessie). Вот и попробуем всё это завести в нашем 2К22.

Dev container поддерживает два варианта окружения:

  • один Dockerfile. Этот вариант проще и подходит, когда нам не нужны дополнительные сервисы для работы, такие как БД.
  • docker-compose.yml. Этот вариант позволяет нам запустить как само окружение, так и все необходимые дополнительные сервисы. В примере я использовал именно этот вариант, так как нам нужна ещё сконфигурированная база данных.

Так выглядит типичная конфигурация в проекте:

Всё находится внутри папки .devcontainer. Файл devcontainer.json является основным и описывает конфигурацию для плагина VSCode.

devcontainer.json

{
	// Имя конфигурации
	"name": "Ruby",

	// указание файла docker-compose.yml и сервиса в нем, 
	// который будет использоваться для разработки
	"dockerComposeFile": "./docker-compose.yml",
	"service": "dev",

	// папка внутри контейнера, в которую монтируются исходники проекта
	"workspaceFolder": "/workspace",

	// действие при завершении работы с проектом
	"shutdownAction": "stopContainer",

	// дополнительные расширения VSCode, которые необходимо установить 
	// внутрь контейнера перед началом работы
	"extensions": [
		"castwide.solargraph", "eamodio.gitlens"
	],
	
	// пользователь внутри контейнера, под которым будет происходить вход 
	// в контейнер
	"remoteUser": "user"
}

Dockerfile

# берем нужную версию базового дистрибутива. Debian 8 из 2015 года, нам подходит.
FROM debian:8

# устанавливаем все необходимые утилиты
RUN apt-get update && apt-get install -y gnupg2 ca-certificates curl procps sudo git mc libpq-dev

# добавляем пользователя, под которым будем работать внутри контейнера, заодно даем ему возможность делать sudo
RUN useradd -m -d /home/user -s /bin/bash user && adduser user sudo && \
    echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

# дальше все действия производим уже от пользователя
USER user 

# устанавливаем rvm
RUN gpg2 --keyserver hkp://keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB && \
    curl -ksSL https://get.rvm.io | bash -s stable

# устанавливаем nvm
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash

# подгружаем все инициализационные скрипты и переменные среды в bash, чтобы заработал установленный rvm
SHELL ["/bin/bash", "-c", "-l"]

# устанавливаем нужную версию Ruby и Bundler
RUN rvm install ruby-2.4 && gem install bundler:1.17.3

# создаем пользовательский каталог .bundle, чтобы были правильные права на эту папку при монтировании volume
RUN mkdir -p /home/user/.bundle

# устанавливаем node v5.3.0 и делаем её по умолчанию
RUN source $HOME/.nvm/nvm.sh && nvm install v5.3.0 && nvm alias default v5.3.0

# просто висим в бесконечном ожидании из /dev/null, это нужно чтобы контейнер не закрылся после запуска
ENTRYPOINT [ "tail", "-f", "/dev/null" ]

Открываем проект, VSCode предлагает нам открыть его внутри контейнера - соглашаемся:

Ждем некоторое время, пока все образы будут загружены, и будет произведена сборка контейнера:

Пока ждем, можно нажать show log и посмотреть, что там происходит.

Всё - проект открыт, и можно приступать к работе:

Выполняем в терминале VSCode следующие команды:

rake db:setup
rails s -b 0.0.0.0

Открываем в браузере наш блог (http://localhost:3000/blog):

Можно зайти в административную панель (http://localhost:3000/admin) и добавить контент. Пользователь и пароль для входа: admin@example.com / 123456

Вот так просто и быстро можно открыть незнакомый проект и приступить к работе!

А что же IDEA и её производные, спросите вы?

Там не всё так радужно, но есть свет в конце туннеля.

Не так давно JetBrains добавила поддержку remote development в свои платные IDE. Community варианты её, к сожалению, не получили. И называется этот продукт JetBrains Gateway (https://www.jetbrains.com/remote-development/gateway/). Данная штука позволяет зайти на удалённую машину, в нашем случае в контейнер с поднятым ssh демоном, и загрузить туда выбранную вами IDE и запустить её в режиме сервера, а на вашем хосте будет запущен IDE Client, который будет по ssh туннелю общаться с серверной IDE. В целом всё выглядит примерно так же, как и в VS Code, но менее автоматизированно. При открытии проекта необходимо руками поднимать контейнер или docker-compose конфигурацию, настраивать соединение внутри контейнера. Серверная IDE, которая грузится в контейнер, ничем не отличается от десктопной, и в размерах тоже (это примерно 1,5 ГБ), что весьма печально. Интерфейс IDE клиента ощущается тормознее по сравнению с десктопной IDE и местами подглючивает. Но в целом, всё довольно работоспособно.

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