
عند إنشاء عملية CI / CD باستخدام Kubernetes ، توجد أحيانًا مشكلة عدم توافق متطلبات البنية التحتية الجديدة والتطبيق المنقول إليها. على وجه الخصوص ، في مرحلة تجميع التطبيقات ، من المهم الحصول على صورة
واحدة سيتم استخدامها في
جميع بيئات المشروع ومجموعاته. يرتكز هذا المبدأ على الإدارة الصحيحة للحاويات في
رأي Google (لقد
قال techdir هذا أكثر من مرة).
ومع ذلك ، لن تفاجئ أي شخص بمواقف عند استخدام إطار جاهز في كود الموقع ، والذي يفرض استخدامه قيودًا على تشغيله الإضافي. وإذا كان من السهل التعامل معها في "بيئة طبيعية" ، في Kubernetes ، يمكن أن يكون هذا النوع من السلوك مشكلة ، لا سيما عند مواجهتك لأول مرة. على الرغم من أن العقل المبتكر قادر على تقديم حلول للبنية التحتية تبدو واضحة وحتى جيدة جدًا للوهلة الأولى ... من المهم أن نتذكر أن معظم المواقف يمكن ويجب
حلها معماريا .
دعنا نحلل الحلول الشائعة لحل تخزين الملفات ، والتي يمكن أن تؤدي إلى عواقب غير سارة أثناء تشغيل الكتلة ، ونشير أيضًا إلى مسار أكثر صحة.
تخزين ثابت
للتوضيح ، ضع في اعتبارك تطبيق ويب يستخدم منشئ ثابت للحصول على مجموعة من الصور والأنماط والمزيد. على سبيل المثال ، يحتوي إطار Yii PHP على مدير أصول مدمج يقوم بإنشاء أسماء دليل فريدة. وفقًا لذلك ، يكون الإخراج عبارة عن مجموعة من المسارات غير المتقاطعة المتعمدة لإحصائيات الموقع (تم ذلك لعدة أسباب - على سبيل المثال ، لإزالة التكرارات عند استخدام نفس المورد مع العديد من المكونات). لذلك ، خارج الصندوق ، عند الوصول إلى وحدة موارد الويب لأول مرة ، يتم تشكيل الإحصائيات وتخطيطها (في الواقع ، في كثير من الأحيان الارتباطات ، ولكن المزيد عن ذلك لاحقًا) مع دليل جذر شائع فريد لهذا النشر:
webroot/assets/2072c2df/css/…
webroot/assets/2072c2df/images/…
webroot/assets/2072c2df/js/…
ما هو هذا محفوف من حيث الكتلة؟
مثال أبسط
لنأخذ حالة شائعة إلى حد ما عندما يواجه PHP nginx لتوزيع الإحصائيات ومعالجة الاستعلامات البسيطة. أسهل طريقة هي
النشر باستخدام حاويتين:
apiVersion: apps/v1 kind: Deployment metadata: name: site spec: selector: matchLabels: component: backend template: metadata: labels: component: backend spec: volumes: - name: nginx-config configMap: name: nginx-configmap containers: - name: php image: own-image-with-php-backend:v1.0 command: ["/usr/local/sbin/php-fpm","-F"] workingDir: /var/www - name: nginx image: nginx:1.16.0 command: ["/usr/sbin/nginx", "-g", "daemon off;"] volumeMounts: - name: nginx-config mountPath: /etc/nginx/conf.d/default.conf subPath: nginx.conf
في نموذج مبسط ، يتلخص تكوين nginx في التالي:
apiVersion: v1 kind: ConfigMap metadata: name: "nginx-configmap" data: nginx.conf: | server { listen 80; server_name _; charset utf-8; root /var/www; access_log /dev/stdout; error_log /dev/stderr; location / { index index.php; try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; } }
عند الوصول إلى الموقع لأول مرة في حاوية بها PHP ، تظهر الأصول. ولكن في حالة وجود حاويتين داخل نفس الحافظة ، فإن nginx لا يعرف شيئًا عن هذه الملفات الثابتة ، والتي (وفقًا للتهيئة) ينبغي إعطاؤها. نتيجة لذلك ، سيشاهد العميل الخطأ 404 لجميع الطلبات على ملفات CSS و JS. أبسط الحلول هنا هو تنظيم دليل مشترك للحاويات. الخيار البدائي هو عام
emptyDir
:
apiVersion: apps/v1 kind: Deployment metadata: name: site spec: selector: matchLabels: component: backend template: metadata: labels: component: backend spec: volumes: - name: assets emptyDir: {} - name: nginx-config configMap: name: nginx-configmap containers: - name: php image: own-image-with-php-backend:v1.0 command: ["/usr/local/sbin/php-fpm","-F"] workingDir: /var/www volumeMounts: - name: assets mountPath: /var/www/assets - name: nginx image: nginx:1.16.0 command: ["/usr/sbin/nginx", "-g", "daemon off;"] volumeMounts: - name: assets mountPath: /var/www/assets - name: nginx-config mountPath: /etc/nginx/conf.d/default.conf subPath: nginx.conf
الآن يتم توفير الملفات الثابتة التي تم إنشاؤها في الحاوية بواسطة nginx بشكل صحيح. لكن اسمحوا لي أن أذكركم بأن هذا هو الحل البدائي ، مما يعني أنه بعيد عن المثالية ولديه فروقه وأوجه القصور الخاصة به ، والتي تمت مناقشتها أدناه.
تخزين أكثر تقدما
الآن تخيل موقفًا عندما زار أحد المستخدمين موقعًا ، وحمل صفحة تحتوي على الأنماط المتوفرة في الحاوية ، وأثناء قراءته لهذه الصفحة ، قمنا بإعادة نشر الحاوية. أصبح دليل الأصول فارغًا ويتطلب طلبًا إلى PHP لبدء إنشاء أدلة جديدة. ومع ذلك ، حتى بعد ذلك ، ستكون روابط الإحصائيات القديمة قديمة ، مما سيؤدي إلى أخطاء في عرض الإحصائيات.
بالإضافة إلى ذلك ، لدينا على الأرجح مشروع تم تحميله بشكل أو بآخر ، مما يعني أن نسخة واحدة من التطبيق لن تكون كافية:
- مقياس النشر إلى نسختين متماثلتين.
- عند الوصول إلى الموقع لأول مرة في نسخة متماثلة واحدة ، تم إنشاء الأصول.
- في مرحلة ما ، قرر ingress (من أجل تحقيق التوازن بين الحمل) إرسال طلب للحصول على نسخة متماثلة ثانية ، وهذه الأصول ليست موجودة بعد. أو ربما لم تعد هناك ، لأننا نستخدم
RollingUpdate
حاليًا RollingUpdate
.
بشكل عام ، والنتيجة هي الأخطاء مرة أخرى.
لكي لا تفقد الأصول القديمة ، يمكنك تغيير
emptyDir
إلى
hostPath
، بإضافة الإحصائيات فعليًا إلى عقدة نظام المجموعة. هذا النهج سيئ لأننا يجب أن
نربط فعليًا
عقدة نظام معيّنة مع التطبيق الخاص بنا ، لأنه - في حالة الانتقال إلى عقد أخرى - لن يحتوي الدليل على الملفات الضرورية. أو بعض المزامنة الخلفية للدليل بين العقد مطلوب.
ما هي الحلول؟
- إذا سمحت الأجهزة والموارد ، يمكنك استخدام cephfs لتنظيم دليل يمكن الوصول إليه على قدم المساواة لاحتياجات احصائيات. توصي الوثائق الرسمية بمحركات أقراص الحالة الثابتة ، على الأقل تكرار ثلاث مرات ، واتصال قوي "كثيف" بين عقد الكتلة.
- سيكون الخيار الأقل تطلبًا هو تنظيم خادم NFS. ومع ذلك ، فأنت بحاجة إلى التفكير في الزيادة المحتملة في وقت الاستجابة لمعالجة الطلبات من خادم الويب ، وسوف يترك التسامح مع الأخطاء الكثير مما هو مرغوب فيه. إن عواقب الفشل كارثية: فقدان الجبل يدمر الكتلة حتى الموت تحت وطأة هجوم لوس أنجلوس الذي يندفع إلى السماء.
من بين أشياء أخرى ، بالنسبة لجميع الخيارات لإنشاء تخزين مستمر ، ستكون هناك حاجة
لتنظيف الخلفية لمجموعات الملفات القديمة المتراكمة خلال فترة زمنية معينة. قبل الحاويات باستخدام PHP ، يمكنك وضع
DaemonSet من التخزين المؤقت nginx ، والذي سيخزن نسخًا من الأصول لفترة محدودة. يمكن تكوين هذا السلوك بسهولة باستخدام
proxy_cache
مع عمق التخزين في الأيام أو غيغا بايت من مساحة القرص.
إن الجمع بين هذه الطريقة وأنظمة الملفات الموزعة المذكورة أعلاه يوفر مجالًا كبيرًا للخيال ، وقيدًا فقط في الميزانية والإمكانات التقنية لأولئك الذين سينفذونها ويدعمونها. من التجربة ، نقول أنه كلما كان النظام أكثر بساطة ، كان يعمل أكثر استقرارًا. مع إضافة هذه الطبقات ، يصبح الحفاظ على البنية التحتية أكثر صعوبة ، وفي الوقت نفسه ، يزيد الوقت الذي يقضيه في التشخيص والشفاء في حالة حدوث أي أعطال.
توصية
إذا بدا أيضًا أن تنفيذ خيارات التخزين المقترحة غير مبرر لك (معقد ومكلف ...) ، فعليك أن تنظر إلى الموقف من الجانب الآخر. وهي ، حفر في بنية المشروع
والقضاء على المشكلة في التعليمات البرمجية من خلال ربط بعض بنية البيانات الثابتة في الصورة ، تقديم تعريف لا لبس فيه للمحتويات أو إجراء "الاحماء" و / أو تجميع الأصول في مرحلة تجميع الصورة. لذلك نحصل على سلوك يمكن التنبؤ به تمامًا ونفس مجموعة الملفات لجميع البيئات والنسخ المتماثلة للتطبيق قيد التشغيل.
إذا عدنا إلى مثال محدد باستخدام إطار Yii ولم نتعمق في بنيته (وهو ليس غرض المقال) ، فهذا يكفي للإشارة إلى نهجين شائعين:
- قم بتعديل عملية تجميع الصورة بحيث يتم وضع الأصول في مكان يمكن التنبؤ به. لذا ، قدم / نفذ في ملحقات مثل الأصول الثابتة yii2 .
- حدد تجزئات محددة لأدلة الأصول ، كما هو موضح ، على سبيل المثال ، في هذا العرض التقديمي (بدءًا بالشريحة 35). بالمناسبة ، ينصح مؤلف التقرير في النهاية (وليس بدون سبب!) ، بعد تجميع الأصول الموجودة على خادم الإنشاء ، بتحميلها إلى مستودع مركزي (مثل S3) ، أمامه وضع CDN.
ملفات قابلة للتحميل
حالة أخرى ستطلق النار بالتأكيد عند نقل تطبيق إلى كتلة Kubernetes وهي تخزين ملفات المستخدم في نظام الملفات. على سبيل المثال ، لدينا مرة أخرى تطبيق PHP يقبل الملفات عبر نموذج التحميل ، ويفعل شيئًا معهم في هذه العملية ويعيدها.
يجب أن يكون المكان الذي يجب وضع هذه الملفات فيه في حقائق Kubernetes شائعًا في جميع النسخ المتماثلة للتطبيق. بناءً على تعقيد التطبيق والحاجة إلى تنظيم ثبات هذه الملفات ، قد يكون هذا المكان بمثابة خيارات للأجهزة المشتركة المذكورة أعلاه ، ولكن ، كما نرى ، لها عيوبها.
توصية
أحد الحلول هو
استخدام وحدة تخزين متوافقة مع S3 (حتى لو كان هناك نوع من الفئات المستضافة ذاتيًا مثل minio). سيتطلب الانتقال إلى العمل مع S3 تغييرات
على مستوى الرمز ، وقد
كتبنا بالفعل كيف سيتم إرجاع المحتوى على الواجهة الأمامية.
جلسات مخصصة
بشكل منفصل ، تجدر الإشارة إلى تنظيم تخزين جلسات المستخدم. غالبًا ما تكون هذه أيضًا ملفات على القرص ، والتي في سياق Kubernetes ، ستؤدي إلى طلبات ترخيص مستمرة من المستخدم إذا وقع طلبه في حاوية أخرى.
يتم حل جزء من المشكلة من خلال تضمين
stickySessions
on ingress
(يتم دعم الميزة في جميع وحدات التحكم في ingress الشائعة - راجع مراجعتنا لمزيد من التفاصيل) من أجل ربط المستخدم بقرنة معينة مع التطبيق:
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: nginx-test annotations: nginx.ingress.kubernetes.io/affinity: "cookie" nginx.ingress.kubernetes.io/session-cookie-name: "route" nginx.ingress.kubernetes.io/session-cookie-expires: "172800" nginx.ingress.kubernetes.io/session-cookie-max-age: "172800" spec: rules: - host: stickyingress.example.com http: paths: - backend: serviceName: http-svc servicePort: 80 path: /
ولكن هذا لن ينقذك من عمليات النشر المتكررة.
توصية
تتمثل الطريقة الأكثر صحة في نقل التطبيق إلى
تخزين الجلسات في memcached و Redis والحلول المماثلة - بشكل عام ، التخلي عن خيارات الملف تمامًا.
استنتاج
تعتبر حلول البنية التحتية التي تم النظر فيها في النص جديرة بالتطبيق فقط بتنسيق "عكازات" مؤقتة (والتي تبدو أكثر جمالًا باللغة الإنجليزية كحل بديل). قد تكون ذات صلة في المراحل المبكرة من ترحيل التطبيق إلى Kubernetes ، لكن يجب ألا تكون "متجذرة".
الطريقة العامة الموصى بها هي التخلص منها لصالح التحسين المعماري للتطبيق بما يتوافق مع
تطبيق 12-Factor المعروف بالفعل. ومع ذلك ، فإن هذا - جلب التطبيق إلى نموذج عديم الجنسية - يعني حتما أن التغييرات في الكود ستكون مطلوبة ، ومن المهم إيجاد توازن بين قدرات / متطلبات العمل وآفاق تنفيذ المسار المختار والحفاظ عليه.
PS
اقرأ أيضًا في مدونتنا: