umount <挂载点>

在Linux系统中,如果你使用 mount -o bind 命令创建了一个绑定挂载,你可以使用 umount 命令来卸载(解除挂载)这个挂载点。umount 命令用于卸载文件系统或挂载点。

下面是卸载绑定挂载的基本语法:

umount <挂载点>
  • umount:这是命令本身,用于执行卸载操作。注意,umount 的拼写中没有字母 n,尽管在某些系统上也可以使用 unmount 作为别名。
  • <挂载点>:这是要卸载的挂载点的路径。在绑定挂载的情况下,这是你之前使用 mount -o bind 命令挂载的目录。

例如,如果你之前创建了一个绑定挂载:

mount -o bind /host/directory/ /container/directory/

你可以使用以下命令来卸载这个挂载点:

umount /container/directory/

执行这个命令后,/container/directory/ 将不再是一个挂载点,容器将无法访问宿主机上的 /host/directory/ 目录。

在某些情况下,如果你尝试卸载一个正在被使用的挂载点,umount 命令可能会返回一个错误,表示设备正忙。为了解决这个问题,你可以使用 fuser 命令来查找并终止使用该挂载点的进程,或者使用 lsof 命令来列出打开的文件和挂载点。

在Go语言中,如果你需要卸载一个挂载点,你可以使用 syscall 包中的 Syscall 函数来调用底层的 umount 系统调用,但这通常不是推荐的做法,因为它涉及到直接的系统调用,可能会导致代码的可移植性降低。通常,卸载挂载点的操作是在命令行中手动执行的,或者在容器化环境中通过容器运行时的命令来实现。

os.Mkdir

在Go语言中,os.Mkdir 函数用于创建一个新的目录。如果你尝试使用 os.Mkdir 创建一个已经存在的目录,函数会返回一个错误。这个错误通常是 syscall.EEXIST,它是 os 包中的一个常量,表示文件已存在。

下面是你的代码片段的详细解释:

if err := os.Mkdir(containervolume_inhost, 0777); err != nil {
    log.Infof("mkdir hostvolume error: %v", err)
}
  • os.Mkdir(containervolume_inhost, 0777):这行代码尝试创建一个名为 containervolume_inhost 的新目录,并设置其权限为 0777,这是一个非常开放的权限设置,允许所有用户读、写和执行。
  • err != nil:这是一个条件判断,检查 os.Mkdir 函数是否返回了非空的错误值。如果返回了错误,那么 err 将不会是 nil
  • log.Infof("mkdir hostvolume error: %v", err):如果 os.Mkdir 返回了错误,这段代码会记录一个信息级别的日志,包含错误信息。%v 是一个格式化动词,用于打印错误信息的默认格式。

如果 containervolume_inhost 目录已经存在,os.Mkdir 会返回一个错误,你的代码会捕获这个错误并记录日志。然而,这段代码没有提供任何处理已存在目录的逻辑,它只是记录了错误。

为了更好地处理这种情况,你可以添加额外的逻辑来检查目录是否已经存在,而不是仅仅依赖于错误处理。例如:

if _, err := os.Stat(containervolume_inhost); os.IsNotExist(err) {
    // 目录不存在,创建目录
    if err := os.Mkdir(containervolume_inhost, 0777); err != nil {
        log.Infof("mkdir hostvolume error: %v", err)
    }
} else {
    // 目录已经存在
    log.Infof("Directory %s already exists", containervolume_inhost)
}

在这个修改后的代码中,我们首先使用 os.Stat 函数来检查目录是否存在。如果目录不存在,我们再尝试创建它。如果目录已经存在,我们记录一个信息,说明目录已经存在,而不尝试创建它。这样可以避免不必要的错误日志。

mount -o bind <源目录> <目标目录>

在Linux系统中,mount 命令用于挂载文件系统,而 -o bind 选项用于创建绑定挂载。下面是这个命令的语法解释:

mount -o bind <源目录> <目标目录>
  • mount:这是命令本身,用于执行挂载操作。
  • -o:这是 mount 命令的一个选项,用于指定挂载选项。-o 后面跟着的是逗号分隔的挂载选项列表。
  • bind:这是 -o 选项后面的一个挂载选项,表示要创建一个绑定挂载。绑定挂载允许你将一个目录的内容挂载到另一个位置,而不改变原始目录的内容。
  • <源目录>:这是要挂载的源目录的路径。在绑定挂载中,这个目录的内容将被复制到目标目录。
  • <目标目录>:这是挂载点的路径,即源目录的内容将被挂载到这个位置。

例如,命令 mount -o bind /host/directory/ /container/directory/ 的解释如下:

  • /host/directory/:这是宿主机上的源目录路径,它包含了你想要在容器内部访问的文件和目录。
  • /container/directory/:这是容器内部的目标目录路径,宿主机上的源目录将被挂载到这里。

执行这个命令后,容器内的/container/directory/目录将直接映射到宿主机的/host/directory/目录。这意味着在容器内部对/container/directory/目录的任何更改都会反映到宿主机的/host/directory/目录,反之亦然。

绑定挂载在容器化环境中非常有用,因为它允许容器访问宿主机上的文件系统,这对于共享配置文件、日志文件或数据文件等非常有用。然而,需要注意的是,绑定挂载可能会带来安全风险,因为容器可以访问宿主机上的文件系统,所以应该谨慎使用,并确保只有授权的容器可以访问敏感的宿主机目录。

在Docker中,绑定挂载可以通过-v--volume选项来实现,例如:

docker run -v /host/directory/:/container/directory/ ...

这个命令与mount -o bind命令的效果类似,但它是在Docker容器启动时自动执行的。

卸载overlayfs时里含有绑定挂载点

当您尝试卸载一个OverlayFS文件系统,而该文件系统中包含通过mount --bind创建的绑定挂载点时,可能会遇到无法卸载成功的问题。这主要是因为Linux内核为了防止数据损坏或一致性问题,不允许直接卸载一个包含活动挂载点的文件系统。

修改merged层中的绑定挂载点对upper层

对/merged/tmp(实际上是访问的/volume的内容,因为它被bind挂载到这里)的修改理论上不会直接影响到OverlayFS定义的upper层。这是因为bind挂载创建了一个新的、独立的访问点到原始的/volume目录,它在文件系统层次上“遮盖”了OverlayFS原本的lower和upper结构。

fmt.Errorf

fmt.Errorf 函数返回的是一个 error 类型的值。在Go语言中,error 是一个内置的接口类型,定义如下:

type error interface {
    Error() string
}

这个接口有一个方法 Error(),它返回一个字符串,通常是描述错误的文本。fmt.Errorf 函数使用类似于 fmt.Printf 的格式化字符串来创建一个新的错误值,并返回这个值。

在你的例子中:

err := fmt.Errorf("invalid volume [%s], must split by `:`", volume)

这里创建了一个新的错误值,并将其赋值给变量 err。这个错误值描述了一个无效的卷,指出卷应该通过冒号 : 进行分割。如果这个错误值被传递给一个函数或方法,并且该函数或方法期望接收一个 error 类型的参数,那么这个错误值就可以被传递进去。

例如,你可以在函数中返回这个错误值:

func validateVolume(volume string) error {
    parts := strings.Split(volume, ":")
    if len(parts) != 2 {
        return fmt.Errorf("invalid volume [%s], must split by `:`", volume)
    }
    // 继续处理...
    return nil
}

在这个例子中,validateVolume 函数检查传入的卷是否正确地通过冒号分割成两部分。如果分割后的切片长度不是2,函数就会返回一个错误值。如果一切正常,函数返回 nil 表示没有错误。

实现目标

  • 启动容器时通过-v参数指定volume 实现指定的位置的文件数据在容器结束后的变化保留在主机上
  • 将需要保留变化的文件通过bind mount使得主机和容器内的文件保持同步变化

流程

  1. 实现-v命令
  • 这里的 “volume” 指的是在容器化技术(如Docker)中使用的卷(volume)的概念。
  1. -v命令会进行bind mount操作使得文件同步
  • 在Docker中,-v 或 --volume 选项用于将宿主机的目录挂载到容器中,或者创建一个命名的卷。
  1. 退出时候先卸载同步的文件挂载点然后再卸载overlayfs,最后才删除,删除依旧是删除overlayfs,因为overlayfs包括挂载点

代码

https://github.com/FULLK/llkdocker/tree/main/volume_docker

  • 实现挂载操作是在子进程(容器)运行起来之前

  • 先挂载overlayfs,然后绑定挂载数据卷,这样主机上的volume就同步到了merged的容器的数据卷的位置
    从零自制docker-13-【实现 mydocker run -v 支持数据卷挂载】-LMLPHP

  • 此时如果在容器中对容器的与主机绑定的数据卷建立文件

  • 注意由于先挂载overlayfs再绑定挂载,此时绑定挂载点已经不属于overlayfs了,所以在绑定里面的修改将不会有overlayfs的机制(绑定挂载点如果在lower,但此时由于是绑定挂载,相当于并没有挂载到overlayfs,所以overlayfs机制对该挂载点不会生效,相当于lower层绑定挂载点变化,但upper层不会变化,merged绑定挂载点变化)
    从零自制docker-13-【实现 mydocker run -v 支持数据卷挂载】-LMLPHP
    从零自制docker-13-【实现 mydocker run -v 支持数据卷挂载】-LMLPHP

  • 实现卸载操作是在子进程结束之后

  • 此时先卸载绑定挂载点,因为绑定挂载点在overlayfs里面,如果先卸载overlayfs就会发现里面还有一个挂载点,此时卸载就会失败,

  • 退出后在宿主机查看挂载点,发现成功卸载
    从零自制docker-13-【实现 mydocker run -v 支持数据卷挂载】-LMLPHP

  • 绑定挂载内容也在主机上保存了
    从零自制docker-13-【实现 mydocker run -v 支持数据卷挂载】-LMLPHP

  • 重新启动容器,此时由于绑定挂载,容器内的数据卷内容保存生效
    从零自制docker-13-【实现 mydocker run -v 支持数据卷挂载】-LMLPHP

05-15 14:04