
das ps-Dienstprogramm, mit einem eBPF-Twist und Container-Kontext
psc (ps container) ist ein schneller Prozess-Scanner, der eBPF-Iteratoren und Google CEL nutzt, um den Systemzustand präzise und mit vollständigem Container-Kontext abzufragen.
psc verwendet eBPF-Iteratoren, um Prozess- und Dateideskriptor-Informationen direkt aus Kernel-Datenstrukturen zu lesen. Dieser Ansatz ist:
/proc zugänglich sind. Ich plane, auch eine Möglichkeit hinzuzufügen, bestimmte Felder der task_struct bei Bedarf für fortgeschrittene Anwendungsfälle abzurufen – dies ist jedoch nur eine Idee/proc-Dateisystem vollständig und bietet Einblick, der nicht durch Userland-Rootkits oder LD_PRELOAD-Tricks untergraben werden kannTraditionelle Linux-Werkzeuge wie ps, lsof und ss sind zwar leistungsstark, aber unflexibel. Sie geben feste Formate aus, die umfangreiches Piping durch grep, awk und sed erfordern:
# Traditionell: nginx-Prozesse finden, die root gehören
ps aux | grep nginx | grep root | grep -v grep
# psc: Drücken Sie genau das aus, was Sie meinen
psc 'process.name == "nginx" && process.user == "root"'
# Traditionell: Prozesse mit etablierten Verbindungen auf Port 443 finden
ss -tnp | grep ESTAB | grep :443 | awk '{print $6}' | cut -d'"' -f2
# psc: Ein klarer Ausdruck
psc 'socket.state == established && socket.dstPort == 443'
psc verwendet die Common Expression Language (CEL), um Prozesse zu filtern. CEL-Ausdrücke lesen sich fast wie natürliche Sprache und machen Ihre Skripte selbstdokumentierend und wartbar. Schluss mit dem Entziffern komplexer Pipelines aus grep | awk | sed | xargs.
Mit der -o-Flagge können Sie genau die Felder ausgeben, die Sie benötigen, und damit die Nachbearbeitung vollständig überflüssig machen:
psc 'socket.state == listen' -o process.name,socket.srcPort
Ausgabe-Voreinstellungen stehen ebenfalls zur Verfügung, um schnell gängige Informationen auszugeben:
psc 'socket.type == tcp && socket.dstPort == 443' -o sockets
Traditionelle Werkzeuge haben kein Konzept von Containern. Um Container-Informationen zu erhalten, müssen cgroup-Pfade analysiert, Container-Runtimes abgefragt und PIDs manuell korreliert werden:
# Traditionell: Containerisierte Prozesse finden (fehleranfällig, unvollständig)
ps aux | xargs -I{} sh -c 'cat /proc/{}/cgroup 2>/dev/null | grep -q docker && echo {}'
# psc: Native Container-Unterstützung
psc 'container.runtime == docker'
psc extrahiert automatisch Container-Kontext (ID, Name, Image, Runtime, Labels) für Docker, containerd, CRI-O und Podman. Debuggen Sie Prozesse, Dateien und Netzwerkverbindungen eines beliebigen Containers direkt vom Host:
# Alle Prozesse in einem bestimmten Container anzeigen
psc 'container.name == "meine-app"' --tree
# Container finden, die als root laufen
psc 'container.runtime == docker && process.user == "root"'
# Container mit ihren Images auflisten
psc 'container.id != ""' -o process.pid,process.name,container.name,container.image
Auf Debian/Ubuntu:
sudo apt-get install clang llvm libbpf-dev linux-headers-$(uname -r) linux-tools-$(uname -r)
Auf Fedora/RHEL:
sudo dnf install clang llvm libbpf-devel kernel-devel bpftool
# vmlinux.h generieren (einmal pro Kernel-Version erforderlich)
make vmlinux
# Die Binärdatei bauen
make build
Oder manuell:
bpftool btf dump file /sys/kernel/btf/vmlinux format c > bpf/vmlinux.h
go generate ./...
go build -o psc
sudo make install
psc erfordert Root-Rechte, um eBPF-Programme zu laden.
# Alle Prozesse auflisten
sudo psc
# Alle Prozesse als Baum auflisten
sudo psc --tree
Übergeben Sie einen CEL-Ausdruck als erstes Argument, um Prozesse zu filtern:
# Nach Prozessnamen filtern
psc 'process.name == "nginx"'
# Nach Benutzer filtern
psc 'process.user == "root"'
# Nach Inhalt der Befehlszeile filtern
psc 'process.cmdline.contains("--config")'
# Nach PID-Bereich filtern
psc 'process.pid > 1000 && process.pid < 2000'
# Bedingungen kombinieren
psc 'process.name == "bash" || process.name == "zsh"'
# Nur containerisierte Prozesse anzeigen
psc 'container.id != ""'
# Nach Container-Runtime filtern (Konstanten: docker, containerd, crio, podman)
psc 'container.runtime == docker'
# Nach Container-Name filtern
psc 'container.name == "nginx"'
# Nach Container-Image filtern
psc 'container.image.contains("nginx:latest")'
# Als Baum anzeigen, um die Container-Prozesshierarchie zu sehen
psc 'container.runtime == docker' --tree
Um zu verstehen, warum ein Prozess existiert, muss man oft seine offenen Dateideskriptoren und Netzwerkverbindungen betrachten:
# Prozesse mit lauschenden TCP-Sockets finden
psc 'socket.type == tcp && socket.state == listen'
# Prozesse mit etablierten Verbindungen finden
psc 'socket.state == established'
# Prozesse finden, die mit einem bestimmten Port verbunden sind
psc 'socket.dstPort == 443'
# Prozesse finden, die Unix-Sockets verwenden
psc 'socket.family == unix'
# Prozesse finden, die Dateien in /etc geöffnet haben
psc 'file.path.startsWith("/etc")'
Prozessfelder (process.X):
name - Prozessname (string)pid - Prozess-ID (int)ppid - Elternprozess-ID (int)tid - Thread-ID (int)euid - Effektive Benutzer-ID (int)ruid - Reale Benutzer-ID (int)suid - Saved set-user-ID (int)user - Benutzername (string)cmdline - Vollständige Befehlszeile (string)state - Prozessstatus (uint)Capability-Felder (process.capabilities.X):
effective - Effektive Capabilities-Bitmaske (uint)permitted - Erlaubte Capabilities-Bitmaske (uint)inheritable - Vererbbare Capabilities-Bitmaske (uint)Namespace-Felder (process.namespaces.X):
net - Netzwerk-Namespace-Inode (int)pid - PID-Namespace-Inode (int)mnt - Mount-Namespace-Inode (int)uts - UTS-Namespace-Inode (int)ipc - IPC-Namespace-Inode (int)cgroup - Cgroup-Namespace-Inode (int)Container-Felder (container.X):
id - Container-ID (string)name - Container-Name (string)image - Container-Image (string)runtime - Container-Runtime (string)labels - Container-Labels (map)Datei-/Socket-Felder (file.X oder socket.X):
path - Dateipfad (string)fd - Dateideskriptor-Nummer (int)srcPort - Quellport (int)dstPort - Zielport (int)type - Socket-Typ (tcp, udp)state - Socket-Status (für Filterung Konstanten wie listen, established verwenden)family - Adressfamilie (unix, inet, inet6)unixPath - Unix-Socket-Pfad (string)fdType - FD-Typ (file_type, socket_type)Verwenden Sie diese ohne Anführungszeichen in Ausdrücken:
docker, containerd, crio, podmantcp, udpunix, inet, inet6established, listen, syn_sent, syn_recv, fin_wait1, fin_wait2, , , , , Hinweis: Die Ausgabe verwendet
ss-artige Statusnamen:ESTAB,LISTEN,SYN-SENT, usw. Für UDP-Sockets werden nurUNCONN(nicht verbunden) oderESTAB(verbunden) angezeigt, da UDP verbindungslos ist.
CEL bietet String-Manipulationsfunktionen:
.contains("substr") - Prüft, ob der String einen Teilstring enthält.startsWith("prefix") - Prüft, ob der String mit einem Präfix beginnt.endsWith("suffix") - Prüft, ob der String mit einem Suffix endet--tree, -t - Prozesse als Baum anzeigen--no-color - Farbige Ausgabe deaktivieren-o, --output - Benutzerdefinierte Ausgabespalten (kommagetrennte Feldnamen oder Voreinstellung)psc fields - Alle verfügbaren CEL-Felder, Konstanten und Ausgabevoreinstellungen auflistenpsc version - Versionsinformationen anzeigen-oDie -o-Flagge ermöglicht es Ihnen, genau die anzuzeigenden Felder anzugeben. Sie können Voreinstellungen für häufige Anwendungsfälle oder einzelne Felder verwenden.
Voreinstellungen:
sockets - Prozessinfo + vollständige Socket-Details (Familie, Typ, Status, Adressen, Ports)files - Prozessinfo + Dateideskriptor-Details (fd, Typ, Pfad)containers - Prozessinfo + Container-Details (Name, Image, Runtime)network - Kompakte Netzwerkansicht (pid, name, typ, status, ports)# Eine Voreinstellung verwenden
psc 'socket.state == listen' -o sockets
psc 'container.id != ""' -o containers
# Oder einzelne Felder angeben
psc -o process.pid,process.name,process.user
psc 'socket.state == listen' -o process.pid,process.name,socket.srcPort,socket.state
Wenn die Ausgabe Datei-/Socket-Felder enthält und der Filter mehrere Dateien pro Prozess trifft, erhält jeder Treffer eine eigene Zeile:
$ psc 'socket.state == listen' -o network
PID NAME TYPE STATE SRCPORT DSTPORT
1234 nginx tcp LISTEN 80 0
1234 nginx tcp LISTEN 443 0
5678 sshd tcp LISTEN 22 0
Alle Webserver finden:
psc 'process.name == "nginx" || process.name == "apache2" || process.name == "httpd"'
Prozesse finden, die auf privilegierten Ports lauschen:
psc 'socket.state == listen && socket.srcPort < 1024'
Prozesse in einem anderen Netzwerk-Namespace finden (nützlich für Container-/Pod-Inspektion):
psc 'process.namespaces.net != 4026531840' -o process.pid,process.name,process.namespaces.net
Capabilities für privilegierte Prozesse anzeigen:
psc 'process.euid == 0' -o process.pid,process.name,process.capabilities.effective,process.capabilities.permitted
Prozesse finden, die Rechte über SUID-Binärdateien erhöht haben (reale UID unterscheidet sich von effektiver UID):
psc 'process.ruid != process.euid'
Prozesse mit Verbindungen zu externen Diensten finden:
psc 'socket.state == established && socket.dstPort == 443'
Netzwerkverbindungen mit benutzerdefinierten Spalten anzeigen:
psc 'socket.state == established' -o process.pid,process.name,socket.srcPort,socket.dstPort,socket.dstAddr
MIT
time_waitcloseclose_waitlast_ackclosingfile_type, socket_type