Когда-то в Яндексе использовали интересный подход для деплоя сервисов — с помощью debian-пакетов. В этой статье я расскажу основную суть и покажу пример.
Debian-пакет — архив со специальной структурой папок, в которые упакованы исходные файлы чего-либо (сайта, программы и/или прочего) и специальные файлы конфигурации, описывающие куда и каким образом будут копироваться эти исходные файлы на файловую систему.
У любого пакета имеется обязательная информация:
- ИМЯ ПАКЕТА — для того, чтобы как-то отличать пакеты одного сервиса от пакетов другого сервиса;
- ВЕРСИЯ ПАКЕТА — чтобы как-то отличать разные пакеты одного проекта.
Для пакетов с одним именем существует одна особенность: при установке пакета более новой версии, пакет со старой версией будет полностью удален. Это означает, что нельзя установить две версии одного пакета.

Создание debian-пакетов#
Согласно официальному гайду необходимо указать дополнительные переменные окружения в .bash_profile:
export DEBFULLNAME="Name Surname"
export DEBEMAIL=login@yandex-team.ruВсе, что связано со сборкой пакетов, находится в служебной папке debian в корне сервиса.
Во внутренней документации в Яндексе я описывал алгоритм сборки debian-пакета на примере несуществующего сервиса kukusik. Не буду изменять традиции и в этой статье :)
Проект kukusik содержит как серверную часть (“динамику”), так и клиентскую (“статику”). Обе эти части должны быть выложены с помощью debian-пакетов.
У каждой команды в Яндексе был свой префикс для debian-пакетов, чтобы их легко можно было отличать в общем списке. В моей команде использовался префикс yandex-maps-ui-.
Создадим “скелет” для debian-пакета:
$ mkdir debian
$ dch --create --distribution unstable --package yandex-maps-ui-kukusik --newversion 1.0.0 "Initial version"
$ echo 10 > debian/compat
$ touch debian/control
$ touch debian/rules
$ chmod +x debian/rulesБыло создано 4 файла:
Файл compat отвечает за совместимость дебхелперов, и туда нужно записывать число 10.
Файл changelog содержит историю изменений пакета, его версию и прочую дополнительную информацию.
Наиболее интересны файлы control и rules. О них и пойдет речь в следующих разделах.
control#
В control файле описывается служебная информация, которая будет использоваться при сборке и установке пакетов.
В control файле необходимо описать:
- управляющую информацию для пакета с исходным кодом (исходники сервиса) и бинарных пакетов (динамика и статика)
- список зависимостей, нужных для сборки
- мейнтейнера (ответственного за пакет)
Развертывание сервиса происходит тремя пакетами: один - для “динамики” и два - для “статики”.
Почему для статики необходимо два пакета? Дело в том, что пакеты со статикой сразу размещаются на боевые сервера. Но если выложить пакет новой версии на боевой сервер с тем же именем, то предыдущая версия пакета будет удалена. Возникает вопрос: как установить две версии одного пакета одновременно, чтобы не сломать релиз, находящийся в продакшене, и развернуть новую версию в тестовом режиме? Было найдено хитрое решение, основанное на том, что при установке новой версии одного пакета, зависимости, указанные от этого пакета, устанавливаются, а при удалении - не уничтожаются.
Тогда было решено создать два пакета со статикой: виртуальный (yandex-maps-ui-kukusik-static) и настоящий, в имени которого добавлялся номер версии для уникальности (yandex-maps-ui-kukusik-static-).
Т.е. для каждой новой версии статики создается абсолютно новый пакет, и он записывается в зависимости для виртуального пакета. Например, при сборке версии 1.0.0 мы получим два пакета со статикой: yandex-maps-ui-kukusik-static=1.0.0, у которого будет прописана зависимость от пакета yandex-maps-ui-kukusik-static-1-0-0=1.0.0. Именно последний пакет и будет фактически устанавливаться на машинку.


Итоговый control файл будет выглядеть следующим образом:
Source: yandex-maps-ui-kukusik
Priority: optional
Build-Depends: cdbs
Maintainer: Kukusik <kukusik@kukusik.ru>
Package: yandex-maps-ui-kukusik
Priority: optional
Architecture: all
Description: WWW for kukusik
Package: yandex-maps-ui-kukusik-static-1-0-0
Priority: optional
Architecture: all
Description: Static for WWW for kukusik
Package: yandex-maps-ui-kukusik-static
Priority: optional
Depends: yandex-maps-ui-kukusik-static-1-0-0 (>= 1.0.0)
Architecture: all
Description: Depends on latest yandex-maps-ui-kukusik-static-rules#
rules — специальный Makefile с инструкциями для сборки пакета.
Для сборки пакета обычно используется CDBS с набором хелперов и предустановленных переменных.
В Яндексе было написано два debhelper:
dh_versionsдля замены строки{{DEBIAN_VERSION}}в указанных файлах или директорияхdh_environmentформирует postinstall-файл, который после установки пакета проставит симлинкуcurrentв конфигах в зависимости от окружения
Чтобы не усложнять, я напишу упрощенную реализацию прямо в файле rules.
Таким образом rules должен выглядеть примерно так:
#!/usr/bin/make -f
include /usr/share/cdbs/1/rules/buildvars.mk
include /usr/share/cdbs/1/rules/debhelper.mk
ENVIRONMENT=$(shell cat /etc/yandex/environment.type)
DEB_PACKAGE_DESTDIR=debian/$(DEB_SOURCE_PACKAGE)/usr/local/www/app/$(DEB_SOURCE_PACKAGE)
DEB_NGINX_CONFIG_DESTDIR=debian/$(DEB_SOURCE_PACKAGE)-nginx-config/etc/nginx/sites-available
STATIC_PACKAGE=$(if $(filter $(DEB_SOURCE_PACKAGE)-static-%,$(DEB_PACKAGES)),$(DEB_SOURCE_PACKAGE)-static-$(subst .,-,$(DEB_VERSION)))
DEB_STATIC_PACKAGE_DESTDIR=debian/$(STATIC_PACKAGE)/usr/local/www/static/$(subst yandex-maps-ui-,,$(DEB_SOURCE_PACKAGE))/$(DEB_VERSION)/
debian/control::
▸ sed -i 's!^\(Package: \).\+-static\-.*!\1$(STATIC_PACKAGE)!' $@
▸ sed -i 's!^\(Depends: \).\+-static\-.*!\1$(STATIC_PACKAGE) (>= $(DEB_VERSION))!' $@
build:
▸ ln -snf $(ENVIRONMENT) configs/current
▸ find configs -type f -exec sed -i 's/{{DEB_VERSION}}/$(DEB_VERSION)/g' {} \;
▸ npm i
▸ npm run build
▸ npm prune --production
install/$(DEB_SOURCE_PACKAGE)::
▸ mkdir -p $(DEB_PACKAGE_DESTDIR)
▸ cp -r node_modules configs server package.json $(DEB_PACKAGE_DESTDIR)
install/$(STATIC_PACKAGE)::
▸ mkdir -p $(DEB_STATIC_PACKAGE_DESTDIR)
▸ cp -r out $(DEB_STATIC_PACKAGE_DESTDIR)В проекте kukusik используются только две цели:
buildвыполнится один раз для запуска сборки проекта;installслужит для установки файлов в дерево файлов для каждого бинарного пакета из каталога debian.
См. также:
Особенности пакета со статикой#
В Яндексе также использовались файлы postinst.in и postrm.in для статического пакета. Первый из них выполняется после установки пакета (часто в нем прописывается установка различных симлинков), а второй — после удаления (т.е. когда начинает выполняться postrm, то пакет уже удален).
Все статические файлы во фронтенде подключаются с CDN по версионированному пути, например: http://yastatic.net/kukusik/1.0.0/pages/index/_index.css .
Однако, все картинки лежат в одной папке и не зависят от версии, например,
http://yastatic.net/kukusik/_/SRaiqrtlCWzYa3hXDwJxk_zoHIY.svg . Это нужно для того, чтобы при выкладке очередного релиза пользователи не загружали заново все картинки.
После установки нового пакета со статикой все новые картинки добавляются в общую кучу.
А при удалении пакета собираются картинки со всех установленных пакетов со статикой, этот список сравнивается с тем, что есть в общей куче, и удаляются неиспользуемые картинки. Тем самым папка _ всегда содержит актуальный список картинок, которые используются хотя бы в одном пакете со статикой.
Для простоты я не стал реализовывать этот механизм в своем примере. Но я открыт для пулриквестов ;)
Nginx-конфиг#
Исторически мы используем nginx для проксирования запросов в само приложение.
Сборку отдельного пакета несложно добавить в текущую систему.
Создадим конфиг в debian/nginx.conf:
server {
listen [::]:8081 ipv6only=off backlog=204;
location / {
proxy_pass http://127.0.0.1:8080/;
}
}В control файл нужно добавить несколько строк:
Package: yandex-maps-ui-kukusik-nginx-config
Architecture: all
Description: Nginx config for kukusikВ rules файл нужно добавить несколько строк:
install/$(DEB_SOURCE_PACKAGE)-nginx-config::
mkdir -p $(DEB_NGINX_CONFIG_DESTDIR)
cp debian/nginx.conf $(DEB_NGINX_CONFIG_DESTDIR)/$(DEB_SOURCE_PACKAGE).confЕще дополнительно настроить postinst и postrm для создания и удаления симлинки в /etc/nginx/sites-enabled.
Запуск приложения#
Для запуска приложений в Яндексе использовался upstart, но за это время появился специализированный менеджер процессов для nodejs — pm2.
Строка запуска выглядит следующим образом:
pm2 start /usr/local/www/app/yandex-maps-ui-kukusik/server/app.jsПолный пример#
Чтобы можно было попробовать сборку debian-пакетов самостоятельно, я создал небольшой проект на гитхабе. В нем используется все то, что я описывал в статье.
Вы сами можете убедиться, что это рабочий вариант для выкладки проектов ;)