Сдерживаемое зло лучше, чем распространяемое
Последняя часть головоломки в нашем собственном приложении — интерфейс. Знаете, обычный ангуляр, бутстрап, еще какая-то js-магия. Он был построен очень типичным безответственным способом, к которому привыкли многие фронтенд-разработчики: запустить npm install
, запустить bower install
, запустить grunt build
. Сейчас Bower устарел, большинство зависимостей закреплены случайным образом в не-LTS-версиях, некоторые зависимости не имеют понятия о LTS, некоторые не закреплены. Через несколько лет из-за вложенных зависимостей возникли небольшие конфликты версий. Если бы вы собрали это приложение из того же коммита git две недели назад, а сегодня оно дает совершенно другой (но чем-то похожий) код.
Обычные, скучные, невоспроизводимые сборки.
Идеальное решение требовало бы упаковки и поддержки всех зависимостей как системных пакетов, но быстрая оценка показывает, что в настоящее время в современном Debian присутствует только около половины используемых пакетов.
У меня нет времени разбираться с ~700 пакетами, созданными комбинацией вызовов npm
и bower
, но даже за один рабочий день я могу сделать его менее злым.
Минимальные требования к здравомыслию
- Сборки должны быть воспроизводимыми. Некоторые зависимости не закреплены. Это вина разработчика, но я имею дело с тем, что у меня есть. Нам нужно иметь возможность каждый раз создавать одно и то же из одного и того же коммита.
- Процесс сборки зависит от внешнего сервера. Это может звучать параноидально, но около 10 лет назад у меня был неприятный случай, когда pypi вышел из строя, и мы не смогли доставить критическое исправление из-за того, что сборка заняла около 4 часов. Сборка всегда должна быть полностью независимой от каких-либо внешних сервисов (на которые не распространяется SLA, контрактные обязательства или что-то еще, кроме добросовестности).
- Все не лучшие практики должны четко содержаться в каком-то ограниченном рабочем процессе (конвейере) с минимальным влиянием на проект. Это определенно поможет, если кто-то решит их исправить.
- Среда сборки должна быть воспроизведена из декларативной конфигурации. Я ненавижу «золотых драгоценных рабов» для CI, чьи резервные копии нужно делать, потому что их можно воссоздать.
Осложнения
У меня есть только одно осложнение, с которым нужно справиться. Angular генерирует некоторый script.xxxx.js, который содержит настройки для приложения, и эти настройки включают baseUrl
, который должен быть частью URL-адреса с полным доменным именем (и протоколом). Это усложняет доставку на постановку. Наше приложение запускается на app.example.com, но промежуточные этапы выполняются на app.staging.project.internal.example.com, а некоторые промежуточные этапы даже запускаются на простом http. Мне нужно изменить это полное доменное имя на Ansible во время развертывания, но я не хочу искать файл для исправления во время Ansible. Более того, Debian Sanity требует, чтобы я пометил этот файл как файл конфигурации.
В то же время я не хочу, чтобы более старая версия этого (измененного) файла присутствовала в системе после обновления, поэтому я не мог просто пометить этот файл как conffile.
Решение
Предлагаемое мной решение — написать простой скрипт для замены baseUrl
и включить этот скрипт в приложение. Он прочитает правильный файл конфигурации (например, /etc/app.conf
) и выполнит замену. Чтобы запустить этот скрипт разумным образом, я создам одну короткую службу systemd, которая будет выполнять эти изменения во всех необходимых случаях: загрузка системы, обновление пакета, обработчик ansible и т. д.
Работа с зависимостями
Как вы можете себе представить, мое решение здесь — вендорство.
Приложение живет в ветке master
(или ветках функций, которые объединены в master
, здесь это вне области действия оператора). Каждая новая версия для развертывания объединяется в vendoring
ветку. Если зависимости необходимо обновить, это выполняется в этой ветке. Изменения, связанные с Debian, происходят в ветке debian
. debian/gbp.conf
указывает на vendoring
как на восходящую ветвь, а на debian
— на ветку Debian. Код поставщика входит в «исходный пакет». Поскольку все зависимости уже установлены, grunt
может создавать приложения без подключения к сети. Более того, я использую grunt для создания зависимости для приложения и использую grunt, предоставляемый системой, а не поставщиком.
Сценарий, о котором я говорил ранее, взят из ветки Debian, а также модуля systemd.
Файлы
дебиан/правила:
#!/usr/bin/make -f override_dh_auto_build: grunt build %: dh $@
приложение.сервис:
[Service] Type=one-short ExecStart=/usr/lib/app/config-update
приложение.установить:
dist/* /usr/share/app/html
скрипт для обновления конфигурации:
(Еще не писал, но надеюсь будет банально).
Почему упаковка Debian?
Толпа на фронтенде может быть мощнее — нужно ли это? Абсолютно нет.
Но он обеспечивает несколько ключевых преимуществ:
- Оператор имеет контроль над установленными версиями.
apt-cache policy
показывает вам все доступные версии, текущую версию и ожидающую (для установки). - Все версии приложения (включая исходники) хранятся в репозитории apt (мы используем для этого
aptly
), единообразно со всеми остальными программными пакетами. - Изменения в conffiles сохраняются во время установки/обновления и обрабатываются свойствами. Это означает, что я могу понизить версию программного обеспечения без использования ansible в экстренных ситуациях, а промежуточная стадия позволяет разработчикам играть с версиями без каких-либо ручных обновлений конфигурации.
- Деинсталляция, даунгрейд, апгрейд и все остальные манипуляции убирают за собой. Если старые версии предоставили файл, а новые нет, обновление удалит этот старый файл.
- Можно узнать, кто является поставщиком файла: (
dpkg -S
) - Список всех файлов в пакете можно просмотреть, даже не скачивая его (
apt-file
) - Присутствует вся криптография для защиты целостности пакета.
changes
можно подписать, репозиторий подписывается, и все это на уровне содержимого, а не только «SSL сверху». - Есть способ проверить целостность установленного пакета в случае компрометации (
dpkg --verify
).
Можно возразить, что все это можно сделать и по-другому. Да, оно может. Но кто будет обслуживать этот велосипед NIH? А зачем его писать, если он уже написан и широко доступен (проверен, поддерживается, поддерживается)?
Это хорошо?
Нет. Торговля — это зло, но это меньшее зло, чем получение во время сборки.