Berbagi Data Di Antara Beberapa Server Melalui AWS S3

Diterbitkan: 2022-03-10
Ringkasan cepat Saat membuat formulir multi-langkah di mana file diunggah dan dimanipulasi, jika aplikasi berjalan di beberapa server di belakang penyeimbang beban, maka kita perlu memastikan bahwa file tersedia di seluruh proses eksekusi , untuk server mana pun yang menangani proses di setiap langkah. Dalam artikel ini, kami akan memecahkan masalah ini dengan membuat repositori yang dapat diakses oleh semua server tempat mengunggah file, berdasarkan AWS S3.

Saat menyediakan beberapa fungsionalitas untuk memproses file yang diunggah oleh pengguna, file tersebut harus tersedia untuk proses selama eksekusi. Operasi unggah dan simpan yang sederhana tidak menimbulkan masalah. Namun, jika selain itu file harus dimanipulasi sebelum disimpan, dan aplikasi berjalan di beberapa server di belakang penyeimbang beban, maka kita perlu memastikan bahwa file tersebut tersedia untuk server mana pun yang menjalankan proses setiap saat.

Misalnya, fungsi "Unggah avatar pengguna Anda" multi-langkah mungkin mengharuskan pengguna untuk mengunggah avatar pada langkah 1, memotongnya pada langkah 2, dan akhirnya menyimpannya pada langkah 3. Setelah file diunggah ke server pada langkah 1, file harus tersedia untuk server mana pun yang menangani permintaan untuk langkah 2 dan 3, yang mungkin sama atau tidak sama untuk langkah 1.

Pendekatan naif adalah menyalin file yang diunggah pada langkah 1 ke semua server lain, sehingga file akan tersedia di semuanya. Namun, pendekatan ini tidak hanya sangat kompleks tetapi juga tidak layak: misalnya, jika situs berjalan di ratusan server, dari beberapa wilayah, maka itu tidak dapat dilakukan.

Solusi yang mungkin adalah mengaktifkan "sesi lengket" pada penyeimbang beban, yang akan selalu menetapkan server yang sama untuk sesi tertentu. Kemudian, langkah 1, 2 dan 3 akan ditangani oleh server yang sama, dan file yang diunggah ke server ini pada langkah 1 akan tetap ada untuk langkah 2 dan 3. Namun, sesi lengket tidak sepenuhnya dapat diandalkan: Jika di antara langkah 1 dan 2 server itu mogok, maka penyeimbang beban harus menetapkan server yang berbeda, mengganggu fungsionalitas dan pengalaman pengguna. Demikian juga, selalu menugaskan server yang sama untuk suatu sesi dapat, dalam keadaan khusus, menyebabkan waktu respons yang lebih lambat dari server yang terlalu terbebani.

Solusi yang lebih tepat adalah menyimpan salinan file di repositori yang dapat diakses oleh semua server. Kemudian, setelah file diunggah ke server pada langkah 1, server ini akan mengunggahnya ke repositori (atau, sebagai alternatif, file dapat diunggah ke repositori langsung dari klien, melewati server); server yang menangani langkah 2 akan mengunduh file dari repositori, memanipulasinya, dan mengunggahnya ke sana lagi; dan akhirnya server yang menangani langkah 3 akan mengunduhnya dari repositori dan menyimpannya.

Lebih banyak setelah melompat! Lanjutkan membaca di bawah ini

Dalam artikel ini, saya akan menjelaskan solusi terakhir ini, berdasarkan aplikasi WordPress yang menyimpan file di Amazon Web Services (AWS) Simple Storage Service (S3) (solusi penyimpanan objek cloud untuk menyimpan dan mengambil data), yang beroperasi melalui AWS SDK.

Catatan 1: Untuk fungsionalitas sederhana seperti memotong avatar, solusi lain adalah mengabaikan server sepenuhnya, dan mengimplementasikannya langsung di cloud melalui fungsi Lambda. Tetapi karena artikel ini tentang menghubungkan aplikasi yang berjalan di server dengan AWS S3, kami tidak mempertimbangkan solusi ini.

Catatan 2: Untuk menggunakan AWS S3 (atau layanan AWS lainnya) kami harus memiliki akun pengguna. Amazon menawarkan tingkat gratis di sini selama 1 tahun, yang cukup baik untuk bereksperimen dengan layanan mereka.

Catatan 3: Ada plugin pihak ke-3 untuk mengunggah file dari WordPress ke S3. Salah satu plugin tersebut adalah WP Media Offload (versi lite tersedia di sini), yang menyediakan fitur hebat: plugin ini secara mulus mentransfer file yang diunggah ke Perpustakaan Media ke ember S3, yang memungkinkan untuk memisahkan konten situs (seperti semua yang ada di bawah /wp-content/uploads) dari kode aplikasi. Dengan memisahkan konten dan kode, kami dapat menerapkan aplikasi WordPress kami menggunakan Git (jika tidak, kami tidak dapat melakukannya karena konten yang diunggah pengguna tidak dihosting di repositori Git), dan menghosting aplikasi di beberapa server (jika tidak, setiap server perlu menyimpan salinan semua konten yang diunggah pengguna.)

Membuat Bucket

Saat membuat bucket, kita perlu mempertimbangkan nama bucket: Setiap nama bucket harus unik secara global di jaringan AWS, jadi meskipun kita ingin menyebut bucket kita sebagai sesuatu yang sederhana seperti “avatar”, nama itu mungkin sudah digunakan , maka kita dapat memilih sesuatu yang lebih khas seperti "avatar-nama-perusahaan-saya".

Kita juga perlu memilih wilayah di mana ember berada (wilayah adalah lokasi fisik di mana pusat data berada, dengan lokasi di seluruh dunia.)

Region harus sama dengan tempat aplikasi kita di-deploy, agar akses S3 saat proses eksekusi berlangsung cepat. Jika tidak, pengguna mungkin harus menunggu beberapa detik lagi untuk mengunggah/mengunduh gambar ke/dari lokasi yang jauh.

Catatan: Masuk akal untuk menggunakan S3 sebagai solusi penyimpanan objek cloud hanya jika kami juga menggunakan layanan Amazon untuk server virtual di cloud, EC2, untuk menjalankan aplikasi. Jika sebaliknya, kami mengandalkan beberapa perusahaan lain untuk menghosting aplikasi, seperti Microsoft Azure atau DigitalOcean, maka kami juga harus menggunakan layanan penyimpanan objek cloud mereka. Jika tidak, situs kami akan mengalami overhead dari perjalanan data di antara jaringan perusahaan yang berbeda.

Pada tangkapan layar di bawah ini kita akan melihat cara membuat ember tempat mengunggah avatar pengguna untuk dipotong. Pertama-tama kita menuju ke dasbor S3 dan klik "Buat ember":

Dasbor S3
Dasbor S3, menampilkan semua bucket kami yang ada. (Pratinjau besar)

Kemudian kita ketik nama ember (dalam hal ini, "avatars-smashing") dan pilih wilayah ("EU (Frankfurt)"):

Buat layar ember
Membuat ember melalui di S3. (Pratinjau besar)

Hanya nama dan wilayah bucket yang wajib. Untuk langkah-langkah berikut kita dapat mempertahankan opsi default, jadi kita klik "Next" sampai akhirnya mengklik "Buat ember", dan dengan itu, kita akan membuat ember.

Menyiapkan Izin Pengguna

Saat menghubungkan ke AWS melalui SDK, kami akan diminta untuk memasukkan kredensial pengguna kami (sepasang ID kunci akses dan kunci akses rahasia), untuk memvalidasi bahwa kami memiliki akses ke layanan dan objek yang diminta. Izin pengguna bisa sangat umum (peran "admin" dapat melakukan segalanya) atau sangat terperinci, hanya memberikan izin untuk operasi tertentu yang diperlukan dan tidak ada yang lain.

Sebagai aturan umum, semakin spesifik izin yang kami berikan, semakin baik, untuk menghindari masalah keamanan . Saat membuat pengguna baru, kita perlu membuat kebijakan, yang merupakan dokumen JSON sederhana yang mencantumkan izin yang akan diberikan kepada pengguna. Dalam kasus kami, izin pengguna kami akan memberikan akses ke S3, untuk bucket "avatars-smashing", untuk operasi "Put" (untuk mengunggah objek), "Dapatkan" (untuk mengunduh objek), dan "Daftar" ( untuk mendaftar semua objek di ember), menghasilkan kebijakan berikut:

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

Pada tangkapan layar di bawah, kita dapat melihat cara menambahkan izin pengguna. Kita harus pergi ke dasbor Identity and Access Management (IAM):

Dasbor IAM
Dasbor IAM, mencantumkan semua pengguna yang telah kami buat. (Pratinjau besar)

Di dasbor, kami mengklik "Pengguna" dan segera setelah "Tambah Pengguna". Di halaman Tambah Pengguna, kami memilih nama pengguna (“crop-avatars”), dan mencentang “Programmatic access” sebagai jenis Access, yang akan memberikan ID kunci akses dan kunci akses rahasia untuk menghubungkan melalui SDK:

Tambahkan halaman pengguna
Menambahkan pengguna baru. (Pratinjau besar)

Kami kemudian mengklik tombol "Berikutnya: Izin", klik "Lampirkan kebijakan yang ada secara langsung", dan klik "Buat kebijakan". Ini akan membuka tab baru di browser, dengan halaman Buat kebijakan. Kami mengklik tab JSON, dan memasukkan kode JSON untuk kebijakan yang ditentukan di atas:

Buat halaman kebijakan
Membuat kebijakan yang memberikan operasi 'Dapatkan', 'Poskan', dan 'Daftar' di keranjang 'penghancur avatar'. (Pratinjau besar)

Kami kemudian mengklik Kebijakan Tinjau, beri nama ("PangkasAvatar"), dan terakhir klik Buat kebijakan. Setelah kebijakan dibuat, kami beralih kembali ke tab sebelumnya, pilih kebijakan CropAvatars (kami mungkin perlu menyegarkan daftar kebijakan untuk melihatnya), klik Berikutnya: Tinjau, dan terakhir Buat pengguna. Setelah ini selesai, kami akhirnya dapat mengunduh ID kunci akses dan kunci akses rahasia (harap perhatikan bahwa kredensial ini tersedia untuk momen unik ini; jika kami tidak menyalin atau mengunduhnya sekarang, kami harus membuat pasangan baru ):

Halaman sukses pembuatan pengguna
Setelah pengguna dibuat, kami ditawari waktu khusus untuk mengunduh kredensial. (Pratinjau besar)

Menghubungkan Ke AWS Melalui SDK

SDK tersedia melalui berbagai bahasa. Untuk aplikasi WordPress, kami memerlukan SDK untuk PHP yang dapat diunduh dari sini, dan petunjuk tentang cara menginstalnya ada di sini.

Setelah bucket dibuat, kredensial pengguna siap, dan SDK diinstal, kita dapat mulai mengunggah file ke S3.

Mengunggah Dan Mengunduh File

Untuk kenyamanan, kami mendefinisikan kredensial pengguna dan wilayah sebagai konstanta dalam file 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)"

Dalam kasus kami, kami menerapkan fungsionalitas crop avatar, yang avatarnya akan disimpan di bucket "avatars-smashing". Namun, dalam aplikasi kami, kami mungkin memiliki beberapa ember lain untuk fungsi lain, yang memerlukan operasi yang sama untuk mengunggah, mengunduh, dan membuat daftar file. Oleh karena itu, kami menerapkan metode umum pada kelas abstrak AWS_S3 , dan kami memperoleh input, seperti nama bucket yang ditentukan melalui fungsi get_bucket , di kelas anak yang mengimplementasikan.

 // 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 ''; } }

Kelas S3Client mengekspos API untuk berinteraksi dengan S3. Kami membuat instance hanya jika diperlukan (melalui inisialisasi malas), dan menyimpan referensinya di bawah $this->s3Client agar tetap menggunakan instance yang sama:

 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; } }

Saat kita berurusan dengan $file di aplikasi kita, variabel ini berisi path absolut ke file di disk (mis /var/app/current/wp-content/uploads/users/654/leo.jpg ), tetapi saat mengunggah file ke S3 kita tidak boleh menyimpan objek di bawah jalur yang sama. Secara khusus, kita harus menghapus bit awal mengenai informasi sistem ( /var/app/current ) untuk alasan keamanan, dan secara opsional kita dapat menghapus bit /wp-content (karena semua file disimpan di bawah folder ini, ini adalah informasi yang berlebihan ), hanya menyimpan jalur relatif ke file ( /uploads/users/654/leo.jpg ). Mudah, ini dapat dicapai dengan menghapus semuanya setelah WP_CONTENT_DIR dari jalur absolut. Fungsi get_file dan get_file_relative_path di bawah ini beralih antara jalur file absolut dan relatif:

 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; } }

Saat mengunggah objek ke S3, kami dapat menetapkan siapa yang diberikan akses ke objek dan jenis akses, yang dilakukan melalui izin daftar kontrol akses (ACL). Opsi yang paling umum adalah menjaga agar file tetap pribadi (ACL => “private”) dan membuatnya dapat diakses untuk dibaca di internet (ACL => “public-read”). Karena kita perlu meminta file langsung dari S3 untuk menunjukkannya kepada pengguna, kita memerlukan ACL => “public-read”:

 abstract class AWS_S3 { // Continued from above... protected function get_acl() { return 'public-read'; } }

Terakhir, kami menerapkan metode untuk mengunggah objek ke, dan mengunduh objek dari, ember 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, ]); } }

Kemudian, di kelas anak implementasi, kami mendefinisikan nama ember:

 class AvatarCropper_AWS_S3 extends AWS_S3 { protected function get_bucket() { return 'avatars-smashing'; } }

Terakhir, kita cukup membuat instance kelas untuk mengunggah avatar ke, atau mengunduh dari, S3. Selain itu, saat transisi dari langkah 1 ke 2 dan 2 ke 3, kita perlu mengomunikasikan nilai $file . Kita dapat melakukan ini dengan mengirimkan bidang "file_relative_path" dengan nilai jalur relatif $file melalui operasi POST (kami tidak melewati jalur absolut karena alasan keamanan: tidak perlu menyertakan "/var/www/current ” informasi untuk dilihat orang luar):

 // 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 // ...

Menampilkan File Langsung Dari S3

Jika kita ingin menampilkan status perantara file setelah manipulasi pada langkah 2 (misalnya avatar pengguna setelah dipotong), maka kita harus merujuk file langsung dari S3; URL tidak dapat menunjuk ke file di server karena, sekali lagi, kami tidak tahu server mana yang akan menangani permintaan itu.

Di bawah ini, kami menambahkan fungsi get_file_url($file) yang memperoleh URL untuk file itu di S3. Jika menggunakan fungsi ini, harap pastikan bahwa ACL dari file yang diunggah "dibaca oleh publik", atau jika tidak, tidak akan dapat diakses oleh pengguna.

 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); } }

Kemudian, kita cukup mendapatkan URL file di S3 dan mencetak gambar:

 printf( "<img src='%s'>", $avatarcropper->get_file_url($file) );

Daftar File

Jika dalam aplikasi kami, kami ingin mengizinkan pengguna untuk melihat semua avatar yang diunggah sebelumnya, kami dapat melakukannya. Untuk itu, kami memperkenalkan fungsi get_file_urls yang mencantumkan URL untuk semua file yang disimpan di bawah jalur tertentu (dalam istilah S3, ini disebut awalan):

 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; } }

Kemudian, jika kita menyimpan setiap avatar di bawah path “/users/${user_id}/“, dengan melewati awalan ini kita akan mendapatkan daftar semua file:

 $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 ); }

Kesimpulan

Dalam artikel ini, kami menjelajahi cara menggunakan solusi penyimpanan objek cloud untuk bertindak sebagai repositori umum untuk menyimpan file untuk aplikasi yang digunakan di beberapa server. Untuk solusinya, kami berfokus pada AWS S3, dan melanjutkan untuk menunjukkan langkah-langkah yang diperlukan untuk diintegrasikan ke dalam aplikasi: membuat bucket, menyiapkan izin pengguna, serta mengunduh dan menginstal SDK. Terakhir, kami menjelaskan cara menghindari jebakan keamanan dalam aplikasi, dan melihat contoh kode yang menunjukkan cara melakukan operasi paling dasar pada S3: mengunggah, mengunduh, dan membuat daftar file, yang masing-masing hampir tidak memerlukan beberapa baris kode. Kesederhanaan solusi menunjukkan bahwa mengintegrasikan layanan cloud ke dalam aplikasi tidak sulit, dan juga dapat dilakukan oleh pengembang yang tidak terlalu berpengalaman dengan cloud.