使用访问点分隔应用目录

AWSBeginner
立即练习

介绍

两个应用需要共享文件存储,但各自应在自己的目录内工作。配置 EFS 访问点,为每个应用挂载选择特定根目录和 POSIX 身份,再观察实际文件与所有权边界。

请先完成使用 EFS 在应用服务器之间共享文件。本独立 VM 提供新的应用主机和子网,不复用之前实验的文件系统。在 Terminal 中使用配置好的 AWS CLI 和 SSH 连接,在 AWS View 中比较上传响应。无需个人 AWS 账号。

认证关联

认证 考纲任务 实践
Cloud Practitioner (CLF-C02) Task 3.6 探索 EFS 文件服务,观察应用目录和文件权限与对象存储的不同。

课程概览

概念示意图;实际标识符以你的新环境为准。

准备共享文件系统和 NFS 端点

本步骤创建两个应用使用的存储和受限网络端点。

在 /home/labex/project 中识别准备好的网络。AWS CLI 已为本实验配置:

cd /home/labex/project
aws sts \
  get-caller-identity
SUBNET_ID=$(jq -r .subnet_id resources.json)
APP_GROUP_ID=$(jq -r .application_group_id resources.json)
VPC_ID=$(jq -r .vpc_id resources.json)
echo "$SUBNET_ID"
echo "$APP_GROUP_ID"
echo "$VPC_ID"

创建新的 EFS 文件系统。两个应用使用同一资源,而各自的访问点选择其中不同的目录:

FS_ID=$(aws efs \
  create-file-system \
  --creation-token fs02-application-files \
  --encrypted \
  --tags Key=Name,Value=fs02-application-files \
  --query FileSystemId --output text)
echo "$FS_ID"

创建专用存储安全组:

EFS_GROUP_ID=$(aws ec2 \
  create-security-group \
  --group-name fs02-efs-access \
  --description 'NFS for the upload application servers' \
  --vpc-id "$VPC_ID" \
  --query GroupId --output text)
echo "$EFS_GROUP_ID"

允许应用安全组访问 TCP 2049。这条网络规则允许 NFS 连接,但不会决定应用的文件系统目录或用户身份:

aws ec2 \
  authorize-security-group-ingress \
  --group-id "$EFS_GROUP_ID" \
  --ip-permissions "IpProtocol=tcp,FromPort=2049,ToPort=2049,UserIdGroupPairs=[{GroupId=$APP_GROUP_ID}]"

在准备好的子网创建挂载目标:

MOUNT_TARGET_ID=$(aws efs \
  create-mount-target \
  --file-system-id "$FS_ID" \
  --subnet-id "$SUBNET_ID" \
  --security-groups "$EFS_GROUP_ID" \
  --query MountTargetId --output text)
echo "$MOUNT_TARGET_ID"

查看资源状态,等待两者都为 available:

aws efs \
  describe-file-systems \
  --file-system-id "$FS_ID" \
  --query 'FileSystems[].{ID:FileSystemId,State:LifeCycleState}'
aws efs \
  describe-mount-targets \
  --file-system-id "$FS_ID" \
  --query 'MountTargets[].{ID:MountTargetId,IP:IpAddress,State:LifeCycleState}'

创建应用专用访问点

本步骤定义每个应用挂载的根目录和文件身份。

访问点是 EFS 文件系统的应用专用入口。它的根目录成为该挂载内的 /,其 POSIX 用户替代客户端文件操作身份。POSIX 用数字 UID 标识用户,用数字 GID 标识组。

创建应用 A 的访问点:

AP_A_ID=$(aws efs \
  create-access-point \
  --file-system-id "$FS_ID" \
  --posix-user Uid=2001,Gid=2001 \
  --root-directory 'Path=/app-a,CreationInfo={OwnerUid=2001,OwnerGid=2001,Permissions=700}' \
  --tags Key=Name,Value=fs02-app-a \
  --query AccessPointId --output text)
echo "$AP_A_ID"

如果 /app-a 不存在,CreationInfo 提供所有者和权限。700 允许目录所有者读取、写入和遍历,不给其他用户这些权限。访问点的 UID/GID 与目录所有者一致。

创建另一个具有不同根目录和身份的访问点:

AP_B_ID=$(aws efs \
  create-access-point \
  --file-system-id "$FS_ID" \
  --posix-user Uid=2002,Gid=2002 \
  --root-directory 'Path=/app-b,CreationInfo={OwnerUid=2002,OwnerGid=2002,Permissions=700}' \
  --tags Key=Name,Value=fs02-app-b \
  --query AccessPointId --output text)
echo "$AP_B_ID"

查看两个定义:

aws efs \
  describe-access-points \
  --file-system-id "$FS_ID" \
  --query 'AccessPoints[].{ID:AccessPointId,Root:RootDirectory.Path,User:PosixUser,Creation:RootDirectory.CreationInfo}'

根目录分别为 /app-a 和 /app-b,身份分别是 2001:2001 和 2002:2002。下一步测试实际行为,仅配置相符还不能证明边界。

访问点约束的是选择该访问点的挂载的根目录和身份。它本身不会阻止已获授权的客户端选择其他访问点。生产设计可增加 IAM 授权和文件系统策略,控制客户端可以挂载哪些访问点;本实验聚焦目录和 POSIX 行为。

观察独立目录和强制所有权

本步骤挂载两个访问点,证明相同文件名对应不同应用文件。

在应用 A 上挂载 A 的访问点。访问点挂载需要 TLS:

ssh -F ssh_config application-a \
  "sudo mkdir -p /srv/shared && sudo mount -t efs -o tls,stunnel,lookupcache=positive,accesspoint=${AP_A_ID} ${FS_ID}:/ /srv/shared"

挂载使用 lookupcache=positive:当另一台应用创建之前不存在的文件时,再次向服务器查询该文件名,让短时间内的上传结果可见。生产工作负载的缓存调整还需要单独评估性能。参见 Linux NFS 目录缓存选项。

在应用 B 上挂载 B 的访问点:

ssh -F ssh_config application-b \
  "sudo mkdir -p /srv/shared && sudo mount -t efs -o tls,stunnel,lookupcache=positive,accesspoint=${AP_B_ID} ${FS_ID}:/ /srv/shared"

两台主机都使用本地路径 /srv/shared,但它在 A 上代表 /app-a,在 B 上代表 /app-b。让两个上传应用直接使用各自访问点的根目录:

ssh -F ssh_config application-a \
  'printf "{\"upload_directory\":\"/srv/shared\"}\n" | sudo tee /etc/uploads-app/config.json'
ssh -F ssh_config application-b \
  'printf "{\"upload_directory\":\"/srv/shared\"}\n" | sudo tee /etc/uploads-app/config.json'

通过 A 写入 private.txt,再尝试通过 B 读取:

curl -i -X PUT --data-binary 'Private upload for application A' \
  http://127.0.0.1:8081/uploads/private.txt
curl -i http://127.0.0.1:8082/uploads/private.txt

A 返回 201 Created,B 返回 404 Not Found。现在用同一文件名创建 B 自己的文件:

curl -i -X PUT --data-binary 'Private upload for application B' \
  http://127.0.0.1:8082/uploads/private.txt
curl -i http://127.0.0.1:8081/uploads/private.txt
curl -i http://127.0.0.1:8082/uploads/private.txt

A 仍返回 A 的内容,B 返回 B 的内容。在 AWS View 使用文件名 private.txt,点击两个卡片的 Read upload 比较结果。

查看实际挂载和文件身份:

ssh -F ssh_config application-a 'findmnt -t nfs4 /srv/shared; stat -c "%i %u:%g" /srv/shared/private.txt'
ssh -F ssh_config application-b 'findmnt -t nfs4 /srv/shared; stat -c "%i %u:%g" /srv/shared/private.txt'

两个文件的 inode 不同,所有者分别为 2001:2001 和 2002:2002。应用进程以 labex 运行,但服务器应用访问点指定的身份。

即使 root 客户端通过 A 的挂载写入,也使用选定身份:

ssh -F ssh_config application-a \
  'printf "Access-point identity also applies to root callers\n" | sudo tee /srv/shared/root-caller.txt; stat -c "%u:%g" /srv/shared/root-caller.txt'

所有者是 2001:2001。EFS 使用这些数字身份,并不要求本地存在名为 2001、2002 的用户账号。

A 的挂载只暴露 /app-a 的内容,不是整个 EFS 根目录。B 的同级目录不是 A 挂载内的子目录:

ssh -F ssh_config application-a 'ls -a /srv/shared'
ssh -F ssh_config application-a 'cat /srv/shared/app-b/private.txt'

最后一条命令提示 No such file or directory,尽管 B 的实际文件存在且应用可以读取。独立挂载的两个根目录和不同文件内容证明了目录分隔。从本地挂载点访问 .. 会到达本地父目录,不能用它查看 EFS 同级目录。

AWS View application uploads

同一文件名位于各自访问点目录,内容不同。标识符以每次新环境为准。

删除访问点和共享存储

本步骤释放挂载,仅删除你的应用存储资源。

卸载前将两个应用恢复为本地目录:

ssh -F ssh_config application-a \
  'printf "{\"upload_directory\":\"/srv/uploads\"}\n" | sudo tee /etc/uploads-app/config.json'
ssh -F ssh_config application-b \
  'printf "{\"upload_directory\":\"/srv/uploads\"}\n" | sudo tee /etc/uploads-app/config.json'
ssh -F ssh_config application-a 'sudo umount /srv/shared'
ssh -F ssh_config application-b 'sudo umount /srv/shared'

删除两个访问点,并确认文件系统没有访问点:

aws efs \
  delete-access-point \
  --access-point-id "$AP_A_ID"
aws efs \
  delete-access-point \
  --access-point-id "$AP_B_ID"
aws efs \
  describe-access-points \
  --file-system-id "$FS_ID"

AccessPoints 应为空。删除访问点仅删除入口定义,不删除目录中的文件。接下来删除整个临时文件系统及其数据:

aws efs \
  delete-mount-target \
  --mount-target-id "$MOUNT_TARGET_ID"
aws efs \
  describe-mount-targets \
  --file-system-id "$FS_ID"

等待 MountTargets 为空再继续:

aws efs \
  delete-file-system \
  --file-system-id "$FS_ID"
aws ec2 \
  delete-security-group \
  --group-id "$EFS_GROUP_ID"

通过成功的服务响应确认具名资源列表为空:

aws efs \
  describe-file-systems \
  --query 'FileSystems[?Name==`fs02-application-files`].FileSystemId'
aws ec2 \
  describe-security-groups \
  --filters Name=group-name,Values=fs02-efs-access \
  --query 'SecurityGroups[].GroupId'

两个查询都返回空列表。保留准备好的应用服务器、安全组和子网,然后运行本步骤检查。

总结

你用两个访问点,在同一 EFS 文件系统中选择不同根目录和 POSIX 身份。同名文件保存了不同的真实应用数据,服务器强制所有权对普通用户和 root 调用者都生效。随后卸载客户端,删除临时访问点、网络端点和文件系统。

网络可达性、挂载选择和目录权限是不同层次。下一个引导实验将结合这些层次诊断 EFS 挂载连接。

进一步阅读 AWS 官方资料:Access point 根目录、身份与授权范围。