Docker和Kubernetes中使用Ceph RBD卷的原理分析

本文详细介绍了Linux的Mount Namespace和挂载传播概念,探讨了Docker中数据卷的多种挂载方式,并通过实例展示了如何在Docker中使用块设备,特别提到了在Kubernetes中使用PVC创建RBD卷的操作。
摘要由CSDN通过智能技术生成

Linux的Mount绑定关系

Linux Mount命名空间通过隔离文件系统挂载点对隔离文件系统提供支持,它是历史上第一个 Linux Namespace,所以它的标识位比较特殊,就是 CLONE_NEWNS。隔离后,不同 Mount Namespace 中的文件结构发生变化也互不影响。你可以通过 /proc/[pid]/mounts 查看到所有挂载在当前 Namespace 中的文件系统,还可以通过 /proc/[pid]/mountstats 看到 Mount Namespace 中文件设备的统计信息,包括挂载文件的名字、文件系统类型、挂载位置等等。

进程在创建 Mount Namespace 时,会把当前的文件结构复制给新的 Namespace。新 Namespace 中的所有 Mount 操作都只影响自身的文件系统,而对外界不会产生任何影响。这样做非常严格地实现了隔离,但是某些情况可能并不适用。比如父节点 Namespace 中的进程挂载了一张CD-ROM,这时子节点 Namespace 拷贝的目录结构就无法自动挂载上这张 CD-ROM,因为这种操作会影响到父节点的文件系统。

2006 年引入的挂载传播(Mount Propagation)解决了这个问题,挂载传播定义了挂载对象(Mount Object)之间的关系,系统用这些关系决定任何挂载对象中的挂载事件如何传播到其他挂载对象

共享关系(Share Relationship)。如果两个挂载对象具有共享关系,那么一个挂载对象中的挂载事件会传播到另一个挂载对象,反之亦然。
从属关系(Slave Relationship)。如果两个挂载对象形成从属关系,那么一个挂载对象中的挂载事件会传播到另一个挂载对象,但是反过来不行;在这种关系中,从属对象是事件的接收者。
一个挂载状态可能为如下的其中一种:

共享挂载(Shared)
从属挂载(Slave)
共享 /从属挂载(Shared And Slave)
私有挂载(Private)
不可绑定挂载(Unbindable)
传播事件的挂载对象称为共享挂载(Shared Mount);接收传播事件的挂载对象称为从属挂载(Slave Mount)。既不传播也不接收传播事件的挂载对象称为私有挂载(Private Mount)。另一种特殊的挂载对象称为不可绑定的挂载(Unbindable Mount),它们与私有挂载相似,但是不允许执行绑定挂载,即创建 Mount Namespace 时这块文件对象不可被复制。

共享挂载的应用场景非常明显,就是为了文件数据的共享所必须存在的一种挂载方式;从属挂载更大的意义在于某些“只读”场景;私有挂载其实就是纯粹的隔离,作为一个独立的个体而存在;不可绑定挂载则有助于防止没有必要的文件拷贝,如某个用户数据目录,当根目录被递归式的复制时,用户目录无论从隐私还是实际用途考虑都需要有一个不可被复制的选项。

默认情况下,所有挂载都是私有的。设置为共享挂载的命令如下:
$ mount --make-shared
从共享挂载克隆的挂载对象也是共享的挂载;它们相互传播挂载事件。

设置为从属挂载的命令如下:
$ mount --make-slave
从从属挂载克隆的挂载对象也是从属的挂载,它也从属于原来的从属挂载的主挂载对象。

将一个从属挂载对象设置为共享 / 从属挂载,可以执行如下命令或者将其移动到一个共享挂载对象下:
$ mount --make-shared
如果你想把修改过的挂载对象重新标记为私有的,可以执行如下命令:
$ mount --make-private
通过执行以下命令,可以将挂载对象标记为不可绑定的:
$ mount --make-unbindable
Linux的Mount绑定测试

查看本机块设备:
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 222.6G 0 disk
├─sda1 8:1 0 200M 0 part /boot
└─sda2 8:2 0 222.4G 0 part
├─centos-root 253:0 0 122.4G 0 lvm /
└─centos-home 253:1 0 100G 0 lvm /home
创建和绑定目录:
$ mkdir /opt/tmp /mnt/tmp /mnt/tmp1 /mnt/tmp2
$ mount --bind /opt/tmp /mnt/tmp
$ mount --bind /mnt/tmp1 /mnt/tmp2
查看绑定目录的详细信息:
$ cat /proc/self/mountinfo | grep /mnt/tmp
549 40 253:0 /opt/tmp /mnt/tmp rw,relatime shared:1 - xfs /dev/mapper/centos-root rw,seclabel,attr2,inode64,noquota
583 40 253:0 /mnt/tmp1 /mnt/tmp2 rw,relatime shared:1 - xfs /dev/mapper/centos-root rw,seclabel,attr2,inode64,noquota
可以看到两个绑定目录都是共享的,且共享ID为1,父目录在253:0设备上。

在Docker中使用数据卷的主要方式

参考文档:

Manage data in Docker;
Use bind mounts。
Docker支持的挂载模式:

创建一个数据卷(卷挂载):
$ docker run --rm -it -v /data1 centos:7 bash

Or

$ docker run --rm -it -v data1:/data1 centos:7 bash

Or

$ docker run --rm -it --mount target=/data1 centos:7 bash

Or

$ docker run --rm -it --mount type=volume,target=/data1 centos:7 bash

Or

$ docker run --rm -it --mount type=volume,source=data1,target=/data1 centos:7 bash

$ docker ps | awk ‘NR==2 {print $1}’ | xargs -i docker inspect -f ‘{{.State.Pid}}’ {} | xargs -i cat /proc/{}/mountinfo | grep data
1029 1011 253:0 /var/lib/docker/volumes/239be79a64f7fa6ec815b1d9f2a7773a678ee5c8c1150f03ca81b0d5177b36a0/_data /data1 rw,relatime master:1 - xfs /dev/mapper/centos-root rw,seclabel,attr2,inode64,noquota
映射一个外部卷(绑定挂载):
$ docker run --rm -it -v /opt:/data2 centos:7 bash

Or

$ docker run --rm -it --mount type=bind,source=/opt,target=/data2 centos:7 bash

$ docker ps | awk ‘NR==2 {print $1}’ | xargs -i docker inspect -f ‘{{.State.Pid}}’ {} | xargs -i cat /proc/{}/mountinfo | grep data
1029 1011 253:0 /opt /data2 rw,relatime - xfs /dev/mapper/centos-root rw,seclabel,attr2,inode64,noquota
使用数据型容器(卷挂载):
$ docker create --name vc -v /data1 centos:7
$ docker run --rm -it --volumes-from vc centos:7 bash

$ docker ps | awk ‘NR==2 {print $1}’ | xargs -i docker inspect -f ‘{{.State.Pid}}’ {} | xargs -i cat /proc/{}/mountinfo | grep data
1029 1011 253:0 /var/lib/docker/volumes/fe71f2d0ef18beb92cab7b99afcc5f501e47ed18224463e8c1aa1e8733003803/_data /data1 rw,relatime master:1 - xfs /dev/mapper/centos-root rw,seclabel,attr2,inode64,noquota
带打包数据的容器(卷挂载):
编辑Dockerfile

FROM busybox:latest
ADD htdocs /usr/local/apache2/htdocs
VOLUME /usr/local/apache2/htdocs
创建容器

$ mkdir htdocs
$ echo date > htdocs/test.txt
$ docker build -t volume-test .

$ docker create --name vc2 -v /data1 volume-test
$ docker run --rm -it --volumes-from vc2 volume-test sh
/ # cat /proc/self/mountinfo | grep htdocs
1034 1011 253:0 /var/lib/docker/volumes/54f47af60b8fb25602f022dcd8ad5b3e1a93a2d20c1045184a70391d9bed69b6/_data /usr/local/apache2/htdocs rw,relatime master:1 - xfs /dev/mapper/centos-root rw,seclabel,attr2,inode64,noquota

$ docker ps | awk ‘NR==2 {print $1}’ | xargs -i docker inspect -f ‘{{.State.Pid}}’ {} | xargs -i cat /proc/{}/mountinfo | grep htdocs
1034 1011 253:0 /var/lib/docker/volumes/54f47af60b8fb25602f022dcd8ad5b3e1a93a2d20c1045184a70391d9bed69b6/_data /usr/local/apache2/htdocs rw,relatime master:1 - xfs /dev/mapper/centos-root rw,seclabel,attr2,inode64,noquota
使用临时外部卷(临时挂载):
$ docker run --rm -it --mount type=tmpfs,target=/data1 centos:7 bash

$ docker ps | awk ‘NR==2 {print $1}’ | xargs -i docker inspect -f ‘{{.State.Pid}}’ {} | xargs -i cat /proc/{}/mountinfo | grep data
1029 1011 0:160 / /data1 rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,seclabel
在Docker中使用块设备的相关测试

在容器内通过绑定挂载使用宿主机的块设备,只能使用已经在宿主机格式化和挂载好的目录:
$ docker run --rm -it -v /data1 -v /opt:/data2 centos:7 bash
[root@4282b3df2417 /]# mount | grep data
/dev/sdb1 on /data2 type xfs (rw,relatime,attr2,inode64,noquota)
/dev/sda1 on /data1 type xfs (rw,relatime,attr2,inode64,noquota)

$ docker inspect 4282b3df2417 | grep -i pid
“Pid”: 12797,
“PidMode”: “”,
“PidsLimit”: 0,

$ cat /proc/12797/mounts | grep data
/dev/sdb1 /data2 xfs rw,relatime,attr2,inode64,noquota 0 0
/dev/sda1 /data1 xfs rw,relatime,attr2,inode64,noquota 0 0
在容器内通过共享设备使用块设备,可以读写,但是不能挂载:
$ docker run --rm -it --device /dev/sdc:/dev/sdc centos:7 bash

[root@55423f5eaeea /]# mkfs -t minix /dev/sdc
21856 inodes
65535 blocks
Firstdatazone=696 (696)
Zonesize=1024
Maxsize=268966912

[root@55423f5eaeea /]# mknod /dev/sdd b 8 48
[root@55423f5eaeea /]# mkfs -t minix /dev/sdd
mkfs.minix: cannot open /dev/sdd: Operation not permitted

[root@55423f5eaeea /]# rm /dev/sdc
rm: remove block special file ‘/dev/sdc’? y

[root@55423f5eaeea /]# mknod /dev/sdc b 8 32
[root@55423f5eaeea /]# mkfs -t minix /dev/sdc
21856 inodes
65535 blocks
Firstdatazone=696 (696)
Zonesize=1024
Maxsize=268966912

[root@55423f5eaeea /]# mount /dev/sdc mnt/
[root@55423f5eaeea /]# mount: permission denied

[root@55423f5eaeea /]# dd if=/dev/sdc of=/dev/null bs=512 count=10
10+0 records in
10+0 records out
5120 bytes (5.1 kB) copied, 0.000664491 s, 7.7 MB/s

[root@55423f5eaeea /]# dd if=/dev/zero of=/dev/sdc bs=512 count=10
10+0 records in
10+0 records out
5120 bytes (5.1 kB) copied, 0.00124138 s, 4.1 MB/s
在容器内通过特权模式使用块设备,可以读写和挂载:
$ docker run --rm -it --privileged=true centos:7 bash

[root@b5c40e199476 /]# mount /dev/sdc mnt
[root@b5c40e199476 /]# mkfs -t minix /dev/sdc
mount: unknown filesystem type ‘minix’

[root@b5c40e199476 /]# yum install -y xfsprogs

[root@b5c40e199476 /]# mkfs.xfs /dev/sdc -f
meta-data=/dev/sdc isize=512 agcount=4, agsize=6553600 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=0, sparse=0
data = bsize=4096 blocks=26214400, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0 ftype=1
log =internal log bsize=4096 blocks=12800, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0

[root@b5c40e199476 /]# mount /dev/sdc mnt
[root@b5c40e199476 /]# df -h
Filesystem Size Used Avail Use% Mounted on
overlay 30G 19G 12G 62% /
tmpfs 910M 0 910M 0% /dev
tmpfs 910M 0 910M 0% /sys/fs/cgroup
/dev/sda1 30G 19G 12G 62% /etc/hosts
shm 64M 0 64M 0% /dev/shm
/dev/sdc 100G 33M 100G 1% /mnt

[root@b5c40e199476 /]# echo date > /mnt/time.txt
[root@b5c40e199476 /]# cat /mnt/time.txt
Wed Mar 6 12:23:05 UTC 2019
Kubernetes中的块设备使用和实现

查看kublet初始化根目录/var/lib/kubelet时的源码,可以看到kubelet使用syscall.MS_SHARED|syscall.MS_REC标志,该目录下下所有的Mount都默认共享(相当于执行mount --make-rshared /var/lib/kubelet):
// pkg/kubelet/kubelet.go
// setupDataDirs creates:
// 1. the root directory
// 2. the pods directory
// 3. the plugins directory
// 4. the pod-resources directory
func (kl *Kubelet) setupDataDirs() error {

if err := kl.mounter.MakeRShared(kl.getRootDir()); err != nil {
return fmt.Errorf(“error configuring root directory: %v”, err)
}

}

// pkg/util/mount/nsenter_mount.go
func (n *NsenterMounter) MakeRShared(path string) error {
return doMakeRShared(path, hostProcMountinfoPath)
}

// pkg/util/mount/mount_linux.go
// doMakeRShared is common implementation of MakeRShared on Linux. It checks if
// path is shared and bind-mounts it as rshared if needed. mountCmd and
// mountArgs are expected to contain mount-like command, doMakeRShared will add
// '–bind ’ and '–make-rshared ’ to mountArgs.
func doMakeRShared(path string, mountInfoFilename string) error {
shared, err := isShared(path, mountInfoFilename)
if err != nil {
return err
}
if shared {
klog.V(4).Infof(“Directory %s is already on a shared mount”, path)
return nil
}

klog.V(2).Infof("Bind-mounting %q with shared mount propagation", path)
// mount --bind /var/lib/kubelet /var/lib/kubelet
if err := syscall.Mount(path, path, "" /*fstype*/, syscall.MS_BIND, "" /*data*/); err != nil {
	return fmt.Errorf("failed to bind-mount %s: %v", path, err)
}

// mount --make-rshared /var/lib/kubelet
if err := syscall.Mount(path, path, "" /*fstype*/, syscall.MS_SHARED|syscall.MS_REC, "" /*data*/); err != nil {
	return fmt.Errorf("failed to make %s rshared: %v", path, err)
}

return nil

}
创建一个使用PVC创建RBD的Pod:
$ echo 'apiVersion: v1
kind: Pod
metadata:
name: nginx-test
spec:
containers:

  • name: nginx
    image: nginx:latest
    volumeMounts:
    • name: nginx-test-vol1
      mountPath: /data/
      readOnly: false
      volumes:
  • name: nginx-test-vol1
    persistentVolumeClaim:
    claimName: nginx-test-vol1-claim’ | kubectl create -f -
    pod/nginx-test created
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值