当前位置:   article > 正文

[docker] 数据的持久化 - Volume & bind mounts

[docker] 数据的持久化 - Volume & bind mounts

[docker] 数据的持久化 - Volume & bind mounts

docker 的数据笼统分类可以分为下面这三种:

  1. 只读数据

    这种数据大多为源码、容器的配置文件,大多数情况下与镜像进行绑定

  2. 临时数据

    这部分的数据大多数情况下与容器进行绑定,属于可写数据

    具体案例为存储与内存的数据,如进行 AJAX 操作后获取的数据会被存在内存中,db 数据可以存在容器里等

    属于经常被读写的数据

  3. 永久数据

    这部分数据属于永久保存数据,并不依托于容器或镜像存在,并且容器被销毁时,永久数据也应当被保存,而不会随着容器的销毁而消失

    目前这部分的数据是还没有接触过的,也是本章笔记的主题——volume

    volume 中的数据季不存在于 images 中,也不存在于 containers 中,它存在于 host 的文件系统中

    volume 是可读写的

demo app 设置

下面依然是一个 express 的 web server,通过案例了解一下 volume 的实现

express 代码

const fs = require("fs").promises;
const exists = require("fs").exists;
const path = require("path");

const express = require("express");
const bodyParser = require("body-parser");

const app = express();

app.use(bodyParser.urlencoded({ extended: false }));

app.use(express.static("public"));
app.use("/feedback", express.static("feedback"));

app.get("/", (req, res) => {
  const filePath = path.join(__dirname, "pages", "feedback.html");
  res.sendFile(filePath);
});

app.get("/exists", (req, res) => {
  const filePath = path.join(__dirname, "pages", "exists.html");
  res.sendFile(filePath);
});

app.post("/create", async (req, res) => {
  const title = req.body.title;
  const content = req.body.text;

  const adjTitle = title.toLowerCase();

  const tempFilePath = path.join(__dirname, "temp", adjTitle + ".txt");
  const finalFilePath = path.join(__dirname, "feedback", adjTitle + ".txt");

  await fs.writeFile(tempFilePath, content);
  exists(finalFilePath, async (exists) => {
    if (exists) {
      res.redirect("/exists");
    } else {
      await fs.copyFile(tempFilePath, finalFilePath);
      await fs.unlink(tempFilePath);
      res.redirect("/");
    }
  });
});

app.listen(80);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

大概走一下这个代码的流程:

  1. 会在 80 端口启动一个服务器

    服务器中有若干 endpoints

  2. 当用户填写信息的时候,会在 temp 文件夹下写一个临时文件

  3. 检查 feedback 中是否存在同名的文件

    • 如果存在,则重定向到 /exists

    • 如果不存在,则将 temp 中的临时文件写入 feedback 中,并删除 temp 中的临时文件

      重定向到 /

Dockerfile

这部分和之前实现的基本没什么区别,所以就不加注释了

FROM node

WORKDIR /app

COPY package.json /app

RUN npm install

COPY . /app

EXPOSE 80

CMD [ "node", "server.js" ]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

运行

docker build -t feedback-node .
[+] Building 0.5s (10/10) FINISHED                                                                                                                                       docker:desktop-linux
 => [internal] load .dockerignore                                                                                                                                                        0.0s
#  忽略一些build过程
 => => naming to docker.io/library/feedback-node                                                                                                                                         0.0s

❯ docker run -p3000:80 -d --name feedback-app --rm feedback-node
66ffd4249a89d5eb1b1f979ab46ee836d2093917877bd91b8e68896669ec2044
❯ docker ps
CONTAINER ID   IMAGE           COMMAND                  CREATED         STATUS         PORTS                  NAMES
66ffd4249a89   feedback-node   "docker-entrypoint.s…"   4 seconds ago   Up 3 seconds   0.0.0.0:3000->80/tcp   feedback-app
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

最终 UI 渲染如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

现在的问题就在于,tempfeedback 只存在于 docker 的容器里。如果这是一个真实世界里的项目,那么当版本迭代,旧容器被删除,新容器生成部署后,用户提供的信息丢失

为了避免这样的情况,就需要持久化数据,使得不管容器的状态是什么,需要持久化的数据都必须存在

配置 volume

前面提到了,volume 是真实存在于 host 的文件系统上,docker 通过 挂载(mounting) 的方式,实现从容器到 host 文件系统的沟通

当前的案例的情况为:

  • temp 中会存储临时文件

  • feedback 是在容器外的 volume

    通过挂载使得容器可以访问 host 文件系统上的存储

  • 如果当前文件不存在于 feedback 中,那么本程序就会将 temp(容器中的数据存储方式) 中的文件复制到 feedback(容器外的数据存储方式,持久化,不会随着容器的销毁而消失) 中去

匿名卷 anonymous volume

anonymous volume 的创建方式有两种,一种是通过 Dockerfile:

# inside the container where should be mapped
VOLUME [ "/app/feedback" ]
  • 1
  • 2

另一种方式是通过 cli:

docker run -v <path_in_container> <container_name>
  • 1

这里选择哪种方式都行,第一种的话需要重新 build

recap 一下,现在所有的 container 都已经停止了,除了 mysql 的 container 之外,其余的 container 已经全都 删除

然后使用 docker volume 查看当前系统所使用的 volume:

# 这里有的 volume 是 mysql 的docker volume ls
DRIVER    VOLUME NAME
local     417ec38d03c862da140a876341fe02adb0aebdd352ca31799d3dd56da43b5b62
  • 1
  • 2
  • 3
  • 4

发现现在只有 1 个 volume 存在,这是因为在运行 container 的时候使用了 --rm 这个 flag——使用 --rm flag 会在容器停止后自动删除 anonymous volume:

在这里插入图片描述

这里之所以提到 --rm,是因为如果不使用 --rm 的话,anonymous volume 就不会被自动删除,从而创建出 Orphaned Volumes(孤儿卷)。Orphaned Volumes 指的是没有任何的容器与它有所关联,但是当前 volume 也没有被删除的情况,这种情况下只能用以下两个指令进行清除:

  • docker volume rm [volume_id]

  • docker volume prune

这个其实跟 docker 的运作有关联……我不太确定这是不是应该被称之为生命周期,官网上没找到对应的资料。看其他的资料虽然也有说生命周期,不过官网上列举的其实是一些指令:

官网其他参考
在这里插入图片描述在这里插入图片描述

可以看到看的其他资料说的是 stage,官网上并没有明确的说明……

不过简化一下,这三个阶段是比较直接的:

  • 创建

    这个阶段是使用 Dockerfile/指令启动容器时,docker 会创建一个新的 anonymous volume,换言之,anonymous volume 与 container 的生命周期所绑定

    需要注意的一点就是,因为这里没有办法进行 mapping,所以如果一个 container 只是 stop/restart 的话,还能够复用 anounymous volume。但是 delete/restart 就不行了,reference 会丢

    这是因为 anonymous volume 的路径是存储在 container 里的,所以当 container 被删除,那么该 anonymous volume 的关联也就丢了。这种绑定的过程被称之为 direct attach

  • 使用

    就是持久化的这个阶段,因为是挂载在容器上,所以 volume 本质上还是 host 文件系统上的一个文件夹

  • 销毁

    这个阶段是可能会产生 Orphaned Volumes 的阶段

    如果有 --rm 这个 flag,那么 docker 就会自动清理该 container 产生的文件系统,也包括清理对应的 anounymous volume——如果该 anounymous volume 没有被其他容器所使用

    如果没有这个 flag,那么清理就不会被执行,自然也不会清理 anounymous volume。当一个 anounymous volume 没有任何引用,它也就无法自动被删除,这个情况下它就成了 Orphaned Volumes

可以看到,因为会 失去引用 的关系,当容器被删除又重启之后,对应的 volume 还是会被删除,因此 anonymous volume 无法解决持久化的问题

命名卷 named volume

使用命名卷可以保持独立性,因此可以更好的解决当前情况。

⚠️:named volume 只能通过命令行去实现,如:

# remove image and rebuilddocker run -p 3000:80 -d --rm --name feedback-app -v feedback:/app/feedback feedback-node:volumes
  • 1
  • 2

在这里插入图片描述

虽然二者语法很像,不过 named volume 并不与 container 的生命周期所绑定,它不依附于 container,生命周期是独立的,因此可以被分别引用

绑定挂载 bind mounts

bind mounts 其实和 volume 不是一个东西,不过它们的语法很像。如下面会创建一个 bind mounts 和一个 named volume:

# may need to check the file sharing permissiondocker run
  -p 3000:80
  -d
  --rm
  --name feedback-app
  # named volume
  -v feedback:/app/feedback
  # bind mounts
  # 区别在于这是绝对路径
  -v "$(pwd):/app"
  feedback-node:volumes
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如果使用 -v 绝对路径,那么就是创建一个 bind mount 了,它的处理方式也和 volume 不一样

  • volume 是完全由 docker 进行管理的

  • 使用 bind mount 时,docker 提供的管理是有限的

    它会设立 host 的文件系统与容器内的关联,但是当前路径的管理是通过 host 本身的系统进行的实现

    任何变化都可以同步到 docker 的容器里去

换言之,这对开发环境非常的有帮助——可以不用重新 rebuild 整个 docker image,修改的代码就能够被 docker 镜像所检测到

需要注意的一点就是,docker 必须要有权利访问当前被 bind mount 的文件夹

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/446381
推荐阅读
相关标签