Две новые технологии, BTF и CO-RE, прокладывают для BPF путь в отрасль с миллиардным оборотом. Уже сейчас существует множество BPF (eBPF) стартапов, создающих сетевые продукты, продукты для обеспечения безопасности и производительности (и многое другое вне нашего поля зрения), но требующих от клиентов установки зависимостей LLVM, Clang и заголовков библиотек ядра (kernel-headers), которые могут занимать в памяти более 100 мегабайт, что негативно сказывается на скорости распространения технологии. BTF и CO-RE устраняют эти зависимости во время выполнения, делая BPF не только более практичным для встроенных сред Linux, но и для повсеместного внедрения.
Эти технологии представляют из себя:
BTF: BPF Type Format, который предоставляет структурную информацию в целях устранения необходимости в заголовках библиотек ядра и Clang.
CO-RE: BPF Compile-Once Run-Everywhere, который делает скомпилированный байт-код BPF перемещаемым, избавляя от необходимости перекомпиляции с помощью LLVM.
Clang и LLVM по-прежнему требуются для компиляции, но в результате получается облегченный ELF-бинарник, который включает предварительно скомпилированный BPF байт-код и может выполняться везде. В проекте BCC есть такой набор, называемый libbpf tools. Для примера я перенес свой инструмент opensnoop(8):
# ./opensnoop
PID COMM FD ERR PATH
27974 opensnoop 28 0 /etc/localtime
1482 redis-server 7 0 /proc/1482/stat
1657 atlas-system-ag 3 0 /proc/stat
[…]
Это opensnoop(8) - ELF-бинарник, который не использует libLLVM или libclang:
# file opensnoop
opensnoop: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=b4b5320c39e5ad2313e8a371baf5e8241bb4e4ed, with debuginfo, not stripped
# ldd opensnoop
linux-vdso.so.1 (0x00007ffddf3f1000)
libelf.so.1 => /usr/lib/x8664-linux-gnu/libelf.so.1 (0x00007f9fb7836000)
libz.so.1 => /lib/x8664-linux-gnu/libz.so.1 (0x00007f9fb7619000)
libc.so.6 => /lib/x8664-linux-gnu/libc.so.6 (0x00007f9fb7228000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9fb7c76000)
# ls -lh opensnoop opensnoop.stripped
-rwxr-xr-x 1 root root 645K Feb 28 23:18 opensnoop
-rwxr-xr-x 1 root root 151K Feb 28 23:33 opensnoop.stripped
… и stripped занимает всего 151 Кбайт.
А теперь представьте BPF-продукт: вместо того, чтобы требовать от клиентов установки различных тяжелых (и хрупких) зависимостей, BPF-агент теперь может быть одним крошечным бинарником, который работает с любым ядром, имеющим BTF.
Как это работает
Дело не только в том, чтобы сохранить BPF байт-код в ELF и затем отправить его в любое другое ядро. Многие BPF-программы используют структуры ядра (kernel structs), которые могут изменяться от одной версии ядра к другой. Ваш BPF байт-код может по-прежнему выполняться на разных ядрах, но он может считывать неправильные смещения структуры и выводить бессмыслицу! opensnoop(8) не просматривает структуры ядра, поскольку он использует стабильные точки трассировки и их аргументы, но многие другие инструменты это делают.
Это проблема перемещения, и BTF и CO-RE решают эту проблему для BPF-бинарников. BTF предоставляет информацию о типе, чтобы смещения структуры и другие детали могли быть запрошены по мере необходимости, а CO-RE записывает, какие части BPF-программы необходимо переписать и как. Разработчик CO-RE Андрей Накрыко написал длинные статьи, в которых это объясняется более подробно: «Переносимость BPF» и «информация о типах CO-RE BTF».
CONFIG_DEBUG_INFO_BTF=y
Эти новые BPF-бинарники возможны только в том случае, если установлен этот параметр конфигурации ядра. Он добавляет около 1,5 Мбайт к образу ядра (это крошечный размер по сравнению с DWARF debuginfo, который может составлять сотни Мбайт). В Ubuntu 20.10 этот параметр конфигурации уже установлен по умолчанию, и все остальные дистрибутивы должны следовать ему. Примечание для разработчиков дистрибутива: он требует pahole >= 1.16.
Будущее средств оценки производительности BPF, BCC Python и bpftrace
Для работы со средствами оценки производительности BPF (BPF performance tools) вы должны начать с запуска инструментария BCC и bpftrace, а затем написания bpftrace-кода. Инструментарий BCC в конечном итоге следует переключить с Python на libbpf C - на работе это не отразится. Средства оценки производительности кода в BCC Python теперь считаются устаревшими, по этому мы переходим к libbpf C с BTF и CO-RE (хотя у нас все еще остается некоторая работа с библиотеками, например, поддержка USDT, поэтому Python-версии будут нам необходимы еще некоторое время). Обратите внимание, что существуют другие варианты использования BCC, которые могут продолжать использовать Python-интерфейс; сомейнтейнер BPF Алексей Старовойтов и я кратко обсудили это на iovisor-dev.
Моя книга BPF Performance Tools посвящена запуску инструментария BCC и написанию bpftrace-кода, и эта часть не меняется. Однако примеры программирования на Python в Приложении C теперь считаются устаревшими. Приносим извинения за неудобства. К счастью, это всего лишь 15 страниц из 880-страничной книги.
А что насчет bpftrace? Он поддерживает BTF, и в будущем мы планируем также уменьшить объем его установки (в настоящее время он может достигать 29 Мбайт, и мы убеждены, что этот показатель может быть намного меньше). Учитывая средний размер программы libbpf в 229 Кбайт (на основе текущих инструментов libbpf, stripped) и средний размер программы bpftrace в 1 Кбайт (инструменты из моей книги), большая коллекция инструментов bpftrace плюс бинарник bpftrace может потребовать меньший объем установки, чем эквивалент в libbpf. Кроме того, версии bpftrace можно изменять на лету. libbpf лучше подходит для более сложных и серьезных инструментов, которым нужны настраиваемые аргументы и библиотеки.
Как видно из скриншотов, будущее инструментов оценки производительности BPF таково:
# ls /usr/share/bcc/tools /usr/sbin/*.bt
argdist drsnoop mdflush pythongc tclobjnew
bashreadline execsnoop memleak pythonstat tclstat
[…]
/usr/sbin/bashreadline.bt /usr/sbin/mdflush.bt /usr/sbin/tcpaccept.bt
/usr/sbin/biolatency.bt /usr/sbin/naptime.bt /usr/sbin/tcpconnect.bt
[…]
… и таково:
# bpftrace -e 'BEGIN { printf("Hello, World!\n"); }'
Attaching 1 probe…
Hello, World!
^C
… а не таково:
#!/usr/bin/python
from bcc import BPF
from bcc.utils import printb
prog = """
int hello(void *ctx) {
bpftrace_printk("Hello, World!\n");
return 0;
}
"""
[…]
Спасибо Yonghong Song (Facebook) за руководство в разработке BTF, Andrii Nakryiko (Facebook) за руководство разработке CO-RE и всем, кто участвует в этом проекте.
Перевод статьи подготовлен в преддверии старта курса "Нагрузочное тестирование". В связи с этим приглашаем всех желающих посетить бесплатный демо-урок по теме: "Проведение нагрузочного тестирования в средстве Performance center".