将 AWS EFS 与 Docker 结合使用
Using AWS EFS with Docker
我在我的单个容器 EB 部署中使用亚马逊提供的新弹性文件系统。我想不通为什么挂载的 EFS 不能映射到容器中。
在 /efs-mount-point 的主机上成功执行了 EFS 装载。
提供给Dockerrun.aws.json的是
{
"AWSEBDockerrunVersion": "1"
"Volumes": [
{
"HostDirectory": "/efs-mount-point",
"ContainerDirectory": "/efs-mount-point"
}
]
}
一旦容器启动,就会在容器中创建该卷 运行。但是它映射了主机目录 /efs-mount-point,而不是实际的 EFS 挂载点。我不知道如何让 Docker 映射到挂载在 /efs-mount-point 而不是主机目录的 EFS 卷中。
NFS 卷与 Docker 兼容吗?
在主机 EC2 实例中安装 EFS 卷后,您需要 restart docker。
举个例子,.ebextensions/efs.config
:
commands:
01mkdir:
command: "mkdir -p /efs-mount-point"
02mount:
command: "mountpoint -q /efs-mount-point || mount -t nfs4 -o nfsvers=4.1 $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone).fs-fa35c253.efs.us-west-2.amazonaws.com:/ /efs-mount-point"
03restart:
command: "service docker restart"
带有 AWS Beanstalk 的 EFS - Multicontainer Docker 将起作用。但是许多事情将停止工作,因为您必须在安装 EFS 后重新启动 docker。
实例命令
四处搜索可能会发现您需要在安装 EFS 后执行 "docker restart"。没那么简单。当发生自动缩放和/或部署新版本的应用程序时,您会遇到麻烦。
下面是我用于将 EFS 挂载到 docker 实例的脚本,其中需要以下步骤:
- 停止 ECS 管理器。耗时 60 秒。
- 停止Docker服务
- 杀死剩下的docker个东西
- 删除网络以前的绑定。查看问题https://github.com/docker/docker/issues/7856#issuecomment-239100381
- 挂载 EFS
- 启动 docker 服务。
- 启动ECS服务
- 等待 120 秒。使 ECS 进入正确的 start/* 状态。否则例如00enact 脚本将失败。请注意,此显示是强制性的,并且很难找到任何文档。
这是我的脚本:
.ebextensions/commands.config
:
commands:
01stopdocker:
command: "sudo stop ecs > /dev/null 2>&1 || /bin/true && sudo service docker stop"
02killallnetworkbindings:
command: 'sudo killall docker > /dev/null 2>&1 || /bin/true'
03removenetworkinterface:
command: "rm -f /var/lib/docker/network/files/local-kv.db"
test: test -f /var/lib/docker/network/files/local-kv.db
# Mount the EFS created in .ebextensions/media.config
04mount:
command: "/tmp/mount-efs.sh"
# On new instances, delay needs to be added because of 00task enact script. It tests for start/ but it can be various states of start...
# Basically, "start ecs" takes some time to run, and it runs async - so we sleep for some time.
# So basically let the ECS manager take it's time to boot before going on to enact scritps and post deploy scripts.
09restart:
command: "service docker start && sudo start ecs && sleep 120s"
挂载脚本和环境变量
.ebextensions/mount-config.config
# efs-mount.config
# Copy this file to the .ebextensions folder in the root of your app source folder
option_settings:
aws:elasticbeanstalk:application:environment:
EFS_REGION: '`{"Ref": "AWS::Region"}`'
# Replace with the required mount directory
EFS_MOUNT_DIR: '/efs_volume'
# Use in conjunction with efs_volume.config or replace with EFS volume ID of an existing EFS volume
EFS_VOLUME_ID: '`{"Ref" : "FileSystem"}`'
packages:
yum:
nfs-utils: []
files:
"/tmp/mount-efs.sh":
mode: "000755"
content : |
#!/bin/bash
EFS_REGION=$(/opt/elasticbeanstalk/bin/get-config environment | jq -r '.EFS_REGION')
EFS_MOUNT_DIR=$(/opt/elasticbeanstalk/bin/get-config environment | jq -r '.EFS_MOUNT_DIR')
EFS_VOLUME_ID=$(/opt/elasticbeanstalk/bin/get-config environment | jq -r '.EFS_VOLUME_ID')
echo "Mounting EFS filesystem ${EFS_DNS_NAME} to directory ${EFS_MOUNT_DIR} ..."
echo 'Stopping NFS ID Mapper...'
service rpcidmapd status &> /dev/null
if [ $? -ne 0 ] ; then
echo 'rpc.idmapd is already stopped!'
else
service rpcidmapd stop
if [ $? -ne 0 ] ; then
echo 'ERROR: Failed to stop NFS ID Mapper!'
exit 1
fi
fi
echo 'Checking if EFS mount directory exists...'
if [ ! -d ${EFS_MOUNT_DIR} ]; then
echo "Creating directory ${EFS_MOUNT_DIR} ..."
mkdir -p ${EFS_MOUNT_DIR}
if [ $? -ne 0 ]; then
echo 'ERROR: Directory creation failed!'
exit 1
fi
chmod 777 ${EFS_MOUNT_DIR}
if [ $? -ne 0 ]; then
echo 'ERROR: Permission update failed!'
exit 1
fi
else
echo "Directory ${EFS_MOUNT_DIR} already exists!"
fi
mountpoint -q ${EFS_MOUNT_DIR}
if [ $? -ne 0 ]; then
AZ=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)
echo "mount -t nfs4 -o nfsvers=4.1 ${AZ}.${EFS_VOLUME_ID}.efs.${EFS_REGION}.amazonaws.com:/ ${EFS_MOUNT_DIR}"
mount -t nfs4 -o nfsvers=4.1 ${AZ}.${EFS_VOLUME_ID}.efs.${EFS_REGION}.amazonaws.com:/ ${EFS_MOUNT_DIR}
if [ $? -ne 0 ] ; then
echo 'ERROR: Mount command failed!'
exit 1
fi
else
echo "Directory ${EFS_MOUNT_DIR} is already a valid mountpoint!"
fi
echo 'EFS mount complete.'
资源和配置
您必须更改下面的 option_settings。 要找到您必须在下面的 option_settings 下定义的 VPC 和子网,请查看 AWS Web控制台 -> VPC,您必须在那里找到默认 VPC id 和 3 个默认子网 id。如果你的 beantalk 使用自定义 VPC,你必须使用这些设置。
.ebextensions/efs-volume.config
:
# efs-volume.config
# Copy this file to the .ebextensions folder in the root of your app source folder
option_settings:
aws:elasticbeanstalk:customoption:
EFSVolumeName: "EB-EFS-Volume"
VPCId: "vpc-xxxxxxxx"
SubnetUSWest2a: "subnet-xxxxxxxx"
SubnetUSWest2b: "subnet-xxxxxxxx"
SubnetUSWest2c: "subnet-xxxxxxxx"
Resources:
FileSystem:
Type: AWS::EFS::FileSystem
Properties:
FileSystemTags:
- Key: Name
Value:
Fn::GetOptionSetting: {OptionName: EFSVolumeName, DefaultValue: "EB_EFS_Volume"}
MountTargetSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for mount target
SecurityGroupIngress:
- FromPort: '2049'
IpProtocol: tcp
SourceSecurityGroupId:
Fn::GetAtt: [AWSEBSecurityGroup, GroupId]
ToPort: '2049'
VpcId:
Fn::GetOptionSetting: {OptionName: VPCId}
MountTargetUSWest2a:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId: {Ref: FileSystem}
SecurityGroups:
- {Ref: MountTargetSecurityGroup}
SubnetId:
Fn::GetOptionSetting: {OptionName: SubnetUSWest2a}
MountTargetUSWest2b:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId: {Ref: FileSystem}
SecurityGroups:
- {Ref: MountTargetSecurityGroup}
SubnetId:
Fn::GetOptionSetting: {OptionName: SubnetUSWest2b}
MountTargetUSWest2c:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId: {Ref: FileSystem}
SecurityGroups:
- {Ref: MountTargetSecurityGroup}
SubnetId:
Fn::GetOptionSetting: {OptionName: SubnetUSWest2c}
资源:
AWS 有在 elastic beanstalk 上自动创建和安装 EFS 的说明。他们可以找到 here
这些说明 link 两个配置文件将被自定义并放置在部署包的 .ebextensions 文件夹中。
文件 storage-efs-mountfilesystem.config 需要进一步修改才能与 Docker 容器一起使用。添加以下命令:
02_restart:
command: "service docker restart"
并且对于 multi-container 环境 Elastic Container Service 也必须重新启动(上面 docker 重新启动时它被杀死):
03_start_eb:
command: |
start ecs
start eb-docker-events
sleep 120
test: sh -c "[ -f /etc/init/ecs.conf ]"
所以 storage-efs-mountfilesystem.config 的完整 commands 部分是:
commands:
01_mount:
command: "/tmp/mount-efs.sh"
02_restart:
command: "service docker restart"
03_start_eb:
command: |
start ecs
start eb-docker-events
sleep 120
test: sh -c "[ -f /etc/init/ecs.conf ]"
这不起作用的原因 "out-of-the-box" 是因为 docker 守护程序由 EC2 实例在 .ebextensions 中的命令 运行 之前启动。启动顺序为:
- 启动docker守护进程
- 在multi-containerdocker环境中启动弹性容器服务代理
- 运行 .ebextensions
中的命令
- 运行 个容器应用
第一步,docker 守护进程提供给容器的文件系统视图是固定的。因此,在步骤 3 中对主机文件系统所做的更改不会反映在容器的视图中。
一个奇怪的效果是容器在文件系统挂载到主机之前看到一个挂载点。主机看到挂载的文件系统。因此,容器写入的文件将写入挂载目录下的主机目录,而不是挂载的文件系统。卸载 EC2 主机上的文件系统将暴露写入挂载目录的容器文件。
我在我的单个容器 EB 部署中使用亚马逊提供的新弹性文件系统。我想不通为什么挂载的 EFS 不能映射到容器中。
在 /efs-mount-point 的主机上成功执行了 EFS 装载。
提供给Dockerrun.aws.json的是
{
"AWSEBDockerrunVersion": "1"
"Volumes": [
{
"HostDirectory": "/efs-mount-point",
"ContainerDirectory": "/efs-mount-point"
}
]
}
一旦容器启动,就会在容器中创建该卷 运行。但是它映射了主机目录 /efs-mount-point,而不是实际的 EFS 挂载点。我不知道如何让 Docker 映射到挂载在 /efs-mount-point 而不是主机目录的 EFS 卷中。
NFS 卷与 Docker 兼容吗?
在主机 EC2 实例中安装 EFS 卷后,您需要 restart docker。
举个例子,.ebextensions/efs.config
:
commands:
01mkdir:
command: "mkdir -p /efs-mount-point"
02mount:
command: "mountpoint -q /efs-mount-point || mount -t nfs4 -o nfsvers=4.1 $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone).fs-fa35c253.efs.us-west-2.amazonaws.com:/ /efs-mount-point"
03restart:
command: "service docker restart"
带有 AWS Beanstalk 的 EFS - Multicontainer Docker 将起作用。但是许多事情将停止工作,因为您必须在安装 EFS 后重新启动 docker。
实例命令
四处搜索可能会发现您需要在安装 EFS 后执行 "docker restart"。没那么简单。当发生自动缩放和/或部署新版本的应用程序时,您会遇到麻烦。
下面是我用于将 EFS 挂载到 docker 实例的脚本,其中需要以下步骤:
- 停止 ECS 管理器。耗时 60 秒。
- 停止Docker服务
- 杀死剩下的docker个东西
- 删除网络以前的绑定。查看问题https://github.com/docker/docker/issues/7856#issuecomment-239100381
- 挂载 EFS
- 启动 docker 服务。
- 启动ECS服务
- 等待 120 秒。使 ECS 进入正确的 start/* 状态。否则例如00enact 脚本将失败。请注意,此显示是强制性的,并且很难找到任何文档。
这是我的脚本:
.ebextensions/commands.config
:
commands:
01stopdocker:
command: "sudo stop ecs > /dev/null 2>&1 || /bin/true && sudo service docker stop"
02killallnetworkbindings:
command: 'sudo killall docker > /dev/null 2>&1 || /bin/true'
03removenetworkinterface:
command: "rm -f /var/lib/docker/network/files/local-kv.db"
test: test -f /var/lib/docker/network/files/local-kv.db
# Mount the EFS created in .ebextensions/media.config
04mount:
command: "/tmp/mount-efs.sh"
# On new instances, delay needs to be added because of 00task enact script. It tests for start/ but it can be various states of start...
# Basically, "start ecs" takes some time to run, and it runs async - so we sleep for some time.
# So basically let the ECS manager take it's time to boot before going on to enact scritps and post deploy scripts.
09restart:
command: "service docker start && sudo start ecs && sleep 120s"
挂载脚本和环境变量
.ebextensions/mount-config.config
# efs-mount.config
# Copy this file to the .ebextensions folder in the root of your app source folder
option_settings:
aws:elasticbeanstalk:application:environment:
EFS_REGION: '`{"Ref": "AWS::Region"}`'
# Replace with the required mount directory
EFS_MOUNT_DIR: '/efs_volume'
# Use in conjunction with efs_volume.config or replace with EFS volume ID of an existing EFS volume
EFS_VOLUME_ID: '`{"Ref" : "FileSystem"}`'
packages:
yum:
nfs-utils: []
files:
"/tmp/mount-efs.sh":
mode: "000755"
content : |
#!/bin/bash
EFS_REGION=$(/opt/elasticbeanstalk/bin/get-config environment | jq -r '.EFS_REGION')
EFS_MOUNT_DIR=$(/opt/elasticbeanstalk/bin/get-config environment | jq -r '.EFS_MOUNT_DIR')
EFS_VOLUME_ID=$(/opt/elasticbeanstalk/bin/get-config environment | jq -r '.EFS_VOLUME_ID')
echo "Mounting EFS filesystem ${EFS_DNS_NAME} to directory ${EFS_MOUNT_DIR} ..."
echo 'Stopping NFS ID Mapper...'
service rpcidmapd status &> /dev/null
if [ $? -ne 0 ] ; then
echo 'rpc.idmapd is already stopped!'
else
service rpcidmapd stop
if [ $? -ne 0 ] ; then
echo 'ERROR: Failed to stop NFS ID Mapper!'
exit 1
fi
fi
echo 'Checking if EFS mount directory exists...'
if [ ! -d ${EFS_MOUNT_DIR} ]; then
echo "Creating directory ${EFS_MOUNT_DIR} ..."
mkdir -p ${EFS_MOUNT_DIR}
if [ $? -ne 0 ]; then
echo 'ERROR: Directory creation failed!'
exit 1
fi
chmod 777 ${EFS_MOUNT_DIR}
if [ $? -ne 0 ]; then
echo 'ERROR: Permission update failed!'
exit 1
fi
else
echo "Directory ${EFS_MOUNT_DIR} already exists!"
fi
mountpoint -q ${EFS_MOUNT_DIR}
if [ $? -ne 0 ]; then
AZ=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)
echo "mount -t nfs4 -o nfsvers=4.1 ${AZ}.${EFS_VOLUME_ID}.efs.${EFS_REGION}.amazonaws.com:/ ${EFS_MOUNT_DIR}"
mount -t nfs4 -o nfsvers=4.1 ${AZ}.${EFS_VOLUME_ID}.efs.${EFS_REGION}.amazonaws.com:/ ${EFS_MOUNT_DIR}
if [ $? -ne 0 ] ; then
echo 'ERROR: Mount command failed!'
exit 1
fi
else
echo "Directory ${EFS_MOUNT_DIR} is already a valid mountpoint!"
fi
echo 'EFS mount complete.'
资源和配置
您必须更改下面的 option_settings。 要找到您必须在下面的 option_settings 下定义的 VPC 和子网,请查看 AWS Web控制台 -> VPC,您必须在那里找到默认 VPC id 和 3 个默认子网 id。如果你的 beantalk 使用自定义 VPC,你必须使用这些设置。
.ebextensions/efs-volume.config
:
# efs-volume.config
# Copy this file to the .ebextensions folder in the root of your app source folder
option_settings:
aws:elasticbeanstalk:customoption:
EFSVolumeName: "EB-EFS-Volume"
VPCId: "vpc-xxxxxxxx"
SubnetUSWest2a: "subnet-xxxxxxxx"
SubnetUSWest2b: "subnet-xxxxxxxx"
SubnetUSWest2c: "subnet-xxxxxxxx"
Resources:
FileSystem:
Type: AWS::EFS::FileSystem
Properties:
FileSystemTags:
- Key: Name
Value:
Fn::GetOptionSetting: {OptionName: EFSVolumeName, DefaultValue: "EB_EFS_Volume"}
MountTargetSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for mount target
SecurityGroupIngress:
- FromPort: '2049'
IpProtocol: tcp
SourceSecurityGroupId:
Fn::GetAtt: [AWSEBSecurityGroup, GroupId]
ToPort: '2049'
VpcId:
Fn::GetOptionSetting: {OptionName: VPCId}
MountTargetUSWest2a:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId: {Ref: FileSystem}
SecurityGroups:
- {Ref: MountTargetSecurityGroup}
SubnetId:
Fn::GetOptionSetting: {OptionName: SubnetUSWest2a}
MountTargetUSWest2b:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId: {Ref: FileSystem}
SecurityGroups:
- {Ref: MountTargetSecurityGroup}
SubnetId:
Fn::GetOptionSetting: {OptionName: SubnetUSWest2b}
MountTargetUSWest2c:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId: {Ref: FileSystem}
SecurityGroups:
- {Ref: MountTargetSecurityGroup}
SubnetId:
Fn::GetOptionSetting: {OptionName: SubnetUSWest2c}
资源:
AWS 有在 elastic beanstalk 上自动创建和安装 EFS 的说明。他们可以找到 here
这些说明 link 两个配置文件将被自定义并放置在部署包的 .ebextensions 文件夹中。
文件 storage-efs-mountfilesystem.config 需要进一步修改才能与 Docker 容器一起使用。添加以下命令:
02_restart:
command: "service docker restart"
并且对于 multi-container 环境 Elastic Container Service 也必须重新启动(上面 docker 重新启动时它被杀死):
03_start_eb:
command: |
start ecs
start eb-docker-events
sleep 120
test: sh -c "[ -f /etc/init/ecs.conf ]"
所以 storage-efs-mountfilesystem.config 的完整 commands 部分是:
commands:
01_mount:
command: "/tmp/mount-efs.sh"
02_restart:
command: "service docker restart"
03_start_eb:
command: |
start ecs
start eb-docker-events
sleep 120
test: sh -c "[ -f /etc/init/ecs.conf ]"
这不起作用的原因 "out-of-the-box" 是因为 docker 守护程序由 EC2 实例在 .ebextensions 中的命令 运行 之前启动。启动顺序为:
- 启动docker守护进程
- 在multi-containerdocker环境中启动弹性容器服务代理
- 运行 .ebextensions 中的命令
- 运行 个容器应用
第一步,docker 守护进程提供给容器的文件系统视图是固定的。因此,在步骤 3 中对主机文件系统所做的更改不会反映在容器的视图中。
一个奇怪的效果是容器在文件系统挂载到主机之前看到一个挂载点。主机看到挂载的文件系统。因此,容器写入的文件将写入挂载目录下的主机目录,而不是挂载的文件系统。卸载 EC2 主机上的文件系统将暴露写入挂载目录的容器文件。