APISIX: ترحيل عمليات etcd من HTTP إلى gRPC
February 10, 2023
قيود عمليات etcd القائمة على HTTP في Apache APISIX
عندما كانت etcd في الإصدار 2.x، كانت الواجهة البرمجية التي تعرضها هي HTTP 1 (سنشير إليها من الآن فصاعدًا باسم HTTP). بعد ترقية etcd إلى الإصدار 3.x، تم تغيير البروتوكول من HTTP إلى gRPC. بالنسبة للمستخدمين الذين لا يدعمون gRPC، توفر etcd gRPC-Gateway لتحويل طلبات HTTP إلى gRPC للوصول إلى واجهات gRPC الجديدة.
عندما بدأت APISIX باستخدام etcd، كانت تستخدم واجهة etcd v2 API. في APISIX 2.0 (2020)، قمنا بتحديث متطلبات etcd من الإصدار 2.x إلى 3.x. توافق etcd مع HTTP وفر علينا الجهد لتحديث الإصدار. كل ما كان علينا فعله هو تعديل الكود الخاص بطرق الاستدعاء ومعالجة الاستجابات. ومع ذلك، على مر السنين، واجهنا أيضًا بعض المشاكل المتعلقة بواجهة HTTP API الخاصة بـ etcd. لا تزال هناك بعض الاختلافات الدقيقة. أدركنا أن وجود gRPC-gateway لا يعني أنه يمكنه دعم الوصول عبر HTTP بشكل مثالي.
فيما يلي قائمة بالمشاكل التي واجهناها مع etcd على مدار السنوات القليلة الماضية:
- gRPC-gateway معطل بشكل افتراضي. بسبب إهمال المطورين، فإن التكوين الافتراضي لـ etcd لا يقوم بتمكين gRPC-gateway في بعض المشاريع. لذلك كان علينا إضافة تعليمات في الوثائق للتحقق مما إذا كان gRPC-gateway ممكّنًا في etcd الحالي. انظر https://github.com/apache/apisix/pull/2940.
- بشكل افتراضي، يحدد gRPC حجم الاستجابات إلى 4MB. تقوم etcd بإزالة هذا القيد في SDK الذي توفره ولكن ليس في gRPC-gateway. اتضح أن etcdctl الرسمي (المبني على SDK الذي توفره) يعمل بشكل جيد، ولكن APISIX لا يعمل. انظر https://github.com/etcd-io/etcd/issues/12576.
- نفس المشكلة - هذه المرة مع الحد الأقصى لعدد الطلبات لنفس الاتصال. تنفيذ HTTP2 في Go يحتوي على تكوين
MaxConcurrentStreams
الذي يتحكم في عدد الطلبات التي يمكن أن يرسلها عميل واحد في نفس الوقت، ويتم تعيينه افتراضيًا إلى 250. أي عميل عادةً ما يرسل أكثر من 250 طلبًا في نفس الوقت؟ لذلك كانت etcd تستخدم هذا التكوين دائمًا. ومع ذلك، فإن gRPC-gateway، "العميل" الذي يعمل كوسيط لجميع طلبات HTTP إلى واجهة gRPC المحلية، قد يتجاوز هذا الحد. انظر https://github.com/etcd-io/etcd/issues/14185. - بعد تمكين mTLS في etcd، تستخدم etcd نفس الشهادة كشهادة الخادم والشهادة العميل: شهادة الخادم لـ gRPC-gateway وشهادة العميل عندما يصل gRPC-gateway إلى واجهة gRPC. إذا تم تمكين امتداد مصادقة الخادم على الشهادة، ولكن لم يتم تمكين امتداد مصادقة العميل، فسيؤدي ذلك إلى حدوث خطأ في التحقق من الشهادة. مرة أخرى، الوصول مباشرة باستخدام etcdctl يعمل بشكل جيد (لأن الشهادة لن تستخدم كشهادة عميل في هذه الحالة)، ولكن APISIX لا يعمل. انظر https://github.com/etcd-io/etcd/issues/9785.
- بعد تمكين mTLS، تسمح etcd بتكوين سياسات الأمان لمعلومات مستخدم الشهادات. كما ذكرنا سابقًا، يستخدم gRPC-gateway شهادة عميل ثابتة عند الوصول إلى واجهة gRPC بدلاً من معلومات الشهادة المستخدمة للوصول إلى واجهة HTTP في البداية. وبالتالي، لن تعمل هذه الميزة بشكل طبيعي لأن شهادة العميل ثابتة ولن تتغير. انظر https://github.com/apache/apisix/issues/5608.
يمكننا تلخيص المشاكل في نقطتين:
- gRPC-gateway (وربما محاولات أخرى لتحويل HTTP إلى gRPC) ليست حلًا سحريًا لجميع المشاكل.
- مطورو etcd لا يضعون تركيزًا كافيًا على طريقة التحويل من HTTP إلى gRPC. وأكبر مستخدم لهم، Kubernetes، لا يستخدم هذه الميزة.
لحل هذه المشكلة، نحتاج إلى استخدام etcd مباشرة عبر gRPC، حتى لا نضطر إلى المرور عبر مسار HTTP الخاص بـ gRPC-Gateway المحفوظ للتطابق.
التغلب على تحديات الانتقال إلى gRPC
عيب في lua-protobuf
كانت مشكلتنا الأولى خلال عملية الانتقال هي عيب غير متوقع في مكتبة طرف ثالث. مثل معظم تطبيقات OpenResty، نستخدم lua-protobuf لفك تشفير/تشفير protobuf.
بعد دمج ملف proto الخاص بـ etcd، وجدنا أن هناك تعطلًا عرضيًا في كود Lua، يبلغ عن خطأ "table overflow". لأن هذا التعطل لا يمكن إعادة إنتاجه بشكل موثوق، كانت غريزتنا الأولى هي البحث عن مثال بسيط يمكن إعادة إنتاجه. ومن المثير للاهتمام، إذا كنت تستخدم ملف proto الخاص بـ etcd بمفرده، فلا يمكنك إعادة إنتاج المشكلة على الإطلاق. يبدو أن هذا التعطل يحدث فقط عند تشغيل APISIX.
بعد بعض التصحيح، حددت المشكلة في lua-protobuf عند تحليل حقل oneof
في ملف proto. كان lua-protobuf يحاول تخصيص حجم الجدول مسبقًا عند التحليل، ويتم حساب الحجم المخصص وفقًا لقيمة معينة. كان هناك احتمال معين أن تكون هذه القيمة رقمًا سالبًا. ثم يقوم LuaJIT بتحويل هذا الرقم إلى رقم موجب كبير عند التخصيص، مما يؤدي إلى حدوث خطأ "table overflow". قمت بالإبلاغ عن المشكلة إلى المؤلف وقمنا بالحفاظ على نسخة مع حل مؤقت داخليًا.
كان مؤلف lua-protobuf سريعًا في الاستجابة، حيث قدم إصلاحًا في اليوم التالي وأصدر إصدارًا جديدًا بعد بضعة أيام. اتضح أنه عند تنظيف lua-protobuf لملفات proto التي لم تعد مستخدمة، فاتته تنظيف بعض الحقول، مما أدى إلى رقم سالب غير معقول عند معالجة oneof
لاحقًا. حدثت المشكلة مرة واحدة فقط، ولماذا لم يتم إعادة إنتاجها عند استخدام ملف proto الخاص بـ etcd بمفرده لأنه فاتته خطوات تنظيف هذه الحقول.
محاذاة مع سلوك HTTP
خلال عملية الانتقال، وجدت أن الواجهة الحالية لا تعيد نتيجة التنفيذ بالضبط ولكنها تعيد استجابة HTTP مع حالة الاستجابة والنص. ثم يحتاج المتصل إلى معالجة استجابة HTTP بنفسه.
إذا كانت الاستجابات في gRPC، فيجب تغليفها بقشرة استجابة HTTP لمحاذاة منطق المعالجة. وإلا، يحتاج المتصل إلى تعديل الكود في أماكن متعددة للتكيف مع تنسيق الاستجابة الجديد (gRPC). خاصةً بالنظر إلى أن عمليات etcd القائمة على HTTP القديمة تحتاج أيضًا إلى أن تكون مدعومة في نفس الوقت.
على الرغم من أن إضافة طبقة إضافية لتكون متوافقة مع استجابة HTTP ليست مرغوبة، إلا أننا مضطرون للتعامل معها. بالإضافة إلى ذلك، نحتاج أيضًا إلى القيام ببعض المعالجة على استجابة gRPC. على سبيل المثال، عندما لا توجد بيانات مقابلة، لا تعيد HTTP أي بيانات، ولكن gRPC تعيد جدولًا فارغًا. يجب أيضًا تعديلها لمحاذاة سلوك HTTP.
من الاتصال القصير إلى الاتصال الطويل
في عمليات etcd القائمة على HTTP، تستخدم APISIX اتصالات قصيرة، لذلك لا داعي للتفكير في إدارة الاتصال. كل ما نحتاج إليه هو بدء اتصال جديد كلما احتجنا إليه وإغلاقه عند الانتهاء.
لكن gRPC لا يمكنه القيام بذلك. أحد الأغراض الرئيسية للانتقال إلى gRPC هو تحقيق التعددية، وهو ما لا يمكن تحقيقه إذا تم إنشاء اتصال gRPC جديد لكل عملية. هنا نحتاج إلى شكر gRPC-go، لقدرته المدمجة على إدارة الاتصال، والتي يمكنها إعادة الاتصال تلقائيًا بمجرد انقطاع الاتصال. لذلك يمكننا استخدام gRPC-go لإعادة استخدام الاتصال. ولا نحتاج إلا إلى النظر في متطلبات الأعمال على مستوى APISIX.
يمكن تقسيم عمليات etcd في APISIX إلى فئتين، الأولى هي عمليات CRUD (إضافة، حذف، تعديل، استعلام) على بيانات etcd؛ والثانية هي مزامنة التكوين من مستوى التحكم. بينما يمكن نظريًا مشاركة نفس اتصال gRPC لهاتين العمليتين، قررنا تقسيمهما إلى اتصالين من أجل فصل المسؤوليات. بالنسبة لاتصال عمليات CRUD، نظرًا لأن APISIX يحتاج إلى معالجة بشكل منفصل عند بدء التشغيل وبعد بدء التشغيل، تمت إضافة عبارة if عند الحصول على اتصال جديد. إذا كان هناك عدم تطابق (أي أن الاتصال الحالي تم إنشاؤه عند بدء التشغيل بينما نحتاج إلى اتصال بعد بدء التشغيل)، فسنقوم بإغلاق الاتصال الحالي وإنشاء اتصال جديد. لقد قمت بتطوير طريقة مزامنة جديدة لمزامنة التكوين، بحيث يستخدم كل مورد تيارًا تحت الاتصال الحالي لمراقبة etcd.
فوائد الانتقال إلى gRPC
أحد الفوائد الواضحة بعد الانتقال إلى gRPC هو أن عدد الاتصالات المطلوبة لتشغيل etcd قد انخفض بشكل كبير. عند تشغيل etcd عبر HTTP، كانت APISIX تستخدم فقط اتصالات قصيرة. وعند مزامنة التكوين، كان كل مورد لديه اتصال منفصل.
بعد التبديل إلى gRPC، يمكننا استخدام وظيفة التعددية في gRPC، ويستخدم كل مورد تيارًا واحدًا فقط بدلاً من اتصال كامل. بهذه الطريقة، لم يعد عدد الاتصالات يزداد مع عدد الموارد. بالنظر إلى أن التطوير اللاحق لـ APISIX سيقدم المزيد من أنواع الموارد، على سبيل المثال، أضاف الإصدار 3.1 الأخير secrets
، فإن انخفاض عدد الاتصالات باستخدام gRPC سيكون أكثر أهمية.
عند استخدام gRPC للمزامنة، يكون لكل عملية اتصال واحد فقط (اثنان، إذا تم تمكين نظام التيار الفرعي) لمزامنة التكوين. في الشكل أدناه، يمكننا أن نرى أن العمليتين لديهما أربعة اتصالات، اثنان منها لمزامنة التكوين، وواحد لـ Admin API، والاتصال المتبقي هو للوكيل المميز للإبلاغ عن معلومات الخادم.
للمقارنة، يظهر الشكل أدناه 22 اتصالًا مطلوبًا لاستخدام طريقة مزامنة التكوين الأصلية مع الحفاظ على المعلمات الأخرى دون تغيير. بالإضافة إلى ذلك، هذه الاتصالات هي اتصالات قصيرة.
الفرق الوحيد بين هذين التكوينين هو ما إذا كان gRPC ممكّنًا لعمليات etcd:
etcd:
use_grpc: true
host:
- "http://127.0.0.1:2379"
prefix: "/apisix"
...
بالإضافة إلى تقليل عدد الاتصالات، فإن استخدام gRPC للوصول إلى etcd مباشرة بدلاً من gRPC-gateway يمكن أن يحل سلسلة من المشاكل المعمارية المحدودة مثل مصادقة mTLS المذكورة في بداية المقال. سيكون هناك أيضًا مشاكل أقل بعد استخدام gRPC، لأن Kubernetes يستخدم gRPC لتشغيل etcd. إذا كانت هناك مشكلة، سيتم اكتشافها من قبل مجتمع Kubernetes.
بالطبع، بما أن طريقة gRPC لا تزال جديدة نسبيًا، فإن APISIX ستواجه بالتأكيد بعض المشاكل الجديدة عند تشغيل etcd عبر gRPC. حاليًا، لا يزال التكوين الافتراضي يستخدم الطريقة الأصلية القائمة على HTTP لتشغيل etcd بشكل افتراضي. يمكن للمستخدمين تكوين use_grpc
تحت etcd إلى true في config.yaml
بأنفسهم. يمكنك تجربة ما إذا كانت طريقة gRPC أفضل. سنقوم أيضًا بجمع التعليقات من مصادر مختلفة لتحسين تشغيل etcd القائم على gRPC. عندما نجد أن نهج gRPC ناضج بما فيه الكفاية، سنجعله النهج الافتراضي.
لتحقيق أقصى استفادة من APISIX، أنت بحاجة إلى API7
أنت تحب أداء Apache APISIX، وليس عبء إدارته. يمكنك التركيز على عملك الأساسي دون القلق بشأن التكوين والصيانة والتحديث.
يتكون فريقنا من مبتكري Apache APISIX والمساهمين فيها، والمطورين الأساسيين لـ OpenResty وNGINX، وأعضاء Kubernetes، وخبراء الصناعة في البنية التحتية السحابية. تحصل على أفضل الأشخاص خلف الكواليس.
هل تريد تسريع تطويرك بثقة؟ لتحقيق أقصى دعم لـ APISIX، أنت بحاجة إلى API7. نقدم دعمًا متعمقًا لـ APISIX وحلول إدارة API بناءً على احتياجاتك!
اتصل بنا الآن: https://api7.ai/contact.