AWS S3를 통해 여러 서버 간에 데이터 공유

게시 됨: 2022-03-10
빠른 요약 ↬ 파일이 업로드되고 조작되는 다단계 형식을 만들 때 응용 프로그램이 로드 밸런서 뒤에 있는 여러 서버에서 실행 중인 경우 프로세스 실행 내내 파일을 사용할 수 있는지 확인해야 합니다. , 각 단계에서 프로세스를 처리하는 서버에 대해 이 기사에서는 AWS S3를 기반으로 파일을 업로드할 모든 서버에 액세스할 수 있는 리포지토리를 생성하여 이 문제를 해결합니다.

사용자가 업로드한 파일을 처리하기 위한 일부 기능을 제공할 때 해당 파일은 실행 전반에 걸쳐 프로세스에서 사용할 수 있어야 합니다. 간단한 업로드 및 저장 작업에는 문제가 없습니다. 그러나 추가로 파일을 저장하기 전에 조작해야 하고 애플리케이션이 로드 밸런서 뒤에 있는 여러 서버에서 실행 중인 경우 매번 프로세스를 실행 중인 서버에서 파일을 사용할 수 있는지 확인해야 합니다.

예를 들어 다단계 "사용자 아바타 업로드" 기능을 사용하려면 사용자가 1단계에서 아바타를 업로드하고 2단계에서 자르고 마지막으로 3단계에서 저장해야 할 수 있습니다. 파일이 1단계에서 서버에 업로드된 후 1에서 파일은 2단계와 3단계에 대한 요청을 처리하는 서버에서 사용할 수 있어야 합니다. 이 요청은 1단계와 같을 수도 있고 아닐 수도 있습니다.

순진한 접근 방식은 1단계에서 업로드된 파일을 다른 모든 서버에 복사하여 모든 서버에서 파일을 사용할 수 있도록 하는 것입니다. 그러나 이 접근 방식은 매우 복잡할 뿐만 아니라 실현 불가능합니다. 예를 들어 사이트가 여러 지역의 수백 대의 서버에서 실행되는 경우에는 달성할 수 없습니다.

가능한 솔루션은 로드 밸런서에서 "고정 세션"을 활성화하는 것입니다. 그러면 주어진 세션에 대해 항상 동일한 서버가 할당됩니다. 그런 다음 1, 2, 3단계는 동일한 서버에서 처리되고 1단계에서 이 서버에 업로드된 파일은 2단계와 3단계에서도 그대로 유지됩니다. 그러나 고정 세션은 완전히 신뢰할 수 없습니다. 1단계 사이에 있는 경우 2 해당 서버가 충돌하면 로드 밸런서가 다른 서버를 할당해야 하므로 기능과 사용자 경험이 중단됩니다. 마찬가지로 세션에 대해 항상 동일한 서버를 할당하면 특수한 상황에서 과부하된 서버의 응답 시간이 느려질 수 있습니다.

보다 적절한 솔루션은 모든 서버가 액세스할 수 있는 저장소에 파일 사본을 보관하는 것입니다. 그런 다음 1단계에서 파일을 서버에 업로드한 후 이 서버는 파일을 저장소에 업로드합니다(또는 서버를 우회하여 클라이언트에서 직접 파일을 저장소에 업로드할 수 있음). 서버 처리 단계 2는 저장소에서 파일을 다운로드하여 조작하고 다시 업로드합니다. 마지막으로 서버 처리 3단계는 저장소에서 다운로드하여 저장합니다.

점프 후 더! 아래에서 계속 읽기 ↓

이 기사에서는 AWS SDK를 통해 작동하는 Amazon Web Services(AWS) Simple Storage Service(S3)(데이터를 저장하고 검색하는 클라우드 객체 스토리지 솔루션)에 파일을 저장하는 WordPress 애플리케이션을 기반으로 하는 후자의 솔루션에 대해 설명합니다.

참고 1: 아바타 자르기와 같은 간단한 기능의 경우 또 다른 솔루션은 서버를 완전히 우회하고 Lambda 함수를 통해 클라우드에서 직접 구현하는 것입니다. 그러나 이 기사는 서버에서 실행되는 애플리케이션을 AWS S3와 연결하는 것에 관한 것이므로 이 솔루션은 고려하지 않습니다.

참고 2: AWS S3(또는 기타 AWS 서비스)를 사용하려면 사용자 계정이 있어야 합니다. Amazon은 여기에서 1년 동안 프리 티어를 제공하며, 이는 서비스를 실험하기에 충분합니다.

참고 3: WordPress에서 S3로 파일을 업로드하기 위한 타사 플러그인이 있습니다. 이러한 플러그인 중 하나는 WP Media Offload(라이트 버전은 여기에서 사용 가능)로 훌륭한 기능을 제공합니다. 미디어 라이브러리에 업로드된 파일을 S3 버킷으로 원활하게 전송하여 사이트 콘텐츠(예: 아래의 모든 항목)를 분리할 수 있습니다. /wp-content/uploads) 응용 프로그램 코드에서. 콘텐츠와 코드를 분리하여 Git을 사용하여 WordPress 애플리케이션을 배포할 수 있고(그렇지 않으면 사용자 업로드 콘텐츠가 Git 리포지토리에서 호스팅되지 않기 때문에 배포할 수 없음) 여러 서버에서 애플리케이션을 호스팅할 수 있습니다(그렇지 않으면 각 서버는 사용자가 업로드한 모든 콘텐츠의 사본.)

버킷 생성

버킷을 생성할 때 버킷 이름을 고려해야 합니다. 각 버킷 이름은 AWS 네트워크에서 전역적으로 고유해야 하므로 버킷을 "아바타"와 같은 간단한 이름으로 부르고 싶지만 해당 이름이 이미 사용 중일 수 있습니다. , 그런 다음 "avatars-name-of-my-company"와 같이 더 독특한 것을 선택할 수 있습니다.

또한 버킷이 기반으로 하는 지역을 선택해야 합니다(지역은 전 세계에 위치하는 데이터 센터가 있는 물리적 위치입니다).

지역은 애플리케이션이 배포된 지역과 같아야 프로세스 실행 중에 S3에 빠르게 액세스할 수 있습니다. 그렇지 않으면 사용자가 멀리 떨어진 위치에서 이미지를 업로드/다운로드하는 데 몇 초를 더 기다려야 할 수 있습니다.

참고: 애플리케이션을 실행하기 위해 클라우드의 가상 서버용 Amazon 서비스인 EC2를 사용하는 경우에만 S3를 클라우드 객체 스토리지 솔루션으로 사용하는 것이 합리적입니다. 대신 Microsoft Azure 또는 DigitalOcean과 같은 애플리케이션 호스팅을 위해 다른 회사에 의존하는 경우 해당 클라우드 개체 스토리지 서비스도 사용해야 합니다. 그렇지 않으면 우리 사이트는 다른 회사의 네트워크 사이를 이동하는 데이터로 인한 오버헤드를 겪게 됩니다.

아래 스크린샷에서 자르기 위해 사용자 아바타를 업로드할 버킷을 만드는 방법을 볼 수 있습니다. 먼저 S3 대시보드로 이동하여 "버킷 생성"을 클릭합니다.

S3 대시보드
모든 기존 버킷을 보여주는 S3 대시보드. (큰 미리보기)

그런 다음 버킷 이름(이 경우 "avatars-smashing")을 입력하고 지역("EU(프랑크푸르트)")을 선택합니다.

버킷 화면 생성
S3에서 버킷 생성. (큰 미리보기)

버킷 이름과 리전만 필수입니다. 다음 단계에서는 기본 옵션을 유지할 수 있으므로 마지막으로 "버킷 생성"을 클릭할 때까지 "다음"을 클릭하면 버킷이 생성됩니다.

사용자 권한 설정

SDK를 통해 AWS에 연결할 때 요청된 서비스 및 객체에 대한 액세스 권한이 있는지 확인하기 위해 사용자 자격 증명(액세스 키 ID 및 보안 액세스 키 쌍)을 입력해야 합니다. 사용자 권한은 매우 일반적이거나("관리자" 역할이 모든 작업을 수행할 수 있음) 매우 세분화되어 필요한 특정 작업에만 권한을 부여할 수 있습니다.

일반적으로 보안 문제를 피하기 위해 부여된 권한이 더 구체적일수록 더 좋습니다 . 새 사용자를 만들 때 사용자에게 부여할 권한을 나열하는 간단한 JSON 문서인 정책을 만들어야 합니다. 우리의 경우 사용자 권한은 버킷 "avatars-smashing", "Put"(객체 업로드), "Get"(객체 다운로드) 및 "List"( 버킷의 모든 객체를 나열하기 위해), 그 결과 다음 정책이 생성됩니다.

 { "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/*" ] } ] }

아래 스크린샷에서 사용자 권한을 추가하는 방법을 볼 수 있습니다. IAM(Identity and Access Management) 대시보드로 이동해야 합니다.

IAM 대시보드
생성한 모든 사용자를 나열하는 IAM 대시보드. (큰 미리보기)

대시보드에서 "사용자"를 클릭하고 바로 "사용자 추가"를 클릭합니다. 사용자 추가 페이지에서 사용자 이름("crop-avatars")을 선택하고 SDK를 통해 연결하기 위한 액세스 키 ID와 보안 액세스 키를 제공하는 액세스 유형으로 "프로그래밍 방식 액세스"를 선택합니다.

사용자 페이지 추가
새 사용자를 추가합니다. (큰 미리보기)

그런 다음 "다음: 권한" 버튼을 클릭하고 "기존 정책을 직접 연결"을 클릭한 다음 "정책 생성"을 클릭합니다. 그러면 브라우저에서 정책 만들기 페이지와 함께 새 탭이 열립니다. JSON 탭을 클릭하고 위에서 정의한 정책에 대한 JSON 코드를 입력합니다.

정책 생성 페이지
'avatars-smashing' 버킷에 대한 '가져오기', '게시' 및 '목록' 작업을 부여하는 정책 생성. (큰 미리보기)

그런 다음 정책 검토를 클릭하고 이름("CropAvatars")을 지정한 다음 마지막으로 정책 만들기를 클릭합니다. 정책이 생성되면 이전 탭으로 돌아가 CropAvatars 정책을 선택하고(정책 목록을 보려면 정책 목록을 새로 고쳐야 할 수 있음) 다음: 검토를 클릭하고 마지막으로 사용자 만들기를 클릭합니다. 이 작업이 완료되면 마침내 액세스 키 ID와 비밀 액세스 키를 다운로드할 수 있습니다(이 자격 증명은 이 특별한 순간에 사용할 수 있습니다. 지금 복사하거나 다운로드하지 않으면 새 쌍을 만들어야 합니다. ):

사용자 생성 성공 페이지
사용자가 생성되면 자격 증명을 다운로드할 수 있는 고유한 시간이 제공됩니다. (큰 미리보기)

SDK를 통해 AWS에 연결

SDK는 수많은 언어를 통해 사용할 수 있습니다. WordPress 애플리케이션의 경우 여기에서 다운로드할 수 있는 PHP용 SDK가 필요하며 설치 방법에 대한 지침은 여기에 있습니다.

버킷이 생성되고 사용자 자격 증명이 준비되고 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)"

우리의 경우 아바타가 "avatars-smashing" 버킷에 저장되는 자르기 아바타 기능을 구현하고 있습니다. 그러나 우리 애플리케이션에는 파일 업로드, 다운로드 및 나열과 같은 동일한 작업을 실행해야 하는 다른 기능을 위한 여러 다른 버킷이 있을 수 있습니다. 따라서 추상 클래스 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를 노출합니다. 필요할 때만 인스턴스화하고(지연 초기화를 통해) 동일한 인스턴스를 계속 사용하기 위해 $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_fileget_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에서 가장 기본적인 작업인 파일 업로드, 다운로드 및 나열을 수행하는 방법을 보여주는 코드 예제를 보았습니다. 이 작업은 각각 몇 줄의 코드가 거의 필요하지 않습니다. 솔루션의 단순성은 클라우드 서비스를 애플리케이션에 통합하는 것이 어렵지 않으며 클라우드 경험이 많지 않은 개발자도 수행할 수 있음을 보여줍니다.