はじめに
アップロード用アプリケーションが 2 台のサーバーで動いています。それぞれがローカルディスクに保存するため、一方でアップロードできても、もう一方ではファイルが見つかりません。Amazon Elastic File System(EFS)を使い、両方に共有ファイルへのアクセスを提供します。
先に AWS VPC for Beginners と AWS EC2 and EBS for Beginners を完了してください。この独立した環境にはアプリケーション、3 台のホスト、ネットワークが用意されています。Terminal で AWS CLI と SSH を実行し、AWS View で実際の応答を比較します。CLI は設定済みで、個人の AWS アカウントや前の実験のリソースは不要です。本実験はファイル共有と接続を扱い、本番の性能や可用性は評価しません。
認定試験との関連
| 認定 | 試験タスク | 実践 |
|---|---|---|
| Cloud Practitioner (CLF-C02) | Task 3.6 | EFS を共有ファイルサービスとして理解し、マウントされたファイルと別々のローカル保存先を区別する。 |

概念図です。実際の識別子は新しい環境で確認してください。
アップロードアプリケーションのストレージを作成する
このステップでは、ローカル保存の問題を観察し、共有ファイルシステムを作成します。
/home/labex/project で作業します。認証情報を表示せず、設定された ID を確認してください。
cd /home/labex/project
aws sts \
get-caller-identity
ユーザー ARN の末尾は labex-fs01-operator です。resources.json に用意されたサブネットとアプリケーション用セキュリティグループが記録されています。識別子を読み込み、現在の実際のリソースを使います。
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"
aws ec2 \
describe-instances \
--filters Name=tag:Name,Values=application-a,application-b \
--query 'Reservations[].Instances[].{Name:Tags[?Key==`Name`].Value|[0],IP:PrivateIpAddress,State:State.Name}'
まずローカル保存の問題を確認します。HTTP PUT はアップロードを保存し、HTTP GET はファイルを読み取ります。次のアドレスは用意されたサーバーに接続します。
curl -i -X PUT --data-binary 'A local upload' \
http://127.0.0.1:8081/uploads/local-example.txt
curl -i http://127.0.0.1:8082/uploads/local-example.txt
A は 201 Created、B は 404 Not Found を返します。B のローカルディレクトリには別のファイルがあるためです。AWS View の 2 枚のカードから同じファイル名を保存して読み取ることもできます。
EFS は共有ファイルシステムを提供します。両アプリケーションは Network File System(NFS)プロトコルで同じディレクトリとファイルにアクセスします。最初にファイルシステムを作成します。作成トークンはリクエストを識別し、Name タグは確認と削除時の識別に役立ちます。--encrypted はサービスの保存時暗号化設定を有効にします。通信の暗号化はマウント時に別途設定します。
FS_ID=$(aws efs \
create-file-system \
--creation-token fs01-shared-uploads \
--encrypted \
--tags Key=Name,Value=fs01-shared-uploads \
--query FileSystemId --output text)
echo "$FS_ID"
aws efs \
describe-file-systems \
--file-system-id "$FS_ID" \
--query 'FileSystems[].{ID:FileSystemId,State:LifeCycleState,Encrypted:Encrypted}'
available になるまで待ってください。リソースが存在しても、アプリケーションには到達可能なマウントターゲットと実際のマウントが必要です。
ファイルシステムをアプリケーションのサブネットに接続する
このステップでは、到達可能な NFS エンドポイントを作り、アクセスをアプリケーションサーバーに限定します。
マウントターゲットはサブネット内の接続先です。NFS は TCP 2049 を使います。専用セキュリティグループを作成し、用意されたアプリケーショングループからこのポートへの接続を許可します。送信元グループのルールは 2 台を含みますが、全アドレスには開放しません。無関係な参照サーバーは別グループに所属し、引き続き除外されます。
EFS_GROUP_ID=$(aws ec2 \
create-security-group \
--group-name fs01-efs-access \
--description 'NFS from the upload application servers' \
--vpc-id "$VPC_ID" \
--query GroupId --output text)
echo "$EFS_GROUP_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"
aws efs \
describe-mount-targets \
--file-system-id "$FS_ID" \
--query 'MountTargets[].{ID:MountTargetId,IP:IpAddress,State:LifeCycleState,AZ:AvailabilityZoneName}'
aws efs \
describe-mount-target-security-groups \
--mount-target-id "$MOUNT_TARGET_ID"
マウントターゲットが available になるまで待ちます。2 台は同じアベイラビリティゾーンにあるため、この構成ではターゲットは 1 個です。本番ではクライアントが存在する各ゾーンへの配置を計画してください。
両アプリケーションをマウントし共有ファイルを確認する
このステップでは、両アプリケーションを EFS に接続し、双方向のアップロードを試します。
ssh_config を使って各ホストに接続します。マウントポイントは、リモートのファイルシステムにアクセスするローカルディレクトリです。両ホストに /srv/shared を作成し、EFS のルートをマウントします。
ssh -F ssh_config application-a \
"sudo mkdir -p /srv/shared && sudo mount -t efs -o tls,stunnel,lookupcache=positive ${FS_ID}:/ /srv/shared"
ssh -F ssh_config application-b \
"sudo mkdir -p /srv/shared && sudo mount -t efs -o tls,stunnel,lookupcache=positive ${FS_ID}:/ /srv/shared"
lookupcache=positive を使うと、別のアプリケーションが新しいファイルを作成した際、以前存在しなかった名前をサーバーに再問い合わせします。この短い実験で新しいアップロードをすぐ確認できます。本番のキャッシュ調整では性能を別途評価してください。Linux NFS のディレクトリキャッシュ設定を参照できます。
-t efs は公式マウントヘルパーを選び、tls は通信を暗号化し、stunnel は対応する TLS 通信方式を選びます。${FS_ID}:/ はファイルシステムのルートです。必要なクライアントツールは準備済みです。
新しいディレクトリの所有者は最初 root です。アプリケーションの labex ユーザー用にアップロードディレクトリを作り、両アプリケーションを設定します。
ssh -F ssh_config application-a \
'sudo mkdir -p /srv/shared/uploads && sudo chown labex:labex /srv/shared/uploads'
ssh -F ssh_config application-a \
'printf "{\"upload_directory\":\"/srv/shared/uploads\"}\n" | sudo tee /etc/uploads-app/config.json'
ssh -F ssh_config application-b \
'printf "{\"upload_directory\":\"/srv/shared/uploads\"}\n" | sudo tee /etc/uploads-app/config.json'
アプリケーションはリクエストごとに設定を読むため、再起動は不要です。実際のマウントを確認します。
ssh -F ssh_config application-a 'findmnt -t nfs4 /srv/shared'
ssh -F ssh_config application-b 'findmnt -t nfs4 /srv/shared'
両方で /srv/shared に NFS 4 マウントが表示されます。TLS 使用時は、ヘルパーがローカルの暗号化トンネルで NFS を転送するため、カーネルの接続元表示が 127.0.0.1:/ になることがあります。
A からアップロードし、B から読み取ります。
curl -i -X PUT --data-binary 'Shared upload version 1' \
http://127.0.0.1:8081/uploads/shared.txt
curl -i http://127.0.0.1:8082/uploads/shared.txt
B は 200 OK と Shared upload version 1 を返します。B から更新して A から読み、逆方向も確認します。
curl -i -X PUT --data-binary 'Shared upload version 2' \
http://127.0.0.1:8082/uploads/shared.txt
curl -i http://127.0.0.1:8081/uploads/shared.txt
ssh -F ssh_config application-a 'stat -c "%i %u:%g" /srv/shared/uploads/shared.txt'
ssh -F ssh_config application-b 'stat -c "%i %u:%g" /srv/shared/uploads/shared.txt'
両アプリケーションがバージョン 2 を読み、stat は同じ inode を示します。独立したマウントが同じ EFS ファイルを参照しており、ローカルディレクトリをコピーまたは同期した結果ではありません。AWS View でも同じアップロードを比較してください。

両アプリケーションが同じ更新済みファイルを読みます。識別子は環境ごとに異なります。
アンマウントして自分のストレージを削除する
このステップでは、共有ディレクトリの使用を終え、自分が作成したストレージだけを削除します。
接続先を削除する前に、両アプリケーションを用意されたローカルディレクトリに戻し、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'
umount が使用中と表示した場合は、/srv/shared を使うシェルから移動して再試行します。マウントが消えたことを確認してください。
ssh -F ssh_config application-a 'findmnt -t nfs4'
ssh -F ssh_config application-b 'findmnt -t nfs4'
NFS マウントは残っていないはずです。対象がない findmnt は終了状態 1 を返します。ここでは想定どおりです。
最初にマウントターゲット、次にファイルシステム、最後に自分の専用セキュリティグループだけを削除します。
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==`fs01-shared-uploads`].FileSystemId'
aws ec2 \
describe-security-groups \
--filters Name=group-name,Values=fs01-efs-access \
--query 'SecurityGroups[].GroupId'
両問い合わせが空のリストを返します。削除の証拠になるのは成功した許可済み問い合わせです。通信や認証のエラーでは削除を証明できません。準備済みサーバー、そのグループ、サブネットは残します。
このステップのチェックを実行し、ストレージの削除と準備済みリソースの保持を確認します。以降の実験も、それぞれ独立した新しい環境で始まります。
まとめ
マウントターゲット、制限付き NFS アクセス、TLS マウントを使い、2 台のアプリケーションを同じ EFS に接続しました。双方向のアップロードと同じファイル識別情報で実際の共有を確認し、その後ローカル保存に戻してアンマウントし、依存順に自分のリソースを削除しました。
共有ファイルシステムだけではアプリケーションのディレクトリや POSIX ID は分離されません。次の実験ではアクセス・ポイントを使い、これらの境界を設定します。
詳しくは、AWS 公式ドキュメントを参照してください:EFS マウントヘルパーと TLS 転送; クライアントとマウントターゲットのネットワークルール。



