Jak znaleźć ducha w linuksowej skorupie? – eBPF
Napisał: Patryk Krawaczyński
07/09/2017 w Administracja Brak komentarzy. (artykuł nr 637, ilość słów: 4070)
A
naliza wydajności często jest ograniczona przez brak widoczności różnych zjawisk zachodzących w systemie. Obserwacja tych zjawisk pozwala inżynierom systemów łatwo identyfikować elementy, które zawierają ograniczenia i mogą być szybsze. “Najnowszym” narzędziem do obserwacji systemu operacyjnego Linux jest BPF (ang. Berkeley Packet Filter). Został on opracowany w 1992 roku, aby zapewnić sposób filtrowania pakietów sieciowych oraz uniknięcia ich bezużytecznego kopiowania z przestrzeni jądra do użytkownika. Początkowo składał się z prostego kodu bajtowego, który był wstrzykiwany z przestrzeni użytkownika do jądra. Tam był sprawdzany przez weryfikator – w celu uniknięcia krachu jądra lub problemów z bezpieczeństwem – i dołączany do gniazda, a następnie uruchamiany na każdym odebranym pakiecie.
Kilka lat później (1997 r.) mechanizm ten został przeniesiony do systemu Linux (v2.1.75) i używany przez niewielką liczbę aplikacji, jak np. tcpdump. Prostota języka oraz dodanie kompilatora Just-In-Time (JIT) przez Erica Dumazeta (2011 r.) w jądrze dla BPF przyczyniły się do doskonałych osiągów tego narzędzia. W 2013 r. Alexei Starovoitov przedstawił propozycję przekształconego i wyposażonego w nowe funkcje BPF. Nowa wersja została zarejestrowana oficjalnie w jądrze v3.15 jako eBPF (rozszerzony BPF – ang. extended BPF), podczas gdy poprzednia wersja stała się cBPF (klasycznym BPF – ang. classic BPF). Od tego czasu przepisano JIT oraz dodano nowe punkty zaczepienia w jądrze (Tracepoint / Kprobe / USDT (ang. Userland Statically Defined Tracepoints)). Dzięki nowym zaczepom programy eBPF mogą być projektowane dla różnych zastosowań, których działanie możemy podzielić na dwa obszary. Pierwszym z obszarów jest sfera śledzenia jądra przez monitorowanie i mierzenie opóźnień zdarzeń systemowych. Drugi obszar natomiast pozostaje w dziedzinie programowania sieciowego, gdzie programy eBPF mogą być podłączane do socketów (v3.19), czy interfejsów wejściowych i wyjściowych tc (ang. traffic control – v4.1) wykonując wiele zadań związanych z przetwarzaniem pakietów. Mogą zostać także wykorzystane w służbie: programowalnych sieci komputerowych, łagodzenia ataków DDoS (wczesne zrzucanie pakietów), poprawy wydajności sieci (eXpress Data Path), systemów wykrywania intruzów (IDS), bezpieczeństwa kontenerów i wielu innych.
Ingo Molnár opisał eBPF jako:
One of the more interesting features in this cycle is the ability to attach eBPF programs (user-defined, sandboxed bytecode executed by the kernel) to kprobes. This allows user-defined instrumentation on a live kernel image that can never crash, hang or interfere with the kernel negatively.
Jak już wspomniałem wcześniej kod BPF jest kompilowany do kodu bajtowego i wysyłany do jądra, gdzie weryfikator decyduje o jego przyjęciu bądź odrzuceniu. Jeśli zostanie zaakceptowany może zostać podpięty do różnych źródeł zdarzeń:
- kprobes – kernel dynamic tracing,
- uprobes – user level dynamic tracing,
- tracepoints – kernel static tracing,
- perf_events – performance counters
Po odczycie z źródła program BPF ma dwie drogi, aby przekazać pobrane dane z powrotem do przestrzeni użytkownika: albo szczegółowo per zdarzenie albo przez mapy. Te drugie mogą implementować tablice, tablice asocjacyjne lub histogramy, które są odpowiednimi obiektami do przekazywania podsumowujących statystyk (obrazek #1 – wewnętrzny mechanizm eBPF – źródło).
BCC:
BPF Compiler Collection jest zestawem narzędzi do tworzenia wydajnych programów służących do śledzenia i manipulowania zdarzeniami jądra systemu. Posiada także interfejsy w języku Python i LUA do ich developmentu. Zestaw sam w sobie zawiera już dość sporą kolekcję narzędzi wraz z przykładami ich zastosowania. W celu ich wykorzystania wymagane jest jądro w wersji 4.1 lub wyżej (obrazek #2 – wsparcie eBPF w zależności od wersji jądra – źródło). Przykład instalacji BCC oprzemy o Ubuntu 16.04 ze względu na możliwość instalacji jądra w wersji 4.10:
echo "deb [trusted=yes] https://repo.iovisor.org/apt/xenial xenial-nightly main" | \ tee /etc/apt/sources.list.d/iovisor.list apt-get update apt-get install linux-image-4.10.0-32-generic linux-headers-4.10.0-32-generic apt-get install bcc-tools libbcc-examples shutdown -r now
Po restarcie systemu narzędzia bcc znajdziemy w katalogu /usr/share/bcc/tools
. Jeśli do dyspozycji mamy starsze wersje jądra (pomiędzy 4.1, a 4.8), a interesujące nas narzędzie nie do końca działa zgodnie z naszymi oczekiwaniami to powinniśmy zainteresować się wersjami dostępnymi w katalogu /usr/share/bcc/tools/old
, które zawierają starsze z różnymi obejściami. Poniżej znajduje się demonstracja działania kilku programów bcc – zarówno tych z serii śledzenia aktywności systemowej, jak i aktywności sieciowej:
1) bashreadline
– wyświetla wszystkie polecenia wydane w powłoce bash poprzez śledzenie funkcji readline() za pomocą uprobes.
root@darkstar:/usr/share/bcc/tools# ./bashreadline TIME PID COMMAND 22:30:11 1833 ps x 22:30:21 1833 uname -a 22:36:38 1833 uptime
2) execsnoop
– śledzi nowe procesy poprzez wywołanie exec() wyświetlając proces rodzica i inne szczegóły. Poniżej znajduje się lista procesów odpalanych przy okazji polecenia: sudo su –:
root@darkstar:/usr/share/bcc/tools# ./execsnoop PCOMM PID PPID RET ARGS sudo 2463 2450 0 /usr/bin/sudo su - su 2464 2463 0 /bin/su - bash 2465 2464 0 /bin/bash groups 2467 2466 0 /usr/bin/groups ls 2469 2468 0 /bin/ls /etc/bash_completion.d lesspipe 2471 2470 0 /usr/bin/lesspipe basename 2472 2471 0 /usr/bin/basename /usr/bin/lesspipe dirname 2474 2473 0 /usr/bin/dirname /usr/bin/lesspipe dircolors 2476 2475 0 /usr/bin/dircolors -b mesg 2477 2465 0 /usr/bin/mesg n
3) opensnoop
– program bardzo podobny do powyższego z tą różnicą, że śledzi wywołanie open(). Poniżej znajduje się lista otwieranych plików przy okazji polecenia: ping:
root@darkstar:/usr/share/bcc/tools# ./opensnoop PID COMM FD ERR PATH 2716 ping 3 0 /etc/ld.so.cache 2716 ping 3 0 /lib/x86_64-linux-gnu/libcap.so.2 2716 ping 3 0 /lib/x86_64-linux-gnu/libc.so.6 2716 ping 4 0 /etc/resolv.conf 2716 ping 4 0 /etc/resolv.conf 2716 ping 4 0 /etc/nsswitch.conf 2716 ping 4 0 /etc/ld.so.cache 2716 ping 4 0 /lib/x86_64-linux-gnu/libnss_files.so.2 2716 ping 4 0 /etc/host.conf 2716 ping 4 0 /etc/hosts 2716 ping 4 0 /etc/ld.so.cache 2716 ping 4 0 /lib/x86_64-linux-gnu/libnss_mdns4_minimal.so.2 2716 ping 4 0 /etc/ld.so.cache 2716 ping 4 0 /lib/x86_64-linux-gnu/libnss_dns.so.2 2716 ping 4 0 /lib/x86_64-linux-gnu/libresolv.so.2 2716 ping 4 0 /etc/hosts
4) ext4slower
– śledzi operacje systemu plików ext4 (istnieją wersje dla: brtfs, xfs oraz zfs), które zajmują więcej niż X milisekund. Poniżej znajduje się lista operacji, które zajęła więcej niż 10 milisekund poleceniu: apt-get remove snapd:
root@darkstar:/usr/share/bcc/tools# ./ext4slower 10 Tracing ext4 operations slower than 10 ms TIME COMM PID T BYTES OFF_KB LAT(ms) FILENAME 21:53:48 mandb 8234 S 0 0 12.14 8234 21:53:48 dpkg 8523 S 0 0 10.82 status-new 21:53:48 apt-get 8019 R 47095092 0 13.47 srcpkgcache.bin 21:53:48 apt-get 8019 W 47095191 0 20.12 pkgcache.bin.1xKxwV
5) cachestat
– co sekundę pokazuje statystyki odnośnie trafień i chybień w page cache. Poniżej znajdują się statystyki pokazujące ciągłe odpytywanie o zawartość katalogu /etc/
poleceniem: ls -al /etc:
root@darkstar:/usr/share/bcc/tools# ./cachestat HITS MISSES DIRTIES READ_HIT% WRITE_HIT% BUFFERS_MB CACHED_MB 2585 4 4 99.7% 0.1% 84 441 3244 12 10 99.3% 0.1% 84 441 3228 0 0 100.0% 0.0% 84 441 6391 0 0 100.0% 0.0% 84 441 1083 0 0 100.0% 0.0% 84 441
6) biosnoop
– śledzi operacje wejścia/wyjścia urządzeń blokowych pokazując informacje o procesie, dysku oraz opóźnieniach.
root@darkstar:/usr/share/bcc/tools# ./biosnoop TIME(s) COMM PID DISK T SECTOR BYTES LAT(ms) 14.995055000 updatedb 8603 sda R 4327128 4096 0.34 14.999967000 updatedb 8603 sda W 9017344 688128 3.43 17.441281000 jbd2/sda1-8 332 sda W 4642296 40960 0.46 17.443919000 jbd2/sda1-8 332 sda W 4642376 4096 0.07 42.060127000 bash 8584 sda R 1371008 16384 1.16 42.061292000 rmdir 8584 sda R 1371040 24576 0.43 46.657396000 mc 8586 sda W 627880 4096 0.11
7) biotop
– śledzi operacje wejścia/wyjścia z danego czasu i sporządza listę programów wraz z podsumowaniem ich aktywności:
root@darkstar:/usr/share/bcc/tools# ./biotop 60 1 Tracing... Output every 60 secs. Hit Ctrl-C to end 21:03:08 loadavg: 0.15 0.06 0.02 1/186 4759 PID COMM D MAJ MIN DISK I/O Kbytes AVGms 1235 kworker/u2:2 W 8 0 sda 193 46948 4.02 4698 updatedb R 8 0 sda 7033 30604 0.30 333 jbd2/sda1-8 W 8 0 sda 756 14848 0.10 4698 updatedb W 8 0 sda 5 2552 4.74 4282 dpkg W 8 0 sda 161 2416 0.17 3785 dpkg W 8 0 sda 73 1516 0.11 3825 mandb W 8 0 sda 20 688 0.25 4670 dpkg W 8 0 sda 1 548 0.83 4698 bash R 8 0 sda 1 16 1.33 3769 apt-get W 8 0 sda 1 12 0.14 4494 insserv W 8 0 sda 3 12 0.16 4731 mc W 8 0 sda 1 12 0.12
8) gethostlatency
– pokazuje opóźnienie dla wywołań funkcji getaddrinfo/gethostbyname w całym systemie:
root@darkstar:/usr/share/bcc/tools# ./gethostlatency TIME PID COMM LATms HOST 22:41:38 8642 curl 43.37 nfsec.pl 22:41:52 8643 hostname 0.13 darkstar 22:42:01 8644 ping 16.77 wp.pl 22:43:15 8645 ssh 75.02 darkstar.example.com
9) tcplife
– śledzi połączenia TCP dla zdalnych i lokalnych portów oraz konkretnych procesów PID:
root@darkstar:/usr/share/bcc/tools# ./tcplife -tD 80 TIME(s) PID COMM LADDR LPORT RADDR RPORT TX_KB RX_KB MS 0.000000 9000 links 10.0.2.15 52608 213.180.141.140 80 0 0 5136.69 11.983495 9000 links 10.0.2.15 48868 213.180.141.188 80 0 9 12505.75 11.983542 9000 links 10.0.2.15 34960 213.180.141.148 80 0 2 12530.80 11.984304 9000 links 10.0.2.15 41138 216.58.209.68 80 0 0 12531.60 11.984767 9000 links 10.0.2.15 52988 213.180.141.132 80 0 0 12507.60 11.985196 9000 links 10.0.2.15 47092 213.180.141.178 80 0 0 12530.88 11.985682 9000 links 10.0.2.15 35604 213.180.141.156 80 0 68 12580.80 11.986178 9000 links 10.0.2.15 59688 213.180.141.150 80 0 0 12531.68 11.986532 9000 links 10.0.2.15 57260 213.180.141.128 80 0 0 12533.65 31.357647 9018 http 10.0.2.15 40066 91.189.91.26 80 0 1011 1027.56
10) tcpaccept
– śledzi aktywne połączenia TCP dzięki monitorowaniu connect() (o tcpretrans możemy przeczytać tutaj):
root@darkstar:/usr/share/bcc/tools# ./tcpconnect -t TIME(s) PID COMM IP SADDR DADDR DPORT 0.000 2518 telnet 4 127.0.0.1 127.0.0.1 22 19.902 2520 links 4 192.168.111.2 192.168.111.2 80 49.121 2539 wget 4 10.0.2.15 37.187.104.217 80 49.222 2539 wget 4 10.0.2.15 37.187.104.217 443
i wiele innych (obrazek #3 – inne skrypty eBPF – źródło). Jednak moim faworytem jest ttysnoop, który potrafi przechwytywać informacje wyjściowe z danego terminala (czyli administrator może w czasie rzeczywistym zobaczyć, co się dzieje na dowolnej konsoli użytkownika):
root@darkstar:/usr/share/bcc/tools# ./ttysnoop -h usage: ttysnoop [-h] [-C] device Snoop output from a pts or tty device, eg, a shell positional arguments: device path to a tty device (eg, /dev/tty0) or pts number optional arguments: -h, --help show this help message and exit -C, --noclear don't clear the screen examples: ./ttysnoop /dev/pts/2 # snoop output from /dev/pts/2 ./ttysnoop 2 # snoop output from /dev/pts/2 (shortcut) ./ttysnoop /dev/console # snoop output from the system console ./ttysnoop /dev/tty0 # snoop output from /dev/tty0
Co w przypadku, gdy chcemy prześledzić dowolny proces? Możemy wykonać to za pomocą trace. Dla przykładu podepnijmy się do procesu apache2 i sprawdźmy, jakie pliki otwiera funkcja do_sys_open()
:
root@darkstar:/usr/share/bcc/tools# pidof apache2 1052 1051 1048 root@darkstar:/usr/share/bcc/tools# ./trace -p 1052 'do_sys_open "%s", arg2' PID TID COMM FUNC - 1052 1069 apache2 do_sys_open /var/www/html/index.html 1052 1069 apache2 do_sys_open /etc/localtime
Możemy również wyświetlić listę tracepointów i sond USDT danego procesu:
root@darkstar:/usr/share/bcc/tools# ./tplist -p 1052 /lib/x86_64-linux-gnu/libpthread-2.23.so libpthread:pthread_start /lib/x86_64-linux-gnu/libpthread-2.23.so libpthread:pthread_create /lib/x86_64-linux-gnu/libpthread-2.23.so libpthread:pthread_join /lib/x86_64-linux-gnu/libpthread-2.23.so libpthread:pthread_join_ret
Dla lepszego ich wsparcia powinniśmy skompilować wybrany język lub serwer z odpowiednimi parametrami np.:
- MySQL / PostgreSQL : --enable-dtrace - JVM : -XX:+ExtendedDTraceProbes (przy uruchomieniu) - NodeJS : --with-dtrace - Python : --with-dtrace - Ruby : --enable-dtrace
Jeśli jesteśmy zainteresowani rozwijaniem własnych narzędzi ebpf z pomocą bcc powinniśmy zapoznać się z bcc Reference Guide oraz bcc Python Developer Tutorial.
Podsumowanie:
“eBPF to niesamowita technologia jądra Linuksa napędzająca niestandardowe narzędzia do analizy, które mogą być uruchamiane na produkcji, aby znaleźć wygrane na wydajności, których nie są w stanie wykryć inne narzędzia. Dzięki niej, jak nigdy dotąd możemy wyciągnąć miliony metryk z jądra i aplikacji oraz badać uruchomione programy. To supermoc.” – jak powiedział Brendan Gregg – ten sam, który stworzył DTraceToolkit – zresztą bardzo wiele skryptów z tego rozwiązania zostało właśnie przeniesione do BCC. Kamienie milowe nowych funkcjonalności eBPF w kolejnych wersjach jąder (3.18, 3.19, 4.1, 4.4, 4.6, 4.7, 4.8, 4.9, 4.10, 4.11) czynią ten projekt jednym z ważniejszych, jeśli chodzi o analizę systemu. Stawia to Linuksa jako system operacyjny i jądro w świetle doskonałej platformy do analizy aplikacji (oprócz konkretnych narzędzi dostarczonych z aplikacjami). Niektóre rzeczy są łatwiejsze do debugowania z poziomu jądra np. jak performuje system plików i urządzenia pamięci masowej; czy planista blokuje aplikację oraz gdzie jest wąskie gardło na sieci. Dzięki eBPF możemy zbadać obszary systemu operacyjnego, na które aplikacja jest totalnie ślepa.
Więcej informacji: New (and Exciting!) Developments in Linux Tracing, Trace Aggregation and Collection with eBPF, TraceCompass, Dynamic Tracing Tools for Linux, Dive into BPF, Notes on BPF & eBPF