انتقال فایلهای آپلود وردپرس یا لاراول به Object Storage سازگار با S3
پوشهی wp-content/uploads در وردپرس یا storage/app در لاراول معمولاً سریعترین بخش در حال رشد سرور است. تا وقتی همهچیز روی یک سرور است مشکلی به چشم نمیآید، اما بهمحض اینکه بخواهید سرور دوم اضافه کنید، اپلیکیشن را داکرایز کنید یا بکاپها را سریعتر کنید، این فایلها به مانع اصلی تبدیل میشوند. راهحل، انتقال آنها به یک Object Storage سازگار با S3 است. در این آموزش هر دو مسیر لاراول و وردپرس را میبینیم.
پیش از شروع
این اطلاعات را از Object Storage خود آماده کنید (اگر هنوز ندارید، مقایسهی MinIO، Ceph و Garage را ببینید):
- Endpoint: مثلاً
https://s3.example.com - Access Key و Secret Key یک کاربر اختصاصی که فقط به Bucket همین اپلیکیشن دسترسی دارد
- نام Bucket: مثلاً
app-media - Region: برای بیشتر Object Storageهای اختصاصی،
us-east-1یا مقداری که در تنظیمات سرویس تعریف شده - Path-style یا Virtual-hosted: سرویسهای اختصاصی معمولاً Path-style (
https://s3.example.com/app-media/file.jpg) را پشتیبانی میکنند. Virtual-hosted (https://app-media.s3.example.com) به DNS و گواهی Wildcard نیاز دارد.
و قبل از هر کاری، از فایلها و دیتابیس بکاپ بگیرید.
بخش اول: لاراول
۱. نصب درایور Flysystem
لاراول برای کار با فایلها از Flysystem استفاده میکند و درایور S3 آن جداگانه نصب میشود:
composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies
۲. تنظیم disk در config/filesystems.php
disk پیشفرض s3 در نسخههای جدید لاراول همهی گزینههای لازم را دارد؛ کافی است مطمئن شوید endpoint و use_path_style_endpoint در آن هست:
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => true,
],
و در فایل .env:
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=app-user
AWS_SECRET_ACCESS_KEY=کلید-مخفی
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=app-media
AWS_ENDPOINT=https://s3.example.com
AWS_USE_PATH_STYLE_ENDPOINT=true
AWS_URL=https://s3.example.com/app-media
AWS_URL آدرسی است که Storage::url() برای ساخت لینک عمومی فایلها استفاده میکند. اگر بعداً CDN یا دامنهی اختصاصی جلوی Bucket قرار دادید، فقط همین مقدار را عوض کنید. 'throw' => true هم باعث میشود خطاهای آپلود پنهان نمانند؛ در غیر این صورت ممکن است متدها فقط false برگردانند و مشکل دیر کشف شود.
بعد از تغییر:
php artisan config:clear
php artisan tinker
>>> Storage::disk('s3')->put('test/hello.txt', 'hello');
>>> Storage::disk('s3')->exists('test/hello.txt');
۳. نکته دربارهی Checksum در نسخههای جدید AWS SDK
نسخههای جدید AWS SDK برای PHP (از اوایل ۲۰۲۵) بهصورت پیشفرض Checksumهای جدیدی به درخواستها اضافه میکنند که برخی Object Storageهای سازگار با S3 هنوز پشتیبانی نمیکنند. اگر هنگام آپلود خطای مرتبط با Checksum یا Header نامعتبر دیدید، این دو گزینه را به تنظیمات disk اضافه کنید تا فقط در موارد ضروری محاسبه شود:
'request_checksum_calculation' => 'when_required',
'response_checksum_validation' => 'when_required',
۴. کد اپلیکیشن
اگر کد شما از Facade Storage استفاده میکند و مسیر فایل را در دیتابیس ذخیره کرده، تقریباً هیچ تغییری لازم نیست:
$path = $request->file('avatar')->store('avatars'); // روی disk پیشفرض
$url = Storage::url($path);
// برای فایلهای خصوصی، لینک موقت ۱۵ دقیقهای
$link = Storage::temporaryUrl('invoices/1001.pdf', now()->addMinutes(15));
جاهایی که مستقیم با public_path()، storage_path() یا توابع file_put_contents کار میکنند باید به Storage تغییر کنند. لینک نمادین php artisan storage:link هم دیگر برای این فایلها لازم نیست.
۵. انتقال فایلهای قبلی با rclone
مسیرهایی که در دیتابیس ذخیره شدهاند (مثلاً avatars/abc.jpg) نسبت به ریشهی disk هستند. پس محتوای پوشهی disk قبلی (معمولاً storage/app/public) باید عیناً به ریشهی Bucket منتقل شود. یک remote در rclone تعریف کنید، ~/.config/rclone/rclone.conf:
[objstore]
type = s3
provider = Other
access_key_id = app-user
secret_access_key = کلید-مخفی
endpoint = https://s3.example.com
force_path_style = true
اگر سرویس شما Ceph یا MinIO است، میتوانید provider را به همان نام تنظیم کنید. انتقال و بررسی:
rclone copy /var/www/app/storage/app/public objstore:app-media \
--transfers 16 --checksum --progress
rclone check /var/www/app/storage/app/public objstore:app-media --one-way
جایگزین با AWS CLI:
aws --endpoint-url https://s3.example.com s3 sync \
/var/www/app/storage/app/public s3://app-media/
برای مهاجرت بدون توقف: یک بار کپی کامل انجام دهید، سپس در زمان کمترافیک تنظیمات .env را عوض کنید و بلافاصله یک rclone copy دیگر برای فایلهایی که در این فاصله آپلود شدهاند اجرا کنید. فایلهای محلی را تا چند هفته بعد از اطمینان کامل پاک نکنید.
بخش دوم: وردپرس
وردپرس بهطور پیشفرض فقط روی دیسک محلی ذخیره میکند و برای اتصال به S3 به یک افزونهی Offload نیاز دارید. این افزونهها معمولاً این کارها را انجام میدهند:
- فایل آپلودشده (و اندازههای مختلف تصاویر) را بعد از آپلود به Bucket میفرستند.
- آدرس فایلها را در خروجی سایت به آدرس Bucket یا CDN تغییر میدهند.
- در صورت تمایل نسخهی محلی را حذف میکنند تا فضای دیسک آزاد شود.
- ابزاری برای انتقال فایلهای قبلی کتابخانهی رسانه (Bulk Offload) دارند.
هنگام انتخاب افزونه این موارد را بررسی کنید: پشتیبانی از Endpoint اختصاصی (نه فقط Amazon S3)، گزینهی Path-style، امکان تعریف کلیدها در wp-config.php بهجای دیتابیس، و بهروز بودن افزونه. برای نمونه، در زمان نگارش این مقاله افزونهی Advanced Media Offloader در مخزن وردپرس بهروز است و برای Object Storageهای سازگار با S3، گزینهای با Endpoint دلخواه و تنظیم Path-style دارد. نام ثابتها و جزئیات را از مستندات همان افزونه بخوانید؛ الگوی کلی در wp-config.php چیزی شبیه این است:
define('ADVMO_MINIO_KEY', 'app-user');
define('ADVMO_MINIO_SECRET', 'کلید-مخفی');
define('ADVMO_MINIO_BUCKET', 'wp-media');
define('ADVMO_MINIO_ENDPOINT', 'https://s3.example.com');
define('ADVMO_MINIO_DOMAIN', 'https://media.example.com');
define('ADVMO_MINIO_PATH_STYLE_ENDPOINT', true);
برای انتقال فایلهای قبلی در وردپرس، بهجای کپی دستی با rclone، از ابزار Bulk Offload خود افزونه (ترجیحاً نسخهی WP-CLI آن برای کتابخانههای بزرگ) استفاده کنید؛ چون افزونه باید بداند کدام فایل منتقل شده تا آدرس را درست بسازد. کپی دستی فایلها بدون ثبت در افزونه معمولاً به لینکهای شکسته میرسد. فقط اگر افزونه صراحتاً روش «کپی دستی و سپس همگامسازی» را پشتیبانی میکند، از rclone استفاده کنید.
بعد از انتقال: چند نوشتهی قدیمی و جدید را باز کنید، تصاویر شاخص و گالریها و فایلهای قالب و Page Builder را بررسی کنید و کش صفحه را پاک کنید. برخی Page Builderها آدرس کامل تصاویر را داخل محتوا ذخیره میکنند که ممکن است به جایگزینی آدرس در دیتابیس نیاز داشته باشد.
دسترسی عمومی، امنیت و CDN
- کل Bucket را عمومی نکنید. فقط خواندن (
s3:GetObject) آن هم برای Prefixهایی که باید عمومی باشند. هرگز اجازهیListBucketعمومی ندهید، وگرنه هر کسی فهرست همهی فایلها را میبیند. - فایلهای خصوصی (فاکتور، مدارک کاربران، محصولات دانلودی) را در Bucket یا Prefix جداگانه و بدون دسترسی عمومی نگه دارید و فقط با Presigned URL تحویل دهید.
- دامنهی اختصاصی: فایلها را از دامنهای مثل
media.example.comسرو کنید، نه آدرس مستقیم Object Storage. این کار تعویض بعدی زیرساخت یا اضافه کردن CDN را بدون تغییر لینکها ممکن میکند. - CDN و Cache-Control: برای تصاویر و فایلهایی که نامشان با هر تغییر عوض میشود، هدر
Cache-Controlطولانی تنظیم کنید تا بار Object Storage کم شود. - CORS: اگر مرورگر مستقیم به Bucket آپلود میکند (Direct Upload)، قوانین CORS را فقط برای دامنهی سایت خودتان تعریف کنید.
- ترافیک کاربران ایرانی: اگر کاربران شما بیشتر داخل ایران هستند، نگهداری فایلها روی Object Storage داخل کشور هم سرعت را بالا میبرد و هم وابستگی به ترافیک بینالملل را کم میکند.
جمعبندی
در لاراول، انتقال به Object Storage بیشتر یک تغییر تنظیمات است و در وردپرس با یک افزونهی مناسب انجام میشود. بخش حساس کار، انتقال فایلهای قبلی بدون لینک شکسته و تنظیم درست دسترسیهاست. نتیجه، سروری سبکتر، بکاپهای سریعتر (مقالهی بکاپ با Restic) و اپلیکیشنی است که راحت مقیاسپذیر میشود.
نمیخواهید این کار را خودتان انجام دهید؟ تیم مروانت Object Storage اختصاصی را راهاندازی میکند، فایلهای وردپرس یا لاراول شما را بدون توقف سرویس منتقل میکند و دسترسیها و CDN را درست تنظیم میکند. صفحهی راهاندازی Object Storage اختصاصی را ببینید.
