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

gRPC عبر البروكسي في هندسة الخدمات المصغرة: الدليل الشامل لإعداد نفق HTTP/2

دليل مفصل لإعداد حركة gRPC عبر خوادم البروكسي في بنية الخدمات المصغرة - من نفق HTTP/2 إلى توزيع الحمل وإنهاء TLS.

📅٢٤ صفر ١٤٤٨ هـ
```html

gRPC هو إطار عمل RPC عالي الأداء من Google يعمل فوق HTTP/2 ويصبح معياراً فعلياً للتفاعل بين الخدمات. ولكن بمجرد أن تحاول تمرير حركة gRPC عبر وكيل، تظهر المشاكل على الفور: معظم خوادم الوكلاء التقليدية لا تعرف كيفية التعامل مع HTTP/2 والاتصالات المستمرة طويلة الأمد. في هذه المقالة، سنستعرض كيفية إعداد gRPC بشكل صحيح عبر الوكيل — من اختيار البنية إلى تكوينات Nginx وEnvoy وHAProxy المحددة.

لماذا gRPC لا يعمل بشكل جيد مع الوكلاء العاديين

لفهم المشكلة، يجب أن نفهم كيف يعمل gRPC تحت الغطاء. يستخدم البروتوكول HTTP/2 كطبقة نقل، وهذا يميزه جذرياً عن REST المعتاد فوق HTTP/1.1. تم تصميم معظم الوكلاء المؤسسيين، وجدران الحماية، وموازني الحمل في عصر HTTP/1.1 ولا يعرفون ببساطة كيفية معالجة تدفقات HTTP/2 المتعددة.

إليك المشاكل المحددة التي ستواجهها عند محاولة تمرير gRPC عبر وكيل HTTP التقليدي:

  • الخفض إلى HTTP/1.1. العديد من الوكلاء يقومون تلقائياً بخفض إصدار البروتوكول. يتطلب gRPC HTTP/2 — بدون ذلك، لن يتم إنشاء الاتصال، وسيتلقى العميل خطأ UNAVAILABLE.
  • قطع الاتصالات طويلة الأمد. يستخدم gRPC بشكل نشط البث من الخادم والاتصالات ثنائية الاتجاه. الوكلاء الذين لديهم مهلات عدوانية (خاصة AWS ELB Classic، وبعض إصدارات Squid) يقطعون الاتصالات التي لا تنقل البيانات لأكثر من 60 ثانية.
  • مشاكل مع Content-Type. يستخدم gRPC رأس Content-Type: application/grpc. قد يرفض الوكيل الذي لا يعرف هذا النوع الطلب أو يقوم بتخزين الجسم بشكل غير صحيح.
  • Trailers (تريلرز). يستخدم gRPC تريلرز HTTP/2 لنقل حالة انتهاء الاستدعاء. لا تدعم الوكلاء HTTP/1.1 التريلرز — ستفقد معلومات الحالة.
  • تخزين الجسم. يقوم بعض الوكلاء بتخزين الجسم بالكامل من الاستجابة قبل إرساله إلى العميل. بالنسبة لاستدعاءات gRPC المتدفقة، يعني هذا أن العميل لن يتلقى أي رسالة حتى ينتهي البث.

الاستنتاج الرئيسي:

لعمل gRPC عبر الوكيل، تحتاج إلى وكيل يدعم HTTP/2 من النهاية إلى النهاية أو وضع نفق خاص. الوكيل التقليدي HTTP بدون تعديل التكوين لن يكون مناسباً.

نفق HTTP/2: كيف يعمل

هناك نهجان مختلفان تماماً لتمرير حركة gRPC، ومن المهم فهم الفرق بينهما لاختيار الحل الصحيح لبنيتك.

النهج 1: HTTP/2 من النهاية إلى النهاية (موصى به)

يفهم الوكيل HTTP/2 ويقوم بإنشاء اتصال HTTP/2 مع كل من العميل والواجهة الخلفية. يمكنه تحليل التدفقات الفردية، وتطبيق التوازن على مستوى الطلبات، وإضافة الرؤوس، وتنفيذ إنهاء TLS. هذه هي الخيار الأكثر وظيفية — هكذا تعمل Envoy وNginx (بدءاً من الإصدار 1.13.10) وموازني الحمل الذين يدعمون gRPC في مزودي السحابة.

النهج 2: نفق TCP (CONNECT)

لا يقوم الوكيل بتحليل حركة HTTP/2، بل يقوم ببساطة بإنشاء نفق TCP شفاف عبر طريقة CONNECT. يقوم العميل بإنشاء اتصال TLS مباشرة مع الواجهة الخلفية عبر النفق. يرى الوكيل فقط تدفق بايت مشفر. هذه الطريقة أسهل في الإعداد، لكنها تحرمك من إمكانيات التوازن على مستوى طلبات gRPC وإضافة الرؤوس.

الميزة HTTP/2 من النهاية إلى النهاية نفق TCP CONNECT
توازن الحمل حسب الطلبات ✅ نعم ❌ لا (فقط عبر TCP)
إنهاء TLS على الوكيل ✅ نعم ❌ لا
إضافة الرؤوس ✅ نعم ❌ لا
صعوبة الإعداد متوسطة منخفضة
دعم البث ✅ كامل ✅ كامل
الرصد (المقاييس) ✅ مفصل ❌ فقط TCP

إعداد Nginx كوكيل gRPC

يدعم Nginx تمرير gRPC بدءاً من الإصدار 1.13.10 (فبراير 2018). يتطلب الأمر وحدة ngx_http_grpc_module، التي تأتي في التوزيعة القياسية. من المهم: يدعم Nginx HTTP/2 على جانب العميل (الواجهة الأمامية)، ولكنه يستخدم HTTP/2 فقط لـ gRPC على جانب الواجهة الخلفية — يعمل HTTP upstream العادي عبر HTTP/1.1.

التكوين الأساسي لوكيل gRPC على Nginx

server {
    listen 443 ssl http2;
    server_name grpc.example.com;

    # شهادات TLS
    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;

    # معلمات TLS الحديثة
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        # توجيه grpc_pass بدلاً من proxy_pass
        grpc_pass grpc://grpc_backend;

        # مهلات للتدفقات طويلة الأمد
        grpc_read_timeout  3600s;
        grpc_send_timeout  3600s;
        grpc_connect_timeout 5s;

        # تمرير عنوان IP الحقيقي للعميل
        grpc_set_header X-Real-IP $remote_addr;
        grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

upstream grpc_backend {
    server backend-1:50051;
    server backend-2:50051;
    server backend-3:50051;

    # Keepalive لاتصالات HTTP/2
    keepalive 32;
}

لاحظ بعض التفاصيل الحرجة. أولاً، يتم استخدام توجيه grpc_pass، وليس proxy_pass — هذه وحدات مختلفة بسلوك مختلف. ثانياً، تم تعيين المهلات grpc_read_timeout وgrpc_send_timeout إلى 3600 ثانية (1 ساعة) — هذا مهم للتدفقات الخدمية التي قد تعمل لفترة طويلة دون نقل البيانات. ثالثاً، keepalive 32 في upstream يسمح بإعادة استخدام اتصالات HTTP/2 مع الواجهات الخلفية.

تكوين لـ gRPC غير المشفر (grpc://)

server {
    listen 80 http2;
    server_name grpc-internal.example.com;

    location / {
        grpc_pass grpc://127.0.0.1:50051;

        # معالجة أخطاء gRPC
        error_page 502 = /error502grpc;
    }

    location = /error502grpc {
        internal;
        default_type application/grpc;
        add_header grpc-status 14;
        add_header content-length 0;
        return 204;
    }
}

كتلة error502grpc — تفصيل مهم: عند عدم توفر الواجهة الخلفية، تعيد الحالة الصحيحة لـ gRPC UNAVAILABLE (14) بدلاً من HTTP 502، الذي لن يتمكن عميل gRPC من معالجته بشكل صحيح.

Envoy Proxy: الخيار الأفضل لـ gRPC في الميكروسيرفيسات

تم إنشاء Envoy بواسطة Lyft خصيصاً لبنية الميكروسيرفيس، ودعم gRPC فيه تم تنفيذه على أعمق مستوى. إنه يفهم Protocol Buffers، ويستطيع تحويل gRPC إلى REST، ويجمع مقاييس مفصلة لكل طريقة RPC، ويعد أساساً لحلول service mesh — Istio وAWS App Mesh وغيرها. إذا كنت تبني بنية ميكروسيرفيس جادة، فإن Envoy هو معيار الصناعة.

التكوين الأساسي لـ Envoy لـ gRPC

static_resources:
  listeners:
  - name: grpc_listener
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8080
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: grpc_proxy
          codec_type: HTTP2
          route_config:
            name: grpc_routes
            virtual_hosts:
            - name: grpc_services
              domains: ["*"]
              routes:
              - match:
                  prefix: "/com.example.UserService"
                route:
                  cluster: user_service
                  timeout: 30s
                  retry_policy:
                    retry_on: "reset,connect-failure,retriable-status-codes"
                    num_retries: 3
                    retriable_status_codes: [14]
              - match:
                  prefix: "/com.example.OrderService"
                route:
                  cluster: order_service
                  timeout: 60s
          http_filters:
          - name: envoy.filters.http.grpc_stats
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_stats.v3.FilterConfig
              emit_filter_state: true
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

  clusters:
  - name: user_service
    connect_timeout: 5s
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    http2_protocol_options: {}
    load_assignment:
      cluster_name: user_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: user-service
                port_value: 50051

المزايا الرئيسية لهذا التكوين: التوجيه حسب خدمات gRPC (يجب أن يتطابق بادئة المسار مع الاسم الكامل للخدمة بتنسيق package.ServiceName)، وإعادة المحاولة التلقائية عند الحالة UNAVAILABLE وجمع مقاييس gRPC عبر فلتر grpc_stats.

تحويل gRPC-Web في Envoy

واحدة من الميزات القاتلة لـ Envoy هي المحول المدمج gRPC-Web. لا تدعم المتصفحات gRPC مباشرة (بسبب قيود Fetch API على تريلرز HTTP/2)، لذلك يتم استخدام بروتوكول gRPC-Web. يمكن لـ Envoy تحويل طلبات gRPC-Web من المتصفح تلقائياً إلى gRPC العادي للواجهة الخلفية:

http_filters:
- name: envoy.filters.http.grpc_web
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.cors
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.CorsPolicy
- name: envoy.filters.http.router
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

HAProxy و gRPC: تكوين مع توازن الحمل

يدعم HAProxy gRPC بدءاً من الإصدار 1.9.2. يعمل على مستوى TCP أو HTTP/2، ويستطيع توازن حركة gRPC وإجراء فحوصات الصحة. HAProxy هو خيار جيد إذا كنت تستخدمه بالفعل في البنية التحتية الخاصة بك وتريد إضافة دعم gRPC دون إدخال مكون جديد.

global
    maxconn 50000
    log stdout format raw local0

defaults
    log global
    timeout connect 5s
    timeout client  3600s
    timeout server  3600s

frontend grpc_frontend
    bind *:443 ssl crt /etc/haproxy/certs/server.pem alpn h2,http/1.1
    mode http
    option http-use-htx
    default_backend grpc_servers

backend grpc_servers
    mode http
    balance leastconn
    option http-use-htx

    # فحص الصحة عبر بروتوكول فحص صحة gRPC
    option httpchk GET /grpc.health.v1.Health/Check
    http-check expect status 200

    server grpc1 10.0.0.1:50051 check ssl verify none
    server grpc2 10.0.0.2:50051 check ssl verify none
    server grpc3 10.0.0.3:50051 check ssl verify none

لاحظ بعض الإعدادات المهمة. alpn h2,http/1.1 في توجيه bind يشير إلى أن HAProxy يقبل كل من اتصالات HTTP/2 وHTTP/1.1 عبر مفاوضة TLS ALPN. timeout client 3600s وtimeout server 3600s — معلمات حرجة لتدفقات gRPC طويلة الأمد. خوارزمية leastconn تفضل roundrobin لـ gRPC، حيث أن الاتصالات المتدفقة يمكن أن تكون طويلة الأمد وتؤثر بشكل غير متساوٍ على الواجهات الخلفية.

إنهاء TLS والتشفير الشامل لـ gRPC

يفترض gRPC بشكل افتراضي استخدام TLS — هذه جزء من المواصفات. في الممارسة العملية، في بنية الميكروسيرفيس، تبرز مسألة: أين يتم تنفيذ إنهاء TLS وكيف يتم تنظيم التشفير بين المكونات؟ هناك ثلاثة أنماط أساسية.

النمط 1: إنهاء TLS على الوكيل (Edge TLS)

يستقبل الوكيل حركة مشفرة من العملاء، يفك تشفيرها وينقلها إلى الواجهات الخلفية عبر قناة غير مشفرة (أو مع TLS منفصل). هذه هي الطريقة الأكثر شيوعاً في الشبكات المؤسسية. يمكن أن تستخدم الواجهات الخلفية grpc.Insecure() لتبسيط التكوين.

النمط 2: التشفير الشامل (mTLS)

TLS المتبادل (mTLS) هو معيار لـ service mesh. كل خدمة لديها شهادة خاصة بها، وعند كل اتصال، تتحقق كلا الجانبين من شهادات بعضهما البعض. هذا يوفر مصادقة الخدمات وتشفير الحركة داخل الكلاستر. هكذا يعمل Istio مع وكيل Envoy sidecar.

# Go: خادم gRPC مع mTLS
import (
    "crypto/tls"
    "crypto/x509"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

func createMTLSCredentials() credentials.TransportCredentials {
    cert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
    
    caCert, _ := os.ReadFile("ca.crt")
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)
    
    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
        ClientAuth:   tls.RequireAndVerifyClientCert,
        ClientCAs:    caCertPool,
    }
    
    return credentials.NewTLS(tlsConfig)
}

// إنشاء خادم مع mTLS
creds := createMTLSCredentials()
server := grpc.NewServer(grpc.Creds(creds))

النمط 3: TLS Passthrough

يعمل الوكيل على مستوى TCP ولا يفك تشفير الحركة — يقوم ببساطة بإعادة توجيه بايت مشفرة إلى الواجهة الخلفية. هذه هي أبسط خيار من وجهة نظر الوكيل، لكنها تحرم من إمكانية تحليل حركة gRPC، إضافة الرؤوس أو تنفيذ التوازن على مستوى الطلبات.

توازن الحمل لحركة gRPC: الخصائص والحلول

توازن الحمل لـ gRPC هو مهمة غير تافهة، وإليك السبب. في HTTP/1.1، كل طلب هو اتصال TCP منفصل (أو اتصال من مجموعة)، ويقوم موازن الحمل بسهولة بتوزيع الطلبات على الواجهات الخلفية. في HTTP/2، يقوم اتصال TCP واحد بتعدد تدفقات متعددة — إذا كان موازن الحمل يعمل على مستوى TCP، فإن جميع التدفقات من اتصال واحد ستذهب إلى واجهة خلفية واحدة.

بالنسبة لـ gRPC، يعني هذا: إذا قام العميل بإنشاء اتصال HTTP/2 واحد ويرسل من خلاله 100 طلب RPC، عند توازن الحمل TCP، ستتم معالجة جميع الطلبات الـ 100 بواسطة مثيل واحد من الواجهة الخلفية. ستبقى الواجهات الخلفية الأخرى غير مستخدمة. الحل هو التوازن على مستوى تدفقات HTTP/2 (توازن L7).

خوارزميات التوازن لـ gRPC

الخوارزمية تناسب gRPC تعليق
Round Robin ✅ نعم جيد لـ RPC الأحادي مع أوقات معالجة متشابهة تقريباً
Least Connection ✅ الخيار الأفضل يأخذ في الاعتبار التدفقات النشطة، ويوزع الحمل بشكل متساوٍ
Random ⚠️ مشروط يعمل عند وجود عدد كبير من الطلبات، غير متساوٍ عند القليل منها
IP Hash ❌ سيء يربط العميل بواجهة خلفية واحدة، لا معنى له عند L7
Pick First (مبني في gRPC) ❌ ليس للإنتاج تذهب جميع الطلبات إلى أول خادم متاح

توازن الحمل من جانب العميل في gRPC

يدعم gRPC توازن الحمل من جانب العميل — يقرر العميل بنفسه إلى أي خادم يرسل الطلب. هذا يسمح بتجاوز مشكلة تعدد TCP. يتم استخدام DNS مع سجلات A متعددة أو محللات خاصة (Consul، etcd) لاكتشاف الخدمة. مثال على إعداد توازن الحمل من جانب العميل في Go:

import (
    "google.golang.org/grpc"
    "google.golang.org/grpc/balancer/roundrobin"
)

// عميل مع توازن الحمل round-robin عبر DNS
conn, err := grpc.Dial(
    "dns:///grpc-service.internal:50051",
    grpc.WithDefaultServiceConfig(
        `{"loadBalancingConfig": [{"round_robin":{}}]}`
    ),
    grpc.WithTransportCredentials(creds),
)

// عميل مع least-connection (متاح في gRPC >= 1.58)
conn, err := grpc.Dial(
    "dns:///grpc-service.internal:50051",
    grpc.WithDefaultServiceConfig(
        `{"loadBalancingConfig": [{"least_request":{}}]}`
    ),
    grpc.WithTransportCredentials(creds),
)

الوكلاء الخارجيون لـ gRPC: متى ولماذا هم مطلوبون في الميكروسيرفيسات

بالإضافة إلى مكونات الوكلاء الداخلية (Nginx، Envoy، HAProxy)، في بنية الميكروسيرفيس، قد تظهر الحاجة أحياناً لاستخدام خوادم الوكلاء الخارجية — على سبيل المثال، لتوجيه الحركة عبر مناطق معينة، أو لتجاوز قيود الشبكة، أو لعزل الاتصالات الصادرة. دعنا نستعرض السيناريوهات الرئيسية.

السيناريو 1: الميكروسيرفيسات الموزعة جغرافياً

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

السيناريو 2: عزل الحركة الصادرة

في بعض البيئات المؤسسية، يجب أن تمر كل الحركة الصادرة عبر وكيل مؤسسي. بالنسبة لعملاء gRPC الذين يحتاجون إلى الوصول إلى خدمات gRPC الخارجية (مثل Google Cloud APIs، التي تستخدم gRPC)، فإن هذا يخلق صعوبات. الحل هو إعداد نفق CONNECT عبر الوكيل المؤسسي.

مثال على إعداد عميل gRPC للعمل عبر وكيل HTTP CONNECT في Go:

import (
    "net"
    "net/http"
    "golang.org/x/net/proxy"
    "google.golang.org/grpc"
)

// استخدام وكيل SOCKS5 لـ gRPC
proxyDialer, _ := proxy.SOCKS5(
    "tcp",
    "proxy.example.com:1080",
    &proxy.Auth{User: "user", Password: "pass"},
    proxy.Direct,
)

conn, err := grpc.Dial(
    "grpc-service.example.com:443",
    grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
        return proxyDialer.Dial("tcp", addr)
    }),
    grpc.WithTransportCredentials(creds),
)

السيناريو 3: الاختبار والتطوير

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

الأخطاء الشائعة عند إعداد gRPC عبر الوكيل وكيفية إصلاحها

دعنا نستعرض أكثر المشاكل شيوعاً التي يواجهها المطورون عند إعداد gRPC عبر الوكيل، وطرق محددة لحلها.

الخطأ 1: "transport: received the unexpected content-type"

الأعراض:

يتلقى العميل خطأ transport: received the unexpected content-type "text/html; charset=utf-8"

السبب: أعاد الوكيل صفحة HTML تحتوي على خطأ (على سبيل المثال، 502 Bad Gateway) بدلاً من استجابة gRPC. لا يعرف عميل gRPC كيفية معالجة HTML ويصدر هذا الخطأ.
الحل: تأكد من أن الواجهة الخلفية متاحة. أضف معالجة أخطاء gRPC على مستوى الوكيل (كما هو موضح في مثال Nginx أعلاه مع كتلة error502grpc).

الخطأ 2: يتم قطع الاتصال بعد 60-120 ثانية

الأعراض:

تتقطع اتصالات gRPC المتدفقة بشكل غير متوقع مع خطأ UNAVAILABLE: transport is closing بعد فترة زمنية متشابهة تقريباً.

السبب: يقوم الوكيل بإغلاق الاتصالات غير النشطة بناءً على المهلة. القيم التقليدية: AWS ELB — 60 ثانية، Nginx بشكل افتراضي — 60 ثانية.
الحل 1: زيادة المهلات على الوكيل (كما هو موضح في الأمثلة أعلاه).
الحل 2: إعداد keepalive على جانب عميل gRPC:

import "google.golang.org/grpc/keepalive"

kaParams := keepalive.ClientParameters{
    Time:                30 * time.Second, // Ping كل 30 ثانية
    Timeout:             10 * time.Second, // الانتظار للحصول على استجابة 10 ثوانٍ
    PermitWithoutStream: true,             // Ping حتى بدون RPC نشط
}

conn, err := grpc.Dial(
    "grpc-service:50051",
    grpc.WithKeepaliveParams(kaParams),
    grpc.WithTransportCredentials(creds),
)

الخطأ 3: HTTP/2 لا يتوافق (فشل ALPN)

الأعراض:

خطأ transport: failed to dial: context deadline exceeded أو no application protocol

السبب: لا يدعم الوكيل أو الأجهزة الوسيطة ALPN (مفاوضة بروتوكول الطبقة التطبيقية) أو لم يتم تضمين h2 في قائمة البروتوكولات المدعومة.
الحل: تأكد من أن بروتوكول h2 محدد بوضوح في تكوين TLS للوكيل: alpn h2,http/1.1 (HAProxy) أو listen 443 ssl http2 (Nginx).

الخطأ 4: يتوقف البث — لا تصل البيانات

الأعراض:

يعمل البث من الخادم على الواجهة الخلفية (يظهر في السجلات)، لكن العميل لا يتلقى رسائل حتى ينتهي البث.

السبب: يقوم الوكيل بتخزين الاستجابة ويرسلها إلى العميل فقط بعد الانتهاء. هذه مشكلة نموذجية للوكلاء الذين تم إعدادهم مع proxy_buffering on.
الحل لـ Nginx:

location / {
    grpc_pass grpc://backend;
    
    # تعطيل التخزين للتدفقات gRPC
    grpc_buffer_size 0;
    
    # أو لـ proxy_pass العادي:
    proxy_buffering off;
    proxy_cache off;
}

قائمة التحقق لتشخيص gRPC عبر الوكيل

✅ قائمة التحقق: تشخيص gRPC-الوكيل

  • يدعم الوكيل HTTP/2 (تحقق عبر curl --http2 -v)
  • ALPN h2 مفعلة في تكوينات TLS للوكيل
  • تم تعيين مهلات العميل والخادم على الأقل 300 ثانية
  • تم تعطيل تخزين الاستجابات لنقاط نهاية gRPC
  • تم إعداد keepalive لـ gRPC على العميل (الوقت: 30 ثانية، المهلة: 10 ثوانٍ)
  • تُعاد أخطاء الواجهة الخلفية كحالات gRPC، وليس كودات HTTP
  • فحص الصحة يستخدم بروتوكول فحص صحة gRPC
  • يعمل التوازن على L7 (تدفقات HTTP/2)، وليس L4 (TCP)

الخاتمة

يتطلب إعداد gRPC عبر الوكيل فهم الفروق الرئيسية بين HTTP/2 وHTTP/1.1: تعدد التدفقات، الاتصالات طويلة الأمد، تريلرز HTTP/2 ومفاوضة ALPN. لا تعمل الوكلاء HTTP التقليدية بدون إعداد خاص مع gRPC — تحتاج إلى إما وكيل L7 يدعم HTTP/2 (Nginx 1.13.10+، Envoy، HAProxy 1.9.2+)، أو نفق TCP CONNECT.

يُوصى باستخدام Envoy في بيئة الإنتاج — حيث تم إنشاؤه خصيصاً لبنية الميكروسيرفيس، ويحتوي على دعم أصلي لـ gRPC، ويستطيع جمع مقاييس مفصلة لكل طريقة RPC، ويعد أساساً لمعظم حلول service mesh. يعد Nginx خياراً جيداً إذا كنت تستخدمه بالفعل كـ API Gateway وترغب في إضافة دعم gRPC دون إدخال مكون جديد. يناسب HAProxy إذا كانت الأداء حاسمة وتعرف بالفعل كيفية تكوينه.

بغض النظر عن الوكيل المختار، تبقى ثلاث قواعد ثابتة: زيادة المهلات للتدفقات طويلة الأمد، تعطيل تخزين الاستجابات، وإعداد keepalive على العميل. تحل هذه الإعدادات الثلاثة 80% من مشاكل gRPC عبر الوكيل.

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

```