【问题标题】:Can an unprivileged docker container be paused from the inside?可以从内部暂停非特权 docker 容器吗?
【发布时间】:2020-10-18 14:25:00
【问题描述】:

有没有一种简单的方法可以从内部完全暂停非特权 docker 容器,同时保留从外部取消暂停/执行它的能力?

【问题讨论】:

    标签: linux docker init


    【解决方案1】:

    TL;DR;

    在 linux 容器上,答案肯定是,,因为这两者是等价的:

    • 来自主机:
      docker pause [container-id]
      
    • 来自容器:
      kill -SIGSTOP [process(es)-id]
      
      或者,even shorter
      kill -SIGSTOP -1 
      
      请注意
      1. 如果您的进程 ID 或 PID1,那么您属于边缘情况,因为在 Linux 中,初始化进程 PID 1 确实具有特定含义 and behaviour
      2. 某些进程可能会产生子工作进程,如下面的 NGINX 示例。

    这两个也是等价的:

    • 来自主机:
      docker unpause [container-id]
      
    • 来自容器:
      kill -SIGCONT [process(es)-id]
      
      或者,even shorter
      kill -SIGCONT -1 
      

    还要注意,在某些边缘情况下,这不起作用。边缘情况是您的进程旨在捕获这两个信号,SIGSTOPSIGCONT,并忽略它们。

    在这些情况下,您将不得不

    • 或者,成为特权用户,因为the cgroup freezer 的使用在一个文件夹下,默认情况下,在 Docker 中只读,可能这会让你结束死路一条,因为你不能再跳进容器了。
    • ,使用标志--init 运行您的容器,这样PID 1 将只是一个由Docker 初始化的包装进程,您无需再暂停它为了暂停容器内运行的进程。

      您可以使用--init 标志来指示应该将一个init 进程用作容器中的PID 1。指定一个 init 进程可确保在创建的容器内执行 init 系统的通常职责,例如获取僵尸进程。

      使用的默认初始化进程是在 Docker 守护进程的系统路径中找到的第一个 docker-init 可执行文件。这个 docker-init 二进制文件包含在默认安装中,由 tini 提供支持。


    这对于 Linux 容器绝对是可能的,并且以某种方式在文档中进行了解释,他们指出运行 docker pause [container-id] 只是意味着 Docker 将使用等效机制将 SIGSTOP 信号发送到进程运行在您的容器中。

    docker pause 命令暂停指定容器中的所有进程。在 Linux 上,这使用 freezer cgroup。传统上,当挂起一个进程时,会使用SIGSTOP 信号,这可以通过被挂起的进程观察到。使用 freezer cgroup,该进程不知道也无法捕获它正在暂停并随后恢复。在 Windows 上,只能暂停 Hyper-V 容器。

    有关详细信息,请参阅freezer cgroup documentation

    来源:https://docs.docker.com/engine/reference/commandline/pause/

    下面是一个关于 NGINX Alpine 容器的示例:

    ### For now, we are on the host machine
    $ docker run -p 8080:80 -d nginx:alpine
    f444eaf8464e30c18f7f83bb0d1bd07b48d0d99f9d9e588b2bd77659db520524
    
    ### Testing if NGINX answers, successful 
    $ curl -I -m 1  http://localhost:8080/ 
    HTTP/1.1 200 OK
    Server: nginx/1.19.0
    Date: Sun, 28 Jun 2020 11:49:33 GMT
    Content-Type: text/html
    Content-Length: 612
    Last-Modified: Tue, 26 May 2020 15:37:18 GMT
    Connection: keep-alive
    ETag: "5ecd37ae-264"
    Accept-Ranges: bytes
    
    ### Jumping into the container
    $ docker exec -ti f7a2be0e230b9f7937d90954ef03502993857c5081ab20ed9a943a35687fbca4 ash
    
    ### This is the container, now, let's see the processes running
    / # ps -o pid,vsz,rss,tty,stat,time,ruser,args
    PID   VSZ  RSS  TT     STAT TIME  RUSER    COMMAND
        1 6000 4536 ?      S     0:00 root     nginx: master process nginx -g daemon off;
       29 6440 1828 ?      S     0:00 nginx    nginx: worker process
       30 6440 1828 ?      S     0:00 nginx    nginx: worker process
       31 6440 1828 ?      S     0:00 nginx    nginx: worker process
       32 6440 1828 ?      S     0:00 nginx    nginx: worker process
       49 1648 1052 136,0  S     0:00 root     ash
       55 1576    4 136,0  R     0:00 root     ps -o pid,vsz,rss,tty,stat,time,ruser,args
    
    ### Now let's send the SIGSTOP signal to the workers of NGINX, as docker pause would do
    / # kill -SIGSTOP 29 30 31 32
    
    ### Running ps again just to observer the T (stopped) state of the processes
    / # ps -o pid,vsz,rss,tty,stat,time,ruser,args
    PID   VSZ  RSS  TT     STAT TIME  RUSER    COMMAND
        1 6000 4536 ?      S     0:00 root     nginx: master process nginx -g daemon off;
       29 6440 1828 ?      T     0:00 nginx    nginx: worker process
       30 6440 1828 ?      T     0:00 nginx    nginx: worker process
       31 6440 1828 ?      T     0:00 nginx    nginx: worker process
       32 6440 1828 ?      T     0:00 nginx    nginx: worker process
       57 1648 1052 136,0  S     0:00 root     ash
       63 1576    4 136,0  R     0:00 root     ps -o pid,vsz,rss,tty,stat,time,ruser,args
    / # exit
    
    ### Back on the host to confirm NGINX doesn't answer anymore
    $ curl -I -m 1  http://localhost:8080/ 
    curl: (28) Operation timed out after 1000 milliseconds with 0 bytes received
    
    $ docker exec -ti f7a2be0e230b9f7937d90954ef03502993857c5081ab20ed9a943a35687fbca4 ash
    
    ### Sending the SIGCONT signal as docker unpause would do
    / # kill -SIGCONT 29 30 31 32
    / # ps -o pid,vsz,rss,tty,stat,time,ruser,args
    PID   VSZ  RSS  TT     STAT TIME  RUSER    COMMAND
        1 6000 4536 ?      S     0:00 root     nginx: master process nginx -g daemon off;
       29 6440 1828 ?      S     0:00 nginx    nginx: worker process
       30 6440 1828 ?      S     0:00 nginx    nginx: worker process
       31 6440 1828 ?      S     0:00 nginx    nginx: worker process
       32 6440 1828 ?      S     0:00 nginx    nginx: worker process
       57 1648 1052 136,0  S     0:00 root     ash
       62 1576    4 136,0  R     0:00 root     ps -o pid,vsz,rss,tty,stat,time,ruser,args 29 30 31 32
    / # exit
    
    ### Back on the host to confirm NGINX is back
    $ curl -I http://localhost:8080/       
    HTTP/1.1 200 OK
    Server: nginx/1.19.0
    Date: Sun, 28 Jun 2020 11:56:23 GMT
    Content-Type: text/html
    Content-Length: 612
    Last-Modified: Tue, 26 May 2020 15:37:18 GMT
    Connection: keep-alive
    ETag: "5ecd37ae-264"
    Accept-Ranges: bytes
    

    对于有意义的进程是PID 1is protected by the Linux kernel 的情况,您可能想在容器运行时尝试--init 标志,这样Docker 将创建一个能够将信号传递给您的应用程序。

    $ docker run -p 8080:80 -d --init nginx:alpine                                        
    e61e9158b2aab95007b97aa50bc77fff6b5c15cf3b16aa20a486891724bec6e9
    $ docker exec -ti e61e9158b2aab95007b97aa50bc77fff6b5c15cf3b16aa20a486891724bec6e9 ash
    
    / # ps -o pid,vsz,rss,tty,stat,time,ruser,args
    PID   VSZ  RSS  TT     STAT TIME  RUSER    COMMAND
        1 1052    4 ?      S     0:00 root     /sbin/docker-init -- /docker-entrypoint.sh nginx -g daemon off;
        7 6000 4320 ?      S     0:00 root     nginx: master process nginx -g daemon off;
       31 6440 1820 ?      S     0:00 nginx    nginx: worker process
       32 6440 1820 ?      S     0:00 nginx    nginx: worker process
       33 6440 1820 ?      S     0:00 nginx    nginx: worker process
       34 6440 1820 ?      S     0:00 nginx    nginx: worker process
       35 1648    4 136,0  S     0:00 root     ash
       40 1576    4 136,0  R     0:00 root     ps -o pid,vsz,rss,tty,stat,time,ruser,args
    

    看看之前用例中的PID 1nginx: master process nginx -g daemon off; 现在如何变成PID 7
    这使我们能够kill -SIGSTOP -1 并确保所有有意义的进程都已停止,但我们仍不会被锁定在容器之外。


    在研究这个问题时,我发现这篇博文似乎是关于该主题的好读物:https://major.io/2009/06/15/two-great-signals-sigstop-and-sigcont/

    还与ps 手册页有关进程状态代码的摘录:

      Here are the different values that the s, stat and state output
      specifiers (header "STAT" or "S") will display to describe the state
      of a process:
    
              D    uninterruptible sleep (usually IO)
              I    Idle kernel thread
              R    running or runnable (on run queue)
              S    interruptible sleep (waiting for an event to complete)
              T    stopped by job control signal
              t    stopped by debugger during the tracing
              W    paging (not valid since the 2.6.xx kernel)
              X    dead (should never be seen)
              Z    defunct ("zombie") process, terminated but not reaped by
                   its parent
    
      For BSD formats and when the stat keyword is used, additional
      characters may be displayed:
    
              <    high-priority (not nice to other users)
              N    low-priority (nice to other users)
              L    has pages locked into memory (for real-time and custom
                   IO)
              s    is a session leader
              l    is multi-threaded (using CLONE_THREAD, like NPTL
                   pthreads do)
              +    is in the foreground process group
    

    来源https://man7.org/linux/man-pages/man1/ps.1.html#PROCESS_STATE_CODES

    【讨论】:

    • 优秀的答案。您包含的分步示例清晰且非常有帮助。你知道我也可以停止 pid 1 吗?发送sigstop 似乎没有任何作用。
    • 另外,您如何提供更多关于我如何从非特权容器中与冷冻机 cgroup 交互的信息?
    • 我想知道PID 1 是否不是这种边缘情况之一,让我测试一下 cgroup 设置,然后将其添加到答案中。
    • @TendersMcChiken 总结了已编辑的答案:可悲的是,要“弄乱” cgroup 冰箱,您必须享有特权。但是,希望还是有的!如果您使用--init 标志运行容器,那么您将能够将SIGSTOP 信号发送到所有有意义的进程。
    【解决方案2】:

    docker pause 内部命令对于非特权容器是不可能的。它需要通过挂载套接字来访问 docker 守护进程。

    您需要构建自定义解决方案。只是基本的想法:您可以从主机绑定一个文件夹。在此文件夹中,您创建一个用作锁的文件。因此,当您在容器内暂停时,您将创建文件。当文件存在时,您会主动等待/睡眠。一旦主机删除已挂载的路径上的文件,您的代码就会恢复。这是一种相当幼稚的方法,因为您会主动等待,但它应该可以解决问题。

    您还可以研究 inotify 以克服激活等待。 https://lwn.net/Articles/604686/

    【讨论】:

    • 您确实无法运行docker pause,但您可以完全按照docker pause 的方式运行。
    • 你的答案就是要走的路:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-25
    • 1970-01-01
    • 2015-03-08
    • 1970-01-01
    • 1970-01-01
    • 2022-06-15
    • 2023-01-28
    相关资源
    最近更新 更多