في مقال ما هي بوابة 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 وهو المكان الذي تكتب فيه المسارات والمفاتيح ومنه تصل إلى البوابة. والفرق بين الأدوات في الجزء الثاني أكبر بكثير من الفرق في الأول، لأنه يحدد كيف تضيف مساراً، وكيف تأخذ نسخة احتياطية، وماذا يحدث إذا توقف المخزن. والصورة التالية تبين ذلك لخمس أدوات:

ولاحظ أن البوابات المبنية على 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والصورة التالية تبين الوسيط من الجولات الثلاث:

والجدول التالي فيه الأرقام كاملة مع أصغر قيمة وأكبرها بين الجولات، وكلها محلية على Intel i7-7700K مع Docker Desktop، وكل بوابة على معالجين منطقيين:
| الخدمة مباشرة | APISIX 3.19.0 | Kong 3.9.3 | KrakenD 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 ms | 90.7 ms | 162.4 ms | 93.7 ms |
| p50 عند 400 طلب في الثانية | 0.55 ms | 2.21 ms | 4.31 ms | 1.74 ms |
| p99 عند 400 طلب في الثانية (المدى) | 18.2 ms | 36.5 ms (6.3 إلى 117.2) | 176.1 ms (71.3 إلى 231.7) | 241.3 ms (30.5 إلى 375.2) |
| وقت المعالج لكل طلب عند أقصى حمل | 129 µs | 706 µs | 1,388 µs | 690 µs |
| الذاكرة عند الخمول، ثم بعد الجولات | 5 MiB | 52، ثم 78 MiB | 188، ثم 332 MiB | 27، ثم 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.
المقارنة في جدول واحد
| Kong | Apache APISIX | Tyk | KrakenD CE | Envoy Gateway | |
|---|---|---|---|---|---|
| الترخيص | Apache 2.0 | Apache 2.0 | MPL 2.0 (عدا مجلد ee) | Apache 2.0 | Apache 2.0 |
| آخر إصدار مفتوح | 3.9.3 (فرع 3.9) | 3.19.0 | v5.15.1 | 3.0.0 | v1.9.2 |
| اللغة | Lua على NGINX | Lua على NGINX | Go | Go | Envoy (C++) مع متحكم Go |
| مخزن الإعداد | PostgreSQL أو ملف | etcd أو ملف | ملفات أو API، مع Redis إلزامي | ملف فقط | Kubernetes أو ملف |
| واجهة إدارة مفتوحة | Kong Manager OSS | مدمجة منذ 3.13 | لا، تجارية | لا | لا |
| OIDC في النسخة المفتوحة | لا، إضافة openid-connect للنسخة التجارية | نعم | تحقق من JWT، ولم نجربه | تحقق من JWT بمفاتيح JWKS | نعم |
| إضافات مخصصة | Lua وGo وJavaScript | Lua، وGo وJava وPython، وWasm | Go وJavaScript وPython وLua وgRPC | أزيلت في 3.0 المجتمعية | Lua وWasm ومعالجة خارجية |
| في تجربتنا: الذاكرة عند الخمول | 188 MiB | 52 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.