kai.kzLab

Сбой left-pad в марте 2016 года: как удаление пакета из 11 строк сломало тысячи сборок на npm

Команда KAI Lab · · 7 мин чтения

22 марта 2016 года автор пакета left-pad удалил из реестра npm все свои 273 пакета, и примерно с 14:30 по тихоокеанскому времени сборки, которым left-pad был нужен напрямую или через цепочку зависимостей, начали падать — npm видела сотни ошибок в минуту. Сбой длился 2,5 часа и закончился тем, что npm пошла на беспрецедентный шаг и вернула удалённую версию 0.0.3 из бэкапа.

Сам left-pad — одна функция, которая дополняет строку слева до нужной длины: в файле index.js версии 0.0.3 всего 11 непустых строк. Но его через пакет line-numbers подтягивали, среди прочих, babel и atom, и по оценке npm сбой задел многие тысячи проектов.

Главное о сбое в одной таблице

ВопросОтвет
Когда22 марта 2016, примерно с 14:30 по тихоокеанскому времени, 2,5 часа
С чего началосьспор автора пакета kik с компанией Kik за имя; npm решила, что имя kik должна поддерживать компания
Что удалилипакет kik и ещё 272 пакета того же автора, среди них left-pad
Почему падали сборкиleft-pad был транзитивной зависимостью, в том числе у babel и atom через line-numbers; скачать удалённую версию было нельзя
Почему не помогла заменачерез десять минут другой разработчик опубликовал такой же left-pad как 1.0.0, но зависимости запрашивали 0.0.3
Как починилиnpm восстановила 0.0.3 из бэкапа: объявила в 16:05, закончила к 16:55
Что изменилосьnpm пообещала усложнить удаление версий, от которых зависят другие пакеты; сейчас пакет с зависимыми удалить нельзя

Хронология сбоя left-pad

Когда (PT)Что произошло
Середина марта 2016Представитель Kik просит npm помочь в споре с автором пакета kik. npm решает, что имя должна поддерживать компания Kik
22 мартаАвтор удаляет kik и ещё 272 своих пакета, среди них left-pad
Вскоре после 14:30npm фиксирует сотни ошибок в минуту: пакеты, их зависимые и зависимые зависимых не могут скачать left-pad
В течение 10 минутКэмерон Уэстленд публикует функционально идентичный left-pad 1.0.0. Сборкам, которым нужна 0.0.3, это не помогает
16:05npm объявляет, что вернёт версию 0.0.3
16:55Версия 0.0.3 восстановлена из бэкапа, сбой закончен
23 мартаАйзек Шлютер из npm публикует разбор «kik, left-pad, and npm»

Почему 11 строк сломали тысячи сборок

Проекты на JavaScript редко зависят от left-pad напрямую. Его тянула библиотека line-numbers, её — инструменты уровня babel, а их — почти любой фронтенд-проект. Разработчик мог годами не знать, что в его сборке есть left-pad, пока npm install не упал с ошибкой, что пакета нет.

Посмотреть, кто в вашем проекте тянет конкретный пакет, можно так (пример вывода):

$ npm ls left-pad
my-app@1.0.0 /srv/my-app
└─┬ report-lib@2.1.0
  └─┬ text-utils@0.4.2
    └── left-pad@1.3.0

Быстрая замена не сработала из-за версий. Новый left-pad вышел как 1.0.0, а цепочки зависимостей просили именно 0.0.3, и диапазон версий 1.0.0 не допускал. Опубликовать 0.0.3 заново штатно было невозможно: реестр не позволяет повторно выпустить удалённую версию. Поэтому npm восстановила её из бэкапа, и сама назвала этот шаг беспрецедентным.

Удалить свои пакеты в тот момент было штатной операцией: правила npm это позволяли. Проблема была не в одном авторе, а в том, что вся экосистема строилась на допущении, что опубликованная версия будет доступна всегда.

Что изменилось в npm после left-pad

Уже в разборе 23 марта npm пообещала усложнить удаление версии, если оно сломает другие пакеты. Действующая политика unpublish такая:

Возраст пакетаКогда можно удалить
Меньше 72 часовесли в публичном реестре от него не зависят другие пакеты
Больше 72 часовтолько если одновременно: нет зависимых пакетов, меньше 300 скачиваний за последнюю неделю и один владелец

Удалённую версию нельзя опубликовать снова никогда, а если удалены все версии, имя пакета заблокировано на 24 часа. Вместо удаления npm предлагает помечать версии устаревшими: пакет продолжает скачиваться, но при установке показывает предупреждение.

$ npm deprecate my-lib@"< 2.0.0" "Используйте 2.x: в старых версиях ошибка в парсере"

Уроки для DevOps

1. Lock-файл нужен, но не спасает от исчезновения пакета

package-lock.json фиксирует точные версии и хеши всего дерева зависимостей, а npm ci ставит ровно то, что в нём записано, и падает, если lock-файл не совпадает с package.json. Это даёт воспроизводимость. Но если версию удалили из реестра, lock-файл честно попросит именно её, и установка всё равно упадёт.

2. Свой прокси-реестр или кэш пакетов

От исчезновения пакета защищает только копия, которая уже лежит у вас: прокси-реестр вроде Nexus, Artifactory, Verdaccio или реестр пакетов GitLab. Он кэширует всё, что проекты когда-то скачали, и отдаёт это, даже если в публичном реестре пакета больше нет. Подключается одной строкой в .npmrc:

registry=https://nexus.example.kz/repository/npm-proxy/

Бонусом — сборки быстрее и не зависят от доступности внешнего реестра. С Docker Hub та же история: без своего зеркала сборки упираются в лимит скачиваний toomanyrequests, а удалённый тег образа превращается в ImagePullBackOff.

3. Знайте своё дерево зависимостей

Прямых зависимостей у проекта десятки, а в node_modules — сотни и тысячи пакетов. npm ls, npm explain и регулярный аудит показывают, от чего вы на самом деле зависите. Для крошечных функций стоит спросить, нужна ли зависимость вообще: сегодня то же, что делал left-pad, умеет встроенный String.prototype.padStart.

4. Внешний реестр — часть вашей цепочки поставок

Пакет может исчезнуть, как left-pad, а может быть подменён, как скрипт в истории с Codecov. В обоих случаях сборка зависит от того, что вы не контролируете. Закрепляйте версии и хеши, держите копии и проверяйте, что скачиваете.

Что проверить у себя завтра:

  • ставит ли CI зависимости через npm ci по lock-файлу;
  • откуда CI скачивает пакеты и образы и что будет, если публичный реестр недоступен;
  • сколько пакетов в node_modules и какие из них тянутся ради одной функции.

Реши сам

Вопрос 1. Через десять минут появился left-pad 1.0.0 с тем же кодом. Почему сборки продолжали падать?

  1. В новой версии была ошибка
  2. Цепочки зависимостей запрашивали версию 0.0.3, а их диапазон версий не допускал 1.0.0
  3. npm заблокировал новую версию
  4. Мешал кэш CDN
Показать ответ

Верный ответ — 2. Менеджер пакетов ищет версию, подходящую под диапазон, а не любую с тем же именем. Поэтому npm пришлось вернуть именно 0.0.3.

Вопрос 2. Что защитит сборку, если нужная версия пакета исчезнет из публичного реестра?

  1. Lock-файл сам по себе
  2. Собственный прокси-реестр или кэш, где уже лежат скачанные версии
  3. npm install без lock-файла
  4. Обновление Node.js
Показать ответ

Верный ответ — 2. Lock-файл говорит, какую версию взять, но скачивать её всё равно откуда-то надо. Прокси-реестр хранит копию и отдаёт её, даже если в публичном реестре пакета уже нет.

Частые вопросы

Что такое left-pad? Маленький пакет npm с одной функцией: дополнить строку слева пробелами или другим символом до нужной длины. В index.js версии 0.0.3 — 11 непустых строк.

Почему удаление одного маленького пакета сломало столько проектов? Он был транзитивной зависимостью популярных инструментов, в том числе babel и atom через line-numbers. Проекты, которые никогда не подключали left-pad сами, получали его через цепочку зависимостей и не могли установить.

Можно ли сейчас удалить пакет из npm, как в 2016 году? Нет, если от него зависят другие пакеты в публичном реестре. Пакет старше 72 часов можно удалить, только если у него нет зависимых, меньше 300 скачиваний за неделю и один владелец. В остальных случаях npm предлагает npm deprecate.

Защищает ли package-lock.json от такой ситуации? Частично. Он гарантирует, что установится та же версия с тем же хешем, но саму версию надо откуда-то скачать. От исчезновения пакета защищает прокси-реестр или кэш пакетов.

Пройти похожий сбой самому

В симуляторе DevOps KAI Lab разбор left-pad стоит в четвёртом месяце, посвящённом Git. А в шестом, про CI/CD, есть тикет о пайплайне, который тратит шесть минут на npm ci, — хороший повод разобраться, как кэш зависимостей ускоряет сборки и делает их независимее от внешнего реестра.

Начать можно бесплатно: тест навыков и первые задания первого месяца.

Источники

  • npm Blog, Isaac Schlueter. kik, left-pad, and npm, 23.03.2016 — хронология, 273 пакета, 2,5 часа, восстановление 0.0.3.
  • npm Docs. npm Unpublish Policy — действующие правила удаления пакетов.
  • unpkg. left-pad@0.0.3/index.js — исходный код версии 0.0.3.

Читать дальше