العودة إلى المدونة

بروكسي لهندسة الخدمات المصغرة: حماية واجهة برمجة التطبيقات، التوازن والأمان

دليل كامل لدمج البروكسي في بنية الخدمات المصغرة: حماية واجهة برمجة التطبيقات، توزيع الحمل، أمان الاتصال بين الخدمات وأمثلة على الإعداد.

📅١ رمضان ١٤٤٧ هـ
```html

تتطلب هندسة الخدمات المصغرة اتصالات موثوقة بين الخدمات، وحماية طلبات API الخارجية، وتوازن الحمل. تعمل خوادم البروكسي على حل هذه المشكلات من خلال العمل كوسيط بين الخدمات وواجهات برمجة التطبيقات الخارجية والعملاء. في هذا الدليل، سنستعرض كيفية دمج البروكسي بشكل صحيح في بنية الخدمات المصغرة، وأنواع البروكسي التي يجب استخدامها في سيناريوهات مختلفة، وكيفية إعداد اتصالات آمنة.

دور البروكسي في هندسة الخدمات المصغرة

في هندسة الخدمات المصغرة، تقوم خوادم البروكسي بعدة وظائف حيوية تختلف عن الاستخدام التقليدي للبروكسي للتخفي أو تجاوز الحجب. هنا، يصبح البروكسي جزءًا لا يتجزأ من البنية التحتية، حيث يوفر اتصالات موثوقة وآمنة بين مكونات النظام.

الأدوار الرئيسية للبروكسي في الخدمات المصغرة:

  • بوابة API — نقطة دخول واحدة لجميع طلبات العملاء، تقوم بتوجيهها إلى الخدمات المصغرة المناسبة، مخفيةً البنية الداخلية للنظام
  • بروكسي Sidecar — حاوية بروكسي تعمل بجانب كل خدمة (نمط شبكة الخدمات)، تت intercept جميع حركة المرور الواردة والصادرة
  • بروكسي عكسي — توزيع الحمل بين عدة نسخ من خدمة واحدة، وضمان الموثوقية
  • بروكسي أمامي — التحكم وحماية الطلبات الصادرة إلى واجهات برمجة التطبيقات الخارجية، إخفاء عناوين IP الداخلية للبنية التحتية
  • بروكسي الأمان — إنهاء SSL/TLS، المصادقة، التفويض، الحماية من هجمات DDoS وغيرها من الهجمات

يسمح البروكسي بتنفيذ أنماط معمارية مهمة: circuit breaker (فصل تلقائي للخدمات غير العاملة)، retry logic (محاولات متكررة عند الفشل)، rate limiting (تحديد معدل الطلبات)، request/response transformation (تحويل تنسيقات البيانات). كل هذا يجعل النظام أكثر مقاومة للفشل ويسهل إدارة بنية تحتية معقدة موزعة.

مهم: في هندسة الخدمات المصغرة، يعمل البروكسي على مستويين — كبوابة خارجية للعملاء (بوابة API) وكبروكسي داخلي بين الخدمات (شبكة الخدمات). كلا المستويين حيويين لأمان وموثوقية النظام.

أنواع البروكسي لسيناريوهات الاستخدام المختلفة

يعتمد اختيار نوع البروكسي على المهمة المحددة في هندسة الخدمات المصغرة. تتطلب السيناريوهات المختلفة خصائص مختلفة: السرعة، الموثوقية، التخفي أو التوزيع الجغرافي.

السيناريو نوع البروكسي لماذا
الاتصالات الداخلية بين الخدمات بروكسي HTTP/HTTPS (Envoy، NGINX) أقصى سرعة، زمن تأخير منخفض، دعم HTTP/2
طلبات إلى واجهات برمجة التطبيقات الخارجية مع حدود بروكسي سكنية تجاوز حدود المعدل، عناوين IP حقيقية للمستخدمين، خطر منخفض للحجب
تحليل البيانات من أجل التحليلات بروكسي مراكز البيانات سرعة عالية، تكلفة منخفضة، مناسبة للطلبات الجماعية
العمل مع واجهات برمجة التطبيقات المحمولة بروكسي محمولة محاكاة مستخدمين حقيقيين على الهواتف المحمولة، الوصول إلى واجهات برمجة التطبيقات الخاصة بالهواتف المحمولة فقط
توازن الحمل بروكسي عكسي (HAProxy، NGINX) توزيع حركة المرور، فحوصات الصحة، التبديل التلقائي عند الفشل
نظام موزع جغرافيًا بروكسي سكنية مع استهداف جغرافي الوصول إلى واجهات برمجة التطبيقات الإقليمية، الامتثال لمتطلبات توطين البيانات

للاستخدامات الداخلية بين الخدمات المصغرة، عادةً ما يتم استخدام حلول بروكسي متخصصة مثل Envoy Proxy أو NGINX، والتي تم تحسينها للزمن المنخفض والقدرة العالية. تدعم هذه الحلول البروتوكولات الحديثة (HTTP/2، gRPC) وتتكامل مع أنظمة شبكة الخدمات.

بالنسبة للعمل مع واجهات برمجة التطبيقات الخارجية، يعتمد الاختيار على متطلبات الخدمة المحددة. إذا كانت واجهة برمجة التطبيقات تحتوي على حدود صارمة أو تحجب الطلبات من عناوين IP لمراكز البيانات، فإن البروكسي السكنية تصبح ضرورية. لجمع البيانات بشكل جماعي، حيث تكون السرعة أكثر أهمية من التخفي، فإن بروكسي مراكز البيانات تكون مناسبة. البروكسي المحمولة ضرورية عند العمل مع واجهات برمجة التطبيقات التي تتحقق من نوع الجهاز أو تتطلب عناوين IP محمولة.

البروكسي كبوابة API: الحماية والتوجيه

بوابة API هي خادم بروكسي متخصص يعمل كنقطة دخول واحدة لجميع طلبات العملاء إلى نظام الخدمات المصغرة. بدلاً من أن يتوجه العملاء مباشرة إلى عشرات الخدمات المختلفة، يقومون بإرسال جميع الطلبات إلى عنوان واحد هو بوابة API، التي تقوم بتوجيهها إلى الخدمات المطلوبة.

الوظائف الرئيسية لبوابة API:

  • توجيه الطلبات — تحديد أي خدمة مصغرة يجب أن تعالج الطلب بناءً على URL، أو الرؤوس، أو معلمات أخرى
  • المصادقة والتفويض — التحقق من الرموز (JWT، OAuth)، إدارة الوصول إلى خدمات مختلفة
  • تحديد المعدل — تحديد عدد الطلبات من عميل واحد لحماية النظام من الحمل الزائد وهجمات DDoS
  • تجميع الردود — دمج البيانات من عدة خدمات في رد واحد للعميل
  • تحويل البروتوكولات — تحويل REST إلى gRPC، HTTP/1.1 إلى HTTP/2
  • التخزين المؤقت — حفظ البيانات المطلوبة بشكل متكرر لتقليل الحمل على الخدمات
  • التسجيل والمراقبة — جمع مركزي للقياسات وسجلات جميع الطلبات

الحلول الشائعة لبوابة API: Kong، Tyk، AWS API Gateway، Azure API Management، NGINX Plus، Traefik. يعتمد الاختيار على نطاق النظام، ومتطلبات الأداء، والمنصة السحابية المستخدمة.

// مثال على تكوين NGINX كبوابة API
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 البنية الداخلية للنظام عن العملاء الخارجيين. لا يعرف العملاء عدد الخدمات المصغرة الموجودة وكيف تتفاعل — يرون فقط API موحد. يسهل ذلك إدارة الإصدارات، ويسمح بتغيير الهيكل الداخلي دون التأثير على العملاء، ويحسن الأمان، حيث لا تتوفر الخدمات الداخلية مباشرة من الإنترنت.

الدمج مع شبكة الخدمات (Istio، Linkerd)

شبكة الخدمات هي طبقة بنية تحتية تدير الاتصالات بين الخدمات المصغرة باستخدام خوادم البروكسي، التي يتم نشرها بجانب كل خدمة (نمط Sidecar). على عكس بوابة API، التي تعالج فقط حركة المرور الخارجية، تتحكم شبكة الخدمات في جميع حركة المرور الداخلية بين الخدمات.

الحلول الأكثر شيوعًا لشبكة الخدمات هي Istio (تستخدم بروكسي Envoy كـ sidecar) وLinkerd (تستخدم بروكسي خفيف الوزن خاص بها). تقوم هذه الحلول تلقائيًا بنشر حاوية بروكسي بجانب كل pod في Kubernetes، مما يلتقط جميع حركة المرور الواردة والصادرة.

إمكانيات شبكة الخدمات عبر البروكسي:

  • Mutual TLS (mTLS) — تشفير تلقائي لجميع حركة المرور بين الخدمات مع مصادقة متبادلة
  • إدارة الحركة — إدارة التوجيه، نشرات الكاناري، اختبار A/B
  • قابلية المراقبة — جمع تلقائي للقياسات، والبيانات، والسجلات دون تغيير كود الخدمات
  • المرونة — كسر الدائرة، منطق إعادة المحاولة، إدارة الوقت المستغرق، حقن الأخطاء للاختبار
  • اكتشاف الخدمات — اكتشاف الخدمات تلقائيًا وتوازن الحمل
# مثال على تكوين 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

---
# كسر الدائرة لحماية من الفشل المتسلسل
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

تحل شبكة الخدمات مشكلة "المونوليث الموزع" — عندما يتم تكرار منطق التفاعل بين الخدمات (إعادة المحاولة، الوقت المستغرق، كسر الدائرة) في كود كل خدمة. بدلاً من ذلك، يتم نقل كل هذه المنطق إلى طبقة البروكسي، مما يبسط كود الخدمات ويضمن سلوكًا موحدًا للنظام بأكمله.

ميزة مهمة هي الشفافية الكاملة للحركة. تمر كل طلب بين الخدمات عبر بروكسي، يقوم بتسجيل القياسات: زمن الاستجابة، رموز الأخطاء، حجم الحمولة. يتم إرسال هذه البيانات تلقائيًا إلى أنظمة المراقبة (Prometheus، Grafana) وأنظمة التتبع (Jaeger، Zipkin)، مما يخلق صورة كاملة عن عمل النظام الموزع دون الحاجة إلى إضافة أدوات في كود كل خدمة.

حماية الطلبات إلى واجهات برمجة التطبيقات الخارجية عبر البروكسي

تتفاعل الخدمات المصغرة غالبًا مع واجهات برمجة التطبيقات الخارجية: أنظمة الدفع، خدمات تحديد المواقع، واجهات برمجة التطبيقات الخاصة بالشبكات الاجتماعية، مزودو البيانات. تؤدي الطلبات المباشرة إلى واجهات برمجة التطبيقات الخارجية إلى عدة مشاكل: الكشف عن عناوين IP الداخلية للبنية التحتية، خطر الحجب عند تجاوز حدود المعدل، عدم التحكم في حركة المرور الصادرة.

يساعد استخدام البروكسي للطلبات الصادرة في حل هذه المشاكل ويضيف إمكانيات إضافية:

  • إخفاء البنية التحتية — ترى واجهات برمجة التطبيقات الخارجية عناوين IP للبروكسي، وليس خوادمك
  • تجاوز حدود المعدل — تدوير عناوين IP لتوزيع الطلبات
  • التوزيع الجغرافي — الوصول إلى واجهات برمجة التطبيقات الإقليمية عبر بروكسي في البلدان المطلوبة
  • إدارة مركزية — نقطة تحكم واحدة لجميع الطلبات الصادرة
  • تخزين مؤقت للردود — تقليل عدد الطلبات إلى واجهات برمجة التطبيقات المكلفة
  • المراقبة والتسجيل — تتبع جميع الاتصالات بالخدمات الخارجية
// Python: إعداد البروكسي لطلبات إلى واجهات برمجة التطبيقات الخارجية
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):
        """طلب إلى واجهة برمجة التطبيقات للدفع عبر البروكسي"""
        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"خطأ في واجهة برمجة التطبيقات للدفع: {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())

للعمل مع واجهات برمجة التطبيقات الخارجية التي تحتوي على قيود صارمة أو تحجب الطلبات من عناوين IP لمراكز البيانات، تصبح البروكسي السكنية ضرورة. فهي توفر عناوين IP حقيقية لمستخدمي المنازل، مما يقلل من خطر الحجب ويسمح بتجاوز القيود الجغرافية.

// Node.js: بروكسي لواجهات برمجة التطبيقات الخارجية مع تدوير تلقائي
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 الخاص به، يضمن جلسات ثابتة
  • Weighted Round Robin — توزيع وفقًا لقوة الخوادم (الخوادم الأقوى تحصل على المزيد من الطلبات)
  • Random — اختيار خادم عشوائي، مناسب للخدمات غير الحالة
# تكوين 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 كل ثانيتين
    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

تعتبر فحوصات الصحة وظيفة حيوية للموثوقية. يقوم البروكسي بفحص توفر كل خادم بانتظام (عادةً عبر نقطة نهاية HTTP /health أو /ready) ويستبعد تلقائيًا الخوادم غير العاملة من مجموعة التوازن. عندما يستعيد الخادم نشاطه ويبدأ في الاستجابة لفحوصات الصحة، يعود تلقائيًا إلى المجموعة.

استراتيجيات الموثوقية عبر البروكسي:

  • فحوصات الصحة النشطة — يقوم البروكسي بالاستعلام عن الخوادم للتحقق من توفرها
  • فحوصات الصحة السلبية — يقوم البروكسي بتتبع الطلبات الحقيقية ويستبعد الخوادم عند تراكم الأخطاء
  • Kسر الدائرة — فصل مؤقت للخدمة المعيبة لمنع الفشل المتسلسل
  • تدهور لطيف — الانتقال إلى وضع تشغيل مبسط أو بيانات مخزنة مؤقتًا عند الفشل
  • التبديل إلى الاحتياطي — التبديل التلقائي إلى الخوادم أو المناطق الاحتياطية
// Python: تنفيذ كسر الدائرة للبروكسي إلى الخدمات الخارجية
from datetime import datetime, timedelta
from enum import Enum

class CircuitState(Enum):
    CLOSED = "مغلق"      # التشغيل الطبيعي
    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("كسر الدائرة: الانتقال إلى HALF_OPEN")
            else:
                raise Exception("كسر الدائرة مفتوح: الخدمة غير متاحة")
        
        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("كسر الدائرة: الاستعادة، الانتقال إلى 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"كسر الدائرة مفتوح: {self.failures} أخطاء متتالية")

# الاستخدام
breaker = CircuitBreaker(failure_threshold=3, timeout=30)

def call_external_service():
    # كودك لطلب إلى واجهة برمجة التطبيقات الخارجية عبر البروكسي
    pass

try:
    result = breaker.call(call_external_service)
except Exception as e:
    # منطق الاحتياطي: التخزين المؤقت، القيم الافتراضية، إلخ.
    print(f"الخدمة غير متاحة: {e}")

أمان الاتصالات بين الخدمات

في هندسة الخدمات المصغرة، توفر خوادم البروكسي عدة مستويات من الأمان: تشفير الحركة، مصادقة الخدمات، الحماية من الهجمات، وعزل القطاعات الشبكية. بدون إعداد الأمان الصحيح، يمكن أن يتم اعتراض أو تزوير الحركة الداخلية بين الخدمات.

الجوانب الرئيسية للأمان عبر البروكسي:

  • Mutual TLS (mTLS) — مصادقة ثنائية الاتجاه، حيث يتحقق كل من العميل والخادم من شهادات بعضهما البعض. تقوم شبكة الخدمات تلقائيًا بإعداد mTLS بين جميع الخدمات
  • إنهاء TLS — يقوم البروكسي بفك تشفير حركة HTTPS عند الحدود، والتحقق منها، وتمريرها إلى الخدمات عبر قناة آمنة
  • التحقق من JWT — التحقق من رموز الوصول على مستوى البروكسي، قبل أن تصل الطلبات إلى الخدمة
  • قائمة بيضاء لعناوين IP — تقييد الوصول إلى الخدمات فقط من عناوين IP المسموح بها
  • حماية DDoS — تحديد المعدل، حدود الاتصال، الحماية من هجمات SYN flood على مستوى البروكسي
  • WAF (جدار حماية تطبيقات الويب) — تصفية الطلبات الضارة، الحماية من 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
    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;
    }
}

تسهل شبكة الخدمات بشكل كبير إعداد الأمان، حيث تقوم تلقائيًا بإنشاء وتدوير الشهادات لـ mTLS، وتطبيق سياسات الوصول، وتشفير جميع الحركة بين الخدمات. على سبيل المثال، في Istio، يمكن تحديد سياسة بأن الخدمة "payment" يمكنها قبول الطلبات فقط من الخدمة "order"، وسيتم تطبيق ذلك تلقائيًا على مستوى البروكسي دون تغيير كود الخدمات.

مهم للإنتاج: استخدم دائمًا mTLS للاتصالات الداخلية بين الخدمات، حتى لو كانت في نفس الشبكة الخاصة. يحمي ذلك من هجمات man-in-the-middle ويضمن المصادقة على مستوى الخدمات، وليس فقط على مستوى الشبكة.

مراقبة وتسجيل حركة مرور البروكسي

توفر خوادم البروكسي فرصة فريدة للمراقبة المركزية لجميع الحركة في نظام الخدمات المصغرة. نظرًا لأن جميع الحركة تمر عبر البروكسي (سواء كانت خارجية عبر بوابة API أو داخلية عبر شبكة الخدمات)، فإنك تحصل على رؤية كاملة لعمل النظام دون الحاجة إلى إضافة أدوات في كل خدمة.

القياسات الرئيسية للمراقبة على مستوى البروكسي:

  • الزمن المستغرق (Latency) — زمن معالجة الطلب في كل مرحلة: البروكسي، الخدمة، واجهات برمجة التطبيقات الخارجية
  • السعة (Throughput) — عدد الطلبات في الثانية، حجم البيانات المنقولة
  • معدل الأخطاء — نسبة الأخطاء (4xx، 5xx)، أنواع الأخطاء، النقاط النهائية المعيبة
  • قياسات الاتصال — عدد الاتصالات النشطة، استخدام مجموعة الاتصالات
  • حالة كسر الدائرة — حالة كسر الدوائر لكل خدمة
  • قياسات SSL/TLS — حالة الشهادات، إصدارات البروتوكولات، أخطاء 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;

تتبع التوزيع — واحدة من أقوى إمكانيات المراقبة عبر البروكسي. يحصل كل طلب على معرف تتبع فريد، يضيفه البروكسي إلى الرؤوس وينقله عبر سلسلة الخدمات. تجمع أنظمة التتبع (Jaeger، Zipkin) المعلومات من جميع البروكسيات وتبني المسار الكامل للطلب عبر النظام، موضحةً كم من الوقت قضاه في كل خدمة.

// Node.js: إضافة تتبع إلى middleware البروكسي
const express = require('express');
const { v4: uuidv4 } = require('uuid');
const axios = require('axios');

const app = express();

// Middleware لإضافة معرف التتبع
app.use((req, res, next) => {
  // الحصول على معرف التتبع من الرأس أو إنشاء واحد جديد
  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 جديد لطلب downstream
      }
    });
    
    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 لسيناريوهات مختلفة: الاتصالات الداخلية، العمل مع واجهات برمجة التطبيقات الخارجية، توازن الحمل.

Python: خدمة مع بروكسي لواجهات برمجة التطبيقات الخارجية

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_client = ExternalAPIClient(proxy_pool)

@app.get("/data/{resource_id}")
async def get_external_data(resource_id: str):
    """نقطة النهاية التي تحصل على البيانات من واجهة برمجة التطبيقات الخارجية عبر البروكسي"""
    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": "صحي", "service": "external-api-proxy"}

# التشغيل: uvicorn main:app --host 0.0.0.0 --port 8000

Node.js: بوابة API مع توازن الحمل

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 },
    // المزيد من الخدمات
  ],
  orders: [
    { url: 'http://order-service-1:8002', healthy: true, activeConnections: 0 },
    { url: 'http://order-service-2:8002', healthy: true, activeConnections: 0 },
    // المزيد من الخدمات
  ],
};

// إعداد تحديد المعدل
const limiter = rateLimit({
  windowMs: 1 * 60 * 1000, // 1 دقيقة
  max: 100 // الحد الأقصى لعدد الطلبات لكل عميل
});

// نقطة النهاية لتوجيه الطلبات إلى خدمات المستخدمين
app.use('/api/users', limiter, async (req, res) => {
  const service = services.users.find(s => s.healthy);
  if (!service) return res.status(503).send('لا توجد خدمات متاحة');

  try {
    const response = await axios({
      method: req.method,
      url: `${service.url}${req.path}`,
      data: req.body,
      headers: { 'User-Agent': 'MyAPI/1.0' }
    });
    res.status(response.status).json(response.data);
  } catch (error) {
    res.status(500).send('خطأ في الاتصال بالخدمة');
  }
});

// نقطة النهاية لتوجيه الطلبات إلى خدمات الطلبات
app.use('/api/orders', limiter, async (req, res) => {
  const service = services.orders.find(s => s.healthy);
  if (!service) return res.status(503).send('لا توجد خدمات متاحة');

  try {
    const response = await axios({
      method: req.method,
      url: `${service.url}${req.path}`,
      data: req.body,
      headers: { 'User-Agent': 'MyAPI/1.0' }
    });
    res.status(response.status).json(response.data);
  } catch (error) {
    res.status(500).send('خطأ في الاتصال بالخدمة');
  }
});

// بدء التشغيل
app.listen(3000, () => {
  console.log('خادم API يعمل على http://localhost:3000');
});
```