AI Infra 训练营
总览
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
HiHuo 主站
GitHub
总览
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
HiHuo 主站
GitHub

ps —— 看进程

一句话定义

ps(process status)显示当前时刻的进程快照——和 top 实时刷新不同,ps 是"这一瞬间的列表"。它有两套语法体系(BSD 和 SysV)让人混乱,但实战只需要记住两三种调用。

典型场景

  • 找进程 PID:ps aux | grep nginx
  • 看进程树:ps auxf 或 pstree
  • 看 K8s 容器进程:ps -ef | grep containerd-shim
  • 自定义列:ps -eo pid,user,cmd
  • 找占内存最多的 10 个:ps aux --sort=-%mem | head

两套语法

ps aux                            # BSD 风格(无前导 -)
ps -ef                            # SysV 风格(带 -)
ps -A                             # POSIX

两种都常见、各人偏好不同。记住 ps aux 一个就够——它显示得最全。

语法列
ps auxUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ps -efUID PID PPID C STIME TTY TIME CMD

ps aux 输出详解

USER       PID  %CPU %MEM   VSZ   RSS  TTY    STAT  START  TIME  COMMAND
root         1   0.0  0.1  167544 11920 ?     Ss    May27  0:02  /sbin/init
root      1234   0.5  1.2   45000  8000 ?     Ssl   10:23  0:15  /usr/sbin/sshd -D
root      5678   0.0  0.3   12000  3000 ?     Ss    11:05  0:00  /usr/sbin/cron
nginx     8901   0.2  0.5   80000 15000 ?     S     12:30  0:05  nginx: worker process
列含义
USER进程的 effective user
PID进程 ID
%CPUCPU 占比(注意:自进程启动以来的平均值)
%MEM物理内存占比
VSZ虚拟内存大小(KB),含 mmap 但未必真实占用
RSS常驻物理内存(KB),这才是真用了多少
TTY关联的终端(? = 没有终端,daemon)
STAT进程状态(见下表)
START启动时间
TIME累计 CPU 时间
COMMAND命令行(截断,要完整加 -w 或 -f)

STAT 列详解

字符含义
RRunning 或可运行
SSleeping(可中断的睡眠,绝大多数进程)
DUninterruptible sleep(通常是 I/O 等待,kill 不掉)
ZZombie(僵尸,已 exit 但父进程没 wait)
T停止(SIGSTOP / 调试中)
+前台进程组
s会话首进程(session leader)
l多线程
<高优先级(nice 负值)
N低优先级(nice 正值)

D 状态是排错警钟——通常是磁盘 I/O 卡住、NFS 挂死、内核 bug。kill -9 也杀不掉 D 状态进程,要解决根因(释放 I/O)。


--sort 排序(实战最爱)

ps aux --sort=-%cpu | head                # CPU 占用 top 10
ps aux --sort=-%mem | head                # 内存占用 top 10
ps aux --sort=-rss | head                  # 按 RSS 真实内存排
ps aux --sort=-time | head                 # 累计 CPU 时间最长(运行时间最久的工作进程)

- 前缀表示降序,无前缀升序。

K8s 节点上找"哪个 pod 在烧 CPU":

ssh m4 'ps aux --sort=-%cpu | head -20'
# 看 COMMAND 列,配合 crictl ps 找到对应容器

自定义列 -o

ps -eo pid,user,%cpu,%mem,rss,comm --sort=-%cpu | head
# PID  USER  %CPU %MEM   RSS  COMMAND
# 1234 root   5.2  1.0  8000  nginx

常用列:

列名含义
pidPID
ppid父 PID
pgid进程组 ID
sid会话 ID
usereffective user
ruserreal user
uidUID
comm命令(短,不含参数)
args完整命令(含参数)
cmd同 args
%cpuCPU 占比
%mem内存占比
rssRSS(KB)
vsz虚拟内存
stat状态
start / start_time启动时间
time累计 CPU 时间
etime进程已运行多久(elapsed)
lstart启动时间(完整格式)
tty终端
nicenice 值
psr当前在哪个 CPU 核上
wchan内核等待原因
cgroup所属 cgroup

K8s 排错常用:

ps -eo pid,etime,user,%cpu,%mem,cgroup,args --sort=-%mem | head
# 看 cgroup 列能直接看出"这个进程属于哪个 K8s pod"
# cgroup 名形如 /kubepods/burstable/pod-xxx/abc...

进程树

ps auxf                            # f = forest
# 用缩进显示父子关系

pstree                             # 更直观的树
pstree -p                          # 加 PID
pstree -p 1234                      # 看 PID 1234 的子树

K8s 节点上:

systemd
├─ containerd
│  ├─ containerd-shim
│  │  ├─ pause                            ← pod sandbox
│  │  └─ nginx ─ nginx worker             ← 应用容器
│  └─ containerd-shim
│     ├─ pause
│     └─ python ─ uvicorn ─ python ...
└─ kubelet

排查"这个进程是哪个 pod 的":从进程往上追到 containerd-shim,再用 crictl 反查。


找进程的几个常见方式

1. ps aux | grep(最普及,有"自我匹配"陷阱)

$ ps aux | grep nginx
root  1234  ...  nginx: master
root  1235  ...  nginx: worker
root  5678  ...  grep --color=auto nginx       ← grep 自己也被匹配

避免自我匹配:

ps aux | grep "[n]ginx"            # 模式 [n]ginx 实际匹配 nginx
                                   # 但 ps 里 grep 的命令行是 grep [n]ginx,不匹配

2. pgrep / pkill(更现代)

pgrep nginx                        # 只输出 PID
pgrep -a nginx                      # 输出 PID + 命令行
pgrep -u root nginx                 # 限定 user
pgrep -P 1234                       # 按 PPID 找(找子进程)

pkill nginx                         # 杀
pkill -9 nginx                      # SIGKILL
pkill -u alice                      # 杀某用户所有进程(小心)

写脚本里首选 pgrep,比 ps | grep 更可靠。

3. pidof(按精确进程名)

pidof nginx
# 1234 1235

只匹配命令名,不匹配参数。比 pgrep 更严格。


看进程详情:/proc/<PID>/

ps 信息源都来自 /proc/<PID>/。直接看更详细:

ls /proc/1234/
# cmdline cwd environ exe fd/ limits maps mountinfo ns/ root status ...

cat /proc/1234/cmdline | tr '\0' ' '         # 完整命令行(参数用 \0 分隔,转空格)
cat /proc/1234/status                         # 详细状态(含线程数 / 内存 / capabilities)
cat /proc/1234/limits                         # ulimit
cat /proc/1234/cgroup                         # 所属 cgroup
ls /proc/1234/fd/                             # 打开的所有文件描述符
readlink /proc/1234/exe                       # 可执行文件路径
readlink /proc/1234/cwd                       # 当前工作目录
ls /proc/1234/ns/                             # namespace

K8s 排错常用:

# 找 pod 容器的 PID
PID=$(crictl inspect $(crictl ps -q --name my-pod) | jq '.info.pid')

# 看它的 cgroup
cat /proc/$PID/cgroup
# 12:cpuset:/kubepods/burstable/pod-xxx/...

# 看它的 namespaces
ls -l /proc/$PID/ns/
# 跟节点 root 的 /proc/1/ns/ 比,能看出哪些 ns 隔离了

看线程

ps -eLf | grep java                # -L 显示线程
ps -eo pid,nlwp,comm | sort -k2 -rn | head    # 按线程数排序

ps -T -p 1234                      # 看进程 1234 的所有线程
# PID  SPID  TTY  TIME  CMD
# 1234 1234  ...  java
# 1234 1235  ...  java
# 1234 1236  ...  java

Java 应用排查"哪个线程在烧 CPU":

top -H -p 1234                     # top 的线程模式
# 看到 TID 烧 CPU,转 16 进制 → jstack 里查

等价命令的实战速查

# 1. 看全部进程
ps aux
ps -ef
ps -A
ps -e

# 2. 看自己的进程
ps                                 # 默认只看当前 shell 的
ps x                               # 看自己所有

# 3. 按 PID
ps -p 1234,5678                    # 多个 PID
ps -p 1234 -o pid,user,cmd

# 4. 按 user
ps -u nginx                        # 按 effective user
ps -U nginx                        # 按 real user

# 5. 按 cgroup(K8s 视角)
ps -eo pid,cgroup,cmd | grep kube-system
# 找 kube-system pod 的进程

训练营实战

1. 节点 CPU 突然飙高

top                                # 大致看到哪个进程
# 但是 top 切到的话用 ps:
ps aux --sort=-%cpu | head -10
# 拿到 PID

# 进一步
top -H -p <PID>                    # 看是哪个线程
cat /proc/<PID>/cgroup             # 确定哪个 K8s pod

2. 找占内存最大的 pod

ps -eo pid,rss,cgroup,comm --sort=-rss | head -20
# RSS 是 KB,要 MB 自己 / 1024

# 或者更整洁
ps -eo pid,rss,comm --sort=-rss | awk '$2 > 100000 {printf "%-8s %8d MB  %s\n", $1, $2/1024, $3}' | head

3. 找 zombie

ps aux | awk '$8 ~ /Z/'           # STAT 列含 Z
# 或者
ps -eo stat,pid,ppid,cmd | awk '$1 ~ /Z/'

zombie 都需要父进程 wait 才能彻底清。父进程挂了 zombie 被 init (PID 1) 接管自动清。

K8s pod 里 zombie 堆积通常是因为容器主进程没正确 reap 子进程。修法:用 --init 或者多进程容器用 tini / dumb-init。

4. 找一个进程的容器 / pod

PID=12345
cat /proc/$PID/cgroup
# 12:cpu,cpuacct:/kubepods/burstable/pod-d4e6a7-...

# 提取 pod UID 反查 K8s
POD_UID=$(grep -oE 'pod[a-f0-9-]+' /proc/$PID/cgroup | head -1 | sed 's/pod//;s/_/-/g')
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.uid} {.metadata.namespace}/{.metadata.name}{"\n"}{end}' \
  | grep $POD_UID

5. 长跑进程(uptime)

ps -eo pid,etime,cmd --sort=-etime | head
# etime 显示进程运行了多久([[DD-]hh:]mm:ss)

# 跑了多久的 sshd?
ps -eo pid,lstart,cmd | grep sshd
# 看 lstart 列得到精确启动时间戳

常见踩坑

坑 1:%CPU 是平均值,不是当前

ps aux | grep my-app
# %CPU 显示 50%

ps aux 的 %CPU 是进程启动以来的累计 CPU 时间 / 进程运行时长。一个跑了一周、最近 1 小时狂吃 CPU 的进程,%CPU 可能只显示 5%——因为平均下来不高。

要看"当前瞬时 CPU"用 top(或 top -b -n 1)。

坑 2:RSS 比"实际占用"高

RSS 是常驻内存(含共享页),多个进程共享同一 libc 的话,每个进程 RSS 都把 libc 算上。所以所有进程 RSS 之和 > 系统总内存 是正常的。

要看进程独占内存:PSS(proportional set size)—— 共享页按比例分摊。

ps -eo pid,rss,comm | head
# 没 PSS

cat /proc/<PID>/smaps_rollup | grep -E 'Pss|Rss|Uss'
# Rss:              50000 kB
# Pss:              30000 kB           ← 这才是"按比例分摊的实际占用"

或者用 smem 工具直接看 PSS。

坑 3:ps aux 截断长命令

ps aux | grep python
# python ...
# 后面参数被截掉,看不出哪个 python 进程

加 -w / -ww 或 f:

ps auxw                            # 不截断
ps auxww                           # 完全不截断
ps -eo pid,args                    # args 列默认不截断

或者读 /proc/<PID>/cmdline。

坑 4:USER 显示 1000 而不是用户名

ps -eo pid,user,cmd
# 1234 1000      python   ← UID 没解析成名字

UID 在 /etc/passwd 里没条目(容器里很常见,UID 1000 没用户名)。要看名字加 --no-headers 或自定义:

ps -eo pid,user:20,cmd     # user 列展宽

容器里看不到名字是正常的——pod 的 UID 在节点 host /etc/passwd 里没条目。

坑 5:用 ps -ef | grep 也匹配自己

同 grep 的坑:

ps -ef | grep nginx
# ... grep --color=auto nginx     ← 自己

用 [n]ginx 或 pgrep。

坑 6:D 状态进程 kill 不掉

kill -9 1234
# 进程还在
ps aux | grep 1234
# STAT 还是 D

D 状态在内核等 I/O。SIGKILL 也杀不掉——必须等内核把 I/O 完成或失败。

判断 D 原因:

cat /proc/1234/wchan         # 看在等什么内核函数
# nfs_wait_on_request         ← NFS 卡了
# io_schedule                 ← 磁盘 I/O

NFS / iSCSI 网络存储卡死是 D 状态的常见来源。

坑 7:ps 输出在不同发行版列宽不同

ps aux            # Ubuntu 上 PID 4 位、CentOS 上 6 位

写脚本解析时不要按列宽硬切,用 awk 按字段:

ps aux | awk '{print $2, $11}'    # 第 2 字段 PID、第 11+ 字段 COMMAND

但是 COMMAND 含空格 → awk 不能简单 $11。要用:

ps -eo pid,comm,args
# 或者用 ps -o 显式指定列

关联命令

  • top —— 实时刷新的 ps
  • pgrep / pkill —— 现代 ps + grep 替代
  • pidof —— 按命令名找 PID
  • pstree —— 进程树
  • kill / killall —— 发信号
  • lsof —— 看进程的打开文件 / socket
  • strace —— 看进程在做啥系统调用
  • crictl —— 进程对应 K8s 容器反查
在 GitHub 上编辑此页