AWSS3を介した複数のサーバー間でのデータの共有
公開: 2022-03-10ユーザーがアップロードしたファイルを処理するための機能を提供する場合、ファイルは実行中ずっとプロセスで使用可能である必要があります。 単純なアップロードと保存の操作で問題は発生しません。 ただし、さらにファイルを保存する前に操作する必要があり、アプリケーションがロードバランサーの背後にある複数のサーバーで実行されている場合は、毎回プロセスを実行しているサーバーでファイルを使用できることを確認する必要があります。
たとえば、複数ステップの「ユーザーアバターのアップロード」機能では、ユーザーがステップ1でアバターをアップロードし、ステップ2でトリミングし、最後にステップ3で保存する必要がある場合があります。ファイルがステップでサーバーにアップロードされた後図1に示されるように、ファイルは、ステップ2および3の要求を処理するサーバーが利用可能でなければならず、これは、ステップ1の要求と同じである場合もそうでない場合もある。
単純なアプローチは、ステップ1でアップロードされたファイルを他のすべてのサーバーにコピーすることです。これにより、ファイルはすべてのサーバーで使用できるようになります。 ただし、このアプローチは非常に複雑であるだけでなく、実行不可能でもあります。たとえば、サイトが複数の地域の数百台のサーバーで実行されている場合、それを実現することはできません。
考えられる解決策は、ロードバランサーで「スティッキーセッション」を有効にすることです。これにより、特定のセッションに常に同じサーバーが割り当てられます。 次に、ステップ1、2、および3は同じサーバーによって処理され、ステップ1でこのサーバーにアップロードされたファイルはステップ2および3でも引き続き存在します。ただし、スティッキーセッションは完全には信頼できません。ステップ1の間にある場合2そのサーバーがクラッシュした場合、ロードバランサーは別のサーバーを割り当てる必要があり、機能とユーザーエクスペリエンスが中断されます。 同様に、セッションに常に同じサーバーを割り当てると、特別な状況下で、過負荷のサーバーからの応答時間が遅くなる可能性があります。
より適切な解決策は、すべてのサーバーがアクセスできるリポジトリにファイルのコピーを保持することです。 次に、ステップ1でファイルがサーバーにアップロードされた後、このサーバーはファイルをリポジトリにアップロードします(または、サーバーをバイパスして、ファイルをクライアントから直接リポジトリにアップロードすることもできます)。 サーバー処理ステップ2は、リポジトリからファイルをダウンロードして操作し、そこに再度アップロードします。 最後に、サーバー処理ステップ3は、リポジトリからダウンロードして保存します。
この記事では、AWS SDKを介して動作するAmazonWeb Services(AWS)Simple Storage Service(S3)(データを保存および取得するクラウドオブジェクトストレージソリューション)にファイルを保存するWordPressアプリケーションに基づくこの後者のソリューションについて説明します。
注1:アバターのトリミングなどの単純な機能の場合、別の解決策は、サーバーを完全にバイパスし、Lambda関数を介してクラウドに直接実装することです。 ただし、この記事はサーバー上で実行されているアプリケーションをAWS S3に接続することに関するものであるため、このソリューションは考慮していません。
注2: AWS S3(またはその他のAWSサービス)を使用するには、ユーザーアカウントが必要です。 アマゾンはここで1年間無料のティアを提供しています。これは彼らのサービスを試すのに十分です。
注3: WordPressからS3にファイルをアップロードするためのサードパーティのプラグインがあります。 そのようなプラグインの1つにWPMedia Offload(ライトバージョンはこちらから入手可能)があります。これは優れた機能を提供します。メディアライブラリにアップロードされたファイルをS3バケットにシームレスに転送し、サイトのコンテンツ( / wp-content / uploads)をアプリケーションコードから取得します。 コンテンツとコードを分離することで、Gitを使用してWordPressアプリケーションをデプロイし(そうでない場合、ユーザーがアップロードしたコンテンツはGitリポジトリでホストされないため、デプロイできません)、アプリケーションを複数のサーバーでホストできます(そうでない場合、各サーバーは維持する必要があります)ユーザーがアップロードしたすべてのコンテンツのコピー。)
バケットの作成
バケットを作成するときは、バケット名を考慮する必要があります。各バケット名はAWSネットワーク上でグローバルに一意である必要があるため、バケットを「アバター」のような単純なものと呼びたい場合でも、その名前はすでに使用されている可能性があります。 、次に、「avatars-name-of-my-company」のようなより特徴的なものを選択する場合があります。
また、バケットのベースとなるリージョンを選択する必要があります(このリージョンは、データセンターが配置されている物理的な場所であり、世界中に場所があります)。
プロセスの実行中にS3にすばやくアクセスできるように、リージョンはアプリケーションがデプロイされているリージョンと同じである必要があります。 そうしないと、ユーザーは離れた場所に画像をアップロード/ダウンロードしてからさらに数秒待たなければならない場合があります。
注: S3をクラウドオブジェクトストレージソリューションとして使用するのは、アプリケーションの実行にクラウド上の仮想サーバー用のAmazonのサービスであるEC2も使用する場合にのみ意味があります。 代わりに、Microsoft AzureやDigitalOceanなど、アプリケーションのホスティングを他の会社に依存している場合は、それらのクラウドオブジェクトストレージサービスも使用する必要があります。 そうしないと、さまざまな企業のネットワーク間を移動するデータによって、サイトにオーバーヘッドが発生します。
以下のスクリーンショットでは、トリミング用のユーザーアバターをアップロードするバケットを作成する方法を示しています。 まず、S3ダッシュボードに移動し、[バケットの作成]をクリックします。
次に、バケット名(この場合は「アバタースマッシング」)を入力し、地域(「EU(フランクフルト)」)を選択します。
バケット名とリージョンのみが必須です。 次の手順では、デフォルトのオプションを維持できるため、最後に[バケットの作成]をクリックするまで[次へ]をクリックします。これにより、バケットが作成されます。
ユーザー権限の設定
SDKを介してAWSに接続する場合、リクエストされたサービスとオブジェクトにアクセスできることを確認するために、ユーザークレデンシャル(アクセスキーIDとシークレットアクセスキーのペア)を入力する必要があります。 ユーザー権限は、非常に一般的(「管理者」ロールがすべてを実行できます)または非常にきめ細かく、必要な特定の操作に権限を付与するだけで、他には何もできません。
原則として、セキュリティの問題を回避するために、付与された権限が具体的であるほど優れています。 新しいユーザーを作成するときは、ポリシーを作成する必要があります。これは、ユーザーに付与する権限をリストした単純なJSONドキュメントです。 この場合、ユーザー権限により、バケット「アバタースマッシング」、「Put」(オブジェクトのアップロード用)、「Get」(オブジェクトのダウンロード用)、「List」(リスト)の操作のためのS3へのアクセスが許可されます。バケット内のすべてのオブジェクトを一覧表示するため)、次のポリシーになります。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:Put*", "s3:Get*", "s3:List*" ], "Resource": [ "arn:aws:s3:::avatars-smashing", "arn:aws:s3:::avatars-smashing/*" ] } ] }
以下のスクリーンショットでは、ユーザー権限を追加する方法を確認できます。 Identity and Access Management(IAM)ダッシュボードに移動する必要があります。
ダッシュボードで「ユーザー」をクリックし、その直後に「ユーザーの追加」をクリックします。 [ユーザーの追加]ページで、ユーザー名( "crop-avatars")を選択し、アクセスタイプとして[Programmatic access]にチェックマークを付けます。これにより、SDKを介して接続するためのアクセスキーIDとシークレットアクセスキーが提供されます。
次に、[次へ:アクセス許可]ボタンをクリックし、[既存のポリシーを直接添付する]をクリックして、[ポリシーの作成]をクリックします。 これにより、ブラウザに[ポリシーの作成]ページの新しいタブが開きます。 [JSON]タブをクリックし、上記で定義したポリシーのJSONコードを入力します。
次に、[ポリシーの確認]をクリックし、名前( "CropAvatars")を付けて、最後に[ポリシーの作成]をクリックします。 ポリシーを作成したら、前のタブに戻り、CropAvatarsポリシーを選択し(ポリシーのリストを更新して表示する必要がある場合があります)、[次へ:確認]をクリックし、最後に[ユーザーの作成]をクリックします。 これが完了すると、最終的にアクセスキーIDとシークレットアクセスキーをダウンロードできます(これらのクレデンシャルはこのユニークな瞬間に利用可能であることに注意してください。今すぐコピーまたはダウンロードしない場合は、新しいペアを作成する必要があります)::
SDKを介したAWSへの接続
SDKは、無数の言語で利用できます。 WordPressアプリケーションの場合、ここからダウンロードできるSDK for PHPが必要です。インストール方法については、こちらをご覧ください。
バケットを作成し、ユーザークレデンシャルを準備し、SDKをインストールしたら、S3へのファイルのアップロードを開始できます。
ファイルのアップロードとダウンロード
便宜上、ユーザーの資格情報と地域をwp-config.phpファイルの定数として定義します。
define ('AWS_ACCESS_KEY_ID', '...'); // Your access key id define ('AWS_SECRET_ACCESS_KEY', '...'); // Your secret access key define ('AWS_REGION', 'eu-central-1'); // Region where the bucket is located. This is the region id for "EU (Frankfurt)"
私たちの場合、アバターが「アバタースマッシング」バケットに保存されるクロップアバター機能を実装しています。 ただし、このアプリケーションでは、他の機能用に他のバケットがいくつかある場合があり、ファイルのアップロード、ダウンロード、リストの同じ操作を実行する必要があります。 したがって、抽象クラスAWS_S3
に共通メソッドを実装し、実装する子クラスで関数get_bucket
を介して定義されたバケット名などの入力を取得します。
// Load the SDK and import the AWS objects require 'vendor/autoload.php'; use Aws\S3\S3Client; use Aws\Exception\AwsException; // Definition of an abstract class abstract class AWS_S3 { protected function get_bucket() { // The bucket name will be implemented by the child class return ''; } }
S3Client
クラスは、S3と対話するためのAPIを公開します。 必要な場合にのみ(lazy-initializationを介して)インスタンス化し、同じインスタンスを引き続き使用するために、 $this->s3Client
の下に参照を保存します。
abstract class AWS_S3 { // Continued from above... protected $s3Client; protected function get_s3_client() { // Lazy initialization if (!$this->s3Client) { // Create an S3Client. Provide the credentials and region as defined through constants in wp-config.php $this->s3Client = new S3Client([ 'version' => '2006-03-01', 'region' => AWS_REGION, 'credentials' => [ 'key' => AWS_ACCESS_KEY_ID, 'secret' => AWS_SECRET_ACCESS_KEY, ], ]); } return $this->s3Client; } }
アプリケーションで$file
を処理する場合、この変数にはディスク内のファイルへの絶対パスが含まれます(例: /var/app/current/wp-content/uploads/users/654/leo.jpg
)が、アップロードする場合S3へのファイルは同じパスの下にオブジェクトを保存するべきではありません。 特に、セキュリティ上の理由から、システム情報( /var/app/current
)に関する最初のビットを削除する必要があります。オプションで/wp-content
ビットを削除できます(すべてのファイルがこのフォルダーに保存されるため、これは冗長な情報です) )、ファイルへの相対パスのみを保持します( /uploads/users/654/leo.jpg
)。 便利なことに、これはWP_CONTENT_DIR
の後のすべてを絶対パスから削除することで実現できます。 以下の関数get_file
およびget_file_relative_path
は、絶対ファイルパスと相対ファイルパスを切り替えます。
abstract class AWS_S3 { // Continued from above... function get_file_relative_path($file) { return substr($file, strlen(WP_CONTENT_DIR)); } function get_file($file_relative_path) { return WP_CONTENT_DIR.$file_relative_path; } }
オブジェクトをS3にアップロードするときに、アクセス制御リスト(ACL)のアクセス許可を介して、オブジェクトへのアクセスを許可されるユーザーとアクセスのタイプを確立できます。 最も一般的なオプションは、ファイルをプライベートに保ち(ACL =>「プライベート」)、インターネットで読み取るためにアクセスできるようにする(ACL =>「パブリック読み取り」)ことです。 ユーザーに表示するには、S3から直接ファイルをリクエストする必要があるため、ACL =>“ public-read”が必要です。
abstract class AWS_S3 { // Continued from above... protected function get_acl() { return 'public-read'; } }
最後に、S3バケットにオブジェクトをアップロードし、S3バケットからオブジェクトをダウンロードするメソッドを実装します。
abstract class AWS_S3 { // Continued from above... function upload($file) { $s3Client = $this->get_s3_client(); // Upload a file object to S3 $s3Client->putObject([ 'ACL' => $this->get_acl(), 'Bucket' => $this->get_bucket(), 'Key' => $this->get_file_relative_path($file), 'SourceFile' => $file, ]); } function download($file) { $s3Client = $this->get_s3_client(); // Download a file object from S3 $s3Client->getObject([ 'Bucket' => $this->get_bucket(), 'Key' => $this->get_file_relative_path($file), 'SaveAs' => $file, ]); } }
次に、実装する子クラスで、バケットの名前を定義します。
class AvatarCropper_AWS_S3 extends AWS_S3 { protected function get_bucket() { return 'avatars-smashing'; } }
最後に、クラスをインスタンス化して、アバターをS3にアップロードするか、S3からダウンロードします。 さらに、ステップ1から2および2から3に移行するときは、 $file
の値を伝達する必要があります。 これを行うには、POST操作を介して$file
の相対パスの値を含むフィールド「file_relative_path」を送信します(セキュリティ上の理由から絶対パスは渡しません。「/ var / www / current」を含める必要はありません。 ”部外者が見るための情報):
// Step 1: after the file was uploaded to the server, upload it to S3. Here, $file is known $avatarcropper = new AvatarCropper_AWS_S3(); $avatarcropper->upload($file); // Get the file path, and send it to the next step in the POST $file_relative_path = $avatarcropper->get_file_relative_path($file); // ... // -------------------------------------------------- // Step 2: get the $file from the request and download it, manipulate it, and upload it again $avatarcropper = new AvatarCropper_AWS_S3(); $file_relative_path = $_POST['file_relative_path']; $file = $avatarcropper->get_file($file_relative_path); $avatarcropper->download($file); // Do manipulation of the file // ... // Upload the file again to S3 $avatarcropper->upload($file); // -------------------------------------------------- // Step 3: get the $file from the request and download it, and then save it $avatarcropper = new AvatarCropper_AWS_S3(); $file_relative_path = $_REQUEST['file_relative_path']; $file = $avatarcropper->get_file($file_relative_path); $avatarcropper->download($file); // Save it, whatever that means // ...
S3から直接ファイルを表示する
手順2で操作した後のファイルの中間状態を表示する場合(たとえば、トリミング後のユーザーアバター)、S3から直接ファイルを参照する必要があります。 繰り返しになりますが、どのサーバーがそのリクエストを処理するかわからないため、URLはサーバー上のファイルを指すことができませんでした。
以下に、S3でそのファイルのURLを取得する関数get_file_url($file)
を追加します。 この機能を使用する場合は、アップロードされたファイルのACLが「公開読み取り」されていることを確認してください。そうでない場合、ユーザーはアクセスできません。
abstract class AWS_S3 { // Continue from above... protected function get_bucket_url() { $region = $this->get_region(); // North Virginia region is simply "s3", the others require the region explicitly $prefix = $region == 'us-east-1' ? 's3' : 's3-'.$region; // Use the same scheme as the current request $scheme = is_ssl() ? 'https' : 'http'; // Using the bucket name in path scheme return $scheme.'://'.$prefix.'.amazonaws.com/'.$this->get_bucket(); } function get_file_url($file) { return $this->get_bucket_url().$this->get_file_relative_path($file); } }
次に、S3でファイルのURLを取得し、画像を印刷するだけです。
printf( "<img src='%s'>", $avatarcropper->get_file_url($file) );
ファイルの一覧表示
私たちのアプリケーションで、ユーザーが以前にアップロードされたすべてのアバターを表示できるようにしたい場合は、そうすることができます。 そのために、特定のパス(S3用語ではプレフィックスと呼ばれます)に保存されているすべてのファイルのURLを一覧表示する関数get_file_urls
を導入します。
abstract class AWS_S3 { // Continue from above... function get_file_urls($prefix) { $s3Client = $this->get_s3_client(); $result = $s3Client->listObjects(array( 'Bucket' => $this->get_bucket(), 'Prefix' => $prefix )); $file_urls = array(); if(isset($result['Contents']) && count($result['Contents']) > 0 ) { foreach ($result['Contents'] as $obj) { // Check that Key is a full file path and not just a "directory" if ($obj['Key'] != $prefix) { $file_urls[] = $this->get_bucket_url().$obj['Key']; } } } return $file_urls; } }
次に、パス「/ users / $ {user_id} /」の下に各アバターを保存している場合、このプレフィックスを渡すことにより、すべてのファイルのリストを取得します。
$user_id = get_current_user_id(); $prefix = "/users/${user_id}/"; foreach ($avatarcropper->get_file_urls($prefix) as $file_url) { printf( "<img src='%s'>", $file_url ); }
結論
この記事では、クラウドオブジェクトストレージソリューションを使用して、複数のサーバーにデプロイされたアプリケーションのファイルを格納するための共通のリポジトリとして機能する方法について説明しました。 このソリューションでは、AWS S3に焦点を当て、アプリケーションに統合するために必要な手順(バケットの作成、ユーザー権限の設定、SDKのダウンロードとインストール)を示しました。 最後に、アプリケーションのセキュリティの落とし穴を回避する方法を説明し、S3で最も基本的な操作(ファイルのアップロード、ダウンロード、リスト)を実行する方法を示すコード例を確認しました。ファイルのアップロード、ダウンロード、リストの作成には、それぞれ数行のコードが必要です。 ソリューションのシンプルさは、クラウドサービスをアプリケーションに統合することは難しくなく、クラウドの経験があまりない開発者でも実現できることを示しています。