31

There are several processes running in a Docker container, their PIDs are isolated in the container namespace, is there a way to figure out what are their PIDs on the Docker host?

For example there is an Apache web server running inside a Docker container, (I use Apache+PHP image from Docker Hub), and the Apache, when it starts, creates more worker processes inside the container. Those worker processes are actually handling incoming requests. To view these processes I run pstree inside the docker container:

# pstree -p 1
apache2(1)-+-apache2(8)
           |-apache2(9)
           |-apache2(10)
           |-apache2(11)
           |-apache2(12)
           `-apache2(20)

The parent Apache process runs on PID 1 inside of the container process namespace. However from the host's perspective it can be also accessed, but its PID on the host is different and can be determined by running docker compose command:

 $ docker inspect --format '{{.State.Pid}}' container
 17985

From this we can see that the PID 1 from within the container process namespace maps to PID 17985 on the host. So I can run pstree on the host, to list the children of the Apache process:

$ pstree -p 17985
apache2(17985)─┬─apache2(18010)
               ├─apache2(18011)
               ├─apache2(18012)
               ├─apache2(18013)
               ├─apache2(18014)
               └─apache2(18164)

From this I assume that the same way how PID 1 in the container maps to PID 17985 on the host, it also maps:

  • PID 8 in container to PID 18010 on host, and
  • PID 9 to PID 18011;
  • PID 10 to PID 18012 and so on...

(This allows me to debug the processes from docker container, using tools that are only available only on the host, and not the in the container, like strace)

The problem is that I don't know how safe is to assume that pstree lists the processes in the same order both in the container and in the host.

Would be great if someone could suggest a more reliable way to detect what is a PID on the host of a specific process running inside the Docker container.

Luke 10X
  • 1,071
  • 2
  • 14
  • 30
  • The pids can also mean OS Threads, at least on linux. You can check with java (or other language) by creating a number of threads and counting the PIDS you get. – ieugen Jan 09 '18 at 15:00

3 Answers3

40

You can look at the /proc/<pid>/status file to determine the mapping between the namespace PID and the global PID. For example, if in a docker container I start several sleep 900 processes, like this:

# docker run --rm -it alpine sh
/ # sleep 900 &
/ # sleep 900 &
/ # sleep 900 &

I can see them running in the container:

/ # ps -fe
PID   USER     TIME   COMMAND
    1 root       0:00 sh
    7 root       0:00 sleep 900
    8 root       0:00 sleep 900
    9 root       0:00 sleep 900
   10 root       0:00 ps -fe

I can look at these on the host:

# ps -fe | grep sleep
root     10394 10366  0 09:11 pts/10   00:00:00 sleep 900
root     10397 10366  0 09:12 pts/10   00:00:00 sleep 900
root     10398 10366  0 09:12 pts/10   00:00:00 sleep 900

And for any one of those, I can look at the status file to see the namespace pid:

# grep -i pid /proc/10394/status
Pid:    10394
PPid:   10366
TracerPid:  0
NSpid:  10394   7

Looking at the NSpid line, I can see that within the PID namespace this process has pid 7. And indeed, if I kill process 10394 on the host:

# kill 10394

Then in the container I see that PID 7 is no longer running:

/ # ps -fe
PID   USER     TIME   COMMAND
    1 root       0:00 sh
    8 root       0:00 sleep 900
    9 root       0:00 sleep 900
   11 root       0:00 ps -fe
Benoit Duffez
  • 11,839
  • 12
  • 77
  • 125
larsks
  • 277,717
  • 41
  • 399
  • 399
  • 2
    Looks like it solves my problem, following this advice I can retrieve full mapping of PIDs with this one-liner: `for i in $(ps -ef | grep $(docker inspect --format '{{.State.Pid}}' php-sandbox) | awk '{print $2}') ; do grep NSpid: /proc/$i/status ; done` – Luke 10X Oct 08 '16 at 15:26
  • 4
    Note that NSpid is only available since Linux 4.1. so this might not work on older machines. As an alternative, you can find the in-container pid by docker exec'ing grep -l pid /proc/*/sched. – Gordon May 15 '18 at 12:17
  • This doesn't seem to work on Mac. The pid given by `docker top` command doesn't exist when I run `ps`. Secondly there's no /proc on Mac. – Parth Thakkar Feb 01 '19 at 12:48
  • 1
    That's because on a Mac, you're running Docker in a Linux VM. If you were to log into the Linux VM you would see the behavior described here. – larsks Feb 01 '19 at 12:50
  • gr8 Q&A&C. this worked for me - `for i in $(ps -ef | grep \`docker inspect --format '{{.State.Pid}}' containerID\` | awk '{print $2}') ; do grep NSpid: /proc/$i/status ; done`. can anyone suggest why the orginal did not work . – samshers Aug 15 '19 at 17:24
  • Seems not working on CentOS 8, kernel 4.18. Neither in the status nor sched file can see host pid. – TangXC Oct 14 '20 at 12:28
4

If you know either the host pid or the container pid you can find by searching all NSpid maps on the host like so:

# grep NSpid.*10061 /proc/*/status 2> /dev/null
/proc/1194200/status:NSpid: 1194200 10061
  • 1194200 is the host pid
  • 10061 is the container pid

The 2>/dev/null is to ignore short-lived processes that cause grep errors like this: grep: /proc/1588467/status: No such file or directory

KJ7LNW
  • 1,437
  • 5
  • 11
  • The answer is correct and short, but essentially the same principle as in @larsks answer which is actually more detailed. I this up for its brevity, but I leave the same answer as accepted for now. Thank you – Luke 10X Nov 15 '21 at 15:04
  • While, in practice, it might be close enough for a human user, one shouldn't use this solution in scripts. There's no guarantee that there won't be another PID with the 10061 suffix, which may produce two or more results instead of one. – matt Feb 09 '23 at 09:01
0
  • call docker sdk api ContainerList get container_id map docker image
  • read /proc/<pid>/cgroup get the docker container_id
  • you can get pid, container_id,docker image
func GetContainerID(pid int32) string {
    cgroupPath := fmt.Sprintf("/proc/%s/cgroup", strconv.Itoa(int(pid)))
    return getContainerID(cgroupPath)
}

func GetImage(containerId string) string {
    if containerId == "" {
        return ""
    }
    image, ok := containerImage[containerId]
    if ok {
        return image
    } else {
        return ""
    }
}
func getContainerID(cgroupPath string) string {
    containerID := ""
    content, err := ioutil.ReadFile(cgroupPath)
    if err != nil {
        return containerID
    }
    lines := strings.Split(string(content), "\n")
    for _, line := range lines {
        field := strings.Split(line, ":")
        if len(field) < 3 {
            continue
        }
        cgroup_path := field[2]
        if len(cgroup_path) < 64 {
            continue
        }
        // Non-systemd Docker
        //5:net_prio,net_cls:/docker/de630f22746b9c06c412858f26ca286c6cdfed086d3b302998aa403d9dcedc42
        //3:net_cls:/kubepods/burstable/pod5f399c1a-f9fc-11e8-bf65-246e9659ebfc/9170559b8aadd07d99978d9460cf8d1c71552f3c64fefc7e9906ab3fb7e18f69
        pos := strings.LastIndex(cgroup_path, "/")
        if pos > 0 {
            id_len := len(cgroup_path) - pos - 1
            if id_len == 64 {
                //p.InDocker = true
                // docker id
                containerID = cgroup_path[pos+1 : pos+1+64]
                // logs.Debug("pid:%v in docker id:%v", pid, id)
                return containerID
            }
        }
        // systemd Docker
        //5:net_cls:/system.slice/docker-afd862d2ed48ef5dc0ce8f1863e4475894e331098c9a512789233ca9ca06fc62.scope
        docker_str := "docker-"
        pos = strings.Index(cgroup_path, docker_str)
        if pos > 0 {
            pos_scope := strings.Index(cgroup_path, ".scope")
            id_len := pos_scope - pos - len(docker_str)
            if pos_scope > 0 && id_len == 64 {
                containerID = cgroup_path[pos+len(docker_str) : pos+len(docker_str)+64]
                return containerID
            }
        }
    }
    return containerID
}

The golang code with get the pid container

wcc526
  • 3,915
  • 2
  • 31
  • 29