Docker入门:使用数据卷、文件挂载进行数据存储与共享

2023-04-07 15:46:17 浏览数 (1)

一、前言

默认情况下,在Docker容器内创建的所有文件都只能在容器内部使用。容器删除后,数据也跟着删除,虽然通常我们不会删除容器,但是一旦宿主机发生故障,我们重新创建容器恢复服务,那么之前容器创建的文件就会丢失,这会为我们带来不必要的麻烦。另外,由于在容器中的文件对于Docker来说是卸载了“可写层”,性能也会下降,所以我们需要把数据写到宿主机,方便数据的存储、转移,以及容器间的数据共享,提高数据读写性能等等

1、本文主要内容

  • 使用Golang提供HTTP服务,将日志写入磁盘,并制作镜像
  • 数据卷绑定、文件挂载、tmpfs缓存挂载优点与特性介绍
  • 将数据卷(Volume)绑定到容器指定目录,实现容器数据的持久化存储与共享
  • 将宿主机文件/目录挂载(bind mounts)到容器指定目录,实现容器数据的持久化存储与共享
  • 将宿主机tmpfs缓存挂载到容器指定目录

2、本文环境

环境

说明

Docker

Docker CE 20.10.21

Docker Desktop

4.14

Docker Golang镜像

1.19.4

Golang

1.19.4

CentOS

CentOS 7.X

Windows

Windows 11

curl for Windows

7.87

本文中使用命令如非专门说明,均在Windows cmd中执行,如使用macOS,可以替换为对应命令操作

二、数据卷与挂载

1、数据卷(Volume)

数据卷(Volume)是Docker官方推荐的数据持久化存储方式,也是目前最成熟的Docker持久化存储方案,它具备以下优点/特性

  • 数据卷比绑定挂载(Bind mounts)更容易备份或迁移
  • 数据卷可以通过 Docker CLI 命令或 Docker API 进行管理
  • 数据卷适用于 Linux 和 Windows 容器
  • 数据卷支持存储在远端主机上,并支持加密存储
  • 数据卷的数据不支持在宿主机上直接查看或管理
  • 数据卷可以在宿主机上预先占用空间,以免磁盘被占用导致容器无法正常运行
  • 在 Mac 和 Windows 开发环境下,数据卷相比绑定挂载(Bind mounts)有更好的性能
  • 数据卷可以用于容器之间共享数据

2、绑定挂载(Bind mounts)

绑定挂载(Bind mounts)是Docker早期提供的数据持久化存储方式,我们可以将宿主机的目录/文件挂载到容器中, 并绑定在容器指定的目录/文件上,它具备以下优点/特性

  • 挂载目录/文件非常方便,但文件的备份跟迁移相对麻烦
  • 挂载的目录/文件无法通过Docker本身进行管理
  • 挂载的目录/文件使用的磁盘空间可能会受其他程序影响
  • 挂载的目录/文件可以便捷的在宿主机上进行查看及管理
  • 挂载的目录/文件可以用于容器之间共享数据 绑定挂载为直译,我更愿称之为文件挂载

3、缓存挂载(tmpfs mounts)

Docker在Linux上提供了tmpfs(一种基于内存的文件系统)挂载,可以让容器把内容放在宿主机内存中进行读写,它具备以下优点/特性

  • 存储在内存中,有较好的读写性能
  • 存储在内存中,适合存储一些敏感信息,或者随着容器关闭就丢弃的数据
  • 只能被Linux上运行的Docker容器使用(?),且不能用于容器间的数据共享 虽然官方文档上说只能在Linux环境下的Docker上使用,但我基于Docker Desktop 4.14 on Windows测试下来是可以,猜测是Docker官方在某个Docker Desktop版本之后支持了该特性的测试,但并没有更新文档

4、关系说明图

这个图可以帮助我们理解这三种方式,后面我就简称为数据卷绑定、文件挂载、缓存挂载

三、镜像制作

创建镜像制作根目录,例如:d:dockersharedata(Windows),~/docker/sharedata(macOS),后续所有文件都放在该目录中

1、准备测试代码

用golang写的一个简单http server,并将运行日志写入到文件中,新建main.go文件保存以下代码

代码语言:javascript复制
package main

import (
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
)

// 初始化日志配置
func initLog() {
    // 创建文件夹
    err := os.MkdirAll("/app/logs/sharedata", 0755)
    if err != nil {
        log.Fatalf("Failed to open log file: %v", err)
        return
    }
    // 打开或者创建一个日志文件
    logFile, err := os.OpenFile("/app/logs/sharedata/default.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {
        log.Fatalf("Failed to open log file: %v", err)
        return
    }
    // 设置日志输出到文件&终端
    multiLog := io.MultiWriter(logFile, os.Stdout)
    log.SetOutput(multiLog)

    // 设置hostname作为日志前缀
    log.SetPrefix(getHostName()   " - ")
}

// 获取主机名
func getHostName() string {
    hostname, err := os.Hostname()
    if err != nil {
        log.Fatalf("Failed to open log file: %v", err)
    }
    return hostname
}

// 接收HTTP请求
func handler(w http.ResponseWriter, r *http.Request) {
    log.Println("request:", r.Host, r.URL)
    var welcome = r.URL.Path[1:]
    if len(welcome) == 0 {
        welcome = "World"
    }
    fmt.Fprintf(w, "Hello, %s!  ---ken.io", welcome)
}

func main() {
    initLog()
    http.HandleFunc("/", handler)
    log.Println("starting server on port 8000")
    log.Fatal(http.ListenAndServe(":8000", nil))
}

2、编写Dockerfile文件

新建Dockerfile文件,保存以下指令

代码语言:javascript复制
# 使用官方提供的 Go 镜像作为基础镜像
FROM golang:1.19.4

# 将工作目录设置为 /app
WORKDIR /app

# 将当前目录下的所有内容复制到 /app 下
COPY . /app

# 创建日志文件夹
RUN mkdir -m 777 logs

# 允许宿主机访问容器的 8000 端口
EXPOSE 8000

# 设置容器进程为:go run main.go
CMD go run main.go

3、编译镜像文件

代码语言:javascript复制
#进入目录
d: && cd d:dockersharedata

#编译指定版本镜像(注意结尾一定要加.)
docker build -t sharedata:1.0 .

#查看本地镜像
docker images

#镜像列表
REPOSITORY      TAG       IMAGE ID       CREATED          SIZE
sharedata       1.0       edbb8e808d63   19 seconds ago   992MB

4、启动容器

代码语言:javascript复制
docker run -d --name sdtest -p 8000:8000 sharedata:1.0

容器启动成功后,我们curl命令或者浏览器访问下http://localhost:8000,看到以下内容表示运行成功

代码语言:javascript复制
Hello, World!  ---ken.io

5、查看日志

进入已启动的容器sdtest,查看我们在代码中写入的日志情况

代码语言:javascript复制
# 进入已启动的容器sdtest
docker exec -it sdtest /bin/bash

# 查看目录中的文件
ls
# 这里可以看到在Dockerfile中创建的logs文件夹
# Dockerfile  logs  main.go

# 进入logs/sharedata目录,然后通过cat命令查看日志信息
cd logs/sharedata/
cat default.log

# 输出内容示例
6b3e4d12e2b6 - 2022/12/22 22:19:58 starting server on port 8000
6b3e4d12e2b6 - 2022/12/22 22:20:31 request: localhost:8000 /

四、数据卷绑定

1、创建数据卷

代码语言:javascript复制
# 创建数据卷
docker volume create voltest

# 查看所有数据卷
docker volume ls

# 查看指定数据卷属性
docker volume inspect voltest

# 示例
[
    {
        "CreatedAt": "2022-12-22T12:11:54Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/voltest/_data",
        "Name": "voltest",
        "Options": {},
        "Scope": "local"
    }
]

2、创建容器并绑定数据卷

这里我们基于sharedata镜像创建2-4个容器,将/app/logs目录映射到数据卷voltest,这样在容器中对该目录的数据读写,实际上都是读写的数据卷:voltest

代码语言:javascript复制
# 使用-v参数(简洁)
docker run -d --name sd01 -v voltest:/app/logs -p 8001:8000 sharedata:1.0
docker run -d --name sd02 -v voltest:/app/logs -p 8002:8000 sharedata:1.0

# 使用--mount参数(可指定更详细参数)
docker run -d --name sd03 --mount source=voltest,target=/app/logs -p 8003:8000 sharedata:1.0
docker run -d --name sd04 --mount source=voltest,target=/app/logs -p 8004:8000 sharedata:1.0

3、查看日志

进入已启动的容器sd01(01-04任选一个即可),查看我们在代码中写入的日志情况

代码语言:javascript复制
# 进入已启动的容器sd01
docker exec -it sd01 /bin/bash

# 查看日志
root@b7f5df537c57:/app  cat /app/logs/sharedata/default.log

# 输出内容示例
b7f5df537c57 - 2022/12/22 22:55:11 starting server on port 8000
42d90decf7a9 - 2022/12/22 22:55:17 starting server on port 8000
0a85e6bd4e32 - 2022/12/22 22:55:24 starting server on port 8000
bd99a6c2fcd7 - 2022/12/22 22:55:30 starting server on port 8000

从日志中可以看出,在其中1个容器中,看到了4个容器中mian.go的启动日志

此时我们可以分别访问下4个容器中HTTP Server

代码语言:javascript复制
# 新开一个终端窗口,访问以下日志
# 如果没有curl,也可以通过浏览器访问
curl http://localhost:8001
curl http://localhost:8002
curl http://localhost:8003
curl http://localhost:8004

然后我们再看下日志

代码语言:javascript复制
# 查看日志
root@b7f5df537c57:/app  cat /app/logs/sharedata/default.log

# 输出内容示例
b7f5df537c57 - 2022/12/22 22:55:11 starting server on port 8000
42d90decf7a9 - 2022/12/22 22:55:17 starting server on port 8000
0a85e6bd4e32 - 2022/12/22 22:55:24 starting server on port 8000
bd99a6c2fcd7 - 2022/12/22 22:55:30 starting server on port 8000
b7f5df537c57 - 2022/12/22 22:08:01 request: localhost:8001 /
42d90decf7a9 - 2022/12/22 22:08:01 request: localhost:8002 /
0a85e6bd4e32 - 2022/12/22 22:08:01 request: localhost:8003 /
bd99a6c2fcd7 - 2022/12/22 22:08:01 request: localhost:8004 /

五、文件挂载

1、创建容器并挂载目录

首先要在宿主机上创建目录 d:dockerlogs(Windows),~/docker/logs(macOS)

这里我们基于sharedata镜像创建2-4个容器,并将宿主机目录挂载到容器的/app/logs目录,这样在容器中对/app/logs目录的数据读写,实际上都是读写宿主机目录的读写

另外,我们是在Windows上,我们可以指定Windows上的路径格式,也可以保持风格统一,把目录转换为:/d/docker/logs

代码语言:javascript复制
# 使用-v参数(简洁)
docker run -d --name sd05 -v /d/docker/logs:/app/logs -p 8005:8000 sharedata:1.0
docker run -d --name sd06 -v /d/docker/logs:/app/logs -p 8006:8000 sharedata:1.0

# 使用--mount参数(可指定更详细参数)
docker run -d --name sd07 --mount type=bind,source=/d/docker/logs,target=/app/logs -p 8007:8000 sharedata:1.0
docker run -d --name sd08 --mount type=bind,source=/d/docker/logs,target=/app/logs -p 8008:8000 sharedata:1.0

如果执行命令时弹窗提示需要授权,记得确认下即可

另外,如果你不想挂载目录,你也可以选择挂载文件,例如:

代码语言:javascript复制
docker run -d --name sd09 -v /d/docker/logs/log.txt:/app/logs/sharedata/default.log -p 8009:8000 sharedata:1.0

2、查看日志

查看宿主机上日志的写入情况

代码语言:javascript复制
# 进入日志目录并查看所有文件
d: && cd d:dockerlogs && dir

# 查看日志
type sharedatadefault.log

# 输出内容示例
669133c17e2d - 2022/12/22 23:13:39 starting server on port 8000
b54d1857a6f5 - 2022/12/22 23:13:45 starting server on port 8000
4045ec36d051 - 2022/12/22 23:13:52 starting server on port 8000
9e1b80edb8fa - 2022/12/22 23:13:58 starting server on port 8000

从日志中可以看出,在其中1个容器中,看到了4个容器中mian.go的启动日志

访问HTTP Server之后的日志,可以参考上一章节

六、缓存挂载

tmpfs是Linux上的缓存系统,为了更接近生产环境的使用,这里使用CentOS虚拟机进行测试,镜像制作等准备工作略去。不过,如果你电脑没有额外资源来运行虚拟机进行测试了,那么你基于Docker Desktop环境测试也可以

1、创建容器并挂载缓存

代码语言:javascript复制
# 使用--tmpfs参数(简洁)
docker run -d --name sd10 --tmpfs /app/logs -p 8010:8000 sharedata:1.0

# 使用--mount参数(可指定更详细参数)
docker run -d --name sd11 --mount target=/app/logs -p 8011:8000 sharedata:1.0

2、查看日志

这里我们先访问下2个容器中HTTP Server

代码语言:javascript复制
# 新开一个终端窗口,访问以下日志
# 如果没有curl,也可以通过浏览器访问
curl http://localhost:8010
curl http://localhost:8011
curl http://localhost:8010/ken
curl http://localhost:8011/ken

然后选择sd10进行查看日志

代码语言:javascript复制
# 进入已启动的容器sd10
docker exec -it sd10 /bin/bash

# 查看日志
root@345bfa4c4cbe:/app cat /app/logs/sharedata/default.log

# 输出内容示例
345bfa4c4cbe - 2022/12/23 21:03:40 starting server on port 8000
345bfa4c4cbe - 2022/12/23 21:03:55 request: localhost:8010 /
345bfa4c4cbe - 2022/12/23 21:03:56 request: localhost:8010 /ken

通过日志也可以看出,两个容器的日志内容并没有互通,我们把容器重启一下,再进入容器查看,会发现只有新的一条启动日志了

代码语言:javascript复制
# 重启容器
docker restart sd10

# 进入已启动的容器sd10
docker exec -it sd10 /bin/bash

# 查看日志
root@345bfa4c4cbe:/app cat /app/logs/sharedata/default.log

# 输出内容示例
345bfa4c4cbe - 2022/12/23 21:08:11 starting server on port 8000

七、备注

一、参数说明

Docker提供-v(—volume)、—mount、—tmpfs 三个参数,可以在创建容器的时候绑定数据卷/挂载文件/挂载缓存,其中—tmpfs只用于挂载缓存,非常简单,只有上述一种用法,这里只介绍-v和—mount

-v参数说明

-v命令相对简单,是通过:连接的三段式参数来指定源以及目标

参数格式

示例

说明

{source}:{target}

-v voltest:/app/logs

{source}可以是数据卷也可以是文件/目录

—mount参数说明

—mount命令要比-v命令更强大,它主要有三个参数:type、source、target用来指定类型、源、目标,但也可以指定其他参数来调整对源的读取权限等等

参数

说明

type

挂载类型:bind、volume、 tmpfs,分别对应目录、数据卷、tmpfs缓存

source

挂载源,可以是:宿主机目录/文件绝对路径、数据卷名称,type=tmpfs时无需指定source

target

挂载目标:容器目录/文件的绝对路径

destination

作用相当于target

readonly

只读模式,不可修改源

bind-propagation

传播模式,挂载文件/目录时使用,默认是rprivate,也可指定为:private, rshared, shared, rslave, slave,只支持在服务器环境下使用,不支持Docker Desktop

tmpfs-size

tmpfs缓存大小,单位:字节,默认无限制;type=tmpfs时生效

tmpfs-mode

tmpfs缓存权限,例如:700或0770,默认为1777;type=tmpfs时生效

二、本文参考

  • https://docs.docker.com/storage/volumes/
  • https://docs.docker.com/storage/bind-mounts/
  • https://docs.docker.com/storage/tmpfs/

  • 系列名称:Docker入门教程
  • 上一篇:Docker入门:使用Dockerfile构建Docker镜像
  • 下一篇:Docker入门:端口映射与容器互联

0 人点赞