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

news2024/12/24 2:21:59

[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. 会在 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" ]

运行

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

最终 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" ]

另一种方式是通过 cli:

docker run -v <path_in_container> <container_name>

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

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

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

# 这里有的 volume 是 mysql 的docker volume ls
DRIVER    VOLUME NAME
local     417ec38d03c862da140a876341fe02adb0aebdd352ca31799d3dd56da43b5b62

发现现在只有 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

在这里插入图片描述

虽然二者语法很像,不过 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

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

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

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

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

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

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

需要注意的一点就是,docker 必须要有权利访问当前被 bind mount 的文件夹 📁,这点可以通过下面这里查看:

在这里插入图片描述

同样需要注意的一点就是,docker 在这里是提供 有限 的管理,而且 docker 本身也是提供一个容器化管理的工具,因此 docker 不会根据容器内的文件,去覆写 host 的系统文件。这里也会产生一个冲突——本机的源码贴到 docker 里,会覆盖掉原本的文件夹,那么自然就会将通过 RUN npm install 下载好的 node_modules 所覆盖掉,从而导致找不到依赖的问题:

docker run -p 3000:80  --rm --name feedback-app -v feedback:/app/feedback -v "$(pwd):/app" feedback-node:volumes
node:internal/modules/cjs/loader:1145
  throw err;
  ^

Error: Cannot find module 'express'
Require stack:
- /app/server.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1142:15)
    at Module._load (node:internal/modules/cjs/loader:983:27)
    at Module.require (node:internal/modules/cjs/loader:1230:19)
    at require (node:internal/modules/helpers:179:18)
    at Object.<anonymous> (/app/server.js:5:17)
    at Module._compile (node:internal/modules/cjs/loader:1368:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1426:10)
    at Module.load (node:internal/modules/cjs/loader:1205:32)
    at Module._load (node:internal/modules/cjs/loader:1021:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:142:12) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/app/server.js' ]
}

Node.js v21.7.3

⚠️:这里说的是 覆盖 而不是 覆写,两边的文件都是同时存在的,只是因为 bind mount 的关系,host 的文件系统具有更高的权重

这个解决方式可以用 anonymous volume 去解决——即生成一个关于 node_modules 的 anonymous volume,实现如下:

使用 Dockerfile:

VOLUME [ "/app/node_modules" ]

或者直接命令行:

docker run
    -p 3000:80
    --rm
    --name feedback-app
    -v feedback:/app/feedback
    -v "$(pwd):/app"
    -v /app/node_modules
    feedback-node:volumes
# 没有 -d 所以会卡在这里,在另一个终端停止当前容器
❯ tree .
.
├── Dockerfile
├── feedback
# 这是一个空的文件夹,它的创立是受到 volume 的影响
# bind mount之后,host 和 container 的影响是相互的
├── node_modules
├── package.json
├── pages
│   ├── exists.html
│   └── feedback.html
├── public
│   └── styles.css
├── server.js
└── temp

6 directories, 6 files

在这里插入图片描述

最后一个是关于 node 项目的优化,也就是使用 nodemon 建立一个简易的 dev server,用以检测代码的变化,从而实现热部署:

{
  "devDependencies": {
    "nodemon": "3.1.0"
  },
  "scripts": {
    "start": "nodemon server.js"
  }
}

更新 Dockerfile:

CMD [ "npm", "start" ]

这就需要重新 build 了,效果如下:

docker logs feedback-app

> data-volume-example@1.0.0 start
> nodemon server.js

[nodemon] 3.1.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node server.js`
[nodemon] restarting due to changes...
[nodemon] starting `node server.js`
[nodemon] clean exit - waiting for changes before restart
[nodemon] restarting due to changes...
[nodemon] starting `node server.js`
nodemon is running

总结

anonymous volumesnamed volumesbind mounts
和 1 个容器进行绑定不和任何一个容器进行绑定,可以复用基于 host 的文件系统,不和任何一个容器进行绑定
持久化不完整,容器被删除则无法溯源容器的生命周期不影响持久化的数据,使用 docker volume rm 进行删除容器的生命周期不影响持久化的数据,需要将 host 上的文件进行删除
无法被共享可以被共享可以被共享
无法被复用可以被复用可以被复用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1614369.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DRF 路由层

路由层 【1】原始路由 &#xff08;1&#xff09;正则re_path from django.urls import path, re_path from book import viewsurlpatterns [re_path(rbook/(?P<pk>\d)?/?$, views.BookAPIView.as_view(), namebook) ] 示例解释 (?P<pk>\d)是一个命名正…

使用mmsegmentaion遇到的问题

利用mmsegmentaion跑自定义数据集时的bug处理&#xff08;使用bisenetV2&#xff09; 1. ValueError: val_dataloader, val_cfg, and val_evaluator should be either all None or not None, but got val_dataloader{batch_size: 1, num_workers: 4}, val_cfg{type: ValLoop}, …

c++ 线性搜索与二分搜索

线性搜索 假设该项目以随机顺序存在于数组中&#xff0c;并且我们必须找到一个项目。那么搜索目标项目的唯一方法就是从第一个位置开始&#xff0c;并将其与目标进行比较。如果项目相同&#xff0c;我们将返回当前项目的位置。否则&#xff0c;我们将转移到下一个位置。…

在Qt creator中使用多光标

2024年4月22日&#xff0c;周一下午 Qt Creator 支持多光标模式。 多光标模式允许你在同一时间在多个光标位置进行编辑&#xff0c;从而可以更快地进行一些重复性的编辑操作。 要启用多光标模式&#xff0c;请按住 Alt 键&#xff0c;并用鼠标左键在文本编辑器中选择多个光标…

恒创科技:网站更改域名对seo的影响大吗?

众所周知&#xff0c;搜索引擎优化 (SEO) 对于增加网站的自然流量、提高转化率以及提高品牌知名度至关重要。所以&#xff0c;网站在进行更改域名时&#xff0c;我们应当关注其对 SEO 的影响。 网站更改域名对 SEO 影响大吗? 更改域名会更改网站上每个页面的 URL &#xff0c;…

SpringSecurity初探(一)

认证:知道你是谁 授权:你能干什么 创建SpringBoot应用,引入web和security <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency><group…

Redis数据类型——String

Redis官网指令文档&#xff1a;Commands | Docs 前言 此处的String类型是针对Redis的Value的&#xff0c;因为Key的形式都是String&#xff0c;而Value则有哈性、列表、集合等形式。 众所周知&#xff0c;由于不同编码&#xff0c;经常会出现乱码的问题&#xff0c;但在Redi…

代码签名的定义、重要性及其应用实例

代码签名证书&#xff0c;简单来说&#xff0c;是一种数字证书&#xff0c;用于验证软件开发者身份以及软件代码的完整性和未被篡改。在软件开发和分发过程中&#xff0c;代码签名证书扮演着至关重要的角色&#xff0c;它为用户提供了信任保证&#xff0c;确保所下载和安装的软…

Quarto Dashboards 教程 2:Dashboard Layout

「写在前面」 学习一个软件最好的方法就是啃它的官方文档。本着自己学习、分享他人的态度&#xff0c;分享官方文档的中文教程。软件可能随时更新&#xff0c;建议配合官方文档一起阅读。推荐先按顺序阅读往期内容&#xff1a; 1.quarto 教程 1&#xff1a;Hello, Quarto 2.qu…

vue ant form validate如何对数组下的表单校验

问题 使用Ant Design Vue校验表单时&#xff0c;通过validateFields&#xff0c;但是如何一个数组内部的校验呢&#xff1f; 效果图&#xff1a; 实现方式&#xff1a; 通过 v-for 循环渲染:name"[]"实现&#xff0c;我们直接看代码。 <template><a-for…

vue3组件之间的传参

1、父传子 defineProps 父组件 <script setup>import { reactive } from vue;import Children from ./children.vue;const parentProps reactive({name:zhangsan,age:20})</script><template><div>这是父组件</div><div>子组件:<Chil…

空间数据索引的利器:R-Tree原理与实现深度解析

空间数据索引的利器&#xff1a;R-Tree原理与实现深度解析 R-Tree的原理插入操作分裂操作查询操作 R-Tree的伪代码R-Tree的C语言实现讨论结论 R-Tree是一种平衡树&#xff0c;用于空间数据索引&#xff0c;特别是在二维或更高维度的几何对象存储和检索中。它由Antony Guttman和…

万益蓝Wonderlab益生菌:吃雪糕的自由,我终于找回来了!

虽然现在不是夏天&#xff0c;但是我必须要来说一下&#xff01;我不仅在夏天可以吃雪糕&#xff01;冬天吃也完全没有问题&#xff01; 夏天&#xff0c;对于很多人来说&#xff0c;是雪糕、冷饮和冰镇西瓜的代名词。但对于我&#xff0c;这简直就是个“禁区”。每次看着别人…

ACS510-01-157A-4 3ABD00015756-D通用变频器

商业别名:ACS510-01-157A-4 产品ID:3ABD00015756-D ABB型号名称:ACS510-01-157A-4 目录说明:ACS510-01-157A-4,157A,380V,IP21 详细描述:ACS510-01-157A-4,157A,380V,IP21 原产地:China (CN) 海关关税号:85044099 发票说明:ACS510-01-157A-4,157A,380V,IP21 按订单生产:No 最小…

农业四情监测系统:科技助力农业智慧化

【MQ-4NYSQ】农业四情监测系统&#xff0c;依托农业物联网环境传感器&#xff0c;精准捕捉苗田苗情、土壤墒情、苗田灾情以及苗田病虫草情&#xff0c;为现代农业生产提供了有力支持。 传统的苗情监测往往依赖人工手动记录与上报&#xff0c;这种方式不仅效率低下&#xff0c;…

【深度学习实战(8)】如何绘制loss曲线图

一、步骤 我们先定义一个dict&#xff0c;每一个key对应的value都是一个list。 loss_history dict((k, []) for k in ["epoch", "train_loss", "val_loss"])每一轮或者每一次迭代的损失都通过list记录下来。 loss_history["epoch"…

Task01:初识深度学习

学习视频&#xff1a; 深度学习介绍_哔哩哔哩_bilibili 03 安装【动手学深度学习v2】_哔哩哔哩_bilibili 02 深度学习介绍【动手学深度学习v2】 x轴&#xff1a;不同的模式 y轴&#xff1a;“我”想做的东西 计算机视觉中是像素&#xff0c;很难用符号表示&#xff0c;所以…

Meta通过开源Llama 3 LLM提高了标准

Meta 推出了 Llama 3,这是其最先进的开源大型语言模型(LLM)的下一代产品。这家科技巨头声称,Llama 3 在现实场景中建立了新的性能基准,超越了之前行业领先的模型,如 GPT-3.5。 Meta 在一篇博文中宣布了这一发布,并表示:"通过 Llama 3,我们致力于打造与当今最好的专有模型…

qt实现不定数量的按钮向前向后移动展示

按钮模拟移动 引言示例代码第一种思路开发环境代码结构实现代码第二种思路开发环境实现代码第三种思路开发环境实现代码总结引言 此文主要记录用qt实现按钮的移动,具体效果如下: 模拟按钮移动效果 示例代码 本文记录了三种实现方式。 第一种 思路 用动态数组vector存放创…

系统调优助手,PyTorch Profiler TensorBoard 插件教程

0x1. 前言 使用PyTorch Profiler进行性能分析已经一段时间了&#xff0c;毕竟是PyTorch提供的原生profile工具&#xff0c;个人感觉做系统性能分析时感觉比Nsys更方便一些&#xff0c;并且画的图也比较直观。这里翻译一下PyTorch Profiler TensorBoard Plugin的教程并分享一些…