سرور S3 و استفاده از آن در لاراول

مقدمه

در دنیای امروزی تکنولوژی، ذخیره‌سازی اطلاعات و فایل‌ها یکی از اساسی‌ترین نیازهای هر سیستم است. ذخیره‌سازی ابری هم یکی از پرکاربردترین راه‌حل‌ها برای این منظور هستند. Amazon S3 (Simple Storage Service) یکی از معروف‌ترین سرویس‌های ذخیره‌سازی ابری است که توسط Amazon Web Services (AWS) ارائه می‌شود.

Object Storage

راه‌های زیادی برای ذخیره سازی وجود داره. File Storage, Object Storage و Block Storage. در File Storage ما فایل‌ها را به صورت پوشه بندی شده ذخیره می‌کنیم. این فرمت ذخیره سازی ویندوز و سیستم‌عامل های دیگر است. حقیقتا درباره Block Storage چیز زیادی نمیدونم. در آینده یه مقاله دربارش منتشر میکنم.

میرسیم به Object Storage. در این سیستم ذخیره سازی، فایل‌ها به صورت ‌Object ذخیره می‌شود و به هر فایل یه شناسه یا کلید اختصاص داده می‌شود. در این سیستم خبری از پوشه بندی نیست. 

این سیستم مقیاس پذیری خیلی خوبی داره و میشه به صورت کلاستری استفاده کرد. دسترسی پذیری به فایل‌ها خیلی سریعه و به همین دلیل روشیه که در یادگیری ماشین (به خاطر حجم بالای داده‌ها) استفاده میشه.

نصب سرور S3

سرور S3 مختص آمازون است و به دلیل تحریم، استفادش برای ما یه مقدار داستان داره. به همین دلیل ما سراغ یک نمونه متن‌باز از همین سرور میریم که SDK و API های آن با Amazon S3 یکسان است و به راحتی میتوانیم در لاراول از آن استفاده کنیم.

نصب و راه‌اندازی این سرور کار آسانی است. 

میتوان آن را به صورت مستقیم روی سرور لینوکسی نصب کرد و یا از داکر استفاده کرد. ما در این مقاله با استفاده از داکر آن را نصب می‌کنیم.

برای راه‌اندازی سرور MinIO دو فایل docker-compose.yml و .env را در یک دایرکتوری ایجاد کنید. بهتر است دسترسی‌های این دو فایل به صورت زیر باشد (فایل .env فقط توسط owner خود قابلیت مشاهده و ویرایش دارد):

drwxr-xr-x 2 root root 4.0K Nov 26 06:42 .
drwxr-xr-x 9 root root 4.0K Nov 19 06:20 ..
-rw-r--r-- 1 root root  495 Nov 26 04:52 docker-compose.yml
-rw------- 1 root root  321 Nov 26 06:42 .env

برای تنظیم پرمیشن‌های این دو فایل میتوانید از دستورات زیر استفاده کنید:

chmod 644 docker-compose.yml
chmod 600 .env

محتویات هر دو فایل نیز به این ترتیب است:

version: '3'
 
services:
  minio:
    image: minio/minio
    ports:
      - "${MINIO_API_PORT}:9000"
      - "${MINIO_CONSOLE_PORT}:9001"
    volumes:
      - minio_storage:/data
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
      MINIO_SERVER_URL: ${MINIO_SERVER_URL}
      MINIO_BROWSER_REDIRECT_URL: ${MINIO_BROWSER_REDIRECT_URL}
    command: server --console-address ":9001" --address ":9000" /data
 
volumes:
  minio_storage: {}
MINIO_ROOT_USER=************
MINIO_ROOT_PASSWORD=*************
# if you wanna change ports, don't forget to change them in nginx reverse proxy as well.
MINIO_CONSOLE_PORT=9001
MINIO_API_PORT=9000
MINIO_SERVER_URL=https://storage.domain.com
MINIO_BROWSER_REDIRECT_URL=https://storage.domain.com/minio/ui

در فایل .env ،مقدار MINIO_SERVER_URL آدرس سرور MinIO است. یعنی API های این سرور برای ذخیره سازی و دریافت فایل‌ها. و MINIO_BROWSER_REDIRECT_URL هم آدرس کنسول یا همان پنل گرافیکی سرور است.

تا اینجای کار می‌توانیم این docker-compose را اجرا کنیم و به وسیله همان ip سرور و پورت‌ها از آن استفاده کنیم. ولی بیایید یک reverse proxy در nginx اضافه کنیم و با دامنه به سرور متصل شویم.

بنابراین یک فایل جدید در /etc/nginx/sites-enabled ایجاد کنید و محتویات زیر را داخل آن کپی کنید و nginx را ریلود یا ریستارت کنید:

server {
    listen  80;
    listen  [::]:80;   
    listen  443 ssl;
    listen  [::]:443 ssl;
     
    server_name storage.domain.com;
     
    # SSL
    ssl_certificate         /etc/letsencrypt/live/storage.domain.com/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/storage.domain.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/storage.domain.com/chain.pem;
 
    # logging
    access_log              /var/log/nginx/access.log combined buffer=512k flush=1m;
    error_log               /var/log/nginx/error.log warn;
 
    # Allow special characters in headers
    ignore_invalid_headers off;
 
    client_max_body_size 200M;
 
    # Disable buffering
    proxy_buffering off;
    proxy_request_buffering off;
 
    location / {
 
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
 
      proxy_connect_timeout 300;
      # Default is HTTP/1, keepalive is only enabled in HTTP/1.1
      proxy_http_version 1.1;
      proxy_set_header Connection "";
      chunked_transfer_encoding off;   
 
      proxy_pass http://127.0.0.1:9000; 
   }
 
 
   location /minio/ui/ {
      rewrite ^/minio/ui/(.*) /$1 break;
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_set_header X-NginX-Proxy true;
 
      # This is necessary to pass the correct IP to be hashed
      real_ip_header X-Real-IP;
 
      proxy_connect_timeout 300;
 
      # To support websockets in MinIO versions released after January 2023
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
      # Some environments may encounter CORS errors (Kubernetes + Nginx Ingress)
      # Uncomment the following line to set the Origin request to an empty string
      # proxy_set_header Origin '';
 
      chunked_transfer_encoding off;
 
      proxy_pass http://127.0.0.1:9001;
   }
 
}

البته فراموش نکنید که ما اینجا از دامنه storage.domain.com استفاده کردیم. حتما دامنه‌ی خود را در DNS خود ست کنید و ویرایش‌های لازم را در این کدها بدهید.

حالا با دستور docker-compose up -d سرور را اجرا می‌کنیم.

اکنون پنل گرافیکی در آدرس https://storage.main.com/minio/ui قابل مشاهده است. می‌توانید با username و password وارد شده در .env وارد شوید.

docker-compose up -d

استفاده از S3 در لاراول

اتصال لاراول

لاراول برای تعامل با s3 به پکیج زیر نیاز دارد. پس قبل از هر کاری آن را نصب کنید:

composer require league/flysystem-aws-s3-v3

یکی از disk های لاراول که در کانفیگ filesystem.php قرار دارد، s3 است:

's3' => [
     'driver' => 's3',
     'key' => env('AWS_ACCESS_KEY_ID'),
     'secret' => env('AWS_SECRET_ACCESS_KEY'),
     'region' => env('AWS_DEFAULT_REGION'),
     'bucket' => env('AWS_BUCKET'),
     'url' => env('AWS_URL'),
     'endpoint' => env('AWS_ENDPOINT'),
     'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
  ],

فقط دقت کنید که همه این موارد در فایل کانفیگ باشند. ممکن است بسته به نسخه لاراول، برخی به طور پیش‌فرض اینجا نباشند.
بعد مقادیر مورد نظر را در فایل .env ویرایش میکنیم:

مقدار AWS_USE_PATH_STYLE_ENDPOINT به معنای نوع آدرس‌دهی فایل هاست.

مثلا فرض کنید در bucket  ی به نام test فایلی به نام one.png داریم. اگر AWS_USE_PATH_STYLE_ENDPOINT  برابر با true باشد، آدرس این فایل برابر با https://storage.domain.com/test/one.png خواهد بود. ولی اگر false باشد، آدرس فایل https://test.storage.domain.com/one.png خواهد بود. (این مقدار به طور پیشفرض در سرور MinIO برابر با true است و باید در همان راه‌اندازی در فایل docker-compose.yml مشخص شود. پس ما همان true را انتخاب میکنیم)

ایجاد Bucket

مفهوم bucket چیزی شبیه به درایور در ویندوز است. می‌توانید برای هر اپلیکیشن یک ‌bucket مجزا ایجاد کنید. 
برای اینکار بعد از ورود به پنل MinIO از منوی سمت چپ روی ‌buckets کلیک کنید:

و بعد از بالا سمت راست روی Create Bucket کلیک کنید. یک نام برای bucket خود وارد کنید و روی دکمه پایین صفحه (create bucket) کلیک کنید. اکنون میتوانید در لیست، ‌bucket خود را ببینید:

Username & Password

برای مقادیر AWS_ACCESS_KEY_ID و AWS_SECRET_ACCESS_KEY میتوانید یوزرنیم و پسوردی که در .env سرور MinIO وارد کردید را وارد کنید. ولی از آنجایی که این یوزرنیم و پسورد روت هست، بهتر است اینکار را نکنید.

پیشنهاد میشود که ابتدا به Identity/Users رفته و یک یوزر ایجاد کنید و بعد از همانجا وارد کانفیگ‌های آن یوزر شوید و به قسمت service accounts بروید و یک ‌access key ایجاد کنید و از آن استفاده کنید.

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_ENDPOINT=https://storage.domain.com
AWS_BUCKET=test
AWS_USE_PATH_STYLE_ENDPOINT=true

در زیر یک نمونه کد نحوه استفاده از آن در لاراول را میبینید که البته برای ما فرقی با ذخیره سازی عادی ندارد.

public function upload(Request $request)
{
    $file = $request->file('file_input_name');
    $fileName = time() . '_' . $file->getClientOriginalName();

    $filePath = Storage::disk('s3')->putfileAs('uploads', $file, $fileName);

    $expires = now()->addMinutes(30);

    return response()->json([
        'message' => 'file uploaded succesfully',
        'file_path' => Storage::disk('s3')->temporaryUrl($filePath, $expires)
    ]);
}

نکته قابل توجه این است که وقتی در minio باکت جدید میسازیم به طور پیش‌فرض دسترسی فایل‌های درون آن امکان پذیر نیست و فقط با لینک‌های زمان‌دار (مثل کد بالا) میتوان به یک فایل دسترسی داشت. برای اینکه بتوان به صورت پابلیک به فایل‌های یک باکت دسترسی داشته باشید باید با کلیک روی چرخ دنده باکت (در صفحه باکت، بالا سمت راست) به تنظیمات رفته و Access Policy را روی public تنظیم کنید. البته می‌توانید این گزینه را برای یک فولدر خاص درون این باکت نیز انجام دهید و کل باکت را پابلیک نکنید.

حتما ذهنتون درگیر این شده که من گفتم در Object Storage ما چیزی به اسم فولدر نداریم پس چرا در minio همچین چیزی را داریم!؟

خب هنوز هم سر حرفم هستم. در MinIO و کلا در Amazon S3 این یک ساختار به اصطلاح User-Friendly است. تصور کنید فایلی به اسم test.png در پوشه‌ای به اسم folder1 در باکت bucketone موجود است. به این صورت آدرس آن چیزی شبیه به این می‌شود:

https://storage.main.com/bucketone/folder1/test.png

در اینجا MinIo در واقع یه جورایی اسم فولدر folder1 رو به اول اسم test.png اضافه کرده و سپس آن را به صورت آبجکت ذخیره میکند و قضیه را جوری به ما نشان می‌دهد که انگار این فایل در یک فولدر ذخیره شده است.

دیدگاهتان را بنویسید