افزایش مقیاس‌پذیری سرویس‌ها با Docker Compose: چگونه چند اینستنس را اجرا کنیم؟

فهرست مطالب

افزایش مقیاس‌پذیری سرویس‌ها با Docker Compose: چگونه چند اینستنس را اجرا کنیم؟

در دنیای مدرن توسعه نرم‌افزار، مقیاس‌پذیری یک الزام حیاتی است، نه یک آپشن لوکس. با رشد کاربران و افزایش حجم درخواست‌ها، توانایی یک سرویس برای مدیریت بار کاری رو به افزایش، مستقیماً بر موفقیت و پایداری آن تأثیر می‌گذارد. Docker Compose به عنوان ابزاری قدرتمند برای تعریف و اجرای برنامه‌های چند کانتینری، نقشی کلیدی در چرخه توسعه و استقرار ایفا می‌کند. اما چگونه می‌توان از Docker Compose برای مقیاس‌گذاری سرویس‌ها و اجرای چندین اینستنس از یک جزء خاص بهره برد؟ این مقاله به بررسی عمیق این موضوع می‌پردازد و راهکارها، چالش‌ها و بهترین روش‌ها را برای رسیدن به مقیاس‌پذیری افقی با استفاده از Docker Compose ارائه می‌دهد.

هدف این بحث، فراتر از معرفی اولیه Docker Compose است؛ ما به دنبال ارائه راهکارهای عملی و بینش‌های فنی برای مهندسانی هستیم که قصد دارند معماری سرویس‌های خود را برای تحمل ترافیک بالاتر، افزایش قابلیت اطمینان و بهینه‌سازی توزیع بار، با استفاده از قابلیت‌های موجود در Docker Compose، بهبود بخشند. از دستورات ساده مقیاس‌گذاری محلی گرفته تا رویکردهای پیشرفته‌تر در ترکیب با توازن‌دهنده‌های بار خارجی و ملاحظات مربوط به سرویس‌های با وضعیت، هر جنبه‌ای که برای مهندسی یک سیستم مقیاس‌پذیر با Docker Compose لازم است، به دقت تشریح خواهد شد.

درک مبانی مقیاس‌پذیری و نقش Docker در آن

مقیاس‌پذیری به توانایی یک سیستم برای مدیریت بار کاری رو به افزایش یا کاهش منابع بر اساس تقاضا اطلاق می‌شود. این مفهوم در دو بعد اصلی قابل بررسی است: مقیاس‌پذیری عمودی (Vertical Scaling) و مقیاس‌پذیری افقی (Horizontal Scaling).

مقیاس‌پذیری عمودی (Vertical Scaling)

این رویکرد شامل افزایش ظرفیت یک سرور واحد است؛ به عنوان مثال، ارتقاء RAM، CPU یا فضای ذخیره‌سازی یک ماشین مجازی یا فیزیکی. سادگی پیاده‌سازی از مزایای آن است، اما دارای محدودیت‌های ذاتی است. هر سرور دارای حداکثر ظرفیتی است که فراتر از آن نمی‌توانید بروید، و به عنوان یک نقطه شکست منفرد (Single Point of Failure) عمل می‌کند. همچنین، در زمان ارتقاء، معمولاً نیاز به داون‌تایم (Downtime) وجود دارد.

مقیاس‌پذیری افقی (Horizontal Scaling)

این روش شامل اضافه کردن ماشین‌های بیشتر به سیستم است که هر کدام اینستنس‌های مستقلی از سرویس را اجرا می‌کنند. به عنوان مثال، به جای داشتن یک سرور با 128 گیگابایت RAM، می‌توان چهار سرور با 32 گیگابایت RAM داشت. مزایای مقیاس‌پذیری افقی شامل قابلیت اطمینان بالاتر (زیرا از نقاط شکست منفرد جلوگیری می‌کند)، توزیع بهتر بار، و انعطاف‌پذیری بیشتر در مدیریت منابع است. این رویکرد پایه و اساس معماری‌های توزیع‌شده و میکروسرویس‌ها را تشکیل می‌دهد و هدف اصلی این مقاله نیز بر روی آن متمرکز است.

چرا مقیاس‌پذیری حیاتی است؟

  • عملکرد و پاسخگویی: با افزایش بار، بدون مقیاس‌پذیری، زمان پاسخگویی سرویس‌ها به شدت افزایش یافته و تجربه کاربری افت می‌کند.
  • قابلیت اطمینان و تحمل خطا: در یک سیستم مقیاس‌پذیر افقی، خرابی یک اینستنس به معنای از کار افتادن کل سرویس نیست. ترافیک به سمت اینستنس‌های سالم هدایت می‌شود.
  • بهره‌وری از منابع: امکان تخصیص بهینه منابع بر اساس تقاضای واقعی، جلوگیری از هدر رفت منابع در دوره‌های کم‌ترافیک و تأمین منابع کافی در دوره‌های اوج.

نقش Docker در تسهیل مقیاس‌پذیری

Docker با معرفی مفهوم کانتینر، انقلابی در نحوه توسعه، بسته‌بندی و استقرار برنامه‌ها ایجاد کرده است. کانتینرها واحدهای سبک‌وزن و ایزوله‌ای هستند که شامل کد برنامه، کتابخانه‌ها، وابستگی‌ها و تنظیمات لازم برای اجرای برنامه می‌شوند. این ایزوله‌سازی و بسته‌بندی، قابلیت تکرارپذیری را به ارمغان می‌آورد: یک کانتینر در هر محیطی که Docker نصب باشد، به یک شکل اجرا می‌شود.

مزایای Docker برای مقیاس‌پذیری:

  • همگونی محیط: اطمینان از اینکه همه اینستنس‌های یک سرویس در یک محیط استاندارد و قابل پیش‌بینی اجرا می‌شوند، فارغ از سروری که روی آن قرار دارند.
  • شروع سریع: کانتینرها به سرعت بالا می‌آیند و خاموش می‌شوند، که برای مقیاس‌گذاری سریع به بالا و پایین (Scale Up/Down) بسیار مفید است.
  • ایزوله‌سازی: مشکلات در یک کانتینر معمولاً بر کانتینرهای دیگر تأثیر نمی‌گذارد و به پایداری کلی سیستم کمک می‌کند.
  • بهره‌وری منابع: کانتینرها نسبت به ماشین‌های مجازی، سربار کمتری دارند و امکان اجرای تعداد بیشتری سرویس در یک سرور فیزیکی را فراهم می‌کنند.

در حالی که Docker قابلیت‌های بنیادی را برای کانتینریزاسیون فراهم می‌کند، مدیریت و ارکستراسیون چندین کانتینر مرتبط (که یک برنامه کامل را تشکیل می‌دهند) به خصوص در محیط‌های تولیدی، نیازمند ابزارهای قدرتمندتری است. اینجاست که Docker Compose وارد عمل می‌شود.

Docker Compose: ابزاری قدرتمند برای تعریف و مدیریت برنامه‌های چندکانتینری

Docker Compose ابزاری است برای تعریف و اجرای برنامه‌های Docker چندکانتینری. با استفاده از یک فایل YAML، می‌توان تمام سرویس‌های تشکیل‌دهنده یک برنامه را پیکربندی کرد و سپس با یک دستور واحد، کل برنامه را بالا آورد، متوقف کرد یا بازسازی نمود. این قابلیت، فرآیند توسعه، آزمایش و استقرار برنامه‌های پیچیده‌تر را به طرز چشمگیری ساده می‌کند.

ساختار فایل docker-compose.yml

یک فایل docker-compose.yml معمولاً از بخش‌های اصلی زیر تشکیل شده است:


version: '3.8' # یا نسخه‌های جدیدتر
services:
  web:
    image: my-web-app:1.0
    ports:
      - "80:80"
    environment:
      - DB_HOST=db
      - DB_PORT=5432
    depends_on:
      - db
    networks:
      - app-net
  db:
    image: postgres:13
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
    networks:
      - app-net

volumes:
  db_data:

networks:
  app-net:
  • version: نسخه فرمت Compose File را مشخص می‌کند. نسخه‌های بالاتر قابلیت‌های بیشتری ارائه می‌دهند.
  • services: هسته فایل Compose است که تمام سرویس‌های برنامه شما را تعریف می‌کند. هر کلید در این بخش (مثل web و db) یک سرویس مجزا است.
    • image: نام ایمیج Docker که برای ساخت کانتینر سرویس استفاده می‌شود. می‌تواند از Docker Hub یا یک ریپازیتوری خصوصی باشد.
    • build: به جای image، می‌توانید مسیر Dockerfile را برای ساخت ایمیج به صورت محلی مشخص کنید.
    • ports: مپ کردن پورت‌ها از کانتینر به هاست (مثلاً "80:80" به معنی مپ کردن پورت 80 کانتینر به پورت 80 هاست).
    • volumes: مپ کردن حجم‌ها برای نگهداری داده‌های پایدار (مثلاً db_data:/var/lib/postgresql/data).
    • environment: تنظیم متغیرهای محیطی درون کانتینر.
    • depends_on: تعریف وابستگی‌ها بین سرویس‌ها. Compose اطمینان حاصل می‌کند که سرویس‌های وابسته قبل از سرویس اصلی راه‌اندازی شوند. این فقط ترتیب راه‌اندازی را تضمین می‌کند و نه آماده بودن سرویس.
    • networks: اتصال سرویس‌ها به شبکه‌های تعریف‌شده. این کار امکان ارتباط بین سرویس‌ها را فراهم می‌کند.
  • volumes: تعریف حجم‌های نام‌گذاری‌شده (Named Volumes) برای پایداری داده‌ها.
  • networks: تعریف شبکه‌های سفارشی برای ایزوله کردن و سازماندهی ترافیک بین سرویس‌ها.

چرا Docker Compose؟

  • سادگی توسعه محلی: به توسعه‌دهندگان اجازه می‌دهد تا یک محیط توسعه کامل را با تمام وابستگی‌هایش با یک دستور واحد بالا بیاورند.
  • بازسازی آسان: تغییرات در پیکربندی سرویس‌ها یا ایمیج‌ها به راحتی قابل اعمال و بازسازی است.
  • مستندسازی پیکربندی: فایل docker-compose.yml به عنوان یک مستند زنده از معماری و پیکربندی برنامه عمل می‌کند.
  • یکپارچگی با Docker Swarm: همان فایل Compose را می‌توان با تغییرات جزئی برای استقرار در یک کلاستر Docker Swarm استفاده کرد که این قابلیت را به یک ابزار قدرتمند برای محیط‌های تولیدی نیز تبدیل می‌کند.

با این مقدمه، اکنون می‌توانیم به جنبه اصلی بحث خود، یعنی چگونگی مقیاس‌گذاری سرویس‌ها و اجرای چندین اینستنس از یک سرویس با Docker Compose، بپردازیم.

رویکردهای مقیاس‌گذاری سرویس‌ها با Docker Compose

Docker Compose ابزارهای مختلفی برای مقیاس‌گذاری سرویس‌ها فراهم می‌کند، که هر یک برای سناریوهای خاصی مناسب هستند. در این بخش، به بررسی عمیق سه رویکرد اصلی می‌پردازیم: استفاده از دستور --scale، تعریف دستی چندین اینستنس، و استفاده از بخش deploy.replicas که بیشتر در زمینه Docker Swarm کاربرد دارد اما در فرمت Compose گنجانده شده است.

۱. استفاده از دستور docker compose up --scale

ساده‌ترین و مستقیم‌ترین راه برای اجرای چندین اینستنس از یک سرویس در یک محیط محلی یا تک سرور با Docker Compose، استفاده از پرچم --scale همراه با دستور docker compose up است. این دستور به شما امکان می‌دهد تا تعداد کانتینرهایی که برای یک سرویس خاص باید اجرا شوند را مشخص کنید.

نحوه عملکرد

هنگامی که شما docker compose up --scale <service_name>=<N> را اجرا می‌کنید، Compose به جای راه‌اندازی تنها یک کانتینر برای سرویس مشخص‌شده، N تعداد کانتینر از آن سرویس را ایجاد می‌کند. این کانتینرها در یک شبکه مشترک Docker Compose قرار می‌گیرند و از طریق نام سرویس قابل دسترسی هستند.

مثال عملی

فرض کنید یک سرویس وب ساده با نام webapp در فایل docker-compose.yml خود دارید:


# docker-compose.yml
version: '3.8'
services:
  webapp:
    image: nginx:latest
    ports:
      - "80:80" # این پورت برای یک اینستنس است، برای چند اینستنس باید تغییر کند
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 128M

نکته مهم در مورد پورت‌ها: اگر پورت‌ها را به صورت مستقیم به پورت‌های هاست مپ کنید (مانند "80:80")، نمی‌توانید چندین اینستنس از سرویس را روی یک هاست اجرا کنید، زیرا پورت هاست تنها می‌تواند توسط یک فرآیند اشغال شود. برای مقیاس‌گذاری، یا باید پورت‌ها را به صورت پویا مپ کنید (مثلاً "80" که Docker یک پورت رندوم را به آن اختصاص می‌دهد) یا از یک Reverse Proxy / Load Balancer خارجی استفاده کنید که به پورت‌های داخلی کانتینرها متصل می‌شود. برای مقیاس‌گذاری روی یک هاست، بهترین رویکرد این است که سرویس شما به هیچ پورت هاستی مپ نشود و تنها از طریق شبکه داخلی Compose قابل دسترسی باشد، و یک سرویس Reverse Proxy/Load Balancer جداگانه پورت هاست را مپ کند.

بیایید مثال webapp را با در نظر گرفتن این نکته اصلاح کنیم تا برای مقیاس‌پذیری مناسب باشد، یعنی پورت‌ها مستقیماً به هاست مپ نشوند:


# docker-compose.yml
version: '3.8'
services:
  webapp:
    image: nginx:latest # یک ایمیج ساده برای تست
    expose:
      - "80" # پورت 80 کانتینر را برای دسترسی داخلی در شبکه Compose نمایش می‌دهد
    environment:
      - MESSAGE="Hello from instance" # متغیر محیطی برای تشخیص اینستنس
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 128M
  
  loadbalancer:
    image: nginx:latest
    ports:
      - "80:80" # تنها loadbalancer پورت هاست را اشغال می‌کند
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro # فایل تنظیمات Nginx
    depends_on:
      - webapp

و فایل nginx.conf برای سرویس loadbalancer:


# nginx.conf
worker_processes 1;

events { worker_connections 1024; }

http {
    upstream webapp_upstream {
        # Docker Compose DNS round-robin handles resolution for 'webapp' to multiple IPs
        # For more explicit load balancing, you might list servers manually or use an external tool
        server webapp:80; 
        # Add more servers explicitly if you were managing IPs manually
        # server 172.18.0.2:80;
        # server 172.18.0.3:80;
    }

    server {
        listen 80;
        location / {
            proxy_pass http://webapp_upstream;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

حالا برای اجرای 3 اینستنس از webapp:


docker compose up -d --scale webapp=3 loadbalancer

این دستور 3 کانتینر از سرویس webapp و 1 کانتینر از سرویس loadbalancer را راه‌اندازی می‌کند. سرویس loadbalancer (Nginx) به طور خودکار ترافیک را بین 3 اینستنس webapp توزیع خواهد کرد، زیرا Docker Compose به صورت پیش‌فرض از DNS Round-Robin برای نام سرویس‌هایی که چندین اینستنس دارند، استفاده می‌کند.

محدودیت‌های --scale

  • توازن بار ابتدایی: Docker Compose یک توازن بار DNS Round-Robin بسیار ابتدایی بین اینستنس‌ها در شبکه داخلی فراهم می‌کند. این روش از وضعیت سلامت (Health Check) کانتینرها آگاهی ندارد و ممکن است ترافیک را به کانتینرهای ناسالم نیز ارسال کند.
  • محدودیت تک هاست: دستور --scale فقط روی یک هاست کار می‌کند. برای مقیاس‌گذاری واقعی در چندین سرور فیزیکی یا مجازی، به ارکستراتورهای کانتینری مانند Docker Swarm یا Kubernetes نیاز است.
  • عدم خودکارسازی: مقیاس‌گذاری پویا (بر اساس بار) به صورت خودکار انجام نمی‌شود و نیاز به اجرای دستی دستور دارد.

۲. تعریف دستی چندین اینستنس (رویکرد کمتر رایج برای مقیاس‌پذیری خودکار)

در برخی سناریوها، ممکن است نیاز باشد که چندین اینستنس از یک سرویس را به صورت صریح در فایل docker-compose.yml تعریف کنید، به جای استفاده از --scale. این رویکرد معمولاً زمانی استفاده می‌شود که هر اینستنس نیاز به پیکربندی کمی متفاوت (مانند پورت‌های مختلف یا متغیرهای محیطی خاص) داشته باشد، یا برای تست‌های A/B.

مثال


# docker-compose.yml
version: '3.8'
services:
  webapp1:
    image: my-app:latest
    ports:
      - "8001:80"
    environment:
      - INSTANCE_ID=1
  webapp2:
    image: my-app:latest
    ports:
      - "8002:80"
    environment:
      - INSTANCE_ID=2
  webapp3:
    image: my-app:latest
    ports:
      - "8003:80"
    environment:
      - INSTANCE_ID=3

با این پیکربندی، هر سرویس به صورت جداگانه راه‌اندازی می‌شود و هر کدام پورت هاست منحصربه‌فردی را اشغال می‌کنند. این روش به شما کنترل دقیقی بر هر اینستنس می‌دهد، اما بسیار تکراری (boilerplate) است و مدیریت آن برای تعداد زیادی اینستنس عملی نیست. همچنین، نیاز به یک توازن‌دهنده بار خارجی برای توزیع ترافیک به این پورت‌های مختلف خواهید داشت.

معایب

  • boilerplate بالا: برای هر اینستنس باید یک ورودی جدید در فایل docker-compose.yml ایجاد کنید.
  • مدیریت پیچیده: اضافه یا حذف اینستنس‌ها نیازمند ویرایش فایل و بازسازی است.
  • عدم بهره‌وری از قابلیت‌های مقیاس‌گذاری: این رویکرد از قابلیت‌های خودکار Docker Compose برای مقیاس‌گذاری افقی بهره نمی‌برد.

۳. استفاده از deploy.replicas (در زمینه Docker Swarm)

فایل‌های Docker Compose (از نسخه 3 به بعد) شامل یک بخش deploy هستند که برای پیکربندی نحوه استقرار سرویس‌ها در محیط‌های تولیدی مبتنی بر ارکستراتور، مانند Docker Swarm، طراحی شده است. یکی از مهم‌ترین پارامترها در این بخش، replicas است که به شما امکان می‌دهد تعداد اینستنس‌های مورد نظر برای یک سرویس را مشخص کنید.

نحوه عملکرد

هنگامی که شما یک فایل Compose حاوی بخش deploy را با دستور docker stack deploy به یک کلاستر Docker Swarm ارسال می‌کنید، Swarm از اطلاعات replicas برای راه‌اندازی و مدیریت تعداد مشخصی از کانتینرها در سراسر کلاستر استفاده می‌کند. Swarm خود مسئولیت توازن بار، بازیابی از خطا و اطمینان از اجرای تعداد صحیح اینستنس‌ها را بر عهده می‌گیرد.

مثال


# docker-compose.yml
version: '3.8'
services:
  webapp:
    image: my-app:latest
    ports:
      - "80:80" # این پورت در Swarm به صورت ingress load-balancing عمل می‌کند
    deploy:
      mode: replicated
      replicas: 5 # 5 اینستنس از این سرویس را اجرا کن
      resources:
        limits:
          cpus: '0.50'
          memory: 128M
        reservations:
          cpus: '0.25'
          memory: 64M
      restart_policy:
        condition: on-failure
      update_config:
        parallelism: 2
        delay: 10s

برای استقرار این فایل در Docker Swarm:


docker swarm init # اگر Swarm را راه‌اندازی نکرده‌اید
docker stack deploy -c docker-compose.yml myapp

در این سناریو، Docker Swarm به طور خودکار 5 کانتینر از سرویس webapp را در سراسر نودهای کلاستر توزیع می‌کند. اگر یکی از کانتینرها یا نودها از کار بیفتد، Swarm به طور خودکار اینستنس‌های جدیدی را برای حفظ تعداد replicas مورد نظر راه‌اندازی می‌کند. همچنین، Swarm یک توازن بار داخلی پیشرفته‌تر (Ingress Load Balancer) را ارائه می‌دهد که ترافیک ورودی را به تمام اینستنس‌های سالم سرویس توزیع می‌کند.

مزایای deploy.replicas با Swarm

  • توازن بار هوشمند: Swarm از DNS Round-Robin و همچنین یک توازن بار لایه 7 (برای HTTP/S) استفاده می‌کند که قابلیت اطمینان و توزیع بار بهتری را فراهم می‌کند.
  • تحمل خطا: در صورت خرابی یک کانتینر یا نود، Swarm به طور خودکار کانتینر جایگزین را راه‌اندازی می‌کند.
  • استقرار و به‌روزرسانی تدریجی (Rolling Updates): Swarm امکان به‌روزرسانی سرویس‌ها را بدون داون‌تایم با استفاده از استراتژی‌های rolling update فراهم می‌کند.
  • مقیاس‌گذاری در کلاستر: امکان توزیع سرویس‌ها در چندین نود فیزیکی یا مجازی.

در حالی که --scale برای توسعه محلی و آزمایش سریع مناسب است، deploy.replicas (همراه با Docker Swarm) رویکردی قدرتمندتر و مناسب‌تر برای استقرار مقیاس‌پذیر در محیط‌های تولیدی است، بدون اینکه نیاز به تغییر عمده در ساختار فایل Compose باشد.

مدیریت توازن بار (Load Balancing) در سرویس‌های مقیاس‌پذیر

هنگامی که چندین اینستنس از یک سرویس را اجرا می‌کنید، توزیع درخواست‌های ورودی به این اینستنس‌ها از اهمیت بالایی برخوردار است. این فرآیند که به آن توازن بار (Load Balancing) گفته می‌شود، نه تنها عملکرد سیستم را با توزیع یکنواخت بار بهبود می‌بخشد، بلکه با هدایت ترافیک از اینستنس‌های ناسالم، به افزایش قابلیت اطمینان و تحمل خطا نیز کمک می‌کند. در محیط Docker Compose، توازن بار می‌تواند به صورت داخلی یا با استفاده از ابزارهای خارجی پیاده‌سازی شود.

۱. توازن بار داخلی Docker Compose (DNS Round-Robin)

به صورت پیش‌فرض، هنگامی که شما چندین اینستنس از یک سرویس را با docker compose up --scale <service>=<N> راه‌اندازی می‌کنید، Docker Compose یک شبکه داخلی برای کانتینرهای شما ایجاد می‌کند. در این شبکه، نام سرویس به یک نقطه ورودی برای تمام اینستنس‌های آن سرویس تبدیل می‌شود. سیستم DNS داخلی Docker از مکانیزم Round-Robin برای حل نام سرویس به آدرس‌های IP مختلف اینستنس‌های در حال اجرا استفاده می‌کند.

نحوه عملکرد

فرض کنید سرویس webapp شما 3 اینستنس دارد. هنگامی که یک سرویس دیگر (مثلاً loadbalancer) سعی می‌کند با نام webapp به آن متصل شود، Docker DNS به صورت چرخشی یکی از IPهای کانتینرهای webapp را برمی‌گرداند. این باعث می‌شود درخواست‌ها به صورت تقریبی بین اینستنس‌ها توزیع شوند.

محدودیت‌ها

  • عدم آگاهی از وضعیت سلامت (Health Check): DNS Round-Robin به وضعیت سلامت کانتینرها توجهی نمی‌کند. اگر یکی از اینستنس‌های webapp ناسالم شود و نتواند به درخواست‌ها پاسخ دهد، DNS همچنان ممکن است آدرس IP آن را برگرداند و باعث شکست درخواست‌ها شود.
  • توزیع غیرهوشمند: این روش بار سرورها را در نظر نمی‌گیرد و صرفاً به صورت چرخشی عمل می‌کند. ممکن است یک سرور با بار کاری بسیار زیاد، همچنان درخواست‌های جدیدی دریافت کند.
  • محدود به شبکه داخلی: این نوع توازن بار فقط برای ارتباطات داخلی بین کانتینرهای Compose معتبر است. برای ترافیک ورودی از خارج، نیاز به یک توازن‌دهنده بار خارجی دارید.

۲. توازن بار خارجی با Reverse Proxy

برای حل محدودیت‌های DNS Round-Robin و ارائه قابلیت‌های پیشرفته‌تر مانند Health Checks، SSL Termination و الگوریتم‌های توزیع بار هوشمند، استفاده از یک Reverse Proxy و Load Balancer خارجی ضروری است. این Reverse Proxy می‌تواند خود یک کانتینر Docker باشد که همراه با بقیه سرویس‌ها با Compose راه‌اندازی می‌شود.

الف. Nginx

Nginx یکی از محبوب‌ترین و پرکاربردترین Reverse Proxy و Load Balancer‌ها است. پیکربندی Nginx برای توازن بار بین چندین اینستنس Docker Compose نسبتاً ساده است.

پیکربندی docker-compose.yml برای Nginx:

# docker-compose.yml
version: '3.8'
services:
  webapp:
    image: my-app:latest # ایمیج اپلیکیشن شما
    expose:
      - "8000" # پورت داخلی اپلیکیشن
    deploy:
      resources:
        limits:
          cpus: '0.25'
          memory: 64M

  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443" # اگر از SSL استفاده می‌کنید
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      # - ./ssl:/etc/nginx/ssl:ro # اگر از SSL استفاده می‌کنید
    depends_on:
      - webapp # اطمینان از راه‌اندازی webapp قبل از nginx
فایل nginx.conf:

# nginx.conf
worker_processes 1;

events { worker_connections 1024; }

http {
    upstream webapp_upstream {
        # 'webapp' is the service name, Docker's internal DNS will resolve it to multiple IPs
        server webapp:8000; 
        # Nginx also supports basic health checks for upstream servers
        # For more robust health checks, consider a paid Nginx Plus or other dedicated load balancer
    }

    server {
        listen 80;
        server_name your_domain.com; # دامنه خود را اینجا قرار دهید

        location / {
            proxy_pass http://webapp_upstream;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_connect_timeout 60s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
        }

        # Optional: For basic health check path
        # location /health {
        #     return 200 'OK';
        #     add_header Content-Type text/plain;
        # }
    }
}

با این پیکربندی، Nginx پورت 80 هاست را مپ می‌کند و تمام درخواست‌های ورودی را به گروهی از سرورها به نام webapp_upstream که شامل تمام اینستنس‌های سرویس webapp (که از طریق نام سرویس webapp در شبکه Compose قابل دسترسی هستند) می‌شود، پروکسی می‌کند. Nginx به صورت پیش‌فرض از الگوریتم Round-Robin برای توزیع درخواست‌ها استفاده می‌کند.

ب. HAProxy

HAProxy یک Reverse Proxy و Load Balancer بسیار قدرتمند و با کارایی بالا است که برای محیط‌های با حجم ترافیک بالا طراحی شده است. HAProxy الگوریتم‌های توازن بار پیشرفته‌تر، Health Checks جامع و قابلیت‌های مدیریت نشست (Session Management) را ارائه می‌دهد.

پیکربندی HAProxy کمی پیچیده‌تر از Nginx است، اما برای سناریوهایی که نیاز به کنترل دقیق‌تری بر توزیع بار و پایداری سرویس دارید، انتخاب بهتری است.

ج. Traefik

Traefik یک Edge Router و Load Balancer مدرن است که به طور خاص برای محیط‌های کانتینری و میکروسرویس‌ها طراحی شده است. Traefik به صورت پویا سرویس‌های جدید را کشف می‌کند و به طور خودکار تنظیمات توازن بار را به روزرسانی می‌کند. این ویژگی Traefik را برای محیط‌های دینامیک Docker Compose و Swarm بسیار مناسب می‌سازد.

مزایای Traefik:
  • کشف سرویس پویا (Dynamic Service Discovery): به طور خودکار کانتینرهای جدید را کشف کرده و به توازن بار اضافه می‌کند.
  • یکپارچگی با Docker: قابلیت خواندن پیکربندی مستقیماً از سوکت Docker.
  • SSL/TLS خودکار: یکپارچگی با Let’s Encrypt برای صدور و تمدید خودکار گواهی‌نامه‌های SSL.
  • داشبورد مدیریتی: رابط کاربری گرافیکی برای نظارت بر سرویس‌ها و ترافیک.
پیکربندی docker-compose.yml با Traefik:

# docker-compose.yml
version: '3.8'
services:
  traefik:
    image: "traefik:v2.10"
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false" # فقط سرویس‌هایی که لیبل دارند را expose کن
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
      - "8080:8080" # داشبورد Traefik
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

  webapp:
    image: my-app:latest
    expose:
      - "8000"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.webapp.rule=Host(`localhost`) && PathPrefix(`/`)"
      - "traefik.http.routers.webapp.entrypoints=web"
      - "traefik.http.services.webapp.loadbalancer.server.port=8000"
    deploy:
      resources:
        limits:
          cpus: '0.25'
          memory: 64M

برای مقیاس‌گذاری webapp با Traefik:


docker compose up -d --scale webapp=3 traefik

Traefik به طور خودکار لیبل‌های روی سرویس webapp را می‌خواند و یک Router و Service برای آن ایجاد می‌کند که ترافیک را به 3 اینستنس webapp توزیع می‌کند.

انتخاب بین Nginx، HAProxy یا Traefik به نیازهای خاص پروژه، پیچیدگی معماری و میزان پویایی محیط شما بستگی دارد. برای سادگی و استفاده عمومی، Nginx یک گزینه عالی است. برای کارایی بالا و کنترل دقیق، HAProxy مناسب است و برای محیط‌های میکروسرویس پویا، Traefik بهترین انتخاب است.

چالش‌های داده‌های با وضعیت (Stateful) در مقیاس‌گذاری

یکی از مهم‌ترین ملاحظات در هنگام مقیاس‌گذاری سرویس‌ها، نحوه مدیریت داده‌ها است، به خصوص زمانی که سرویس‌ها نیاز به حفظ وضعیت (State) دارند. تمایز بین سرویس‌های بی‌وضعیت (Stateless) و با وضعیت (Stateful) برای طراحی یک معماری مقیاس‌پذیر حیاتی است.

۱. سرویس‌های بی‌وضعیت (Stateless Services)

یک سرویس بی‌وضعیت، سرویسی است که هیچ داده‌ای را بین درخواست‌ها یا جلسات کاربر (sessions) در حافظه خود ذخیره نمی‌کند. هر درخواست به این سرویس مستقل از درخواست‌های قبلی یا بعدی است. این سرویس‌ها معمولاً داده‌های خود را در یک منبع خارجی (مانند پایگاه داده یا سیستم فایل مشترک) ذخیره می‌کنند.

ویژگی‌ها:

  • سادگی مقیاس‌گذاری: مقیاس‌گذاری افقی سرویس‌های بی‌وضعیت بسیار آسان است. می‌توانید به سادگی تعداد اینستنس‌ها را افزایش دهید، و هر اینستنس جدید می‌تواند بدون هیچ پیکربندی اضافه‌ای شروع به کار کند.
  • تحمل خطا بالا: اگر یک اینستنس از کار بیفتد، می‌توان آن را بدون از دست دادن اطلاعات به سادگی جایگزین کرد.
  • مثال‌ها: API Gateway، microservices که داده‌های خود را در پایگاه داده ذخیره می‌کنند، سرورهای وب ساده‌ای که فقط فایل‌های استاتیک ارائه می‌دهند.

۲. سرویس‌های با وضعیت (Stateful Services)

یک سرویس با وضعیت، سرویسی است که داده‌ها را بین درخواست‌ها و جلسات کاربر در خود ذخیره می‌کند. این داده‌ها می‌توانند شامل وضعیت نشست کاربر، کش‌های محلی، یا داده‌های اصلی برنامه باشند.

ویژگی‌ها:

  • پیچیدگی مقیاس‌گذاری: مقیاس‌گذاری سرویس‌های با وضعیت چالش‌برانگیزتر است، زیرا باید از همگام‌سازی و پایداری داده‌ها در بین تمام اینستنس‌ها اطمینان حاصل شود.
  • نیاز به ذخیره‌سازی مشترک: اینستنس‌های متعدد باید به یک منبع داده مشترک دسترسی داشته باشند، یا داده‌ها باید بین آن‌ها به طور موثر همگام شوند.
  • مثال‌ها: پایگاه‌های داده (PostgreSQL, MySQL, MongoDB)، صف‌های پیام (RabbitMQ, Kafka)، سرورهای کش (Redis)، سرویس‌های StatefulSession.

چالش‌های اصلی در مقیاس‌گذاری سرویس‌های با وضعیت با Docker Compose

  1. ثبات داده (Data Consistency): اطمینان از اینکه همه اینستنس‌ها دید یکسانی از داده‌ها دارند و تغییرات به درستی در سراسر سیستم منتشر می‌شوند.
  2. ذخیره‌سازی پایدار و مشترک: کانتینرها به طور پیش‌فرض موقتی هستند. داده‌های با وضعیت باید در حجم‌های پایدار (Volumes) ذخیره شوند. برای مقیاس‌گذاری، این حجم‌ها باید به صورت مشترک بین چندین اینستنس در دسترس باشند.
  3. کلاسترینگ و همگام‌سازی: سرویس‌های با وضعیت اغلب نیاز به مکانیزم‌های کلاسترینگ داخلی (مانند Replica Sets در MongoDB یا Streaming Replication در PostgreSQL) برای اطمینان از High Availability و تحمل خطا دارند.
  4. انتخاب رهبر (Leader Election): در برخی سرویس‌های با وضعیت، نیاز به انتخاب یک “رهبر” برای هماهنگی عملیات یا جلوگیری از تداخل وجود دارد.

راهکارها برای مدیریت سرویس‌های با وضعیت در محیط Docker Compose (و فراتر از آن)

الف. جدا کردن پایگاه داده

بهترین رویکرد برای اکثر برنامه‌ها این است که پایگاه داده را از سرویس‌های اپلیکیشن جدا کنید. یعنی پایگاه داده را در یک سرویس Compose جداگانه قرار دهید (اگر توسعه محلی است) یا از یک سرویس پایگاه داده مدیریت شده ابری (مانند AWS RDS, Azure SQL Database, Google Cloud SQL) استفاده کنید. این کار به سرویس‌های اپلیکیشن شما امکان می‌دهد تا بی‌وضعیت باشند و به راحتی مقیاس‌پذیر شوند، در حالی که پیچیدگی‌های مقیاس‌گذاری پایگاه داده به عهده راهکار تخصصی آن است.


# docker-compose.yml (مثال جدا کردن پایگاه داده)
version: '3.8'
services:
  webapp:
    image: my-app:latest
    expose:
      - "8000"
    environment:
      - DATABASE_URL=postgres://user:password@db:5432/mydb # اشاره به سرویس db
  db:
    image: postgres:13
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password

volumes:
  db_data:

ب. استفاده از سیستم‌های فایل توزیع‌شده (برای داده‌های مشترک)

در سناریوهایی که سرویس‌های اپلیکیشن شما نیاز به دسترسی به فایل‌های مشترک دارند (مثلاً آپلود فایل‌ها توسط کاربران)، می‌توانید از سیستم‌های فایل توزیع‌شده مانند NFS، GlusterFS یا Ceph استفاده کنید. کانتینرها می‌توانند این سیستم‌های فایل را به عنوان حجم‌های مشترک mount کنند. این راهکار پیچیدگی‌های عملیاتی خود را دارد و معمولاً برای محیط‌های توسعه محلی توصیه نمی‌شود.

ج. کلاسترینگ داخلی پایگاه داده‌ها

بسیاری از پایگاه‌های داده مدرن (مانند MongoDB, PostgreSQL, Redis) دارای قابلیت‌های کلاسترینگ داخلی هستند که امکان مقیاس‌گذاری افقی و High Availability را فراهم می‌کنند. راه‌اندازی این کلاسترها با Docker Compose برای محیط‌های توسعه و تست ممکن است، اما برای محیط تولیدی، معمولاً استفاده از ابزارهای ارکستراسیون پیشرفته‌تر (مانند Kubernetes StatefulSets) یا سرویس‌های مدیریت شده ابری توصیه می‌شود.

  • MongoDB Replica Set: برای راه‌اندازی یک Replica Set در Compose، باید چندین اینستنس MongoDB را تعریف کرده و سپس آن‌ها را به عنوان اعضای یک Replica Set پیکربندی کنید.
  • PostgreSQL Streaming Replication: برای High Availability، می‌توان یک master و یک یا چند slave را با Compose راه‌اندازی کرد که داده‌ها را به صورت Streaming دریافت می‌کنند.
  • Redis Cluster: Redis نیز قابلیت Cluster دارد که به توزیع داده‌ها و بار بین چندین نود کمک می‌کند.

محدودیت Docker Compose برای سرویس‌های با وضعیت:

اگرچه می‌توان با Docker Compose تا حدی سرویس‌های با وضعیت را راه‌اندازی کرد (مثلاً یک Replica Set MongoDB)، اما Compose به تنهایی فاقد قابلیت‌های ارکستراسیون پیشرفته مورد نیاز برای مدیریت پایدار، خودکار و انعطاف‌پذیر این سرویس‌ها در مقیاس تولیدی است. قابلیت‌هایی مانند استقرار StatefulSets (در Kubernetes)، مدیریت PVCs/PVs، انتخاب رهبر خودکار و مدیریت پیچیده failover، فراتر از دامنه Docker Compose هستند.

به طور خلاصه، در حالی که Docker Compose ابزاری عالی برای توسعه و آزمایش برنامه‌های چند کانتینری است، برای مقیاس‌گذاری سرویس‌های با وضعیت در محیط‌های تولیدی، باید به راهکارهای تخصصی‌تر مانند پایگاه‌های داده مدیریت شده ابری یا ارکستراتورهای کانتینری پیشرفته روی آورد. بهترین استراتژی معمولاً طراحی سرویس‌های اپلیکیشن به گونه‌ای است که بی‌وضعیت باشند و داده‌های با وضعیت را به سیستم‌های تخصصی و جداگانه بسپارند.

بهترین روش‌ها و ملاحظات برای مقیاس‌پذیری با Docker Compose

مقیاس‌پذیری تنها به اضافه کردن اینستنس‌های بیشتر محدود نمی‌شود. برای اینکه یک سیستم به صورت کارآمد مقیاس‌پذیر باشد و در عین حال پایدار و قابل مدیریت باقی بماند، رعایت مجموعه‌ای از بهترین روش‌ها و در نظر گرفتن ملاحظات خاص ضروری است. این اصول، اگرچه برخی فراتر از دامنه مستقیم Docker Compose هستند، اما در معماری کلی سیستم‌های مبتنی بر کانتینر نقش محوری دارند.

۱. معماری میکروسرویس (Microservices Architecture)

میکروسرویس‌ها به طور ذاتی برای مقیاس‌پذیری افقی طراحی شده‌اند. به جای یک Monolith بزرگ، برنامه به مجموعه‌ای از سرویس‌های کوچک و مستقل شکسته می‌شود که هر کدام مسئولیت خاصی دارند. این رویکرد مزایای بسیاری برای مقیاس‌پذیری دارد:

  • مقیاس‌پذیری مستقل: هر میکروسرویس می‌تواند به صورت مستقل از سایرین مقیاس‌گذاری شود. به عنوان مثال، اگر سرویس احراز هویت شما بار زیادی را تجربه می‌کند، می‌توانید تنها همان سرویس را مقیاس‌گذاری کنید، بدون اینکه منابع اضافی به سرویس‌های دیگر اختصاص دهید.
  • ایزوله‌سازی خطا: خرابی یک سرویس بر عملکرد سایر سرویس‌ها تأثیر نمی‌گذارد (در صورت پیاده‌سازی صحیح با Circuit Breaker و Retry Logic).
  • تکنولوژی‌های متنوع: تیم‌ها می‌توانند از تکنولوژی‌ها و زبان‌های برنامه‌نویسی مختلفی برای هر سرویس استفاده کنند.

Docker Compose ابزاری ایده‌آل برای توسعه و آزمایش محیط‌های میکروسرویس محلی است، زیرا به شما امکان می‌دهد تمام سرویس‌های تشکیل‌دهنده برنامه را در یک فایل docker-compose.yml تعریف کنید.

۲. کانتینرهای کوچک و تک‌منظوره

اصل “یک کانتینر، یک فرآیند” (One Container, One Process) یک بهترین روش کلیدی است. هر کانتینر باید مسئولیت انجام یک وظیفه واحد را بر عهده داشته باشد. این رویکرد به:

  • بهینه‌سازی منابع: کانتینرها سبک‌تر و سریع‌تر بالا می‌آیند.
  • استقرار و مقیاس‌پذیری آسان‌تر: می‌توانید کانتینرهای کوچک‌تر را به صورت مستقل مقیاس‌گذاری کنید.
  • اشکال‌زدایی ساده‌تر: تمرکز بر یک فرآیند در هر کانتینر، عیب‌یابی را آسان‌تر می‌کند.

۳. سلامت‌سنجی (Health Checks)

سلامت‌سنجی‌ها برای اطمینان از اینکه کانتینرهای شما نه تنها در حال اجرا هستند، بلکه به درستی نیز کار می‌کنند، حیاتی هستند. یک کانتینر ممکن است در حال اجرا باشد اما برنامه درون آن به دلیل خطایی از کار افتاده باشد. Docker Compose (و همچنین Docker Swarm و Kubernetes) از قابلیت Health Check پشتیبانی می‌کند.


# docker-compose.yml (مثال Health Check)
services:
  webapp:
    image: my-app:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 20s # زمان اولیه برای راه‌اندازی کامل سرویس

این پیکربندی به Docker می‌گوید که هر 30 ثانیه یکبار به پورت 8000 سرویس webapp درخواست /health ارسال کند. اگر پاسخ خطا (Non-2xx) باشد یا تایم‌اوت شود، کانتینر ناسالم در نظر گرفته می‌شود. Load Balancers می‌توانند از این اطلاعات برای هدایت ترافیک به سمت اینستنس‌های سالم استفاده کنند.

۴. مانیتورینگ و لاگینگ متمرکز

با افزایش تعداد اینستنس‌ها، پیگیری وضعیت سیستم و اشکال‌زدایی بسیار دشوار می‌شود. راه‌اندازی یک سیستم مانیتورینگ و لاگینگ متمرکز حیاتی است:

  • مانیتورینگ: ابزارهایی مانند Prometheus و Grafana برای جمع‌آوری و بصری‌سازی معیارهای عملکرد (CPU, Memory, Network I/O, QPS, Latency) از تمام کانتینرها و هاست‌ها.
  • لاگینگ متمرکز: ابزارهایی مانند ELK Stack (Elasticsearch, Logstash, Kibana) یا Loki/Grafana برای جمع‌آوری، ذخیره و جستجوی لاگ‌ها از همه کانتینرها.

Docker Compose به شما اجازه می‌دهد تا درایورهای لاگ را در فایل docker-compose.yml پیکربندی کنید تا لاگ‌ها به سیستم‌های متمرکز ارسال شوند.


# docker-compose.yml (مثال Log Driver)
services:
  webapp:
    image: my-app:latest
    logging:
      driver: "json-file" # پیش‌فرض
      options:
        max-size: "10m"
        max-file: "3"
    # یا ارسال به syslog
    # logging:
    #   driver: "syslog"
    #   options:
    #     syslog-address: "udp://127.0.0.1:514"

۵. پیکربندی خارجی (Externalized Configuration)

هرگز اطلاعات حساس یا پیکربندی‌های محیطی را مستقیماً در ایمیج Docker یا فایل docker-compose.yml سخت کد نکنید. به جای آن، از متغیرهای محیطی یا فایل‌های .env برای تزریق پیکربندی در زمان اجرا استفاده کنید. برای محیط‌های تولیدی، ابزارهایی مانند HashiCorp Vault (برای اسرار) یا Consul (برای پیکربندی دینامیک) توصیه می‌شوند.


# .env (مثال)
DB_USER=myuser
DB_PASSWORD=mypassword

# docker-compose.yml
services:
  webapp:
    image: my-app:latest
    environment:
      - DB_USER=${DB_USER} # استفاده از متغیر محیطی از فایل .env
      - DB_PASSWORD=${DB_PASSWORD}

۶. مدیریت منابع (Resource Management)

برای جلوگیری از رقابت منابع و اطمینان از عملکرد پایدار، تعیین محدودیت‌ها و رزرو منابع برای کانتینرها ضروری است. می‌توانید limits (حداکثر مصرف) و reservations (حداقل تضمین‌شده) را برای CPU و Memory در بخش deploy یک سرویس در docker-compose.yml مشخص کنید. این قابلیت بیشتر در Docker Swarm و Kubernetes کاربرد دارد، اما در فایل Compose نیز قابل تعریف است.


# docker-compose.yml (مثال Resource Limits)
services:
  webapp:
    image: my-app:latest
    deploy:
      resources:
        limits:
          cpus: '0.50' # حداکثر 0.5 هسته CPU
          memory: 256M # حداکثر 256 مگابایت حافظه
        reservations:
          cpus: '0.10' # حداقل 0.1 هسته CPU تضمین‌شده
          memory: 64M # حداقل 64 مگابایت حافظه تضمین‌شده

۷. CI/CD (Continuous Integration/Continuous Deployment)

برای استقرار مقیاس‌پذیر و خودکار، ادغام Docker Compose در یک پایپ‌لاین CI/CD ضروری است. این پایپ‌لاین می‌تواند مسئولیت ساخت ایمیج‌ها، اجرای تست‌ها، و در نهایت استقرار برنامه‌ها را بر عهده بگیرد. ابزارهایی مانند Jenkins, GitLab CI, GitHub Actions یا Travis CI می‌توانند با Docker Compose ادغام شوند.

با رعایت این بهترین روش‌ها، می‌توانید سیستم‌هایی را با Docker Compose توسعه و استقرار دهید که نه تنها مقیاس‌پذیر هستند، بلکه پایدار، قابل اعتماد و آسان برای مدیریت در طولانی‌مدت نیز می‌باشند.

مثال عملی: مقیاس‌گذاری یک اپلیکیشن وب ساده با Docker Compose و Nginx

برای درک بهتر مفاهیم مقیاس‌پذیری با Docker Compose، یک مثال عملی را پیاده‌سازی می‌کنیم. در این مثال، یک اپلیکیشن وب ساده Flask (یا هر فریم‌ورک مشابهی) را کانتینری کرده و با استفاده از Docker Compose چندین اینستنس از آن را اجرا می‌کنیم. سپس از Nginx به عنوان یک Reverse Proxy و Load Balancer برای توزیع درخواست‌ها بین این اینستنس‌ها استفاده خواهیم کرد.

۱. اپلیکیشن وب (Flask)

یک فایل app.py بسیار ساده ایجاد می‌کنیم:


# app.py
from flask import Flask, request
import os
import socket

app = Flask(__name__)

@app.route('/')
def hello():
    instance_id = os.environ.get("INSTANCE_ID", "UNKNOWN")
    hostname = socket.gethostname()
    return f"Hello from Instance ID: {instance_id} on Hostname: {hostname}\n"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

این اپلیکیشن Flask یک پیغام ساده را برمی‌گرداند که شامل INSTANCE_ID (از متغیر محیطی) و نام هاست (hostname) کانتینر است. این به ما کمک می‌کند تا ببینیم کدام اینستنس به درخواست پاسخ داده است.

۲. Dockerfile برای اپلیکیشن

برای کانتینری کردن اپلیکیشن Flask، یک فایل Dockerfile ایجاد می‌کنیم:


# Dockerfile
FROM python:3.9-slim-buster

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .

EXPOSE 5000

CMD ["python", "app.py"]

و فایل requirements.txt:


Flask

۳. پیکربندی Nginx برای Load Balancing

یک فایل nginx.conf برای Nginx ایجاد می‌کنیم که به عنوان Reverse Proxy عمل کند و ترافیک را به سرویس webapp ما هدایت کند:


# nginx.conf
worker_processes 1;

events { worker_connections 1024; }

http {
    upstream webapp_upstream {
        # 'webapp' is the service name in docker-compose.yml.
        # Docker's internal DNS will resolve this to the IPs of all 'webapp' instances.
        server webapp:5000;
    }

    server {
        listen 80;
        server_name localhost;

        location / {
            proxy_pass http://webapp_upstream;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_connect_timeout 60s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
        }
    }
}

توجه داشته باشید که server webapp:5000; در بخش upstream به نام سرویس webapp در فایل docker-compose.yml و پورت داخلی که اپلیکیشن Flask روی آن گوش می‌دهد، اشاره دارد. Docker Compose DNS به طور خودکار این نام را به آدرس IPهای متعدد اینستنس‌های webapp تبدیل می‌کند.

۴. فایل docker-compose.yml

اکنون، فایل اصلی docker-compose.yml را ایجاد می‌کنیم تا همه چیز را کنار هم قرار دهیم:


# docker-compose.yml
version: '3.8'

services:
  webapp:
    build: . # ساخت ایمیج از Dockerfile در همین دایرکتوری
    expose:
      - "5000" # پورت داخلی کانتینر که توسط Nginx استفاده می‌شود
    environment:
      - INSTANCE_ID=${HOSTNAME} # استفاده از HOSTNAME کانتینر به عنوان ID
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - app-network

  nginx:
    image: nginx:latest
    ports:
      - "80:80" # Nginx روی پورت 80 هاست گوش می‌دهد
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - webapp # اطمینان از راه‌اندازی webapp قبل از nginx
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

در این فایل:

  • سرویس webapp از Dockerfile محلی ساخته می‌شود و پورت 5000 آن expose شده است (یعنی در شبکه داخلی Compose قابل دسترسی است، اما به پورت هاست مپ نشده است).
  • متغیر محیطی INSTANCE_ID را برابر با ${HOSTNAME} کانتینر قرار می‌دهیم تا بتوانیم تشخیص دهیم کدام اینستنس پاسخ داده است.
  • یک healthcheck برای webapp تعریف شده تا Nginx بتواند در صورت پیشرفته بودن تنظیماتش از وضعیت سلامت کانتینرها آگاه شود (هرچند Nginx ساده در اینجا از آن استفاده نمی‌کند، اما وجود آن یک بهترین روش است).
  • سرویس nginx از ایمیج رسمی Nginx استفاده می‌کند، پورت 80 هاست را مپ می‌کند و فایل nginx.conf ما را به داخل کانتینر mount می‌کند.
  • هر دو سرویس در یک app-network مشترک قرار دارند تا بتوانند با یکدیگر ارتباط برقرار کنند.

۵. اجرای و مقیاس‌گذاری

تمام فایل‌ها را در یک دایرکتوری قرار دهید. سپس، دستور زیر را در ترمینال اجرا کنید:


docker compose up -d --build --scale webapp=3 nginx
  • -d: کانتینرها را در پس‌زمینه اجرا می‌کند.
  • --build: ایمیج webapp را قبل از راه‌اندازی، دوباره می‌سازد.
  • --scale webapp=3: به Docker Compose می‌گوید که 3 اینستنس از سرویس webapp را راه‌اندازی کند.
  • nginx: اطمینان حاصل می‌کند که سرویس Nginx نیز راه‌اندازی شود.

۶. تست مقیاس‌پذیری

پس از اینکه کانتینرها بالا آمدند (با docker compose ps می‌توانید وضعیت آن‌ها را بررسی کنید)، می‌توانید چندین بار به آدرس http://localhost/ درخواست ارسال کنید:


curl http://localhost/
curl http://localhost/
curl http://localhost/
curl http://localhost/
curl http://localhost/
curl http://localhost/

شما باید خروجی‌هایی مشابه زیر را مشاهده کنید که نشان می‌دهد درخواست‌ها به اینستنس‌های مختلف webapp ارسال شده‌اند:


Hello from Instance ID: webapp-1 on Hostname: webapp-1-dskdsjdjdj
Hello from Instance ID: webapp-2 on Hostname: webapp-2-ewrwerwerw
Hello from Instance ID: webapp-3 on Hostname: webapp-3-dsfsdfsdfd
Hello from Instance ID: webapp-1 on Hostname: webapp-1-dskdsjdjdj
Hello from Instance ID: webapp-2 on Hostname: webapp-2-ewrwerwerw
Hello from Instance ID: webapp-3 on Hostname: webapp-3-dsfsdfsdfd

Nginx به عنوان توازن‌دهنده بار، درخواست‌ها را به صورت Round-Robin بین 3 اینستنس سرویس webapp توزیع می‌کند.

۷. پاکسازی

برای متوقف کردن و حذف تمام کانتینرها و شبکه‌ها:


docker compose down

این مثال ساده نشان می‌دهد که چگونه می‌توان با استفاده از docker compose up --scale و یک Reverse Proxy مانند Nginx، مقیاس‌پذیری افقی را در یک محیط Docker Compose پیاده‌سازی کرد.

فراتر از Docker Compose: ارکستراتورهای کانتینری برای مقیاس‌گذاری در سطح تولید

در حالی که Docker Compose ابزاری فوق‌العاده برای توسعه محلی و حتی استقرار‌های کوچک در محیط‌های غیرتولیدی است، محدودیت‌های ذاتی آن برای مدیریت سیستم‌های مقیاس‌پذیر و پیچیده در سطح تولید، به‌ویژه در محیط‌های چند سروره، آشکار می‌شود. برای رسیدن به مقیاس‌پذیری، قابلیت اطمینان و خودکارسازی مورد نیاز در یک محیط تولیدی واقعی، به ارکستراتورهای کانتینری (Container Orchestrators) نیاز داریم.

محدودیت‌های Docker Compose برای محیط تولید

  1. محدودیت تک هاست: دستور docker compose up --scale تنها روی یک هاست عمل می‌کند. برای مقیاس‌گذاری واقعی در چندین سرور، Compose به تنهایی کافی نیست.
  2. عدم تحمل خطا و بازیابی خودکار: اگر یک کانتینر یا هاست از کار بیفتد، Compose به طور خودکار اینستنس‌های جدید را راه‌اندازی نمی‌کند یا کانتینرها را به هاست‌های سالم منتقل نمی‌کند. این نیاز به دخالت دستی دارد.
  3. عدم توازن بار پیشرفته: توازن بار داخلی Compose (DNS Round-Robin) ساده است و فاقد قابلیت‌هایی مانند Health Checks پیشرفته، الگوریتم‌های توزیع بار پویا و مدیریت نشست است.
  4. به‌روزرسانی و بازگشت (Rolling Updates/Rollbacks): Compose به صورت بومی قابلیت به‌روزرسانی تدریجی سرویس‌ها را بدون داون‌تایم یا بازگشت آسان به نسخه‌های قبلی را ندارد.
  5. مدیریت ذخیره‌سازی پایدار: مدیریت حجم‌های توزیع‌شده و ذخیره‌سازی برای سرویس‌های با وضعیت در یک کلاستر چند هاستی با Compose پیچیده است.
  6. مدیریت اسرار و پیکربندی: Compose راهکار بومی پیشرفته‌ای برای مدیریت امن اسرار و پیکربندی دینامیک در یک محیط کلاستر ارائه نمی‌دهد.

Docker Swarm: گام بعدی در ارکستراسیون

Docker Swarm یک ابزار ارکستراسیون کانتینر بومی Docker است که در هسته Docker گنجانده شده است. این ابزار به شما امکان می‌دهد گروهی از ماشین‌های Docker را به یک کلاستر واحد (Swarm) تبدیل کنید و سپس سرویس‌ها را روی آن کلاستر استقرار دهید.

مزایای Docker Swarm:

  • سادگی: راه‌اندازی و مدیریت Swarm نسبتاً آسان است، به خصوص برای کاربرانی که با Docker آشنا هستند.
  • استفاده از فایل Compose: می‌توانید از همان فایل docker-compose.yml (با افزودن بخش deploy) برای استقرار سرویس‌ها در Swarm استفاده کنید (با دستور docker stack deploy).
  • مقیاس‌پذیری در کلاستر: امکان توزیع اینستنس‌های سرویس در چندین هاست.
  • توازن بار داخلی: Swarm یک Ingress Load Balancer داخلی دارد که درخواست‌ها را بین اینستنس‌های سرویس در کلاستر توزیع می‌کند.
  • تحمل خطا و خودبهبودی (Self-Healing): Swarm به طور مداوم وضعیت کلاستر را نظارت می‌کند و در صورت خرابی یک کانتینر یا هاست، به طور خودکار اینستنس‌های جایگزین را راه‌اندازی می‌کند.
  • به‌روزرسانی تدریجی: پشتیبانی از Rolling Updates برای به‌روزرسانی سرویس‌ها بدون داون‌تایم.

Docker Swarm یک گزینه عالی برای تیم‌هایی است که به دنبال یک راه‌حل ارکستراسیون نسبتاً ساده و با نگهداری کم برای محیط‌های تولیدی متوسط هستند.

Kubernetes: استاندارد صنعتی برای ارکستراسیون کانتینر

Kubernetes (K8s) یک پلتفرم متن‌باز و قدرتمند برای اتوماسیون استقرار، مقیاس‌گذاری و مدیریت برنامه‌های کانتینری است. Kubernetes به عنوان استاندارد صنعتی برای ارکستراسیون کانتینر شناخته می‌شود و قابلیت‌های بسیار گسترده‌تری نسبت به Docker Swarm ارائه می‌دهد.

قابلیت‌های کلیدی Kubernetes:

  • Pods: کوچکترین واحد قابل استقرار در Kubernetes، که می‌تواند شامل یک یا چند کانتینر باشد.
  • Deployments: مدیریت استقرار و به‌روزرسانی برنامه‌ها. Deploymentها مسئول حفظ تعداد مشخصی از Pods و انجام Rolling Updates هستند.
  • Services: فراهم کردن یک نقطه دسترسی پایدار برای گروهی از Pods، صرف‌نظر از اینکه کدام Pod در حال اجراست یا IP آن چیست.
  • Ingress: مدیریت دسترسی خارجی به سرویس‌ها در کلاستر، شامل توازن بار لایه 7، SSL Termination و Routing.
  • StatefulSets: مدیریت برنامه‌های با وضعیت (Stateful Applications) که نیاز به هویت پایدار (Persistent Identity) و ذخیره‌سازی پایدار (Persistent Storage) دارند.
  • Persistent Volumes (PVs) / Persistent Volume Claims (PVCs): راهکاری برای فراهم کردن ذخیره‌سازی پایدار و مدیریت شده برای Pods.
  • ConfigMaps / Secrets: مدیریت پیکربندی و اسرار به صورت امن و دینامیک.
  • Horizontal Pod Autoscaler (HPA): مقیاس‌گذاری خودکار Pods بر اساس معیارهای CPU، Memory یا معیارهای سفارشی.
  • Service Discovery و Load Balancing پیشرفته: Kubernetes دارای یک سیستم DNS داخلی و توازن بار داخلی پیچیده است.

Kubernetes یک پلتفرم بسیار پیچیده و قدرتمند است که منحنی یادگیری بالایی دارد، اما برای مدیریت برنامه‌های بسیار بزرگ، پیچیده و حیاتی در مقیاس تولید، قابلیت‌های بی‌نظیری را فراهم می‌کند. بسیاری از ارائه‌دهندگان سرویس ابری (مانند Google Kubernetes Engine, Azure Kubernetes Service, Amazon Elastic Kubernetes Service) خدمات مدیریت شده Kubernetes را ارائه می‌دهند که مدیریت آن را ساده‌تر می‌کند.

نتیجه‌گیری در مورد انتخاب ابزار ارکستراسیون

انتخاب بین Docker Compose، Docker Swarm و Kubernetes به نیازها و اندازه پروژه شما بستگی دارد:

  • Docker Compose: بهترین گزینه برای توسعه محلی، آزمایش و راه‌اندازی پروژه‌های کوچک غیرتولیدی روی یک سرور.
  • Docker Swarm: یک گزینه میانی مناسب برای محیط‌های تولیدی کوچک تا متوسط که به دنبال سادگی، مقیاس‌پذیری کلاستر و تحمل خطا هستند، بدون پیچیدگی‌های Kubernetes.
  • Kubernetes: بهترین انتخاب برای پروژه‌های بزرگ و پیچیده در مقیاس تولید که نیاز به قابلیت‌های ارکستراسیون جامع، خودکارسازی پیشرفته، و انعطاف‌پذیری بالا دارند.

بسیاری از توسعه‌دهندگان از Docker Compose در مرحله توسعه محلی استفاده می‌کنند و سپس همان فایل Compose را (با اصلاحات برای بخش deploy) برای استقرار در Docker Swarm یا با استفاده از ابزارهایی مانند Kompose برای تبدیل آن به مانیفست‌های Kubernetes استفاده می‌کنند. این رویکرد به شما امکان می‌دهد تا از مزایای سادگی Compose در توسعه بهره‌مند شوید، در حالی که برای محیط تولیدی به راهکارهای ارکستراسیون قوی‌تر مهاجرت کنید.

نتیجه‌گیری

مقیاس‌پذیری، ستون فقرات هر اپلیکیشن مدرنی است که قصد دارد در دنیای پرتقاضای امروز موفق شود. Docker Compose به عنوان یک ابزار قدرتمند در اکوسیستم Docker، نقش بسزایی در ساده‌سازی تعریف، راه‌اندازی و مدیریت برنامه‌های چند کانتینری ایفا می‌کند و قابلیت‌های اساسی برای مقیاس‌گذاری افقی سرویس‌ها را، به ویژه در محیط‌های توسعه و آزمایش محلی، فراهم می‌آورد.

همانطور که در این مقاله بررسی شد، با استفاده از دستور docker compose up --scale می‌توان به سادگی چندین اینستنس از یک سرویس را راه‌اندازی کرد. این قابلیت، همراه با ادغام با Reverse Proxyهایی مانند Nginx یا Traefik، امکان توزیع بار کارآمد را بین اینستنس‌های متعدد فراهم می‌آورد و به بهبود عملکرد و تحمل خطای اپلیکیشن کمک می‌کند. همچنین، بحث در مورد سرویس‌های با وضعیت و بی‌وضعیت، چالش‌های مقیاس‌گذاری داده‌های پایدار و راهکارهای متناسب با آن، بینش‌های مهمی را برای طراحی معماری‌های قوی‌تر ارائه داد.

در نهایت، درک محدودیت‌های Docker Compose برای محیط‌های تولیدی در مقیاس بزرگ و نیاز به ارکستراتورهای کانتینری پیشرفته‌تر مانند Docker Swarm و Kubernetes، برای هر مهندس نرم‌افزاری که در حوزه کانتینری‌سازی فعالیت می‌کند، ضروری است. Docker Compose گام اول و ایده‌آلی است، اما برای مواجهه با پیچیدگی‌های دنیای واقعی و تضمین پایداری و عملکرد در اوج بار، حرکت به سمت پلتفرم‌های ارکستراسیون جامع، اجتناب‌ناپذیر است.

با بهره‌گیری هوشمندانه از قابلیت‌های Docker Compose و در نظر گرفتن بهترین روش‌ها در طراحی سیستم، توسعه‌دهندگان می‌توانند سرویس‌هایی را ایجاد کنند که نه تنها از قدرت کانتینرها بهره‌مند می‌شوند، بلکه برای رشد و پاسخگویی به نیازهای متغیر کاربران نیز کاملاً آماده هستند. اکنون زمان آن است که دانش به‌دست‌آمده را به کار گیرید و سرویس‌های خود را برای مقیاس‌پذیری بهینه طراحی و پیاده‌سازی کنید.

“تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”

قیمت اصلی 2.290.000 ریال بود.قیمت فعلی 1.590.000 ریال است.

"تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"

"با شرکت در این دوره جامع و کاربردی، به راحتی مهارت‌های برنامه‌نویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر می‌سازد تا به سرعت الگوریتم‌های پیچیده را درک کرده و اپلیکیشن‌های هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفه‌ای و امکان دانلود و تماشای آنلاین."

ویژگی‌های کلیدی:

بدون نیاز به تجربه قبلی برنامه‌نویسی

زیرنویس فارسی با ترجمه حرفه‌ای

۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان