第一章:Go语言中Process.Pid()返回-1现象概览
在 Go 标准库 os/exec 和 os 包中,*os.Process 类型的 Pid() 方法本应返回进程的有效 PID(正整数),但实践中常观察到其返回 -1。该现象并非 Go 运行时错误,而是由进程生命周期管理、权限约束及底层系统调用语义共同导致的确定性行为。
常见触发场景
- 进程已终止且未被父进程
Wait()或Waitpid()回收,此时*os.Process对象虽仍存在,但内核中对应 task_struct 已释放,Pid()退化为占位符-1 - 跨用户或跨命名空间访问非子进程(如通过
os.FindProcess()查询任意 PID),在 Linux 上受/proc/[pid]可见性限制;若当前用户无权限读取目标进程状态(如EPERM),os.FindProcess(pid).Pid()亦返回-1 - 使用
exec.Command启动命令后未调用Start(),cmd.Process为nil,直接访问cmd.Process.Pid()将 panic;若已Start()但立即调用Pid(),而内核尚未完成 fork/exec 的 PID 分配(极罕见,多见于高负载容器环境),也可能短暂返回-1
验证与诊断方法
可通过以下代码复现典型场景:
package main
import (
"os"
"os/exec"
"time"
)
func main() {
cmd := exec.Command("sleep", "1")
err := cmd.Start()
if err != nil {
panic(err)
}
// 立即检查:此时进程已启动,Pid() 应为正数
println("Started PID:", cmd.Process.Pid()) // 输出如:Started PID: 12345
// 等待进程退出
time.Sleep(2 * time.Second)
// 此时进程已终止,但 cmd.Process 仍存在
// Pid() 行为取决于平台:Linux/macOS 通常返回 -1,Windows 可能保留原值
println("After exit PID:", cmd.Process.Pid()) // 很可能输出:After exit PID: -1
}
关键注意事项
Pid()返回-1意味着该*os.Process实例不再关联一个活跃的、可操作的内核进程实体- 不应将
-1视为错误码,而应结合ProcessState.Exited()或ProcessState.Success()判断进程终态 - 安全检测模式:始终先确认
cmd.Process != nil,再调用Pid();对FindProcess()结果,需配合process.Signal(syscall.Signal(0)) == nil验证存活性
| 场景 | 是否可调用 Signal() | 是否可调用 Wait() | Pid() 合理性 |
|---|---|---|---|
| 进程运行中 | ✅ 是 | ❌ 否(会阻塞) | ✅ 正整数 |
| 进程已退出未 Wait | ❌ 否(sys: no such process) | ✅ 是 | ⚠️ 通常 -1 |
| FindProcess 找到但无权限 | ❌ 否(EPERM) | ❌ 否 | ⚠️ -1 |
第二章:权限模型失效导致Pid获取失败的五类典型场景
2.1 非特权用户尝试读取跨用户进程的/proc/PID/status文件(理论分析+strace验证实验)
Linux 内核通过 ptrace_may_access() 和 proc_pid_permission() 机制校验 /proc/PID/status 的读取权限,仅允许:
- 进程自身(
current->pid == task->pid) - 父进程(需满足
ptrace权限) - root 用户(
capable(CAP_SYS_PTRACE))
权限判定流程
graph TD
A[open(\"/proc/1234/status\")] --> B{caller UID == target UID?}
B -->|Yes| C[Allow]
B -->|No| D{Has CAP_SYS_PTRACE?}
D -->|Yes| C
D -->|No| E[Permission denied - EACCES]
strace 实验验证
$ strace -e trace=openat,read -p 1001 2>&1 | grep status
openat(AT_FDCWD, "/proc/2002/status", O_RDONLY) = -1 EACCES (Permission denied)
openat 系统调用返回 -1 EACCES,表明内核在 proc_pid_status_open() 中已拒绝非特权跨用户访问。
关键内核路径
fs/proc/base.c:proc_pid_status_open()security/commoncap.c:cap_ptrace_access_check()kernel/ptrace.c:ptrace_may_access()
该机制确保进程内存与状态信息的隔离性,是 Linux DAC 模型的核心防护之一。
2.2 Capabilities缺失:CAP_SYS_PTRACE未授予时syscall.PtraceAttach失败路径追踪(内核capability检查+go test复现)
当进程调用 syscall.PtraceAttach(pid) 时,内核首先校验调用者是否具备 CAP_SYS_PTRACE 能力:
// linux/kernel/ptrace.c: ptrace_attach()
if (!ns_capable(__task_cred(task)->user_ns, CAP_SYS_PTRACE))
return -EPERM;
该检查在 ptrace_may_access() 中触发,依赖 capable() → ns_capable() → has_ns_capability_noaudit() 链路。
复现关键步骤
- 使用
unshare -r -U创建无 CAP_SYS_PTRACE 的用户命名空间 - 运行 Go 测试程序调用
unix.PtraceAttach() - 观察返回
EPERM及strace -e trace=ptrace日志
内核能力检查流程(简化)
graph TD
A[PtraceAttach syscall] --> B[ptrace_attach]
B --> C[ptrace_may_access]
C --> D[ns_capable: CAP_SYS_PTRACE]
D -- fail --> E[return -EPERM]
| 检查环节 | 触发条件 | 错误码 |
|---|---|---|
| 用户命名空间能力 | !has_ns_capability_noaudit |
EPERM |
| 目标进程权限 | !ptrace_may_access |
EPERM |
2.3 进程被ptrace attach后进入PTRACE_TRACEME状态导致/proc/PID/stat解析异常(runtime/pprof对比+procfs源码级调试)
当进程调用 ptrace(PTRACE_TRACEME, ...) 后,内核将其 task_struct->ptrace 置为 PT_TRACE_ME,并触发 task_state_to_char() 中的特殊状态映射逻辑。
/proc/PID/stat 中的 state 字段异常
proc_pid_stat() 调用 task_state_to_char() 时,对被 trace 的进程会返回 't'(traced),但部分 Go 工具(如 runtime/pprof)仍按 'R'/'S' 解析,导致采样误判。
// linux/fs/proc/array.c: task_state_to_char()
if (p->state == TASK_TRACED || p->state == TASK_STOPPED)
return 't'; // 注意:非 'T'!'T' 仅用于 /proc/PID/status
TASK_TRACED→'t'(小写),而传统ps显示T(大写),造成工具链兼容性断裂。
runtime/pprof 的解析局限
pprof直接读取/proc/self/stat第3字段(state),未处理't'procfs库(v0.12+)已修复:显式检查't'并映射为Stopped
| 字符 | 内核状态 | pprof 当前行为 |
|---|---|---|
R |
TASK_RUNNING | ✅ 正确识别 |
t |
TASK_TRACED | ❌ 视为未知 |
S |
TASK_INTERRUPTIBLE | ✅ 正确识别 |
源码级验证路径
# 在被 ptrace 的进程中执行:
cat /proc/self/stat | awk '{print $3}'
# 输出:t —— 非预期的单字符,非标准 POSIX 状态码
2.4 容器运行时强制drop CAP_SYS_ADMIN导致/proc/PID目录不可读(docker run –cap-drop=ALL实测+seccomp profile反向定位)
复现现象
# 启动无任何能力的容器
docker run --cap-drop=ALL -it alpine sh -c 'ls -l /proc/1'
# 输出:ls: can't open '/proc/1': Permission denied
CAP_SYS_ADMIN 是访问 /proc/PID/ 下多数子目录(如 status, cmdline, fd/)的隐式前提——即使仅需读取,内核在 proc_pid_readdir 和 proc_pid_permission 中仍检查该能力。
能力依赖映射表
/proc/1/xxx |
所需 capability | 原因 |
|---|---|---|
status, stat |
CAP_SYS_ADMIN |
触发 ptrace_may_access() 检查 |
fd/ |
CAP_SYS_ADMIN |
ptrace_may_access() 防止信息泄露 |
environ |
CAP_SYS_ADMIN |
同上,避免敏感环境变量暴露 |
seccomp 反向定位路径
graph TD
A[容器 ls /proc/1] --> B[系统调用 openat(AT_FDCWD, “/proc/1”, …)]
B --> C[内核进入 proc_pid_permission]
C --> D{是否持有 CAP_SYS_ADMIN?}
D -- 否 --> E[返回 -EACCES]
D -- 是 --> F[允许访问]
关键验证命令
- 查看当前容器实际能力集:
cat /proc/1/status | grep CapEff - 对比启用
--cap-add=SYS_ADMIN后行为变化
2.5 systemd服务Unit配置中RestrictAddressFamilies或ProtectProc=strict拦截/proc访问(journalctl日志分析+go进程strace输出比对)
当启用 ProtectProc=strict 时,systemd 将挂载 /proc 为只读且隐藏非本进程的 PID 目录,导致 Go 程序调用 os.ReadDir("/proc") 或 filepath.WalkDir 失败。
journalctl 日志关键线索
systemd[1]: myapp.service: Failed at step EXEC spawning /usr/bin/myapp: Permission denied
此错误并非来自可执行文件权限,而是
openat(AT_FDCWD, "/proc/1/cmdline", O_RDONLY)被内核拒绝(-EACCES),因ProtectProc=strict隐式启用hidepid=2。
strace 对比差异
| 场景 | strace -e trace=openat,readlink 输出片段 |
|---|---|
| 默认 | openat(AT_FDCWD, "/proc/1/cmdline", O_RDONLY) = 3 |
ProtectProc=strict |
openat(AT_FDCWD, "/proc/1/cmdline", O_RDONLY) = -1 EACCES |
根本原因流程图
graph TD
A[Go 进程尝试读取 /proc/1/cmdline] --> B{ProtectProc=strict?}
B -->|是| C[内核拒绝访问:hidepid=2 + noexec + ro]
B -->|否| D[正常返回进程参数]
C --> E[syscall 返回 -EACCES → os.IsPermission(err) == true]
建议:若需枚举进程,改用 RestrictAddressFamilies= 白名单替代全局 /proc 限制,或显式设置 ProtectProc=no 并配合 NoNewPrivileges=yes 降权。
第三章:命名空间隔离引发的PID语义断裂问题
3.1 PID namespace嵌套下getpid()与/proc/self/status中Tgid不一致导致runtime.processFromPid误判(nsenter进容器+gdb动态查看runtime.pids)
现象复现路径
使用 nsenter -t <init_pid> -n -p -- /bin/sh 进入容器 PID namespace 后执行:
# 在容器内运行
echo $$ # 输出 1(getpid() 返回当前namespace的PID)
cat /proc/self/status | grep Tgid # 显示 Tgid: 12345(宿主机视角的线程组ID)
根本原因
当 runtime 调用 runtime.processFromPid(1) 时,会查 runtime.pids 映射表——该表由 procfs 遍历 /proc/[0-9]+/status 构建,但未做 namespace 上下文对齐,直接以 Tgid 字段为键。而 getpid() 返回的是当前 PID namespace 中的局部 PID。
关键验证步骤
gdb -p $(pidof containerd-shim)→p runtime.pids查看实际映射- 对比
nsenter -n -t $PID -- cat /proc/1/status | grep Tgid与getpid()
| 字段 | 值(容器内) | 含义 |
|---|---|---|
$$ |
1 |
getpid() 返回当前 namespace 局部 PID |
Tgid |
12345 |
宿主机 PID,即 init 进程在 root ns 中的真实 PID |
// runtime/process.go 中误判逻辑节选
func processFromPid(pid int) *Process {
if p, ok := pids[pid]; ok { // ❌ 错误:pid 是局部 PID,pids 键却是全局 Tgid
return p
}
return nil
}
此处
pid来自getpid()(如 1),但pidsmap 的 key 实际存的是/proc/*/status中读出的Tgid(如 12345),导致查表失败,进程对象丢失。需通过/proc/[pid]/status+NSpid字段做 namespace-aware 匹配。
3.2 Go runtime初始化阶段未正确感知init_pid_ns外的namespace边界(runtime/os_linux.go补丁级分析+clone(CLONE_NEWPID)最小复现)
Go runtime 在 os_linux.go 中通过 getpid() 和 getppid() 初始化 runtime.pid 与 runtime.ppid,但该逻辑仅在初始 namespace 中有效。当进程通过 clone(CLONE_NEWPID) 创建新 PID namespace 后,getpid() 返回的是新 namespace 内的 1(即 init 进程),而 runtime 仍将其误认为宿主 namespace 的真实 PID。
关键补丁逻辑(Go 1.22+)
// runtime/os_linux.go(补丁后)
func osinit() {
// 原始:pid = getpid()
// 补丁:显式检查 /proc/self/status 中的 NSpid 字段
pid, _ := readProcStatusField("/proc/self/status", "NSpid:")
if len(pid) > 1 {
runtime.pid = atoi(pid[1]) // 取第二列:NSpid: 1 42 → 新 ns 内 PID=1,宿主 ns PID=42
}
}
NSpid:行格式为"NSpid:\t1\t42\t105",各列对应嵌套深度从内到外的 PID。runtime 原逻辑忽略此层级信息,导致runtime.pid == 1错误传播至net、os/exec等依赖 PID 的子系统。
最小复现场景
- 父进程
clone(CLONE_NEWPID | SIGCHLD)→ 子进程进入新 PID namespace - 子进程调用
os.Getpid()→ 返回1(非宿主 PID) exec.Command("sh").Start()因 PID 混淆触发fork/exec失败
| 组件 | 旧行为(无补丁) | 新行为(带 NSpid 解析) |
|---|---|---|
os.Getpid() |
总返回 1(新 ns 内) | 返回宿主 namespace PID |
runtime.ppid |
恒为 0(因 getppid() 在新 ns 中失败) | 正确解析父进程宿主 PID |
graph TD
A[clone CLONE_NEWPID] --> B[/proc/self/status]
B --> C{读取 NSpid: 行}
C -->|取第2列| D[设为 runtime.pid]
C -->|仅调用 getpid| E[错误设为 1]
3.3 /proc/PID/status中PPid指向host PID而runtime期望namespace内PID引发校验失败(procfs库解析逻辑逆向+自定义procfs.Reader验证)
问题根源定位
容器运行时(如runc)在调用procfs.NewStat()解析/proc/PID/status时,直接读取PPid:字段值——该值始终为宿主机PID命名空间视角下的父进程ID,而非容器内PID命名空间的相对PID。
procfs库解析逻辑逆向
// procfs/status.go 中关键片段(简化)
func (s *Status) parseLine(line string) error {
parts := strings.Fields(line)
if len(parts) < 2 || parts[0] != "PPid:" {
return nil
}
pid, err := strconv.ParseInt(parts[1], 10, 64) // ← 直接解析原始字符串,无ns转换
s.PPid = int(pid)
return err
}
parts[1]是/proc/PID/status中原始文本值(如PPid: 1234),procfs库未集成/proc/PID/status与/proc/PID/ns/pid上下文联动,导致PPid语义失真。
自定义Reader修复路径
- 构造
procfs.Reader子类,在ReadStatus()后注入pidns.TranslateHostToContainerPID() - 或预加载
/proc/PID/status+/proc/PID/status双源比对
| 字段 | 宿主机视角 | 容器内PID命名空间视角 |
|---|---|---|
PPid: |
1234 | 5 |
NSpid: |
1 5 | — |
graph TD
A[/proc/PID/status] --> B[PPid: 1234]
C[/proc/PID/status] --> D[NSpid: 1 5]
B --> E[误判父进程非init]
D --> F[正确映射容器内PPid=5]
第四章:SELinux策略深度干预下的syscall阻断链路
4.1 type enforcement规则禁止sys_readlinkat访问/proc/PID/exe(audit.log AVC denial解析+sesearch策略匹配)
当进程尝试通过 readlinkat(AT_FDCWD, "/proc/1234/exe", ...) 获取符号链接目标时,内核SELinux模块触发AVC拒绝:
type=AVC msg=audit(1715829304.123:456): avc: denied { read } for pid=1234 comm="ls" name="exe" dev="proc" ino=4026532001 scontext=u:r:unconfined_t:s0 tcontext=u:object_r:proc_pid_exe_t:s0 tclass=lnk_file permissive=0
该拒绝源于TE规则中缺失 allow unconfined_t proc_pid_exe_t:lnk_file { read };。
AVC关键字段解析
scontext: 调用进程的域类型(unconfined_t)tcontext: 目标文件的类型(proc_pid_exe_t)tclass: 对象类别(lnk_file)perm: 被拒权限(read)
策略匹配验证
使用 sesearch 检查现有规则:
sesearch -A -s unconfined_t -t proc_pid_exe_t -c lnk_file -p read
# 无输出 → 规则不存在
| 权限类型 | 是否允许 | 原因 |
|---|---|---|
read |
❌ | TE规则未显式授权 |
getattr |
✅ | proc_pid_t 类型有隐式继承 |
graph TD A[sys_readlinkat syscall] –> B{SELinux检查} B –> C[匹配scontext→tcontext→tclass→perm] C –> D[无allow规则 → AVC denial] D –> E[audit.log记录AVC]
4.2 domain transition失败导致go进程运行在unconfined_t而非container_t上下文(ps -Z确认+setcon()手动切换验证)
SELinux域过渡失败时,Go二进制在容器中启动后仍滞留于 unconfined_t,而非预期的 container_t。
验证当前上下文
# 查看进程SELinux标签
ps -Z | grep myapp
# 输出示例:unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 myapp
ps -Z 显示进程未完成域过渡,说明 execve() 未触发 container_runtime_t → container_t 的策略规则匹配。
手动切换验证
#include <selinux/selinux.h>
int main() {
return setcon("system_u:system_r:container_t:s0"); // 强制切换上下文
}
setcon() 成功表明内核支持该上下文,排除策略缺失,指向 entrypoint 或 docker run --security-opt 配置遗漏。
常见根因对比
| 原因 | 是否触发domain transition | 检查命令 |
|---|---|---|
缺失 type_transition 规则 |
否 | sesearch -s container_runtime_t -t container_file_t -c file -p entrypoint |
未启用 container_manage_cgroup |
否 | getsebool container_manage_cgroup |
graph TD
A[Go进程启动] --> B{SELinux策略匹配?}
B -->|否| C[保持unconfined_t]
B -->|是| D[transition→container_t]
C --> E[权限过大,违反最小特权]
4.3 selinuxfs接口/sys/fs/selinux/checkreqprot被禁用时runtime.getpid()返回-1的隐式fallback逻辑(/proc/self/attr/current读取失败路径跟踪)
当 selinuxfs 的 checkreqprot 被显式禁用(如写入 到 /sys/fs/selinux/checkreqprot),内核将拒绝通过 selinuxfs 提供的 security 接口服务,导致 Go 运行时在调用 runtime.getpid() 时触发 SELinux 上下文读取 fallback。
/proc/self/attr/current 读取失败路径
Go 运行时尝试读取 /proc/self/attr/current 获取当前线程 SELinux 上下文,若该文件 open 失败(ENODEV 或 EACCES),则 getpid 不再返回真实 PID,而是隐式返回 -1 以规避后续 context 验证逻辑。
// src/runtime/os_linux.go 中简化逻辑
func getpid() int32 {
fd := open("/proc/self/attr/current", _O_RDONLY, 0)
if fd < 0 {
return -1 // 隐式 fallback,非错误传播
}
close(fd)
return int32(syscall.Getpid())
}
此处
-1并非系统调用错误码,而是 runtime 内部约定的“SELinux 不可用”信号,用于跳过setcon(3)等安全上下文操作。
关键行为差异对比
| 场景 | /proc/self/attr/current 可读 |
checkreqprot=0 时行为 |
|---|---|---|
| 正常 SELinux 启用 | ✅ 返回真实 PID | ✅ 仍可读,getpid() 正常 |
checkreqprot=0 + securityfs 卸载 |
❌ open() 返回 ENODEV |
❌ 触发 -1 fallback |
graph TD
A[runtime.getpid()] --> B{open /proc/self/attr/current?}
B -- success --> C[return real PID]
B -- fail ENODEV/EACCES --> D[return -1]
D --> E[skip setcon/setfscreate]
4.4 container-selinux策略中allow docker_t proc_type:file read_link操作缺失(policycoreutils-seinfo分析+audit2why修复建议)
当容器内进程通过/proc/[pid]/exe等符号链接解析路径时,SELinux拒绝docker_t对proc_type的read_link访问,触发avc: denied { read_link }审计日志。
审计日志定位
# 从audit.log提取关键拒绝事件
ausearch -m avc -m user_avc -i | grep "docker_t.*proc_type.*read_link"
该命令过滤出docker_t域尝试读取proc_type文件链接的拒绝记录,是策略缺口的直接证据。
策略现状验证
# 检查当前策略是否定义该权限
seinfo -a allow -x | grep -E "docker_t.*proc_type.*read_link"
若无输出,表明container-selinux策略包(如policycoreutils-python-utils)未声明该allow规则。
修复建议对比
| 工具 | 输出示例 | 适用场景 |
|---|---|---|
audit2why |
read_link required for /proc/self/exe |
快速理解拒绝原因 |
audit2allow |
生成allow docker_t proc_type:file read_link; |
直接生成策略补丁 |
权限依赖关系
graph TD
A[docker_t] -->|needs| B[read_link]
B --> C[proc_type:file]
C --> D[/proc/*/exe, /proc/*/cwd]
缺失该规则将导致nsenter、runc exec等依赖/proc符号链接的调试操作失败。
第五章:综合诊断框架与生产环境防御性实践
构建可扩展的故障诊断流水线
在某电商大促期间,订单服务突发 50% 超时率。团队未依赖单点日志排查,而是触发预设的诊断流水线:自动采集 Prometheus 指标(HTTP 4xx/5xx、JVM GC 频次、线程池活跃度)、调用 OpenTelemetry 链路采样(TraceID 关联 32 个微服务节点)、同步拉取对应时间窗口的容器事件(OOMKilled、Liveness Probe Fail)。该流水线以 YAML 配置驱动,支持按服务名、错误码、延迟阈值动态组合诊断策略,12 分钟内定位到支付网关下游 Redis 连接池耗尽问题。
生产环境防御性检查清单
以下为已在金融级系统落地的强制性检查项(每日凌晨自动执行):
| 检查维度 | 检查项示例 | 失败响应动作 |
|---|---|---|
| 基础设施健康 | etcd 集群 leader 任期 > 2h | 触发 Slack 告警 + 自动重启 follower |
| 配置一致性 | 所有 Pod 的 configmap 版本号是否匹配 | 阻断新版本发布并回滚至前一版 |
| 安全基线 | /etc/passwd 中是否存在非 root UID 0 用户 | 立即隔离节点并生成 SOC 工单 |
| 流量防护 | Hystrix fallback 触发率连续 5 分钟 > 15% | 自动降级非核心接口并推送熔断拓扑图 |
实时流量染色与故障注入验证
使用 eBPF 编写的 trace_http 工具,在不修改应用代码前提下,对生产流量实施无感染色:所有来自 CDN 的请求头注入 X-Env-Trace: prod-canary,并通过 Istio EnvoyFilter 将其路由至灰度集群。同时,每小时在数据库连接池中注入 3% 的模拟连接超时(通过 LD_PRELOAD hook connect() 系统调用),验证服务降级逻辑是否正确返回兜底数据而非抛出 500 错误。过去三个月共捕获 7 次未覆盖的异常分支,其中 3 次导致缓存穿透风险被提前修复。
多维根因分析看板
基于 Grafana 构建的统一诊断看板集成四类数据源:
- 应用层:Spring Boot Actuator
/health状态聚合(含自定义RedisHealthIndicator延迟阈值告警) - 网络层:Cilium Network Policy 匹配日志(标记被拒绝的跨命名空间调用)
- 存储层:TiDB
SLOW_QUERY表实时解析(提取执行计划中的IndexRangeScan低效模式) - 安全层:Falco 检测到的异常进程行为(如
curl访问外网 API 的容器 PID)
当任意维度触发红灯状态,看板自动高亮关联路径,并展示历史相似故障的修复方案快照(从内部 GitLab Wiki API 动态拉取)。
flowchart LR
A[告警触发] --> B{是否满足多源证据收敛?}
B -->|是| C[启动根因推演引擎]
B -->|否| D[延长观测窗口 2min]
C --> E[匹配知识图谱中的故障模式]
E --> F[输出 Top3 排查路径]
F --> G[推送至运维终端 CLI]
可观测性数据生命周期治理
生产集群每日产生 18TB 原始日志,但仅 0.7% 被用于真实故障分析。实施分级存储策略:
- Level 0:全量 trace 数据保留 72 小时(对象存储冷归档)
- Level 1:错误日志 + 慢查询日志永久保留(Elasticsearch ILM 策略)
- Level 2:指标聚合数据保留 180 天(Thanos 对象存储压缩)
- Level 3:诊断流水线生成的结构化报告(JSON Schema 校验)保留 2 年,供审计追溯
每次诊断任务结束,自动执行 log_sweep --retain-pattern 'ERROR|timeout|panic' --compress-level 9 清理临时日志。
