Топ факапов Циан

zyiu-qkcptbeezgf_c5adiagete.png
Всем добра!  
Меня зовут Никита, я тимлид команды инженеров Циан. Одной из моих обязанностей в компании является снижение количества инцидентов, связанных с инфраструктурой на проде, до нуля.
То, о чем пойдет речь далее, принесло нам много боли, и цель этой статьи — не дать другим людям повторить наших ошибок или хотя бы минимизировать их влияние. 
Преамбула
 
Давным-давно, когда Циан состоял из монолитов, и никаких намеков на микросервисы еще не было, мы измеряли доступность ресурса проверкой 3–5 страниц. 
Отвечают — все хорошо, не отвечают в течение длительного времени — алерт. Сколько времени они должны не работать для того, чтобы это считалось инцидентом, — решали люди на совещаниях. Команда инженеров всегда участвовала в расследовании инцидента. Когда расследование было закончено, писали постмортем — своеобразный отчет на почту в формате: что было, сколько длилось, что сделали в моменте, что сделаем в будущем. 

Основные страницы сайта или как мы понимаем, что пробили дно 

Для того чтобы можно было как-то понимать приоритет ошибки, мы выделили наиболее критичные для бизнес-функционала страницы сайта. По ним мы считаем количество успешных/неуспешных запросов и таймаутов. Таким образом мы измеряем uptime. 

Допустим, мы выяснили, что есть ряд суперважных разделов сайта, которые отвечают за основной сервис — поиск и подачу объявлений. Если количество запросов, которые завершились ошибкой, превышает 1%, — это критический инцидент. Если в течение 15 минут в прайм-тайм процент ошибок превышает 0,1% — то это также считается критическим инцидентом. Эти критерии покрывают бОльшую часть инцидентов, остальные выходят за рамки этой статьи.
otmwk90qyl-jcomorjhkofebrgq.png

Топ лучших инцидентов Циан

Итак, мы точно научились определять тот факт, что инцидент случился. 
Теперь каждый инцидент у нас детально описан и отражен в эпике 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 мегабайт. Верхний уровень структуры:  

Nginx structure

├── 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 после добавления ноды из-за нехватки места на разделах;
  • производительность понемногу падает, так как не работает компакция;  
  • кластер работает в аварийном режиме.


lpahie7bann64sajoibumpqbr5g.png

Выход — добавили еще 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,
и строить компоненты, руководствуясь им. 

© Habrahabr.ru