
Witam wszystkich!
Nazywam się Nikita, jestem liderem zespołu inżynierów w Cian. Jednym z moich obowiązków w firmie jest doprowadzenie liczby incydentów związanych z infrastrukturą w produkcji do zera.
Temat, który poruszymy dalej, przysporzył nam wielu problemów, a celem tego artykułu jest zapobieżenie powtórzeniu naszych błędów lub przynajmniej zminimalizowanie ich wpływu.
Wstęp
Za dawnych czasów, kiedy Cian składał się z monolitów i nie było żadnych wskazówek o mikrousługach, mierzyliśmy dostępność zasobów sprawdzając 3–5 stron.
Jeśli odpowiedzi były - wszystko dobrze, jeśli nie było odpowiedzi przez dłuższy czas - alert. Ile czasu powinny nie działać, aby to uznawano za incydent, decydowali ludzie na spotkaniach. Zespół inżynierów zawsze uczestniczył w badaniu incydentu. Gdy badanie było zakończone, pisaliśmy post mortem - swoisty raport na e-mail w formacie: co się stało, jak długo trwało, co zrobiliśmy w danym momencie, co zrobimy w przyszłości.
Główne strony serwisu lub jak rozumiemy, że dotknęliśmy dna
Aby jakoś zrozumieć priorytet błędu, wydzieliliśmy najważniejsze dla funkcjonalności biznesowej strony serwisu. Na ich podstawie liczymy liczbę udanych/niedanych żądań i timeoutów. W ten sposób mierzymy uptime.
Powiedzmy, że ustaliliśmy, że są pewne superważne sekcje serwisu, które odpowiadają za główną usługę - wyszukiwanie i składanie ogłoszeń. Jeśli liczba żądań, które zakończyły się błędem, przekracza 1% - to jest to krytyczny incydent. Jeśli w ciągu 15 minut w godzinach szczytu procent błędów przekracza 0,1% - to również uznaje się za krytyczny incydent. Te kryteria pokrywają większość incydentów, pozostałe wykraczają poza ramy tego artykułu.

Top najlepszych incydentów w Cian
Zatem dokładnie nauczyliśmy się określać, że incydent się zdarzył.
Teraz każdy incydent jest szczegółowo opisany i odzwierciedlony w epiku Jira. Swoją drogą, dla tego celu założyliśmy osobny projekt, nazwealiśmy go FAIL - można w nim tworzyć tylko epiki.
Jeśli zbierzemy wszystkie porażki z ostatnich kilku lat, to dominują:
- incydenty związane z mssql;
- incydenty spowodowane czynnikami zewnętrznymi;
- błędy administratorów.
Skupimy się bardziej szczegółowo na błędach administratorów oraz na innych interesujących porażkach.
Pięć miejsc - 'Porządkujemy DNS'
To był pochmurny wtorek. Postanowiliśmy uporządkować klaster DNS.
Zdecydowaliśmy się przenieść wewnętrzne serwery DNS z bind na powerdns, wydzielając na to zupełnie oddzielne serwery, na których nie ma nic poza DNS.
Umieściliśmy po jednym serwerze DNS w każdej lokalizacji naszych data center, a nadszedł czas przeniesienia stref z bind na powerdns oraz przełączenia infrastruktury na nowe serwery.
W trakcie przenoszenia z wszystkich serwerów, które były wskazane w lokalnych pamięciach cache bind na wszystkich serwerach, pozostał tylko jeden, który znajdował się w data center w Sankt Petersburgu. To data center początkowo zostało zadeklarowane jako niekrytyczne dla nas, ale nagle stało się single point of failure.
W takim właśnie okresie przeniesienia padł kanał między Moskwą a Sankt Petersburgiem. Faktycznie zostaliśmy bez DNS przez pięć minut i wznowiliśmy działanie, gdy powinien umieć to zrobić) usunięto usterki.
Wnioski:
Jeśli wcześniej lekceważyliśmy zewnętrzne czynniki podczas przygotowań do prac, to teraz również zostały one uwzględnione w liście rzeczy, do których się przygotowujemy. Dążymy do tego, aby wszystkie komponenty były zarezerwowane w systemie n-2, a podczas prac możemy obniżyć ten poziom do n-1.
- Podczas tworzenia planu działania zaznaczaj punkty, w których usługa może spaść, i wymyślaj scenariusz, w którym wszystko idzie "najgorzej jak to możliwe" z wyprzedzeniem.
- Rozdzielaj wewnętrzne serwery DNS po różnych geolokalizacjach/data center/szafach/przełącznikach/łączeniach.
- Na każdym serwerze zainstaluj lokalny serwer DNS cache, który przekierowuje zapytania do głównych serwerów DNS, a w przypadku jego niedostępności odpowiada z pamięci cache.
Czwarte miejsce - "Porządkujemy Nginx"
Pewnego pięknego dnia nasz zespół postanowił, że "dość tego znosimy" i rozpoczął proces refaktoryzacji konfiguracji Nginx. Głównym celem było nadanie konfiguracjom intuicyjnej struktury. Wcześniej wszystko było "historycznie ugruntowane" i nie miało żadnej logiki. Teraz każdy server_name został przeniesiony do pliku o tej samej nazwie i wszystkie konfiguracje zostały rozdzielone na foldery. Na marginesie — konfiguracja zawiera 253949 wierszy lub 7836520 znaków i zajmuje prawie 7 megabajtów. Górny poziom struktury:
Struktura Nginx
├── dostęp
│ ├── 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.confZdecydowanie stało się lepiej, ale podczas zmiany nazw i rozdzielania konfiguracji część z nich miała niewłaściwe rozszerzenie i nie trafiła do dyrektywy include *.conf. W rezultacie część hostów stała się niedostępna i zwracała 301 na stronę główną. Ponieważ kod odpowiedzi nie był 5xx/4xx, zauważono to dopiero nad ranem. Po tym zaczęliśmy pisać testy do sprawdzenia komponentów infrastrukturalnych.
Wnioski:
- Poprawnie strukturyzuj konfiguracje (nie tylko nginx) i przemyśl ich strukturę na wczesnym etapie projektu. Dzięki temu będą bardziej zrozumiałe dla zespołu, co z kolei zmniejszy czas wprowadzenia na rynek (TTM).
- Dla niektórych komponentów infrastrukturalnych pisz testy. Na przykład: sprawdzanie, czy wszystkie kluczowe server_name zwracają właściwy status oraz treść odpowiedzi. Wystarczy mieć kilka skryptów, które sprawdzają podstawowe funkcje komponentu, aby nie trzeba było w środku nocy nerwowo przypominać sobie, co jeszcze należy sprawdzić.
Trzecie miejsce — "Nagle skończyło się miejsce w Cassandra"
Dane stopniowo rosły i wszystko było dobrze, aż do momentu, gdy w klastrze Cassandra zaczęły padać naprawy dużych keyspace'ów, ponieważ nie mogła działać kompakcja.
Pewnego deszczowego dnia klaster niemal zamienił się w dynię, a dokładnie:
- pozostało około 20% łącznej przestrzeni w klastrze;
- nie można w pełni dodać węzłów, ponieważ nie przechodzi czyszczenie po dodaniu węzła z powodu braku miejsca na partycjach;
- wydajność stopniowo spada, ponieważ kompakcja nie działa;
- klaster działa w trybie awaryjnym.

Wyjście — dodano jeszcze 5 węzłów bez czyszczenia, po czym zaczęliśmy systematycznie wyprowadzać z klastra i ponownie wprowadzać jako puste węzły, na których skończyło się miejsce. Czas poświęcony jest znacznie większy niż byśmy chcieli. Istniało ryzyko częściowej lub całkowitej niedostępności klastra.
Wnioski:
- Na wszystkich serwerach Cassandra nie powinno być zajęte więcej niż 60% miejsca na każdej partycji.
- Powinny być załadowane nie więcej niż w 50% pod względem CPU.
- Nie należy lekceważyć planowania pojemności i trzeba je przemyśleć dla każdego komponentu, biorąc pod uwagę jego specyfikę.
- Im więcej węzłów w klastrze — tym lepiej. Serwery zawierające małą ilość danych szybciej się odnawiają, a taki klaster łatwiej przywrócić do działania.
Drugie miejsce — „Zniknęły dane z magazynu kluczy wartości Consul”
Do usług odkrywania serwisów używamy, jak wielu, Consul. Ale nasz magazyn kluczy wartości jest wykorzystywany także do wdrożeń blue-green monolitu. Przechowuje informacje o aktywnych i nieaktywnych upstreamach, które zmieniają się miejscami podczas wdrożenia. W tym celu napisano usługę wdrożenia, która współdziałała z magazynem KV. W pewnym momencie dane z KV zniknęły. Odzyskaliśmy według pamięci, ale z licznymi błędami. W konsekwencji — podczas wdrożenia obciążenie na upstreamach rozłożyło się nierównomiernie, a otrzymaliśmy wiele błędów 502 z powodu przeciążenia backendów pod względem CPU. Ostatecznie przenieśliśmy się z magazynu KV Consul do Postgresa, skąd ich usunięcie nie jest już tak proste.
Wnioski:
- Usługi bez jakiejkolwiek autoryzacji nie powinny zawierać krytycznych danych dla działania strony. Na przykład, jeśli nie ma autoryzacji w ES — lepiej byłoby zablokować dostęp na poziomie sieci z wszędzie, gdzie nie jest potrzebny, pozostawić tylko niezbędne, a także ustawić action.destructive_requires_name: true.
- Zastosuj mechanizm tworzenia kopii zapasowych i przywracania zawczasu. Na przykład, przygotuj skrypt (na przykład w Pythonie), który potrafi zarówno tworzyć kopie zapasowe, jak i przywracać.
Pierwsze miejsce — „Kapitan Nieoczywistość”
W pewnym momencie zauważyliśmy nierównomierne rozłożenie obciążenia na upstreamy Nginx, gdy w backendzie było 10+ serwerów. Ponieważ round-robin kierował zapytania od pierwszego do ostatniego upstreamu w kolejności, a każdy reload Nginx zaczynał od nowa, na pierwsze upstreamy zawsze przypadało więcej zapytań niż na pozostałe. W rezultacie pracowały one wolniej i ucierpiała cała strona. Stawało się to coraz bardziej zauważalne wraz ze wzrostem ruchu. Proste zaktualizowanie Nginx, aby włączyć random, nie wystarczyło — trzeba było przerobić masę kodu Lua, który nie działał w wersji 1.15 (w tym czasie). Musieliśmy poprawić naszego Nginx 1.14.2, wprowadzając wsparcie dla random. To rozwiązało problem. Ten błąd zdobywa nagrodę w kategorii „kapitan nieoczywistość”.
Wnioski:
To było bardzo interesujące i angażujące badanie tego błędu).
- Ustawcie monitoring tak, aby pomagał szybko znajdować podobne fluktuacje. Na przykład można używać ELK, aby obserwować rps na każdym backendzie każdego upstreamu, śledzić ich czasy odpowiedzi z perspektywy Nginx. W tym przypadku to właśnie pomogło nam zidentyfikować problem.
W rezultacie większą część awarii można byłoby uniknąć przy bardziej skrupulatnym podejściu do tego, co robisz. Zawsze trzeba pamiętać o prawie Murphy'ego: Cokolwiek może pójść źle, pójdzie źle, i budować komponenty, kierując się tym.
Źródło: habr.com
