Od redakcji bloga Google: Czy kiedykolwiek zastanawiałeś się, jak inżynierowie Google Cloud Technical Solutions (TSE) zajmują się Twoimi zgłoszeniami do pomocy technicznej? Inżynierowie wsparcia technicznego TSE są odpowiedzialni za wykrywanie i rozwiązywanie problemów zgłaszanych przez użytkowników. Niektóre z tych problemów są dość proste, ale czasami zgłoszenie wymaga uwagi kilku inżynierów. W tym artykule jeden z członków TSE opowie nam o bardzo złożonym problemie z jego niedawnej praktyki — . W trakcie tej opowieści zobaczymy, w jaki sposób inżynierowie zdołali rozwiązać sytuację oraz czego nowego się dowiedzieli podczas usuwania błędu. Mamy nadzieję, że ta historia nie tylko opowie o głęboko zakorzenionym błędzie, ale także pomoże zrozumieć procesy, które zachodzą przy składaniu zgłoszenia w wsparciu Google Cloud.

Rozwiązywanie problemów to jednocześnie nauka i sztuka. Wszystko zaczyna się od postawienia hipotezy na temat przyczyny nietypowego zachowania systemu, a następnie jest ona poddawana próbie. Jednak przed sformułowaniem hipotezy musimy dokładnie określić i precyzyjnie sformułować problem. Jeśli pytanie jest zbyt niejasne, będziesz musiał je dokładnie przeanalizować; w tym tkwi „sztuka” rozwiązywania problemów.
W warunkach Google Cloud takie procesy są znacznie bardziej skomplikowane, ponieważ Google Cloud dołożył wszelkich starań, aby zagwarantować prywatność swoich użytkowników. Z tego powodu inżynierowie TSE nie mają dostępu do edytowania Twoich systemów ani możliwości przeglądania konfiguracji tak szeroko, jak użytkownicy. Dlatego w celu weryfikacji którejkolwiek z naszych hipotez (inżynierowie) nie mogą szybko modyfikować systemu.
Niektórzy użytkownicy uważają, że naprawiamy wszystko jak mechanicy w warsztacie samochodowym i po prostu przesyłają nam identyfikator wirtualnej maszyny, podczas gdy w rzeczywistości proces przebiega w formie rozmowy: zbierania informacji, formułowania i potwierdzania (lub obalenia) hipotez, a ostatecznie rozwiązanie problemu opiera się na komunikacji z klientem.
Rozważany problem
Dziś mamy do czynienia z historią z dobrym zakończeniem. Jednym z powodów sukcesu w rozwiązaniu zaproponowanego przypadku jest bardzo szczegółowy i dokładny opis problemu. Poniżej można zobaczyć kopię pierwszego zgłoszenia (edytowaną w celu ukrycia danych poufnych):

W tej wiadomości jest wiele przydatnych dla nas informacji:
- Wskazano konkretną VM
- Wskazano problem — nie działa DNS
- Wskazano, gdzie problem się objawia — VM i kontener
- Wskazano kroki, które użytkownik podjął, aby zidentyfikować problem
Zgłoszenie zostało zarejestrowane jako „P1: Krytyczny wpływ — Usługa niewykonalna w produkcji”, co oznacza stały nadzór nad sytuacją 24/7 na zasadzie „Follow the Sun” (tutaj można przeczytać więcej o ), z przekazywaniem go z jednego zespołu wsparcia technicznego do drugiego za każdym razem, gdy zmienia się strefa czasowa. W zasadzie, w momencie, gdy problem dotarł do naszego zespołu w Zurychu, okrążył już kulę ziemską. W tym czasie użytkownik podjął działania w celu zminimalizowania skutków, jednak obawiał się powtórzenia sytuacji w produkcji, ponieważ główna przyczyna wciąż nie została odkryta.
W momencie, gdy zgłoszenie dotarło do Zurychu, mieliśmy już następujące informacje:
- Zawartość
/etc/hosts - Zawartość
/etc/resolv.conf - Wnioski
iptables-save - Zebrane przez zespół
ngrepplik pcap
Z tymi danymi byliśmy gotowi przejść do etapu „dochodzenia” i rozwiązywania problemów.
Nasze pierwsze kroki
Przede wszystkim sprawdziliśmy logi i status serwera metadanych i upewniliśmy się, że działa on poprawnie. Serwer metadanych odpowiada na adres IP 169.254.169.254 i, oprócz innego, odpowiada za zarządzanie nazwami domen. Również sprawdziliśmy, czy zapora ogniowa działa poprawnie z VM i nie blokuje pakietów.
To był jakiś dziwny problem: sprawdzenie nmap obaliło naszą główną hipotezę o utracie pakietów UDP, więc w myślach wyprowadziliśmy kilka innych możliwości i sposobów ich weryfikacji:
- Czy pakiety giną wybiórczo? => Sprawdzić zasady iptables
- Czy to nie za mało ? => Проверить вывод
ip a show - Czy problem dotyczy tylko pakietów UDP, czy również TCP? => Uruchomić
dig +tcp - Czy generowane pakiety dig wracają? => Uruchomić
tcpdump - Czy libdns działa poprawnie? => Uruchomić
stracedo sprawdzenia przesyłania pakietów w obie strony
Tu postanawiamy zadzwonić do użytkownika, aby rozwiązać problemy na żywo.
W trakcie rozmowy udaje nam się zweryfikować kilka rzeczy:
- Po kilku sprawdzeniach wykluczamy reguły iptables z listy przyczyn
- Sprawdzamy interfejsy sieci i tablice routingu oraz ponownie weryfikujemy poprawność MTU
- Odkrywamy, że
dig +tcp google.com(TCP) działa poprawnie, aledig google.com(UDP) nie działa - Uruchamiając
tcpdumpdotychczas działadig, odkrywamy, że pakiety UDP wracają - Przeprowadzamy
strace dig google.comi widzimy, jak dig poprawnie wywołujesendmsg()irecvms(), jednak drugie przerywa się z powodu limitu czasu
Niestety, kończy się nasza zmiana i musimy przekazać problem do następnej strefy czasowej. Zgłoszenie, niemniej jednak, wzbudziło zainteresowanie w naszym zespole, a kolega proponuje stworzenie pakietu DNS za pomocą modułu scrapy w Pythonie.
from scapy.all import *
answer = sr1(IP(dst="169.254.169.254")/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname="google.com")),verbose=0)
print ("169.254.169.254", answer[DNS].summary())Ten fragment tworzy pakiet DNS i wysyła zapytanie do serwera metadanych.
Użytkownik uruchamia kod, pakiet DNS jest zwracany, a aplikacja go odbiera, co potwierdza brak problemu na poziomie sieci.
Po kolejnym 'podróżowaniu po świecie' zgłoszenie wraca do naszego zespołu, a ja całkowicie przejmuję je na siebie, uznając, że użytkownikowi będzie wygodniej, jeśli zgłoszenie przestanie krążyć z miejsca na miejsce.
W międzyczasie użytkownik uprzejmie zgadza się dostarczyć obraz systemu. To bardzo dobra wiadomość: możliwość samodzielnego testowania systemu znacząco przyspiesza proces rozwiązywania problemów, ponieważ nie muszę już prosić użytkownika o uruchamianie poleceń, przesyłanie mi wyników i ich analizowanie, mogę wszystko zrobić sam!
Koleżanki i koledzy zaczynają mi zazdrościć. W czasie lunchu omawiamy zgłoszenie, jednak nikt nie ma pomysłów na to, co się dzieje. Na szczęście sam użytkownik już podjął kroki w celu złagodzenia skutków i nigdzie się nie spieszy, więc mamy czas na dokładne zbadanie problemu. A ponieważ mamy obraz, możemy przeprowadzić wszelkie interesujące nas testy. Świetnie!
Cofając się o krok
Jednym z najpopularniejszych pytań na rozmowie kwalifikacyjnej na stanowisko inżyniera systemowego jest: 'Co się dzieje, gdy pingujesz ?» Вопрос шикарный, так как кандидату необходимо описать пусть от оболочки до пользовательского пространства, до ядра системы и далее к сети. Я улыбаюсь: иногда вопросы с интервью оказываются полезны и в реальной жизни…
Postanawiam zastosować to pytanie rekrutacyjne do bieżącego problemu. Mówiąc prosto, gdy próbujesz zlokalizować nazwę DNS, dzieje się następujące:
- Aplikacja wywołuje systemową bibliotekę, na przykład libdns
- libdns sprawdza konfigurację systemu, do którego serwera DNS ma się zwracać (na diagramie to 169.254.169.254, serwer metadanych)
- libdns korzysta z wywołań systemowych do utworzenia gniazda UDP (SOKET_DGRAM) i przesyła pakiety UDP z zapytaniem DNS w obie strony
- Za pomocą interfejsu sysctl można skonfigurować stos UDP na poziomie jądra
- Jądro współdziała z sprzętem w celu przesyłania pakietów przez sieć za pośrednictwem interfejsu sieciowego
- Hypervisor przechwytuje i przekazuje pakiet do serwera metadanych podczas kontaktu z nim
- Serwer metadanych dzięki swoim czarom określa nazwę DNS i w taki sam sposób zwraca odpowiedź

Przypomnę, jakie hipotezy rozważaliśmy do tej pory:
Hipoteza: Złamane biblioteki
- Test 1: Uruchomić w systemie strace, sprawdzić, czy dig wywołuje poprawne wywołania systemowe
- Wynik: wywoływane są poprawne wywołania systemowe
- Test 2: przez srapy sprawdzić, czy możemy określać nazwy omijając systemowe biblioteki
- Wynik: możemy
- Test 3: uruchomić rpm –V na pakiecie libdns i md5sum plików biblioteki
- Wynik: kod biblioteki jest w pełni identyczny z kodem w działającym systemie operacyjnym
- Test 4: zamontować obraz systemu głównego użytkownika na VM bez podobnego zachowania, uruchomić chroot, sprawdzić, czy działa DNS
- Wynik: DNS działa poprawnie
Wnioski na podstawie testów: problem nie leży w bibliotekach
Hipoteza: Występuje błąd w konfiguracji DNS
- Test 1: sprawdzić tcpdump i obserwować, czy pakiety DNS są poprawnie wysyłane i odbierane po uruchomieniu dig
- Wynik: pakiety są przesyłane poprawnie
- Test 2: ponownie sprawdzić na serwerze
/etc/nsswitch.confi/etc/resolv.conf - Wynik: wszystko w porządku
Wnioski na podstawie testów: problem nie leży w konfiguracji DNS
Hipoteza: uszkodzone jądro
- Test: zainstalować nowe jądro, sprawdzić podpis, zrestartować
- Wynik: podobne zachowanie
Wnioski na podstawie testów: jądro nie jest uszkodzone
Hipoteza: nieprawidłowe zachowanie sieci użytkownika (lub interfejsu sieciowego hypervisora)
- Test 1: sprawdzić ustawienia zapory ogniowej
- Wynik: zapora ogniowa przepuszcza pakiety DNS zarówno na hoście, jak i w GCP
- Test 2: przechwycić ruch i monitorować poprawność przesyłania i zwracania zapytań DNS
- Wynik: tcpdump potwierdza odbiór zwrotnych pakietów przez hosta
Wnioski na podstawie testów: problem nie leży w sieci
Hipoteza: serwer metadanych nie działa
- Test 1: sprawdzić logi serwera metadanych pod kątem anomalii
- Wynik: w logach nie ma anomalii
- Test 2: obejście serwera metadanych przez
dig @8.8.8.8 - Wynik: rozwiązywanie problemów występuje nawet bez użycia serwera metadanych
Wnioski na podstawie testów: problem nie leży w serwerze metadanych
Podsumowanie: przetestowaliśmy wszystkie podsystemy poza ustawieniami środowiska uruchomieniowego!
Zagłębiając się w ustawienia środowiska uruchomieniowego jądra
Aby skonfigurować środowisko uruchomieniowe jądra, możesz skorzystać z opcji wiersza poleceń (grub) lub interfejsu sysctl. Zajrzałem do /etc/sysctl.conf i pomyśleć tylko, odkryłem kilka niestandardowych ustawień. Czując się jakbym trzymał coś, odrzuciłem wszystkie niesieciowe lub nie-tcp ustawienia, pozostawiając tylko garść ustawień net.core. Następnie zwróciłem uwagę na miejsce, gdzie znajdują się uprawnienia hosta dla VM i zacząłem zastosowywać kolejno ustawienia z zepsutą VM, aż natrafiłem na winowajcę:
net.core.rmem_default = 2147483647Oto ona, łamiąca konfiguracja DNS! Znalazłem narzędzie przestępstwa. Ale dlaczego to się dzieje? Wciąż potrzebowałem motywu.
Ustawienie podstawowego rozmiaru bufora pakietów DNS odbywa się przez net.core.rmem_default. Typowa wartość waha się w granicach 200KiB, jednak jeśli twój serwer otrzymuje wiele pakietów DNS, możesz zwiększyć rozmiar bufora. Jeśli w momencie przybycia nowego pakietu bufor jest pełny, na przykład dlatego, że aplikacja nie przetwarza go wystarczająco szybko, zaczniesz tracić pakiety. Nasz klient słusznie zwiększył rozmiar bufora, ponieważ obawiał się utraty danych, korzystając z aplikacji do zbierania metryk przez pakiety DNS. Ustawiona przez niego wartość była maksymalna: 231-1 (jeśli ustawić 231, jądro zwróci „INVALID ARGUMENT”).
Nagle zrozumiałem, dlaczego nmap i scapy działały prawidłowo: używały surowych gniazd! Surowe gniazda różnią się od zwykłych: działają z pominięciem iptables i nie są buforowane!
Ale dlaczego „zbyt duży bufor” powoduje problemy? Wyraźnie nie działa tak, jak to zamierzono.
W tym momencie mogłem odtworzyć problem na kilku jądrach i wielu dystrybucjach. Problem ujawnił się już w jądrze 3.x, a teraz również w jądrze 5.x.
Rzeczywiście, po uruchomieniu
sysctl -w net.core.rmem_default=$((2**31-1))DNS przestał działać.
Zacząłem szukać roboczych wartości za pomocą prostego algorytmu wyszukiwania binarnego i odkryłem, że system działa z wartością 2147481343, jednak dla mnie ta liczba była bezsensownym zbiorem cyfr. Sugerowałem klientowi, aby spróbował tej liczby, a on odpowiedział, że system zadziałał z google.com, ale nadal wyświetlał błąd dla innych domen, więc kontynuowałem moje dochodzenie.
Zainstalowałem , narzędzie, którego powinienem był użyć wcześniej: pokazuje, gdzie dokładnie w jądrze pakiet trafia. Winna okazała się funkcja udp_queue_rcv_skb. Pobralem źródła jądra i dodałem kilka , aby śledzić, gdzie konkretnie trafia pakiet. Szybko odkryłem potrzebny warunek if, i przez jakiś czas po prostu wpatrywałem się w niego, ponieważ wtedy wszystko w końcu złożyło się w całość: 231-1, bezsensowna liczba, niedziałająca domena… To była kwestia fragmentu kodu w __udp_enqueue_schedule_skb:
if (rmem > (size + sk->sk_rcvbuf))
goto uncharge_drop;Zwróć uwagę:
rmemjest typu introzmiarjest typu u16 (niesigned 16-bitowy int) i przechowuje rozmiar pakietusk->sk_rcybufjest typu int i przechowuje rozmiar bufora, który z definicji jest równy wartości wnet.core.rmem_default
Kiedy sk_rcvbuf bliski 231, sumowanie rozmiaru pakietu może prowadzić do . A ponieważ to jest int, jego wartość staje się ujemna, w ten sposób warunek staje się prawdziwy, kiedy powinien być fałszywy (więcej informacji można znaleźć na ).
Błąd można naprawić w trywialny sposób: rzutując na unsigned int. Zastosowałem poprawkę i ponownie uruchomiłem system, po czym DNS znowu zaczął działać.
Smak zwycięstwa
Przesłałem moje odkrycia klientowi i wysłałem łatkę jądra. Jestem zadowolony: każdy kawałek układanki złożył się w całość, mogę dokładnie wyjaśnić, dlaczego obserwowaliśmy to, co obserwowaliśmy, a co najważniejsze, udało nam się znaleźć rozwiązanie problemu dzięki wspólnej pracy!
Trzeba przyznać, że przypadek okazał się rzadki, i na szczęście użytkownicy rzadko zgłaszają tak skomplikowane zapytania.
Źródło: habr.com
