Топ факапов Циан
Всем добра!
Меня зовут Никита, я тимлид команды инженеров Циан. Одной из моих обязанностей в компании является снижение количества инцидентов, связанных с инфраструктурой на проде, до нуля.
То, о чем пойдет речь далее, принесло нам много боли, и цель этой статьи — не дать другим людям повторить наших ошибок или хотя бы минимизировать их влияние.
Преамбула
Давным-давно, когда Циан состоял из монолитов, и никаких намеков на микросервисы еще не было, мы измеряли доступность ресурса проверкой 3–5 страниц.
Отвечают — все хорошо, не отвечают в течение длительного времени — алерт. Сколько времени они должны не работать для того, чтобы это считалось инцидентом, — решали люди на совещаниях. Команда инженеров всегда участвовала в расследовании инцидента. Когда расследование было закончено, писали постмортем — своеобразный отчет на почту в формате: что было, сколько длилось, что сделали в моменте, что сделаем в будущем.
Основные страницы сайта или как мы понимаем, что пробили дно
Для того чтобы можно было как-то понимать приоритет ошибки, мы выделили наиболее критичные для бизнес-функционала страницы сайта. По ним мы считаем количество успешных/неуспешных запросов и таймаутов. Таким образом мы измеряем uptime.
Допустим, мы выяснили, что есть ряд суперважных разделов сайта, которые отвечают за основной сервис — поиск и подачу объявлений. Если количество запросов, которые завершились ошибкой, превышает 1%, — это критический инцидент. Если в течение 15 минут в прайм-тайм процент ошибок превышает 0,1% — то это также считается критическим инцидентом. Эти критерии покрывают бОльшую часть инцидентов, остальные выходят за рамки этой статьи.
Топ лучших инцидентов Циан
Итак, мы точно научились определять тот факт, что инцидент случился.
Теперь каждый инцидент у нас детально описан и отражен в эпике Jira. К слову: для этого мы завели отдельный проект, назвали его FAIL — в нем можно создавать только эпики.
Если собрать все фейлы за последние несколько лет, то лидируют:
- инциденты, связанные с mssql;
- инциденты, вызванные внешними факторами;
- ошибки админа.
Остановимся более детально на ошибках админов, а также на некоторых других интересных фейлах.
Пятое место — «Наводим порядок в DNS»
Это был ненастный вторник. Решили мы навести порядок в DNS-кластере.
Захотелось перевести внутренние dns-серверы c bind на powerdns, выделив под это полностью отдельные серверы, где кроме dns ничего нет.
Разместили мы по одному dns-серверу в каждой локации наших ДЦ, и наступил момент переезда зон из bind в powerdns и переключения инфраструктуры на новые серверы.
В самый разгар переезда из всех серверов, которые были указаны в локальных кэширующих bind-ах на всех серверах, остался только один, который был в дата-центре в Санкт-Петербурге. Этот ДЦ изначально был задекларирован как некритичный для нас, но внезапно стал single point of failure.
Как раз в такой период переезда упал канал между Москвой и Санкт-Петербургом. Мы фактически остались без DNS на пять минут и поднялись, когда хостер устранил неполадки.
Выводы:
Если раньше мы пренебрегали внешними факторами во время подготовки к работам, то сейчас их тоже включили в список того, к чему готовимся. И теперь стремимся к тому, что все компоненты зарезервированы n-2, а на время работ мы можем опускать этот уровень до n-1.
- Во время составления плана действий отмечайте пункты, где сервис может упасть, и продумывайте сценарий, где все пошло «хуже некуда», заранее.
- Распределяйте внутренние dns-серверы по разным геолокациям/датацентрам/стойкам/коммутаторам/вводам.
- На каждом сервере ставьте локальный кеширующий dns-сервер, который перенаправляет запросы на основные dns-серверы, а в случае его недоступности будет отвечать из кеша.
Четвертое место — «Наводим порядок в Nginx»
В один прекрасный день наша команда решила, что «хватит это терпеть», и запустился процесс рефакторинга конфигов nginx. Основная цель — привести конфиги к интуитивно понятной структуре. Раньше все было «исторически сложившимся» и логики в себе никакой не несло. Теперь каждый server_name вынесли в одноименный файл и распределили все конфиги по папкам. К слову — конфиг содержит в себе 253949 строк или 7836520 символа и занимает почти 7 мегабайт. Верхний уровень структуры:
├── access
│ ├── allow.list
…
│ └── whitelist.conf
├── geobase
│ ├── exclude.conf
…
│ └── geo_ip_to_region_id.conf
├── geodb
│ ├── GeoIP.dat
│ ├── GeoIP2-Country.mmdb
│ └── GeoLiteCity.dat
├── inc
│ ├── error.inc
…
│ └── proxy.inc
├── lists.d
│ ├── bot.conf
…
│ ├── dynamic
│ └── geo.conf
├── lua
│ ├── cookie.lua
│ ├── log
│ │ └── log.lua
│ ├── logics
│ │ ├── include.lua
│ │ ├──…
│ │ └── utils.lua
│ └── prom
│ ├── stats.lua
│ └── stats_prometheus.lua
├── map.d
│ ├── access.conf
│ ├── …
│ └── zones.conf
├── nginx.conf
├── robots.txt
├── server.d
│ ├── cian.ru
│ │ ├── cian.ru.conf
│ │ ├──…
│ │ └── my.cian.ru.conf
├── service.d
│ ├──…
│ └── status.conf
└── upstream.d
├── cian-mcs.conf
├──…
└── wafserver.conf
Стало значительно лучше, но в процессе переименования и распределения конфигов часть из них имела неправильное расширение и не попала в директиву include *.conf. Как следствие — часть хостов стала недоступна и возвращала 301 на главную. Из-за того что код ответа был не 5хх/4хх, это заметили не сразу, а лишь под утро. После этого мы начали писать тесты на проверку инфраструктурных компонентов.
Выводы:
- Правильно структурируйте конфиги (не только nginx) и продумывайте структуру на раннем этапе проекта. Так вы сделаете их более понятными команде, что в свою очередь уменьшит ТТМ.
- Для некоторых инфраструктурных компонентов пишите тесты. Например: проверка, что все ключевые server_name отдают правильный статус, + тело ответа. Достаточно будет иметь под рукой просто несколько скриптов, которые проверяют основные функции компонента, чтобы судорожно не вспоминать в 3 часа ночи, что же еще надо проверить.
Третье место — «Внезапно закончилось место в Cassandra»
Данные планомерно росли, и все было хорошо до того момента, когда в кластере Cassandra начали падать repair больших кейспейсов, потому что на них не может отработать compaction.
В один ненастный день кластер почти превратился в тыкву, а именно:
- места оставалось около 20% суммарно по кластеру;
- полноценно добавить ноды нельзя, потому что не проходит cleanup после добавления ноды из-за нехватки места на разделах;
- производительность понемногу падает, так как не работает компакция;
- кластер работает в аварийном режиме.
Выход — добавили еще 5 нод без cleanup, после чего начали планомерно выводить из кластера и снова вводить, как пустые ноды, на которых закончилось место. Времени затрачено сильно больше, чем хотелось бы. Был риск частичной или полной недоступности кластера.
Выводы:
- На всех серверах cassandra должно быть занято не больше 60% места на каждом разделе.
- Загружены они должны быть не более чем на 50% по cpu.
- Не стоит забивать на capacity planning и продумывать его надо для каждого компонента, исходя из его специфики.
- Чем больше нод в кластере — тем лучше. Серверы, содержащие небольшой объем данных, быстрее переналиваются, и такой кластер легче реанимировать.
Второе место — «Исчезли данные из consul key-value storage»
Для service discovery мы, как и многие, используем consul. Но у нас его key-value используется еще и для blue-green выкладки монолита. Там хранится информация об активных и неактивных апстримах, которые меняются местами во время деплоя. Для этого был написан сервис деплоя, который взаимодействовал с KV. В какой-то момент данные из KV пропали. Восстановили по памяти, но с рядом ошибок. Как следствие — при выкладке нагрузка на апстримы распределилась неравномерно, а мы получили много ошибок 502 из-за перегрузки бекендов по CPU. В итоге переехали с consul KV на postgres, откуда удалить их уже не так просто.
Выводы:
- Сервисы без какой-либо авторизации не должны содержать в себе критичных для работы сайта данных. Например, если у вас нет авторизации в ES — лучше бы запретить доступ на уровне сети отовсюду, где он не нужен, оставить только необходимые, а также сделать action.destructive_requires_name: true.
- Отрабатывайте механизм резервного копирования и восстановления заранее. Например, заранее сделайте скрипт (к примеру, на python), который умеет и бекапить и восстанавливать.
Первое место — «Капитан неочевидность»
В какой-то момент мы заметили неравномерное распределение нагрузки на апстримы nginx в случаях, когда в бекенде было 10+ серверов. Из-за того что round-robin направлял запросы с 1 по последний апстрим по порядку, и каждый релоад nginx начинал сначала, на первые апстримы всегда приходилось больше запросов, чем на остальные Как следствие — они работали медленнее и страдал весь сайт. Это становилось все более заметным по мере увеличения количества трафика. Просто обновить nginx для включения random не вышло — надо переделывать кучу lua кода, который не взлетел на версии 1.15 (в тот момент). Пришлось пропатчить наш nginx 1.14.2, внедрив в него поддержку random. Это решило проблему. Этот баг побеждает в номинации «капитан неочевидность».
Выводы:
Было очень интересно и увлекательно исследовать этот баг).
- Выстройте мониторинг так, чтобы он помогал находить подобные флуктуации быстро. Например, можно использовать ELK для того, чтобы наблюдать за rps на каждый backend каждого upstream, следить за их временем ответа с точки зрения nginx. В данном случае это нам и помогло выявить проблему.
В результате бОльшей части фейлов можно было бы избежать при более скрупулезном подходе к тому, что делаешь. Надо всегда помнить о законе Мерфи:
Anything that can go wrong will go wrong,
и строить компоненты, руководствуясь им.