Поставка софта Обновления и версии: каналы, откат, поддержка старых
0%

Обновления и версии: каналы, откат, поддержка старых

Обновления и версии: каналы, откат, поддержка старых

В пятницу вечером в разборе архивов вашего агента находят уязвимость. Исправление — одиннадцать строк и полчаса. Дальше начинается настоящая работа: агент стоит у 340 клиентов в девяти версиях; у сорока автообновление выключено политикой безопасности; версия 3.8 не переходит на 4.x напрямую, потому что между ними необратимая миграция локальной базы; три клиента подписали договор, где смена версии в проде происходит только после их внутренней сертификации длиной шесть недель; и, наконец, апдейтер в версиях до 3.6 умеет обновлять приложение, но не умеет обновлять сам себя.

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

Рамка трека — в карте трека; от модели поставки из главы о моделях напрямую зависит, кто нажимает кнопку «обновить». Техника выката — канареечные релизы, blue-green, флаги — разобрана в devops и sre; здесь она используется, а не повторяется. Нас интересует не «как выкатить на свой прод», а «как довести версию до чужой машины, которой вы не управляете, и как забрать её обратно».

Обновление как обязательство: три колонки

Что получает получатель Во что обходится поставщику Какое обязательство создаёт
Автообновление по умолчанию исправления без действий, одна версия в поддержке канал, подпись, откат, метрики охвата не сломать чужой прод молча; уметь остановить выкат за минуты
Обновление по решению получателя контроль над своей средой и окном обслуживания зоопарк живых версий, бэкпорты, воспроизведение багов поддерживать заявленные версии заявленный срок
Принудительное обновление защита от известной уязвимости механизм блокировки старых клиентов и его отладка не превращать защиту в рычаг продаж
Долгая поддержка (LTS) предсказуемость на годы вторая и третья линия сборки и тестов доводить до объявленной даты, а не до потери интереса

Мысль, которую стоит принять до чтения дальше: обновление — не функция продукта, а обязательство поставщика. Функцию можно не сделать; обязательство содержится годами и стоит денег каждый месяц. В юнит-экономике эта строка обычно отсутствует — поэтому маржа продуктов с самостоятельной установкой регулярно ниже расчётной.

Три разные вещи, которые называют «версией»

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

Что версионируется Кто читает номер Что означает несовместимость Частота
Артефакт (сборка, пакет, образ) установщик, апдейтер, админ не установится или не запустится на этой платформе каждый релиз
Протокол/API между вашими компонентами клиент и сервер в рантайме клиент не понимает ответ, сервер — запрос редко, но больно
Формат данных на диске и в базе сама программа при старте старая версия не прочитает новые данные — а это ломает откат ещё реже, необратимо

Четвёртая примыкающая сущность — редакция условий (оферта, тарифы, политика конфиденциальности). Она версионируется по тем же правилам: у сделки фиксируется та редакция, что действовала на момент продажи (биллинг).

Правая нижняя ветка — самая важная во всей главе. Если новая версия переписала формат так, что предыдущая его не читает, откатить код вы больше не можете: только восстановить резервную копию, потеряв всё накопленное после обновления. Любая регрессия превращается из «откатились за две минуты» в инцидент с потерей данных. Отсюда правило: релиз, меняющий формат данных, и релиз, меняющий поведение, — два разных релиза, разнесённые во времени.

SemVer и CalVer: что вы обещаете номером

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

  • Для библиотеки это обычно экспортируемый API. Для приложения — почти никогда. Что считать ломающим изменением в редакторе? Формат файла — да. Убранный пункт меню? Он ломает чужой скрипт автоматизации, о котором вы не знали. Приложению полезнее зафиксировать список поверхностей совместимости и версионировать их явно.
  • Ветка 0.x — не «ещё не готово», а формальное «контракта нет». Задерживаться в ней после первого платящего получателя нечестно: люди читают номер как обещание.
  • Номер версии и идентификатор сборки — разные поля, и в артефакте нужны оба: 4.4.0 для человека, 4.4.0+2026.07.16.a91f3c2 для расследования. Восстановить, из какого коммита собрана версия у клиента, — задача на минуту, если это записано, и на день, если нет (цепочка поставки).
  • CalVer (2026.07, 24.04) честнее там, где главное обещание — не совместимость, а срок жизни: дистрибутивы, платформы, продукты для организаций. Из номера сразу видно, насколько версия устарела, и это ровно то, что нужно администратору.
  • Сравнение версий пишется один раз и переиспользуется: строковое сравнение считает 4.10.0 младше 4.9.0, разбор «по точкам» спотыкается на предрелизных суффиксах (1.0.0-rc.2 обязан быть младше 1.0.0), а метаданные сборки после плюса в сравнении не участвуют вовсе.

Окно совместимости: кто с кем обязан работать

Как только процессов стало больше одного, версии перестают меняться синхронно. Во время выката на вашем же кластере одновременно работают N и N+1 — это нормальный режим, а не исключение. У получателей с самостоятельной установкой разрыв измеряется годами.

Окно совместимости — письменное утверждение вида «сервер версии N обслуживает клиентов от N-2 до N+1», проверяемое в конвейере. Иначе это пожелание. Образец формулировки — политика расхождения версий Kubernetes, где отдельно указано, насколько узловой агент может отставать от API-сервера (https://kubernetes.io/releases/version-skew-policy/): цифры там менялись от релиза к релизу, ценна сама форма обязательства.

Пара Кто обязан быть терпимым Практическое правило
Клиент новее сервера сервер обновляем сервер первым; клиент не использует новое, пока сервер не подтвердил поддержку
Клиент старее сервера сервер N-2 поддерживаем явно, ниже — внятный отказ, а не пятисотка
Две версии сервиса рядом при выкате обе ни один релиз не требует одновременной остановки всех экземпляров
Новая версия и старые данные новая миграция на старте, идемпотентная и прерываемая
Старая версия и новые данные новая поле добавляем, но не делаем обязательным целое окно поддержки

Отсюда порядок операций, который стоит выучить наизусть: сначала расширяем, потом переключаем, потом убираем — и между «переключаем» и «убираем» проходит не спринт, а целое окно поддержки. Это parallel change Фаулера, применённый не к рефакторингу, а к поставке.

Каналы: способ ограничить радиус поражения

Канал — не наклейка «бета», а ответ на вопрос: сколько получателей пострадает, если этот билд плохой. Механика проста: у установки есть подписка на канал, сервер обновлений отдаёт по каналу свой «последний хороший» номер.

Канал Кто там Что обещано Скорость
dev / nightly команда, автотесты, десяток энтузиастов ничего, кроме «собирается» каждый день
beta добровольцы, внутренние стенды клиентов близко к финальному, откат бесплатный недели
stable все остальные всё, что заявлено в политике поддержки по графику
extended / LTS организации с окнами обслуживания только исправления, никаких новых функций месяцы

Браузеры — наглядный публичный пример: параллельные каналы плюс отдельный расширенный стабильный для организаций (https://chromiumdash.appspot.com/schedule). Цена видна сразу: каждый канал — отдельная линия сборки, матрица тестов и поток багов. Три канала на команду из пяти человек почти всегда ошибка; два (stable плюс beta) окупаются.

Фазовый выкат: проценты, которые нельзя брать случайно

Внутри stable обновление отдают долями: 1, 5, 20, 50, 100 процентов. Магазины предоставляют механизм из коробки — у одних поэтапная публикация растянута на неделю фиксированными ступенями, у других процент задаётся вручную и выкат можно остановить (конкретные ступени и правила меняются, сверяйтесь с документацией площадки на момент чтения; общий разбор — в главе о магазинах). Если механизм ваш собственный, критично одно свойство: назначение когорты должно быть устойчивым. Случайный бросок на каждом запросе означает, что при расширении с 5 до 20 процентов часть уже обновившихся «выпадет» обратно.

import hashlib

def cohort(install_id: str, release: str) -> float:
    """Устойчивая доля от нуля до единицы для пары «установка + релиз».

    Одна и та же установка при одном релизе всегда получает одно число, поэтому
    расширение выката только ДОБАВЛЯЕТ получателей и никогда не отбирает обновление
    у тех, кому его уже предложили. Соль включает номер релиза: иначе одни и те же
    невезучие всегда идут первыми. Время O(1), память O(1).
    """
    digest = hashlib.sha256(f"{release}:{install_id}".encode()).digest()
    return int.from_bytes(digest[:8], "big") / 2 ** 64

def offer_update(install, release, rollout_percent: float) -> bool:
    if install.channel != release.channel:
        return False
    if install.pinned_version:            # админ зафиксировал версию — уважаем
        return False
    if release.halted:                    # рубильник важнее процентов
        return False
    if install.id in release.early_ring:  # добровольцы всегда впереди
        return True
    return cohort(install.id, release.version) < rollout_percent / 100.0

«Ранний круг» добровольцев — не роскошь: проценты дают статистику, а люди, готовые к поломке и умеющие её описать, дают текст. Сами же ступени бессмысленны без критериев остановки, заданных до выката: доля установок, не подтвердивших успешный запуск; частота падений на новой версии против предыдущей с учётом доверительного интервала; всплеск обращений с упоминанием версии (самый ранний сигнал там, где телеметрии нет); рост доли откатившихся вручную — всё в тех же терминах, что бюджет ошибок. Рубильник «остановить» обязан быть отдельной операцией, доступной дежурному без релиза: если для остановки нужно собрать билд, вы будете останавливаться час.

Состояние «Остановлен» отделено от «Отозван» не из педантизма. Остановка стоит секунды и ничего не ломает; отзыв — это второй выкат в обратную сторону со своими рисками. Большинство инцидентов лечится остановкой: пострадал один процент, остальные не пострадают.

Как обновление физически доезжает

Манифест — документ, а не ссылка на файл. Минимальный честный набор полей:

# Ответ сервера обновлений установке 4.2.1, канал stable, платформа linux-amd64
schema: 1
channel: stable
latest: "4.4.0"
minimum_supported: "4.1.0"   # ниже этой версии мы вообще не разговариваем
minimum_required: "4.2.3"    # ниже этой версии работа блокируется: уязвимость
expires: "2026-07-23T00:00:00Z"   # защита от подсовывания старого манифеста
sequence: 4471                    # монотонный счётчик: назад не откатывается
upgrade_path:                     # через 4.3 не прыгать: там необратимая миграция
  - { from_range: "<4.2.0", via: "4.2.7" }
  - { from_range: ">=4.2.0", to: "4.4.0" }
artifact:
  url: "https://updates.example.com/4.4.0/agent-linux-amd64.tar.zst"
  size_bytes: 41203712
  sha256: "3f7a…"
  deltas:                         # дельты считаются от конкретных версий
    "4.3.0": { url: "…/4.3.0-to-4.4.0.patch", size_bytes: 3118422, sha256: "9c1b…" }
    "4.2.7": { url: "…/4.2.7-to-4.4.0.patch", size_bytes: 7740115, sha256: "aa04…" }
notes_url: "https://example.com/releases/4.4.0"
signature: "…"                    # подпись всего документа ключом релизов

Поля expires и sequence защищают не от абстрактного злоумышленника, а от двух конкретных атак на канал: заморозки (получателю бесконечно отдают старый валидный манифест, чтобы он не узнал о заплатке) и отката (подсовывают подписанную, но уязвимую версию). Систематически модель угроз описана в The Update Framework; криптография — в security, форматы пакетов и подпись установщиков — в следующей главе трека.

Дельты экономят не трафик, а деньги. Разница между 40 мегабайтами и 3 на миллионе установок — это терабайты исходящего трафика, то есть заметная строка в счёте (модель расчёта — в платформенных затратах). Цена — комбинаторика: дельты генерируются от каждой поддерживаемой версии, хранятся и тестируются. Компромисс — дельты от двух-трёх массовых версий плюс полный артефакт как запасной путь.

Апдейтер обновляет всё, кроме себя. Классическая ловушка: механизм обновления живёт внутри приложения, а ошибка — именно в нём; доставить исправление нечем. Лечится разделением: маленький, редко меняющийся компонент обновления отдельно от приложения. Правило — чем реже меняется апдейтер, тем он ценнее, и любая «удобная фича» в нём это правило нарушает.

Атомарное обновление через два слота: загрузчик, счётчик попыток, подтверждение и автоматический откат

Схема со слотами родом из мобильных и встраиваемых систем — там она обязательна: устройство может остаться без питания посреди записи, а физического доступа к нему нет (в Android это «бесшовные» обновления с двумя наборами разделов, https://source.android.com/docs/core/ota/ab; во встраиваемом Linux те же идеи в RAUC, SWUpdate, Mender). Полезна она везде: на сервере тот же инвариант достигается дёшево — распаковать релиз в каталог releases/4.4.0, прогреть, атомарно переставить символическую ссылку current, перезапустить; откат — переставить ссылку обратно.

Откат: что откатывается, а что нет

Что откатываем Механика Время Что может пойти не так
Поведение (флаг) выключить флаг, код остаётся секунды флагов накопилось столько, что комбинацию в проде никто не знает
Код (артефакт) предыдущий слот или образ минуты новая версия успела записать данные в новом формате
Данные (миграция) резервная копия или обратная миграция часы, с потерями теряется всё, что произошло после обновления

Отсюда практика, экономящая больше всего нервов: держите изменение поведения отдельно от изменения формата. Новая функциональность приезжает выключенной и включается флагом, откат делается флагом, а не бинарником (Feature Toggles). Тогда откат кода нужен редко, а откат данных — почти никогда. Для схемы данных тот же приём разворачивается в три отдельных релиза:

-- Релиз N (expand): добавляем, ничего не убирая. Версия N-1 колонки не знает и работает.
ALTER TABLE orders ADD COLUMN customer_email_norm text;   -- без NOT NULL и без DEFAULT
CREATE INDEX CONCURRENTLY idx_orders_email_norm ON orders (customer_email_norm);

-- Релиз N (код): двойная запись, чтение ещё из старой колонки.
-- Откат на N-1 в этот момент бесплатен.

-- Между релизами: перенос истории пачками, без долгих транзакций.
UPDATE orders SET customer_email_norm = lower(btrim(customer_email))
WHERE customer_email_norm IS NULL AND id BETWEEN :lo AND :hi;

-- Релиз N+1 (код): читаем из новой, пишем по-прежнему в обе. Откат на N всё ещё бесплатен.

-- Релиз N+2 (contract): убираем старое — и только когда в поддержке
-- не осталось версий, которые её читают.
ALTER TABLE orders DROP COLUMN customer_email;

Ключевая деталь здесь не SQL, а календарь: между первым и последним шагом проходит всё окно поддержки. Поэтому «expand/contract» так часто вырождается в «expand» — контракцию никто не планирует, и через два года в схеме тридцать мёртвых колонок. Лечится организационным приёмом: задача на шаг contract заводится с датой в момент шага expand (подробнее — у Фаулера и в databases).

Практическое следствие для коробочных и self-hosted продуктов: откат должен быть возможен на стороне получателя без вас. Инструкция «как вернуться на предыдущую версию» — такой же элемент поставки, как установщик. Если она начинается со слов «обратитесь в поддержку», при массовой регрессии поддержка ляжет первой.

Принудительное обновление и право не обновляться

Механика проста: сервер сообщает minimum_required, клиент ниже этой версии отказывается работать и показывает экран обновления. Сложным здесь является не код, а решение, когда этим пользоваться.

Оправданно: эксплуатируемая уязвимость в клиенте, особенно позволяющая навредить другим; изменение протокола, при котором старый клиент портит данные, а не просто не работает; требование площадки или регулятора со сроком; истечение объявленного заранее срока поддержки. Неоправданно: заставлять обновляться ради новой функциональности, ради удаления бесплатного тарифа, ради телеметрии или потому, что поддерживать старый код надоело, а сроков вы не объявляли. Кнопка одна и та же; разница только в том, что вы обещали.

Есть третий вариант, применяемый реже, чем стоило бы: деградация вместо блокировки. Старый клиент работает, но с предупреждением и без функций, которые сервер больше не поддерживает. Для многих продуктов это честнее полной блокировки и дешевле полной поддержки; логика частичной работоспособности — в sre.

Отдельный класс ограничений — внешние. Клиент, у которого версия проходит внутреннюю сертификацию, может быть по договору вправе оставаться на старой сборке; площадки, наоборот, регулярно требуют обновлять приложение под новые требования платформы, иначе снимают публикацию (у мобильных магазинов это ежегодные требования к целевому уровню API — см. магазины приложений и mobile).

Вопрос «имеем ли мы право отключить версию 3.x у клиента с годовым договором» — не инженерный. Инженерная работа здесь другая: заметить, что такой вопрос возник, и задать его юристу в пригодной для ответа форме. Плохой вопрос: «а можно нам отключить старую версию?» Хороший: «В договоре с клиентом X от такого-то числа сказано то-то про версии и сроки поддержки. Мы планируем прекратить обслуживание версий ниже 4.2 через 90 дней после письменного уведомления, потому что в них уязвимость такого класса. Достаточно ли уведомления на адрес из договора, обязаны ли мы предложить альтернативу и какой срок считается разумным?» Разница между формулировками — это разница между счётом за пять часов и ответом за двадцать минут. Живой пример того, как такие обязательства формулируются, лежит на самом портале: оферта, правила отмены и возврата и политика обработки персональных данных в разделе «Легальные документы» в подвале сайта.

Поддержка старых версий как строка бюджета

Каждая поддерживаемая ветка — отдельная линия сборки, отдельный набор тестов, отдельный поток бэкпортов и отдельная очередь багов.

Картинка выглядит аккуратно ровно до момента, когда исправление не переносится черри-пиком: в ветке 4.2 нет модуля, который успели отрефакторить, и заплатку приходится писать заново под старую архитектуру, а потом отдельно тестировать. Эта работа, а не сам перенос, и составляет основную стоимость.

def support_cost(branches_besides_main: int, fixes_per_month: float,
                 h_backport: float = 1.5, h_verify: float = 2.0,
                 share_rewrite: float = 0.3, rewrite_mult: float = 4.0) -> float:
    """Часы в месяц на поддержку старых веток. Считаем только безопасность и
    критические ошибки: новые функции в старые ветки не переносятся. O(1)."""
    simple = h_backport + h_verify
    hard = h_backport * rewrite_mult + h_verify
    avg = (1 - share_rewrite) * simple + share_rewrite * hard
    return branches_besides_main * fixes_per_month * avg

print(round(support_cost(2, 6), 1))   # 63.0 часа в месяц при двух ветках и шести фиксах
# Это около 40 процентов ставки инженера — навсегда, каждый месяц.

Шестьдесят три часа в месяц — не «немного дополнительной работы», а строка бюджета, которую видно в цене продукта. Отсюда следует, что длительная поддержка старых версий — платная услуга, а не жест доброй воли: именно так она и продаётся у зрелых поставщиков. Модель цены для неё — в главе о ценообразовании.

Политика Формулировка Кому подходит Скрытая цена
N-1 «поддерживается текущая и предыдущая минорная» SaaS, быстрые продукты, внутренние платформы требует, чтобы обновление было дёшево получателю, иначе он застрянет
LTS-линии «каждая четвёртая версия живёт 24 месяца» инфраструктура, self-hosted, продукты для организаций две-три параллельные линии сборки и тестов
Календарный EOL «версия 4.x поддерживается до 1 сентября 2028» всё, что покупают по договору дату нельзя двигать назад, только вперёд

Порядок величин по публичным политикам (цифры меняются, проверяйте актуальные): линия LTS у Node.js живёт около 30 месяцев от первого релиза (https://github.com/nodejs/release); минорная версия Kubernetes получает исправления примерно год с небольшим хвостом (https://kubernetes.io/releases/patch-releases/); мажорная версия PostgreSQL — пять лет (https://www.postgresql.org/support/versioning/); LTS-выпуск Ubuntu — пять лет обычной поддержки с платным продлением сверх. Диапазон «год — пять лет» и есть интервал, внутри которого лежат разумные обещания. «Поддерживаем всё» в него не попадает: это бесконечность при нулевом бюджете.

В SaaS роль старой версии играет старая версия интерфейса, а не установленный артефакт. Механика та же: объявленный срок, машиночитаемое предупреждение (заголовок Sunset, RFC 8594), измеримый остаток трафика на устаревшем эндпойнте и адресные письма тем, кто ещё на нём. Образец письменного обязательства — политика устаревания API у Kubernetes, где заранее зафиксировано, сколько релизов проживёт объявленный устаревшим объект (https://kubernetes.io/docs/reference/using-api/deprecation-policy/).

Хвост установок: метрики, которые считают за вас

Кривая распространения новой версии и хвост старых установок за двенадцать недель

Метрика Как считать Зачем нужна
Время до 50 и до 90 процентов дни от публикации до доли установок реалистичный срок между expand и contract
Хвост доля установок на версиях старше предыдущей на 12-й неделе прямая оценка числа веток, которые придётся чинить
Время до заплатки, 95-й процентиль от готовности исправления до установки у 95 процентов ваш настоящий SLA по безопасности, а не тот, что на сайте
Доля неудачных обновлений откаты и неподтверждённые запуски к числу попыток качество самого канала обновлений
Доля установок после EOL по телеметрии или по обращениям сколько людей затронет отключение

Хвост в 5–7 процентов — типичная картина для продукта, где автообновление можно выключить, и он почти не тает: стенды без интернета, зафиксированные политикой сборки, забытые установки, клиенты с согласованием обновлений. Планируйте не по среднему, а по хвосту. Как смотреть на распределения вместо средних — в performance; как связывать эти числа с решениями — в product-management. При этом сам факт «эта установка проверила обновления» — уже данные о получателе, и в некоторых юрисдикциях к ним есть требования: что вы собираете, зачем и на каком основании — вопрос главы про ограничения и трека security.

Как это выглядит в разных моделях поставки

Модель Кто решает, когда Что тяжелее всего Что обязательно сделать
SaaS вы старые интеграции и клиенты API версионировать API, объявлять срок, мерить остаток трафика
Self-hosted получатель цепочки миграций через несколько версий описать поддерживаемые пути обновления и проверять их в CI
Мобильное приложение пользователь и магазин ревью посередине, старые версии ОС поэтапная публикация, minimum_required на сервере, план на отказ ревью
Десктоп пользователь права на запись, антивирусы, самообновление апдейтера подписанный установщик, тихое обновление, ручной откат
Встраиваемое и IoT вы, но устройство офлайн месяцами питание, память, отсутствие доступа A/B-слоты, watchdog, устойчивость к обрыву
Игры магазин и консольная платформа сертификация патчей, размер, синхронность релиза планировать патч как отдельный релизный цикл

Клиент, который два года не обновлялся, попытается перейти с 3.8 сразу на 4.4. Если между ними были необратимые миграции, честный ответ — «нельзя, сначала 4.2.7». Такие обязательные промежуточные релизы полезно называть явно (в Mozilla для этого есть термин watershed) и, главное, проверять автоматически: тест обновления с каждой поддерживаемой версии до текущей либо стоит в конвейере, либо не работает (тесты в CI). Ошибка, которую делают почти все, — тестировать обновление только с предыдущей версии. В игровых патчах к этому добавляются все ограничения сразу: сертификация платформы, размер обновления и обязательное совпадение версий клиента и сервера (дистрибуция игр).

Типичные ошибки

  1. Считать, что откат кода откатывает всё. Данные, отправленные письма и проведённые платежи он не откатывает; список необратимого составьте заранее.
  2. Один релиз, меняющий и поведение, и формат данных. Первая же регрессия ставит выбор между «жить со сломанным» и «терять данные».
  3. Случайная когорта при фазовом выкате. Расширение процента отбирает обновление у тех, кому его уже отдали, и метрики перестают что-либо значить.
  4. Отсутствие рубильника «остановить». Если остановка требует релиза, вы будете останавливаться час вместо минуты.
  5. Тестировать обновление только с предыдущей версии. Реальные получатели прыгают через четыре — ломается именно эта дорожка.
  6. Апдейтер, который умеет всё. Чем больше в нём логики, тем выше шанс, что чинить придётся именно его — тем самым, что сломан.
  7. Обещать поддержку без даты и принуждать к обновлению ради функций. Первое нельзя спланировать, второе обесценивает механизм принуждения к моменту настоящей уязвимости.
  8. Считать хвост исчезающим и не фиксировать, из какого коммита собрана версия. Хвост — постоянный бюджет, а расследование старого бага без ссылки на коммит начинается с археологии.

Мини-итог

Версия — не число, а обещание, и обещаний минимум три: артефакт, протокол и формат данных. Совместимость держится письменным окном «кто с кем обязан работать» и порядком «расширяем — переключаем — убираем», где между первым и последним шагом проходит всё окно поддержки. Каналы и фазовый выкат ограничивают число пострадавших и работают только при устойчивой когорте и заранее заданных критериях остановки. Откат бывает трёх видов и стоит по-разному: флаг — секунды, артефакт — минуты, данные — часы и потери; поэтому изменение формата всегда едет отдельным релизом. Принудительное обновление — инструмент для уязвимостей, а не для продаж, и там, где вопрос упирается в договор, работа инженера — задать юристу точный вопрос. А поддержка старых версий измеряется в человеко-часах в месяц: посчитайте их до того, как объявите срок.

Источники

Что дальше

Мы разобрались, как версия доезжает до получателя и как её забрать обратно. Осталась форма, в которой она едет: пакет, образ, установщик — и подпись, без которой всё описанное выше не имеет смысла, потому что подменить можно любой канал.

Упаковка и установка: пакеты, контейнеры, инсталляторы, подпись

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов