はじめに
2 つのアプリケーションに共有ストレージが必要ですが、それぞれ独自のディレクトリで動作させたいとします。EFS アクセス・ポイントで各マウントのルートと POSIX ID を設定し、実際のファイルと所有者の境界を確認します。
先に 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 接続のルールであり、ディレクトリやユーザー ID を選ぶものではありません。
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}'
アプリケーション専用アクセス・ポイントを作成する
このステップでは、各マウントのルートディレクトリとファイル操作 ID を定義します。
アクセス・ポイントはアプリケーション用の EFS 入口です。ルートディレクトリはマウント内で / となり、POSIX ユーザーがクライアントのファイル操作 ID に代わって使われます。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 は所有者と一致します。
異なるルートと ID を持つ 2 つ目を作成します。
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、ID は 2001:2001 と 2002:2002 です。次に実際の動作を試します。設定だけでは境界を証明できません。
ポイントが制約するのは、そのポイントを選んだマウントのルートと ID です。許可されたクライアントが別のポイントを選ぶことを、それだけでは防げません。本番では 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 を選びます。
実際のマウントとファイル ID を確認します。
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 で動きますが、サーバーはポイントの ID を適用します。
root が A のマウントから書いても同じ ID が使われます。
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 が数字の ID を適用するために、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 のファイルは存在し、B は読み取れます。独立したルートと異なる内容が分離を示します。マウントポイントから .. をたどるとローカル親ディレクトリに移動し、EFS の兄弟ディレクトリには移動しません。

同じ名前が異なるルートにあり、内容も異なります。識別子は環境ごとに変わります。
アクセス・ポイントと共有ストレージを削除する
このステップでは、マウントを解放し、自分のストレージだけを削除します。
アンマウントの前にローカルディレクトリに戻します。
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 内で、2 つのアクセス・ポイントに異なるルートと POSIX ID を設定しました。同じファイル名に異なるデータが入り、root を含むクライアントにサーバーが所有者を強制しました。その後アンマウントし、ポイント、接続先、一時ファイルシステムを削除しました。
ネットワーク到達性、マウントの選択、ディレクトリ権限は別の層です。次の実験ではそれぞれを確認して EFS 接続を診断します。
詳しくは、AWS 公式ドキュメントを参照してください:アクセスポイントのルート、ID、認可の範囲。



