الأدلة التقنية

Kong أم APISIX أم Tyk أم KrakenD؟ أدوات بوابات API

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

Kong أم APISIX أم Tyk أم KrakenD؟ أدوات بوابات API

في مقال ما هي بوابة API Gateway ومتى تحتاجها؟ بنينا بوابة صغيرة بـ Nginx ورأينا أين تتوقف: لا يوجد تحقق من JWT في النسخة المفتوحة، ولا توجد إدارة للمفاتيح وهي تعمل، والعدادات لا تشترك بين النسخ. والخطوة التالية أن تختار أداة بوابة حقيقية، وهنا تجد عشرات الخيارات التي تقول كلها إنها مفتوحة المصدر، ولكن عندما تقرأ التفاصيل تجد أن «المفتوح» في بعضها هو البوابة فقط، وأن لوحة الإدارة وبوابة المطورين وبعض الإضافات في نسخة مدفوعة، وأن أداة مشهورة توقفت نسختها المفتوحة عند إصدار قديم.

لذلك سوف نقرأ ترخيص كل أداة من مستودعها، ثم نبني تجربة واحدة مشتركة: نفس الخدمة ونفس المسارات الثلاثة، بالتحقق من JWT وحد للطلبات، على Apache APISIX وKong وKrakenD، ثم نقيس على نفس الجهاز.

دليلما هي بوابة API Gateway ومتى تحتاجها؟ما الذي تفعله بوابة API ولا يفعله ال Reverse Proxy، وما الذي يبقى في خدماتك، ومتى لا تحتاج إلى بوابة أصلاً.دليلApache APISIX عملياً: من أول Route إلى بوابة إنتاجكيف تشغل Apache APISIX مع etcd، وتحمي واجهاتك بالتوكنات والحدود وauthentik، وتراقبها وتأخذ نسخة احتياطية من إعدادها.دليلTraefik: Reverse Proxy بشهادات تلقائية لتطبيقات Dockerكيف تثبت Traefik على سيرفرك فيحصل كل تطبيق على نطاقه وشهادة HTTPS بمجرد إضافة بضعة أسطر إلى ملف Compose الخاص به.

وسوف نناقش في هذا المقال ما يلي:

  • كل أداة: ما هي، وترخيصها، وحدود نسختها المفتوحة، ونقاط القوة ونقاط الضعف: Kong وApache APISIX وTyk وKrakenD وEnvoy Gateway وTraefik وGravitee.
  • الفرق في البنية: أين تحفظ كل أداة إعدادها، وبأي لغة تكتب الإضافات.
  • تجربة مشتركة: نفس الإعداد على ثلاث بوابات، ونكسره بتوكن منتهي وتوكن بلا تاريخ انتهاء وطلبات أكثر من الحد.
  • أرقام محلية: الطلبات في الثانية وزمن p99 والمعالج لكل طلب والذاكرة.
  • دليل اختيار مختصر لكل حالة.

الفرق الأول: أين تحفظ البوابة إعدادها؟

كل بوابة فيها جزءان: مستوى البيانات Data Plane وهو البرنامج الذي يمر عبره كل طلب، ومستوى التحكم Control Plane وهو المكان الذي تكتب فيه المسارات والمفاتيح ومنه تصل إلى البوابة. والفرق بين الأدوات في الجزء الثاني أكبر بكثير من الفرق في الأول، لأنه يحدد كيف تضيف مساراً، وكيف تأخذ نسخة احتياطية، وماذا يحدث إذا توقف المخزن. والصورة التالية تبين ذلك لخمس أدوات:

خمسة أعمدة. APISIX: شخص يستخدم Admin API و/ui يكتب في etcd، وetcd يرسل التغيير بال watch إلى عقدتين NGINX + Lua، وتحتها plugins: Lua, or Go, Java, Python by RPC, Wasm وor a YAML file (standalone). KONG: Admin API or decK إلى PostgreSQL ثم إلى عقدتين NGINX + Lua، وor DB-less: kong.yml وplugins: Lua, or Go and JavaScript. KRAKEND: ملف krakend.json in Git يقرأ مرة عند البدء إلى عقدتين Go nodes, no state، وno database, no Admin API وv3 CE dropped custom plugins. TYK: API files or REST API إلى عقدتين Go nodes وبجانبها Redis، وRedis required: keys and rate counters وdashboard is commercial. ENVOY GATEWAY: kubectl apply HTTPRoute إلى controller ثم xDS إلى Envoy proxies، وKubernetes or standalone وextensions: Lua, Wasm, external processing
خمس طرق لحفظ الإعداد وتوصيله إلى البوابة، من مخزن يراقب التغيير إلى ملف يقرأ مرة واحدة

ولاحظ أن البوابات المبنية على NGINX مع Lua، وهي APISIX وKong، تقرأ التغيير وهي تعمل دون إعادة تحميل، والبوابات المكتوبة بلغة Go، وهي KrakenD وTyk، تعطيك ملفاً ثابتاً أو API بسيطاً، وEnvoy Gateway يعتمد على Kubernetes نفسه مخزناً للإعداد.

الأدوات واحدة واحدة

Kong Gateway

أشهر بوابة مفتوحة المصدر، مبنية على NGINX وOpenResty، ومستودعها Kong/kong بترخيص Apache 2.0، وتعمل مع قاعدة PostgreSQL أو دون قاعدة بيانات DB-less من ملف kong.yml، وتكتب إضافاتها بلغة Lua أو Go أو JavaScript، ومعها منذ الإصدار 3.4 واجهة إدارة مفتوحة اسمها Kong Manager OSS. ولكن هناك أمر يجب أن تعرفه قبل أن تبني عليها: آخر إصدار في المستودع المفتوح هو 3.9.3 في يونيو 2026، وهو إصدار إصلاحات أمنية لفرع 3.9 الذي صدر في ديسمبر 2024، بينما وصلت ال Docker Images الخاصة بـ Kong Gateway Enterprise إلى 3.13 في سبتمبر 2026. وفي المستودع نقاش مفتوح منذ أبريل 2025 يسأل فيه المستخدمون عن Docker Image للإصدار 3.10 المفتوح دون رد رسمي من الشركة، وفي نقاش آخر رد أحد المساهمين من Kong بأنه لا يوجد موعد مؤكد للإصدار. وهذا لا يعني أن الكود أغلق، فالترخيص ما زال Apache 2.0، ولكنه يعني أن النسخة المفتوحة لا تتابع الميزات الجديدة.

نقاط القوة: أكبر مجتمع ونظام إضافات، وتوثيق كثير، وواجهة إدارة مفتوحة، وأداة decK لإدارة الإعداد كملفات.

نقاط الضعف: النسخة المفتوحة متوقفة عند فرع 3.9، وأكثر الأدوات استهلاكاً للذاكرة في تجربتنا، وإضافة JWT فيها لا تتحقق من تاريخ الانتهاء إلا إذا طلبت ذلك صراحة، كما سنرى في التجربة.

Apache APISIX

بوابة من مؤسسة Apache مبنية أيضاً على NGINX وOpenResty، ومستودعها apache/apisix بترخيص Apache 2.0، وآخر إصدار 3.19.0 في سبتمبر 2026. وتحفظ إعدادها في etcd وتراقبه فيصل التغيير إلى كل العقد خلال لحظات، أو تعمل في الوضع المستقل Standalone من ملف YAML كما في تجربتنا هنا، والإضافات بلغة Lua، أو بلغات Go وJava وPython عبر مشغل خارجي، أو بال Wasm. وأما لوحة الإدارة فقد تغيرت: مشروع apisix-dashboard المنفصل نشر إعلاناً بأنه لم يعد يصدر إصدارات مستقلة وأن اللوحة أصبحت مدمجة في APISIX نفسها بدءاً من الإصدار 3.13، فلا تقم بتثبيت الإصدار القديم 3.0.1 الذي ما زال يظهر في المقالات القديمة.

نقاط القوة: كل الإضافات في النسخة المفتوحة، وتشمل openid-connect وتقسيم الحركة ومقاييس Prometheus وOpenTelemetry، ولوحة إدارة مدمجة، وإصدار جديد كل شهر أو شهرين تقريباً.

نقاط الضعف: etcd مكون إضافي تحتاج أن تشغله وتأخذ نسخته الاحتياطية، والإعدادات الافتراضية فيها مفاجآت مثل رمز 503 للطلب الزائد بدل 429، وتشرحها كلها في دليل APISIX العملي.

Tyk

بوابة مكتوبة بلغة Go، وملف الترخيص في مستودعها يقول إن الكود كله بترخيص MPL 2.0 ما عدا مجلد ee الذي يخضع لترخيص تجاري، وآخر إصدار v5.15.1 في أكتوبر 2026. ويذكر توثيق البوابة المفتوحة أنها لا تعتمد على شيء غير Redis للحد الموزع وحفظ التوكنات، وأن لوحة الإدارة وبوابة المطورين جزء من منتج Tyk Self-Managed وليست في النسخة المفتوحة.

نقاط القوة: البوابة نفسها كاملة دون ميزات محجوبة كما يقول توثيقها، وتدعم REST وGraphQL وgRPC، والحد الموزع جاهز لأن Redis جزء أساسي منها.

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

KrakenD

بوابة مكتوبة بلغة Go لا تحفظ أي حالة: لا قاعدة بيانات ولا Admin API، وإنما ملف krakend.json واحد تقرؤه عند البدء، فتضع الملف في Git وتنشر نسخة جديدة عند كل تغيير. ونسختها المجتمعية krakend-ce بترخيص Apache 2.0، وصدر منها الإصدار 3.0.0 في 30 سبتمبر 2026، ومعه تغييران مهمان: ملف الإعداد أصبح بالإصدار 4، وإزالة دعم الإضافات Plugins من النسخة المجتمعية، فإذا كانت عندك إضافة مكتوبة بلغة Go لنسخة 2.x فلن تعمل على 3.0 المجتمعية. والحد في KrakenD المجتمعي يكون لكل نقطة أو لكل عنوان IP أو ترويسة، ولا يوجد حد بحسب حقل في التوكن كما يذكر توثيق الحد.

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

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

Envoy Gateway

مشروع من مجتمع Envoy يدير Envoy Proxy كبوابة في Kubernetes أو مستقلة، وإعداده هو موارد Gateway API نفسها مع موارد إضافية للسياسات، وترخيصه Apache 2.0، وآخر إصدار v1.9.2 في سبتمبر 2026، وفي مرجع موارده تجد التحقق من JWT وOIDC والحد المحلي والموزع، والتوسعة بال Lua وال Wasm والمعالجة الخارجية.

نقاط القوة: الخيار الطبيعي إذا كنت في Kubernetes وتريد Gateway API بلا إضافات من شركة، وEnvoy نفسه مجرب في أكبر الأنظمة.

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

Traefik Proxy كبوابة خفيفة

إذا كنت تستخدم Traefik أصلاً فهو يعطيك جزءاً من عمل البوابة: حد المعدل و Forward Auth الذي يسأل خدمة مثل authentik قبل كل طلب، كما في دليل authentik. ولكن قائمة الإضافات Middlewares تبين أن التحقق من JWT ومفاتيح ال API وOIDC والحد الموزع موسومة بـ Traefik Hub، وهو المنتج التجاري، أما Traefik Proxy نفسه فترخيصه MIT وآخر إصدار v3.7.14 في أكتوبر 2026.

Gravitee

منصة كاملة لإدارة ال APIs مكتوبة بلغة Java، ومستودعها بترخيص Apache 2.0، وفيها بوابة ولوحة إدارة وبوابة مطورين، ولكن ملف التثبيت السريع يشغل معها MongoDB وElasticsearch وأربع خدمات أخرى، وتذكر صفحة النسخة التجارية أن ميزات مثل سجل التدقيق Audit Trail والأدوار المخصصة والدخول الموحد للمؤسسة وبعض السياسات تحتاج ترخيصاً. وهي مناسبة عندما تحتاج بوابة مطورين مفتوحة وعندك فريق يستطيع تشغيل هذا العدد من المكونات.

تجربة عملية: نفس المسارات على ثلاث بوابات

سوف نضع أمام خدمة واحدة ثلاثة مسارات /orders و/users و/products، وكلها تتطلب JWT موقعاً بال HS256، والمسار الثالث محدود بخمسة طلبات في الثانية لكل عميل، ونفس الإعداد على APISIX 3.19.0 وKong 3.9.3 وKrakenD 3.0.0. واخترنا هذه الثلاث لأنها تعمل كلها من ملف دون قاعدة بيانات، فالمقارنة بين بوابات وليست بين قواعد بيانات، وكل ال Containers تبدأ أسماؤها بـ lab-gw-tools- والمنافذ على 127.0.0.1 فقط.

الملفات المشتركة

الخدمة الخلفية Nginx يرد على المسارات الثلاثة بـ JSON، وهذا ملف upstream.conf:

# The three backend services behind every gateway: one Nginx answering three paths with JSON.
server {
    listen 8080;
    default_type application/json;
    location /orders   { return 200 '{"service":"orders","items":[1001,1002]}\n'; }
    location /users    { return 200 '{"service":"users","name":"alice"}\n'; }
    location /products { return 200 '{"service":"products","count":42}\n'; }
}

والتوكن نصنعه بمكتبة Python القياسية فقط، حتى يكون نفس التوكن صالحاً للبوابات الثلاث، وهذا ملف token.py:

# Signs an HS256 JWT with the standard library only, so one token works on the three gateways.
#   python token.py            -> valid for 1 hour
#   python token.py expired    -> expired 60 s ago
#   python token.py noexp      -> no exp claim at all
#   python token.py wrongkey   -> signed with another secret
import base64, hashlib, hmac, json, sys, time

SECRET = b"lab-gw-secret-change-me-32-bytes!!"
def b64(b): return base64.urlsafe_b64encode(b).rstrip(b"=").decode()

mode = sys.argv[1] if len(sys.argv) > 1 else "valid"
now = int(time.time())
payload = {"iss": "lab-client", "sub": "alice", "iat": now}
if mode != "noexp":
    payload["exp"] = now - 60 if mode == "expired" else now + 3600
header = {"alg": "HS256", "typ": "JWT", "kid": "lab"}
signing = b64(json.dumps(header, separators=(",", ":")).encode()) + "." + b64(json.dumps(payload, separators=(",", ":")).encode())
key = b"another-secret" if mode == "wrongkey" else SECRET
print(signing + "." + b64(hmac.new(key, signing.encode(), hashlib.sha256).digest()))

لاحظ أن الحقل iss هو الذي يربط التوكن بالعميل في البوابات الثلاث، وأن الترويسة فيها kid لأن KrakenD يختار المفتاح من ملف JWK بهذا المعرف، وقد شرحنا بنية التوكن والفرق بين HS256 وRS256 في الجلسات وJWT.

إعداد APISIX

APISIX في الوضع المستقل يحتاج ملفين، الأول config.yaml يخبره أن الإعداد في ملف وأي الإضافات يحمل:

# APISIX in standalone mode: no etcd, routes come from apisix.yaml (same idea as Kong DB-less and KrakenD's file).
deployment:
  role: data_plane
  role_data_plane:
    config_provider: yaml
apisix:
  node_listen: 9080
nginx_config:
  worker_processes: 2
  error_log_level: warn
plugins:
  - jwt-auth
  - limit-count
stream_plugins: []

والثاني apisix.yaml فيه المسارات والمستهلك، ويجب أن ينتهي بالسطر #END:

upstreams:
  - id: backend
    type: roundrobin
    nodes:
      "lab-gw-tools-upstream:8080": 1
    keepalive_pool:
      size: 320
consumers:
  - username: labclient
    plugins:
      jwt-auth:
        key: lab-client
        secret: lab-gw-secret-change-me-32-bytes!!
        algorithm: HS256
routes:
  - id: orders
    uri: /orders
    upstream_id: backend
    plugins:
      jwt-auth: { key_claim_name: iss }
      limit-count: { count: 1000000, time_window: 1, key_type: var, key: consumer_name, rejected_code: 429 }
  - id: users
    uri: /users
    upstream_id: backend
    plugins:
      jwt-auth: { key_claim_name: iss }
      limit-count: { count: 1000000, time_window: 1, key_type: var, key: consumer_name, rejected_code: 429 }
  - id: products
    uri: /products
    upstream_id: backend
    plugins:
      jwt-auth: { key_claim_name: iss }
      limit-count: { count: 5, time_window: 1, key_type: var, key: consumer_name, rejected_code: 429 }
#END

في الإعداد أعلاه لاحظ التالي:

  • الإضافة jwt-auth تبحث افتراضياً عن العميل في حقل اسمه key داخل التوكن، فغيرناه إلى iss بالخيار key_claim_name.
  • الحد العالي جداً على /orders و/users موجود حتى تعمل إضافة الحد في القياس دون أن ترفض شيئاً، فنقيس كلفتها أيضاً.
  • الرمز الافتراضي للطلب الزائد في limit-count هو 503، لذلك حددنا 429 صراحة.

إعداد Kong

Kong دون قاعدة بيانات يقرأ ملف kong.yml:

_format_version: "3.0"
services:
  - name: backend
    url: http://lab-gw-tools-upstream:8080
    routes:
      - { name: orders, paths: [/orders], strip_path: false }
      - { name: users, paths: [/users], strip_path: false }
      - name: products
        paths: [/products]
        strip_path: false
        plugins:
          - name: rate-limiting
            config: { second: 5, policy: local, limit_by: consumer }
plugins:
  - name: jwt
    config: { claims_to_verify: [exp], key_claim_name: iss }
  - name: rate-limiting
    config: { second: 1000000, policy: local, limit_by: consumer }
consumers:
  - username: labclient
    jwt_secrets:
      - { key: lab-client, secret: "lab-gw-secret-change-me-32-bytes!!", algorithm: HS256 }

الإضافتان العامتان في الأسفل تطبقان على كل المسارات، والإضافة المعرفة على المسار products تحل محل الإضافة العامة بنفس الاسم لأنها أكثر تحديداً. ولاحظ السطر claims_to_verify: [exp]، وسوف نرى بعد قليل ماذا يحدث بدونه.

إعداد KrakenD

KrakenD يقرأ المفتاح السري من ملف JWK، والمفتاح المتماثل في صيغة JWK نوعه oct وقيمته هي السر نفسه بترميز base64url، وهذا ملف krakend/jwk.json:

{ "keys": [ { "kty": "oct", "kid": "lab", "alg": "HS256", "k": "bGFiLWd3LXNlY3JldC1jaGFuZ2UtbWUtMzItYnl0ZXMhIQ" } ] }

وهذا جزء من krakend/krakend.json فيه المسار الأول والمسار المحدود، والمسار /users مثل الأول تماماً:

{
  "$schema": "https://www.krakend.io/schema/v3.0/krakend.json",
  "version": 4,
  "port": 8080,
  "timeout": "3s",
  "extra_config": { "telemetry/logging": { "level": "WARNING", "stdout": true } },
  "endpoints": [
    {
      "endpoint": "/orders",
      "method": "GET",
      "output_encoding": "no-op",
      "extra_config": {
        "auth/validator": { "alg": "HS256", "jwk_local_path": "/etc/krakend/jwk.json", "disable_jwk_security": true },
        "qos/ratelimit/router": { "client_max_rate": 1000000, "every": "1s", "strategy": "ip" }
      },
      "backend": [ { "host": ["http://lab-gw-tools-upstream:8080"], "url_pattern": "/orders", "encoding": "no-op" } ]
    },
    {
      "endpoint": "/products",
      "method": "GET",
      "output_encoding": "no-op",
      "extra_config": {
        "auth/validator": { "alg": "HS256", "jwk_local_path": "/etc/krakend/jwk.json", "disable_jwk_security": true },
        "qos/ratelimit/router": { "client_max_rate": 5, "every": "1s", "strategy": "ip", "client_capacity": 5 }
      },
      "backend": [ { "host": ["http://lab-gw-tools-upstream:8080"], "url_pattern": "/products", "encoding": "no-op" } ]
    }
  ]
}

في الإعداد أعلاه لاحظ التالي:

  • output_encoding وencoding بالقيمة no-op تجعل KrakenD يمرر الرد كما هو دون أن يحلله ويعيد بناءه، وهذا أعدل للمقارنة مع البوابتين الأخريين.
  • disable_jwk_security يسمح بقراءة المفتاح دون HTTPS، وهو مناسب لملف محلي فقط.
  • الحد هنا بعنوان IP لأن الحد بحسب العميل في النسخة المجتمعية يكون بالعنوان أو بترويسة أو بمعامل في الرابط فقط.
  • أول محاولة لنا كانت مع "cache": true في المتحقق، فرفض KrakenD البدء وكتب في السجل Unable to create the validator: cache could not be used with jwk_local_path، فأزلناه.

ملف compose.yaml

# One shared lab: the same upstream and the same three routes behind APISIX, Kong and KrakenD (and a plain Nginx as the baseline).
# Each gateway gets the same 2 CPUs (cpuset 0-1) and 2 workers; the load generator runs on other CPUs.
name: lab-gw-tools
services:
  upstream:
    image: nginx:1.31.6-alpine
    container_name: lab-gw-tools-upstream
    cpuset: "6,7"
    volumes: ["./upstream.conf:/etc/nginx/conf.d/default.conf:ro"]
    ports: ["127.0.0.1:18080:8080"]
  apisix:
    image: apache/apisix:3.19.0-debian
    container_name: lab-gw-tools-apisix
    cpuset: "0,1"
    volumes:
      - ./apisix/config.yaml:/usr/local/apisix/conf/config.yaml:ro
      - ./apisix/apisix.yaml:/usr/local/apisix/conf/apisix.yaml:ro
    ports: ["127.0.0.1:19080:9080"]
    profiles: [apisix]
  kong:
    image: kong:3.9.3
    container_name: lab-gw-tools-kong
    cpuset: "0,1"
    environment:
      KONG_DATABASE: "off"
      KONG_DECLARATIVE_CONFIG: /kong/kong.yml
      KONG_NGINX_WORKER_PROCESSES: "2"
      KONG_PROXY_ACCESS_LOG: "off"
      KONG_ADMIN_LISTEN: "off"
    volumes: ["./kong/kong.yml:/kong/kong.yml:ro"]
    ports: ["127.0.0.1:19081:8000"]
    profiles: [kong]
  krakend:
    image: krakend:3.0.0
    container_name: lab-gw-tools-krakend
    cpuset: "0,1"
    environment: { GOMAXPROCS: "2" }
    volumes: ["./krakend:/etc/krakend:ro"]
    ports: ["127.0.0.1:19082:8080"]
    profiles: [krakend]

كل بوابة مقيدة بنفس المعالجين المنطقيين 0 و1 وبعمليتين أو خيطين، والخدمة الخلفية على معالجين آخرين، ومولد الحمل على أربعة غيرها، حتى لا يأخذ أحدهم وقت الآخر، وكل بوابة في Profile خاص بها فتستطيع تشغيل واحدة فقط:

docker compose --profile apisix --profile kong --profile krakend up -d

نكسرها: نفس التوكنات على البوابات الثلاث

الملف checks.sh يرسل طلباً بلا توكن، ثم بتوكن صالح، ثم منتهي، ثم بلا تاريخ انتهاء، ثم موقع بمفتاح آخر، ثم عشرة طلبات متوازية على المسار المحدود:

#!/bin/sh
# Same checks against one gateway: ./checks.sh http://127.0.0.1:19080
U=$1
for m in none valid expired noexp wrongkey; do
  if [ $m = none ]; then H=""; else H="Authorization: Bearer $(python token.py $m)"; fi
  printf '%-9s ' $m; curl -s -o /tmp/b -w '%{http_code} ' -H "$H" $U/users; head -c 90 /tmp/b | tr -d '\n'; echo
done
T=$(python token.py)
printf 'products, 10 at once: '
curl -s --parallel --parallel-immediate --parallel-max 10 -H "Authorization: Bearer $T" -w '%{http_code} '   $(for i in 1 2 3 4 5 6 7 8 9 10; do printf -- '-o /dev/null %s/products ' $U; done); echo

وعند تشغيله على APISIX ثم Kong ثم KrakenD سوف تجد أن المخرج كما يلي:

$ sh checks.sh http://127.0.0.1:19080      # APISIX
none      401 {"message":"Missing JWT token in request"}
valid     200 {"service":"users","name":"alice"}
expired   401 {"message":"failed to verify jwt"}
noexp     200 {"service":"users","name":"alice"}
wrongkey  401 {"message":"failed to verify jwt"}
products, 10 at once: 429 429 429 200 429 200 200 200 200 429

$ sh checks.sh http://127.0.0.1:19081      # Kong
none      401 {"message":"Unauthorized"}
valid     200 {"service":"users","name":"alice"}
expired   401 {"exp":"token expired"}
noexp     401 {"exp":"must be a number"}
wrongkey  401 {"message":"Invalid signature"}
products, 10 at once: 200 429 200 200 200 429 429 200 200 200

$ sh checks.sh http://127.0.0.1:19082      # KrakenD
none      401
valid     200 {"service":"users","name":"alice"}
expired   401
noexp     200 {"service":"users","name":"alice"}
wrongkey  401

والنتائج المهمة هنا ثلاث:

  • التوكن الذي ليس فيه exp قبلته APISIX وKrakenD، والسبب أن claims_to_verify في APISIX يفحص exp «إذا وجد» كما يذكر توثيق الإضافة، فتوكن بلا تاريخ انتهاء صالح للأبد. والحل أن يصدر مزود التوكنات exp دائماً، وأن تستخدم في APISIX مع مزود هوية حقيقي الإضافة openid-connect التي تتحقق من الحقول المطلوبة.
  • Kong رفض التوكن بلا exp لأننا كتبنا claims_to_verify: [exp]، فأزلنا هذا السطر وشغلنا نسخة مؤقتة من Kong على المنفذ 19083، وكانت النتيجة كما يلي:
valid    200
expired  200
noexp    200
  • أي أن إضافة jwt في Kong دون claims_to_verify تقبل التوكن المنتهي نفسه، ومرجع الإضافة يصف الحقل بأنه قائمة الحقول التي يمكن أن يتحقق منها Kong، دون أن يكون exp فيها افتراضياً. وهذا خطأ يمر بسهولة في المراجعة، لذلك اجعل أول اختبار بعد إعداد أي بوابة توكناً منتهياً.
  • الحد في APISIX أعطى خمسة من عشرة في كل مرة، وفي Kong أعطى بين خمسة وسبعة لأن نافذته ثابتة بالثانية، فإذا جاءت الدفعة على حدود ثانيتين مر جزء منها في كل ثانية، وقد شرحنا هذا السلوك في النوافذ الثابتة.
  • في KrakenD مر في البداية طلبان أو ثلاثة فقط من العشرة رغم أن الحد خمسة، فلما أضفنا "client_capacity": 5 صراحة أصبح يمر خمسة أو ستة، فلا تعتمد على القيمة الافتراضية للسعة واكتبها بنفسك.

القياس: الطلبات في الثانية وp99 والمعالج والذاكرة

استخدمنا k6 من Container على نفس الشبكة، بطريقتين لمدة 20 ثانية لكل منهما: خمسون مستخدماً يرسلون بأقصى سرعة لقياس الطلبات في الثانية، ثم 400 طلب في الثانية بالضبط لقياس الزمن عند حمل متساو، وكلاهما على /orders بتوكن صالح، أي أن التحقق من التوقيع والحد يعملان في كل طلب. ونقرأ وقت المعالج الذي استهلكته كل بوابة من ال cgroup الخاص بها قبل كل تجربة وبعدها، وهذا ملف load.js:

// k6 inside the lab network, one route (/orders) with a valid JWT.
//   MODE=max  : 50 virtual users as fast as they can for 20 s (throughput)
//   MODE=rate : a fixed 400 requests per second for 20 s (latency at the same load)
import http from 'k6/http';
import { check } from 'k6';
const max = { executor: 'constant-vus', vus: 50, duration: '20s' };
const rate = { executor: 'constant-arrival-rate', rate: 400, timeUnit: '1s', duration: '20s', preAllocatedVUs: 100 };
export const options = {
  scenarios: { s: __ENV.MODE === 'rate' ? rate : max },
  summaryTrendStats: ['avg', 'p(50)', 'p(95)', 'p(99)', 'max'],
};
const H = { headers: { Authorization: `Bearer ${__ENV.TOKEN}` } };
export default function () {
  const r = http.get(`${__ENV.TARGET}/orders`, H);
  check(r, { '200': (x) => x.status === 200 });
}

وملف bench.sh يشغله ويحسب وقت المعالج:

#!/bin/sh
# ./bench.sh <container> <port> <max|rate>
# Runs load.js from a k6 container (CPUs 2-5) and prints k6's numbers plus the CPU time the gateway used, read from its cgroup.
export MSYS_NO_PATHCONV=1
cpu() { docker exec $1 cat /sys/fs/cgroup/cpuacct/cpuacct.usage | awk '{print int($1/1000)}'; }  # cgroup v1 (Docker Desktop), in microseconds
c0=$(cpu $1)
out=$(docker run --rm --name lab-gw-k6 --cpuset-cpus 2-5 --network lab-gw-tools_default \
  -e TARGET=http://$1:$2 -e MODE=$3 -e TOKEN=$(python token.py) -v "$(pwd)/load.js:/load.js:ro" grafana/k6:2.3.0 run --quiet /load.js 2>&1)
c1=$(cpu $1)
reqs=$(echo "$out" | awk '/http_reqs/ {print $2; exit}')
echo "$out" | grep -E 'http_req_duration|http_reqs|http_req_failed' | grep -v expected_response
echo "    gateway CPU: $(( (c1 - c0) / 1000 )) ms for $reqs requests = $(( (c1 - c0) / reqs )) us per request"

وشغلناه ثلاث جولات، وفي كل جولة نمر على الخدمة مباشرة ثم البوابات الثلاث بالتناوب، حتى يصيب أي ضغط من برامج أخرى على الجهاز كل الأدوات بالتساوي، وهذا مثال من الجولة الثانية لـ APISIX:

## round 2 lab-gw-tools-apisix 9080 max
    http_req_duration..............: avg=16.15ms p(50)=12.87ms p(95)=41ms    p(99)=57.65ms max=111.91ms
    http_req_failed................: 0.00%  0 out of 60820
    http_reqs......................: 60820  3040.523599/s
    gateway CPU: 35303 ms for 60820 requests = 580 us per request

والصورة التالية تبين الوسيط من الجولات الثلاث:

ثلاث لوحات أعمدة. REQUESTS / S, 50 USERS: Nginx 4,887 بالرمادي وهو الخدمة مباشرة، وAPISIX 1,799، وKong 1,162، وKrakenD 1,962. GATEWAY CPU PER REQUEST (us), lower is better: APISIX 706 وKong 1,388 وKrakenD 690. MEMORY, MiB: APISIX 52 ثم 78، وKong 188 ثم 332، وKrakenD 27 ثم 37، والعمود الفاتح عند الخمول والداكن بعد ثلاث جولات
وسيط ثلاث جولات على جهاز واحد، والأرقام للمقارنة بين البوابات على هذا الجهاز فقط

والجدول التالي فيه الأرقام كاملة مع أصغر قيمة وأكبرها بين الجولات، وكلها محلية على Intel i7-7700K مع Docker Desktop، وكل بوابة على معالجين منطقيين:

الخدمة مباشرةAPISIX 3.19.0Kong 3.9.3KrakenD 3.0.0
طلبات في الثانية، 50 مستخدماً (الوسيط، والمدى)4,887 (2,973 إلى 5,640)1,799 (1,370 إلى 3,041)1,162 (779 إلى 1,988)1,962 (1,079 إلى 2,731)
p99 عند أقصى حمل69.6 ms90.7 ms162.4 ms93.7 ms
p50 عند 400 طلب في الثانية0.55 ms2.21 ms4.31 ms1.74 ms
p99 عند 400 طلب في الثانية (المدى)18.2 ms36.5 ms (6.3 إلى 117.2)176.1 ms (71.3 إلى 231.7)241.3 ms (30.5 إلى 375.2)
وقت المعالج لكل طلب عند أقصى حمل129 µs706 µs1,388 µs690 µs
الذاكرة عند الخمول، ثم بعد الجولات5 MiB52، ثم 78 MiB188، ثم 332 MiB27، ثم 37 MiB

وقبل أن تقرأ الأرقام تذكر أن الجهاز كان يشغل في نفس الوقت تجارب أخرى، فالطلبات في الثانية للخدمة نفسها تغيرت بين 2,973 و5,640 من جولة إلى أخرى، وp99 عند 400 طلب في الثانية لـ KrakenD كان 30.5 ms في جولة و375.2 ms في أخرى، وهذا يعني أن الفروق الصغيرة في هذه الأعمدة ضجيج. أما الرقم الأكثر ثباتاً فهو وقت المعالج لكل طلب، لأنه لا يتأثر كثيراً بانتظار البوابة دورها على المعالج، وفيه تتقارب APISIX وKrakenD ويستهلك Kong نحو ضعفهما، وفي الذاكرة يستهلك Kong أضعاف الاثنين. وقد يتساءل البعض: لماذا الأرقام بالآلاف فقط وليس بعشرات الآلاف كما في صفحات الشركات؟ والإجابة أن كل بوابة هنا على معالجين فقط داخل آلة Docker Desktop الافتراضية مع شبكة Docker، وأن كل طلب فيه تحقق من توقيع وحد، والهدف مقارنة الأدوات ببعضها على نفس الجهاز وليس رقماً تعلن عنه، وقد شرحنا كيف تبني اختبار حمل لنظامك أنت في اختبار الأحمال.

إزالة التجربة

docker compose --profile apisix --profile kong --profile krakend down

هذا الأمر يحذف ال Containers الأربعة وشبكة المشروع lab-gw-tools_default فقط، والتجربة لا تنشئ أي Volume.

المقارنة في جدول واحد

KongApache APISIXTykKrakenD CEEnvoy Gateway
الترخيصApache 2.0Apache 2.0MPL 2.0 (عدا مجلد ee)Apache 2.0Apache 2.0
آخر إصدار مفتوح3.9.3 (فرع 3.9)3.19.0v5.15.13.0.0v1.9.2
اللغةLua على NGINXLua على NGINXGoGoEnvoy (C++) مع متحكم Go
مخزن الإعدادPostgreSQL أو ملفetcd أو ملفملفات أو API، مع Redis إلزاميملف فقطKubernetes أو ملف
واجهة إدارة مفتوحةKong Manager OSSمدمجة منذ 3.13لا، تجاريةلالا
OIDC في النسخة المفتوحةلا، إضافة openid-connect للنسخة التجاريةنعمتحقق من JWT، ولم نجربهتحقق من JWT بمفاتيح JWKSنعم
إضافات مخصصةLua وGo وJavaScriptLua، وGo وJava وPython، وWasmGo وJavaScript وPython وLua وgRPCأزيلت في 3.0 المجتمعيةLua وWasm ومعالجة خارجية
في تجربتنا: الذاكرة عند الخمول188 MiB52 MiBلم نقسها27 MiBلم نقسها

ولاحظ أن خانات OIDC والإضافات لم نجربها في المختبر، وإنما من توثيق كل أداة، فتحقق منها في إصدارك قبل أن تعتمد عليها.

كيف تختار؟

  • إذا كنت خارج Kubernetes وتريد بوابة كاملة مفتوحة بكل إضافاتها ولوحة إدارة: Apache APISIX، وهي التي نشرح تشغيلها للإنتاج في الدليل العملي.
  • إذا كان فريقك يريد البوابة ملفاً في Git فقط، ويعيد النشر عند كل تغيير، ولا يحتاج إلى إدارة مفاتيح وهي تعمل: KrakenD، وهو الأخف في الذاكرة.
  • إذا كنت في Kubernetes وتريد الالتزام بـ Gateway API: Envoy Gateway، أو APISIX وKong عبر متحكماتهما.
  • إذا كان عندك Kong يعمل اليوم: لا يوجد سبب للانتقال فوراً، ولكن خطط لما بعد فرع 3.9، فإما ترخيص Enterprise وإما الانتقال إلى أداة أخرى.
  • إذا كان Redis جزءاً من بنيتك أصلاً وتريد بوابة بلغة Go بإدارة مفاتيح عبر API: Tyk، مع تذكر أن لوحة الإدارة تجارية.
  • إذا كنت تحتاج بوابة مطورين بتسجيل ذاتي مفتوحة المصدر ولا مانع من MongoDB وElasticsearch: Gravitee.
  • إذا كان عندك Traefik ومسارات قليلة، وتكفيك Forward Auth مع authentik وحد بسيط: لا تضف بوابة بعد.

أخطاء شائعة

  • الاعتماد على إعدادات JWT الافتراضية دون تجربة توكن منتهي وتوكن بلا exp.
  • تثبيت أداة بحسب مقال قديم، مثل apisix-dashboard المنفصل أو Docker Image لـ Kong أحدث من 3.9 على أنها مفتوحة.
  • قراءة الطلبات في الثانية من صفحة الشركة بدل قياس مساراتك أنت بإضافاتك أنت.
  • ترك الرمز الافتراضي 503 للطلب الزائد، فيعيد الكلاينت المحاولة فوراً بدل أن ينتظر.
  • الحد في كل نسخة من البوابة على حدة دون عداد مشترك، ثم الاستغراب أن الحد الفعلي ضعف المكتوب.

الخلاصة

  • الفرق الحقيقي بين البوابات في مكان الإعداد وما هو مفتوح فعلاً، وليس في عدد الطلبات في الثانية.
  • Kong ما زال Apache 2.0 لكن نسخته المفتوحة متوقفة عند فرع 3.9، وAPISIX كل إضافاتها مفتوحة ولوحتها مدمجة منذ 3.13، وKrakenD 3.0 أزال الإضافات المخصصة من نسخته المجتمعية، وTyk يحتاج Redis ولوحته تجارية.
  • في تجربتنا قبلت APISIX وKrakenD توكناً بلا exp، وقبل Kong التوكن المنتهي إذا لم تطلب منه التحقق من exp.
  • على نفس الجهاز تقاربت APISIX وKrakenD في المعالج لكل طلب، واستهلك Kong نحو الضعف، وكان KrakenD الأقل ذاكرة.
  • اختر APISIX لبوابة كاملة مفتوحة، وKrakenD لبوابة ملف في Git، وEnvoy Gateway داخل Kubernetes.

سجل التحديثات

  • أكتوبر 2026: كتابة المقال، والتجربة على Docker Engine 29.8.0 مع apache/apisix:3.19.0-debian وkong:3.9.3 وkrakend:3.0.0 وnginx:1.31.6-alpine، والحمل بـ grafana/k6:2.3.0.
نشرة عرب رووت | ArabRoot

معرفة تستحق مكاناً في بريدك.

مقالات مختارة وأدوات مفيدة وأفكار لمشروعك القادم، في رسالة واحدة كل أسبوع.

يمكنك إلغاء الاشتراك متى شئت. الخصوصية

تم استلام طلبك. افتح بريدك واضغط رابط التأكيد لإتمام الاشتراك.