
컨테이너 컨텍스트와 eBPF 트위스트를 갖춘 ps 유틸리티
psc (ps container)는 eBPF 반복자와 Google CEL을 사용하여 시스템 상태를 정밀하게 조회하고 완전한 컨테이너 컨텍스트를 제공하는 빠른 프로세스 스캐너입니다.
psc는 eBPF 반복자를 사용하여 커널 데이터 구조에서 직접 프로세스 및 파일 디스크립터 정보를 읽습니다. 이 접근 방식은:
/proc을 통해 노출되지 않는 커널 정보에 액세스합니다. 고급 사용을 위해 task_struct의 특정 필드에 주문형으로 액세스하는 방법도 추가할 계획이지만, 이는 아직 아이디어 단계입니다./proc 파일 시스템을 완전히 우회하여, 사용자 영역 루트킷이나 LD_PRELOAD 트릭에 의해 손상될 수 없는 가시성을 제공합니다.ps, lsof, ss와 같은 전통적인 Linux 도구는 강력하지만 유연성이 부족합니다. 고정된 형식으로 출력되며, grep, awk, sed를 통한 광범위한 파이핑이 필요합니다.
# 전통적인 방식: root가 소유한 nginx 프로세스 찾기
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는 CEL(Common Expression Language)을 사용하여 프로세스를 필터링합니다. 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는 Docker, containerd, CRI-O 및 Podman에 대해 컨테이너 컨텍스트(ID, 이름, 이미지, 런타임, 레이블)를 자동으로 추출합니다. 호스트에서 직접 모든 컨테이너의 프로세스, 파일 및 네트워크 연결을 디버그하세요.
# 특정 컨테이너의 모든 프로세스 표시
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는 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'
# 유닉스 소켓을 사용하는 프로세스 찾기
psc 'socket.family == unix'
# /etc에 열린 파일이 있는 프로세스 찾기
psc 'file.path.startsWith("/etc")'
프로세스 필드 (process.X):
name - 프로세스 이름 (문자열)pid - 프로세스 ID (정수)ppid - 부모 프로세스 ID (정수)tid - 스레드 ID (정수)euid - 유효 사용자 ID (정수)ruid - 실제 사용자 ID (정수)suid - 저장된 설정-사용자-ID (정수)user - 사용자 이름 (문자열)cmdline - 전체 명령줄 (문자열)state - 프로세스 상태 (uint)역량 필드 (process.capabilities.X):
effective - 유효 역량 비트마스크 (uint)permitted - 허용된 역량 비트마스크 (uint)inheritable - 상속 가능한 역량 비트마스크 (uint)네임스페이스 필드 (process.namespaces.X):
net - 네트워크 네임스페이스 inode (정수)pid - PID 네임스페이스 inode (정수)mnt - 마운트 네임스페이스 inode (정수)uts - UTS 네임스페이스 inode (정수)ipc - IPC 네임스페이스 inode (정수)cgroup - Cgroup 네임스페이스 inode (정수)컨테이너 필드 (container.X):
id - 컨테이너 ID (문자열)name - 컨테이너 이름 (문자열)image - 컨테이너 이미지 (문자열)runtime - 컨테이너 런타임 (문자열)labels - 컨테이너 레이블 (맵)파일/소켓 필드 (file.X 또는 socket.X):
path - 파일 경로 (문자열)fd - 파일 디스크립터 번호 (정수)srcPort - 소스 포트 (정수)dstPort - 대상 포트 (정수)type - 소켓 유형 (tcp, udp)state - 소켓 상태 (필터링 시 listen, established 같은 상수 사용)family - 주소 패밀리 (unix, inet, inet6)unixPath - 유닉스 소켓 경로 (문자열)fdType - FD 유형 (file_type, socket_type)표현식에서 따옴표 없이 사용하세요.
docker, containerd, crio, podmantcp, udpunix, inet, inet6established, listen, syn_sent, syn_recv, fin_wait1, fin_wait2, , , , , 참고: 출력은
ss스타일 상태 이름(ESTAB,LISTEN,SYN-SENT등)을 사용합니다. UDP 소켓의 경우 연결이 없는 특성상UNCONN(연결되지 않음) 또는ESTAB(연결됨)만 표시됩니다.
CEL은 문자열 조작 함수를 제공합니다.
.contains("substr") - 문자열이 부분 문자열을 포함하는지 확인.startsWith("prefix") - 문자열이 접두사로 시작하는지 확인.endsWith("suffix") - 문자열이 접미사로 끝나는지 확인--tree, -t - 프로세스를 트리로 표시--no-color - 컬러 출력 비활성화-o, --output - 사용자 정의 출력 열 (쉼표로 구분된 필드 이름 또는 프리셋)psc fields - 사용 가능한 모든 CEL 필드, 상수 및 출력 프리셋 나열psc version - 버전 정보 표시-o를 사용한 사용자 정의 출력-o 플래그를 사용하면 표시할 필드를 정확히 지정할 수 있습니다. 일반적인 사용 사례에 프리셋을 사용하거나 개별 필드를 지정할 수 있습니다.
프리셋:
sockets - 프로세스 정보 + 전체 소켓 세부 정보 (패밀리, 유형, 상태, 주소, 포트)files - 프로세스 정보 + 파일 디스크립터 세부 정보 (fd, 유형, 경로)containers - 프로세스 정보 + 컨테이너 세부 정보 (이름, 이미지, 런타임)network - 간결한 네트워크 보기 (pid, 이름, 유형, 상태, 포트)# 프리셋 사용
psc 'socket.state == listen' -o sockets
psc 'container.id != ""' -o containers
# 또는 개별 필드 지정
psc -o process.pid,process.name,process.user
psc 'socket.state == listen' -o process.pid,process.name,socket.srcPort,socket.state
출력에 파일/소켓 필드가 포함되고 필터가 프로세스당 여러 파일과 일치하는 경우, 각 일치 항목이 자체 행을 얻습니다.
$ 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
모든 웹 서버 찾기:
psc 'process.name == "nginx" || process.name == "apache2" || process.name == "httpd"'
권한 있는 포트에서 리스닝 중인 프로세스 찾기:
psc 'socket.state == listen && socket.srcPort < 1024'
다른 네트워크 네임스페이스의 프로세스 찾기 (컨테이너/파드 검사에 유용):
psc 'process.namespaces.net != 4026531840' -o process.pid,process.name,process.namespaces.net
권한 있는 프로세스의 역량 표시:
psc 'process.euid == 0' -o process.pid,process.name,process.capabilities.effective,process.capabilities.permitted
SUID 바이너리를 통해 권한을 상승시킨 프로세스 찾기 (실제 UID가 유효 UID와 다른 경우):
psc 'process.ruid != process.euid'
외부 서비스에 연결된 프로세스 찾기:
psc 'socket.state == established && socket.dstPort == 443'
사용자 정의 열로 네트워크 연결 표시:
psc 'socket.state == established' -o process.pid,process.name,socket.srcPort,socket.dstPort,socket.dstAddr
MIT
time_waitcloseclose_waitlast_ackclosingfile_type, socket_type