

# 将绑定挂载与 Amazon ECS 结合使用
<a name="bind-mounts"></a>

使用绑定挂载，主机上的文件或目录（如 Amazon EC2 实例）被挂载到容器中。Fargate 和 Amazon EC2 实例上托管的任务都支持绑定挂载。绑定挂载与使用它们的容器的生命周期相关。停止使用绑定挂载的所有容器后（例如，停止任务时），数据将被删除。对于托管在 Amazon EC2 实例上的任务，可以通过在任务定义中指定 `host` 和可选值 `sourcePath`，将数据绑定到主机 Amazon EC2 实例的生命周期。有关更多信息，请参阅 Docker 文档中的 [Bind mounts](https://docs.docker.com/engine/storage/bind-mounts/)。

以下是绑定挂载的常见使用案例。
+ 提供空数据卷以挂载在一个或多个容器中。
+ 在一个或多个容器中挂载主机数据卷。
+ 与相同任务中的其他容器共享来自源容器的数据卷。
+ 将 Dockerfile 路径及其内容公开到一个或多个容器。

## 使用绑定挂载时的注意事项
<a name="bind-mount-considerations"></a>

使用绑定挂载时应考虑以下事项。
+ 默认情况下，使用平台版本 `1.4.0` 或更高版本（Linux）或者 `1.0.0` 或更高版本（Windows）在 AWS Fargate 上托管的任务会收到至少 20GiB 的临时存储以用于绑定挂载。您可以增加临时存储总量，最多可达 200GiB，方法是在您的任务定义中指定 `ephemeralStorage` 参数。
+ 要在任务运行时将 Dockerfile 中的文件公开到数据卷，Amazon ECS 数据面板将查找 `VOLUME` 指令。如果 `VOLUME` 指令中指定的绝对路径与任务定义中指定的 `containerPath` 绝对路径相同，则指令路径 `VOLUME` 中的数据将复制到数据卷。在下面的 Dockerfile 示例中，`/var/log/exported` 目录中名为 `examplefile` 的文件将写入主机，然后挂载到容器中。

  ```
  FROM public.ecr.aws/amazonlinux/amazonlinux:latest
  RUN mkdir -p /var/log/exported
  RUN touch /var/log/exported/examplefile
  VOLUME ["/var/log/exported"]
  ```

  预设情况下，卷权限设置为 `0755` 和所有者设置为 `root`。您可以在 Dockerfile 中自定义这些权限。以下示例将目录的所有者定义为 `node`。

  ```
  FROM public.ecr.aws/amazonlinux/amazonlinux:latest
  RUN yum install -y shadow-utils && yum clean all
  RUN useradd node
  RUN mkdir -p /var/log/exported && chown node:node /var/log/exported
  RUN touch /var/log/exported/examplefile
  USER node
  VOLUME ["/var/log/exported"]
  ```
+ 对于 Amazon EC2 实例上托管的任务，如果未指定 `host` 和 `sourcePath` 值，则 Docker 进程守护程序将为您管理绑定挂载。如果没有容器引用此绑定挂载，则 Amazon ECS 容器代理任务清理服务最终会将其删除。默认情况下，这发生在容器退出三个小时后。但是，您可以使用 `ECS_ENGINE_TASK_CLEANUP_WAIT_DURATION` 代理变量配置此持续时间。有关更多信息，请参阅 [Amazon ECS 容器代理配置](ecs-agent-config.md)。如果需要在容器的生命周期内保持此数据，请为绑定挂载指定一个 `sourcePath` 值。
+ 对于托管在 Amazon ECS 托管实例上的任务，其根文件系统部分为只读。读/写绑定挂载必须使用可写目录，例如 `/var` 用于存储永久数据或 `/tmp` 用于存储临时数据。尝试在其他目录创建读/写绑定挂载将导致任务启动失败，并返回类似如下错误：

  ```
  error creating empty volume: error while creating volume path '/path': mkdir /path: read-only file system
  ```

  只读绑定挂载（在 `mountPoints` 参数中配置 `"readOnly": true`）可以指向主机上任何可访问的目录。

  要查看完整的可写路径列表，您可以在 Amazon ECS 托管实例上运行一个任务，并使用相关命令检查实例的挂载表。创建一个任务定义，并进行如下设置以访问主机文件系统：

  ```
  {
      "pidMode": "host",
      "containerDefinitions": [{
          "privileged": true,
          ...
      }]
  }
  ```

  然后在容器内执行以下命令：

  ```
  # List writable mounts
  cat /proc/1/root/proc/1/mounts | awk '$4 ~ /^rw,/ || $4 == "rw" {print $2}' | sort
  
  # List read-only mounts
  cat /proc/1/root/proc/1/mounts | awk '$4 ~ /^ro,/ || $4 == "ro" {print $2}' | sort
  ```
**重要**  
`privileged` 设置在主机上授予容器扩展功能，相当于 root 权限。在本例中，此项设置用于检查主机的挂载表以进行诊断。有关更多信息，请参阅 [避免以特权身份运行容器（Amazon EC2）](security-tasks-containers.md#security-tasks-containers-recommendations-avoid-privileged-containers)。

  有关在容器中运行交互式命令的更多信息，请参阅 [使用 ECS Exec 监控 Amazon ECS 容器](ecs-exec.md)。

# 在 Amazon ECS 任务定义中指定绑定挂载
<a name="specify-bind-mount-config"></a>

对于在 Fargate 或 Amazon EC2 实例上托管的 Amazon ECS 任务，以下任务定义 JSON 代码段显示任务定义的 `volumes`、`mountPoints` 和 `ephemeralStorage` 对象的语法。

```
{
   "family": "",
   ...
   "containerDefinitions" : [
      {
         "mountPoints" : [
            {
               "containerPath" : "/path/to/mount_volume",
               "sourceVolume" : "string"
            }
          ],
          "name" : "string"
       }
    ],
    ...
    "volumes" : [
       {
          "name" : "string"
       }
    ],
    "ephemeralStorage": {
	   "sizeInGiB": integer
    }
}
```

对于 Amazon EC2 实例上托管的 Amazon ECS 任务，您可以在指定任务卷详细信息时使用可选的 `host` 参数和 `sourcePath`。指定后，它将绑定挂载与任务的生命周期而不是容器关联起来。

```
"volumes" : [
    {
        "host" : {
            "sourcePath" : "string"
        },
        "name" : "string"
    }
]
```

下面将更详细地描述每个任务定义参数。

`name`  
类型：字符串  
必需：否  
卷的名称。最多允许 255 个字母（大写和小写字母）、数字、连字符（`-`）和下划线（`_`）。此名称已在容器定义 `mountPoints` 对象的 `sourceVolume` 参数中引用。

`host`  
必需：否  
`host` 参数用于将绑定挂载的生命周期绑定到主机 Amazon EC2 实例（而不是任务）及其存储位置。如果 `host` 参数为空，则 Docker 进程守护程序将为您的数据卷分配一个主机路径，但不保证数据在与其关联的容器停止运行后将保留。  
Windows 容器可在 `$env:ProgramData` 所在的驱动器上挂载整个目录。  
只有在使用 Amazon EC2 实例或 Amazon ECS 托管实例上托管的任务时，才支持 `sourcePath` 参数。  
`sourcePath`  
类型：字符串  
必需：否  
在使用 `host` 参数时，指定 `sourcePath` 可声明提供给容器的主机 Amazon EC2 容器实例上的路径。如果此参数为空，则 Docker 进程守护程序将为您分配一个主机路径。如果 `host` 参数包含 `sourcePath` 文件位置，则数据卷将在主机 Amazon EC2 容器实例上的指定位置保留，除非您手动将其删除。如果主机 Amazon EC2 容器实例上不存在 `sourcePath` 值，则 Docker 进程守护程序将创建该值。如果该位置不存在，则将导出源路径文件夹的内容。

`mountPoints`  
类型：对象数组  
必需：否  
容器中数据卷的挂载点。此参数对应于 create-container Docker API 中的 `Volumes` 和 docker run 命令的 `--volume` 选项。  
Windows 容器可在 `$env:ProgramData` 所在的驱动器上挂载整个目录。Windows 容器无法在其他驱动器上挂载目录，并且挂载点不能跨驱动器使用。您必须指定挂载点才能将 Amazon EBS 卷直接附加到 Amazon ECS 任务。    
`sourceVolume`  
类型：字符串  
必需：是，当使用 `mountPoints` 时  
要挂载的卷的名称。  
`containerPath`  
类型：字符串  
必需：是，当使用 `mountPoints` 时  
挂载卷的容器中的路径。  
`readOnly`  
类型：布尔值  
必需：否  
如果此值为`true`，则容器具有对卷的只读访问权。如果此值为`false`，则容器可对卷进行写入。默认值为 `false`。  
对于在运行 Windows 操作系统的 EC2 实例上运行的任务，请保留默认值 `false`。

`ephemeralStorage`  
类型：对象  
必需：否  
要为任务分配的临时存储容量。对于使用平台版本 `1.4.0` 或更高版本（Linux）或者 `1.0.0` 或更高版本（Windows）的 AWS Fargate 上托管的任务，此参数用于将短暂可用存储的总量扩展到默认数量之外。  
您可以使用 Copilot CLI、CloudFormation、AWS SDK 或 CLI 为绑定挂载指定临时存储容量。

# Amazon ECS 绑定挂载示例
<a name="bind-mount-examples"></a>

以下示例介绍了为容器使用绑定挂载的常见使用案例。

**为 Fargate 任务分配更多的临时存储空间**

对于在使用平台版本 `1.4.0` 或更高版本（Linux）或 `1.0.0`（Windows）的 Fargate 上托管的 Amazon ECS 任务，您可以为任务中要使用的容器分配超过原定设置数量的短暂存储。此示例可以合并到其他示例中，以便为 Fargate 任务分配更多的临时存储空间。
+ 在任务定义中，定义 `ephemeralStorage` 对象。`sizeInGiB` 必须是介于 `21` 和 `200` 之间的整数，单位是 GiB。

  ```
  "ephemeralStorage": {
      "sizeInGiB": integer
  }
  ```

**为一个或多个容器提供空数据卷**

在某些使用案例中，您希望为任务中的容器提供一些临时空间。例如，您可能在任务期间具有需要访问同一临时文件存储位置的两个数据库容器。可以通过绑定挂载实现此目标。

1. 在任务定义 `volumes` 部分中，使用名称 `database_scratch` 定义绑定挂载。

   ```
     "volumes": [
       {
         "name": "database_scratch"
       }
     ]
   ```

1. 在 `containerDefinitions` 部分中，创建数据库容器定义。以便它们挂载卷。

   ```
   "containerDefinitions": [
       {
         "name": "database1",
         "image": "my-repo/database",
         "cpu": 100,
         "memory": 100,
         "essential": true,
         "mountPoints": [
           {
             "sourceVolume": "database_scratch",
             "containerPath": "/var/scratch"
           }
         ]
       },
       {
         "name": "database2",
         "image": "my-repo/database",
         "cpu": 100,
         "memory": 100,
         "essential": true,
         "mountPoints": [
           {
             "sourceVolume": "database_scratch",
             "containerPath": "/var/scratch"
           }
         ]
       }
     ]
   ```

**将 Dockerfile 中的路径及其内容公开给容器**

在本例中，您有一个 Dockerfile，用于写入要挂载到容器中的数据。此示例适用于 Fargate 或 Amazon EC2 实例上托管的任务。

1. 创建 Dockerfile。下面的示例使用公共 Amazon Linux2 容器映像，并在我们希望挂载容器的 `/var/log/exported` 目录中创建一个名为 `examplefile` 的文件。`VOLUME` 指令应该指定绝对路径。

   ```
   FROM public.ecr.aws/amazonlinux/amazonlinux:latest
   RUN mkdir -p /var/log/exported
   RUN touch /var/log/exported/examplefile
   VOLUME ["/var/log/exported"]
   ```

   预设情况下，卷权限设置为 `0755` 和所有者设置为 `root`。可以在 Dockerfile 中变更这些权限。在下面的示例中，`/var/log/exported` 目录的所有者设置为 `node`。

   ```
   FROM public.ecr.aws/amazonlinux/amazonlinux:latest
   RUN yum install -y shadow-utils && yum clean all
   RUN useradd node
   RUN mkdir -p /var/log/exported && chown node:node /var/log/exported					    
   USER node
   RUN touch /var/log/exported/examplefile
   VOLUME ["/var/log/exported"]
   ```

1. 在任务定义 `volumes` 部分中，使用名称 `application_logs` 定义卷。

   ```
     "volumes": [
       {
         "name": "application_logs"
       }
     ]
   ```

1. 在 `containerDefinitions` 部分中，创建应用程序容器定义。以便它们挂载存储。`containerPath` 值必须与 Dockerfile 的 `VOLUME` 指令中指定的绝对路径匹配。

   ```
     "containerDefinitions": [
       {
         "name": "application1",
         "image": "my-repo/application",
         "cpu": 100,
         "memory": 100,
         "essential": true,
         "mountPoints": [
           {
             "sourceVolume": "application_logs",
             "containerPath": "/var/log/exported"
           }
         ]
       },
       {
         "name": "application2",
         "image": "my-repo/application",
         "cpu": 100,
         "memory": 100,
         "essential": true,
         "mountPoints": [
           {
             "sourceVolume": "application_logs",
             "containerPath": "/var/log/exported"
           }
         ]
       }
     ]
   ```

**为与主机 Amazon EC2 实例生命周期相关的容器提供空数据卷**

对于在 Amazon EC2 实例上托管的任务，可以使用绑定挂载，并将数据绑定到主机 Amazon EC2 实例的生命周期。您可以通过使用 `host` 参数并指定 `sourcePath` 值进行此操作。存在于 `sourcePath` 的任何文件将提交至值为 `containerPath` 的容器。写入 `containerPath` 值的任何文件将会写入主机 Amazon EC2 实例上的 `sourcePath` 值。
**重要**  
Amazon ECS 不会跨 Amazon EC2 实例同步您的存储。使用持久性存储的任务可置于您的集群中具有可用容量的任何 Amazon EC2 实例上。如果任务在停止和重新启动后需要持久存储，应始终在任务启动时使用 AWS CLI [start-task](https://docs.aws.amazon.com/cli/latest/reference/ecs/start-task.html) 命令指定相同的 Amazon EC2 实例。您也可以将 Amazon EFS 卷用于持久性存储。有关更多信息，请参阅 [将 Amazon EFS 卷与 Amazon ECS 结合使用](efs-volumes.md)。

1. 在任务定义 `volumes` 部分中，使用 `name` 和 `sourcePath` 值定义绑定挂载。在下面的示例中，主机 Amazon EC2 实例包含 `/ecs/webdata` 您希望挂载容器中的数据。

   ```
     "volumes": [
       {
         "name": "webdata",
         "host": {
           "sourcePath": "/ecs/webdata"
         }
       }
     ]
   ```

1. 在 `containerDefinitions` 部分中，使用引用绑定挂载的名称的 `mountPoints` 值和要在容器上挂载绑定挂载的 `containerPath` 值定义容器。

   ```
     "containerDefinitions": [
       {
         "name": "web",
         "image": "public.ecr.aws/docker/library/nginx:latest",
         "cpu": 99,
         "memory": 100,
         "portMappings": [
           {
             "containerPort": 80,
             "hostPort": 80
           }
         ],
         "essential": true,
         "mountPoints": [
           {
             "sourceVolume": "webdata",
             "containerPath": "/usr/share/nginx/html"
           }
         ]
       }
     ]
   ```

**在不同位置多个容器上挂载定义的卷**

您可以在任务定义中定义数据卷并在其他容器上的不同位置挂载该卷。例如，您的主机容器在 `/data/webroot` 有一个网站数据文件夹。您可能想在具有不同文档根目录的两个不同 Web 服务器上以只读方式挂载该数据卷。

1. 在任务定义 `volumes` 部分中，使用名称 `webroot` 和源路径 `/data/webroot` 定义数据卷。

   ```
     "volumes": [
       {
         "name": "webroot",
         "host": {
           "sourcePath": "/data/webroot"
         }
       }
     ]
   ```

1. 在 `containerDefinitions` 部分中，使用将 `mountPoints` 卷与指向容器的文档根目录的 `webroot` 值关联的 `containerPath` 值为每个 Web 服务器定义容器。

   ```
     "containerDefinitions": [
       {
         "name": "web-server-1",
         "image": "my-repo/ubuntu-apache",
         "cpu": 100,
         "memory": 100,
         "portMappings": [
           {
             "containerPort": 80,
             "hostPort": 80
           }
         ],
         "essential": true,
         "mountPoints": [
           {
             "sourceVolume": "webroot",
             "containerPath": "/var/www/html",
             "readOnly": true
           }
         ]
       },
       {
         "name": "web-server-2",
         "image": "my-repo/sles11-apache",
         "cpu": 100,
         "memory": 100,
         "portMappings": [
           {
             "containerPort": 8080,
             "hostPort": 8080
           }
         ],
         "essential": true,
         "mountPoints": [
           {
             "sourceVolume": "webroot",
             "containerPath": "/srv/www/htdocs",
             "readOnly": true
           }
         ]
       }
     ]
   ```

**使用 `volumesFrom` 从其他容器挂载卷**

对于托管在 Amazon EC2 实例上的任务，您可以在一个容器上定义一个或多个卷，然后在相同任务内的不同容器定义中使用 `volumesFrom` 参数从其初始定义的挂载点的 `sourceContainer` 中挂载所有卷。`volumesFrom` 参数适用于任务定义中定义的卷以及使用 Dockerfile 内置到映像中的卷。

1. （可选）要共享映像中内置的卷，请使用 Dockerfile 中的 `VOLUME` 指令。以下示例 Dockerfile 使用了 `httpd` 映像，然后添加卷并在 Apache 文档根目录中的 `dockerfile_volume` 处挂载卷。这是 `httpd` Web 服务器使用的文件夹。

   ```
   FROM httpd
   VOLUME ["/usr/local/apache2/htdocs/dockerfile_volume"]
   ```

   您可以使用此 Dockerfile 生成映像并将其推送至存储库（如 Docker Hub），然后在任务定义中使用它。以下步骤中使用的示例 `my-repo/httpd_dockerfile_volume` 映像是通过上述 Dockerfile 构建的。

1. 创建一个为您的容器定义其他卷和挂载点的任务定义。在此示例 `volumes` 部分中，您将创建一个名为 `empty` 的空卷，此卷由 Docker 进程守护程序管理。还有一个定义的主机卷，被称为 `host_etc`。它在主机容器实例上导出 `/etc` 文件夹。

   ```
   {
     "family": "test-volumes-from",
     "volumes": [
       {
         "name": "empty",
         "host": {}
       },
       {
         "name": "host_etc",
         "host": {
           "sourcePath": "/etc"
         }
       }
     ],
   ```

   在容器定义部分中，创建一个挂载之前定义的卷的容器。在此示例中，`web` 容器会挂载 `empty` 和 `host_etc` 卷。该容器使用通过 Dockerfile 中的卷构建的映像。

   ```
   "containerDefinitions": [
       {
         "name": "web",
         "image": "my-repo/httpd_dockerfile_volume",
         "cpu": 100,
         "memory": 500,
         "portMappings": [
           {
             "containerPort": 80,
             "hostPort": 80
           }
         ],
         "mountPoints": [
           {
             "sourceVolume": "empty",
             "containerPath": "/usr/local/apache2/htdocs/empty_volume"
           },
           {
             "sourceVolume": "host_etc",
             "containerPath": "/usr/local/apache2/htdocs/host_etc"
           }
         ],
         "essential": true
       },
   ```

   创建另一个容器，该容器使用 `volumesFrom` 挂载与 `web` 容器关联的所有卷。`web` 容器上的所有卷以同样方式挂载在 `busybox` 容器上。这包括在 Dockerfile 中指定的用于构建 `my-repo/httpd_dockerfile_volume` 映像的卷。

   ```
       {
         "name": "busybox",
         "image": "busybox",
         "volumesFrom": [
           {
             "sourceContainer": "web"
           }
         ],
         "cpu": 100,
         "memory": 500,
         "entryPoint": [
           "sh",
           "-c"
         ],
         "command": [
           "echo $(date) > /usr/local/apache2/htdocs/empty_volume/date && echo $(date) > /usr/local/apache2/htdocs/host_etc/date && echo $(date) > /usr/local/apache2/htdocs/dockerfile_volume/date"
         ],
         "essential": false
       }
     ]
   }
   ```

   当此任务运行时，这两个容器将挂载卷，并且 `busybox` 容器中的 `command` 会将日期和时间写入文件。此文件在每个卷文件夹中被称为 `date`。这些文件夹随后将显示在由 `web` 容器显示的网站上。
**注意**  
由于 `busybox` 容器运行快速命令然后退出，它必须在容器定义中设置为 `"essential": false`。否则，它在退出时会停止整个任务。