
утилита ps, с изюминкой eBPF и контекстом контейнеров
psc (ps container) — это быстрый сканер процессов, использующий eBPF итераторы и Google CEL для точного опроса состояния системы с полным контекстом контейнеров.
psc использует eBPF итераторы для считывания информации о процессах и файловых дескрипторах напрямую из структур данных ядра. Этот подход:
/proc. Планируется также добавить возможность запроса определённых полей task_struct для продвинутого использования, но это пока только идея./proc, обеспечивая видимость, которую невозможно обойти с помощью руткитов пользовательского пространства или трюков с LD_PRELOAD.Традиционные инструменты Linux, такие как ps, lsof и ss, мощны, но негибки. Они выводят фиксированные форматы, требующие обширной обработки через grep, awk и sed:
# Традиционный: найти процессы nginx, принадлежащие root
ps aux | grep nginx | grep root | grep -v grep
# psc: выразить именно то, что нужно
psc 'process.name == "nginx" && process.user == "root"'
# Традиционный: найти процессы с установленными соединениями на порту 443
ss -tnp | grep ESTAB | grep :443 | awk '{print $6}' | cut -d'"' -f2
# psc: одно понятное выражение
psc 'socket.state == established && socket.dstPort == 443'
psc использует Common Expression Language (CEL) для фильтрации процессов. Выражения CEL выглядят почти как естественный язык, что делает скрипты самодокументируемыми и поддерживаемыми. Больше не нужно разбирать запутанные конвейеры из grep | awk | sed | xargs.
Флаг -o позволяет выводить ровно те поля, которые нужны, устраняя постобработку:
psc 'socket.state == listen' -o process.name,socket.srcPort
Также доступны предустановленные наборы вывода для быстрой печати общей информации:
psc 'socket.type == tcp && socket.dstPort == 443' -o sockets
Традиционные инструменты не имеют понятия о контейнерах. Получение информации о контейнерах требует разбора путей cgroup, запросов к средам выполнения контейнеров и ручного соотнесения PID:
# Традиционный: найти процессы в контейнерах (хрупко, неполно)
ps aux | xargs -I{} sh -c 'cat /proc/{}/cgroup 2>/dev/null | grep -q docker && echo {}'
# psc: нативная поддержка контейнеров
psc 'container.runtime == docker'
psc автоматически извлекает контекст контейнера (ID, имя, образ, среда выполнения, метки) для Docker, containerd, CRI-O и Podman. Отлаживайте процессы, файлы и сетевые соединения любого контейнера напрямую с хоста:
# Показать все процессы в определённом контейнере
psc 'container.name == "my-app"' --tree
# Найти контейнеры, работающие от root
psc 'container.runtime == docker && process.user == "root"'
# Список контейнеров с их образами
psc 'container.id != ""' -o process.pid,process.name,container.name,container.image
На Debian/Ubuntu:
sudo apt-get install clang llvm libbpf-dev linux-headers-$(uname -r) linux-tools-$(uname -r)
На Fedora/RHEL:
sudo dnf install clang llvm libbpf-devel kernel-devel bpftool
# Сгенерировать vmlinux.h (требуется один раз для каждой версии ядра)
make vmlinux
# Собрать бинарник
make build
Или вручную:
bpftool btf dump file /sys/kernel/btf/vmlinux format c > bpf/vmlinux.h
go generate ./...
go build -o psc
sudo make install
psc требует прав root для загрузки eBPF-программ.
# Список всех процессов
sudo psc
# Список всех процессов в виде дерева
sudo psc --tree
Передайте CEL-выражение в качестве первого аргумента для фильтрации процессов:
# Фильтр по имени процесса
psc 'process.name == "nginx"'
# Фильтр по пользователю
psc 'process.user == "root"'
# Фильтр по содержимому командной строки
psc 'process.cmdline.contains("--config")'
# Фильтр по диапазону PID
psc 'process.pid > 1000 && process.pid < 2000'
# Комбинирование условий
psc 'process.name == "bash" || process.name == "zsh"'
# Показать только процессы в контейнерах
psc 'container.id != ""'
# Фильтр по среде выполнения контейнера (константы: docker, containerd, crio, podman)
psc 'container.runtime == docker'
# Фильтр по имени контейнера
psc 'container.name == "nginx"'
# Фильтр по образу контейнера
psc 'container.image.contains("nginx:latest")'
# Показать в виде дерева для иерархии процессов контейнера
psc 'container.runtime == docker' --tree
Чтобы понять, почему процесс существует, часто нужно посмотреть на его открытые файловые дескрипторы и сетевые соединения:
# Найти процессы с прослушивающими TCP-сокетами
psc 'socket.type == tcp && socket.state == listen'
# Найти процессы с установленными соединениями
psc 'socket.state == established'
# Найти процессы, подключённые к определённому порту
psc 'socket.dstPort == 443'
# Найти процессы, использующие Unix-сокеты
psc 'socket.family == unix'
# Найти процессы с открытыми файлами в /etc
psc 'file.path.startsWith("/etc")'
Поля процессов (process.X):
name — имя процесса (строка)pid — идентификатор процесса (int)ppid — идентификатор родительского процесса (int)tid — идентификатор потока (int)euid — эффективный идентификатор пользователя (int)ruid — реальный идентификатор пользователя (int)suid — сохранённый set-user-ID (int)user — имя пользователя (строка)cmdline — полная командная строка (строка)state — состояние процесса (uint)Поля возможностей (process.capabilities.X):
effective — битовая маска эффективных возможностей (uint)permitted — битовая маска разрешённых возможностей (uint)inheritable — битовая маска наследуемых возможностей (uint)Поля пространств имён (process.namespaces.X):
net — inode сетевого пространства имён (int)pid — inode пространства имён PID (int)mnt — inode пространства имён монтирования (int)uts — inode пространства имён UTS (int)ipc — inode пространства имён IPC (int)cgroup — inode пространства имён cgroup (int)Поля контейнера (container.X):
id — идентификатор контейнера (строка)name — имя контейнера (строка)image — образ контейнера (строка)runtime — среда выполнения контейнера (строка)labels — метки контейнера (map)Поля файлов/сокетов (file.X или socket.X):
path — путь к файлу (строка)fd — номер файлового дескриптора (int)srcPort — исходный порт (int)dstPort — порт назначения (int)type — тип сокета (tcp, udp)state — состояние сокета (для фильтрации используйте константы, например listen, established)family — семейство адресов (unix, inet, inet6)unixPath — путь Unix-сокета (строка)fdType — тип FD (file_type, socket_type)Используйте их в выражениях без кавычек: