アクセス・ポイントでアプリケーションのディレクトリを分離する

AWSBeginner
オンラインで実践に進む

はじめに

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 の兄弟ディレクトリには移動しません。

AWS View の独立ディレクトリ

同じ名前が異なるルートにあり、内容も異なります。識別子は環境ごとに変わります。

アクセス・ポイントと共有ストレージを削除する

このステップでは、マウントを解放し、自分のストレージだけを削除します。

アンマウントの前にローカルディレクトリに戻します。

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、認可の範囲。