茱莉亚·伊万斯

如何监视在容器中运行的程序??

昨天我给rbspy添加了Linux容器支持,这样在主机上运行的rbspy实例就可以分析运行incontainers的Ruby程序。

这是一件非常简单的事情(~50行代码,,https://github.com/rbspy/rbspy/pull/68)但我想解释一下添加什么会很有趣集装箱支座参与实践!!

为什么rbspy以前没有用过容器??

首先,在容器中运行的程序只是程序,和其他节目一样!您可以通过运行来查看它们聚苯乙烯.因此,rbspy所做的所有正常事情(比如从进程读取内存)对于在容器中运行的程序都工作得很好。只有一个小问题。

在程序的开头有一小部分,它从程序的内存映射中读取1或2个二进制文件(Ruby二进制文件,有时还有另一个动态链接库)。

具体来说,现在我的计算机上有一个运行Ruby程序的容器。它的pid(在主机PID命名空间中)是17474。查看它的内存映射(使用sudo cat/proc/17474/map)显示它正在运行的Ruby二进制文件是/usr/bin/ruby1.9.1它有一个ruby库,加载名为/usr/lib/libruby-1.9.1.so.1.9.1.

00000000000000000401000r-xp00000000000000000000000000000000000000000001413644r-0000000000000000000000000000001413644..us r/宾/ruby1.13644 13644 13644.1.1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000:14 13648/usr/lib/libruby-1.9.1.so.1.9.17f1d45b77000-7f1d45d76000 ---p 001f6000 00:14 13648                      /usr/lib/libruby-1.9.1.so.1.9.17f1d45d76000-7f1d45d7b000 r--p 001f5000 00:14 13648                      /usr/lib/libruby-1.9.1.so.1.9.17f1d45d7b000-7f1d45d7f000 rw-p 001fa000 00:14 13648                      /usr/lib/libruby-1.9.1.so.1.9.1

我需要这两个二进制文件都映射到进程的内存中的地址(简单!只要看看/proc/17474/map,完成)以及它们的内容。

获取它们的内容是我们遇到问题的地方。/usr/bin/ruby1.9.1不是myhost文件系统上的文件。我在运行Ubuntu 16.04,容器正在运行Ubuntu 14.04,他们有不同的系统Ruby版本。

这里的精确问题是目标进程(在容器中)具有不同的挂载命名空间比RBSPY。

如何修复:切换挂载名称空间!!

因此,要使rbspy与容器化过程一起工作,我们只需要在读取之前切换到目标进程的smount名称空间/usr/bin/ruby1.9.1/usr/lib/libruby-1.9.1.so.1.9.1.然后读完这两个文件,立即切换回前一个挂载名称空间,以便我们能够在正确的位置将输出写入文件系统。

这很简单!这里有一些伪代码:

setns(目标进程的名称空间,““山”read(/usr/bin/ruby1.9.1)read(/usr/lib/libruby-1.9.1.so.1.9.1)setns(,““山”)

实际代码的样子

切换挂载名称空间并不难。为了切换挂载名称空间,我只需要:

  1. 打开文件/proc/$PID/ns/mnt.获取该文件的文件描述符。
  2. 呼叫LBC::StnS(FD,libc::CLONE_NEWNS).出于某种原因克隆人新闻意味着“挂载命名空间.

真的很简单!还有3件事要注意/注意:

  • 记得打开/proc/self/ns/mnt 之前切换挂载名称空间,所以我可以使用一个文件描述符来切换回旧的挂载命名空间
  • 确保我总是切换回旧的挂载命名空间,即使读取文件时出现错误。我这样做的推迟!!.(就像围棋)推迟关键字,我用过的那个是锈箱,叫范围保护器
  • 进程的挂载命名空间的ID是/proc/pid/ns/mnt.我可以使用该inode编号来确定两个进程是否在相同的挂载命名空间中

这里有一个代码片段:

让._proc_mnt=&format!(“/proc/{}/ns/mnt”,PID);让self_proc_mnt="[proc/self/ns/mnt];//在切换名称空间slet all_maps=proc_maps(pid)之前,我们需要获取`/proc/$PID/maps'?;//读取inode号以检查两个挂载名称空间是否是sameif fs::metadata(._proc_mnt)?st_ino() ==fs::元数据(self_proc_mnt)?st_ino(){get_._info_.(pid,all_maps)}.{//switch mount namespace,然后在获取程序info//之后切换回来我们需要保存当前mount命名空间的fd,这样我们可以切换回来,让new_ns=fs::File::open(._proc_mnt)?;让old_ns=fs::File::open(self_proc_mnt)?;._ns(&new_ns)?;//如果在任何点有错误,总是切换回旧的名称空间——.r!({._ns(&old_ns);(});设proginfo=get_._info_.(pid,阿尔图);进程信息

如果您是多线程的,则不能切换挂载名称空间

最初,我想创建一个单独的线程来完成名称空间切换。但这是不可能的:男性页面把我弄直。

如果进程是多线程的,那么它不可能与新的挂载命名空间重新关联。

手册页开头的setn描述如下setns-与名称空间重新关联.所以我想你可以如果您是多线程的,那么更改其他类型的名称空间(比如,我想您可以更改单个线程的网络名称空间?)您无法更改挂载名称空间。很好!!

实际上,您甚至不需要切换挂载名称空间!!

在我张贴这篇文章之后,某人在Twitter上非常有益地指出读取文件/usr/bin/ruby1.9.1从进程的挂载名称空间中,您可以直接读取/proc/PID/root/usr/bin/ruby1.9.1.这比切换挂载名称空间更容易!!

这里是什么[proc]手册页说关于/PRO/PID/根

但是,请注意,该文件不仅仅是一个符号链接。它提供了与进程本身相同的文件系统视图(包括名称空间和每进程挂载集)。

就是这样!!

我认为这是一个很好的例子,说明了如何理解容器的基本工作原理(它们使用与主机进程不同的Linux名称空间,在这种情况下,这个挂载名称空间就是相关的名称空间,我们不关心其他名称空间)帮助我们轻松地添加容器支持。

我们不需要关心Docker之类的东西——这与我们的容器使用的容器运行时间无关,我们当然不会和Docker混在一起。我们只需要做一些简单的系统调用,它工作!!

对此有疑问/想法吗??这里有一条twitter消息!!