With CLONE_NEWPID, the child process becomes PID 1 in its own namespace:
func child() {
fmt.Printf("PID as seen inside: %d\n", os.Getpid()) // prints 1
// Set hostname in our new UTS namespace
syscall.Sethostname([]byte("container"))
// Run the user's command
cmd := exec.Command(os.Args[2], os.Args[3:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
}
Inside a PID namespace, your process is PID 1 — the init process. This has consequences:
This is why containers need proper signal handling — and why tini or dumb-init exist.
From the host, you can inspect namespaces:
# See namespaces of a process
ls -la /proc/<pid>/ns/
# Enter a running container's namespaces
nsenter --target <pid> --mount --uts --ipc --net --pid
This is what docker exec and kubectl exec do under the hood.