docker持久化存储

Docker 支持三种不同类型的挂载,允许容器读取 从或写入文件或目录,或者 在内存文件系统上。这些类型是数据卷(通常简称为 作为卷)、绑定挂载、TMPF 和命名管道。

mount参数

https://docs.docker.com/storage/volumes/

docker run中使用mount

docker run -d \
  --name devtest \
  --mount source=myvol2,target=/app \
  nginx:latest

docker service中使用mount

docker service create \
 --mount 'type=volume,src=<VOLUME-NAME>,dst=<CONTAINER-PATH>,volume-driver=<DRIVER>,readonly,volume-opt=<KEY0>=<VALUE0>,volume-opt="<KEY1>=<VALUE1>"'
 --name myservice \
 <IMAGE>
 docker service create \
 --mount 'type=volume,src=<VOLUME-NAME>,dst=<CONTAINER-PATH>,\
volume-driver=local,\
volume-opt=type=nfs,\
volume-opt=device=<nfs-server>:<nfs-path>,\
"volume-opt=o=addr=<nfs-address>,vers=4,soft,timeo=180,bg,tcp,rw"'
 --name myservice \
 IMAGE

docker compose中使用mount:

第一次使用时会自动创建volume,后续会使用之前创建的卷。

由compose、stack自动创建的卷,卷名会加上ProjectName前缀,并会加上com.docker.stack.namespace、com.docker.compose.project、com.docker.compose.volume的labels。

service、stack删除时,不会删除创建的卷,卷需要使用docker image rm单独来删除。

services:
  frontend:
    image: node:lts
    volumes:
      - myapp:/home/node/app
volumes:
  myapp:
    #可用name来指定自定义卷名,可用于引用有特殊字符的卷。这个名称按原样使用,不受stack name的作用域限制。
    #name: "my-app-data"
    #可以从ENV传处名称,如:在.env文件中定义:DATABASE_VOLUME=my_volume_001,在compose中引用
    name: ${DATABASE_VOLUME}

也可以先在外部创建卷,然后在compose中引用它:

services:
  frontend:
    image: node:lts
    volumes:
      - myapp:/home/node/app
volumes:
  myapp:
    # 表示卷已存在,且由外部管理,Compose不会再创建,如果不存在会返回错误。
    external: true
    # 也可用name指定名部的实际名称,而compose中使用myapp来引用。
    name: actual-name-of-volume

bind mount

bind模式是直接将本地目录挂载给容器。

在容器中mount了sdb1到myvolume,另外还有hostname等文件,都是挂载的/dev/sda2。

在容器中向/mnt/myvolume写入内容,在对应node的/data/volumetest目录中可以看到。

mount|grep /dev/sd
/dev/sdb1 on /mnt/myvolume type ext4 (rw,relatime)
/dev/sda2 on /etc/resolv.conf type ext4 (rw,relatime)
/dev/sda2 on /etc/hostname type ext4 (rw,relatime)
/dev/sda2 on /etc/hosts type ext4 (rw,relatime)

查看容器docker inspect中mount相关配置:

            "Mounts": [
                {
                    "Type": "bind",
                    "Source": "/data/volumetest",
                    "Target": "/mnt/myvolume"
                }
            ],

        "Mounts": [
            {
                "Type": "bind",
                "Source": "/data/volumetest",
                "Destination": "/mnt/myvolume",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],

named volume

docker volume create: https://docs.docker.com/engine/reference/commandline/volume_create/

docker service create: https://docs.docker.com/engine/reference/commandline/service_create/#differences-between---mount-and---volume

docker volume create --help
Usage:  docker volume create [OPTIONS] [VOLUME]

Create a volume

Options:
      --availability string       Cluster Volume availability ("active", "pause", "drain") (default "active")
  -d, --driver string             Specify volume driver name (default "local")
      --group string              Cluster Volume group (cluster volumes)
      --label list                Set metadata for a volume
      --limit-bytes bytes         Minimum size of the Cluster Volume in bytes
  -o, --opt map                   Set driver specific options (default map[])
      --required-bytes bytes      Maximum size of the Cluster Volume in bytes
      --scope string              Cluster Volume access scope ("single", "multi") (default "single")
      --secret map                Cluster Volume secrets (default map[])
      --sharing string            Cluster Volume access sharing ("none", "readonly", "onewriter", "all") (default "none")
      --topology-preferred list   A topology that the Cluster Volume would be preferred in
      --topology-required list    A topology that the Cluster Volume must be accessible from
      --type string               Cluster Volume access type ("mount", "block") (default "block")1
docker volume ls
DRIVER    VOLUME NAME
local     cf8d1d07673741dc53da5947947b26c33e972140497078bbca876e60e4a67c24
local     myvolume

docker volume inspect myvolume
[
    {
        "CreatedAt": "2024-01-16T09:23:14+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/myvolume/_data",
        "Name": "myvolume",
        "Options": null,
        "Scope": "local"
    }
]

在容器中可以看到mount了myvolume,另外还有hostname等文件,都是挂载的/dev/sda2。

在容器中,向/mnt/myvolume目录写入文件,在对应节点主机的/var/lib/docker/volumes/myvolume/_data目录可以看到i(其他节点看不到,相互是独立的)。

mount|grep sda2
/dev/sda2 on /mnt/myvolume type ext4 (rw,relatime)
/dev/sda2 on /etc/resolv.conf type ext4 (rw,relatime)
/dev/sda2 on /etc/hostname type ext4 (rw,relatime)
/dev/sda2 on /etc/hosts type ext4 (rw,relatime)

在容器inspect中,hostname文件是是用属性指定,myvolume是有mount配置项。

        "ResolvConfPath": "/var/lib/docker/containers/3d1.../resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/3d1.../hostname",
        "HostsPath": "/var/lib/docker/containers/3d1.../hosts",
        "LogPath": "/var/lib/docker/containers/3d1.../3d1...-json.log",

            "Mounts": [
                {
                    "Type": "volume",
                    "Source": "myvolume",
                    "Target": "/mnt/myvolume"
                }
            ],


        "Mounts": [
            {
                "Type": "volume",
                "Name": "myvolume",
                "Source": "/var/lib/docker/volumes/myvolume/_data",
                "Destination": "/mnt/myvolume",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }
        ],

容器间挂载

# 启动一个名为container1容器,此容器包含两个数据卷/var/volume1和/var/volume2,这两个目录是在容器里的,运行容器时会自动创建
docker run -d --name container1 -v /var/volume1 -v /var/volume2 alpine ping 127.0.0.1

# 创建container2容器,挂载container1容器中的数据卷
docker run -d --rm --volumes-from container1 --name container2 alpine ping 127.0.0.1

# 利用容器间挂载备份数据
docker run -d --rm --volumes-from container1 -v /data/backup:/backup alpine tar cvf /backup/backup1.tar /var/volume1
# 恢复数据 
docker run -d --rm --volumes-from container1 -v /data/backup:/backup alpine tar xvf /backup/backup.tar -C /

NFS Volume

安装NFS Server

在Ubuntu主机上安装NFS服务端、创建共享目录、设置NFS共享。

较老版本的协议需要其它动态指定端口的 RPC 服务。幸运的是通过第 4 版的NFS,只需要端口 2049 (用于 NFS)和 111 (用于 portmapper),这样对于防火墙很容易处理。

https://ubuntu.com/server/docs/service-nfs 或 man nfs.conf查看配置说明。

如果需要设置,可在/etc/nfs.conf参与在如下位置添加:

[lockd]
port=32768

[mountd]
port=32769

[statd]
port=32770
# 重新加载共享配置
exportfs -rv

# 查看主机共享目录
showmount -e 192.168.31.40

Docker NFS volume

方法一:在容器内部安装 nfs-utils ,就如同常规到NFS客户端一样,在容器内部直接通过rpcbind方式挂载NFS共享输出,这种方式需要每个容器独立运行rpcbind服务,并且要使用 Docker systemd进程管理器 ,复杂且消耗较多资源。不过,优点是完全在容器内部控制,符合传统SA运维方式。

方法二:Host主机上创建NFS类型的Docker Volume,然后将docker volume映射到容器内部,这样容器就可以直接使用Docker共享卷,这种方式最为简洁优雅。

方法三:使用Docker volume plugin,例如 ContainX/docker-volume-netshare 可以直接将NFS共享卷作为容器卷挂载(就不需要在host主机上去执行挂载NFS命令了)

需要在swarm集群的每个节点分别创建,否则docker在创建容器里,会自动创建一个本地的Volume。

docker volume create \
--driver local \
--opt type=nfs \
--opt o="addr=192.168.31.40,rw,nolock,soft" \
--opt device=:/data/registry \
registry
docker volume ls

DRIVER    VOLUME NAME
local     registry

docker volume inspect registry
[
    {
        "CreatedAt": "2024-01-16T16:19:48+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/registry/_data",
        "Name": "registry",
        "Options": {
            "device": ":/data/registry",
            "o": "addr=192.168.31.40,rw,nolock,soft",
            "type": "nfs"
        },
        "Scope": "local"
    }
]

ls /var/lib/docker/volumes/registry/_data,目录为空,在没有设备使用时NFS并没有挂载。
sudo apt install nfs-common
# yum中包名:nfs-utils

创建service之前,再次检查各node节点,是否都已经创建好registry volume。

docker service create --name volumetest \
--mount type=volume,src=registry,dst=/var/lib/registry \
--replicas 2 alpine ping 127.0.0.1

后面调整registry的tasck配置使用共享volume,就可以在多节点上运行。见:搭建Docker Registry私有仓库