Сдерживаемое зло лучше, чем распространяемое

Последняя часть головоломки в нашем собственном приложении — интерфейс. Знаете, обычный ангуляр, бутстрап, еще какая-то js-магия. Он был построен очень типичным безответственным способом, к которому привыкли многие фронтенд-разработчики: запустить npm install, запустить bower install, запустить grunt build. Сейчас Bower устарел, большинство зависимостей закреплены случайным образом в не-LTS-версиях, некоторые зависимости не имеют понятия о LTS, некоторые не закреплены. Через несколько лет из-за вложенных зависимостей возникли небольшие конфликты версий. Если бы вы собрали это приложение из того же коммита git две недели назад, а сегодня оно дает совершенно другой (но чем-то похожий) код.

Обычные, скучные, невоспроизводимые сборки.

Идеальное решение требовало бы упаковки и поддержки всех зависимостей как системных пакетов, но быстрая оценка показывает, что в настоящее время в современном Debian присутствует только около половины используемых пакетов.

У меня нет времени разбираться с ~700 пакетами, созданными комбинацией вызовов npm и bower, но даже за один рабочий день я могу сделать его менее злым.

Минимальные требования к здравомыслию

  1. Сборки должны быть воспроизводимыми. Некоторые зависимости не закреплены. Это вина разработчика, но я имею дело с тем, что у меня есть. Нам нужно иметь возможность каждый раз создавать одно и то же из одного и того же коммита.
  2. Процесс сборки зависит от внешнего сервера. Это может звучать параноидально, но около 10 лет назад у меня был неприятный случай, когда pypi вышел из строя, и мы не смогли доставить критическое исправление из-за того, что сборка заняла около 4 часов. Сборка всегда должна быть полностью независимой от каких-либо внешних сервисов (на которые не распространяется SLA, контрактные обязательства или что-то еще, кроме добросовестности).
  3. Все не лучшие практики должны четко содержаться в каком-то ограниченном рабочем процессе (конвейере) с минимальным влиянием на проект. Это определенно поможет, если кто-то решит их исправить.
  4. Среда сборки должна быть воспроизведена из декларативной конфигурации. Я ненавижу «золотых драгоценных рабов» для 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?

Толпа на фронтенде может быть мощнее — нужно ли это? Абсолютно нет.

Но он обеспечивает несколько ключевых преимуществ:

  1. Оператор имеет контроль над установленными версиями. apt-cache policy показывает вам все доступные версии, текущую версию и ожидающую (для установки).
  2. Все версии приложения (включая исходники) хранятся в репозитории apt (мы используем для этого aptly), единообразно со всеми остальными программными пакетами.
  3. Изменения в conffiles сохраняются во время установки/обновления и обрабатываются свойствами. Это означает, что я могу понизить версию программного обеспечения без использования ansible в экстренных ситуациях, а промежуточная стадия позволяет разработчикам играть с версиями без каких-либо ручных обновлений конфигурации.
  4. Деинсталляция, даунгрейд, апгрейд и все остальные манипуляции убирают за собой. Если старые версии предоставили файл, а новые нет, обновление удалит этот старый файл.
  5. Можно узнать, кто является поставщиком файла: (dpkg -S)
  6. Список всех файлов в пакете можно просмотреть, даже не скачивая его (apt-file)
  7. Присутствует вся криптография для защиты целостности пакета. changes можно подписать, репозиторий подписывается, и все это на уровне содержимого, а не только «SSL сверху».
  8. Есть способ проверить целостность установленного пакета в случае компрометации (dpkg --verify).

Можно возразить, что все это можно сделать и по-другому. Да, оно может. Но кто будет обслуживать этот велосипед NIH? А зачем его писать, если он уже написан и широко доступен (проверен, поддерживается, поддерживается)?

Это хорошо?

Нет. Торговля — это зло, но это меньшее зло, чем получение во время сборки.