赞
踩
docker 的数据笼统分类可以分为下面这三种:
只读数据
这种数据大多为源码、容器的配置文件,大多数情况下与镜像进行绑定
临时数据
这部分的数据大多数情况下与容器进行绑定,属于可写数据
具体案例为存储与内存的数据,如进行 AJAX 操作后获取的数据会被存在内存中,db 数据可以存在容器里等
属于经常被读写的数据
永久数据
这部分数据属于永久保存数据,并不依托于容器或镜像存在,并且容器被销毁时,永久数据也应当被保存,而不会随着容器的销毁而消失
目前这部分的数据是还没有接触过的,也是本章笔记的主题——volume
volume 中的数据季不存在于 images 中,也不存在于 containers 中,它存在于 host 的文件系统中
volume 是可读写的
下面依然是一个 express 的 web server,通过案例了解一下 volume 的实现
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);
大概走一下这个代码的流程:
会在 80 端口启动一个服务器
服务器中有若干 endpoints
当用户填写信息的时候,会在 temp
文件夹下写一个临时文件
检查 feedback
中是否存在同名的文件
如果存在,则重定向到 /exists
如果不存在,则将 temp
中的临时文件写入 feedback
中,并删除 temp
中的临时文件
重定向到 /
这部分和之前实现的基本没什么区别,所以就不加注释了
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 渲染如下:
现在的问题就在于,temp
和 feedback
只存在于 docker 的容器里。如果这是一个真实世界里的项目,那么当版本迭代,旧容器被删除,新容器生成部署后,用户提供的信息丢失
为了避免这样的情况,就需要持久化数据,使得不管容器的状态是什么,需要持久化的数据都必须存在
前面提到了,volume 是真实存在于 host 的文件系统上,docker 通过 挂载(mounting) 的方式,实现从容器到 host 文件系统的沟通
当前的案例的情况为:
temp
中会存储临时文件
feedback
是在容器外的 volume
通过挂载使得容器可以访问 host 文件系统上的存储
如果当前文件不存在于 feedback
中,那么本程序就会将 temp
(容器中的数据存储方式) 中的文件复制到 feedback
(容器外的数据存储方式,持久化,不会随着容器的销毁而消失) 中去
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 只能通过命令行去实现,如:
# remove image and rebuild
❯ docker run -p 3000:80 -d --rm --name feedback-app -v feedback:/app/feedback feedback-node:volumes
虽然二者语法很像,不过 named volume 并不与 container 的生命周期所绑定,它不依附于 container,生命周期是独立的,因此可以被分别引用
bind mounts 其实和 volume 不是一个东西,不过它们的语法很像。如下面会创建一个 bind mounts 和一个 named volume:
# may need to check the file sharing permission
❯ docker 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 的文件夹
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。