lxd与lxc挂卷过程对比分析

举报
再累也要打游戏 发表于 2018/02/23 18:50:23 2018/02/23
【摘要】 lxd与lxc挂卷过程对比分析

LXD挂卷流程


在lxd环境中执行如下命令时:

lxc config device add test-centos sdb unix-block path=/dev/sdb

lxd的整个处理流程如下:

步骤1lxc client先向lxd daemon发送一个put请求,更新当前容器的config,调用链如下:

lxc/config.go: (c *configCmd) run(config *lxd.Config, args []string)
lxc/config.go: (c *configCmd) deviceAdd(config *lxd.Config, which string, args []string)
    client.go: (c *Client) ContainerDeviceAdd(container, devname, devtype string, props []string)
        client.go: (c *Client) put(base string, args interface{}, rtype api.ResponseType)

步骤2lxd daemon在收到请求后,通过与原有config进行对比,得到addDevices,然后开始挂卷,调用链如下:

lxd/container_put.go: containerPut(d *Daemon, r *http.Request)
lxd/container_lxc.go: (c *containerLXC) Update(args containerArgs, userRequested bool)
    lxd/container_lxc.go: (c *containerLXC) insertUnixDevice(name string, m types.Device)

步骤3:在insertUnixDevice函数中,lxd会先调用createUnixDevice函数在”/var/lib/lxd/devices/<container_name>/”目录下创建一个与待挂载设备具有相同majorminor号的块设备。

步骤4:创建好块设备后,insertUnixDevice函数会调用insertMount函数,完成挂载操作。具体步骤如下:

4.1、通过调用mount这个linux系统调用,并将flag设置为MS_BINDlxd会在/var/lib/lxd/shmounts/<container_name>目录下创建一个临时文件,该文件为在步骤3中创建的块设备的复制文件,具体关系参见linuxmount调用中关于MS_BIND这个flag的描述:http://man7.org/linux/man-pages/man2/mount.2.html

需要注意的是,/var/lib/lxd/shmounts/<container_name>这个目录具有一定的特殊性,它被挂载到了容器内部的/dev/.lxd-mounts目录下(lxd/container_lxc.go: (c *containerLXC) initLXC()函数中的err = lxcSetConfigItem(cc, "lxc.mount.entry", fmt.Sprintf("%s dev/.lxd-mounts none bind,create=dir 0 0", shared.VarPath("shmounts", c.Name())))),因此lxd这里创建的临时文件在容器中也能看到。

4.2、调用forkmount函数,在容器内部进行mountforkmount函数的实现在lxd/main_nsexec.go文件中,是通过C语言实现的。

forkmount函数中,首先将user namespace与mount namespace切换到该容器的namespace中,然后根据目标路径创建filedir(根据4.1中创建的临时文件的类型来定),最后通过mount调用以及MS_MOVE flag将步骤1中复制的块设备文件move到目标路径(当前从用户参数中的path字段获取,目前没有弄清楚为何不从用户参数的target字段获取。)。

至此,宿主机上的块设备已经可以在容器中看到了。


LXC挂卷流程

不过上述操作是用户执行挂载命令时lxd的内部流程,当容器重启后,lxd会对这些挂载设备进行恢复,这里走的就是另一条流程了,具体流程如下:

步骤1lxc clientlxd daemon发送一个start请求,调用链如下:

lxd/action.go: (c *actionCmd) run(config *lxd.Config, args []string)
client.go: (c *Client) Action(name string, action shared.ContainerAction, timeout int, force bool, stateful bool)

步骤2lxd daemon在收到请求后,根据数据库中记录生成配置文件,作为启动容器的参数,调用链如下:

lxd/container_state.go: containerStatePut(d *Daemon, r *http.Request)
client.go: (c *Client) Action(name string, action shared.ContainerAction, timeout int, force bool, stateful bool)
    lxd/container_lxc.go: (c *containerLXC) Start(stateful bool)
        lxd/container_lxc.go: (c *containerLXC) startCommon()
            lxd/container_lxc.go: (c *containerLXC) initLXC()

initLXC函数中,各个device会被转化为配置文件中的lxc.mount.entry参数。

在最初执行的那个“lxc config device add”命令挂载的磁盘会转化为如下的配置项:

lxc.mount.entry = /var/lib/lxd/devices/test-centos/unix.dev-sdb dev/sdb none bind,create=file

其中,unix.dev-sdb就是在上面的步骤3createUnixDevice函数中创建的块设备,dev/sdb是用户命令中的path字段,nonebind,create=file的含义后面会提到。

步骤3lxc在启动容器时,对lxc.mount.entry参数的处理在文件conf.c中,调用链如下:

conf.c: lxc_setup(struct lxc_handler *handler)
conf.c: setup_mount_entries(const struct lxc_rootfs *rootfs, struct lxc_list *mount, const char *lxc_name, const char *lxc_path)
    conf.c: make_anonymous_mount_file(struct lxc_list *mount)
    conf.c: mount_file_entries(const struct lxc_rootfs *rootfs, FILE *file, const char *lxc_name, const char *lxc_path)
        lxcmntent.c: getmntent_r (FILE *stream, struct mntent *mp, char *buffer, int bufsiz)
        conf.c: mount_entry_on_relative_rootfs(struct mntent *mntent, const struct lxc_rootfs *rootfs, const char *lxc_name, const char *lxc_path)
            conf.c: mount_entry_on_generic(struct mntent *mntent, const char* path, const struct lxc_rootfs *rootfs, const char *lxc_name, const char *lxc_path)
                conf.c: mount_entry_create_dir_file(const struct mntent *mntent, const char* path, const struct lxc_rootfs *rootfs, const char *lxc_name, const char *lxc_path)
                conf.c: cull_mntent_opt(struct mntent *mntent)
                conf.c: parse_mntopts(const char *mntopts, unsigned long *mntflags, char **mntdata)
                conf.c: mount_entry(const char *fsname, const char *target, const char *fstype, unsigned long mountflags, const char *data, int optional, int dev, const char *rootfs)
                    utils.c: safe_mount(const char *src, const char *dest, const char *fstype, unsigned long flags, const void *data, const char *rootfs)
                        utils.c: open_without_symlink(const char *target, const char *prefix_skip)

其中需要注意的有:

3.1lxc_setup在调用setup_mount_entries提供的mount_list中存储的就是lxc配置文件中所有lxc.mount.entry的值(在confile.c: config_mount(const char *key, const char *value, struct lxc_conf *lxc_conf){函数中构造)

3.2make_anonymous_mount_file函数将所有的mount_entries的内容写到了一个内存文件中,供mount_file_entries函数使用。

3.3getmntent_r函数用于将mount_entries的内容解析为如下结构体:

struct mntent
{
    char* mnt_fsname;
    char* mnt_dir;
    char* mnt_type;
    char* mnt_opts;
    int mnt_freq;
    int mnt_passno;
};

以步骤2中生成的配置项为例,该配置项会被解析为如下内容:

struct mntent mp;
mp.mnt_fsname=/var/lib/lxd/devices/test-centos/unix.dev-sdb;
mp. mnt_dir= dev/sdb;
mp.mnt_type= none;
mp.mnt_opts= bind,create=file;
mp.mnt_freq=0;
mp.mnt_passno=0;

3.4mount_entry_on_relative_rootfs在调用mount_entry_on_generic时,会将rootfsmnt_dir拼接起来,组成mnt_dir在宿主机上的绝对路径,作为path参数传给mount_entry_on_generic

3.5mount_entry_create_dir_filepath对应的路径上创建一个filedir(根据mnt_opts中的create=filecreate=dir来决定),这一步与上面的4.2forkmount中的创建操作一致。

3.6cull_mntent_opt函数与parse_mntopts函数共同将mnt_opts转化为mntflags

3.7safe_mount函数中调用的open_without_symlink函数的作用仅仅是打开待挂载路径,得到一个文件句柄(通过校验保证待挂载路径不是文件链接)。

3.8safe_mount函数最后调用mount调用,完成第一次挂载。需要注意的是,调用mount调用时,并没有直接使用待挂载路径作为dest参数,而是将/proc/self/fd/3.7中得到的文件句柄拼接起来,作为mount调用的dest参数。这块有点不明觉厉。

3.9mount_entry在调用safe_mount函数完成第一次挂载后,再次直接调用mount调用进行第二次挂载,两次挂载的区别在于第一次去掉了MS_REMOUNT flag,第二次加上了MS_REMOUNT flag。这块也有点不明觉厉。

对比lxd的挂卷过程与lxc的挂卷过程,可以发现相比于lxdlxc的挂卷过程少了一次namespace切换,也少了一次带MS_MOVE flagmount操作(lxd的挂卷过程是先将devices目录下的unix.dev-sdb通过bind mount方式mountshmounts目录下,然后通过move mount方式mountdev目录下,lxc则直接将devices目录下的unix.dev-sdb通过bind mount方式mountdev目录下)。


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。