Сбой 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:30 | npm фиксирует сотни ошибок в минуту: пакеты, их зависимые и зависимые зависимых не могут скачать left-pad |
| В течение 10 минут | Кэмерон Уэстленд публикует функционально идентичный left-pad 1.0.0. Сборкам, которым нужна 0.0.3, это не помогает |
| 16:05 | npm объявляет, что вернёт версию 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 с тем же кодом. Почему сборки продолжали падать?
- В новой версии была ошибка
- Цепочки зависимостей запрашивали версию 0.0.3, а их диапазон версий не допускал 1.0.0
- npm заблокировал новую версию
- Мешал кэш CDN
Показать ответ
Верный ответ — 2. Менеджер пакетов ищет версию, подходящую под диапазон, а не любую с тем же именем. Поэтому npm пришлось вернуть именно 0.0.3.
Вопрос 2. Что защитит сборку, если нужная версия пакета исчезнет из публичного реестра?
- Lock-файл сам по себе
- Собственный прокси-реестр или кэш, где уже лежат скачанные версии
npm installбез lock-файла- Обновление 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.