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

gRPC از طریق پروکسی در معماری میکروسرویس: راهنمای کامل تنظیم تونل‌سازی HTTP/2

راهنمای جامع برای تنظیم ترافیک gRPC از طریق سرورهای پروکسی در معماری میکروسرویس — از تونل‌سازی HTTP/2 تا توزیع بار و خاتمه TLS.

📅۱۶ مرداد ۱۴۰۵
```html

gRPC یک فریم‌ورک RPC با عملکرد بالا از گوگل است که بر روی HTTP/2 کار می‌کند و به استاندارد de facto برای تعاملات بین سرویس‌ها تبدیل شده است. اما به محض اینکه سعی کنید ترافیک 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 trailers برای انتقال وضعیت پایان فراخوانی استفاده می‌کند. پروکسی‌های HTTP/1.1 از trailers پشتیبانی نمی‌کنند — اطلاعات وضعیت از دست خواهد رفت.
  • بافر کردن بدنه. برخی پروکسی‌ها تمام بدنه پاسخ را قبل از ارسال به مشتری بافر می‌کنند. برای فراخوانی‌های 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 در سمت مشتری (فرانت‌اند) پشتیبانی می‌کند، اما در سمت بک‌اند فقط برای gRPC از HTTP/2 استفاده می‌کند — upstream معمولی HTTP بر اساس 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: بهترین انتخاب برای 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 trailers)، بنابراین از پروتکل 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 برای gRPC نسبت به roundrobin ترجیح داده می‌شود، زیرا اتصالات استریمینگ ممکن است طولانی‌مدت باشند و بار را به طور نامنظم بر روی بک‌اندها توزیع کنند.

خاتمه TLS و رمزگذاری انتها به انتها برای gRPC

gRPC به طور پیش‌فرض استفاده از TLS را فرض می‌کند — این بخشی از مشخصات است. در عمل، در معماری میکروسرویس‌ها، سوال این است که خاتمه TLS را کجا انجام دهیم و چگونه رمزگذاری را بین مؤلفه‌ها سازماندهی کنیم؟ سه الگوی اصلی وجود دارد.

الگو 1: خاتمه TLS در پروکسی (Edge TLS)

پروکسی ترافیک رمزگذاری‌شده را از مشتریان می‌پذیرد، آن را رمزگشایی می‌کند و به بک‌اندها از طریق یک کانال غیر رمزگذاری‌شده (یا با TLS جداگانه) منتقل می‌کند. این رایج‌ترین رویکرد در شبکه‌های شرکتی است. بک‌اندها می‌توانند از grpc.Insecure() برای ساده‌سازی پیکربندی استفاده کنند.

الگو 2: رمزگذاری انتها به انتها (mTLS)

TLS متقابل (mTLS) استانداردی برای service mesh است. هر سرویس دارای گواهی خاص خود است و در هر اتصال، هر دو طرف گواهی‌های یکدیگر را بررسی می‌کنند. این امر احراز هویت سرویس‌ها و رمزگذاری ترافیک درون خوشه را تضمین می‌کند. اینگونه است که Istio با پروکسی Envoy sidecar کار می‌کند.

# Go: gRPC-server با 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: میکروسرویس‌های جغرافیایی توزیع‌شده

اگر میکروسرویس‌های شما در مناطق مختلف قرار دارند (به عنوان مثال، بخشی در اروپا، بخشی در ایالات متحده) و شما نیاز دارید که کنترل کنید که gRPC از کدام آدرس IP برای برقراری اتصالات بین مناطق استفاده می‌کند، پروکسی‌های خارجی می‌توانند به سازماندهی مسیریابی قابل پیش‌بینی کمک کنند. برای چنین کارهایی، پروکسی‌های دیتاسنتر مناسب هستند — آن‌ها آدرس‌های 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 قطع می‌شوند، در حدود زمان مشابه.

دلیل: پروکسی اتصالات idle را بر اساس تایم‌اوت قطع می‌کند. مقادیر کلاسیک: AWS ELB — 60 ثانیه، Nginx به طور پیش‌فرض — 60 ثانیه.
راه‌حل 1: تایم‌اوت‌ها را در پروکسی افزایش دهید (همانطور که در مثال‌های بالا نشان داده شده است).
راه‌حل 2: keepalive را در سمت مشتری gRPC تنظیم کنید:

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

kaParams := keepalive.ClientParameters{
    Time:                30 * time.Second, // پینگ هر 30 ثانیه
    Timeout:             10 * time.Second, // انتظار برای پاسخ 10 ثانیه
    PermitWithoutStream: true,             // حتی بدون 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 در لیست پروتکل‌های پشتیبانی شده فعال نیست.
راه‌حل: اطمینان حاصل کنید که در پیکربندی TLS پروکسی به وضوح پروتکل h2 مشخص شده است: 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 در مشتری تنظیم شده است (Time: 30s, Timeout: 10s)
  • خطاهای بک‌اند به عنوان وضعیت‌های gRPC برگردانده می‌شوند، نه کدهای HTTP
  • بررسی سلامت از پروتکل بررسی سلامت gRPC استفاده می‌کند
  • تعادل بار در L7 (استریم‌های HTTP/2) کار می‌کند، نه L4 (TCP)

نتیجه‌گیری

تنظیم gRPC از طریق پروکسی نیاز به درک تفاوت‌های کلیدی HTTP/2 با HTTP/1.1 دارد: چندگانه‌سازی استریم‌ها، اتصالات طولانی‌مدت، HTTP/2 trailers و توافق 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 با پروتکل باینری و حساسیت به تأخیر بسیار مهم است.

```