EFS でアプリケーションサーバー間のファイルを共有する

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

はじめに

アップロード用アプリケーションが 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 でも同じアップロードを比較してください。

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 転送; クライアントとマウントターゲットのネットワークルール。