بازگشت به وبلاگ

پروکسی برای معماری میکروسرویس: حفاظت از API، تعادل بار و امنیت

راهنمای کامل ادغام پروکسی در معماری میکروسرویس: حفاظت از API، تعادل بار، امنیت ارتباطات بین سرویس‌ها و مثال‌هایی از تنظیمات.

📅۲۹ بهمن ۱۴۰۴
```html

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

نقش پروکسی در معماری میکروسرویس‌ها

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

نقش‌های اصلی پروکسی در میکروسرویس‌ها:

  • API Gateway — نقطه ورودی واحد برای تمام درخواست‌های مشتری که آن‌ها را به میکروسرویس‌های مربوطه مسیریابی می‌کند و معماری داخلی سیستم را پنهان می‌کند
  • Sidecar Proxy — پروکسی کانتینری که در کنار هر سرویس کار می‌کند (الگوی Service Mesh) و تمام ترافیک ورودی و خروجی را ضبط می‌کند
  • Reverse Proxy — توزیع بار بین چندین نمونه از یک سرویس و تضمین مقاومت در برابر خرابی
  • Forward Proxy — کنترل و حفاظت از درخواست‌های خروجی به API‌های خارجی و پنهان‌سازی آدرس‌های IP داخلی زیرساخت
  • پروکسی امنیتی — خاتمه SSL/TLS، احراز هویت، مجوز، حفاظت در برابر حملات DDoS و دیگر حملات

پروکسی‌ها امکان پیاده‌سازی الگوهای معماری مهم را فراهم می‌کنند: circuit breaker (خاموش کردن خودکار سرویس‌های غیرعملکردی)، retry logic (تلاش‌های مجدد در صورت شکست)، rate limiting (محدود کردن فرکانس درخواست‌ها)، request/response transformation (تبدیل فرمت‌های داده). همه این‌ها سیستم را در برابر خرابی‌ها مقاوم‌تر می‌کند و مدیریت زیرساخت توزیع‌شده پیچیده را ساده‌تر می‌کند.

مهم: در معماری میکروسرویس‌ها، پروکسی‌ها در دو سطح کار می‌کنند — به عنوان دروازه خارجی برای مشتریان (API Gateway) و به عنوان پروکسی‌های داخلی بین سرویس‌ها (Service Mesh). هر دو سطح برای امنیت و قابلیت اطمینان سیستم حیاتی هستند.

انواع پروکسی برای سناریوهای مختلف استفاده

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

سناریو نوع پروکسی چرا
ارتباط داخلی سرویس‌ها پروکسی HTTP/HTTPS (Envoy، NGINX) بیشترین سرعت، تأخیر کم، پشتیبانی از HTTP/2
درخواست‌ها به API‌های خارجی با محدودیت‌ها پروکسی‌های مسکونی دور زدن محدودیت‌های نرخ، IP‌های واقعی کاربران، خطر کم مسدودیت
جمع‌آوری داده‌ها برای تحلیل پروکسی‌های دیتاسنتر سرعت بالا، هزینه کم، مناسب برای درخواست‌های انبوه
کار با API‌های موبایل پروکسی‌های موبایل شبیه‌سازی کاربران واقعی موبایل، دسترسی به API‌های مختص موبایل
تعادل بار پروکسی معکوس (HAProxy، NGINX) توزیع ترافیک، بررسی سلامت، سوئیچ خودکار در صورت خرابی
سیستم جغرافیایی توزیع‌شده پروکسی‌های مسکونی با هدف‌گذاری جغرافیایی دسترسی به API‌های منطقه‌ای، رعایت الزامات محلی‌سازی داده‌ها

برای ارتباط داخلی بین میکروسرویس‌ها معمولاً از راه‌حل‌های پروکسی تخصصی مانند Envoy Proxy یا NGINX استفاده می‌شود که برای تأخیر کم و ظرفیت بالا بهینه‌سازی شده‌اند. آن‌ها پروتکل‌های مدرن (HTTP/2، gRPC) را پشتیبانی می‌کنند و با سیستم‌های Service Mesh ادغام می‌شوند.

برای کار با API‌های خارجی، انتخاب بستگی به الزامات سرویس خاص دارد. اگر API دارای محدودیت‌های سختگیرانه باشد یا درخواست‌ها را از IP‌های دیتاسنتر مسدود کند، پروکسی‌های مسکونی ضروری هستند. برای جمع‌آوری داده‌های انبوه، جایی که سرعت مهم‌تر از ناشناس‌سازی است، پروکسی‌های دیتاسنتر مناسب هستند. پروکسی‌های موبایل در زمان کار با API‌هایی که نوع دستگاه را بررسی می‌کنند یا به آدرس‌های IP موبایل نیاز دارند، ضروری هستند.

پروکسی به عنوان API Gateway: حفاظت و مسیریابی

API Gateway یک سرور پروکسی تخصصی است که به عنوان نقطه ورودی واحد برای تمام درخواست‌های مشتری به سیستم میکروسرویس عمل می‌کند. به جای اینکه مشتریان به طور مستقیم به ده‌ها سرویس مختلف مراجعه کنند، آن‌ها تمام درخواست‌ها را به یک آدرس API Gateway ارسال می‌کنند که آن‌ها را به سرویس‌های مورد نیاز مسیریابی می‌کند.

عملکردهای اصلی API Gateway:

  • مسیریابی درخواست‌ها — تعیین اینکه کدام میکروسرویس باید درخواست را بر اساس URL، هدرها یا سایر پارامترها پردازش کند
  • احراز هویت و مجوز — بررسی توکن‌ها (JWT، OAuth)، مدیریت دسترسی به سرویس‌های مختلف
  • محدودیت نرخ — محدود کردن تعداد درخواست‌ها از یک مشتری برای حفاظت در برابر بار اضافی و DDoS
  • ادغام پاسخ‌ها — ترکیب داده‌ها از چندین سرویس در یک پاسخ به مشتری
  • تبدیل پروتکل‌ها — تبدیل REST به gRPC، HTTP/1.1 به HTTP/2
  • کش کردن — ذخیره‌سازی داده‌های پر درخواست برای کاهش بار روی سرویس‌ها
  • لاگ‌برداری و نظارت — جمع‌آوری مرکزی متریک‌ها و لاگ‌های تمام درخواست‌ها

راه‌حل‌های محبوب برای API Gateway: Kong، Tyk، AWS API Gateway، Azure API Management، NGINX Plus، Traefik. انتخاب بستگی به مقیاس سیستم، الزامات عملکرد و پلتفرم ابری مورد استفاده دارد.

// مثال پیکربندی NGINX به عنوان API Gateway
upstream auth_service {
    server auth:8001;
}

upstream user_service {
    server user:8002;
}

upstream order_service {
    server order:8003;
}

server {
    listen 80;
    server_name api.example.com;

    # محدودیت نرخ درخواست‌ها
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

    location /api/auth/ {
        limit_req zone=api_limit burst=20;
        proxy_pass http://auth_service/;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /api/users/ {
        # بررسی توکن قبل از پروکسی
        auth_request /auth/verify;
        proxy_pass http://user_service/;
    }

    location /api/orders/ {
        auth_request /auth/verify;
        proxy_pass http://order_service/;
    }

    # نقطه داخلی برای بررسی توکن‌ها
    location = /auth/verify {
        internal;
        proxy_pass http://auth_service/verify;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
    }
}

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

ادغام با Service Mesh (Istio، Linkerd)

Service Mesh یک لایه زیرساختی است که ارتباطات بین میکروسرویس‌ها را با استفاده از سرورهای پروکسی که در کنار هر سرویس مستقر شده‌اند (الگوی Sidecar) مدیریت می‌کند. بر خلاف API Gateway که فقط ترافیک خارجی را پردازش می‌کند، Service Mesh تمام ترافیک داخلی بین سرویس‌ها را کنترل می‌کند.

محبوب‌ترین راه‌حل‌های Service Mesh — Istio (که از Envoy Proxy به عنوان sidecar استفاده می‌کند) و Linkerd (که از پروکسی سبک خود استفاده می‌کند). آن‌ها به طور خودکار پروکسی کانتینر را در کنار هر pod در Kubernetes مستقر می‌کنند و تمام ترافیک ورودی و خروجی را ضبط می‌کنند.

قابلیت‌های Service Mesh از طریق پروکسی:

  • Mutual TLS (mTLS) — رمزگذاری خودکار تمام ترافیک بین سرویس‌ها با احراز هویت متقابل
  • مدیریت ترافیک — مدیریت مسیریابی، استقرارهای کاناری، تست A/B
  • قابلیت مشاهده — جمع‌آوری خودکار متریک‌ها، ردیابی‌ها و لاگ‌ها بدون تغییر کد سرویس‌ها
  • مقاومت — circuit breaking، retry logic، مدیریت زمان‌سنج، تزریق خطا برای تست
  • کشف سرویس — کشف خودکار سرویس‌ها و تعادل بار
# مثال پیکربندی Istio VirtualService برای مسیریابی
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: user-service
spec:
  hosts:
  - user-service
  http:
  - match:
    - headers:
        version:
          exact: "v2"
    route:
    - destination:
        host: user-service
        subset: v2
  - route:
    - destination:
        host: user-service
        subset: v1
      weight: 90
    - destination:
        host: user-service
        subset: v2
      weight: 10  # استقرار کاناری: 10% ترافیک به v2

---
# Circuit Breaker برای حفاظت در برابر خرابی‌های زنجیره‌ای
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: user-service
spec:
  host: user-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        http1MaxPendingRequests: 50
        maxRequestsPerConnection: 2
    outlierDetection:
      consecutiveErrors: 5
      interval: 30s
      baseEjectionTime: 30s
      maxEjectionPercent: 50

Service Mesh مشکل "مونولیت توزیع‌شده" را حل می‌کند — زمانی که منطق تعامل بین سرویس‌ها (retry، timeout، circuit breaking) در کد هر سرویس تکرار می‌شود. در عوض، تمام این منطق به لایه پروکسی منتقل می‌شود که کد سرویس‌ها را ساده‌تر می‌کند و رفتار یکسانی را برای کل سیستم فراهم می‌کند.

مزیت مهم — شفافیت کامل ترافیک. هر درخواست بین سرویس‌ها از طریق پروکسی عبور می‌کند که متریک‌ها را لاگ‌برداری می‌کند: زمان پاسخ، کدهای خطا، اندازه payload. این داده‌ها به طور خودکار به سیستم‌های نظارت (Prometheus، Grafana) و ردیابی (Jaeger، Zipkin) ارسال می‌شوند و تصویر کاملی از عملکرد سیستم توزیع‌شده بدون نیاز به افزودن ابزار به کد هر سرویس ایجاد می‌کنند.

حفاظت از درخواست‌ها به API‌های خارجی از طریق پروکسی

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

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

  • پنهان‌سازی زیرساخت — API‌های خارجی آدرس‌های IP پروکسی را می‌بینند، نه سرورهای شما
  • دور زدن محدودیت‌های نرخ — چرخش آدرس‌های IP برای توزیع درخواست‌ها
  • توزیع جغرافیایی — دسترسی به API‌های منطقه‌ای از طریق پروکسی در کشورهای مورد نیاز
  • مدیریت متمرکز — نقطه کنترل واحد برای تمام درخواست‌های خروجی
  • کش کردن پاسخ‌ها — کاهش تعداد درخواست‌ها به API‌های پرهزینه
  • نظارت و لاگ‌برداری — پیگیری تمام درخواست‌ها به سرویس‌های خارجی
// Python: تنظیم پروکسی برای درخواست‌ها به API‌های خارجی
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

class ExternalAPIClient:
    def __init__(self, proxy_url, proxy_rotation=False):
        self.session = requests.Session()
        
        # تنظیم پروکسی
        self.proxies = {
            'http': proxy_url,
            'https': proxy_url
        }
        
        # منطق تلاش مجدد برای مقاومت
        retry_strategy = Retry(
            total=3,
            backoff_factor=1,
            status_forcelist=[429, 500, 502, 503, 504],
            allowed_methods=["GET", "POST"]
        )
        adapter = HTTPAdapter(max_retries=retry_strategy)
        self.session.mount("http://", adapter)
        self.session.mount("https://", adapter)
    
    def call_payment_api(self, data):
        """درخواست به API پرداخت از طریق پروکسی"""
        try:
            response = self.session.post(
                'https://api.payment-provider.com/charge',
                json=data,
                proxies=self.proxies,
                timeout=10,
                headers={'User-Agent': 'MyService/1.0'}
            )
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            # لاگ‌برداری خطا
            print(f"خطای API پرداخت: {e}")
            raise

# استفاده با استخر پروکسی برای چرخش
class ProxyPool:
    def __init__(self, proxy_list):
        self.proxies = proxy_list
        self.current = 0
    
    def get_next(self):
        proxy = self.proxies[self.current]
        self.current = (self.current + 1) % len(self.proxies)
        return proxy

# راه‌اندازی
proxy_pool = ProxyPool([
    'http://user:pass@proxy1.example.com:8080',
    'http://user:pass@proxy2.example.com:8080',
    'http://user:pass@proxy3.example.com:8080'
])

# برای هر درخواست از پروکسی بعدی استفاده کنید
client = ExternalAPIClient(proxy_pool.get_next())

برای کار با API‌های خارجی که محدودیت‌های سختگیرانه دارند یا درخواست‌ها را از IP‌های دیتاسنتر مسدود می‌کنند، پروکسی‌های مسکونی ضروری می‌شوند. آن‌ها آدرس‌های IP واقعی کاربران خانگی را ارائه می‌دهند که خطر مسدودیت را کاهش می‌دهد و امکان دور زدن محدودیت‌های جغرافیایی را فراهم می‌کند.

// Node.js: پروکسی برای API‌های خارجی با چرخش خودکار
const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');

class ExternalAPIService {
  constructor(proxyList) {
    this.proxyList = proxyList;
    this.currentProxyIndex = 0;
    this.requestCounts = new Map(); // شمارش درخواست‌ها برای محدودیت نرخ
  }

  getNextProxy() {
    const proxy = this.proxyList[this.currentProxyIndex];
    this.currentProxyIndex = (this.currentProxyIndex + 1) % this.proxyList.length;
    return proxy;
  }

  async callAPI(endpoint, data, options = {}) {
    const proxyUrl = this.getNextProxy();
    const agent = new HttpsProxyAgent(proxyUrl);

    // محدودیت نرخ: حداکثر 100 درخواست در دقیقه به پروکسی
    const proxyKey = proxyUrl;
    const now = Date.now();
    const count = this.requestCounts.get(proxyKey) || { count: 0, resetTime: now + 60000 };
    
    if (count.count >= 100 && now < count.resetTime) {
      // سوئیچ به پروکسی بعدی
      return this.callAPI(endpoint, data, options);
    }

    try {
      const response = await axios({
        method: options.method || 'POST',
        url: endpoint,
        data: data,
        httpsAgent: agent,
        timeout: options.timeout || 10000,
        headers: {
          'User-Agent': 'Mozilla/5.0 (compatible; MyService/1.0)',
          ...options.headers
        }
      });

      // به‌روزرسانی شمارنده
      if (now >= count.resetTime) {
        this.requestCounts.set(proxyKey, { count: 1, resetTime: now + 60000 });
      } else {
        count.count++;
      }

      return response.data;
    } catch (error) {
      if (error.response?.status === 429) {
        // محدودیت نرخ تجاوز شده - سوئیچ به پروکسی دیگر
        console.log(`محدودیت نرخ در ${proxyUrl}، سوئیچ به پروکسی`);
        return this.callAPI(endpoint, data, options);
      }
      throw error;
    }
  }
}

// استفاده
const apiService = new ExternalAPIService([
  'http://user:pass@proxy1.example.com:8080',
  'http://user:pass@proxy2.example.com:8080'
]);

module.exports = apiService;

تعادل بار و مقاومت در برابر خرابی

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

الگوریتم‌های اصلی تعادل بار:

  • Round Robin — ارسال نوبتی درخواست‌ها به هر سرور در لیست، ساده و مؤثر برای سرورهای همگن
  • Least Connections — ارسال درخواست به سرور با کمترین تعداد اتصالات فعال، مناسب برای درخواست‌های طولانی
  • IP Hash — پیوند مشتری به سرور خاص بر اساس IP او، تضمین‌کننده sticky sessions
  • Weighted Round Robin — توزیع با توجه به قدرت سرورها (سرورهای قوی‌تر درخواست‌های بیشتری دریافت می‌کنند)
  • Random — انتخاب تصادفی سرور، مناسب برای سرویس‌های stateless
# پیکربندی HAProxy برای تعادل بار با بررسی سلامت
global
    maxconn 4096
    log stdout format raw local0

defaults
    mode http
    timeout connect 5s
    timeout client 50s
    timeout server 50s
    option httplog

frontend api_frontend
    bind *:80
    default_backend api_servers

backend api_servers
    balance roundrobin
    
    # بررسی سلامت: بررسی /health هر 2 ثانیه
    option httpchk GET /health
    http-check expect status 200
    
    # منطق تلاش مجدد
    retries 3
    option redispatch
    
    # سرورها با وزن‌ها (server3 دو برابر قدرت بیشتری دارد)
    server server1 10.0.1.10:8080 check weight 1 maxconn 500
    server server2 10.0.1.11:8080 check weight 1 maxconn 500
    server server3 10.0.1.12:8080 check weight 2 maxconn 1000
    
    # سرور پشتیبان (فقط زمانی که سرورهای اصلی در دسترس نیستند استفاده می‌شود)
    server backup1 10.0.2.10:8080 check backup

بررسی سلامت — یک عملکرد حیاتی برای مقاومت در برابر خرابی است. پروکسی به طور منظم در دسترس بودن هر سرور را بررسی می‌کند (معمولاً از طریق endpoint HTTP /health یا /ready) و به طور خودکار سرورهای غیرعملکردی را از استخر تعادل بار حذف می‌کند. زمانی که سرور دوباره فعال می‌شود و به بررسی‌های سلامت پاسخ می‌دهد، به طور خودکار به استخر باز می‌گردد.

استراتژی‌های مقاومت در برابر خرابی از طریق پروکسی:

  • Active Health Checks — پروکسی به طور فعال سرورها را برای بررسی در دسترس بودن آن‌ها بررسی می‌کند
  • Passive Health Checks — پروکسی درخواست‌های واقعی را پیگیری می‌کند و سرورها را در صورت انباشت خطاها حذف می‌کند
  • Circuit Breaker — خاموش کردن موقت سرویس مشکل‌دار برای جلوگیری از خرابی‌های زنجیره‌ای
  • Graceful Degradation — سوئیچ به حالت کار ساده یا داده‌های کش شده در صورت خرابی
  • Failover to Backup — سوئیچ خودکار به سرورهای پشتیبان یا مناطق
// Python: پیاده‌سازی Circuit Breaker برای پروکسی به سرویس‌های خارجی
from datetime import datetime, timedelta
from enum import Enum

class CircuitState(Enum):
    CLOSED = "closed"      # کارکرد عادی
    OPEN = "open"          # سرویس در دسترس نیست، درخواست‌ها مسدود می‌شوند
    HALF_OPEN = "half_open"  # حالت آزمایشی پس از بازیابی

class CircuitBreaker:
    def __init__(self, failure_threshold=5, timeout=60, success_threshold=2):
        self.failure_threshold = failure_threshold  # خطاها قبل از باز شدن
        self.timeout = timeout  # ثانیه‌ها قبل از تلاش برای بازیابی
        self.success_threshold = success_threshold  # موفقیت‌ها برای بسته شدن
        
        self.state = CircuitState.CLOSED
        self.failures = 0
        self.successes = 0
        self.last_failure_time = None
    
    def call(self, func, *args, **kwargs):
        if self.state == CircuitState.OPEN:
            if datetime.now() - self.last_failure_time > timedelta(seconds=self.timeout):
                self.state = CircuitState.HALF_OPEN
                print("Circuit breaker: انتقال به HALF_OPEN")
            else:
                raise Exception("Circuit breaker OPEN: سرویس در دسترس نیست")
        
        try:
            result = func(*args, **kwargs)
            self._on_success()
            return result
        except Exception as e:
            self._on_failure()
            raise e
    
    def _on_success(self):
        self.failures = 0
        if self.state == CircuitState.HALF_OPEN:
            self.successes += 1
            if self.successes >= self.success_threshold:
                self.state = CircuitState.CLOSED
                self.successes = 0
                print("Circuit breaker: بازیابی، انتقال به CLOSED")
    
    def _on_failure(self):
        self.failures += 1
        self.last_failure_time = datetime.now()
        if self.failures >= self.failure_threshold:
            self.state = CircuitState.OPEN
            print(f"Circuit breaker OPEN: {self.failures} خطا به طور متوالی")

# استفاده
breaker = CircuitBreaker(failure_threshold=3, timeout=30)

def call_external_service():
    # کد شما برای درخواست به API خارجی از طریق پروکسی
    pass

try:
    result = breaker.call(call_external_service)
except Exception as e:
    # منطق پشتیبان: کش، مقادیر پیش‌فرض و غیره
    print(f"سرویس در دسترس نیست: {e}")

امنیت ارتباطات بین سرویس‌ها

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

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

  • Mutual TLS (mTLS) — احراز هویت دوطرفه، زمانی که هم مشتری و هم سرور گواهی‌های یکدیگر را بررسی می‌کنند. Service Mesh به طور خودکار mTLS را بین تمام سرویس‌ها تنظیم می‌کند
  • TLS Termination — پروکسی ترافیک HTTPS را در مرز رمزگشایی می‌کند، آن را بررسی می‌کند و به سرویس‌ها از طریق کانال امن منتقل می‌کند
  • JWT Validation — بررسی توکن‌های دسترسی در سطح پروکسی، قبل از اینکه درخواست به سرویس برسد
  • IP Whitelisting — محدود کردن دسترسی به سرویس‌ها فقط از آدرس‌های IP مجاز
  • DDoS Protection — محدودیت نرخ، محدودیت اتصالات، حفاظت در برابر حملات SYN flood در سطح پروکسی
  • WAF (Web Application Firewall) — فیلتر کردن درخواست‌های مخرب، حفاظت در برابر SQL injection، XSS
# پیکربندی NGINX با SSL/TLS و امنیت
server {
    listen 443 ssl http2;
    server_name api.internal.example.com;

    # گواهی‌های SSL
    ssl_certificate /etc/nginx/certs/api.crt;
    ssl_certificate_key /etc/nginx/certs/api.key;
    
    # پروتکل‌ها و رمزهای مدرن
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    
    # گواهی مشتری برای mTLS
    ssl_client_certificate /etc/nginx/certs/ca.crt;
    ssl_verify_client on;
    
    # هدرهای ایمن
    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    
    # محدودیت نرخ
    limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
    limit_req zone=api burst=200 nodelay;
    
    # محدودیت اتصالات
    limit_conn_zone $binary_remote_addr zone=addr:10m;
    limit_conn addr 10;
    
    # IP whitelisting
    allow 10.0.0.0/8;      # شبکه داخلی
    allow 172.16.0.0/12;   # VPC
    deny all;
    
    location / {
        # بررسی توکن JWT
        auth_jwt "API محدود شده";
        auth_jwt_key_file /etc/nginx/jwt_key.json;
        
        proxy_pass http://backend_service;
        
        # انتقال اطلاعات درباره گواهی مشتری
        proxy_set_header X-Client-DN $ssl_client_s_dn;
        proxy_set_header X-Client-Verify $ssl_client_verify;
    }
}

Service Mesh به طور قابل توجهی تنظیم امنیت را ساده می‌کند و به طور خودکار گواهی‌ها را برای mTLS تولید و چرخش می‌کند، سیاست‌های دسترسی را اعمال می‌کند و تمام ترافیک بین سرویس‌ها را رمزگذاری می‌کند. به عنوان مثال، در Istio می‌توان سیاستی تعیین کرد که سرویس "payment" فقط می‌تواند درخواست‌ها را از سرویس "order" دریافت کند و این به طور خودکار در سطح پروکسی بدون تغییر کد سرویس‌ها اعمال می‌شود.

مهم برای تولید: همیشه از mTLS برای ارتباط داخلی سرویس‌ها استفاده کنید، حتی اگر آن‌ها در یک شبکه خصوصی باشند. این کار از حملات نوع man-in-the-middle محافظت می‌کند و احراز هویت در سطح سرویس‌ها را تضمین می‌کند، نه فقط در سطح شبکه.

نظارت و لاگ‌برداری ترافیک پروکسی

سرورهای پروکسی فرصت منحصر به فردی برای نظارت متمرکز بر تمام ترافیک در سیستم میکروسرویس فراهم می‌کنند. از آنجا که تمام ترافیک از طریق پروکسی عبور می‌کند (هم خارجی از طریق API Gateway و هم داخلی از طریق Service Mesh)، شما دید کامل از عملکرد سیستم بدون نیاز به ابزارگذاری هر سرویس دارید.

متریک‌های کلیدی برای نظارت در سطح پروکسی:

  • Latency (تاخیر) — زمان پردازش درخواست در هر مرحله: پروکسی، سرویس، API‌های خارجی
  • Throughput (ظرفیت) — تعداد درخواست‌ها در ثانیه، حجم داده‌های منتقل شده
  • Error Rate — درصد خطاها (4xx، 5xx)، نوع خطاها، endpoints مشکل‌دار
  • Connection Metrics — تعداد اتصالات فعال، استفاده از connection pool
  • Circuit Breaker State — وضعیت circuit breakers برای هر سرویس
  • SSL/TLS Metrics — وضعیت گواهی‌ها، نسخه‌های پروتکل‌ها، خطاهای handshake
# پیکربندی NGINX برای صادرات متریک‌ها به Prometheus
server {
    listen 9113;
    location /metrics {
        stub_status;
        access_log off;
        allow 10.0.0.0/8;  # فقط برای سرور Prometheus
        deny all;
    }
}

# لاگ‌برداری در فرمت JSON برای لاگ‌برداری ساختاری
log_format json_combined escape=json
  '{'
    '"time_local":"$time_local",'
    '"remote_addr":"$remote_addr",'
    '"request":"$request",'
    '"status": "$status",'
    '"body_bytes_sent":"$body_bytes_sent",'
    '"request_time":"$request_time",'
    '"upstream_response_time":"$upstream_response_time",'
    '"upstream_addr":"$upstream_addr",'
    '"http_referrer":"$http_referer",'
    '"http_user_agent":"$http_user_agent",'
    '"http_x_forwarded_for":"$http_x_forwarded_for"'
  '}';

access_log /var/log/nginx/access.log json_combined;

ردیابی توزیع‌شده — یکی از قدرتمندترین قابلیت‌های نظارت از طریق پروکسی است. هر درخواست یک trace ID منحصر به فرد دریافت می‌کند که پروکسی آن را به هدرها اضافه می‌کند و به زنجیره سرویس‌ها منتقل می‌کند. سیستم‌های ردیابی (Jaeger، Zipkin) اطلاعات را از تمام پروکسی‌ها جمع‌آوری می‌کنند و مسیر کامل درخواست را در سیستم ترسیم می‌کنند و نشان می‌دهند که چقدر زمان در هر سرویس صرف شده است.

// Node.js: افزودن ردیابی به middleware پروکسی
const express = require('express');
const { v4: uuidv4 } = require('uuid');
const axios = require('axios');

const app = express();

// Middleware برای افزودن trace ID
app.use((req, res, next) => {
  // دریافت trace ID از هدر یا ایجاد یک جدید
  const traceId = req.headers['x-trace-id'] || uuidv4();
  const spanId = uuidv4();
  
  // افزودن به هدرها برای انتقال به جلو
  req.traceId = traceId;
  req.spanId = spanId;
  res.setHeader('x-trace-id', traceId);
  
  // لاگ‌برداری شروع پردازش
  const startTime = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - startTime;
    
    // لاگ ساختاری برای تحلیل
    console.log(JSON.stringify({
      timestamp: new Date().toISOString(),
      traceId: traceId,
      spanId: spanId,
      method: req.method,
      path: req.path,
      status: res.statusCode,
      duration: duration,
      userAgent: req.headers['user-agent'],
      ip: req.ip
    }));
  });
  
  next();
});

// نقطه پروکسی با انتقال هدرهای ردیابی
app.all('/api/*', async (req, res) => {
  const targetService = determineTargetService(req.path);
  
  try {
    const response = await axios({
      method: req.method,
      url: `http://${targetService}${req.path}`,
      data: req.body,
      headers: {
        ...req.headers,
        'x-trace-id': req.traceId,
        'x-parent-span-id': req.spanId,
        'x-span-id': uuidv4()  // span جدید برای درخواست پایین‌دستی
      }
    });
    
    res.status(response.status).json(response.data);
  } catch (error) {
    console.error(JSON.stringify({
      traceId: req.traceId,
      error: error.message,
      service: targetService
    }));
    res.status(500).json({ error: 'سرویس در دسترس نیست' });
  }
});

function determineTargetService(path) {
  if (path.startsWith('/api/users')) return 'user-service:8080';
  if (path.startsWith('/api/orders')) return 'order-service:8080';
  return 'default-service:8080';
}

app.listen(3000);

هشداردهی بر اساس متریک‌های پروکسی امکان شناسایی سریع مشکلات را فراهم می‌کند. به عنوان مثال، می‌توان هشدارهایی را برای: افزایش ناگهانی تاخیر (ممکن است یکی از سرویس‌ها دچار مشکل شود)، افزایش نرخ خطا بالاتر از آستانه (مشکلات با کد یا وابستگی‌ها)، تغییر الگوهای ترافیک (احتمال حمله DDoS یا بار ویروسی) تنظیم کرد.

مثال‌های پیاده‌سازی در Python و Node.js

بیایید به مثال‌های عملی ادغام پروکسی در میکروسرویس‌ها در Python و Node.js برای سناریوهای مختلف: ارتباط داخلی، کار با API‌های خارجی، تعادل بار بپردازیم.

Python: سرویس با پروکسی برای API‌های خارجی

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import httpx
import asyncio
from typing import List, Optional
import logging

app = FastAPI()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class ProxyConfig(BaseModel):
    url: str
    max_requests_per_minute: int = 60

class ProxyPool:
    def __init__(self, proxies: List[ProxyConfig]):
        self.proxies = proxies
        self.current_index = 0
        self.request_counts = {p.url: 0 for p in proxies}
        self.reset_time = asyncio.get_event_loop().time() + 60
    
    async def get_next_proxy(self) -> str:
        # بازنشانی شمارش‌ها هر دقیقه
        current_time = asyncio.get_event_loop().time()
        if current_time >= self.reset_time:
            self.request_counts = {p.url: 0 for p in self.proxies}
            self.reset_time = current_time + 60
        
        # پیدا کردن پروکسی با درخواست‌های در دسترس
        for _ in range(len(self.proxies)):
            proxy = self.proxies[self.current_index]
            self.current_index = (self.current_index + 1) % len(self.proxies)
            
            if self.request_counts[proxy.url] < proxy.max_requests_per_minute:
                self.request_counts[proxy.url] += 1
                return proxy.url
        
        # همه پروکسی‌ها محدودیت را تمام کرده‌اند
        raise HTTPException(status_code=429, detail="همه پروکسی‌ها محدود شده‌اند")

# راه‌اندازی استخر پروکسی
proxy_pool = ProxyPool([
    ProxyConfig(url="http://user:pass@proxy1.example.com:8080", max_requests_per_minute=100),
    ProxyConfig(url="http://user:pass@proxy2.example.com:8080", max_requests_per_minute=100),
    ProxyConfig(url="http://user:pass@proxy3.example.com:8080", max_requests_per_minute=100)
])

class ExternalAPIClient:
    def __init__(self, proxy_pool: ProxyPool):
        self.proxy_pool = proxy_pool
    
    async def fetch_data(self, endpoint: str, params: dict = None) -> dict:
        proxy_url = await self.proxy_pool.get_next_proxy()
        
        async with httpx.AsyncClient(proxies={"http://": proxy_url, "https://": proxy_url}) as client:
            try:
                response = await client.get(
                    endpoint,
                    params=params,
                    timeout=10.0,
                    headers={"User-Agent": "MyMicroservice/1.0"}
                )
                response.raise_for_status()
                
                logger.info(f"با موفقیت از {endpoint} از طریق {proxy_url} دریافت شد")
                return response.json()
            
            except httpx.HTTPStatusError as e:
                logger.error(f"خطای HTTP {e.response.status_code} از {endpoint}")
                raise HTTPException(status_code=e.response.status_code, detail=str(e))
            
            except httpx.RequestError as e:
                logger.error(f"خطای درخواست به {endpoint}: {e}")
                raise HTTPException(status_code=503, detail="API خارجی در دسترس نیست")

api_client = ExternalAPIClient(proxy_pool)

@app.get("/data/{resource_id}")
async def get_external_data(resource_id: str):
    """نقطه‌ای که داده‌ها را از API خارجی از طریق پروکسی دریافت می‌کند"""
    external_endpoint = f"https://api.external-service.com/v1/resources/{resource_id}"
    
    try:
        data = await api_client.fetch_data(external_endpoint)
        return {"status": "success", "data": data}
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"خطای غیرمنتظره: {e}")
        raise HTTPException(status_code=500, detail="خطای داخلی سرور")

@app.get("/health")
async def health_check():
    return {"status": "healthy", "service": "external-api-proxy"}

# راه‌اندازی: uvicorn main:app --host 0.0.0.0 --port 8000

Node.js: API Gateway با تعادل بار

const express = require('express');
const axios = require('axios');
const rateLimit = require('express-rate-limit');

const app = express();
app.use(express.json());

// پیکربندی میکروسرویس‌ها
const services = {
  users: [
    { url: 'http://user-service-1:8001', healthy: true, activeConnections: 0 },
    { url: 'http://user-service-2:8001', healthy: true, activeConnections: 0 },
    // ادامه...
  ],
  // ادامه...
};

// ادامه...
```