freesky-edward

runc source code——mounts propagation

published on 27 Jun 2019.

这篇文章我们主要来研究runc中的文件系统mounts操作,如前面的rootfs流程介绍,文件系统的准备主要是在

  1. prepareRootfs
  2. finalizeRootfs

我们使用runc debug,在关键步骤加上日志先看看都执行了那些mount操作,然后根据假设做一些测试。

在关键代码中加上日志,主要有两个地方,一个是prepareRoot,另一个是

func  prepareRoot(config  *configs.Config)  error  {
  flag  :=  unix.MS_SLAVE  |  unix.MS_REC
  if  config.RootPropagation  !=  0  {
      flag  =  config.RootPropagation
  }

  logrus.Debug(fmt.Sprintf("mount  root  dest=/  flag=%s",  flag))
  if  err  :=  unix.Mount("",  "/",  "",  uintptr(flag),  "");  err  !=  nil  {
      return  err
  }

  //  Make  parent  mount  private  to  make  sure  following  bind  mount  does
  //  not  propagate  in  other  namespaces.  Also  it  will  help  with  kernel
  //  check  pass  in  pivot_root.  (IS_SHARED(new_mnt->mnt_parent))
  if  err  :=  rootfsParentMountPrivate(config.Rootfs);  err  !=  nil  {
      return  err
  }

  logrus.Debug(fmt.Sprintf("mount  rootfs  dest=%s  source=%s  flag=%s  device=bind",  
     config.Rootfs,  config.Rootfs,  (unix.MS_BIND  |  unix.MS_REC)))
  return  unix.Mount(config.Rootfs,  config.Rootfs,  "bind",  unix.MS_BIND|unix.MS_REC,  "")
}
//  Do  the  mount  operation  followed  by  additional  mounts  required  to  take  care

//  of  propagation  flags.

func  mountPropagate(m  *configs.Mount,  rootfs  string,  mountLabel  string)  error  {
  
  ......
 
  logrus.Debug(fmt.Sprintf("start  mount  dest=%s  source=%s  flags=%s  data=%s  device=%s  
    Propagationflags=%s",  dest,  m.Source,  uintptr(flags),  data,  m.Device,  m.PropagationFlags))

  if  err  :=  unix.Mount(m.Source,  dest,  m.Device,  uintptr(flags),  data);  err  !=  nil  {
      return  err
  }

  
  for  _,  pflag  :=  range  m.PropagationFlags  {
      logrus.Debug(fmt.Sprintf("PropagationFlags  flag=%s  dest=%s",  pflag,  dest))
      if  err  :=  unix.Mount("",  dest,  "",  uintptr(pflag),  "");  err  !=  nil  {
          return  err
      }
  }
  return  nil
}

重新编译runc,并安装

cd $GOPATH/src/github.com/opencontainer/runc
make
make install

运行容器时配置log

runc --debug --log /var/log/runc.log run container1

查看执行日志:

cat /var/log/runc.log

"mount root  dest=/ flag=%!!(MISSING)s(int=540672)"
"mount rootfs  dest=/home/slob/runc/mycontainer/rootfs source=/home/slob/runc/mycontainer/rootfs flag=s(int=20480) device=bind"
"start mount dest=/proc source=proc flags=s(uintptr=0) data= device=proc Propagationflags=[]"
"start mount dest=/dev source=tmpfs flags=s(uintptr=16777218) data=mode=755,size=65536k device=tmpfs Propagationflags=[]"
"start mount dest=/home/slob/runc/mycontainer/rootfs/dev/pts source=devpts flags=(uintptr=10) data=newinstance,ptmxmode=0666,mode=0620,gid=5 device=devpts Propagationflags=[]"
"start mount dest=/dev/shm source=shm flags=%!!(MISSING)s(uintptr=14) data=mode=1777,size=65536k device=tmpfs Propagationflags=[]"
"start mount dest=/dev/mqueue source=mqueue flags=%!!(MISSING)s(uintptr=14) data= device=mqueue Propagationflags=[]"
"start mount dest=/sys source=sysfs flags=%!!(MISSING)s(uintptr=15) data= device=sysfs Propagationflags=[]"
"start mount dest=/sys/fs/cgroup source=tmpfs flags=%!!(MISSING)s(uintptr=14) data=mode=755 device=tmpfs Propagationflags=[]"
"start mount dest=/home/slob/runc/mycontainer/rootfs/sys/fs/cgroup/systemd source=/sys/fs/cgroup/systemd/user.slice/user-0.slice/session-8126.scope/container2 flags=%!!(MISSING)s(uintptr=2117647) data= device=bind Propagationflags=[]"
"start mount dest=/home/slob/runc/mycontainer/rootfs/sys/fs/cgroup/blkio source=/sys/fs/cgroup/blkio/user.slice/user-0.slice/session-8126.scope/container2 flags=%!!(MISSING)s(uintptr=2117647) data= device=bind Propagationflags=[]"
"start mount dest=/home/slob/runc/mycontainer/rootfs/sys/fs/cgroup/pids source=/sys/fs/cgroup/pids/user.slice/user-0.slice/session-8126.scope/container2 flags=%!!(MISSING)s(uintptr=2117647) data= device=bind Propagationflags=[]"
"start mount dest=/home/slob/runc/mycontainer/rootfs/sys/fs/cgroup/perf_event source=/sys/fs/cgroup/perf_event/container2 flags=%!!(MISSING)s(uintptr=2117647) data= device=bind Propagationflags=[]"
"start mount dest=/home/slob/runc/mycontainer/rootfs/sys/fs/cgroup/memory source=/sys/fs/cgroup/memory/user.slice/user-0.slice/session-8126.scope/container2 flags=%!!(MISSING)s(uintptr=2117647) data= device=bind Propagationflags=[]"
"start mount dest=/home/slob/runc/mycontainer/rootfs/sys/fs/cgroup/net_cls,net_prio source=/sys/fs/cgroup/net_cls,net_prio/container2 flags=%!!(MISSING)s(uintptr=2117647) data= device=bind Propagationflags=[]"
"start mount dest=/home/slob/runc/mycontainer/rootfs/sys/fs/cgroup/cpu,cpuacct source=/sys/fs/cgroup/cpu,cpuacct/user.slice/user-0.slice/session-8126.scope/container2 flags=%!!(MISSING)s(uintptr=2117647) data= device=bind Propagationflags=[]"
"start mount dest=/home/slob/runc/mycontainer/rootfs/sys/fs/cgroup/cpuset source=/sys/fs/cgroup/cpuset/container2 flags=%!!(MISSING)s(uintptr=2117647) data= device=bind Propagationflags=[]"
"start mount dest=/home/slob/runc/mycontainer/rootfs/sys/fs/cgroup/freezer source=/sys/fs/cgroup/freezer/container2 flags=%!!(MISSING)s(uintptr=2117647) data= device=bind Propagationflags=[]"

从日志信息不难看出,系统首先将根挂载点及其所有子挂载点的创博关系修改为了SLAVE,其中flag十进制值540682正好是MS_SLAVE | MS_REC得值,参见https://godoc.org/golang.org/x/sys/unix 设置以后,后续所有根下的挂载将不再传播给根系统相同的peer group,即不再影响原namespace。

然后将rootfs重新进行bind mount,这里的bind mount在原namespace中将不被传播,但是rootfs下的所有子挂载点,将会是原系统namespace的slave挂载点,原namespace中对rootfs挂载,将传播到容器空间中。

如上启动container后,我们host上执行如下操作:

mount -B /home/slob/test <rootfs>/tmp/test

在容器中实际能看到host上source的内容,在容器中能看到这个挂载点也能看到这个传播来的挂载。

反过来,在容器namespace中进行mount,在host namespace就不会被传播,我们做如下实验: 在config.json的mount字段中加入如下定义:

{
    "destination": "/tmp/terr",
    "type": "none",
    "source": "/home/slob/terraform",
    "options": [
           "rbind",
           "rw"
    ]
}

在重新运行container,挂载后在容器内部查看/tmp/terr目录已经是host的/home/slob/terraform下的内容。

ls -l /tmp/terr
ls -l /tmp/terr
total 108
drwxr-xr-x    3 root     root          4096 May  8 01:51 aksk_test

而在host下,查看rootfs/tmp/terr目录则并没有显示/home/slob/terraform下的内容:

ll rootfs/tmp/terr/
total 16
drwxr-xr-x 4 root root 4096 Jun 27 09:39 ./
drwxr-xr-x 6 root root 4096 Jun 27 16:07 ../

通过在container中查看挂载点信息:

cat /proc/self/mountinfo
393 297 253:1 /home/slob/runc/mycontainer/rootfs / rw,relatime master:1 - ext4 
    /dev/vda1 rw,errors=remount-ro,data=ordered
413 393 253:1 /home/slob/terraform /tmp/terr rw,relatime master:1 - ext4 
    /dev/vda1 rw,errors=remount-ro,data=ordered

我们可以看到/tmp/terr挂载是一个slave传播,该挂载的父挂载点是rootfs点,也是slave传播,他们具有相同peer group

现在如果在host上将其他目录bind mount 到rootfs/tmp/terr下,我们看下会发生什么事。

mount -B /home/slob/spark rootfs/tmp/terr

首先在host上肯定rootfs/tmp/terr目录下的内容会是/home/slob/spark下的内容,这个毋庸置疑。按照上面的分析因为根目录是slave传播,所以这个挂载会传播到container的/tmp/terr,那么/tmp/terr下内容会发生改变。

进入container,查看/tmp/terr

ls -l /tmp/terr
total 108
drwxr-xr-x    3 root     root          4096 May  8 01:51 aksk_test

然而内容并没有变化!!!到底发生了什么呢,我们在看下container中mount点情况

cat /proc/self/mountinfo
393 297 253:1 /home/slob/runc/mycontainer/rootfs / rw,relatime master:1 - ext4 
    /dev/vda1 rw,errors=remount-ro,data=ordered
413 296 253:1 /home/slob/terraform /tmp/terr rw,relatime master:1 - ext4 
    /dev/vda1 rw,errors=remount-ro,data=ordered
296 393 253:1 /home/slob/spark /tmp/terr rw,relatime master:1 - ext4 
    /dev/vda1 rw,errors=remount-ro,data=ordered

注意413挂载点的父挂载点发生了变化,不再是393,而是传播过来挂载点296. 也就是说/tmp/terr挂载点先进行了传播挂载mount /home/slob/spark /tmp/terr, 再进行了/home/slob/terraform /tmp/terr,所以看到的还是原来的内容。

通过上面的分析,我们基本可以知道,container中的根挂载点事slave挂载,在容器外的rootfs下的子挂载会传播到容器内部,但是容器内部的挂载不会影响到容器外部,这个特性理论上可以在容器运行后,通过host挂载进行磁盘卷的挂载与卸载,不必一定要容器启动时进行。同时,容器内的同一挂载点,内部挂载的优先级会高于容器外部(内部的挂载的父挂载是外部挂载点)。