1. 概述

Docker 提供了多种方法在运行容器中持久存储和共享数据。然而,在一个运行的容器中我们可能需要不止一种文件存储,例如为了创建备份或者提供不同的访问权限。或者对于同一个容器,我们可能需要添加命名卷并将其绑定到特定路径。

在这篇教程中,我们将看到如何在容器上挂载多个卷。我们将通过命令行和Docker Compose示例来探讨一些常见的开发使用场景。

2. 在 Docker 容器上挂载多个卷

Docker 使用存储来持久化数据,因此即使容器重启也不会丢失信息。此外,如果我们在集群环境中共享数据,数据持久性将变得尤为重要。

2.1. 使用多个卷

首先,让我们创建两个不同的命名卷:

docker volume create --name first-volume-data && docker volume create --name second-volume-data

假设我们想要为我们的 Web 应用程序挂载两个不同的卷,其中一个路径必须是只读的。

如果我们使用命令行,可以使用\-v选项:

docker run -d -p 8080:8080 -v first-volume-data:/container-path-1 -v second-volume-data:/container-path-2:ro --name web-app web-app:latest

我们还可以使用匿名卷,例如通过包含\-v container-path。Docker 会为我们创建它,但在删除容器后它会被移除。

让我们检查我们的容器以验证挂载是否正确:

docker inspect 0050cda73c6f

我们可以看到来源、目的地、类型以及第二个卷的只读状态等相关信息:

"Mounts": [
  {
      "Type": "volume",
      "Name": "first-volume-data",
      "Source": "/var/lib/docker/volumes/first-volume-data/_data",
      "Destination": "/container-path-1",
      "Driver": "local",
      "Mode": "z",
      "RW": true,
      "Propagation": ""
  },
  {
      "Type": "volume",
      "Name": "second-volume-data",
      "Source": "/var/lib/docker/volumes/second-volume-data/_data",
      "Destination": "/container-path-2",
      "Driver": "local",
      "Mode": "z",
      "RW": false,
      "Propagation": ""
  }
]

同样,Docker 建议我们使用--mount选项:

docker run -d \
  --name web-app \
  -p 8080:8080 \
  --mount source=first-volume-data,target=/container-path-1 \
  --mount source=second-volume-data,target=/container-path-2,readonly \
  web-app:latest

结果与\-v选项相同,但除了更好的清晰度外,--mount是我们在Swarm模式下使用服务时唯一可以使用卷的方法。

因此,如果我们想为具有多个挂载的Web应用服务创建一个服务,我们需要使用--mount选项:

docker service create --name web-app-service \
  --replicas 3 \
  --publish published=8080,target=80 \
  --mount source=first-volume-data,target=/container-path-1 \
  --mount source=second-volume-data,target=/container-path-2,readonly \
  web-app

同样,我们可以检查我们的服务:

docker service inspect web-app-service

类似地,我们也将获得关于服务规范中容器的一些信息:

"Mounts": [
  {
      "Type": "volume",
      "Source": "first-volume-data",
      "Target": "/container-path-1"
  },
  {
      "Type": "volume",
      "Source": "second-volume-data",
      "Target": "/container-path-2",
      "ReadOnly": true
  }
]

2.2. 使用卷和绑定挂载

我们还可能希望在特定目录或主机文件中与卷一起使用绑定挂载。

假设我们有一个MySQL数据库镜像,并且需要运行初始脚本来创建模式或填充数据:

docker run -d \
  --name db \
  -p 3306:3306 \
  --mount source=first-volume-data,target=/var/lib/mysql \
  --mount type=bind,source=/init.sql,target=/docker-entrypoint-initdb.d/init.sql \
  mysql:latest

如果检查我们的容器,我们现在可以看到两种不同的挂载类型:

"Mounts": [
  {
      "Type": "volume",
      "Name": "first-volume-data",
      "Source": "/var/lib/docker/volumes/first-volume-data/_data",
      "Destination": "/var/lib/mysql",
      "Driver": "local",
      "Mode": "z",
      "RW": true,
      "Propagation": ""
  },
  {
      "Type": "bind",
      "Source": "/init.sql",
      "Destination": "/docker-entrypoint-initdb.d/init.sql",
      "Mode": "",
      "RW": true,
      "Propagation": "rprivate"
  }
]

2.3. 使用多个绑定挂载

同样,我们可以使用多个绑定挂载,例如当我们使用Localstack时,这是一个本地 AWS 模拟器。

假设我们想要启动一个本地S3服务:

docker run --name localstack -d \
  -p 4563-4599:4563-4599 -p 8055:8080 \
  -e SERVICES=s3 -e DEBUG=1 -e DATA_DIR=/tmp/localstack/data \
  -v /.localstack:/var/lib/localstack -v /var/run/docker.sock:/var/run/docker.sock \
  localstack/localstack

在检查容器时,我们看到主机配置中的多个绑定:

"Binds": [
  "/.localstack:/var/lib/localstack",
  "/var/run/docker.sock:/var/run/docker.sock"
]

3. Docker Compose

让我们看看使用Docker Compose实现多个挂载更紧凑的方式。

3.1. 使用多个卷

首先,让我们根据 YAML 模板开始创建两个卷:

services:
  my_app:
    image: web-app:latest
    container_name: web-app
    ports:
      - "8080:8080"
    volumes:
      - first-volume-data:/container-path-1
      - second-volume-data:/container-path-2:ro

volumes:
  first-volume-data:
    driver: local
  second-volume-data:
    driver: local

一旦定义了我们之前创建的卷,我们可以在服务中以named-volume:container-path的形式添加挂载。

Docker Compose 的长语法也是可用的,例如:

volumes:
  - type: volume
    source: volume-data
    target: /container-path

3.2. 使用卷和绑定挂载

再次,这里是一个使用 MySQL 服务的示例:

services:
  mysql-db:
    image: mysql:latest
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_ROOT_HOST=localhost
    ports:
      - '3306:3306'
    volumes:
      - db:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql

volumes:
  db:
    driver: local

3.3. 使用多个绑定挂载

最后,让我们将之前的 Localstack 示例转换为Docker Compose:

services:
  localstack:
    privileged: true
    image: localstack/localstack:latest
    container_name: localstack
    ports:
      - '4563-4599:4563-4599'
      - '8055:8080'
    environment:
      - SERVICES=s3
      - DEBUG=1
      - DATA_DIR=/tmp/localstack/data
    volumes:
      - './.localstack:/var/lib/localstack'
      - '/var/run/docker.sock:/var/run/docker.sock'

4. 总结

在这篇文章中,我们看到了如何使用 Docker 创建多个挂载。

我们看到了绑定挂载和命名卷的一些组合,也看到了在命令行和 Docker Compose 中使用的实际使用案例。

如往常一样,我们可以在GitHub上的仓库中找到工作代码示例。