Docker CLI 中 `docker container top` 命令详解:透传 ps 选项查看容器内进程

原创2026-10-09 14:38:471,685 阅读
文章标签:CLI开发工具容器运行时

Docker CLI 中 docker container top 命令详解:透传 ps 选项查看容器内进程

docker container top 是 Docker CLI 用于查看容器内正在运行的进程的命令。它的核心机制是把用户传入的 ps 选项原样透传给容器运行时执行,而输出中的所有信息(包括 PID)都是从宿主机视角呈现的。读完后,你将掌握该命令的完整语法、ps 选项的传递规则,以及从源码层面理解 CLI 如何解析参数、调用 Engine API 并渲染进程列表的完整调用链。

命令定位与官方语义

top 命令的 man 页位于 man/src/container/top.md,其核心描述只有两句话,但信息量很大:

Display the running process of the container. ps-OPTION can be any of the options you would pass to a Linux ps command.

All displayed information is from host's point of view.

这两句话定义了命令的两个关键语义:

  1. ps 选项完全透传:[ps OPTIONS] 部分可以是你想传给 Linux ps 命令的任何选项(例如 -x、-ef、-f 等),CLI 本身不做解析和过滤,全部交给容器运行时处理。
  2. 宿主机视角:输出的所有信息(尤其是 PID 列)是宿主机视角下的值,即容器内进程在宿主机的进程表中显示的 PID,而不是容器内部从 1 开始计数的 PID。

命令的文档页还包括一个示例:使用 -x 选项执行 top:

$ docker container top 8601afda2b -x
    PID      TTY       STAT       TIME         COMMAND
    16623    ?         Ss         0:00         sleep 99999

-x 是 ps 的选项,作用是显示没有终端关联的进程(默认 ps 只显示有终端的进程);输出中的 16623 即为该进程在宿主机上的 PID。

命令语法与别名

命令在 CLI 中的定义为 docker container top 的文档 与 docker top 的文档 所对应:

Usage:  docker container top CONTAINER [ps OPTIONS]

Aliases: docker container top, docker top

注意两个细节:

  • CONTAINER 必填:至少需要一个参数(容器 ID 或名称),第一个位置参数总是被解释为容器。
  • [ps OPTIONS] 是位置参数而非 flag:ps 的选项(如 -x、-ef)直接写在容器 ID 之后,它们不是 docker 命令自己的 flag,而是作为位置参数透传。
  • 两个别名等价:docker top xxx -x 与 docker container top xxx -x 完全相同。

源码解析:参数如何被解析和透传

实现位于 cli/command/container/top.go。该命令的 cobra 定义非常精炼:

cmd := &cobra.Command{
    Use:   "top CONTAINER [ps OPTIONS]",
    Short: "Display the running processes of a container",
    Args:  cli.RequiresMinArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        opts.container = args[0]
        opts.args = args[1:]
        return runTop(cmd.Context(), dockerCLI, &opts)
    },
    Annotations: map[string]string{
        "aliases": "docker container top, docker top",
    },
    ValidArgsFunction:     completion.ContainerNames(dockerCLI, false),
    DisableFlagsInUseLine: true,
}

flags := cmd.Flags()
flags.SetInterspersed(false)

可以看到几个关键设计(见 top.go#L23-L46):

  1. 参数切分规则:args[0] 固定是容器,args[1:] 全部进入 opts.args,作为 ps 的选项整体交给后续 API 调用。这就是为什么 ps 选项必须写在容器 ID 之后的原因。
  2. flags.SetInterspersed(false):关闭 flag 与位置参数的交错解析。这意味着在 CLI 层面,docker container top -x 8601afda2b 这种把 -x 写在容器 ID 之前的写法不会把 -x 当作 ps 选项处理——-x 会被 cobra 尝试解析为命令 flag 本身,而 top 并未注册该 flag。这也是命令使用 DisableFlagsInUseLine 且不带任何自有 flag 的原因:top 命令本身没有任何 flag,一切位置参数即 ps 选项。
  3. 容器名补全:ValidArgsFunction: completion.ContainerNames(dockerCLI, false) 注册了 shell 补全函数,输入容器名前缀按 Tab 可补全容器名称/ID(false 表示只补全运行中的容器相关的候选,具体行为由 completion.ContainerNames 实现)。

底层调用链:CLI 如何拿到进程列表

runTop 的完整实现见 top.go#L48-L64:

func runTop(ctx context.Context, dockerCli command.Cli, opts *topOptions) error {
    procList, err := dockerCli.Client().ContainerTop(ctx, opts.container, client.ContainerTopOptions{
        Arguments: opts.args,
    })
    if err != nil {
        return err
    }

    w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
    _, _ = fmt.Fprintln(w, strings.Join(procList.Titles, "\t"))

    for _, proc := range procList.Processes {
        _, _ = fmt.Fprintln(w, strings.Join(proc, "\t"))
    }
    _ = w.Flush()
    return nil
}

调用链可以概括为三步:

1. 通过 Engine API 客户端发起请求

dockerCli.Client().ContainerTop(...) 是 moby client 的方法,位于 vendor/github.com/moby/moby/client/container_top.go。其核心逻辑:

query := url.Values{}
if len(options.Arguments) > 0 {
    query.Set("ps_args", strings.Join(options.Arguments, " "))
}

resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil)

即 CLI 向 Engine 发送 GET /containers/{containerID}/top?ps_args=... 请求,所有 ps 选项被空格拼接后作为 ps_args 查询参数传递。从源码结构看,实际执行 ps 的动作发生在 Engine(守护进程)侧,CLI 只负责组装请求参数、接收结果并渲染——这也解释了"所有信息显示都是宿主机视角"这一行为:PID 等进程信息天然来自宿主机的进程表。

2. 解析响应结构

API 的响应体由 swagger 生成的类型 TopResponse 描述:

// TopResponse ContainerTopResponse
type TopResponse struct {
    // Each process running in the container, where each process
    // is an array of values corresponding to the titles.
    // Example: {"Processes":[["root","13642","882","0","17:03","pts/0","00:00:00","/bin/bash"], ...]}
    Processes [][]string `json:"Processes"`

    // The ps column titles
    // Example: {"Titles":["UID","PID","PPID","C","STIME","TTY","TIME","CMD"]}
    Titles []string `json:"Titles"`
}

结构非常直白:Titles 是列标题数组,Processes 是二维数组,每个内层数组对应一行进程,且每个元素与 Titles 中的列一一对应。CLI 端把它转换为 ContainerTopResult{Processes, Titles} 后返回。

3. 表格渲染

回到 runTop,CLI 使用 tabwriter 以制表符为分隔符把 Titles 和各进程行逐行写入输出流:Titles 用 \t 连接打印为表头,每个 proc 数组也用 \t 连接打印为一行数据,最后 Flush 对齐输出。这就是你在终端看到的列对齐表格的来源,例如 man 页示例中的 PID TTY STAT TIME COMMAND 表头加一行 sleep 99999 进程。

实操要点与常见误区

结合 man 页与源码,使用 docker container top 时需要注意:

  • ps 选项必须紧跟容器 ID 之后:docker container top <ID> -ef。由于 CLI 关闭了 flag/位置参数交错解析(flags.SetInterspersed(false)),把 -ef 放在容器 ID 之前会被 cobra 当作未定义的命令 flag 而报错。
  • ps 选项的有效性由运行时保证:CLI 对 ps_args 不做校验,原样拼接后传给 Engine(见 client/container_top.go#L30-L33)。因此如果传入的 ps 选项无效,错误会由 Engine 侧返回。
  • PID 是宿主机 PID:文档明确"All displayed information is from host's point of view",所以 top 输出中的 PID 可以直接在宿主机上用于 ps -p <PID>、top -p <PID> 等排查,与 docker inspect 看到的 State.Pid 是同一个 PID。
  • 不传 ps 选项也合法:docker container top <ID> 等价于不设置 ps_args(查询参数为空时 Engine 使用默认 ps 行为),输出列取决于 Engine 返回的 Titles。
  • 容器需处于运行状态:只有运行中的容器才有进程可列,对已停止的容器执行 top 会得到 Engine 侧的错误。

相关命令

top 与容器进程排查相关的常用命令配合使用:

  • docker container logs:查看进程输出日志;
  • docker container inspect:查看容器状态及 State.Pid(与 top 输出中 PID 同源,见 inspect 文档);
  • docker container exec:进入容器执行命令进一步排查(见 exec 文档)。

小结

docker container top 的设计体现了 Docker CLI 一贯的"透传"哲学:命令自身不注册任何 flag,CONTAINER 之后的所有位置参数原样拼接为 Engine API 的 ps_args 参数,最终渲染由 Engine 返回的 Titles/Processes 数据直接驱动。man 页的简短描述(ps 选项透传 + 宿主机视角)在 cli/command/container/top.go 的参数切分、moby client 的 ContainerTop 实现 的 ps_args 拼装以及 TopResponse 类型定义 中都能找到一一对应的代码证据。理解了这条从位置参数到 REST 查询参数再到表格输出的完整链路,你就能准确构造任意 ps 组合来排查容器内进程,并正确解读输出中的宿主机 PID。

登录后查看全文
cli