Как подключить Cloudflare

PIPEFAIL: как отсутствующая опция оболочки замедлила работу Cloudflare

В Cloudflare мы привыкли быть самыми быстрыми в мире. Однако в декабре прошлого года Cloudflare работал медленно примерно 30 минут. Между 20:10 и 20:40 UTC 16 декабря 2021 года веб-запросы, обслуживаемые Cloudflare, перед обработкой искусственно задерживались на срок до пяти секунд. В этом посте рассказывается о том, как отсутствующая опция оболочки под названием «pipefail» замедлила работу Cloudflare.

Фон

Прежде чем мы сможем рассказать эту историю, нам нужно познакомить вас с некоторыми из ее персонажей.

PIPEFAIL: как отсутствующая опция оболочки замедлила работу Cloudflare

Cloudflare Линия фронта защищает миллионы пользователей от крупнейших когда-либо зарегистрированных атак. Эта защита организована дополнительной службой под названием dosd, который анализирует трафик и ищет атаки. Когда dosd обнаруживает атаку, он предоставляет Front Line список отпечатков атак, которые описывают, как Front Line может сопоставить и заблокировать атакующий трафик.

Экземпляры dosd работают на каждом сервере Cloudflare, и они взаимодействуют друг с другом, используя одноранговую сетку для выявления вредоносных шаблонов трафика. Эта децентрализованная структура позволяет dosd выполнять анализ с гораздо большей точностью, чем это возможно в централизованной системе, но ее масштаб также накладывает некоторые строгие требования к производительности. Для выполнения этих требований нам необходимо предоставить dosd с очень быстрым доступом к большим объемам данных конфигурации, что естественно означает, что dosd зависит от Ртуть. Cloudflare разработала Quicksilver для управления данными конфигурации и репликации их по всему миру за миллисекунды, что позволяет получать к ним доступ таким службам, как dosd в микросекундах.

PIPEFAIL: как отсутствующая опция оболочки замедлила работу Cloudflare

Одна часть данных конфигурации, которая dosd потребности исходят из API адресации, который является нашей официальной службой управления IP-адресами. Адресные данные, которые он предоставляет, важны, потому что dosd использует его, чтобы понять, какой трафик ожидается на конкретных IP-адресах. Поскольку адресация данных не меняется очень часто, мы используем простой Работа хрона Kubernetes запрашивать его каждые 10 минут после каждого часа и записывать его в Quicksilver, обеспечивая эффективный доступ к нему dosd.

В этом контексте давайте рассмотрим изменение, внесенное 16 декабря, которое в конечном итоге привело к замедлению.

Изменение

Примерно раз в неделю все наши исправления ошибок и улучшения производительности в кодовой базе Front Line публикуются в сети. 16 декабря команда Front Line выпустила исправление для тонкой ошибки в том, как код обрабатывал сжатие при наличии Cache-Control: no-transform заголовок. К сожалению, команда довольно быстро поняла, что это исправление на самом деле сломало некоторых клиентов, которые начали в зависимости из-за этого ошибочного поведения, поэтому команда решила откатить выпуск и работать с этими клиентами над исправлением проблемы.

PIPEFAIL: как отсутствующая опция оболочки замедлила работу Cloudflare

Вот график, показывающий прогресс отката. Хотя большинство выпусков и откатов полностью автоматизированы, этот конкретный откат нужно было выполнять вручную из-за его срочности. Поскольку это был ручной откат, SRE решили выполнить его двумя партиями в качестве меры безопасности. Первая партия была отправлена ​​в наши меньшие центры обработки данных уровня 2 и 3, а вторая партия — в наши более крупные центры обработки данных уровня 1.

SRE начали первую партию в 19:25 UTC, и она завершилась примерно через 30 минут. Затем, убедившись, что проблем нет, в 20:10 запустили вторую партию. Вот тогда и началось замедление.

Замедление

Через несколько минут после запуска второй партии откатов начали срабатывать оповещения. «Уровень трафика падает». «Загрузка ЦП падает». «Инцидент P0 был автоматически объявлен». Время не могло быть случайным. Каким-то образом развертывание заведомо исправного кода, которое было ограничено подмножеством сети и которое было успешно выполнено только 40 минут назад, оказалось причиной глобальной проблемы.

Инцидент P0 — это чрезвычайная ситуация «все руки на палубе», поэтому десятки инженеров Cloudflare быстро начали оценивать влияние на свои службы и проверять свои теории о первопричине. Откат был приостановлен, но это не решило проблему. Затем, примерно через 10 минут после начала инцидента, моя команда — команда DOS — получила тревожное предупреждение: «dosd не работает на многочисленных серверах». Перед тем, как это оповещение было запущено, мы выясняли, не было ли замедление вызвано атакой, но это требовало нашего немедленного внимания.

Основываясь на сервисных журналах, мы смогли увидеть, что dosd был в панике, потому что данные адресации клиентов в Quicksilver были каким-то образом повреждены. Помните: данные в этом ключе Quicksilver важны. Без этого, dosd не мог больше делать правильный выбор, поэтому отказался продолжать.

Как только мы поняли, что данные адресации были повреждены, нам пришлось выяснить, как они были повреждены, чтобы мы могли это исправить. Ответ оказался довольно очевидным: ключ Quicksilver был совершенно пуст.

Следуя старой поговорке — «вы пробовали перезапустить его?» — мы решили вручную перезапустить задание cron Kubernetes, заполняющее этот ключ, и посмотреть, что произошло. В 20:40 UTC задание cron было запущено вручную. Через несколько секунд после завершения dosd снова начал работать, и уровень трафика начал возвращаться к норме. Мы подтвердили, что ключ Quicksilver больше не пуст, и инцидент исчерпан.

Последствия

Несмотря на решение проблемы, мы так и не поняли, что только что произошло.

Почему ключ Quicksilver был пуст?

Нам нужно было срочно выяснить, как в этот ключ Quicksilver было записано пустое значение, потому что, насколько мы знали, это могло произойти снова в любой момент.

Мы начали с просмотра задания cron Kubernetes, в котором обнаружилась ошибка:

PIPEFAIL: как отсутствующая опция оболочки замедлила работу Cloudflare

Это задание cron реализовано с помощью небольшого сценария Bash. Если вы не знакомы с Bash (особенно с конвейерной обработкой оболочки), вот что он делает:

Во-первых, dos-make-addr-conf исполняемый файл работает. Его работа состоит в том, чтобы запрашивать у Addressing API различные биты данных JSON и сериализовать их в документ Toml, который записывается в config.toml. После этого этот Томл передается в качестве входных данных в dosctl исполняемый файл, работа которого состоит в том, чтобы просто записать его в ключ Quicksilver, называемый template_vars.

Можете ли вы обнаружить ошибку? Вот подсказка: что произойдет, если dos-make-addr-conf по какой-то причине не работает и завершается с ненулевым кодом ошибки? Оказывается, конвейер оболочки по умолчанию игнорирует код ошибки и продолжает выполняться! Это означает, что выход dos-make-addr-conf (который может быть пустым) безоговорочно записывается в dosctl и используется как значение template_vars ключ, независимо от того, dos-make-addr-conf удалось или не удалось.

30 лет назад, когда первые пользователи оболочки Bourne были обожжены этой проблемой, была введена опция оболочки под названием «pipefail». Включение этого параметра изменяет поведение оболочки таким образом, что при сбое любой команды в последовательности конвейеров происходит сбой всего конвейера. Однако этот параметр не включен по умолчанию, поэтому рекомендуется, чтобы все сценарии запускались с включением этого (и некоторых других) параметров.

Вот исправленная версия этого задания cron:

PIPEFAIL: как отсутствующая опция оболочки замедлила работу Cloudflare

Эта ошибка была особенно коварной, потому что dosd на самом деле пытались изящно обработать случай, когда этот ключ Quicksilver содержал недопустимый файл Toml. Однако пустая строка является вполне допустимым документом Toml. Если в этот ключ Quicksilver вместо пустой строки было случайно записано сообщение об ошибке, то dosd отклонил бы обновление и продолжил бы использовать предыдущее значение.

Почему это заставило линию фронта замедлиться?

Мы выяснили, как пустой ключ можно записать в Quicksilver, и были уверены, что это больше не повторится. Однако нам все еще нужно было понять, как этот пустой ключ стал причиной такого серьезного инцидента.

Как я упоминал ранее, «Линия фронта» опирается на dosd рассказать ему, как смягчить атаки, но это не зависит от dosd непосредственно для обслуживания запросов. Вместо этого раз в несколько секунд Front Line асинхронно спрашивает dosd для новых отпечатков атак и сохраняет их в кеше в памяти. К этому кешу обращаются при обслуживании каждого запроса, и если dosd когда-либо не сможет предоставить свежие отпечатки атак, вместо них будут продолжать использоваться устаревшие отпечатки пальцев. Так как же это могло вызвать воздействие, которое мы видели?

PIPEFAIL: как отсутствующая опция оболочки замедлила работу Cloudflare

В рамках процесса отката код Front Line нужно было перезагрузить. Повторная загрузка этого кода неявно очищает кэши в памяти, включая данные об отпечатках атак из dosd. В следующий раз, когда запрос попытался обратиться к кешу, уровень кэширования понял, что у него нет отпечатков атак, которые можно было бы вернуть, и произошел «промах кеша».

Чтобы справиться с промахом кэша, уровень кэширования пытался обратиться к dosd, и это когда замедление произошло. Пока кеширующий слой ждал dosd чтобы ответить, он заблокировал выполнение всех ожидающих запросов. С dosd не работал, попытка в конечном итоге истекла через пять секунд, когда уровень кэширования сдался. Но в то же время каждый ожидающий запрос застрял в ожидании тайм-аута. Как только это произошло, все ожидающие запросы, которые стояли в очереди в течение пятисекундного периода ожидания, разблокировались и, наконец, получили возможность выполняться. Этот цикл повторялся снова и снова каждые пять секунд на каждом сервере, пока не dosd провал был устранен.

Чтобы вызвать это замедление, не только dosd должны были потерпеть неудачу, но кэш-память Front Line также должна была быть очищена в то же время. Если dosd произошел сбой, но кэш Front Line не был очищен, то устаревшие отпечатки атак остались бы в кеше, и обработка запросов не пострадала бы.

Почему первый откат не вызвал эту проблему?

Эти две партии откатов были выполнены путем принуждения серверов к запуску Salt highstate. Когда выполнялся каждый пакет, тысячи серверов одновременно запускали highstate. Процесс highstate включает в себя, среди прочего, обращение к Addressing API для получения различных битов адресной информации клиента.

Первый откат начался в 19:25 UTC, а второй — через 45 минут в 20:10. Помните, как я упоминал, что наша задача cron Kubernetes запускается только на 10-й минуте каждого часа? В 21:10 — как раз в то время, когда началось выполнение нашего задания cron — тысячи серверов также начали перегружаться, заваливая Addressing API запросами. Все эти запросы были поставлены в очередь и в конце концов обслужено, но API адресации потребовалось несколько минут, чтобы справиться с отставанием. Этой задержки было достаточно, чтобы время ожидания нашей работы cron истекло, и из-за ошибки «сбой конвейера» непреднамеренно сбился ключ Quicksilver, за обновление которого он отвечал.

Чтобы вызвать ошибку «pipefail», нам нужно было не только завалить Addressing API запросами, но и сделать это ровно через 10 минут после начала часа. Если бы SRE начали вторую партию откатов на несколько минут раньше или позже, эта ошибка так и осталась бы бездействующей.

Уроки выучены

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

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

Что такое Cloudflare