البحث في الموقع
المحتوى عن 'من الحاويات إلى kubernetes'.
-
هناك دائمًا طرق متعددة لتعزيز مرونة وأمان تطبيقك Node.js. ويتيح لك استخدام وكيل عكسي مثل Nginx إمكانية تحميل طلبات الرصيد وتخزين محتوى ثابت في ذاكرة التخزين المؤقت وتنفيذ بروتوكول أمان طبقة النقل (TLS) وهي اختصار لـTransport Layer Security. ويضمن تفعيل HTTPS المشفر على خادمك أن الاتصالات من وإلى تطبيقك تبقى آمنة. يتضمن تطبيق وكيل عكسي باستخدام TLS/SSLعلى الحاويات مجموعة من الإجراءات مختلفة عن العمل مباشرة على نظام تشغيل مضيف. على سبيل المثال، إذا كنت تحصل على شهادات من Let’s Encrypt لتطبيق يعمل على الخادم، فستثبّت البرنامج المطلوب مباشرة على مضيفك. أما الحاويات فتسمح لك أن تسلك نهجا مختلفا. باستخدام Docker Compose، يمكنك إنشاء حاويات لتطبيقك، وخادم الويب الخاص بك، وعميل Certbot الذي سيتيح لك الحصول على شهاداتك. ويمكّنك اتباع هذه الخطوات من الاستفادة من مزايا النمطية وقابلية التنقل في سير عملٍ يعتمد على الحاويات. في هذا الدرس، سوف تنشر تطبيق Node.js بوكيل عكسي Nginx باستخدام Docker Compose. وستحصل على شهادات TLS/SSL للمجال المرتبط بتطبيقك كما ستتأكد من حصوله على تصنيف أمان عالي من SSL Labs. وفي الأخير، سوف تنشئ وظيفة cron لتجديد شهاداتك بحيث يظل نطاقك آمنًا. المتطلبات الأساسية لمتابعة هذا البرنامج التعليمي، ستحتاج إلى ما يلي: خادم أوبونتو 18.04 ومستخدم غير جذري ذي صلاحيات sudo وجدار حماية نشط. للحصول على إرشادات حول كيفية إعدادها، يرجى الاطلاع على دليل إعداد الخادم الأولي. Docker مثبت على خادمك. للحصول على إرشادات حول تثبيت Docker، اتبع الخطوتين 1 و 2 للدليل كيفية تثبيت واستخدام Docker على Ubuntu 18.04. للحصول على إرشادات حول تثبيت Compose، اتبع الخطوة الأولى من كيفية تثبيت Docker Compose على أوبونتو 18.04. اسم نطاق مسجل. سيستخدم هذا البرنامج التعليمي example.com طوال الوقت. يمكنك الحصول على نطاق مجاني من Freenom ، أو استخدام مسجل النطاقات الذي تختاره. سجلّا DNS التاليين المعدّين لخادمك. يمكنك متابعة هذه المقدمة إلى DNS في DigitalOcean للحصول على تفاصيل حول كيفية إضافتها إلى حساب DigitalOcean، إذا كنت تستخدمه: سجل A يتضمن example.com يشير إلى عنوان IP العام لخادمك. سجل A يتضمن www.example.com يشير إلى عنوان IP العام لخادمك. الخطوة الأولى: استنساخ واختبار تطبيق Node سوف نبدأ أولًا باستنساخ المستودع الذي يحتوي على شيفرة التطبيق Node، والذي يتضمن الملف Dockerfile الذي سنستخدمه لإنشاء صورة تطبيقنا اعتمادًا على Compose. ويمكننا البدء باختبار التطبيق عبر بنائه وتنفيذه باستخدام الأمر docker run دون وكيل عكسي أو شهادة SSL. في المجلّد الرئيسي للمستخدم غير الجذري، استنسخ مستودع nodejs-image-demo من حساب DigitalOcean Community GitHub. يتضمن مستودع التخزين هذا شيفرة الإعداد الموضح في كيفية إنشاء تطبيق Node.js باستخدام Docker. انسخ المستودع في مجلّد يسمى node_project: git clone https://github.com/do-community/nodejs-image-demo.git node_project انتقل إلى المجلّد node_project: cd node_project يوجد في هذا المجلّد ملفّ Dockerfile يحتوي على إرشادات حول بناء تطبيق Node باستخدام صورة Docker node:10 أضافة إلى محتويات مجلّد مشروعك الحالي. يمكنك إلقاء نظرة على محتويات Dockerfile بكتابة مايلي: cat Dockerfile المخرجات: FROM node:10-alpine RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app WORKDIR /home/node/app COPY package*.json ./ USER node RUN npm install COPY --chown=node:node . . EXPOSE 8080 CMD [ "node", "app.js" ] تبني هذه التعليمات صورة Node عبر نسخ شيفرة المشروع من المجلّد الحالي إلى الحاوية وتثبيت الاعتماديات باستخدام npm install. كما أنها تستخدم أيضًا التخزين المؤقت وطبقات الصور في Docker عبر فصل نسخة من package.json و package-lock.json، التي تحتوي على اعتماديات المشروع المدرجة، عن نسخة باقي شيفرة التطبيق. وتحدد هذه التعليمات كذلك أن تشغيل الحاوية سيكون عبر مستخدم Node غير الجذري بالأذونات المناسبة المعينة على شيفرة التطبيق والمجلّدات node_modules. لمزيد من المعلومات حول أفضل ممارسات Dockerfile و Node image ، يرجى الاطلاع على التوضيحات في الخطوة 3 حول كيفية بناء تطبيق Node.js باستخدام Docker. لاختبار التطبيق بدون SSL، يمكنك بناء الصورة وتعليمها باستخدام Docker والراية t-. سوف نسمّي الصورة node-demo، ولكن تبقى لك الحرية في إعطائها اسمًا آخر: docker build -t node-demo . بمجرد اكتمال عملية البناء، يمكنك عرض قائمة صورك باستخدام docker images: docker images سيظهر لك الإخراج التالي مؤكّدًا بناء صورة التطبيق: REPOSITORY TAG IMAGE ID CREATED SIZE node-demo latest 23961524051d 7 seconds ago 73MB node 10-alpine 8a752d5af4ce 3 weeks ago 70.7MB بعد ذلك، أنشئ الحاوية باستخدام docker run. سوف نستخدم ثلاث رايات مع هذا الأمر: p-: ينشر هذا المنفذ على الحاوية ويوجّهه إلى منفذٍ على مضيفنا. سنستخدم المنفذ 80 على المضيف، ولكن يمكنك تعديل هذا عند الضرورة إذا كان لديك عملية أخرى تشغَل هذا المنفذ. لمزيد من المعلومات، راجع هذه التوضيحات في توثيق Docker حول ربط المنافذ. d-: يشغّل هذا الحاوية في الخلفية. name- : يتيح لنا هذا إعطاء الحاوية اسمًا سهل التذكّر. نفّذ الأمر التالي لإنشاء الحاوية: docker run --name node-demo -p 80:8080 -d node-demo تفقّد الحاويات قيد التشغيل باستخدام docker ps: docker ps سترى الإخراج التالي الذي يؤكد أن حاوية تطبيقك قيد التشغيل: CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4133b72391da node-demo "node app.js" 17 seconds ago Up 16 seconds يمكنك الآن زيارة عنوان نطاقك لاختبار إعداداتك: http://example.com. لا تنس تعويض example.com باسم نطاقك الخاص. سيعرض تطبيقك صفحة الهبوط التالية: الآن وبعد اختبار التطبيق، يمكنك إيقاف الحاوية وحذف الصور. استخدم docker ps مرة أخرى للحصول على معرّف الحاويات CONTAINER ID: docker ps المخرجات: CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4133b72391da node-demo "node app.js" 17 seconds ago Up 16 seconds أوقف الحاوية باستخدام docker stop. تأكد من تعويض المعرِّف CONTAINER ID المدرج هنا بمعرّف تطبيقك CONTAINER ID: docker stop 4133b72391da يمكنك الآن حذف الحاوية المتوقفة وجميع الصور، بما في ذلك الصور غير المستخدمة والمعلّقة، باستخدام docker system prune والراية a-: docker system prune -a اكتب y في الإخراج عند ما تطلب منك الكتابة لتأكيد رغبتك في حذف الحاوية والصور المتوقّفة. يرجى العلم أن هذا سيؤدي أيضًا إلى حذف ذاكرة التخزين المؤقت للبناء. بعد اختبار صورة تطبيقك، يمكنك الانتقال إلى بناء بقية الإعداد باستخدام Docker Compose. الخطوة الثانية: تحديد تكوين خادم الويب يمكننا بعد إنشاء Dockerfile لتطبيقنا، إنشاء ملف تكوين لتشغيل حاوية Nginx الخاصة بنا. سنبدأ بالتكوين ذي الحد الأدنى الذي سيتضمن اسم نطاقنا، و الملف الجذري، ومعلومات الوكيل، وكتلة الموقع (Location block) لتوجيه طلبات Certbot إلى المجلّد well-known. إذ سيضع ملفًا مؤقتًا للتحقق من أن معلومات DNS لنطاقنا توجّه نحو خادمنا. أنشئ أولاً مجلّدًا في مجلّد المشروع الحالي لملف التكوين: mkdir nginx-conf افتح الملف باستخدام nano أو المحرر المفضل لديك: nano nginx-conf/nginx.conf أضف كتلة الخادم التالية لطلبات المستخدم الوكيل إلى حاوية تطبيقك Node ولتوجيه طلبات Certbot إلى المجلّد well-known. لا تنس تعويض example.com باسم نطاقك الخاص: server { listen 80; listen [::]:80; root /var/www/html; index index.html index.htm index.nginx-debian.html; server_name example.com www.example.com; location / { proxy_pass http://nodejs:8080; } location ~ /.well-known/acme-challenge { allow all; root /var/www/html; } } تسمح لنا كتلة الخادم هذه بتشغيل حاوية Nginx كبديل عكسي، والذي سيمرر الطلبات إلى حاوية تطبيقنا Node. سيسمح لنا أيضًا باستخدام المكوّن الإضافي webroot الخاص بـ Certbot للحصول على شهادات لنطاقنا. يعتمد هذا المكوّن الإضافي على طريقة التحقق من HTTP-01، والتي تستخدم طلب HTTP لإثبات أن Certbot يمكنه الوصول إلى الموارد من خادمٍ يوافق اسمَ نطاق معين. احفظ الملف وأغلقه بمجرد الانتهاء من التحرير. لمعرفة المزيد حول خوارزميات Nginx لخادم المواقع وكتلها، يرجى الرجوع إلى هذه المقالة حول فهم خوارزميات Nginx للخادم وحظر المواقع. بعد تحديد تفاصيل تكوين خادم الويب، يمكننا الانتقال إلى إنشاء ملف docker-compose.yml، والذي سيتيح لنا إنشاء خدمات التطبيقات الخاصة بنا وحاوية Certbot التي سنستخدمها للحصول على شهاداتنا. الخطوة الثالثة: إنشاء ملف Docker Compose سيحدد ملف docker-compose.yml خدماتنا، بما في ذلك تطبيق Node وخادم الويب. سوف يحدد تفاصيل مثل وحدات التخزين المسماة، والتي ستكون ضرورية لمشاركة بيانات اعتماد SSL بين الحاويات، وكذلك معلومات الشبكة والمنفذ. سوف يسمح لنا أيضًا بتحديد أوامر محددة لتنفيذها عند إنشاء حاوياتنا. هذا الملف هو المورد المركزي الذي سيحدّد كيف ستعمل خدماتنا معًا. افتح الملف في مجلّدك الحالي: nano docker-compose.yml حدد خدمة التطبيق أولًا: version: '3' services: nodejs: build: context: . dockerfile: Dockerfile image: nodejs container_name: nodejs restart: unless-stopped يتضمن تعريف خدمة nodejs ما يلي: Build: يحدّد هذا خيارات التكوين، بما في ذلك context وdockerfile، والتي ستطبّق عندما ينشئ Docker صورة التطبيق. إذا كنت ترغب في استخدام صورة موجودة من سجلٍّ مثل Docker Hub، فيمكنك استخدام التعليمة image عوض ذلك، مع معلومات حول اسم المستخدم والمستودع وعلامة الصورة. Context: يعرّف هذا سياق البناء لصورة التطبيق. وهو في هذه الحالة مجلّد المشروع الحالي. Dockerfile: يحدّد هذا ملف Dockerfile الذي سيستخدمه Compose للبناء وهو الملف Dockerfile الذي رأيناه في الخطوة الأولى. Image و container_name: تعطي هذه أسماء للصورة والحاوية. restart : هذا يحدّد سياسة إعادة التشغيل. تكون قيمته الافتراضية هي "لا"، لكننا عيّننا الحاوية لإعادة تشغيل ما لم يتم إيقافها. لاحظ أننا لا نُضمّن وصلات الربط (bind mounts) مع هذه الخدمة، وذلك لأن إعدادنا يركز على النشر بدلاً من التطوير. لمزيد من المعلومات، يرجى الاطلاع على توثيق Docker على وصلات الربط والحجوم. لتمكين الاتصال بين التطبيق وحاويات خادم الويب، سنضيف أيضًا شبكة جسرية (bridge network) تسمى app-network أسفل تعريف إعادة التشغيل: services: nodejs: ... networks: - app-network تتيح الشبكة الجسرية التي يعرّفها المستخدم الاتصالَ بين الحاويات على مضيف Docker نفسه. ويعمل ذلك على تبسيط حركة المرور والاتصالات داخل التطبيق، لأنه يفتح جميع المنافذ بين الحاويات على نفس الشبكة الجسرية، مع عدم تعريض أي منافذ للعالم الخارجي. وبالتالي، يمكنك الاختيار في فتح المنافذ التي تحتاجها فقط لعرض خدمات واجهتك الأمامية. بعد ذلك، حدّد خدمة خادم الويب: ... webserver: image: nginx:mainline-alpine container_name: webserver restart: unless-stopped ports: - "80:80" volumes: - web-root:/var/www/html - ./nginx-conf:/etc/nginx/conf.d - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt depends_on: - nodejs networks: - app-network تظل بعض الإعدادات التي حددناها لخدمة nodejs كما هي، لكننا أجرينا أيضًا التغييرات التالية: image: يطلب هذا من Compose سحب أحدث صورة ألبية Nginx (Alpine-based Nginx image) من Docker Hub. لمزيد من المعلومات حول الصور الألبية، يرجى الاطلاع على الخطوة الثالثة من كيفية إنشاء تطبيق Node.js باستخدام Docker. ports: هذا يعرض المنفذ 80 لتفعيل خيارات التكوين التي حددناها في تكوين Nginx. لقد حددنا أيضًا وحدات التخزين المسماة ووصلات الربط التالية: web-root:/var/www/html: سيؤدي ذلك إلى إضافة الأصول الثابتة لموقعنا ، المنسوخة إلى وحدة تخزين تسمى web-root ، إلى مجلّد /var/www/html على الحاوية. ./nginx-conf:/etc/nginx/conf.d: سيؤدي هذا إلى ربط دليل تكوين Nginx على المضيف بالمجلّد ذي الصلة على الحاوية، مع التأكد من أن أي تغييرات نجريها على الملفات الموجودة على المضيف ستنعكس في الحاوية. certbot-etc:/etc/letsencrypt : سيؤدي ذلك إلى ربط شهادات ومفاتيح Let’s Encrypt لنطاقنا على المجلّد المناسب على الحاوية. certbot-var:/var/lib/letsencrypt: يؤدي هذا إلى تحديث مجلّد العمل الافتراضي الخاص بـ Let's Encrypt إلى المجلّد المناسب على الحاوية. بعد ذلك، أضف خيارات التكوين لحاوية certbot. تأكد من تعويض النطاق ومعلومات البريد الإلكتروني باسم نطاقك وبريدك الإلكتروني للاتصال: ... certbot: image: certbot/certbot container_name: certbot volumes: - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt - web-root:/var/www/html depends_on: - webserver command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos يطلب هذا التعريف من Compose سحب صورة certbot/certbot من Docker Hub. كما أنّه يستخدم وحدات التخزين المسمّاة لمشاركة الموارد مع حاوية Nginx، بما في ذلك شهادات النّطاق والمفتاح في certbot-etc، ومجلّد عمل Let's Encrypt في certbot-var، وشيفرة التطبيق في web-root. مرة أخرى، لقد استخدمنا depends_on لتحديد أن حاوية certbot يجب أن تشتغل بمجرد تشغيل الخدمة webserver. لقد أضفنا أيضًا خيار command يحدد الأمر الذي سيتم تنفيذه عند بدء تشغيل الحاوية. وهو يتضمن الأمر الفرعي certonly مع الخيارات التالية: webroot--: هذا يطلب من Certbot استخدام البرنامج المساعد webroot لوضع الملفات في مجلد webroot للتصديق. webroot-path--: يحدد مسار المجلّد webroot. email--: بريدك الإلكتروني المفضل للتسجيل والاسترداد. agree-tos--: هذا يحدد أنك توافق على اتفاقية المشتركين في ACME. no-eff-email--: هذا يخبر Certbot أنك لا ترغب في مشاركة بريدك الإلكتروني مع مؤسسة الحدود الإلكترونية Electronic Frontier Foundation (EFF). لا تتردد في حذف هذا إذا كنت تفضل ذلك. staging--: هذا يخبر Certbot أنك ترغب في استخدام بيئة تشغيل Let's Encrypt للحصول على شهادات الاختبار. يتيح لك استخدام هذا الخيار اختبار خيارات التكوين وتفادي حدود طلبات النطاق المحتملة. لمزيد من المعلومات حول هذه الحدود، يرجى الاطلاع على التوثيق الخاص بمعدّل الحدود في Let's Encrypt. d-: يتيح لك ذلك تحديد أسماء النطاق التي ترغب في تطبيقها على طلبك. في هذه الحالة، ضمّننا example.com و www.example.com. لا تنس تعويضها بتفضيلات النطاق الخاصة بك. كخطوة أخيرة، أضف تعريفات وحدة التخزين والشبكة. وتأكد من تعويض اسم المستخدم هنا بمستخدمك غير الجذري: ... volumes: certbot-etc: certbot-var: web-root: driver: local driver_opts: type: none device: /home/sammy/node_project/views/ o: bind networks: app-network: driver: bridge تتضمن وحدات التخزين المسماة لدينا شهادات Certbot ووحدات تخزين مجلّد العمل، ووحدة تخزين الأصول الثابتة لموقعنا، web-root. في معظم الحالات، يكون برنامج التشغيل الافتراضي لوحدات تخزين Docker هو برنامج التشغيل المحلي، والذي يقبل على Linux خيارات مماثلة للأمر mount. ونستطيع بفضل هذا تحديد قائمة خيارات برنامج التشغيل باستخدام driver_opts الذي يربط مجلّد views على المضيف، والذي يحتوي على أصول ثابتة للتطبيق، مع وحدة التخزين في وقت التشغيل. يمكن بعد ذلك مشاركة محتويات المجلّد بين الحاويات. لمزيد من المعلومات حول محتويات المجلّد views، يرجى الاطلاع على الخطوة الثانية من كيفية إنشاء تطبيق Node.js باستخدام Docker. سيبدو ملف docker-compose.yml بهذا الشكل عند الانتهاء: version: '3' services: nodejs: build: context: . dockerfile: Dockerfile image: nodejs container_name: nodejs restart: unless-stopped networks: - app-network webserver: image: nginx:mainline-alpine container_name: webserver restart: unless-stopped ports: - "80:80" volumes: - web-root:/var/www/html - ./nginx-conf:/etc/nginx/conf.d - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt depends_on: - nodejs networks: - app-network certbot: image: certbot/certbot container_name: certbot volumes: - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt - web-root:/var/www/html depends_on: - webserver command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --staging -d example.com -d www.example.com volumes: certbot-etc: certbot-var: web-root: driver: local driver_opts: type: none device: /home/sammy/node_project/views/ o: bind networks: app-network: driver: bridge بوضعك لتعريفات الخدمة في مكانها الصحيح، ستكون مستعدًّا لبدء تشغيل الحاويات واختبار طلبات الشهادة الخاصة بك. الخطوة الرابعة: الحصول على شهادات SSL وبيانات الاعتماد يمكننا أن نبدأ تشغيل حاوياتنا باستخدام docker-compose up، وهو الأمر الذي سيؤدي إلى إنشاء وتشغيل حاوياتنا وخدماتنا بالترتيب الذي حددناه. إذا كانت طلبات النطاق ناجحة، فستظهر حالة الخروج الصحيحة في الإخراج والشهادات الملائمة موصولة بالمجلد etc/letsencrypt/live/ على حاوية webserver. أنشئ الخدمات باستخدام docker-compose up والراية d- التي ستشغّل حاويات nodejs و webserver في الخلفية: docker-compose up -d سيظهر لك الإخراج التالي الذي يؤكد أن خدماتك أُنشِئت: Creating nodejs ... done Creating webserver ... done Creating certbot ... done تحقق من حالة خدماتك باستخدام docker-compose ps: docker-compose ps إذا كان كل شيء على ما يرام، فيجب أن تكون خدماتك nodejs و webserver في الحالة "Up" وستكون حاوية certbot في الحالة "0": Name Command State Ports ------------------------------------------------------------------------ certbot certbot certonly --webroot ... Exit 0 nodejs node app.js Up 8080/tcp webserver nginx -g daemon off; Up 0.0.0.0:80->80/tcp إذا رأيت شيءًا آخر غير Up في عمود الحالة لخدمات node وwebserver، أو قيمة أخرى غير الصفر 0 في حالة الخروج للحاوية certbot، فاحرص على التحقق من سجلات الخدمة باستخدام الأمر docker-compose logs: docker-compose logs service_name يمكنك الآن التحقق من تركيب بيانات الاعتماد على حاوية webserver باستخدام docker-compose exec: docker-compose exec webserver ls -la /etc/letsencrypt/live إذا كان الطلب ناجحًا، فسترى في الإخراج ما يلي: total 16 drwx------ 3 root root 4096 Dec 23 16:48 . drwxr-xr-x 9 root root 4096 Dec 23 16:48 .. -rw-r--r-- 1 root root 740 Dec 23 16:48 README drwxr-xr-x 2 root root 4096 Dec 23 16:48 example.com الآن بعد تأكّدك من نجاح طلبك، يمكنك تحرير تعريف خدمة certbot من أجل حذف الراية staging--. افتح docker-compose.yml: nano docker-compose.yml ابحث في الملف عن الجزء الذي يتضمن تعريف خدمة certbot، وعوّض الراية staging-- في الخيار command بالراية force-renewal--، والتي ستخبر Certbot أنك تريد طلب شهادة جديدة بنفس النطاقات التي في الشهادة الحالية. يجب أن يبدو تعريف خدمة certbot الآن كما يلي: ... certbot: image: certbot/certbot container_name: certbot volumes: - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt - web-root:/var/www/html depends_on: - webserver command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos يمكنك الآن تنفيذ docker-compose up لإعادة إنشاء حاوية certbot ووحدات التخزين ذات الصلة. سنضمّن هنا أيضًا الخيار no-deps-- لنطلب من Compose تخطي تشغيل خدمة webserver، لأنها قيد التشغيل بالفعل: docker-compose up --force-recreate --no-deps certbot سوف تشير المخرجات التي ستظهر لك إلى نجاح طلبك للشهادة: certbot | IMPORTANT NOTES: certbot | - Congratulations! Your certificate and chain have been saved at: certbot | /etc/letsencrypt/live/example.com/fullchain.pem certbot | Your key file has been saved at: certbot | /etc/letsencrypt/live/example.com/privkey.pem certbot | Your cert will expire on 2019-03-26. To obtain a new or tweaked certbot | version of this certificate in the future, simply run certbot certbot | again. To non-interactively renew *all* of your certificates, run certbot | "certbot renew" certbot | - Your account credentials have been saved in your Certbot certbot | configuration directory at /etc/letsencrypt. You should make a certbot | secure backup of this folder now. This configuration directory will certbot | also contain certificates and private keys obtained by Certbot so certbot | making regular backups of this folder is ideal. certbot | - If you like Certbot, please consider supporting our work by: certbot | certbot | Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate certbot | Donating to EFF: https://eff.org/donate-le certbot | certbot exited with code 0 بحصولك على الشهادات اللازمة، يمكنك الانتقال إلى تعديل تكوين Nginx لتضمين SSL. الخطوة الخامسة: تعديل تكوين خادم الويب وتعريف الخدمة سيتطلب تفعيل SSL في تكوين Nginx الخاص بنا إضافة إعادة توجيه HTTP إلى HTTPS وتحديد شهادة SSL والمواقع الرئيسية. سيتضمن أيضًا تحديد مجموعتنا Diffie-Hellman، والتي سنستخدمها في Perfect Forward Secrecy. ونظرًا لأنك ستعيد إنشاء خدمة webserver لتضمين هذه الإضافات، فيمكنك إيقافها الآن: docker-compose stop webserver أنشئ بعد ذلك مجلّدًا في مجلّد المشروع الحالي من أجل مفتاحك Diffie-Hellman: mkdir dhparam ثم أنشئ مفتاحك باستخدام الأمر openssl: sudo openssl dhparam -out /home/sammy/node_project/dhparam/dhparam-2048.pem 2048 سوف يستغرق الأمر بضع دقائق لإنشاء المفتاح. لإضافة معلومات Diffie-Hellman و SSL ذات الصلة إلى تكوين Nginx، احذف أولاً ملفّ تكوين Nginx الذي أنشأته مسبقًا: rm nginx-conf/nginx.conf افتح نسخة أخرى من الملف: nano nginx-conf/nginx.conf أضف الشيفرة التالية إلى الملف لإعادة توجيه HTTP إلى HTTPS ولإضافة بيانات اعتماد SSL والبروتوكولات وترويسات الأمان (security headers). لاتنس تعويض example.com بنطاقك الخاص: server { listen 80; listen [::]:80; server_name example.com www.example.com; location ~ /.well-known/acme-challenge { allow all; root /var/www/html; } location / { rewrite ^ https://$host$request_uri? permanent; } } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example.com www.example.com; server_tokens off; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; ssl_buffer_size 8k; ssl_dhparam /etc/ssl/certs/dhparam-2048.pem; ssl_protocols TLSv1.2 TLSv1.1 TLSv1; ssl_prefer_server_ciphers on; ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5; ssl_ecdh_curve secp384r1; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8; location / { try_files $uri @nodejs; } location @nodejs { proxy_pass http://nodejs:8080; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always; # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # enable strict transport security only if you understand the implications } root /var/www/html; index index.html index.htm index.nginx-debian.html; } تحدد هذه الكتلة الخاصة بالخادم HTTP هوية الخادم webroot المستخدم لطلبات تجديد Certbot على الملف .well-known/acme-challenge. وتتضمن أيضًا توجيهًا لإعادة الكتابة (rewrite directive) يوجه طلبات HTTP إلى المجلّد الجذري إلى HTTPS. أما الكتلة الخاصة بخادم HTTPS فيفعّل SSL و HTTP2. لقراءة المزيد حول كيفية تكرار HTTP/2 على بروتوكولات HTTP والمزايا التي يمكن أن يوفرها لتحسين أداء موقع الويب، يرجى الاطلاع على مقدمة كيفية إعداد Nginx مع دعم HTTP/2 على أوبونتو 18.04. يتضمن هذا الجزء أيضًا سلسلة من الخيارات للتأكد من أنك تستخدم أحدث بروتوكولات SSL والتشفيرات وأن تدبيس OSCP) OSCP stapling) قيد التشغيل. ويسمح لك تدبيس OSCP بتقديم استجابة ذات ختم زمني (time-stamped) من سلطة الإشهاد الخاصة بك أثناء عملية إنشاء الاتصال TLS الأولي، والتي يمكنها تسريع عملية المصادقة. يحدّد الجزء أيضًا بيانات اعتماد SSL و Diffie-Hellman والمواقع الرئيسية. ختامًا، لقد نقلنا معلومات مرور الوكيل إلى هذه الكتلة، بما في ذلك كتلة موقع تتضمّن توجيه try_files، لتوجيه الطلبات إلى حاوية تطبيق Node.js ذات تسمية بديلة، وكتلة موقع لتلك التسمية البديلة، والتي تتضمن ترويسات الأمان التي ستمكننا من الحصول على تقييمات حول أشياء مثل مواقع اختبار خادم SSL Labs ومواقع الأمان. تتضمن هذه الترويسات خيارات X-Frame-Options و X-Content-Type-Options وسياسة الإحالة وسياسة أمان المحتوى و X-XSS-Protection. يتم التصريح علنًا بالترويسة HSTS (اختصار للعبارة HTTP Strict Transport Security)، فعّل هذه فقط إذا فهمت الآثار المترتبة على ذلك وعملت على تقييم الوظيفة "التحميل المسبق" (preload). احفظ الملف وأغلقه عند الانتهاء من التحرير. قبل إعادة إنشاء خدمة webserver، ستحتاج إلى إضافة بعض الأشياء إلى تعريف الخدمة في ملف docker-compose.yml ، بما في ذلك معلومات المنفذ ذات الصلة لـ HTTPS وتعريف وحدة تخزين Diffie-Hellman. افتح الملف: nano docker-compose.yml أضف في تعريف خدمة webserver تعيين المنفذ التالي ووحدة التخزين المسماة dhparam: ... webserver: image: nginx:latest container_name: webserver restart: unless-stopped ports: - "80:80" - "443:443" volumes: - web-root:/var/www/html - ./nginx-conf:/etc/nginx/conf.d - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt - dhparam:/etc/ssl/certs depends_on: - nodejs networks: - app-network أضف بعد ذلك وحدة تخزين dhparam إلى تعريفات وحدات التخزين الخاصة بك: ... volumes: ... dhparam: driver: local driver_opts: type: none device: /home/sammy/node_project/dhparam/ o: bind على غرار وحدة التخزين web-root، ستركّب وحدة تخزين dhparam مفتاح Diffie-Hellman المخزن على المضيف في حاوية webserver. احفظ الملف وأغلقه عند الانتهاء من التحرير. ثم أعد إنشاء خدمة webserver: docker-compose up -d --force-recreate --no-deps webserver وتحقق من خدماتك باستخدام docker-compose ps: docker-compose ps يجب أن تشاهد الإخراج يشير إلى أن العقدة وخدمات خادم الويب تعمل: Name Command State Ports ---------------------------------------------------------------------------------------------- certbot certbot certonly --webroot ... Exit 0 nodejs node app.js Up 8080/tcp webserver nginx -g daemon off; Up 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp ختامًا، يمكنك زيارة عنوان نطاقك لضمان عمل كل شيء على النحو المنتظر. انتقل في متصفحك إلى https://example.com، مع الحرص على تعويض example.com باسم نطاقك. ستظهر لك صفحة الهبوط التالية: وينبغي أن تشاهد أيضًا رمز القفل في مؤشر أمان متصفحك. إذا كنت ترغب في ذلك، يمكنك الانتقال إلى صفحة الهبوط لاختبار SSL Labs Server أو صفحة اختبار خادم ترويسات الأمان. يجب أن تمنح خيارات التكوين التي أدرجناها لموقعك تصنيفًا على كليهما. الخطوة السادسة: تجديد الشهادات تكون شهادات Let’s Encrypt صالحة لمدة 90 يومًا، لذلك ستحتاج إلى إعداد عملية تجديد تلقائية حرصًا على عدم انقضاء صلاحيتها. إحدى الطرق للقيام بذلك هي إنشاء وظيفة باستخدام أداة جدولة cron. في هذه الحالة، سنقوم بجدولة مهمة cron باستخدام سكربتٍ لتجديد شهاداتنا وإعادة تحميل تكوين Nginx الخاص بنا. افتح سكربتًا بالاسم ssl_renew.sh في مجلّد مشروعك: nano ssl_renew.sh أضف الشيفرة التالية إلى السكربت لتجديد شهاداتك وإعادة تحميل تكوين خادم الويب الخاص بك: #!/bin/bash /usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml run certbot renew --dry-run \ && /usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml kill -s SIGHUP webserver بالإضافة إلى تحديد موقع ملف docker-compose الثنائي، فإننا نحدد أيضًا موقع ملف docker-compose.yml الخاص بنا من أجل تشغيل أوامر docker-compose. في هذه الحالة، نستخدم docker-compose run لتشغيل حاوية certbot ولإبطال الأمر command المقدم في تعريف خدمتنا بأمر آخر: وهو الأمر الفرعي renew، والذي سيقوم بتجديد الشهادات التي تكون على وشك الانتهاء. لقد قمنا بتضمين خيار dry-run-- هنا لاختبار السكربت. ثم يستخدم السكربت kill docker-compose لإرسال إشارة SIGHUP إلى حاوية خادم الويب لإعادة تحميل تكوين Nginx. لمزيد من المعلومات حول استخدام هذه العملية لإعادة تحميل تكوين Nginx، يرجى الاطلاع على منشور مدونة Docker هذا عن نشر صورة Nginx الرسمية باستخدام Docker. أغلق الملف عند الانتهاء من التحرير. ثم اجعله قابلًا للتنفيذ: chmod +x ssl_renew.sh بعد ذلك، افتح ملف الجذر crontab الخاص بك لتنفيذ سكربت التجديد في مجال زمني محدد: sudo crontab -e إذا كانت هذه هي المرة الأولى التي تقوم فيها بتحرير هذا الملف، فسيُطلب منك اختيار محرّر: no crontab for root - using an empty one Select an editor. To change later, run 'select-editor'. 1. /bin/ed 2. /bin/nano <---- easiest 3. /usr/bin/vim.basic 4. /usr/bin/vim.tiny Choose 1-4 [2]: ... أضف السطر التالي في الجزء السفلي من الملف: ... */5 * * * * /home/sammy/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1 سيعيّن ذلك المجال الزمني للوظيفة على كل خمس دقائق، إذ يمكنك اختبار ما إذا كان طلبك للتجديد يعمل على النحو المنشود. لقد أنشأنا أيضًا ملف سجل، cron.log، لتسجيل المخرجات ذات الصلة من الوظيفة. بعد خمس دقائق، تحقق من cron.log لمعرفة ما إذا كان طلب التجديد قد نجح أم لا: tail -f /var/log/cron.log يجب أن يظهر لك الإخراج التالي مؤكِّدًا نجاح التجديد: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates below have not been saved.) Congratulations, all renewals succeeded. The following certs have been renewed: /etc/letsencrypt/live/example.com/fullchain.pem (success) ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates above have not been saved.) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Killing webserver ... done يمكنك الآن تعديل ملف crontab لتعيين مجال زمني يومي. لتنفيذ السكربت كل يوم عند الظهر، على سبيل المثال، يمكنك تعديل السطر الأخير من الملف ليبدو كما يلي: ... 0 12 * * * /home/sammy/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1 ستحتاج أيضًا إلى حذف الخيار dry-run-- من السكربت ssl_renew.sh: #!/bin/bash /usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml run certbot renew \ && /usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml kill -s SIGHUP webserver ستضمن مهمة cron ألا تنقضي صلاحية شهاداتك " Let’s Encrypt" عبر تجديدها عند الاقتضاء. يمكنك أيضًا إعداد تدوير السجل باستخدام الأداة المساعدة Logrotate لتدوير وضغط ملفات السجل. خاتمة لقد استخدمت حاوياتٍ لإعداد وتشغيل تطبيق Node باستخدام وكيل Nginx عكسي. كما حصلت أيضًا على شهادات SSL لنطاق تطبيقك وأعددت وظيفة cron لتجديد هذه الشهادات عند الضرورة. إذا كنت مهتمًا بمعرفة المزيد عن Let's Encrypt Plugins، فيرجى الاطلاع على مقالاتنا حول استخدام المكون الإضافي Nginx أو المكون الإضافي المستقل. يمكنك أيضًا معرفة المزيد حول Docker Compose من خلال الاطلاع على الموارد التالية: كيفية تثبيت Docker Compose على أوبونتو 18.04. كيفية تكوين بيئة اختبار تكامل مستمر مع تكوين Docker و Docker Compose على أوبونتو 16.04. كيفية إعداد Laravel وNginx وMySQL باستخدام Docker. ويعدّ توثيق Docker أيضًا مورداً رائعًا لمعرفة المزيد حول التطبيقات متعددة الحاويات. ترجمة -وبتصرف- للمقال How To Secure a Containerized Node.js Application with Nginx, Let's Encrypt, and Docker Compose لصاحبته Kathleen Juell
-
Kubernetes هو نظام لتشغيل التطبيقات الحديثة في حاويات على نطاق واسع. يستطيع المطورون من خلاله نشر وإدارة التطبيقات عبر مجموعات من الأجهزة. ورغم إمكانية استخدامه لتحسين الكفاءة والموثوقية في إعدادات التطبيقات ذات نسخة واحدة، فقد صُمّم Kubernetes لتشغيل نسخٍ متعددة للتطبيق عبر مجموعات من الأجهزة. يختار العديد من المطورين استخدام مدير الحزمة Helm عند إنشاء عمليات نشر متعددة الخدمات باستخدام Kubernetes. ويبسّط Helm عملية إنشاء موارد Kubernetes متعددة من خلال تقديم مخططات وقوالب تنسق كيفية تفاعل هذه الكائنات. كما يقدم أيضًا مخططات مُهيأة مسبقًا لمشاريع مفتوحة المصدر. في هذا الدرس، سوف تنشر تطبيق Node.js بقاعدة بيانات MongoDB على عنقود Kubernetes باستخدام مخططات Helm. ستستخدم مخطط تعيين النسخة المتماثلة Helm MongoDB الرسمي لإنشاء كائن StatefulSet يتكون من ثلاث علب (pods) وخدمة بدون رأس (stateless) وثلاث طلبات وحدة تخزين ثابتة PersistentVolumeClaims. ستُنشئ أيضًا مخططًا لنشر تطبيق Node.js متعدد النسخ باستخدام صورة تطبيق مخصصة. سيعكس الإعداد الذي ستنشئه في هذا البرنامج التعليمي وظيفة الشيفرة الموضحة في كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose وسيكون نقطة انطلاق جيدة لإنشاء تطبيق Node.js مرن مع مخزن بيانات MongoDB يتناسب مع احتياجاتك. المتطلبات الأساسية لإكمال هذا الدرس، ستحتاج إلى: عنقود +Kubernetes 1.10 مع تفعيل التحكم في الوصول المستند إلى الدور role-based access control (RBAC). سيستخدم هذا الإعداد عنقود DigitalOcean Kubernetes، لكن تبقى لك حرية الاختيار في إنشاء عنقود باستخدام طريقة أخرى. أداة سطر الأوامر kubectl المثبتة على جهازك المحلي أو خادم التطوير وإعدادها للاتصال بعنقودك. يمكنك قراءة المزيد حول تثبيت kubectl في التوثيق الرسمي. Docker مثبت على جهازك المحلي أو خادم التطوير. إذا كنت تعمل على نظام أوبونتو 18.04، اتبع الخطوتين 1 و 2 لكيفية تثبيت واستخدام Docker على أوبونتو 18.04؛ خلاف ذلك، اتبع التوثيق الرسمي للحصول على معلومات حول التثبيت على أنظمة التشغيل الأخرى. تأكد من إضافة مستخدمك غير الجذري إلى مجموعة Docker، كما هو موضح في الخطوة 2 من البرنامج التعليمي المرتبط. حساب Docker Hub. للحصول على نظرة عامة حول كيفية إعداده، راجع هذه المقدمة إلى Docker Hub. مدير الحزمة Helm مثبت على جهازك المحلي أو خادم التطوير مع تثبيت Tiller على نظامك، باتباع الإرشادات الموضحة في الخطوتين 1 و 2 حول كيفية تثبيت البرامج على عناقيد Kubernetes باستخدام مدير الحزمة Helm. الخطوة الأولى: استنساخ وتحزيم التطبيق لاستخدام تطبيقنا على Kubernetes، سنحتاج إلى تحزيمه حتى تتمكن الأداة kubelet من سحب الصورة. سنحتاج قبل تحزيم التطبيق، مع ذلك، إلى تعديل العنوان URI لاتصال MongoDB في شيفرة التطبيق للتأكد من أن تطبيقنا يستطيع الاتصال بعناصر مجموعة النسخ المتماثلة التي سننشئها باستخدام مخطط Helm mongodb-replicaset. ستكون خطوتنا الأولى هي استنساخ مستودع node-mongo-docker-dev من حساب DigitalOcean Community GitHub. يتضمن هذا المستودع شيفرة الإعداد الموضح في كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose، والذي يستخدم تطبيق Node.js لشرح كيفية إعداد بيئة تطوير باستخدام Docker Compose. يمكنك العثور على مزيد من المعلومات حول التطبيق نفسه في سلسلة من الحاويات إلى Kubernetes باستخدام Node.js. انسخ المستودع في مجلّد يسمى node_project: git clone https://github.com/do-community/node-mongo-docker-dev.git node_project انتقل إلى المجلّد node_project: cd node_project يحتوي مجلّد node_project على ملفات ومجلدات لتطبيق معلومات سمك القرش الذي يعمل على مدخلات المستخدم. تمّ تحديثه للعمل على الحاويات وأزيلت منه معلومات التكوين الحساسة والمحددة في شيفرة التطبيق وأُعيد تشكيلها من أجل حقنها عند تنفيذ التطبيق، كما أُلغيَ تحميل حالة التطبيق إلى قاعدة بيانات MongoDB. لمزيد من المعلومات حول تصميم التطبيقات الحديثة عديمة الحالة، يرجى الاطلاع على هيكلة التطبيقات ل Kubernetes وتحديث التطبيقات لKubernetes. عندما ننشر مخطط Helm mongodb-replicaset، فسينشئ ما يلي: كائن StatefulSet بثلاث علب تمثّل مجموعة النسخ المتماثلة MongoDB. سيكون لكل علبة طلب PersistentVolumeClaim مرتبط وسيحتفظ بهوية ثابتة في حالة إعادة الجدولة. مجموعة نسخ متماثلة MongoDB تتكون من العلب الموجودة في StatefulSet. سوف تشمل المجموعة واحدة ابتدائية واثنتان ثانويتان. ستُنسَخ البيانات من العلبة الابتدائية إلى الثانويتين، مما يضمن إتاحة بيانات التطبيق على أعلى مستوى. لكي يتفاعل تطبيقنا مع النسخ المتماثلة لقاعدة البيانات، سيتطلب الأمر تضمين أسماء المضيفين (hostnames) لعناصر مجموعة النسخ المتماثلة بالإضافة إلى اسم النسخة المتماثلة نفسها في العنوان URI لاتصال MongoDB في الشيفرة. يُسمّى الملف الموجود في مخزننا المستنسخ والذي يحدد معلومات اتصال قاعدة البيانات db.js. افتح هذا الملف الآن باستخدام nano أو المحرر المفضل لديك: nano db.js يشتمل الملف حاليًا على قيم ثابتة يمكن الرجوع إليها في اتصال URI لقاعدة البيانات أثناء التشغيل. تُحقًن هذه القيم الثابتة باستخدام خاصية process.env الخاصة ب Node، والتي تُعيد كائنًا يحتوي على معلوماتٍ حول بيئة المستخدم الخاصة بك في وقت التشغيل. يتيح لنا تحديد القيم ديناميكيًا في شيفرة التطبيق فصل الشيفرة عن البنية التحتية الأساسية، وهو أمر ضروري في بيئة ديناميكية وعديمة الحالة (stateless). لمزيد من المعلومات حول إعادة تشكيل شيفرة التطبيق بهذه الطريقة، راجع الخطوة الثانية من كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose. تبدو ثوابت الاتصال URI وسلسلة URI نفسها حاليًا كما يلي: ... const { MONGO_USERNAME, MONGO_PASSWORD, MONGO_HOSTNAME, MONGO_PORT, MONGO_DB } = process.env; ... const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`; ... تماشيا مع الطريقة 12FA، لا نريد ترميز أسماء مضيفي مثيلات النسخ المتماثلة أو اسم مجموعة النسخ المتماثلة في سلسلة URI هذه. يمكننا توسيع الثابت MONGO_HOSTNAME الحالي ليشمل عِدّة أسماء مضيفين لعناصر مجموعة النسخ المتماثلة. لذلك سنترك الأمر على حاله. وسنحتاج، مع ذلك، إلى إضافة مجموعة متماثلة ثابتة إلى قسم الخيارات في سلسلة URI. أضف MONGO_REPLICASET إلى كل من كائن الثابت URI وسلسلة الاتصال: ... const { MONGO_USERNAME, MONGO_PASSWORD, MONGO_HOSTNAME, MONGO_PORT, MONGO_DB, MONGO_REPLICASET } = process.env; ... const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?replicaSet=${MONGO_REPLICASET}&authSource=admin`; ... يتيح استخدام خيار replicaSet في قسم الخيارات في URI تمرير اسم مجموعة النسخ المتماثلة، والتي تتيح لنا بمعية أسماء المضيفين المحدّدة في ثابت MONGO_HOSTNAME الاتصال بعناصر المجموعة. احفظ الملف وأغلقه عند الانتهاء من التحرير. بعد تعديل معلومات اتصال قاعدة البيانات الخاصة بك للعمل مع مجموعات النسخ المتماثلة، يمكنك الآن تحزيم تطبيقك، وبناء الصورة باستخدام الأمر docker build، ودفعها إلى Docker Hub. ابدأ ببناء الصورة باستخدام Docker والراية t- التي تتيح لك تعليم الصورة باسم لا يُنسى. في هذه الحالة، علّم الصورة باسم مستخدمك Docker Hub وسَمِّها node-replicas أو اسمًا تختاره أنت: docker build -t your_dockerhub_username/node-replicas . تحدد النقطة . في الأمر أن سياق البناء هو المجلّد الحالي. سوف يستغرق الأمر دقيقة أو دقيقتين لبناء الصورة. بمجرد اكتماله، تحقق من صورك: docker images سوف يظهر لك الإخراج التالي: REPOSITORY TAG IMAGE ID CREATED SIZE your_dockerhub_username/node-replicas latest 56a69b4bc882 7 seconds ago 90.1MB node 10-alpine aa57b0242b33 6 days ago بعد ذلك، سجّل الدخول إلى حساب Docker Hub الذي أنشأته في المتطلبات الأساسية: docker login -u your_dockerhub_username عندما يطلب منك ذلك، أدخل كلمة مرور حساب Docker Hub. سيؤدي التسجيل بهذه الطريقة إلى إنشاء ملف ~/.docker/config.json في المجلّد الرئيسي لمستخدمك غير الجذري باستخدام بيانات اعتماد Docker Hub. ادفع صورة التطبيق إلى Docker Hub باستخدام الأمر docker push. ولا تنس أن تعوّض yourdockerhubusername باسم مستخدمك Docker Hub: docker push your_dockerhub_username/node-replicas لديك الآن صورة للتطبيق يمكنك سحبها لتشغيل التطبيق المنسوخ باستخدام Kubernetes. ستكون الخطوة التالية هي إعداد بارامترات محدّدة لاستخدامها مع مخطط Helm ل MongoDB. الخطوة الثانية: إنشاء أسرار لمجموعة النسخ المتماثلة MongoDB يوفر المخطط stable/mongodb-replicaset خيارات مختلفة عندما يتعلق الأمر باستخدام الأسرار، وسننشئ منها خيارين لاستخدامهما في نشر المخطط: سرٌّ (secret) لمجموعة ملفات النسخ المتماثلة سيعمل ككلمة مرور مشتركة بين عناصر مجموعة النسخ المتماثلة، مما يسمح بتصديق الهوية لأعضاء آخرين. سرٌّ للمستخدم المشرف في MongoDB، الذي سيتم إنشاؤه كمستخدم الجذر في قاعدة بيانات المشرف. سيتيح هذا الدور بإنشاء مستخدمين لاحقًا بأذونات محدودة عند نشر تطبيقك على الإنتاج. بإنشائنا لهذه الأسرار، سنكون قادرين على تعيين قيم المعاملات المفضلة لدينا في ملف قيم مخصص وإنشاء كائن StatefulSet ونسخة متماثلة MongoDB مع مخطط Helm. دعنا أولًا ننشئ ملف keyfile. سنستخدم الأمر openssl مع خيار rand لإنشاء سلسلة عشوائية بحجم 756 بايت لملف keyfile: openssl rand -base64 756 > key.txt سيُشفَّر الإخراج الذي أُنشِئ باستخدام الأمر base64، مما يضمن نقل بيانات موحد، كما سيُعاد توجيهه إلى ملف يسمى key.txt، باتباع الإرشادات الواردة في وثائق مصادقة تخطيط mongodb-replicaset. يجب أن يتراوح طول المفتاح نفسه بين 6 محارف و 1024 محرفًا، ويتألف فقط من المحارف في مجموعة base64. يمكنك الآن إنشاء سر يسمى keyfilesecret على هذا الملف باستخدام kubectl create: kubectl create secret generic keyfilesecret --from-file=key.txt سيُنشئ هذا كائنًا سرًّا في المجال الاسمي الافتراضي، نظرًا لأننا لم ننشئ مجالًا اسميًا محددا لإعدادنا. سيظهر لك الإخراج التالي مشيرًا إلى إنشاء سرّك: secret/keyfilesecret created احذف key.txt: rm key.txt إذا كنت ترغب في حفظ الملف بدلاً من ذلك، فتأكد من تقييد أذوناته وإضافته إلى ملفك gitignore. لإبقائه خارج تحكم الإصدارات. بعد ذلك، أنشئ سرًّا لمستخدمك المشرف في MongoDB. ستكون الخطوة الأولى هي تحويل اسم المستخدم وكلمة المرور المطلوبين إلى base64. ابدأ بتحويل اسم مستخدم قاعدة بياناتك: echo -n 'your_database_username' | base64 دوّن القيمة التي تظهر في الإخراج. بعد ذلك، حوّل كلمة مرورك: echo -n 'your_database_password' | base64 دوّن القيمة الظاهرة في الإخراج هنا أيضا. افتح ملف السر: nano secret.yaml ملاحظة: تُحدّد كائنات Kubernetes عادة باستخدام YAML، الذي يمنع بصرامة علامات التبويب ويتطلب مسافتين للمسافة البادئة. إذا كنت ترغب في التحقق من تنسيق أي من ملفاتك YAML، فيمكنك استخدام linter أو اختبار صحّة صياغة (syntax) تركيبك باستخدام kubectl create مع الرايتين dry-run-- و validate--: kubectl create -f your_yaml_file.yaml --dry-run --validate=true بشكل عام، من المستحسن التحقق من صحة الصياغة قبل إنشاء الموارد باستخدام kubectl. أضف الشيفرة التالية إلى الملف لإنشاء سرّ يحدد المستخدم وكلمة المرور مع القيم المشفرة التي أنشأتها للتو. لا تنس تعويض القيم الوهمية هنا باسم المستخدم وكلمة المرور المشفرة: apiVersion: v1 kind: Secret metadata: name: mongo-secret data: user: your_encoded_username password: your_encoded_password نحن نستخدم هنا أسماء المفاتيح التي يتوقعها مخطط mongodb-replicaset: المستخدم وكلمة المرور. ولقد سمّينا كائن السرّ mongo-secret، ولكن تستطيع تسميته كما تشاء. احفظ الملف وأغلقه عند الانتهاء من التحرير. أنشئ كائن السرّ باستخدام الأمر التالي: kubectl create -f secret.yaml سيظهر لك في الإخراج ما يلي: secret/mongo-secret created يمكنك مرة أخرى إما حذف secret.yaml أو تقييد أذوناته وإضافته إلى ملفك gitignore.. بعد إنشاء كائنات السرّ، يمكنك الانتقال إلى تحديد قيم المعاملات التي ستستخدمها مع مخطط mongodb-replicaset وإنشاء نشر MongoDB. الخطوة الثالثة: تكوين المخطّط MongoDB Helm وإنشاء النشر يأتي Helm مع مستودع نشط ومحفوظ يدعى stable والذي يحتوي على المخطط الذي سنستخدمه: mongodb-replicaset. لاستخدام هذا المخطط مع الأسرار التي أنشأناها للتو، سننشئ ملفًا به قيم معاملات التكوين يسمى mongodb-values.yaml ثم نثبت المخطط باستخدام هذا الملف. سوف يعكس ملفنا mongodb-values.yaml إلى حد كبير ملف value.yaml الافتراضي في مستودع تخزين المخطط mongodb-replicaset. ومع ذلك، سنجري التغييرات التالية على ملفنا: سنعيّن المعامل auth على القيمة true لنضمن تفعيل التفويض (authorization) عند بدء اشتغال مثيلات قاعدة البيانات. هذا يعني أنه سيُطلب من جميع العملاء تصديق الهوية للوصول إلى موارد وعمليات قاعدة البيانات. سنضيف معلومات حول الأسرار التي أنشأناها في الخطوة السابقة حتى يتمكن المخطط من استخدام هذه القيم لإنشاء مجموعة النسخ المتماثلة keyfile والمستخدم المشرف. سنقلّل من سعة وحدات التخزين الثابتة PersistentVolumes المرتبطة بكل علبة Pod في مجموعة StatefulSet لاستخدام الحد الأدنى القابل للتطبيق في وحدات التخزين DigitalOcean ، المحدّد في 1 جيجابايت، رغم أنك تبقى حرًّا في تعديل هذه القيمة لتلبية متطلبات التخزين الخاصة بك. يجب عليك أولاً قبل كتابة ملف mongodb-values.yaml، ومع ذلك، التحقق من أنّك لديك صنف StorageClass أُنشِئ وأُعِدّ لتوفير موارد التخزين. سيكون لكل علبة موجودة في قاعدة بياناتك StatefulSet هوية ملازمة وما يرتبط بها من طلبات PersistentVolumeClaim، والتي ستوفر PersistentVolume ديناميكيًا للعلبة. إذا أُعيدت جدولة العلبة، فستثبّت PersistentVolume على أي عقدة (node) تُجدول عليها العلبة Pod (رغم أنه ينبغي حذف كل وحدة تخزين يدويًا إذا حذفت العلبة Pod أو StatefulSet المقترن بها نهائيًا). وبما أننا نعمل على DigitalOcean Kubernetes، فإن مزود StorageClass الافتراضي الخاص بنا يعيّن على dobs.csi.digitalocean.com أي وحدة التخزين DigitalOcean. ويمكننا التحقق من ذلك بكتابة: kubectl get storageclass إذا كنت تعمل عل عنقود DigitalOcean، فسترى في الإخراج مايلي: NAME PROVISIONER AGE do-block-storage (default) dobs.csi.digitalocean.com 21m إذا كنت لا تعمل مع مجموعة DigitalOcean، فستحتاج إلى إنشاء StorageClass وتكوين مزود من اختيارك. للحصول على تفاصيل حول كيفية القيام بذلك، يرجى الاطلاع على الوثائق الرسمية. الآن وبعد التأكد من تكوين StorageClass، افتح الملفّ mongodb-values.yaml لتحريره: nano mongodb-values.yaml سوف نعيّن القيم في هذا الملف للقيام بما يلي: تفعيل التفويض. تحديد مراجع لملفك keyfilesecret وكائناتك mongo-secret. تحديد القيمة Gi1 لـ PersistentVolumes. تعيين اسم مجموعة النسخ المتماثلة على db. تحديد 3 نسخ متماثلة للمجموعة. تثبيت صورة mongo بأحدث إصدار. انسخ الشيفرة التالية إلى الملف: replicas: 3 port: 27017 replicaSetName: db podDisruptionBudget: {} auth: enabled: true existingKeySecret: keyfilesecret existingAdminSecret: mongo-secret imagePullSecrets: [] installImage: repository: unguiculus/mongodb-install tag: 0.7 pullPolicy: Always copyConfigImage: repository: busybox tag: 1.29.3 pullPolicy: Always image: repository: mongo tag: 4.1.9 pullPolicy: Always extraVars: {} metrics: enabled: false image: repository: ssalaues/mongodb-exporter tag: 0.6.1 pullPolicy: IfNotPresent port: 9216 path: /metrics socketTimeout: 3s syncTimeout: 1m prometheusServiceDiscovery: true resources: {} podAnnotations: {} securityContext: enabled: true runAsUser: 999 fsGroup: 999 runAsNonRoot: true init: resources: {} timeout: 900 resources: {} nodeSelector: {} affinity: {} tolerations: [] extraLabels: {} persistentVolume: enabled: true #storageClass: "-" accessModes: - ReadWriteOnce size: 1Gi annotations: {} serviceAnnotations: {} terminationGracePeriodSeconds: 30 tls: enabled: false configmap: {} readinessProbe: initialDelaySeconds: 5 timeoutSeconds: 1 failureThreshold: 3 periodSeconds: 10 successThreshold: 1 livenessProbe: initialDelaySeconds: 30 timeoutSeconds: 5 failureThreshold: 3 periodSeconds: 10 successThreshold: 1 يوجد هنا تعليق على المُعامل persistentVolume.storageClass: سيؤدي حذف التعليق وتعيين قيمته على "-" إلى تعطيل التزويد الديناميكي. في حالتنا هذه، نظرًا لأننا نترك هذه القيمة غير محددة، فإن المخطط سيختار المزود الافتراضي، أي dobs.csi.digitalocean.com. لاحظ أيضًا وضع الولوج accessMode المقترن بالمفتاح persistentVolume: يعني ReadWriteOnce أن وحدة التخزين المتوفرة ستكون للقراءة والكتابة بواسطة عقدة واحدة فقط. يرجى الاطلاع على التوثيق لمزيد من المعلومات حول أوضاع الولوج المختلفة. لمعرفة المزيد حول المعاملات الأخرى المضمنة في الملف، راجع جدول التكوين المضمّن في المستودع. احفظ الملف وأغلقه عند الانتهاء من التحرير. قبل نشر مخطط mongodb-replicaset، ستحتاج إلى تحديث المستودع الثابت stable باستخدام الأمر helm repo update: helm repo update سيستخرج هذا أحدث معلومات المخطط من المستودع الثابت. أخيرًا، ثبّت المخطط باستخدام الأمر التالي: helm install --name mongo -f mongodb-values.yaml stable/mongodb-replicaset ملاحظة: قبل تثبيت أحد المخططات، يمكنك تنفيذ helm install باستخدام الخيارين run dry-- و debug-- للتحقق من البيانات التي أُنشئت لإصدارك: helm install --name your_release_name -f your_values_file.yaml --dry-run --debug your_chart لاحظ أننا نسمي إصدار Helm بالاسمmongo. سوف يشير هذا الاسم إلى هذا النشر المحدد للمخطط بخيارات التكوين التي حددناها. لقد أشرنا إلى هذه الخيارات من خلال تضمين الراية f- وملفنا mongodb-values.yaml. لاحظ أيضًا أنه نظرًا لأننا لم نضمّن الراية --namespace مع helm install، سيكون إنشاء كائنات المخطط في المجال الاسمي الافتراضي. بمجرد إنشاء الإصدار، ستظهر حالته في الإخراج، إلى جانب معلومات حول الكائنات التي أُنشِئت وإرشادات للتفاعل معها: NAME: mongo LAST DEPLOYED: Tue Apr 16 21:51:05 2019 NAMESPACE: default STATUS: DEPLOYED RESOURCES: ==> v1/ConfigMap NAME DATA AGE mongo-mongodb-replicaset-init 1 1s mongo-mongodb-replicaset-mongodb 1 1s mongo-mongodb-replicaset-tests 1 0s ... يمكنك الآن التحقق من إنشاء علبك pods باستخدام الأمر التالي: kubectl get pods سيظهر لك في الإخراج مايلي عند إنشاء العلب: NAME READY STATUS RESTARTS AGE mongo-mongodb-replicaset-0 1/1 Running 0 67s mongo-mongodb-replicaset-1 0/1 Init:0/3 0 8s تشير المخرجات READY و STATUS هنا إلى أن العلب الموجودة في StatefulSet ليست جاهزة تمامًا: لا تزال حاويات التهيئة المرتبطة بحاويات العلب قيد التشغيل. ولأن إنشاء عناصر StatefulSet يتمّ وفق ترتيب تسلسلي، يجب أن تكون كل علبة في StatefulSet قيد التشغيل وجاهزة قبل إنشاء العلبة التالية. بعد إنشاء العلب وتشغيل جميع الحاويات المرتبطة بها، سيظهر لك هذا الإخراج: NAME READY STATUS RESTARTS AGE mongo-mongodb-replicaset-0 1/1 Running 0 2m33s mongo-mongodb-replicaset-1 1/1 Running 0 94s mongo-mongodb-replicaset-2 1/1 Running 0 36s يشير Running STATUS إلى أن علبك مرتبطة بالعقد وأن الحاويات المرتبطة بتلك العلب تعمل. ويشير READY إلى عدد الحاويات الموجودة في العلب. لمزيد من المعلومات، يرجى الرجوع إلى وثائق دورة حياة العلبة. ملحوظة إذا رأيت مراحل غير متوقعة في العمود STATUS، فتذكر أنه يمكنك استكشاف الأخطاء وإصلاحها باستخدام الأوامر التالية: kubectl describe pods your_pod kubectl logs your_pod لكل علبة موجودة في StatefulSet اسم يجمع اسم StatefulSet مع الرقم الترتيبي للعلبة. ونظرًا لأننا أنشأنا ثلاث نسخ متماثلة، ترقَّم عناصر StatefulSet من 0 إلى 2، ولكل منها إدخال DNS ثابت يتكون من العناصر التالية: $(statefulset-name)-$(ordinal).$(service name).$(namespace).svc.cluster.local. في حالتنا هذه، لكلّ من StatefulSet والخدمة بدون ترويسة التي أنشأها مخطط mongodb-replicaset نفس الاسم: kubectl get statefulset NAME READY AGE mongo-mongodb-replicaset 3/3 4m2s kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 42m mongo-mongodb-replicaset ClusterIP None <none> 27017/TCP 4m35s mongo-mongodb-replicaset-client ClusterIP None <none> 27017/TCP 4m35s هذا يعني أن العنصر الأول في StatefulSet سيحصل على إدخال DNS التالي: mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local ولأننا نحتاج إلى أن يتصل تطبيقنا بكل نسخة من إصدارات MongoDB، فمن الضروري أن تتوفر لدينا هذه المعلومات حتى نتمكن من التواصل مباشرة مع العلب، وليس مع الخدمة. عندما ننشئ مخطط Helm للتطبيق المخصص، سنمرّر إدخالات DNS لكل علبة إلى تطبيقنا باستخدام متغيرات البيئة. بعد تشغيل مثيلات قاعدة بياناتك ، يمكنك البدء في إنشاء المخطط لتطبيقك Node. الخطوة الرابعة: إنشاء مخطط تطبيق مخصّص وتكوين المعاملات سنعمل على إنشاء مخطط Helm مخصّص لتطبيق Node وتعديل الملفات الافتراضية في مجلّد المخطّط الأساسي حتى نتيح للتطبيق العمل على مجموعة النسخ المتماثلة التي أنشأناها للتو. وسننشئ كذلك ملفات لتحديد خريطة الإعداد ConfigMap وكائنات السرّ لتطبيقنا. ابدأ أولاً بإنشاء مجلّد مخطط جديد يسمى nodeapp باستخدام الأمر التالي: helm create nodeapp سيؤدي هذا إلى إنشاء مجلّد بالاسم nodeapp في المجلد ~/node_project بالموارد التالية: ملف Chart.yaml يحتوي على معلومات أساسية حول المخطط. ملف value.yaml يتيح لك تعيين قيم معاملات محددة، كما فعلت مع نشر MongoDB. ملف helmignore. مع أنماط الملفات والمجلّد التي سيتم تجاهلها عند تحزيم المخططات. مجلّد templates/ بملفات القوالب التي سيولّدها Kubernetes. مجلّد templates/tests/ لملفات الاختبار. مجلّد charts/ لأي مخططات يعتمد عليها هذا المخطط. value.yaml هو الملف الأول الذي سنعدّله من هذه الملفات الافتراضية. افتح هذا الملف الآن: nano nodeapp/values.yaml تشمل القيم التي سنضعها هنا ما يلي: عدد النسخ المتماثلة. صورة التطبيق التي نريد استخدامها. في حالتنا، ستكون هذه هي صورة النسخ المتماثلة للعقدة التي أنشأناها في الخطوة الأولى. نوع الخدمة. في هذه الحالة، سنحدد LoadBalancer لإنشاء نقطة وصول إلى تطبيقنا لأغراض الاختبار. ونظرًا لأننا نعمل على عنقود DigitalOcean Kubernetes، فسيؤدي ذلك إلى إنشاء موازن تحميل DigitalOcean عند نشر مخططنا. يمكنك في الإنتاج تكوين مخططك لاستخدام موارد Ingress و Controllers لتوجيه حركة المرور إلى خدماتك. المنفذ المستهدف targetPort لتحديد المنفذ على العلبة Pod حيث سيتم الكشف عن تطبيقنا. لن ندخل متغيرات البيئة في هذا الملف. وبدلاً من ذلك، سننشئ قوالب لـ ConfigMap وكائنات السرّ ونضيف هذه القيم إلى بيان نشر التطبيق (application Deployment manifest) ، الموجود في ~/node_project/nodeapp/templates/deployment.yaml. أضف تكوين القيم التالية في ملف values.yaml: # Default values for nodeapp. # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 3 image: repository: your_dockerhub_username/node-replicas tag: latest pullPolicy: IfNotPresent nameOverride: "" fullnameOverride: "" service: type: LoadBalancer port: 80 targetPort: 8080 ... احفظ الملف وأغلقه عند الانتهاء من التحرير. بعد ذلك، افتح ملف secret.yaml في المجلّد nodeapp/templates: nano nodeapp/templates/secret.yaml أضف في هذا الملف قيمًا لثوابت التطبيق MONGOUSERNAME و MONGOPASSWORD. هذه هي الثوابت التي يتوقع تطبيقك الوصول إليها أثناء التشغيل، كما هو محدّد في db.js، ملف اتصال قاعدة البيانات. وأثناء إضافتك لقيم هذه الثوابت، لا تنس استخدام القيم المرمّزة base64 التي استخدمتها سابقًا في الخطوة الثانية عند إنشاء الكائن mongo-secret. إذا كنت بحاجة إلى إعادة إنشاء هذه القيم، يمكنك العودة إلى الخطوة الثانية ونفّذ الأوامر ذات الصلة مرة أخرى. أضف الشيفرة التالية إلى الملف: apiVersion: v1 kind: Secret metadata: name: {{ .Release.Name }}-auth data: MONGO_USERNAME: your_encoded_username MONGO_PASSWORD: your_encoded_password يعتمد اسم كائن السرّ هذا على اسم إصدار Helm، والذي ستحدّده عند نشر مخطط التطبيق. احفظ الملف وأغلقه عند الانتهاء. بعد ذلك، افتح ملفًا لإنشاء ConfigMap لتطبيقك: nano nodeapp/templates/configmap.yaml سوف نحدد في هذا الملف المتغيرات المتبقية التي ينتظرها تطبيقنا: MONGO_HOSTNAME و MONGO_PORT و MONGO_DB و MONGO_REPLICASET. سيتضمن متغير MONGO_HOSTNAME إدخال DNS لكل مثيل في مجموعة النسخ المتماثلة، إذ أن هذا هو ما يتطلبه عنوان URI لاتصال MongoDB. وفقًا لتوثيق Kubernetes، عندما يقوم أحد التطبيقات بإجراء اختبارات الصلاحية والاستعداد، يجب استخدام سجلات SRV عند الاتصال بـالعلب Pods. وكما تطرقنا إليه في الخطوة الثالثة، تتبع سجلات SRV للعلبة هذا النموذج: $(statefulset-name)-$(ordinal).$(service name).$(namespace).svc.cluster.local. ونظرًا لأن تطبيق StatefulSet يجري عمليات فحص الثبات والاستعداد، فسيتوجب علينا استخدام هذه المعرّفات الثابتة عند تحديد قيم المتغير MONGO_HOSTNAME. أضف الشيفرة التالية إلى الملف لتعريف متغيرات MONGOHOSTNAME و MONGOPORT و MONGODB و MONGOREPLICASET. وتبقى لك الحرية في استخدام اسم آخر لقاعدة بيانات MONGODB، ولكن يجب أن تكتب قيمك MONGOHOSTNAME و MONGO_REPLICASET مثلما تظهر هنا: apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-config data: MONGO_HOSTNAME: "mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local,mongo-mongodb-replicaset-1.mongo-mongodb-replicaset.default.svc.cluster.local,mongo-mongodb-replicaset-2.mongo-mongodb-replicaset.default.svc.cluster.local" MONGO_PORT: "27017" MONGO_DB: "sharkinfo" MONGO_REPLICASET: "db" بما أننا أنشأنا بالفعل كائن StatefulSet ومجموعة النسخ المتماثلة، فيجب أن تظهر أسماء المضيفين الواردة هنا في ملفك تمامًا كما تظهر في هذا المثال. إذا دمّرت هذه الكائنات وأعدت تسمية إصدار Helm ل MongoDB ، فستحتاج إلى مراجعة القيم المضمنة في ConfigMap. ينطبق الشيء نفسه على MONGO_REPLICASET، حيث حدّدنا اسم مجموعة النسخ المتماثلة بإصدار MongoDB. لاحظ أيضًا أن القيم المذكورة هنا موضوعة في شكل اقتباس، وهو ما يتوقّعه Helm لمتغيرات البيئة. احفظ الملف وأغلقه عند الانتهاء من التحرير. بعد تحديد قيم معاملات مخطّطك وإنشاء قوائم Secret و ConfigMap، يمكنك المرور لتحرير قالب نشر التطبيق لاستخدام متغيرات البيئة الخاصة بك. الخطوة الخامسة: دمج متغيرات البيئة في نشر Helm بعد إعدادنا لملفات التطبيق Secret و ConfigMap، سنحتاج إلى التأكد من أن نشر تطبيقنا يستطيع استخدام هذه القيم. وسنعمل أيضًا على تخصيص اختبارات الثبات والاستعداد التي حُدّدت بالفعل في بيان النشر. افتح قالب نشر التطبيق للتحرير: nano nodeapp/templates/deployment.yaml رغم أنّ هذا ملف YAML، فإن قوالب Helm تستخدم صياغة تركيب مختلفة عن ملفات YAML المعيارية Kubernetes لإنشاء كشوف البيانات. لمزيد من المعلومات حول القوالب، راجع توثيق Helm. أضف في الملف أولاً مفتاح env لمواصفات حاوية التطبيق، أسفل مفتاح imagePullPolicy وأعلى ports: apiVersion: apps/v1 kind: Deployment metadata: ... spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} env: ports: بعد ذلك، أضف المفاتيح التالية إلى قائمة متغيرات env: apiVersion: apps/v1 kind: Deployment metadata: ... spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} env: - name: MONGO_USERNAME valueFrom: secretKeyRef: key: MONGO_USERNAME name: {{ .Release.Name }}-auth - name: MONGO_PASSWORD valueFrom: secretKeyRef: key: MONGO_PASSWORD name: {{ .Release.Name }}-auth - name: MONGO_HOSTNAME valueFrom: configMapKeyRef: key: MONGO_HOSTNAME name: {{ .Release.Name }}-config - name: MONGO_PORT valueFrom: configMapKeyRef: key: MONGO_PORT name: {{ .Release.Name }}-config - name: MONGO_DB valueFrom: configMapKeyRef: key: MONGO_DB name: {{ .Release.Name }}-config - name: MONGO_REPLICASET valueFrom: configMapKeyRef: key: MONGO_REPLICASET name: {{ .Release.Name }}-config يتضمن كل متغير مرجعًا إلى قيمته، يعرّف إما بمفتاح secretKeyRef، في حالة قيم السرّ، أو configMapKeyRef لقيم ConfigMap. تشير هذه المفاتيح إلى ملفات Secret و ConfigMap التي أنشأناها في الخطوة السابقة. بعد ذلك، تحت المفتاح ports، عدّل تعريف containerPort لتحديد المنفذ الموجود في الحاوية التي سيُعرَض عليها تطبيقنا: apiVersion: apps/v1 kind: Deployment metadata: ... spec: containers: ... env: ... ports: - name: http containerPort: 8080 protocol: TCP ... دعنا نعدّل بعد ذلك، اختبارات الصلاحية والاستعداد المضمنة في بيان النشر هذا افتراضيًا. تضمن هذه الفحوصات اشتغال علب التطبيق لدينا وجاهزيتها لخدمة حركة المرور: تُقيِّم اختبارات الجاهزية (Readiness) ما إذا كانت العلبة جاهزًا لخدمة حركة المرور أم لا، مع إيقاف جميع الطلبات إلى العلبة حتى تنجح عمليات الفحص. تفحص اختبارات الثبات (Liveness) سلوك التطبيق الأساسي لتحديد ما إذا كان التطبيق في الحاوية قيد التشغيل أو يتصرف كما هو متوقع. في حالة فشل اختبار الثبات، سيعيد Kubernetes تشغيل الحاوية. لمزيد من المعلومات حول كليهما، راجع المناقشة ذات الصلة في هيكلة تطبيقات لـ Kubernetes. في حالتنا هذه، سنبني على طلب httpGet الذي قدمه Helm افتراضيًا ونختبر ما إذا كان تطبيقنا يقبل الطلبات في نقطة نهاية sharks/ أم لا. ستجري خدمة kubelet اختبارًا بإرسال طلب GET إلى خادم Node الذي يشتغل في حاوية علب التطبيق ويستمع على المنفذ 8080. إذا كان رمز الجواب يتراوح بين 200 و 400، فسوف تستنتج kubelet أن الحاوية في وضعٍ صحي. خلاف ذلك، إذا كان الرمز 400 أو 500، فإن kubelet تعمد إمّا إلى إيقاف حركة المرور إلى الحاوية، في حالة اختبار الجاهزية، أو إعادة تشغيل الحاوية، في حالة اختبار الثبات. أضف التعديل التالي إلى المسار path المذكور لاختبارات الثبات والجاهزية: apiVersion: apps/v1 kind: Deployment metadata: ... spec: containers: ... env: ... ports: - name: http containerPort: 8080 protocol: TCP livenessProbe: httpGet: path: /sharks port: http readinessProbe: httpGet: path: /sharks port: http احفظ الملف وأغلقه عند الانتهاء من التحرير. أنت الآن جاهز لإنشاء إصدار التطبيق الخاص بك باستخدام Helm. نفذ الأمر التالي لتثبيت helm، والذي يتضمن اسم الإصدار وموقع مجلّد المخطط: helm install --name nodejs ./nodeapp لا تنس أنه يمكنك تنفيذ تثبيت helm باستخدام الخيارين dry-run-- و debug-- أولاً، كما ذكرناه في الخطوة الثالثة، من أجل التحقق من البيانات (manifests) التي أُنشئت لإصدارك. مرة أخرى، نظرًا لأننا لا نقوم بتضمين الراية --namespace مع helm install، سيكون إنشاء كائنات المخطط في المجال الاسمي الافتراضي. سيظهر لك الإخراج التالي مشيرًا إلى إنشاء إصدارك: NAME: nodejs LAST DEPLOYED: Wed Apr 17 18:10:29 2019 NAMESPACE: default STATUS: DEPLOYED RESOURCES: ==> v1/ConfigMap NAME DATA AGE nodejs-config 4 1s ==> v1/Deployment NAME READY UP-TO-DATE AVAILABLE AGE nodejs-nodeapp 0/3 3 0 1s ... مرة أخرى، سيشير الإخراج إلى حالة الإصدار، بالإضافة إلى معلومات حول الكائنات التي أُنشئت وكيفية تفاعلك معها. تحقق من حالة علبك: kubectl get pods NAME READY STATUS RESTARTS AGE mongo-mongodb-replicaset-0 1/1 Running 0 57m mongo-mongodb-replicaset-1 1/1 Running 0 56m mongo-mongodb-replicaset-2 1/1 Running 0 55m nodejs-nodeapp-577df49dcc-b5fq5 1/1 Running 0 117s nodejs-nodeapp-577df49dcc-bkk66 1/1 Running 0 117s nodejs-nodeapp-577df49dcc-lpmt2 1/1 Running 0 117s تحقق من خدماتك بمجرد تشغيل العلب: kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 96m mongo-mongodb-replicaset ClusterIP None <none> 27017/TCP 58m mongo-mongodb-replicaset-client ClusterIP None <none> 27017/TCP 58m nodejs-nodeapp LoadBalancer 10.245.33.46 your_lb_ip 80:31518/TCP العنوان EXTERNAL_IP المرتبط بخدمة nodejs هو عنوان IP يمكّنك من الوصول إلى التطبيق. إذا رأيت حالة في عمود EXTERNAL_IP، فهذا يعني أن مُوازِن التحميل لا يزال قيد الإنشاء. بمجرد رؤية عنوان IP في هذا العمود، انتقل إليه في متصفحك: http://yourlbip. ينبغي أن تظهر لك صفحة الهبوط التالية: الآن بعد أن تم تشغيل التطبيق المنسوخ، دعنا نضيف بعض بيانات الاختبار لضمان عمل التماثل بين عناصر مجموعة النسخ المتماثلة. الخطوة السادسة: اختبار المتماثل في MongoDB بعد تشغيل التطبيق وإتاحة الوصول إليه من خلال عنوان IP خارجي، يمكننا الآن إضافة بعض بيانات الاختبار والتأكد من أنها تُنسَخ بشكل متماثل بين عناصر مجموعة النسخ المتماثلة MongoDB. تأكّد أولًا من فتح صفحة الهبوط على متصفحك: انقر على زر الحصول على معلومات القرش. ستظهر لك صفحة ذات نموذج يمكنك فيه إدخال اسم سمك القرش ووصف لسلوكه العام: أضف في النموذج سمكة قرش أولية من اختيارك. سنضيف لغرض التوضيح Megalodon Shark إلى حقل Shark Name، وAncient لحقل Shark Character: انقر على زر الإرسال. سترى صفحة بها معلومات القرش معروضة لك: انتقل الآن مرة أخرى إلى نموذج معلومات سمك القرش من خلال النقر على Sharks في شريط التنقل العلوي: أدخل سمكة قرش جديدة من اختيارك. سنستخدم Whale Shark مع Large: بمجرد نقرك على زر الإرسال، سترى أنه القرش الجديد أضيف إلى المجموعة shark في قاعدة البيانات الخاصة بك: دعنا نتحقق من أن البيانات التي أدخلناها نُسخت نسخًا متماثلاً بين العناصر الأساسية والثانوية في مجموعة النسخ المتماثلة. استعرض قائمة علبك: kubectl get pods NAME READY STATUS RESTARTS AGE mongo-mongodb-replicaset-0 1/1 Running 0 74m mongo-mongodb-replicaset-1 1/1 Running 0 73m mongo-mongodb-replicaset-2 1/1 Running 0 72m nodejs-nodeapp-577df49dcc-b5fq5 1/1 Running 0 5m4s nodejs-nodeapp-577df49dcc-bkk66 1/1 Running 0 5m4s nodejs-nodeapp-577df49dcc-lpmt2 1/1 Running 0 5m4s للوصول إلى صدفة mongo على علبك، يمكنك استخدام الأمر kubectl exec واسم المستخدم الذي استعملته لإنشاء mongo secret في الخطوة الثانية. ادخل إلى صدفة mongo على العلبة الأولى في StatefulSet باستخدام الأمر التالي: kubectl exec -it mongo-mongodb-replicaset-0 -- mongo -u your_database_username -p --authenticationDatabase admin عند يطلب منك ذلك، أدخل كلمة المرور المرتبطة باسم المستخدم هذا: MongoDB shell version v4.1.9 Enter password: سيتم تحويلك إلى صدفة إدارية: MongoDB server version: 4.1.9 Welcome to the MongoDB shell. ... db:PRIMARY> رغم أن شاشة الإدخال نفسها تتضمن هذه المعلومات، فيمكنك التحقق يدويًا لمعرفة أي عناصر مجموعة النسخ المتماثلة هو الأساسي باستخدام التابع ()rs.isMaster: rs.isMaster() سيظهر لك الإخراج التالي، مع الإشارة إلى اسم المضيف الأساسي: db:PRIMARY> rs.isMaster() { "hosts" : [ "mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local:27017", "mongo-mongodb-replicaset-1.mongo-mongodb-replicaset.default.svc.cluster.local:27017", "mongo-mongodb-replicaset-2.mongo-mongodb-replicaset.default.svc.cluster.local:27017" ], ... "primary" : "mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local:27017", ... انتقل بعد ذلك إلى قاعدة بياناتك sharkinfo: use sharkinfo switched to db sharkinfo اعرض قائمة المجموعات في قاعدة البيانات: show collections sharks استخرج الملفات في المجموعة: db.sharks.find() سيظهر لك في الإخراج ما يلي: { "_id" : ObjectId("5cb7702c9111a5451c6dc8bb"), "name" : "Megalodon Shark", "character" : "Ancient", "__v" : 0 } { "_id" : ObjectId("5cb77054fcdbf563f3b47365"), "name" : "Whale Shark", "character" : "Large", "__v" : 0 } اخرج الآن من صدفة MongoDB: Exit الآن بعد أن تفحصنا البيانات الموجودة على العنصر الأساسي، دعنا نتحقق من نسخها في الثانوي. نفّذ الأمر exec kubectl في mongo-mongodb-replicaset-1: kubectl exec -it mongo-mongodb-replicaset-1 -- mongo -u your_database_username -p --authenticationDatabase admin بمجرد الدخول إلى الصدفة الإدارية، سنحتاج إلى استخدام التابع ()db.setSlaveOk للسماح بعمليات القراءة من المثيل الثانوي: db.setSlaveOk(1) انتقل إلى قاعدة بيانات sharkinfo: use sharkinfo switched to db sharkinfo اسمح بعمليات قراءة الملفات في المجموعة sharks: db.setSlaveOk(1) استخرج الملفات في المجموعة: db.sharks.find() ينبغي أن تشاهد الآن نفس المعلومات التي شاهدتها عند تنفيذ هذا التابع على العنصر الأساسي: db:SECONDARY> db.sharks.find() { "_id" : ObjectId("5cb7702c9111a5451c6dc8bb"), "name" : "Megalodon Shark", "character" : "Ancient", "__v" : 0 } { "_id" : ObjectId("5cb77054fcdbf563f3b47365"), "name" : "Whale Shark", "character" : "Large", "__v" : 0 } يؤكد هذا الإخراج نسخ بيانات تطبيقك بين عناصر مجموعة النسخ المتماثلة. خاتمة لقد تمكّنت الآن من نشر تطبيق متكرّر (replicated) ومتاح على أعلى مستوى لمعلومات سمك القرش على عنقود Kubernetes باستخدام مخططات Helm. يمكن أن يعمل هذا التطبيق التجريبي وسير العمل الموضح في هذا البرنامج التعليمي كنقطة انطلاق أثناء إنشاء مخططات مخصصة لتطبيقك والاستفادة من مستودع Helm الثابت ومستودعات التخطيط الأخرى. أثناء سعيك نحو الإنتاج، فكر في تنفيذ ما يلي: تسجيل الدّخول والمراقبة بشكل مركزي. يرجى الاطلاع على المناقشة ذات الصلة حول تحديث تطبيقات Kubernetes للحصول على نظرة أشمل. يمكنك أيضًا الاطلاع على كيفية إعداد حزمة تسجيل دخول Elasticsearch, Fluentd and Kibana (EFK) على Kubernetes. راجع أيضًا مقدمة لشبكات الخدمة للحصول على معلومات حول كيفية تنفيذ شبكات الخدمات مثل Istio لهذه الوظيفة. موارد الولوج لتوجيه حركة المرور إلى عنقودك. يعد هذا بديلاً جيدًا لـ LoadBalancer في الحالات التي تشغّل فيها خدمات متعددة، والتي تتطلب كل منها موازِنًا LoadBalancer خاصًّا بها، أو عندما ترغب في تنفيذ استراتيجيات توجيه على مستوى التطبيق (اختبارات A/B & canary، على سبيل المثال). لمزيد من المعلومات، تحقّق من كيفية إعداد Nginx Ingress مع Cert-Manager على DigitalOcean Kubernetes والمناقشة ذات الصلة بالتوجيه في سياق شبكة الخدمة في مقدمة لشبكات الخدمة. استراتيجيات النسخ الاحتياطي لكائناتك Kubernetes. للحصول على إرشادات حول تنفيذ النسخ الاحتياطية باستخدام Velero (سابقًا Heptio Ark) مع منتج Kubernetes الخاص بـ DigitalOcean، يرجى الاطلاع على كيفية عمل نسخة احتياطية واستعادة عنقود Kubernetesعلى DigitalOcean باستخدام Heptio Ark. لمعرفة المزيد حول Helm، راجع مقدمة إلى Helm، ومدير الحزم لـ Kubernetes، وكيفية تثبيت البرامج على عناقيد Kubernetes باستخدام Helm Package Manager، وتوثيق Helm. ترجمة -وبتصرف- للمقال How To Scale a Node.js Application with MongoDB on Kubernetes Using Helm لصاحبته Kathleen Juell
-
عند إنشائك لتطبيقات حديثة عديمة الحالة (stateless)، فإن عملية إعداد الحاويات لمكونات التطبيق ستكون هي الخطوة الأولى في النشر والتوسيع على الأنظمة الأساسية الموزعة. إذا كنت قد استخدمت Docker Compose في التطوير، فسوف تعمل على تحديث التطبيق وحاوياته بما يلي: استخراج معلومات التكوين اللازمة من شيفرتك. إلغاء تحميل حالة تطبيقك. تحزيم تطبيقك من أجل الاستخدام المتكرر. سيكون لديك أيضًا تعريفات خدمة مكتوبة تحدّد كيف ستشتغل صور حاوياتك. لتشغيل خدماتك على نظام أساسي موزّع مثل Kubernetes، ستحتاج إلى ترجمة تعريفات خدمة Compose إلى كائنات Kubernetes. سيسمح لك ذلك بتوسيع نطاق تطبيقك بنوع من المرونة. وتعدّ kompose إحدى الأدوات التي يمكن أن تسرع عملية الترجمة إلى Kubernetes، وهي أداة تحويل تساعد المطورين على نقل سير عمل Compose إلى منسّق للحاويات مثل Kubernetes أو OpenShift. سوف تعمل في هذا البرنامج التعليمي على ترجمة خدمات Compose إلى كائنات Kubernetes باستخدام kompose. ستستخدم تعريفات الكائنات التي توفرها kompose كنقطة بداية، كما ستجري تعديلات للتأكد من أن إعداداتك ستستخدم الأسرار (secrets) والخدمات (services) وطلبات وحدة التخزين الثابتة (PersistentVolumeClaims) على النحو المتوقّع في Kubernetes. عند نهاية الدرس، سيكون لديك تطبيق Node.js بنسخة واحدة لقاعدة بيانات MongoDB تعمل على نظام Kubernetes. سيعكس هذا الإعداد وظائف الشيفرة الموضحة في كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose وسيكون نقطة انطلاق جيدة لإنشاء حلّ جاهز للإنتاج يتناسب مع احتياجاتك. المتطلبات الأساسية عنقود Kubernetes 1.10+ مع تفعيل التحكم في الوصول المستند إلى الدور role-based access control (RBAC). سيستخدم هذا الإعداد عنقود DigitalOcean Kubernetes، لكن تبقى لك حرية الاختيار في إنشاء عنقود باستخدام طريقة أخرى. أداة سطر الأوامر kubectl المثبتة على جهازك المحلي أو خادم التطوير وإعدادها للاتصال بعنقودك. يمكنك قراءة المزيد حول تثبيت kubectl في التوثيق الرسمي. Docker مثبت على جهازك المحلي أو خادم التطوير. إذا كنت تعمل على نظام أوبونتو 18.04، اتبع الخطوتين 1 و 2 لكيفية تثبيت واستخدام Docker على أوبونتو 18.04؛ خلاف ذلك، اتبع التوثيق الرسمي للحصول على معلومات حول التثبيت على أنظمة التشغيل الأخرى. تأكد من إضافة مستخدمك غير الجذري إلى مجموعة Docker، كما هو موضح في الخطوة 2 من البرنامج التعليمي المرتبط. حساب Docker Hub. للحصول على نظرة عامة حول كيفية إعداده، راجع هذه المقدمة إلى Docker Hub. الخطوة الأولى: تثبيت kompose للبدء في استخدام kompose، انتقل إلى صفحة إصدارات GitHub للمشروع، وانسخ الرابط إلى الإصدار الحالي. الصق هذا الرابط في الأمر curl التالي لتنزيل أحدث إصدار من kompose: curl -L https://github.com/kubernetes/kompose/releases/download/v1.18.0/kompose-linux-amd64 -o kompose للحصول على تفاصيل حول التثبيت على أنظمة غير تابعة لنظام لينكس، يرجى الرجوع إلى إرشادات التثبيت. أنشئ الملف التنفيذي (binary): chmod +x kompose انقله إلى مسارك PATH: sudo mv ./kompose /usr/local/bin/kompose ثم تحقق من تثبيته بشكل صحيح. يمكنك إجراء فحص للإصدار: kompose version إذا كان التثبيت ناجحًا، فسيظهر لك الإخراج التالي: Output 1.18.0 (06a2e56) بعد تثبيت kompose وجاهزيته للاستخدام، يمكنك الآن استنساخ شيفرة المشروع Node.js الذي سيُترجَم إلى Kubernetes. الخطوة الثانية: استنساخ وتحزيم التطبيق لاستخدام تطبيقنا على Kubernetes، سنحتاج إلى استنساخ شيفرة المشروع وتحزيم التطبيق حتى تتمكن خدمة kubelet من سحب الصورة. ستكون خطوتنا الأولى هي استنساخ مستودع node-mongo-docker-dev من حسابDigitalOcean Community GitHub. يتضمن هذا المستودع شيفرة الإعداد الموضح في كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose، والذي يستخدم تطبيق Node.js لشرح كيفية إعداد بيئة تطوير باستخدام Docker Compose. يمكنك العثور على مزيد من المعلومات حول التطبيق نفسه في سلسلة من الحاويات إلى Kubernetes باستخدام Node.js. استنسخ المستودع في مجلّد يسمى node_project: git clone https://github.com/do-community/node-mongo-docker-dev.git node_project انتقل إلى المجلّد node_project: cd node_project يحتوي مجلّد node_project على ملفات ومجلدات لتطبيق معلومات سمك القرش الذي يعمل على مدخلات المستخدم. لقد حُدِّث للعمل على الحاويات وأزيلت منه معلومات التكوين الحساسة والمحددة في شيفرة التطبيق وأُعيد تشكيلها من أجل حقنها عند تنفيذ التطبيق، كما أُلغيَ تحميل حالة التطبيق إلى قاعدة بيانات MongoDB. لمزيد من المعلومات حول تصميم التطبيقات الحديثة عديمة الحالة، يرجى الاطلاع على هيكلة التطبيقات ل Kubernetes وتحديث التطبيقات لKubernetes. يتضمن مجلّد المشروع ملفّا Dockerfile بتعليمات لبناء صورة التطبيق. دعنا نبني الصورة الآن على النحو الذي يتيح رفعها إلى حسابك Docker Hub واستخدامها في إعداداتك Kubernetes. استخدم الأمر docker build لبناء الصورة باستخدام الراية t-، والتي تتيح لك تعليمها باسم لا يُنسى. في هذه الحالة، علّم الصورة باسم مستخدمك Docker Hub وسمّها node kubernetes أو أيّ اسم من اختيارك: docker build -t your_dockerhub_username/node-kubernetes . تحدد النقطة . في الأمر أن سياق البناء هو المجلّد الحالي. سوف يستغرق الأمر دقيقة أو دقيقتين لبناء الصورة. بمجرد اكتماله، تحقق من صورك: docker images سيظهر لك الإخراج التالي: Output REPOSITORY TAG IMAGE ID CREATED SIZE your_dockerhub_username/node-kubernetes latest 9c6f897e1fbc 3 seconds ago 90MB node 10-alpine 94f3c8956482 12 days ago 71MB بعد ذلك، سجّل الدخول إلى حساب Docker Hub الذي أنشأته في المتطلبات الأساسية: docker login -u your_dockerhub_username أدخل كلمة مرور حساب Docker Hub عندما يُطلب منك ذلك. سيؤدي التسجيل بهذه الطريقة إلى إنشاء ملف ~/.docker/config.json في المجلّد الرئيسي للمستخدم الخاص بك باستخدام بيانات اعتماد. Docker Hub ارفع صورة التطبيق إلى Docker Hub باستخدام الأمر docker push. ولا تنس أن تعوّض yourdockerhubusername باسم مستخدمك Docker Hub: docker push your_dockerhub_username/node-kubernetes لديك الآن صورة للتطبيق يمكنك سحبها لتشغيل تطبيقك على Kubernetes. ستكون الخطوة التالية هي ترجمة تعريفات خدمة التطبيق إلى كائنات .Kubernetes الخطوة الثالثة: ترجمة الخدمات إلى كائنات Kubernetes باستخدام kompose يحدّد ملف Docker Compose، المسمى هنا docker-compose.yaml، التعريفات التي ستعمل على تشغيل خدماتنا على Compose. الخدمة في "Compose" هي عبارة عن حاوية قيد التشغيل، وتحتوي تعريفات الخدمة على معلومات حول كيفية تشغيل صورة كل حاوية. في هذه الخطوة، سوف نترجم هذه التعريفات إلى كائنات Kubernetes باستخدام kompose لإنشاء ملفات .yaml سوف تحتوي هذه الملفات على خصائص كائنات Kubernetes التي تصف الحالة التي نريدها عليها. سوف نستخدم هذه الملفات لإنشاء أنواع مختلفة من الكائنات: أوّلها الخدمات، والتي ستضمن بقاء العُلَب (pods) التي تشغّل حاوياتنا متاحةً. ثانيها عمليّات النشر، والتي سوف تحتوي على معلومات حول الحالة التي نريد عليها العلب. ثالثها PersistentVolumeClaim لتوفير تخزين لقاعدة البيانات. رابعها خريطة الإعداد ConfigMap لمتغيرات البيئة التي تُحقَن في وقت التشغيل. وآخرها سرٌّ (secret) لمستخدم قاعدة بيانات التطبيق وكلمة المرور. ستكون بعض هذه التعريفات في الملفات التي سينشئها لناkompose ، والبعض الآخر سوف نحتاج إلى إنشائه بأنفسنا. سنحتاج بدايةّ إلى تعديل بعض التعاريف في ملف docker-compose.yaml للعمل على Kubernetes. سنُضمّن إشارة إلى صورة التطبيق التي بُنيت حديثًا في تعريف خدمتنا nodejs، كما سنحذف الروابط bind mounts والحجوم والأوامر الإضافية التي استخدمناها لتشغيل حاوية التطبيق قيد التطوير باستخدام Compose. بالإضافة إلى ذلك، سنعيد تحديد سياسات إعادة تشغيل كلتا الحاويتين بحيث تتوافق مع السلوك الذي المتوقع في Kubernetes. افتح الملف باستخدام nano أو المحرر المفضل لديك: nano docker-compose.yaml يبدو التعريف الحالي لخدمة تطبيق nodejs كما يلي: ... services: nodejs: build: context: . dockerfile: Dockerfile image: nodejs container_name: nodejs restart: unless-stopped env_file: .env environment: - MONGO_USERNAME=$MONGO_USERNAME - MONGO_PASSWORD=$MONGO_PASSWORD - MONGO_HOSTNAME=db - MONGO_PORT=$MONGO_PORT - MONGO_DB=$MONGO_DB ports: - "80:8080" volumes: - .:/home/node/app - node_modules:/home/node/app/node_modules networks: - app-network command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js ... بعدها أَجرِ التعديلات التالية على تعريف الخدمة: استخدم صورة node-kubernetes بدلاً من ملف Dockerfile المحلي. عدّل سياسة إعادة تشغيل الحاوية restart من unless-stopped إلى always. احذف قائمة الحجوم volumes وتعليمات الأوامر command. سيبدو تعريف الخدمة النهائية الآن كما يلي: ... services: nodejs: image: your_dockerhub_username/node-kubernetes container_name: nodejs restart: always env_file: .env environment: - MONGO_USERNAME=$MONGO_USERNAME - MONGO_PASSWORD=$MONGO_PASSWORD - MONGO_HOSTNAME=db - MONGO_PORT=$MONGO_PORT - MONGO_DB=$MONGO_DB ports: - "80:8080" networks: - app-network … بعد ذلك، انزل إلى تعريف الخدمة db. ثم أجر التعديلات التالية: غيّر سياسة إعادة التشغيل restart للخدمة always. احذف ملف env. فبدلاً من استخدام قيمٍ من ملف env.، سنمرّر القيم الخاصة بـ MONGO_INITDB_ROOT_USERNAME و MONGO_INITDB_ROOT_PASSWORD إلى حاوية قاعدة البيانات باستخدام السّر Secret الذي سننشئه في الخطوة الرابعة. سيبدو تعريف خدمة db الآن كما يلي: ... db: image: mongo:4.1.8-xenial container_name: db restart: always environment: - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD volumes: - dbdata:/data/db networks: - app-network ... أخيرًا، في الجزء السفلي من الملف، احذف الحجوم node_modules من مفتاح المستوى الأعلى volumes. وسيبدو المفتاح عندها كما يلي: ... volumes: dbdata: احفظ الملف وأغلقه عند الانتهاء من التحرير. سنحتاج، قبل ترجمة تعريفاتنا للخدمة، إلى كتابة ملف env. الذي سيستخدمه kompose لإنشاء خريطة الإعداد ConfigMap بمعلوماتنا غير الحساسة. الرجاء مراجعة الخطوة الثانية من كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose للمزيد من التوضيح حول هذا الملف. في ذلك الدرس، أضفنا env. إلى ملفنا gitignore. للتأكد من عدم نسخه إلى التحكم في الإصدار. هذا يعني أنه لم يُنسَخ عندما استنسخنا مستودع node-mongo-docker-dev في الخطوة الثانية من هذا الدرس. لذلك سوف نحتاج إلى إعادة إنشائه الآن. أنشئ هذا الملف إذًا: nano .env سوف يستخدم kompose هذا الملف لإنشاء خريطة إعداد ConfigMap لتطبيقنا. ومع ذلك، بدلاً من تعيين كافة المتغيرات من تعريف خدمة nodejs في ملفنا Compose، سنضيف فقط اسم قاعدة البيانات MONGO_DB و MONGO_PORT. سنعيّن اسم المستخدم وكلمة المرور لقاعدة البيانات بشكل منفصل عندما ننشئ يدويًا كائنًا Secret في الخطوة الرابعة. أضف معلومات المنفذ واسم قاعدة البيانات التالية إلى ملف env.. لا تتردد في إعادة تسمية قاعدة بياناتك إذا كنت ترغب في ذلك: MONGO_PORT=27017 MONGO_DB=sharkinfo احفظ الملف وأغلقه عند الانتهاء من التحرير. أنت الآن مستعدّ لإنشاء الملفات اعتمادًا على خصائص كائنك. ويقدم لك kompose خيارات متعددة لترجمة مواردك. إذ تستطيع: إنشاء ملفات yaml بناءً على تعريفات الخدمة في ملفك docker-compose.yaml باستخدام kompose convert. إنشاء كائنات Kubernetes مباشرة باستخدام kompose up. إنشاء مخطط Helm باستخدام kompose convert -c. في الوقت الحالي، سنحوّل تعريفاتنا للخدمة إلى ملفات yaml ثم نضيف الملفات التي ينشئها kompose وننقّحها. استخدام الأمر التالي لتحويل تعريفات الخدمة إلى ملفات yaml: kompose convert يمكنك أيضًا تسمية ملفات تكوين محددة أو متعددة باستخدام الراية f-. بعد تنفيذ هذا الأمر، سيُخرِج kompose معلوماتٍ حول الملفات التي أنشأتها: INFO Kubernetes file "nodejs-service.yaml" created INFO Kubernetes file "db-deployment.yaml" created INFO Kubernetes file "dbdata-persistentvolumeclaim.yaml" created INFO Kubernetes file "nodejs-deployment.yaml" created INFO Kubernetes file "nodejs-env-configmap.yaml" created يتضمّن هذا الإخراج الملفات yaml مع خصائص خدمة ونشر وخريطة إعداد تطبيق Node، وكذلك طلب وحدة التخزين الثابتة ل dbdata ونشر قاعدة بيانات MongoDB. تعدّ هذه الملفات نقطة انطلاق جيدة، ولكن لكي تتطابق وظائف تطبيقنا مع الإعداد الموضح في كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose، سنحتاج إلى إجراء بعض الإضافات والتغييرات على الملفات التي أنشأها kompose. الخطوة الرابعة: إنشاء أسرار Kubernetes لكي يعمل تطبيقنا بالطريقة التي نتوقعها، سنحتاج إلى إجراء بعض التعديلات على الملفات التي أنشأها kompose. أول هذه التغييرات هو إنشاء سرٍّ لمستخدم قاعدة البيانات وكلمة المرور وإضافتها إلى عمليات نشر التطبيق وقاعدة البيانات. ويقدّم Kubernetes طريقتين للعمل بمتغيرات البيئة: ConfigMap وSecret. وقد أنشأ kompose بالفعل خريطة إعداد ConfigMap بالمعلومات غير السرية التي ضمّنناها في ملفنا env.، لذلك سننشئ الآن سرًّا بمعلوماتنا السرية: اسم المستخدم وكلمة المرور لقاعدة البيانات. ستكون الخطوة الأولى في إنشاء السّرِّ يدويًا هي تحويل اسم المستخدم وكلمة المرور إلى base64، وهو نظام ترميز يسمح لك بنقل البيانات بشكل موحد، بما في ذلك البيانات الثنائية. حوّل أولا اسم مستخدم قاعدة بياناتك: echo -n 'your_database_username' | base64 دوّن القيمة التي تظهر لك في الإخراج. بعد ذلك، حوّل كلمة مرورك: echo -n 'your_database_password' | base64 دوّن القيمة الظاهرة في الإخراج هنا أيضا. افتح ملف السّر: nano secret.yaml ملاحظة: تُحدّد كائنات Kubernetes عادة باستخدام YAML الذي يمنع بصرامةٍ علامات التبويب ويتطلب مسافتين للمسافة البادئة. إذا كنت ترغب في التحقق من تنسيق أيٍّ من ملفاتك yaml ، فيمكنك استخدام linter أو اختبار صياغة (syntax) تركيبك باستخدام kubectl create مع الرايتين dry-run-- و validate--: kubectl create -f your_yaml_file.yaml --dry-run --validate=true يُستحسن بشكل عام التحقق من صحة الصياغة قبل إنشاء الموارد باستخدام kubectl. أضف الشيفرة التالية إلى الملف لإنشاء سرٍّ يحدّد MONGO_USERNAME و MONGO_PASSWORD باستخدام القيم المشفرة التي أنشأتها للتو. تأكد من تعويض القيم الوهمية هنا باسم المستخدم وكلمة المرور المشفرة: الملف ~/node_project/secret.yaml: apiVersion: v1 kind: Secret metadata: name: mongo-secret data: MONGO_USERNAME: your_encoded_username MONGO_PASSWORD: your_encoded_password لقد سمّينا كائن السرّ mongo-secret، ولكن يمكنك تسميته كما تشاء. احفظ هذا الملف وأغلقه عند الانتهاء من التحرير. وكما فعلت مع ملفك env. ، تأكد من إضافة secret.yaml إلى ملفك gitignore. لإبقائه خارج نطاق التحكم في الإصدار. بعد تحرير secret.yaml، ستكون خطوتنا التالية هي ضمان استخدام عُلَب كلّ من التطبيق وقاعدة البيانات للقيم التي أضفناها إلى الملف. لنبدأ بإضافة إشارات مرجعية إلى السّرّ Secret في نشر التطبيق. افتح الملف المسمى nodejs-publish.yaml: nano nodejs-deployment.yaml تتضمن مواصفات حاوية الملف متغيرات البيئة التالية المحددة تحت مفتاح env: apiVersion: extensions/v1beta1 kind: Deployment ... spec: containers: - env: - name: MONGO_DB valueFrom: configMapKeyRef: key: MONGO_DB name: nodejs-env - name: MONGO_HOSTNAME value: db - name: MONGO_PASSWORD - name: MONGO_PORT valueFrom: configMapKeyRef: key: MONGO_PORT name: nodejs-env - name: MONGO_USERNAME سنحتاج إلى إضافة إشارات مرجعية إلى السرّ في متغيرات MONGO_USERNAME و MONGO_PASSWORD المدرجة هنا، حتى يتمكن تطبيقنا من الوصول إلى تلك القيم. وبدلاً من تضمين مفتاح configMapKeyRef للإشارة إلى خريطة الإعداد لnodejs-env ، كما هو الحال مع قيم MONGO_DB و MONGO_PORT،، سنُضمِّن مفتاح secretKeyRef للإشارة إلى القيم الموجودة في السرّ secret. أضف إشارات السرّ المرجعية التالية إلى المتغيرين MONGO_USERNAME و MONGO_PASSWORD: apiVersion: extensions/v1beta1 kind: Deployment ... spec: containers: - env: - name: MONGO_DB valueFrom: configMapKeyRef: key: MONGO_DB name: nodejs-env - name: MONGO_HOSTNAME value: db - name: MONGO_PASSWORD valueFrom: secretKeyRef: name: mongo-secret key: MONGO_PASSWORD - name: MONGO_PORT valueFrom: configMapKeyRef: key: MONGO_PORT name: nodejs-env - name: MONGO_USERNAME valueFrom: secretKeyRef: name: mongo-secret key: MONGO_USERNAME احفظ الملف وأغلقه عند الانتهاء من التحرير. بعد ذلك ، سنضيف القيم نفسها إلى ملف db-publish.yaml. افتح الملف للتحرير: nano db-deployment.yaml سنضيف في هذا الملف إشارات مرجعية إلى السرّ في مفاتيح المتغيرات التالية: MONGO_INITDB_ROOT_USERNAME و MONGO_INITDB_ROOT_PASSWORD. تجعل صورة mongo هذه المتغيرات متاحة لتتمكّن من تعديل تهيئة نسخة قاعدة بياناتك. ينشئ MONGO_INITDB_ROOT_USERNAME وMONGO_INITDB_ROOT_PASSWORD معًا مستخدمًا جذرًا في قاعدة بيانات المشرف admin مع التأكد من تفعيل التصديق على الهوية عند بدء تشغيل حاوية قاعدة البيانات. يضمن استخدام القيم التي حدّدناها في السرّ أن يكون لدينا مستخدم للتطبيق يتمتع بأذونات الجذر root في نسخة قاعدة البيانات، مع إمكانية الوصول إلى جميع الأذونات الإدارية والتشغيلية لهذا الدور. عند العمل في الإنتاج، سترغب في إنشاء مستخدم تطبيق مخصّص ذي صلاحيات محددة النطاق. أضف أسفل متغيرات MONGO_INITDB_ROOT_USERNAME وMONGO_INITDB_ROOT_PASSWORD إشارات مرجعية إلى قيم السرّ Secret: apiVersion: extensions/v1beta1 kind: Deployment ... spec: containers: - env: - name: MONGO_INITDB_ROOT_PASSWORD valueFrom: secretKeyRef: name: mongo-secret key: MONGO_PASSWORD - name: MONGO_INITDB_ROOT_USERNAME valueFrom: secretKeyRef: name: mongo-secret key: MONGO_USERNAME image: mongo:4.1.8-xenial … احفظ الملف وأغلقه عند الانتهاء من التحرير. بعد إعداد السر الخاص بك، يمكنك الانتقال إلى إنشاء خدمة قاعدة بياناتك والتأكد من أن حاوية التطبيق تحاول فقط الاتصال بقاعدة البيانات بمجرد إعدادها وتهيئتها بالكامل. الخطوة الخامسة: إنشاء خدمة قاعدة البيانات وحاوية التطبيق الأولية الآن بعد أن أعددنا السرّ، يمكننا الانتقال إلى إنشاء خدمة قاعدة البيانات وحاوية أولية مهمّتها استطلاع هذه الخدمة للتأكد من أن تطبيقنا يحاول فقط الاتصال بقاعدة البيانات بمجرد اكتمال إجراءات بدء تشغيل قاعدة البيانات، بما في ذلك إنشاء مستخدم وكلمة مرور MONGO_INITDB. للمزيد حول كيفية تنفيذ هذه الوظيفة في Compose، يرجى الاطلاع على الخطوة الرابعة من إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose. افتح ملفًا لتحديد مواصفات خدمة قاعدة البيانات: nano db-service.yaml أضف الشيفرة التالية إلى الملف لتعريف الخدمة: الملف ~/node_project/db-service.yaml: apiVersion: v1 kind: Service metadata: annotations: kompose.cmd: kompose convert kompose.version: 1.18.0 (06a2e56) creationTimestamp: null labels: io.kompose.service: db name: db spec: ports: - port: 27017 targetPort: 27017 selector: io.kompose.service: db status: loadBalancer: {} سيُطابِق المحدد selector الذي أدرجناه هنا كائن الخدمة هذا مع علب (Pods) قاعدة بياناتنا، والتي عُرِّفت بالتسمية io.kompose.service: db بواسطة kompose في ملف .db-publish.yaml لقد سمّينا أيضًا هذه الخدمة db. احفظ الملف وأغلقه عند الانتهاء من التحرير. بعد ذلك، دعنا نضيف حقلًا لحاوية أولية إلى المصفوفة containers في nodejs-deployment.yaml سيؤدي هذا إلى إنشاء حاوية أوّلية يمكننا استخدامها لتأخير حاوية تطبيقنا عن الاشتغال حتى إنشاء خدمة db ذات علبة يمكن الوصول إليها. وهذا هو أحد الاستخدامات المحتملة للحاويات الأولية. لمعرفة المزيد عن حالات الاستخدام الأخرى، يرجى الاطلاع على التوثيق الرسمي. افتح الآن الملف nodejs-publish.yaml: nano nodejs-deployment.yaml داخل العلبة Pod وبجانب المصفوفة containers، سنضيف حقلًا initContainers بحاوية مهمتها استقصاء الخدمة db. أضف الشيفرة التالية أسفل الحقلين ports وresources وفوق restartPolicy في مصفوفة nodejs containers: الملف ~/node_project/nodejs-deployment.yaml: apiVersion: extensions/v1beta1 kind: Deployment ... spec: containers: ... name: nodejs ports: - containerPort: 8080 resources: {} initContainers: - name: init-db image: busybox command: ['sh', '-c', 'until nc -z db:27017; do echo waiting for db; sleep 2; done;'] restartPolicy: Always ... تستخدم هذه الحاوية الأولية صورة BusyBox، وهي صورة خفيفة الوزن تتضمن العديد من أدوات UNIX. في هذه الحالة، سنستخدم الأداة netcat لاستقصاء هل تقبل العلبة المرتبطة بالخدمة db الاتصالات TCP على المنفذ 27017. يكرّر أمر الحاوية command هذا وظيفة السكربت wait-for الذي حذفناه من الملف docker-compose.yaml في الخطوة الثالثة. لمزيد من التوضيح حول كيفية وعلّة استخدام تطبيقنا للسكربت wait-for ، يرجى الاطلاع على الخطوة الرابعة من كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose. تشتغل الحاويات الأولية وفق نظام run to completion. هذا يعني في حالتنا هذه أن حاوية التطبيق Node لن تبدأ في الاشتغال حتى تبدأ حاوية قاعدة البيانات في الاشتغال وقبول الاتصالات على المنفذ 27017. يسمح لنا تعريف الخدمة db بضمان هذه الوظيفة بغض النظر عن الموقع المحدّد لحاوية قاعدة البيانات، والذي يمكنه أن يتغيّر. احفظ الملف وأغلقه عند الانتهاء من التحرير. بعد إنشائك لخدمة قاعدة البيانات ووضعك لحاوياتك الأولية للتحكم في ترتيب اشتغال الحاويات، يمكنك الانتقال إلى التحقق من متطلبات التخزين في PersistentVolumeClaim وعرض خدمة تطبيقك باستخدام LoadBalancer. الخطوة السّادسة: تعديل PersistentVolumeClaim وعرض الواجهة الأمامية للتطبيق قبل تشغيل التطبيق، سنجري تغييرين أخيرين لضمان توفير تخزين قاعدة البيانات بشكل صحيح، وأنّنا نستطيع عرض الواجهة الأمامية لتطبيقنا باستخدام LoadBalancer. دعنا نعدل أولاً مورد التخزين storage resource المحدد في PersistentVolumeClaim الذي أنشأناه. تتيح لنا هذه المطالبة (claim) توفير مساحة تخزين ديناميكية لإدارة حالة التطبيق. للعمل ب PersistentVolumeClaims، يجب أن يكون لديك صنف تخزين StorageClass أنشأته وأعددته لتوفير موارد التخزين. في حالتنا هذه، ونظرًا لأننا نعمل على DigitalOcean Kubernetes، يعيّن صنف التخزين provisioner الافتراضي على dobs.csi.digitalocean.com. يمكننا التحقق من ذلك عبر كتابة ما يلي: kubectl get storageclass إذا كنت تعمل مع مجموعة DigitalOcean، فسترى الإخراج التالي: NAME PROVISIONER AGE do-block-storage (default) dobs.csi.digitalocean.com 76m إذا كنت لا تعمل على عنقود DigitalOcean، فستحتاج إلى إنشاء StorageClass وإعداد مزوّد من اختيارك. للحصول على تفاصيل حول كيفية القيام بذلك، يرجى الاطلاع على التوثيق الرسمي. عندما ينشئ kompose الملف dbdata-persistentvolumeclaim.yaml، فإنه يعيّن مورد التخزين storage resource على سعةٍ لا تلبي الحدّ المطلوب في مزوّد الخدمة لدينا. لذلك، سنحتاج إلى تعديل PersistentVolumeClaim الخاص بنا لاستخدام السعة الدنيا القابلة للتطبيق لوحدة تخزين DigitalOcean Block المحدّدة في 1 جيجابايت. ويمكنك تعديل هذه القيمة لتلبية متطلبات التخزين الخاصة بك. افتح الملفّ dbdata-persistentvolumeclaim.yaml: nano dbdata-persistentvolumeclaim.yaml عوّض قيمة التخزين storage بـ 1Gi: الملف ~/node_project/dbdata-persistentvolumeclaim.yaml: apiVersion: v1 kind: PersistentVolumeClaim metadata: creationTimestamp: null labels: io.kompose.service: dbdata name: dbdata spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi status: {} لاحظ أيضًا أنّ accessMode: ReadWriteOnce يعني أن الحجم المتوفّر كنتيجة لهذه الطلب سيكون للقراءة والكتابة من خلال عقدة واحدة فقط. يرجى الاطلاع على التوثيق لمزيد من المعلومات حول أوضاع الوصول المختلفة. احفظ الملف وأغلقه عند الانتهاء. بعد ذلك، افتح nodejs-service.yaml: nano nodejs-service.yaml سنعمل على الكشف عن هذه الخدمة خارجيًا باستخدام .DigitalOcean Load Balancer إذا كنت لا تستخدم عنقود DigitalOcean، فيرجى الرجوع إلى التوثيق ذا الصلة من مزوّد الخدمة السحابية للحصول على معلومات حول موازِنات الحمل الخاصة به. بدلاً من ذلك، يمكنك اتباع توثيق Kubernetes الرسمية الخاصة بإعداد عنقود عالي التوفّر باستخدام kubeadm، لكن في هذه الحالة لن تتمكن من استخدام PersistentVolumeClaims لتوفير التخزين. حدّد ضمن مواصفات الخدمة، LoadBalancer في الحقل type الخاصّ بالخدمة: الملف ~/node_project/nodejs-service.yaml: apiVersion: v1 kind: Service ... spec: type: LoadBalancer ports: ... عندما ننشئ خدمة nodejs، سيُنشَأ مُوازِن للتحميل تلقائيًا، مما يوفر لنا عنوانًا خارجيًا IP يمكِّننا من الوصول إلى تطبيقنا. احفظ الملف وأغلقه عند الانتهاء من التحرير. بوجود جميع ملفاتنا في مكانها الصحيح، نكون على استعداد لبدء واختبار التطبيق. الخطوة السابعة: بدء التطبيق والوصول إليه لقد حان الوقت لإنشاء كائنات Kubernetes واختبار عمل التطبيق على النحو المتوقع. لإنشاء الكائنات التي حددناها، سنستخدم kubectl create مع الراية f-، والتي سوف تتيح لنا تحديد الملفات التي أنشأها لنا Compose، بالإضافة إلى الملفات التي حرّرناها. نفّذ الأمر التالي لإنشاء تطبيق Node وخدمات قاعدة بيانات MongoDB وعمليات النشر، مع Secret وConfigMap وPersistentVolumeClaim: kubectl create -f nodejs-service.yaml,nodejs-deployment.yaml,nodejs-env-configmap.yaml,db-service.yaml,db-deployment.yaml,dbdata-persistentvolumeclaim.yaml,secret.yaml سيظهر لك الإخراج التالي مشيرًا إلى إنشاء الكائنات: Output service/nodejs created deployment.extensions/nodejs created configmap/nodejs-env created service/db created deployment.extensions/db created persistentvolumeclaim/dbdata created secret/mongo-secret created للتحقق من اشتغال العلب Pods، اكتب ما يلي: kubectl get pods لا تحتاج إلى تحديد فضاء اسم (namespace) هنا، لأننا أنشأنا كائناتنا في مساحة اسمية الافتراضية. إذا كنت تعمل على مساحات اسمية متعددة، فتأكد من تضمين الراية n- عند تنفيذ هذا الأمر، بالإضافة إلى اسم مساحتك الاسمية. سيظهر لك الإخراج التالي أثناء بدء تشغيل الحاوية db وحاوية تطبيقك الأولية: Output NAME READY STATUS RESTARTS AGE db-679d658576-kfpsl 0/1 ContainerCreating 0 10s nodejs-6b9585dc8b-pnsws 0/1 Init:0/1 0 10s بمجرد تشغيل تلك الحاوية وبدء تشغيل حاويات التطبيق وقاعدة البيانات، سيظهر لك هذا الإخراج: Output NAME READY STATUS RESTARTS AGE db-679d658576-kfpsl 1/1 Running 0 54s nodejs-6b9585dc8b-pnsws 1/1 Running 0 54s يشير Running STATUS إلى أن العلب الخاصة بك مرتبطة بالعُقَد وأن الحاويات المرتبطة بتلك العلب تعمل. ويشير READY إلى عدد الحاويات الموجودة في العلبة. لمزيد من المعلومات، يرجى الرجوع إلى توثيق دورة حياة العلبة. ملحوظة: إذا رأيت مراحل غير متوقعة في العمود STATUS، تذكر أنه يمكنك استكشاف الأخطاء وإصلاحها باستخدام الأوامر التالية: kubectl describe pods your_pod kubectl logs your_pod بعد تشغيل حاوياتك، يمكنك الآن الوصول إلى التطبيق. للحصول على العنوان IP الخاص بـ LoadBalancer، اكتب: kubectl get svc سيظهر لك الإخراج التالي: NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE db ClusterIP 10.245.189.250 <none> 27017/TCP 93s kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 25m12s nodejs LoadBalancer 10.245.15.56 your_lb_ip 80:30729/TCP 93s العنوان EXTERNAL_IP المرتبط بخدمة nodejs هو عنوان IP يمكّنك من الوصول إلى التطبيق. إذا رأيت حالة <pending> في عمود EXTERNAL_IP،، فهذا يعني أن مُوازِن التحميل لا يزال قيد الإنشاء. بمجرد رؤية عنوان IP في هذا العمود، انتقل إليه في متصفحك: http://your_lb_ip. ينبغي أن تظهر لك صفحة الهبوط التالية: انقر على زر الحصول على معلومات القرش. ستظهر صفحةّ فيها نموذج إدخال حيث يمكنك إدخال اسم سمك القرش ووصف لسلوكه العام: أضف في النموذج سمكة قرش من اختيارك. للتوضيح، سنضيف Megalodon Shark إلى حقل Shark Name، وAncient لحقل Shark Character: انقر على زر الإرسال. ستظهر صفحة بها معلومات القرش معروضة لك: لديك الآن إعداد نسخة واحدة لتطبيق Node.js بقاعدة بيانات MongoDB تعمل على عنقود Kubernetes. خاتمة تعدّ الملفات التي أنشأتها في هذا الدرس نقطة انطلاق جيدة للبناء عليها أثناء تقدمك نحو الإنتاج. ويمكنك العمل أثناء تطوير تطبيقك على تنفيذ ما يلي: تسجيل الدّخول والمراقبة بشكل مركزي. يرجى الاطلاع على المناقشة ذات الصلة على موقع ديجيتال أوشن حول تحديث تطبيقات Kubernetes للحصول على نظرة أشمل. يمكنك أيضًا الاطلاع على كيفية إعداد حزمة تسجيل دخول Elasticsearch، و Fluentd و Kibana (EFK) على Kubernetes على موقع ديجيتال أوشن. راجع أيضًا مقال مقدمة لشبكات الخدمة للحصول على معلومات حول كيفية تنفيذ شبكات الخدمات مثل Istio لهذه الوظيفة. موارد الولوج لتوجيه حركة المرور إلى عنقودك. يعد هذا بديلاً جيدًا لموزع الحمل LoadBalancer في الحالات التي تشغّل فيها خدمات متعددة، والتي تتطلب كل منها موازِنًا LoadBalancer خاصًّا بها، أو عندما ترغب في تنفيذ استراتيجيات توجيه على مستوى التطبيق (اختبارات A/B & canary، على سبيل المثال). لمزيد من المعلومات، تحقّق من مقال كيفية إعداد Nginx Ingress مع Cert-Manager على DigitalOcean Kubernetes والمناقشة ذات الصلة على ديجيتال أوشن بالتوجيه في سياق شبكة الخدمة في مقدمة لشبكات الخدمة. استراتيجيات النسخ الاحتياطي لكائناتك Kubernetes. للحصول على إرشادات حول تنفيذ النسخ الاحتياطية باستخدام Velero (سابقًا Heptio Ark) مع منتج Kubernetes الخاص بـ DigitalOcean، يرجى الاطلاع على مقال كيفية عمل نسخة احتياطية واستعادة عنقود Kubernetes على DigitalOcean باستخدام Heptio Ark. ترجمة -وبتصرف- للمقال How To Migrate a Docker Compose Workflow to Kubernetes لصاحبته Kathleen Juell
-
إذا كنت تعمل على تطوير تطبيقك بنشاط، فإن استخدام Docker يمكنه تبسيط سير العمل وجعل عملية نشر التطبيق على الإنتاج سلسةً، إذ يمنح العمل بالحاويات في عمليّة التطوير العديد من المزايا، نذكر منها أنّ: البيئات متناسقة (consistent)، مما يعني أنه يمكنك اختيار اللغات والاعتماديات التي تريدها لمشروعك دون أن تقلق بشأن تعارضات النظام. البيئات معزولة (isolated)، ممّا يسهّل اكتشاف المشكلات وإدماج الأعضاء الجدد في الفريق. البيئات محمولة (portable)، ممّا يسمح لك بتحزيم ومشاركة شيفرتك مع الآخرين. يشرح لك هذا الدرس كيفية إعداد بيئة تطوير لتطبيق Node.js بالاعتماد على Docker. ستنشئ حاويتين، واحدة من أجل تطبيق Node والأخرى لقاعدة البيانات MongoDB، وذلك باستخدام Docker Compose. ونظرًا لأن هذا التطبيق يعمل على Node وMongoDB، فسيقوم برنامج الإعداد بما يلي: *مزامنة شيفرة التطبيق التي على المضيف مع الشيفرة التي في الحاوية لتسهيل التعديلات أثناء التطوير. التحقّق من أن التعديلات في شيفرة التطبيق تعمل دون الحاجة إلى إعادة تشغيل. إنشاء قاعدة بيانات محمية بمستخدم وكلمة مرور من أجل بيانات التطبيق. جعل هذه البيانات ثابتة ومستقرة (persistent). في نهاية هذا الدرس، سيكون لديك تطبيق لمعلومات سمك القرش يعمل بشكل جيّد على حاويات Docker: المتطلبات الأساسية لمتابعة هذا الدرس، ستحتاج إلى العناصر التالية: خادم تطوير يشتغل على نظام أوبونتو 18.04، إضافة إلى مستخدم غير جذري يمتلك صلاحيات sudo وجدار حماية نشط. للحصول على إرشادات حول كيفية إعدادها، يرجى الاطلاع على دليل إعداد الخادم الأولي. تثبيت Docker على خادمك، باتباع الخطوتين الأولى و الثانية لكيفية تثبيت واستخدام Docker على أوبونتو 18.04. تثبيت Compose Docker على خادمك، باتباع الخطوة الأولى من كيفية تثبيت Docker Compose على أوبونتو 18.04. الخطوة الأولى: استنساخ المشروع وتعديل الاعتماديات تتمثل الخطوة الأولى في إنشاء هذا الإعداد في استنساخ شيفرة المشروع وتعديل ملف package.json الخاص به، والذي يتضمن اعتماديات المشروع. سوف نضيف nodemon إلى اعتماديات المشروع devDependencies، مع تحديد أننا سنستخدمها أثناء التطوير. يضمن تشغيل التطبيق باستخدام nodemon أنه سيُعاد تشغيله تلقائيًا عند إجراء تغييرات على الشيفرة. انسخ، في البداية، مستودع nodejs-mongo-mongoose من حساب DigitalOcean Community GitHub. يتضمن هذا المستودع الشيفرة من الإعداد الموضح في كيفية دمج MongoDB مع تطبيقك Node، والذي يشرح كيفية دمج قاعدة بيانات MongoDB مع تطبيق Node موجودٍ سلفًا باستخدام Mongoose. انسخ المستودع في مجلّد يسمى node_project: git clone https://github.com/do-community/nodejs-mongo-mongoose.git node_project انتقل إلى المجلّد node_project: cd node_project افتح ملف package.json الخاص بالمشروع باستخدام nano أو المحرّر الذي تفضّله: nano package.json أسفل اعتماديات المشروع وفوق قوس الإغلاق، أنشئ كائن devDependencies جديد يتضمن nodemon: ... "dependencies": { "ejs": "^2.6.1", "express": "^4.16.4", "mongoose": "^5.4.10" }, "devDependencies": { "nodemon": "^1.18.10" } } احفظ الملف وأغلقه عند الانتهاء من التحرير. بوضعك لشيفرة المشروع في مكانها وتعديل اعتمادياته، يمكنك الانتقال إلى إعادة بناء الشيفرة من أجل سير عملٍ يعتمد على الحاويات. الخطوة الثانية: إعداد تطبيقك للعمل بالحاويات يعني تعديل التطبيق من أجل سير عملٍ يعتمد على الحاويات، جعل الشيفرة أكثر نمطيةً. إذ توفر الحاويات قابلية التنقل بين البيئات، ويجب أن تعكس الشيفرة ذلك من خلال البقاء قدر الإمكان منفصلةً عن نظام التشغيل الأساسي. لتحقيق ذلك، سنعيد تشكيل شيفرتنا للاستفادة بشكل أكبر من خاصية process.env الخاصة ب Node، والتي تعيد كائنًا يحتوي على معلومات حول بيئة المستخدم في وقت التشغيل. يمكننا استخدام هذا الكائن في شيفرتنا لتعيين معلومات التكوين ديناميكيًا في وقت التشغيل باستخدام متغيرات البيئة. لنبدأ بالملف app.js، الذي يمثّل نقطة دخول التطبيق الرئيسية لدينا. افتح هذا الملف: nano app.js سترى فيه تعريفًا للثابتة port، بالإضافة إلى الدالّة listen التي تستخدم هذه الثابتة لتحديد المنفذ الذي سيستمع إليه التطبيق: ... const port = 8080; ... app.listen(port, function () { console.log('Example app listening on port 8080!'); }); دعنا نعيد تعريف ثابتة port للسماح بالتعيين الديناميكي في وقت التشغيل باستخدام الكائن process.env. لذا، أجرِ التعديلات التالية على تعريف الثابتة وعلى الدالّة listen: ... const port = process.env.PORT || 8080; ... app.listen(port, function () { console.log(`Example app listening on ${port}!`); }); يعيّن تعريفنا الثابت الجديد المنفذ port ديناميكيًا باستخدام القيمة الممرّرة في وقت التشغيل أو القيمة 8080. وبالمثل، أعدنا كتابة الدّالة لاستخدام قالب نصّي حديث، والذي سيُقحم قيمة المنفذ عند الاستماع للاتصالات. ونظرًا لأننا سنعيّن منافذنا في أماكن أخرى، فستغنينا هذه المراجعات عن الاضطرار إلى مراجعة هذا الملف بشكل مستمر عند تغيير بيئتنا. احفظ الملف وأغلقه عند الانتهاء من التحرير. بعد ذلك، سنعدّل معلومات اتصال قاعدة البيانات لحذف أي بيانات اعتماد في التكوينات. افتح الملف db.js الذي يحتوي على هذه المعلومات: nano db.js يقوم الملف حاليا بالأمور التالية: يستورد Mongoose، كائن تخطيط المستندات (ODM) الذي نستخدمه لإنشاء مخطّطات ونماذج لبيانات التطبيق. يعيّن بيانات اعتماد قاعدة البيانات كقيم ثابتة، بما في ذلك اسم المستخدم وكلمة المرور. يتصل بقاعدة البيانات باستخدام التابع mongoose.connect. لمزيد من المعلومات حول الملفّ، يرجى الاطلاع على الخطوة الثالثة من كيفية دمج MongoDB مع تطبيق Node الخاص بك. ستكون خطوتنا الأولى في تعديل الملف هي إعادة تحديد الثوابت التي تتضمن معلومات حساسة. تبدو هذه الثوابت حاليًا على النحو التالي: ... const MONGO_USERNAME = 'sammy'; const MONGO_PASSWORD = 'your_password'; const MONGO_HOSTNAME = '127.0.0.1'; const MONGO_PORT = '27017'; const MONGO_DB = 'sharkinfo'; ... يمكنك استخدام الكائن process.env لالتقاط قيم وقت التشغيل لهذه الثوابت بدلاً من الاضطرار لكتابة هذه المعلومات يدويًا. لذا عدّل هذا المقطع من الشيفرة لتبدو على هذا النحو: ... const { MONGO_USERNAME, MONGO_PASSWORD, MONGO_HOSTNAME, MONGO_PORT, MONGO_DB } = process.env; ... احفظ الملف وأغلقه عند الانتهاء من التحرير. في هذه المرحلة، عدّلت الملف db.js للعمل بمتغيرات البيئة في تطبيقك، ولكنك مازلت تحتاج إلى وسيلة لتمرير هذه المتغيرات إلى تطبيقك. لننشئ ملف env. بقيمٍ يمكنك تمريرها إلى التطبيق في وقت التشغيل. افتح هذا الملف: nano .env سيتضمن هذا الملف المعلومات التي حذفتها من db.js وهي اسم المستخدم وكلمة المرور لقاعدة بيانات التطبيق، بالإضافة إلى إعداد المنفذ واسم قاعدة البيانات. لا تنسَ تحديث اسم المستخدم وكلمة المرور واسم قاعدة البيانات المدرجة هنا بمعلوماتك الخاصة: MONGO_USERNAME=sammy MONGO_PASSWORD=your_password MONGO_PORT=27017 MONGO_DB=sharkinfo لاحظ أننا حذفنا إعدادات المضيف التي كانت في الأصل في db.js. والآن، سنعرّف المضيف على مستوى الملف Docker Compose، إلى جانب معلومات أخرى حول الخدمات والحاويات. احفظ هذا الملف وأغلقه عندما تنتهي من التحرير. نظرًا لاحتواء الملف env. على معلومات حساسة، فستحتاج إلى الحرص على تضمينه في ملفات dockerignore. و gitignore. الخاصة بمشروعك حتى لا يُنسخ إلى وحدة إدارة الإصدار أو إلى حاوياتك. افتح ملفك dockerignore.: nano .dockerignore أضف السطر التالي إلى أسفل الملف: ... .gitignore .env احفظ الملف وأغلقه عند الانتهاء من التحرير. يحتوي الملف gitignore. الموجود في هذا المستودع على env. أصلًا، ولكن لا تتردد في التحقق من وجوده: nano .gitignore ... .env ... تمكنت في هذه المرحلة من استخراج المعلومات الحساسة من شيفرة المشروع واتخذت تدابير للتحكم في كيفية ومكان نسخ هذه المعلومات. يمكنك الآن العمل على تمتين الشيفرة الخاصّة بالاتصال بقاعدة البيانات بُغية تحسينها من أجل سير عملٍ يعتمد على الحاويات. الخطوة الثالثة: تعديل إعدادات اتصال قاعدة البيانات ستكون خطوتنا التالية هي جعل طريقة اتصال قاعدة البيانات أكثر متانة عن طريق إضافة شيفرة تعالج الحالات التي يفشل فيها تطبيقنا في الاتصال بقاعدة البيانات. ويُعدّ تقديم هذا المستوى من المرونة لشيفرة التطبيق ممارسةً ينصح بها عند التعامل مع الحاويات باستخدام "Compose". افتح الملفّ db.js لتحريره: nano db.js ستظهر لك الشفرة التي أضفناها سابقًا، إلى جانب ثابتة العنوان url الخاصة بالمعرّف URI لاتصال Mongo والتابع connect فيMongoose: ... const { MONGO_USERNAME, MONGO_PASSWORD, MONGO_HOSTNAME, MONGO_PORT, MONGO_DB } = process.env; const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`; mongoose.connect(url, {useNewUrlParser: true}); يقبل التابع connect حاليًا خيارًا يطلب من Mongoose استخدام مفسّر URL الجديد الخاص بـMongo. دعنا نضيف بعض الخيارات الإضافية إلى هذا التابع لتعريف المعاملات (paramaters) لمحاولات إعادة الاتصال. يمكننا القيام بذلك عبر إنشاء ثابتة options تتضمّن المعلومات ذات الصلة، بالإضافة إلى خيار مفسّر عناوين URL الجديد. أسفل ثوابت Mongo، أضف التعريف التالي للثابتة options: ... const { MONGO_USERNAME, MONGO_PASSWORD, MONGO_HOSTNAME, MONGO_PORT, MONGO_DB } = process.env; const options = { useNewUrlParser: true, reconnectTries: Number.MAX_VALUE, reconnectInterval: 500, connectTimeoutMS: 10000, }; ... يطلب الخيار reconnectTries من Mongoose مواصلة محاولة الاتصال إلى أجل غير محدّد، في حين تحدد reconnectInterval الفترة الفاصلة بين محاولتي اتصال بوحدة الجزء من الألف من الثانية. ويحدّد connectTimeoutMS الفترة التي سينتظرها برنامج التشغيل Mongo قبل إعلان فشل محاولة الاتصال في 10 ثوانٍ. يمكننا الآن استخدام الثابتة الجديدة options في التابع connect لضبط إعدادات اتصال Mongoose بدقّةٍ. سنضيف أيضًا كائن وعدٍ (promise) لمعالجة أخطاء الاتصال المحتملة. يبدو التابع connect الخاصّ ب Mongoose حاليًا على هذا النحو: ... mongoose.connect(url, {useNewUrlParser: true}); احذف التابع connect الموجود حاليًا وعوّضه بالشيفرة التالية التي تتضمن الثابتة options والوعد: ... mongoose.connect(url, options).then( function() { console.log('MongoDB is connected'); }) .catch( function(err) { console.log(err); }); في حالة اتصال ناجح، تسجّل دالّتنا رسالةً مناسبةً. وإلا فإنها تُمسك الخطأ وتسجّله، مما يتيح لنا استكشاف الأخطاء وإصلاحها. سيبدو الملف النهائي كما يلي: const mongoose = require('mongoose'); const { MONGO_USERNAME, MONGO_PASSWORD, MONGO_HOSTNAME, MONGO_PORT, MONGO_DB } = process.env; const options = { useNewUrlParser: true, reconnectTries: Number.MAX_VALUE, reconnectInterval: 500, connectTimeoutMS: 10000, }; const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`; mongoose.connect(url, options).then( function() { console.log('MongoDB is connected'); }) .catch( function(err) { console.log(err); }); احفظ الملف وأغلقه عندما تنتهي من التحرير. لقد أضفت الآن مرونةً إلى شيفرة التطبيق لمعالجة الحالات التي قد يفشل فيها التطبيق في الاتصال بقاعدة البيانات. بوضعك لهذه الشيفرة في مكانها الصحيح، يمكنك الانتقال إلى تعريف خدماتك باستخدام "Compose". الخطوة الرابعة: تعريف الخدمات باستخدام Docker Compose بإعادة تشكيل الشيفرة، تكون مستعدًا لتحرير الملف docker-compose.yml بتعريفات خدماتك. الخدمة في Compose هي عبارة عن حاويةٍ قيد التشغيل، وتحتوي تعريفات الخدمة، التي ستتضمّنها في ملف docker-compose.yml، على معلومات حول كيفية اشتغال كل حاويةٍ صورةٍ (container image). تتيح لك الأداة "Compose" تعريف خدمات متعددة لإنشاء تطبيقات متعددة الحاويات. قبل تعريف خدماتنا، سنضيف، مع ذلك، أداةً إلى مشروعنا تسمى wait-for للتأكد من أن التطبيق لا يحاول الاتصال بقاعدة البيانات فقط بعد اكتمال مهام بدء تشغيل قاعدة البيانات. يستخدم هذا السكربت المجمِّع netcat لاستقصاء ما إذا كان المضيف والمنفذ المعيّنان يقبلان اتصالات TCP أم لا. ويتيح لك استخدامه التحكم في محاولات التطبيق للاتصال بقاعدة البيانات عن طريق اختبار جاهزية قاعدة البيانات لقبول الاتصالات من عدمها. رغم أنّ "Compose" يتيح لك تحديد الاعتماديات بين الخدمات باستخدام الخيار dependson، فإنّ هذا الترتيب يعتمد على ما إذا كانت الحاوية تعمل أم لا وليس على جاهزيتها. لن يكون استخدام dependson هو الأمثل لإعدادنا، لأننا نريد أن يتّصل تطبيقنا بقاعدة البيانات فقط عند اكتمال مهام بدء التشغيل فيها، بما في ذلك إضافة مستخدم وكلمة مرور إلى استيثاق قاعدة البيانات admin. لمزيد من المعلومات حول استخدام wait-for والأدوات الأخرى للتحكم في ترتيب بدء التشغيل، يرجى الاطلاع على التوصيات ذات الصلة في توثيق Compose. افتح ملفًا يسمى wait-for.sh: nano wait-for.sh انسخ الشيفرة التالية في هذا الملف لإنشاء دالة الاستقصاء: #!/bin/sh # original script: https://github.com/eficode/wait-for/blob/master/wait-for TIMEOUT=15 QUIET=0 echoerr() { if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi } usage() { exitcode="$1" cat << USAGE >&2 Usage: $cmdname host:port [-t timeout] [-- command args] -q | --quiet Do not output any status messages -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout -- COMMAND ARGS Execute command with args after the test finishes USAGE exit "$exitcode" } wait_for() { for i in `seq $TIMEOUT` ; do nc -z "$HOST" "$PORT" > /dev/null 2>&1 result=$? if [ $result -eq 0 ] ; then if [ $# -gt 0 ] ; then exec "$@" fi exit 0 fi sleep 1 done echo "Operation timed out" >&2 exit 1 } while [ $# -gt 0 ] do case "$1" in *:* ) HOST=$(printf "%s\n" "$1"| cut -d : -f 1) PORT=$(printf "%s\n" "$1"| cut -d : -f 2) shift 1 ;; -q | --quiet) QUIET=1 shift 1 ;; -t) TIMEOUT="$2" if [ "$TIMEOUT" = "" ]; then break; fi shift 2 ;; --timeout=*) TIMEOUT="${1#*=}" shift 1 ;; --) shift break ;; --help) usage 0 ;; *) echoerr "Unknown argument: $1" usage 1 ;; esac done if [ "$HOST" = "" -o "$PORT" = "" ]; then echoerr "Error: you need to provide a host and port to test." usage 2 fi wait_for "$@" احفظ الملف وأغلقه عند الانتهاء من إضافة الشيفرة. اجعل السكربتَ قابلًا للتنفيذ: chmod +x wait-for.sh بعد ذلك، افتح الملف docker-compose.yml: nano docker-compose.yml ابدأ بتعريف خدمة تطبيق nodejs عبر إضافة الشيفرة التالية إلى الملف: version: '3' services: nodejs: build: context: . dockerfile: Dockerfile image: nodejs container_name: nodejs restart: unless-stopped env_file: .env environment: - MONGO_USERNAME=$MONGO_USERNAME - MONGO_PASSWORD=$MONGO_PASSWORD - MONGO_HOSTNAME=db - MONGO_PORT=$MONGO_PORT - MONGO_DB=$MONGO_DB ports: - "80:8080" volumes: - .:/home/node/app - node_modules:/home/node/app/node_modules networks: - app-network command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js يتضمّن تعريف خدمة nodejs الخيارات التالية: build: هذا يحدد خيارات التكوين، بما في ذلك context وdockerfile، والتي ستُطبّق عندما يبني Docker صورة التطبيق. إذا كنت ترغب في استخدام صورة موجودة من سجلٍّ مثل Docker Hub، فيمكنك استخدام التعليمة image بدلاً من ذلك، مع معلومات حول اسم المستخدم والمستودع ووسم الصورة. context: هذا يحدّد سياق البناء الخاص ببناء الصورة، وهو في هذه الحالة مجلّد المشروع الحالي. dockerfile : هذا يحدّد الملفّ Dockerfile في مجلّد المشروع الحالي لكي يستخدم Compose هذا الملفّ لبناء صورة التطبيق. لمزيد من المعلومات حول هذا الملف، يرجى الاطلاع على [كيفية إنشاء تطبيق Node.js باستخدام Docker](رابط المقال الأول في هذه السلسلة). image و container_name: لإعطاء اسم لكلّ من الصورة والحاوية. restart: هذا يحدّد سياسة إعادة تشغيل الحاوية. تكون القيمة الافتراضية هي no، لكننا أعدنا تعيينها في yes ما لم يتم إيقافها. env_file: هذا يطلب من Compose إضافة متغيرات البيئة من ملفٍّ يسمى env. موجود في سياق البناء. environment: يتيح لك استخدام هذا الخيار إضافة إعدادات اتصال Mongo التي حدّدتها في ملف env. لاحظ أننا لا نعيّن NODEENV على development، لأن هذا هو السلوك الافتراضي لـ Express إذا لم يتم تعيين NODEENV. عند الانتقال إلى الإنتاج، يمكنك تعيين هذا على production لإتاحة التخزين المؤقت للعرض والتقليل من رسائل الخطأ المطوّلة. لاحظ أيضًا أننا حددنا حاوية قاعدة البيانات db كمضيف، كما وضّحناه في الخطوة الثانية. port: هذا يوجّه المنفذ 80 على المضيف إلى المنفذ 8080 على الحاوية. Volumes: نضمّن هنا نوعين من الربط: الأول هو وصل ترابطي (bind mount) يحمّل شيفرة التطبيق على المضيف إلى المجلّد /home/node/app في الحاوية. سيسهل هذا الأمر التطوير بسرعة، إذ سيتم إدخال أي تغييرات تجريها على شيفرة المضيف في الحاوية على الفور. والثاني هو عبارة عن حجمٍ مُسمّى nodemodules. عندما ينفّذ Docker التعليمة npm install المدرجة في Dockerfile، سينشئ npm مجلّدًا جديدًا nodemodules على الحاوية يتضمن الحزم المطلوبة لتشغيل التطبيق. ومع ذلك، سيحجب الوصل الترابطي الذي أنشأناه المجلّدَ nodemodules حديث الإنشاء. ونظرًا لأن nodemodules على المضيف فارغٌ، فسيعمل الوصل على تعيين مجلّدٍ فارغ على الحاوية، مع تحييد المجلّد nodemodules الجديد ومنع التطبيق من بدء التشغيل. ويعمل الحجم nodemodules المسمى على حلّ هذه المشكلة من خلال تثبيت محتويات المجلّد /home/node/app/node_modules وربطه مع الحاوية، مع إخفاء الربط. ضع في حسبانك النقاط التالية عند استخدام هذه الطريقة: سيعمل الربط على تحميل محتويات مجلّد node_modules على الحاوية إلى المضيف وسيكون هذا المجلّد مِلكًا للجذر root، مادام الحجم المسمى أنشئ بواسطة Docker. إذا كان لديك مجلّد nodemodules موجود مسبقًا على المضيف، فسيحيِّد المجلّد nodemodules الذي أنشئ على الحاوية. يفترض الإعداد الذي نبنيه في هذا الدرس أنك لا تملك مجلّدًا node_modules موجود مسبقًا وأنك لن تعمل مع npm على مضيفك. هذا يتماشى مع طريقة اثني عشر عامل لتطوير التطبيقات التي تقلل من الاعتماديات بين بيئات التنفيذ. networks: هذا يحدّد أن خدمة التطبيق ستنضم إلى الشبكة app-network التي سنعرّفها في أسفل الملف. command: يتيح لك هذا الخيار تعيين الأمر الذي يجب تنفيذه عندما يشغّل Compose الصورة. لاحظ أن هذا سوف يحيّد تعليمات CMD التي وضعناها في تطبيق Dockerfile الخاص بنا. نشغِّل هنا التطبيق باستخدام السكربت wait-for، الذي سيستقصي الخدمة db على المنفذ 27017 لاختبار جاهزية خدمة قاعدة البيانات من عدمها. بمجرد نجاح اختبار الاستعداد، سينفذ السكربت الأمر الذي حددناه، /home/node/app/node_modules/.bin/nodemon app.js، لتشغيل التطبيق عبر nodemon. وسيضمن هذا إعادة تحميل أي تعديلات مستقبلية نجريها على شيفرتنا دون الحاجة إلى إعادة تشغيل التطبيق. بعد ذلك، أنشئ خدمة db من خلال إضافة الشيفرة التالية أسفل تعريف خدمة التطبيق: الملف ~/node_project/docker-compose.yml: ... db: image: mongo:4.1.8-xenial container_name: db restart: unless-stopped env_file: .env environment: - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD volumes: - dbdata:/data/db networks: - app-network تظل بعض الإعدادات التي حددناها لخدمة nodejs كما هي، لكننا أجرينا أيضًا التعديلات التالية على تعريفات image و environment و volumes: image: لإنشاء هذه الخدمة، سيسحب Compose صورة 4.1.8-xenial الخاصة ب Mongo من Docker Hub. نحن نثبّت هنا إصدارًا معينًا لتجنب التعارضات المستقبلية المحتملة عند تغير صورة Mongo. لمزيد من المعلومات حول تثبيت الإصدار، يرجى الاطلاع على توثيق Docker حول أفضل ممارسات Dockerfile. MONGOINITDBROOTUSERNAME و MONGOINITDBROOTPASSWORD: تجعل صورة mongo متغيرات البيئة هذه متاحةً لكي تستطيع تعديل التهيئة الأولية لمثيل قاعدة بياناتك. وينشئ MONGOINITDBROOTUSERNAME و MONGOINITDBROOTPASSWORD معًا مستخدمًا root في استيثاق admin لقاعدة البيانات مع التأكد من تفعيل الاستيثاق عند بدء تشغيل الحاوية. لقد عيّننا MONGOINITDBROOTUSERNAME و MONGOINITDBROOTPASSWORD باستخدام قيم مأخوذة من الملف env.، والتي نمرّرها إلى الخدمة db عبر الخيار env_file. يعني القيام بذلك أن مستخدم التطبيق sammy سيكون مستخدمًا أساسيًا في مثيل قاعدة البيانات، مع إمكانية الوصول إلى جميع الصلاحيات الإدارية والتشغيلية لهذا الدور. عند العمل على الإنتاج، سترغب في إنشاء مستخدم تطبيق مخصص ذي صلاحيات محدّدة بدقّة. dbdata:/data/db: سيثبّت الحجم المسمى dbdata البيانات المخزنة في مجلّد البيانات الافتراضي /data/db الخاص بـ Mongo . سيضمن هذا أنك لن تفقد البيانات في الحالات التي توقف فيها أو تحذف الحاويات. لقد أضفنا أيضًا خدمة db إلى شبكة app-network باستخدام الخيار networks. كخطوة أخيرة، أضف تعريفات الحجم والشبكة إلى أسفل الملف: ... networks: app-network: driver: bridge volumes: dbdata: node_modules: تتيح شبكة المستخدم الجسرية app-network التواصل بين حاوياتنا مادامت موجودة على نفس المضيف العفريت Docker. يعمل هذا على تبسيط حركة المرور والاتصال داخل التطبيق، إذ يفتح جميع المنافذ بين الحاويات على نفس الشبكة الجسرية، دون تعريض أي منفذ للعالم الخارجي. وبالتالي، فإن حاويات db و nodejs تستطيع التواصل مع بعضها البعض، وسنحتاج فقط إلى فتح المنفذ 80 للوصول الأمامي (front-end access) إلى التطبيق. يعرّف مفتاح المستوى الأعلى volumes الحجمين dbdata و node_modules. وعندما ينشئ Docker حجومًا، تُخزّن محتوياتها في جزء من ملفات المضيف النظامية، /var/lib/docker/volumes/ ، التي يديرها Docker. تخزّن محتويات كل حجمٍ في مجلّد تحت /var/lib/docker/volumes/ وتُثبت على أي حاوية تستخدم الحجم. وبهذه الطريقة، ستكون بيانات معلومات سمك القرش التي سيقوم المستخدمون بإنشائها مثبّتة في الحجم dbdata حتى لو أزلنا الحاوية db وأعدنا إنشاءها. سيبدو ملف docker-compose.yml النهائي كما يلي: version: '3' services: nodejs: build: context: . dockerfile: Dockerfile image: nodejs container_name: nodejs restart: unless-stopped env_file: .env environment: - MONGO_USERNAME=$MONGO_USERNAME - MONGO_PASSWORD=$MONGO_PASSWORD - MONGO_HOSTNAME=db - MONGO_PORT=$MONGO_PORT - MONGO_DB=$MONGO_DB ports: - "80:8080" volumes: - .:/home/node/app - node_modules:/home/node/app/node_modules networks: - app-network command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js db: image: mongo:4.1.8-xenial container_name: db restart: unless-stopped env_file: .env environment: - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD volumes: - dbdata:/data/db networks: - app-network networks: app-network: driver: bridge volumes: dbdata: node_modules: احفظ الملف وأغلقه عند الانتهاء من التحرير. بوضعك لتعريفات الخدمة في مكانها الصحيح، تكون مستعدًّا لبدء تشغيل التطبيق. الخطوة الخامسة: اختبار التطبيق بعد إنشاء وتحرير ملف docker-compose.yml ، يمكنك إنشاء خدماتك باستخدام الأمر docker-compose up. يمكنك أيضًا اختبار ثبات بياناتك عبر إيقاف حاوياتك وحذفها باستخدام Docker. ابدأ أولًا ببناء صور الحاوية وإنشاء الخدمات عبر تنفيذ docker-compose up باستخدام الراية d- ، والذي سيشغّل حاويات nodejs و db في الخلفية: docker-compose up -d سيظهر لك الإخراج الذي يؤكد أن خدماتك أُنشئت فعلًا: ... Creating db ... done Creating nodejs ... done يمكنك أيضًا الحصول على معلومات أكثر تفصيلاً حول عمليات التشغيل بعرضك لإخراج سجل الخدمات: docker-compose logs سيظهر لك إخراج مثل هذا إذا اشتغل كل شيء بشكل صحيح: ... nodejs | [nodemon] starting `node app.js` nodejs | Example app listening on 8080! nodejs | MongoDB is connected ... db | 2019-02-22T17:26:27.329+0000 I ACCESS [conn2] Successfully authenticated as principal sammy on admin يمكنك أيضًا التحقق من حالة حاوياتك باستخدام docker-compose ps: docker-compose ps سيظهر لك الإخراج التالي مشيرًا إلى أن حاوياتك تعمل: Name Command State Ports ---------------------------------------------------------------------- db docker-entrypoint.sh mongod Up 27017/tcp nodejs ./wait-for.sh db:27017 -- ... Up 0.0.0.0:80->8080/tcp بعد تشغيل خدماتك، يمكنك تصفّح: http://yourserverip في المتصفح. ستظهر لك صفحة الهبوط على هذا النحو: انقر على زر الحصول على معلومات القرش. ستظهر لك صفحة بنموذج إدخال يمكنك إدخال الاسم ووصف السلوك العام لسمك القرش: أضف سمكة قرش من اختيارك في النموذج. لأغراض هذا العرض التوضيحي، سنضيف Megalodon Shark إلى حقل Shark Name، وAncient إلى حقل Shark Character: انقر على زر الإرسال. ستظهر لك صفحة بها معلومات القرش معروضة: كخطوة أخيرة، يمكننا اختبار ثبات البيانات التي أدخلتها للتو إذا حذفنا حاوية قاعدة البيانات. ارجع مرة أخرى إلى الطرفية، واكتب الأمر التالي لإيقاف وحذف الحاويات والشبكة: docker-compose down لاحظ أننا لم ندرج الخيار --volumes؛ وبالتالي، لن يُحذف الحجم dbdata. يؤكد الإخراج التالي حذف الحاويات والشبكة: Stopping nodejs ... done Stopping db ... done Removing nodejs ... done Removing db ... done Removing network node_project_app-network أعد الآن إنشاء الحاويات: docker-compose up -d ثم عد إلى نموذج معلومات سمك القرش: أدخل سمكة قرش جديدة من اختيارك. سنستعمل هنا Whale Shark وLarge: بمجرد النقر على زر الإرسال، سترى أن القرش الجديد أُضيف إلى مجموعة سمك القرش في قاعدة بياناتك دون أن تفقد البيانات التي أدخلتها سابقًا: إنّ تطبيقك يشتغل الآن على حاويات Docker مع تفعيل خاصيتي ثبات البيانات ومزامنة الشيفرة. خاتمة باتباع هذا الدرس، أنشأت إعداد تطويرٍ لتطبيقك Node باستخدام حاويات Docker. لقد تمكنت من جعل مشروعك أكثر نمطيّةً وأكثر قابلية للنقل من خلال استخراج المعلومات الحساسة وفصل حالة التطبيق عن شيفرة التطبيق. لقد أعددت أيضًا تكوين ملف الشيفرة المتداولة docker-compose.yml الذي تستطيع مراجعته كلما تغيرت احتياجاتك ومتطلبات التطوير الخاصة بك. قد تكون مهتمًا، أثناء عمليتك التطويرية ، بمعرفة المزيد حول تصميم التطبيقات بسير عمل يعتمد على الحاويات وعلى Cloud Native. يرجى الاطلاع على تصميم التطبيقات لKubernetes وتحديث التطبيقات ل Kubernetes إذا كانت لغتك الإنجليزية جيدة لمزيد من المعلومات حول هذه المواضيع. لمعرفة المزيد حول الشيفرة المستخدم في هذا الدرس، يرجى الاطلاع على كيفية إنشاء تطبيق Node.js باستخدام Docker وكيفية دمج MongoDB مع تطبيق Node الخاص بك. وللحصول على معلومات حول نشر تطبيق Node باستخدام وكيل عكسي Nginx يعتمد على الحاويات، يرجى الاطلاع على كيفية تأمين تطبيق Node.js في حاويات باستخدام Nginx و Let’s Encrypt و Docker Compose. ترجمة -وبتصرف- للمقال Containerizing a Node.js Application for Development With Docker Compose لصاحبته Kathleen Juell
-
قد تجد نفسك، وأنت تستخدم node.js، أنك بصدد تطوير مشروع يُخزّن البيانات والاستعلام عنها. في هذه الحالة، ستحتاج إلى اختيار حلّ تقني لقاعدة البيانات يكون ملائما لأنواع البيانات والاستعلامات التي يستخدمها تطبيقك. في هذا الدّرس، ستعمل على دمج قاعدة بيانات MongoDB مع تطبيق Node موجودٍ سلفًا. يمكن أن تكون قواعد البيانات NoSQL مثل MongoDB مفيدةً إذا كانت متطلبات بياناتك تتضمن قابلية التوسع والمرونة. ويتكامل MongoDB جيدًا مع Node لأنه مصمم للعمل بشكل غير متزامن مع كائنات JSON. من أجل دمج MongoDB في مشروعك، سوف تستخدم Object Document Mapper) ODM) الخاص ب Mongoose لإنشاء مخططات ونماذج بيانات لتطبيقك. سيتيح لك ذلك تنظيم شيفرة التطبيق وفقًا للنمط الهيكلي MVC، والذي يسمح بفصل المنطق الذي يحكم كيفية معالجة التطبيق لإدخالات المستخدم عن ذلك الذي يتعلق بكيفية هيكلة البيانات وتقديمها إلى المستخدم. ويسهّل الاعتمادُ على هذا النمط الاختبار والتطوير في المستقبل عبر فصل المتعلّقات في قاعدة البيانات الخاصة بك. في نهاية البرنامج التعليمي، سيكون لديك تطبيق جاهز خاصّ بمعلومات سمك القرش يعمل على أخذ مدخلات المستخدم حول أسماك القرش المفضلة لديه وعرض النتائج في المتصفح: المتطلبات الأساسية جهاز أو خادم تطوير محلي يعمل على نظام أوبونتو 18.04، إلى جانب مستخدم غير جذري بصلاحيات sudo وجدار حماية نشط. للحصول على إرشادات حول كيفية إعدادها على خادم 18.04، يرجى الاطلاع على دليل إعداد الخادم الأولي. Node.js و npm مثبتين على الجهاز أو الخادم، باتباع هذه الإرشادات حول التثبيت باستخدام PPA المدار بواسطة NodeSource. تثبيت MongoDB على الجهاز أو الخادم، باتباع الخطوة الأولى من كيفية تثبيت MongoDB في أوبونتو 18.04. الخطوة الأولى: إنشاء مستخدم Mongo قبل أن نبدأ العمل بشيفرة التطبيق، سننشئ مستخدمًا إداريًا يملك صلاحية الوصول إلى قاعدة بيانات التطبيق. سيكون لهذا المستخدم أذونات إدارية على أي قاعدة بيانات، مما يمنحك المرونة في التبديل وإنشاء قواعد بيانات جديدة حسب الحاجة. أولاً، تحقق أن MongoDB يشتغل على خادمك: sudo systemctl status mongodb يدلّ الإخراج التالي على أنّ MongoDB يشتغل: ● mongodb.service - An object/document-oriented database Loaded: loaded (/lib/systemd/system/mongodb.service; enabled; vendor preset: enabled) Active: active (running) since Thu 2019-01-31 21:07:25 UTC; 21min ago ... بعد ذلك، افتح الصدفة الخاصة ب Mongo من أجل إنشاء مستخدمك: Mongo سينقلك هذا الأمر إلى صدفة إدارية: MongoDB shell version v3.6.3 connecting to: mongodb://127.0.0.1:27017 MongoDB server version: 3.6.3 ... > ستظهر لك بعض التحذيرات الإدارية عند فتح الصدفة (shell) نظرًا لغياب قيود على وصولك إلى قاعدة بيانات الخاصة بالمشرف admin. لمعرفة المزيد حول تقييد هذا الوصول يمكنك قراءة كيفية تثبيت وتأمين MongoDB على أوبونتو 18.04، عندما تنتقل إلى إعدادات الإنتاج. تستطيع الآن الاستفادة من وصولك إلى قاعدة بيانات المشرف admin لإنشاء مستخدم يمتلك الأذونات userAdminAnyDatabase التي تتيح الوصول المحمي بكلمة مرور لقواعد بيانات التطبيق. حدّد في الصّدفة shell، أنك تريد استخدام قاعدة بيانات المشرف admin لإنشاء مستخدمك: use admin بعد ذلك، أنشئ دورًا (role) وكلمة مرور عبر إضافة اسم مستخدم وكلمة مرور باستخدام الأمر db.createUser. بعد كتابة هذا الأمر، ستُظهِر الصدفة ثلاث نقاط قبل كل سطر حتى يكتمل تنفيذ الأمر. تأكد من تغيير المستخدم وكلمة المرور المقدمين هنا باسم المستخدم وكلمة المرور الخاصين بك: db.createUser( { user: "sammy", pwd: "your_password", roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] } ) سينشئ هذا الأمر إدخالًا للمستخدم sammy في قاعدة بيانات المشرف admin. وسيعمل اسم المستخدم الذي تحدده وقاعدة بيانات المشرف admin كمحدّدات للمستخدم. وسيبدو إخراج العملية برمّتها كما يلي، بما في ذلك الرسالة التي تشير إلى نجاح الإدخال: > db.createUser( ... { ... user: "sammy", ... pwd: "your_password", ... roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] ... } ...) Successfully added user: { "user" : "sammy", "roles" : [ { "role" : "userAdminAnyDatabase", "db" : "admin" } ] } يمكنك بعد إنشاء المستخدم وكلمة المرور، الخروج من الصّدفة الخاصة ب Mongo: exit وتستطيع كذلك الآن، بعد أن أنشأت مستخدم قاعدة البيانات الخاصة بك، الانتقال إلى استنساخ شيفرة المشروع البدئية وإضافة مكتبة Mongoose، والتي سوف تسمح لك بتنفيذ المخططات والنماذج للمجموعات (collections) في قواعد بياناتك. الخطوة الثانية: إضافة معلومات Mongoose وقاعدة البيانات إلى المشروع ستكون خطوتنا التالية هي استنساخ شيفرة التطبيق البدئية وإضافة معلومات Mongoose وقاعدة بيانات MongoDB إلى المشروع. في المجلّد الرئيسي للمستخدم غير الجذري، استنسخ مستودع nodejs-image-demo من حساب DigitalOcean Community GitHub. ويتضمن مستودع التخزين هذا الشيفرة الخاصة بالإعداد الموضح في كيفية إنشاء تطبيق Node.js باستخدام Docker. انسخ المستودع في مجلّد يسمى node_project: git clone https://github.com/do-community/nodejs-image-demo.git node_project انتقل إلى المجلّد node_project: cd node_project قبل تعديل شيفرة المشروع، دعنا نلقي نظرة على بنية المشروع باستخدام الأمر tree . ملحوظة: يعدّ الأمر tree مفيدًا لعرض بنيات الملفات والمجلّدات من سطر الأوامر. يمكنك تثبيته باستخدام الأمر التالي: sudo apt install tree ومن أجل استخدامه، انتقل بالأمر cd إلى مجلّد معين واكتب tree. يمكنك أيضًا أن تزوّد الأمر بكامل المسار إلى النقطة المحدّدة باستخدام أمر مثل: tree /home/sammy/sammys-project اكتب ما يلي لمعاينة المجلّد node_project: tree تبدو بنية المشروع الحالي كما يلي: ├── Dockerfile ├── README.md ├── app.js ├── package-lock.json ├── package.json └── views ├── css │ └── styles.css ├── index.html └── sharks.html سنضيف كلما تقدمنا في هذا البرنامج التعليمي بعض المجلّدات إلى هذا المشروع، وسيكون الأمر tree مفيدًا لمساعدتنا في تتبع تقدمنا. بعد ذلك، أضف حزمة npm mongoose إلى المشروع باستخدام الأمر npm install: npm install mongoose سينشئ هذا الأمر مجلّد node_modules في مجلّد مشروعك، باستخدام الاعتماديات المدرجة في ملف package.json الخاص بالمشروع، وسيضيف كذلك mongoose إلى هذا المجلّد. سيضيف mongoose أيضًا إلى الاعتماديات المدرجة في ملفك package.json. للحصول على توضيح أكثر تفصيلا عن package.json، يرجى الاطلاع على الخطوة الأولى في كيفية إنشاء تطبيق Node.js باستخدام Docker. قبل إنشاء أي مخططات أو نماذج لMongoose، سنضيف معلومات اتصال قاعدة البيانات حتى يتمكن التطبيق من الاتصال بقاعدة البيانات الخاصة بنا. من أجل فصل العناصر المتعلّقة بالتطبيق قدر الإمكان، أنشئ ملفًا منفصلًا لمعلومات اتصال قاعدة البيانات باسم db.js. ويمكنك فتح هذا الملف باستخدام nano أو المحرر المفضل لديك: nano db.js ستستورد أولاً وحدة mongoose باستخدام الدالّة require: الملف ~/node_project/db.js: const mongoose = require('mongoose'); سوف يتيح لك ذلك الوصول إلى توابع Mongoose المدمجة، والتي ستستخدمها لإنشاء اتصال بقاعدة البيانات الخاصة بك. بعد ذلك، أضف الثوابت التالية لتحديد معلومات الاتصال URI الخاصة بMongo. رغم أن اسم المستخدم وكلمة المرور اختياريان، ولكننا سندرجهما حتى نتمكن من طلب مصادقة الهوية على قاعدة البيانات. تأكد من استبدال اسم المستخدم وكلمة المرور بمعلوماتك الخاصة، وتبقى لك الحرية في طلب شيء آخر من قاعدة البيانات غير sharkinfo إذا كنت تفضل ذلك: الملف ~/node_project/db.js: const mongoose = require('mongoose'); const MONGO_USERNAME = 'sammy'; const MONGO_PASSWORD = 'your_password'; const MONGO_HOSTNAME = '127.0.0.1'; const MONGO_PORT = '27017'; const MONGO_DB = 'sharkinfo'; نظرًا لأننا نشغّل قاعدة البيانات محليًا، فقد استخدمنا 127.0.0.1 اسمًا للمضيف. قد يكون الأمر مغايرًا في سياقات تطوير أخرى: على سبيل المثال، إذا كنت تستخدم خادم قاعدة بيانات منفصل أو تعمل باستخدام عقد متعدّدة في إطار سير عمل يتضمن حاويات. ختامًا، حدّد قيمة ثابتة لـ URI وأنشئ الاتصال باستخدام التابع ()mongoose.connect: الملف ~/node_project/db.js: ... const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`; mongoose.connect(url, {useNewUrlParser: true}); لاحظ أننا في URI حدّدنا authSource لمستخدمنا كمشرف لقاعدة البيانات. ويعدّ هذا الأمر ضروريًا لأننا حددنا اسم مستخدم في سلسلة الاتصال connection string. ويحدّد استخدام الراية useNewUrlParser مع التابع ()mongoose.connect أننا نريد استخدام مفسّر URL الجديد الخاص بـ Mongo. احفظ الملف وأغلقه عند الانتهاء من التحرير. كخطوة أخيرة، أضف معلومات اتصال قاعدة البيانات إلى ملف app.js بحيث يمكن للتطبيق استخدامها. ثم افتح التطبيق. nano app.js ستبدو الأسطر الأولى من الملف كما يلي: الملف ~/node_project/app.js: const express = require('express'); const app = express(); const router = express.Router(); const path = __dirname + '/views/'; ... أسفل السّطر الذي يحدّد القيمة الثابتة للكائن router، الموجود قريبًا من أعلى الملف، أضف السطر التالي: الملف ~/node_project/app.js: ... const router = express.Router(); const db = require('./db'); const path = __dirname + '/views/'; ... يطلب هذا الأمر من التطبيق استخدام معلومات اتصال قاعدة البيانات المحددة في db.js. احفظ الملف وأغلقه عند الانتهاء من التحرير. بوجود معلومات قاعدة البيانات وبعد إضافتها إلى مشروعك، تكون مستعدًا لإنشاء المخططات والنماذج التي ستشكل البيانات في المجموعة sharks. الخطوة الثالثة: إنشاء مخططات ونماذج Mongoose ستكون خطوتنا التالية هي التفكير في بنية المجموعة sharks التي سينشئها المستخدمون في قاعدة بيانات sharkinfo بواسطة مدخلاتهم. فما هي البنية التي نريد أن تكون لهذه المستندات التي أُنشئت؟ تتضمن صفحة معلومات سمك القرش في تطبيقنا الحالي بعض التفاصيل حول أسماك القرش المختلفة وسلوكياتها: وفقًا لذلك، سيكون بإمكان المستخدمين أن يضيفوا أسماك قرش جديدة مرفقة بتفاصيل حول سلوكها العام. وسيحدّد هذا الهدف كيفية إنشاء مخططنا. من أجل تمييز مخططاتك ونماذجك عن الأجزاء الأخرى لتطبيقك، أنشئ مجلّدا models في مجلّد المشروع الحالي: mkdir models بعد ذلك، افتح ملفًا باسم sharks.js لإنشاء مخططك ونموذجك: nano models/sharks.js استورد وحدة Mongose في الجزء العلوي من الملف: الملف ~/node_project/models/sharks.js: const mongoose = require('mongoose'); بعد ذلك، حدّد كائنًا Schema لاستخدامه أساسًا للمخطط الخاصّ بأسماك القرش: الملف ~/node_project/models/sharks.js: const mongoose = require('mongoose'); const Schema = mongoose.Schema; يمكنك الآن تحديد الحقول التي تريد تضمينها في مخطّطك. ونظرًا لأننا نريد إنشاء مجموعة تحتوي على أسماك قرش فردية ومعلومات حول سلوكياتها، فلنُضمِّن فيها مفتاحين واحدٌ للاسم name والآخر للسلوك character. أضف مخطط Shark التالي أسفل تعريفات الثوابت: الملف ~/node_project/models/sharks.js: ... const Shark = new Schema ({ name: { type: String, required: true }, character: { type: String, required: true }, }); يتضمن هذا التعريف معلومات حول نوع الإدخال الذي نتوقعه من المستخدمين، وهو في هذه الحالة، سلسلة نصّية، وهل هذا الإدخال ضروري أم لا. أخيرًا ، أنشئ نموذج Shark باستخدام دالة Mongoose التالية model(). سيتيح لك هذا النموذج الاستعلام عن المستندات من خلال مجموعتك والتحقق من صحة المستندات الجديدة. أضف السطر التالي في أسفل الملف: ... module.exports = mongoose.model('Shark', Shark) يجعل هذا السطر الأخير النموذج Shark متاحًا كوحدة نمطية باستخدام خاصية module.exports. إذ تحدّد هذه الخاصية القيم التي ستصدرها الوحدة النمطية، مما يجعلها متاحة للاستخدام في أي مكان آخر في التطبيق. سيبدو الملف النهائي models/sharks.js على هذا النحو: const mongoose = require('mongoose'); const Schema = mongoose.Schema; const Shark = new Schema ({ name: { type: String, required: true }, character: { type: String, required: true }, }); module.exports = mongoose.model('Shark', Shark) احفظ الملف وأغلقه عند الانتهاء من التحرير. بإنشائك لمخطط Shark ونموذجه، سيكون بإمكانك البدء في العمل على المنطق الذي تريده أن يحكم كيفية تعامل تطبيقك مع مدخلات المستخدم. الخطوة الرابعة: إنشاء وحدات التحكم ستكون خطوتنا التالية هي إنشاء وحدة التحكم التي تحدد كيفية حفظ مدخلات المستخدم في قاعدة البيانات وإعادتها إليه. أنشئ أولاً مجلّدًا لوحدة التحكم: mkdir controllers بعد ذلك، افتح ملفًا في هذا المجلد باسم sharks.js: nano controllers/sharks.js في الجزء العلوي من الملف، سنستورد الوحدة مع النموذج Shark لكي نستطيع استخدامها في منطق وحدة التحكم لدينا. سنستورد أيضًا وحدة المسار path للوصول إلى الأدوات المساعدة التي ستتيح لنا تعيين المسار إلى الاستمارة التي سيدخل فيها المستخدمون معلوماتهم عن أسماك القرش. أضف الدوالّ require التالية إلى بداية الملف: الملف ~/node_project/controllers/sharks.js: const path = require('path'); const Shark = require('../models/sharks'); بعد ذلك، سنكتب سلسلة من الدوال التي سنصدّرها مع وحدة التحكم باستخدام اختصارات Node للتصدير exports. ستشمل هذه الدوالّ المهام الثلاثة المتعلقة ببيانات سمك القرش الخاصة بمستخدمنا: إرسال إستمارة إدخال سمك القرش للمستخدمين. إنشاء إدخالٍ لقرش جديد. إعادة عرض أسماك القرش للمستخدمين. من أجل البدء، أنشئ دالةً index لعرض صفحة أسماك القرش مع إستمارة الإدخال. أضف هذه الدّالة أسفل الاستيرادات: ... exports.index = function (req, res) { res.sendFile(path.resolve('views/sharks.html')); }; بعد ذلك أضف، أسفل الدالة index، دالّة بالاسم create لإنشاء إدخال جديد لسمك القرش في المجموعة Sharks: ... exports.create = function (req, res) { var newShark = new Shark(req.body); console.log(req.body); newShark.save(function (err) { if(err) { res.status(400).send('Unable to save shark to database'); } else { res.redirect('/sharks/getshark'); } }); }; سوف تستدعى هذه الدّالة عندما يرسل المستخدم بيانات سمك القرش إلى الإستمارة على الصفحة sharks.html. سننشئ لاحقًا المسار route الخاصّ بهذا الإرسال POST في هذا البرنامج التعليمي عندما ننشئ مسارات التطبيق. باستخدام المحتوى body الخاصّ بالطلب POST، ستنشئ الدالة create كائن ملفّ سمك القرش الجديد، الذي يأخذ هنا الاسم newShark، اعتمادًا على النموذج Shark الذي استوردناه. ولقد أضفنا التابع console.log من أجل إظهار الإدخال الخاصّ بسمك القرش إلى الطرفية من أجل التحقق من أن طريقة POST تعمل على النحو المنشود، ولكن تبقى لك الحرّية في تجاوز هذا الأمر إذا كنت تفضل ذلك. باستخدام الكائنnewShark ، ستستدعي الدالة create بعد ذلك تابع Mongoose المسمّى model.save() من أجل إنشاء ملفّ سمك قرش جديد اعتمادًا على المفاتيح التي حدّدتها في النموذج Shark. تتبع دالّة ردّ النداء هذه نموذج ردّ النداء المعياري في Node وهو: callback(error, results). في حالة وجود خطأ، نرسل رسالة تُبلغ المستخدمين عن الخطأ، وفي حالة نجاح العمليّة، نستخدم التابع res.redirect() لتوجيه المستخدمين إلى نقطة النهاية التي ستعيد معلومات القرش إليهم على المتصفح. في الختام، ستعرض الدّالة list محتويات المجموعة مرة أخرى للمستخدم. أضف الشيفرة التالية أسفل الدالة create: ... exports.list = function (req, res) { Shark.find({}).exec(function (err, sharks) { if (err) { return res.send(500, err); } res.render('getshark', { sharks: sharks }); }); }; تستخدم هذه الدالّة النموذج Shark بالاعتماد على التابع ()model.find الخاصّ بMongoose من أجل إرجاع معلومات أسماك القرش التي أُدخِلت في المجموعة sharks. يتمّ هذا الأمر عبر إرجاع كائن الاستعلام query object، ويمثل في هذه الحالة جميع الإدخالات في المجموعة sharks، باستخدام الدالّة ()exec الخاصّة ب Mongoose. في حالة وجود خطأ، سترسل دالّة رد النّداء خطأً 500. سوف يُعرض كائن الاستعلام الذي أرجعته الدّالة مع المجموعة sharks في الصفحة getshark التي سننشئها في الخطوة التالية باستخدام لغة القوالب EJS. سيبدو الملف النهائي ~/node_project/controllers/sharks.js على هذا النحو: const path = require('path'); const Shark = require('../models/sharks'); exports.index = function (req, res) { res.sendFile(path.resolve('views/sharks.html')); }; exports.create = function (req, res) { var newShark = new Shark(req.body); console.log(req.body); newShark.save(function (err) { if(err) { res.status(400).send('Unable to save shark to database'); } else { res.redirect('/sharks/getshark'); } }); }; exports.list = function (req, res) { Shark.find({}).exec(function (err, sharks) { if (err) { return res.send(500, err); } res.render('getshark', { sharks: sharks }); }); }; ضع في اعتبارك أنه رغم أننا لا نستخدم الدّوال السهمية هنا، فقد ترغب في تضمينها أثناء تكرارك لهذه الشيفرة في عمليتك التطويرية. احفظ الملف وأغلقه عند الانتهاء من التحرير. قبل الانتقال إلى الخطوة التالية، يمكنك تنفيذ الأمر tree مرة أخرى من المجلّد nodeproject لعرض بنية المشروع في هذه المرحلة. هذه المرة، ومن أجل الإيجاز، سنطلب من tree استبعاد المجلّد nodemodules عبر الراية -I: tree -I node_modules مع الإضافات التي أجريتها، ستبدو بنية مشروعك على النحو التالي: ├── Dockerfile ├── README.md ├── app.js ├── controllers │ └── sharks.js ├── db.js ├── models │ └── sharks.js ├── package-lock.json ├── package.json └── views ├── css │ └── styles.css ├── index.html └── sharks.html الآن بعد أن أصبحت لديك وحدة تحكم لتوجيه كيفية حفظ مدخلات المستخدم وإعادتها إليه، يمكنك الانتقال إلى إنشاء طرق العرض التي ستبلور منطق وحدة التحكم الخاصة بك. الخطوة الخامسة: استخدام برمجيات EJS و Express الوسيطة لجمع البيانات وعرضها من أجل تمكين التطبيق من العمل ببيانات المستخدم، سنقوم بأمرين مهمّين: سنضمّن أولاً دالة وسيطة Express تسمى ()urlencoded، والتي ستمكن تطبيقنا من تحليل بيانات المستخدم المُدخلَة. ثم سنضيف ثانيا علامات القوالب (template tags) إلى واجهة العرض views لتمكين التفاعل الديناميكي مع بيانات المستخدم في الشيفرة. من أجل استخدام الدالة ()urlencoded الخاصة بـ Express، افتح أولاً الملف app.js: nano app.js أضف السطر التالي فوق الدالة ()express.static في الملف ~/node_project/app.js: ... app.use(express.urlencoded({ extended: true })); app.use(express.static(path)); ... ستمكّن إضافة هذه الدّالة الوصول إلى بيانات POST المحلّلة (parsed) في استمارة معلومات سمك القرش. نحدّد القيمة true في الخيار extended لإتاحة قدر أكبر من المرونة في نوع البيانات التي سيحللها التطبيق (بما في ذلك الكائنات مثل الكائنات المتداخلة). يرجى الاطلاع على توثيق الدّالة لمزيد من المعلومات حول الخيارات. احفظ الملف وأغلقه عند الانتهاء من التحرير. بعد ذلك، سنضيف إمكانية استخدام القالب إلى واجهات العرض. ثبّت أولاً حزمة ejs باستخدام instaill npm: npm install ejs بعد ذلك ، افتح ملف sharks.html في المجلد views: nano views/sharks.html لقد ألقينا نظرة من قبل في الخطوة الثالثة على هذه الصفحة لتحديد كيفية كتابة المخطط والنموذج Mongoose: الآن ، بدلاً من وجود تنسيق بعمودين، سنضيف عمودًا ثالثًا يحتوي على استمارة يمكن للمستخدمين إدخال معلومات حول أسماك القرش فيها. كخطوة أولى، عدّل أبعاد الأعمدة الموجودة إلى 4 لإنشاء ثلاثة أعمدة متساوية الحجم. لاحظ أنك ستحتاج إلى إجراء هذا التعديل على السطرين اللذين يقرآن حاليًا <div class="col-lg-6">. سيصبح كلاهما <div class="col-lg-4">: الملف ~/node_project/views/sharks.html: ... <div class="container"> <div class="row"> <div class="col-lg-4"> <p> <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans. </div> <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark"> </p> </div> <div class="col-lg-4"> <p> <div class="caption">Other sharks are known to be friendly and welcoming!</div> <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark"> </p> </div> </div> </div> </html> للاطلاع على توضيح أكثر لنظام الشبكة في Bootstrap، بما في ذلك تخطيطات الصفوف والأعمدة، يرجى الاطلاع على هذه المقدمة إلى إطار Bootstrap. بعد ذلك، أضف عمودًا آخر يتضمن نقطة النهاية المحددة للطلب POST مع بيانات المستخدم عن سمك القرش ووسوم القالب EJS التي ستلتقط تلك البيانات. سيكون هذا العمود أسفل وسمي الإغلاق </p> و </div> للعمود السابق وفوق وسوم الإغلاق للصف والحاوية والصفحة HTML. وتوجد وسوم الإغلاق هذه بالفعل في الشّيفرة. اتركها في مكانها وأنت تضيف الشيفرة التالية لإنشاء العمود الجديد: ... </p> <!-- closing p from previous column --> </div> <!-- closing div from previous column --> <div class="col-lg-4"> <p> <form action="/sharks/addshark" method="post"> <div class="caption">Enter Your Shark</div> <input type="text" placeholder="Shark Name" name="name" <%=sharks[i].name; %> <input type="text" placeholder="Shark Character" name="character" <%=sharks[i].character; %> <button type="submit">Submit</button> </form> </p> </div> </div> <!-- closing div for row --> </div> <!-- closing div for container --> </html> <!-- closing html tag --> تضيف هنا في الوسم form نقطة نهاية "/sharks/addhark" لبيانات سمك القرش الخاصة بالمستخدم وتحدّد الطريقة POST لإرسالها. كما تحدّد في حقول الإدخال، الحقلين "Shark Name" و"Shark Character" ، وفقًا للنموذج Shark الذي حدّدته سابقًا. لإضافة مُدخلات المستخدم إلى المجموعة sharks، فأنت تستخدم وسوم قالب:EJS (<%=, %>) بناءً على قواعد لغة جافاسكربت لتوجيه مدخلات المستخدم إلى الحقول المناسبة في المستند الذي أنشئ حديثًا. لمزيد من المعلومات حول كائنات جافاسكربت، يرجى مراجعة توثيق جافاسكربت في الموسوعة. ولمعرفة المزيد عن وسوم قالبEJS ، يرجى الاطلاع على التوثيق الرسمي لEJS. ستبدو الحاوية بأكملها مع جميع الأعمدة الثلاثة، بما في ذلك العمود الذي يحتوي على استمارة إدخال سمك القرش، على هذا النحو عند الانتهاء: الملف ~/node_project/views/sharks.html: ... <div class="container"> <div class="row"> <div class="col-lg-4"> <p> <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans. </div> <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark"> </p> </div> <div class="col-lg-4"> <p> <div class="caption">Other sharks are known to be friendly and welcoming!</div> <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark"> </p> </div> <div class="col-lg-4"> <p> <form action="/sharks/addshark" method="post"> <div class="caption">Enter Your Shark</div> <input type="text" placeholder="Shark Name" name="name" <%=sharks[i].name; %> <input type="text" placeholder="Shark Character" name="character" <%=sharks[i].character; %> <button type="submit">Submit</button> </form> </p> </div> </div> </div> </html> احفظ الملف وأغلقه عند الانتهاء من التحرير. الآن بعد أن أصبح لديك طريقة لجمع مدخلات المستخدم، يمكنك إنشاء نقطة نهاية (endpoint) لعرض أسماك القرش المُعادة وكذلك معلومات السلوك المرتبطة بها. انسخ ملف sharks.html المعدّل حديثًا إلى ملف يسمى getshark.html: cp views/sharks.html views/getshark.html افتح الملف getshark.html: nano views/getshark.html داخل الملف، سنعدّل العمود الذي استخدمناه لإنشاء استمارة إدخال أسماك القرش من خلال استبداله بعمود سيعرض أسماك القرش في المجموعة sharks. مرة أخرى، ستمتدّ الشفرة بين وسمي <p/> و </div> للعمود السابق ووسوم الإغلاق للصف والحاوية والصفحة HTML. احرص على ترك هذه الوسوم في مكانها وأنت تضيف الشيفرة التالية لإنشاء العمود: ... </p> <!-- closing p from previous column --> </div> <!-- closing div from previous column --> <div class="col-lg-4"> <p> <div class="caption">Your Sharks</div> <ul> <% sharks.forEach(function(shark) { %> <p>Name: <%= shark.name %></p> <p>Character: <%= shark.character %></p> <% }); %> </ul> </p> </div> </div> <!-- closing div for row --> </div> <!-- closing div for container --> </html> <!-- closing html tag --> هنا تستخدم وسوم قالب EJS والدالّة forEach() لإخراج كل قيمة في المجموعة sharks ، بما في ذلك معلومات حول أحدث أسماك القرش المضافة. ستبدو الحاوية بأكملها التي تحتوي على جميع الأعمدة الثلاثة، بما في ذلك العمود الذي يتضمن المجموعة sharks، على هذا النحو عند الانتهاء: ... <div class="container"> <div class="row"> <div class="col-lg-4"> <p> <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans. </div> <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark"> </p> </div> <div class="col-lg-4"> <p> <div class="caption">Other sharks are known to be friendly and welcoming!</div> <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark"> </p> </div> <div class="col-lg-4"> <p> <div class="caption">Your Sharks</div> <ul> <% sharks.forEach(function(shark) { %> <p>Name: <%= shark.name %></p> <p>Character: <%= shark.character %></p> <% }); %> </ul> </p> </div> </div> </div> </html> احفظ الملف وأغلقه عند الانتهاء من التحرير. لكي يستخدم التطبيق القوالب التي أنشأتها، ستحتاج إلى إضافة بضعة أسطر إلى ملفّك app.js. لذا افتحه مرة أخرى: nano app.js فوق المكان الذي أضفت فيه الدالة express.urlencoded()، أضف الآن الأسطر التالية: ... app.engine('html', require('ejs').renderFile); app.set('view engine', 'html'); app.use(express.urlencoded({ extended: true })); app.use(express.static(path)); ... تطلب الدّالة app.engine من التطبيق توجيه محرك قوالب EJS إلى ملفات HTML، بينما تحدّد app.set محرك العرض الافتراضي. يجب أن يبدو ملف app.js الآن على النحو التالي: الملف ~/node_project/app.js: const express = require('express'); const app = express(); const router = express.Router(); const db = require('./db'); const path = __dirname + '/views/'; const port = 8080; router.use(function (req,res,next) { console.log('/' + req.method); next(); }); router.get('/',function(req,res){ res.sendFile(path + 'index.html'); }); router.get('/sharks',function(req,res){ res.sendFile(path + 'sharks.html'); }); app.engine('html', require('ejs').renderFile); app.set('view engine', 'html'); app.use(express.urlencoded({ extended: true })); app.use(express.static(path)); app.use('/', router); app.listen(port, function () { console.log('Example app listening on port 8080!') }) الآن وبعد أن أنشأت واجهات عرض يمكن أن تعمل ديناميكيًا مع بيانات المستخدم، فقد حان الوقت لإنشاء المسارات لمشروعك من أجل الجمع بين واجهات العرض ومنطق وحدة التحكم الذي تعمل به. الخطوة السادسة: إنشاء المسارات (routes) الخطوة الأخيرة في الجمع بين مكونات التطبيق هي إنشاء المسارات. سنحاول فصل المسارات حسب وظيفتها، بما في ذلك المسار إلى صفحة الهبوط لتطبيقنا ومسار آخر إلى صفحة أسماك القرش. سيكون المسار sharks هو المكان الذي ندمج فيه منطق وحدة التحكم مع واجهات العرض التي أنشأناها في الخطوة السابقة. أنشئ في البداية مجلّدًا باسم routes: mkdir routes بعد ذلك، أنشئ ملفًا يسمى index.js في هذا المجلّد وافتحه: nano routes/index.js سيستورد هذا الملف في البداية كائنات express وrouter وpath، مما يتيح لنا تحديد المسارات التي نريد تصديرها باستخدام الكائن router، وهذا يتيح بدوره العمل ديناميكيًا بمسارات الملفات (file paths). أضف الشيفرة التالية في أعلى الملف: الملف ~/node_project/routes/index.js: const express = require('express'); const router = express.Router(); const path = require('path'); بعد ذلك، أضف الدّالة router.use التالية، والتي تُحمّل الدالّة الوسيطة التي ستسجل طلبات الموجه وتمررها إلى مسار التطبيق: ... router.use (function (req,res,next) { console.log('/' + req.method); next(); }); ستُوجَّه الطلبات الخاصّة بجذر التطبيق إلى هنا في البداية، ومن ثم سيُوجّه المستخدمون إلى صفحة الهبوط لتطبيقنا، وهو المسار الذي سنحدّده بعد ذلك. أضف الشيفرة التالية أسفل الدّالة router.use لتحديد المسار نحو صفحة الهبوط: ... router.get('/',function(req,res){ res.sendFile(path.resolve('views/index.html')); }); إن أول ما نريد إرساله إليه المستخدم عندما يزور التطبيق هو صفحة الهبوط index.html الموجودة في المجلّد views. ختامًا، لجعل هذه المسارات قابلة للوصول كوحدات يمكن استيرادها في مكان آخر من التطبيق، أضف عبارة ختامية إلى نهاية الملف لتصدير الكائن router: ... module.exports = router; سوف يبدو الملف النهائي على هذا النحو: const express = require('express'); const router = express.Router(); const path = require('path'); router.use (function (req,res,next) { console.log('/' + req.method); next(); }); router.get('/',function(req,res){ res.sendFile(path.resolve('views/index.html')); }); module.exports = router; احفظ هذا الملف وأغلقه عندما تنتهي من التحرير. بعد ذلك، افتح ملفًا باسم sharks.js لتحديد كيفية استخدام التطبيق لنقاط النهاية وواجهات العرض المختلفة التي أنشأناها للتعامل مع مدخلات المستخدم لأسماك القرش: nano routes/sharks.js في الجزء العلوي من الملف، استورد الكائنات epress وrouter: const express = require('express'); const router = express.Router(); بعد ذلك، استورد وحدة نمطية تسمى shark لتتيح لك العمل بالدّوال المصدرة التي حددتها في وحدة التحكم: const express = require('express'); const router = express.Router(); const shark = require('../controllers/sharks'); يمكنك الآن إنشاء مسارات باستخدام الدّوال index وcreate وlist التي حدّدتها في ملف وحدة التحكم sharks. سيُربط كل مسار بطريقة HTTP المناسبة والتي تكون إما GET في حالة عرض صفحة الهبوط لمعلومات أسماك القرش الرئيسية وإعادة قائمة أسماك القرش إلى المستخدم، وإما POST في حالة إنشاء إدخال جديد لسمك قرش: ... router.get('/', function(req, res){ shark.index(req,res); }); router.post('/addshark', function(req, res) { shark.create(req,res); }); router.get('/getshark', function(req, res) { shark.list(req,res); }); يستخدم كل مسار الدّالة المرتبطة به في controllers/sharks.js، لأننا جعلنا هذه الوحدة قابلة للوصول عن طريق استيرادها في الجزء العلوي من هذا الملف. ختامًا، أغلق الملف بربط هذه المسارات بالكائن routes وتصديرها: ... module.exports = router; سيبدو الملف النهائي على هذا النحو: const express = require('express'); const router = express.Router(); const shark = require('../controllers/sharks'); router.get('/', function(req, res){ shark.index(req,res); }); router.post('/addshark', function(req, res) { shark.create(req,res); }); router.get('/getshark', function(req, res) { shark.list(req,res); }); module.exports = router; احفظ الملف وأغلقه عند الانتهاء من التحرير. ستكون الخطوة الأخيرة في جعل هذه المسارات متاحةً للتطبيق هي إضافتها إلى app.js. افتح هذا الملف مرة أخرى: nano app.js أسفل الثابتة db، أضف الاستيراد التالي لمساراتك: ... const db = require('./db'); const sharks = require('./routes/sharks'); بعد ذلك، عوّض الدّالة app.use التي تُركّب الكائن router بالسطر التالي الذي سيركّب وحدة التوجيه sharks: ... app.use(express.static(path)); app.use('/sharks', sharks); app.listen(port, function () { console.log("Example app listening on port 8080!") }) تستطيع الآن حذف المسارات التي حدّدتها مسبقًا في هذا الملف، نظرًا لأنك تستورد مسارات تطبيقك باستخدام وحدة التوجيه sharks. ستبدو النسخة النهائية للملف app.js على النحو التالي: const express = require('express'); const app = express(); const router = express.Router(); const db = require('./db'); const sharks = require('./routes/sharks'); const path = __dirname + '/views/'; const port = 8080; app.engine('html', require('ejs').renderFile); app.set('view engine', 'html'); app.use(express.urlencoded({ extended: true })); app.use(express.static(path)); app.use('/sharks', sharks); app.listen(port, function () { console.log('Example app listening on port 8080!') }) احفظ الملف وأغلقه عند الانتهاء من التحرير. يمكنك الآن إعادة تنفيذ الأمر tree مرة أخرى لرؤية البنية النهائية لمشروعك: tree -I node_modules ستبدو بنية مشروعك الآن على هذا النحو: ├── Dockerfile ├── README.md ├── app.js ├── controllers │ └── sharks.js ├── db.js ├── models │ └── sharks.js ├── package-lock.json ├── package.json ├── routes │ ├── index.js │ └── sharks.js └── views ├── css │ └── styles.css ├── getshark.html ├── index.html └── sharks.html بإنشائك لجميع مكونات تطبيقك ووضعها في المكان المناسب، فأنت الآن مستعدّ لإضافة سمك قرش للاختبار إلى قاعدة بياناتك! إذا تتبعت درس إعداد الخادم الأولي في المتطلبات الأساسية، فستحتاج إلى تعديل جدار الحماية الخاص بك، لأنه لا يسمح حاليًا سوى بحركة المرور SSH. لذا نفّذ هذا الأمر من أجل السماح بحركة المرور إلى منفذ 8080: sudo ufw allow 8080 شغّل التطبيق: node app.js بعد ذلك، انتقل بمتصفّحك إلى http://yourserverip:8080. ستظهر لك صفحة الهبوط التالية: انقر على زر الحصول على معلومات القرش. ستظهر لك صفحة المعلومات التالية، مع استمارة إدخال سمك القرش المضافة: أضف في الاستمارة، سمكة قرش من اختيارك. لأغراض هذا العرض التوضيحي، سنضيف Megalodon Shark إلى حقل اسم القرش، وAncient إلى حقل سلوك القرش: انقر على زر الإرسال. ستظهر لك صفحة بها معلومات القرش معروضة لك: سترى أيضًا مخرجاتٍ في وحدة التحكم الخاصة بك تشير إلى أنّ سمك القرش أضيف إلى مجموعتك: Example app listening on port 8080! { name: 'Megalodon Shark', character: 'Ancient' } إذا كنت ترغب في إنشاء إدخال جديد لسمك القرش، فارجع إلى صفحة أسماك القرش وكرر عملية إضافة سمكة قرش. لديك الآن تطبيق لمعلومات سمك القرش يتيح للمستخدمين بإضافة معلومات حول أسماك القرش المفضلة لديهم. خاتمة لقد أنشأت في هذا البرنامج التعليمي تطبيق Node من خلال دمج قاعدة بيانات MongoDB وإعادة كتابة منطق التطبيق باستخدام النظام الهيكلي MVC. ويمكن أن يكون هذا التطبيق بمثابة نقطة انطلاق جيدة لتطبيق CRUD مكتمل. لمزيد من التفصيل حول النظام الهيكلي MVC في سياقات أخرى، يرجى الاطلاع على سلسلة مقالات تطوير Django. لمزيد من المعلومات حول العمل على MongoDB، يرجى الاطلاع على سلسلة المقالات حول MongoDB. ترجمة -وبتصرف- للمقال How To Integrate MongoDB with Your Node Application لصاحبته Kathleen Juell
-
تتيح منصة Docker للمطورين تحزيم وتنفيذ التطبيقات على شكل حاويات (containers). وتعدّ الحاوية عملية (process) منعزلة تعمل على نظام تشغيل مشترك، وتوفر بديلاً أخفّ من الأجهزة الافتراضية (virtual machines) . رغم أن الحاويات ليست جديدة، إلا أن لها فوائد عديدة، من بينها عزل العمليات وتوحيد البيئة، وتزداد أهميتها نظرًا لتزايد إقبال المطورين على استخدام بنية التطبيقات الموزعة. عند بناء تطبيق وتوسيع نطاقه باستخدام Docker، تكون نقطة البداية عادةً هي إنشاء صورة للتطبيق، والتي يمكنك تنفيذها بعد ذلك في حاوية. تتضمن الصورة شيفرة التطبيق والمكتبات وملفات التكوين ومتغيرات البيئة ووقت التشغيل. ويضمن استخدام صورة ما أن تكون البيئة في حاويتك موحدة ومتضمّنة فقط لما هو ضروري لإنشاء التطبيق وتنفيذه. ستنشئ في هذا الدرس صورة تطبيق لموقع ويب ثابت يستخدم إطار عمل Express و Bootstrap. وستنشئ بعد ذلك حاوية باستخدام تلك الصورة ورفعها إلى Docker Hub للاستخدام المستقبلي. أخيرًا، ستتمكّن من سحب الصورة المخزنة من مستودع Docker Hub الخاص بك وتنشئ حاوية أخرى، مع توضيح كيف يمكنك إعادة إنشاء التطبيق وتوسيع نطاقه. المتطلبات الأساسية سوف تحتاج من أجل متابعة هذه السلسلة إلى العناصر التالية: خادم Ubuntu 18.04 يمكنك إعداده باتباع دليل إعداد الخادم الأولي. تثبيت Docker على الخادم الخاص بك، باتباع الخطوتين 1 و 2 في دليل كيفية تثبيت واستخدام Docker على Ubuntu 18.04. تثبيت Node.js و npm، باتباع هذه الإرشادات تحديدًا التّثبيت باستخدام أرشيف الحزم الشّخصي PPA المدار بواسطة NodeSource. حساب Docker Hub. لإلقاء نظرة عامة حول كيفية إعداده، راجع هذه المقدمة عن بدء استخدام Docker Hub. الخطوة الأولى: تثبيت الاعتماديات لتطبيقك لإنشاء الصورة، ستحتاج أولاً إلى إنشاء ملفات التطبيق الخاصة بك والتي يمكنك بعد ذلك نسخها إلى حاويتك. ستتضمن هذه الملفات المحتوى الثابت للتطبيق والشيفرة والاعتماديات. أنشئ في البداية مجلًدًا لمشروعك في المجلّد الرئيسي للمستخدم غير الجذري. سنستدعي node_project الخاص بنا، لكن يمكنك استبداله بشيء آخر: $ mkdir node_project انتقل إلى هذا المجلّد: $ cd node_project سيكون هذا هو المجلّد الرئيسي للمشروع. أنشئ بعد ذلك ملف [package.json](https://docs.npmjs.com/files/package.json) باعتماديات مشروعك ومعلومات التعريف الأخرى. افتح الملف باستخدام nano أو المحرر المفضل لديك: $ nano package.json أضف المعلومات التالية حول المشروع، بما في ذلك اسم المشروع وصاحبه والترخيص ونقطة الدخول والاعتماديات. تأكد من استبدال معلومات صاحب المشروع باسمك ومعلومات الاتصال الخاصة بك: الملف :~/node_project/package.json { "name": "nodejs-image-demo", "version": "1.0.0", "description": "nodejs image demo", "author": "Sammy the Shark <sammy@example.com>", "license": "MIT", "main": "app.js", "keywords": [ "nodejs", "bootstrap", "express" ], "dependencies": { "express": "^4.16.4" } } يحتوي هذا الملف على اسم المشروع وصاحبه والترخيص الذي تتم مشاركته بموجبه. ينصح npm بجعل اسم المشروع قصيرًا وذا دلالة وصفية مع تجنب التكرار في سجل npm. لقد أدرجنا ترخيص MIT في حقل الترخيص، مما يتيح الاستخدام المجاني لشيفرة التطبيق وتوزيعه. بالإضافة إلى ذلك، يحدد الملف العناصر التالية: "main": نقطة دخول التطبيق app.js، ستقوم بإنشاء هذا الملف بعد ذلك. "dependencies": اعتماديات المشروع، في هذه الحالة، Express إصدار 4.16.4 أو أعلى منه. ورغم أن هذا الملف لا يحتوي على مستودعٍ، فيمكنك إضافته باتباع هذه الإرشادات حول إضافة مستودع إلى ملف package.json الخاص بك. وتعدّ هذه إضافةً جيدةً إذا كنت تقوم بإصدار تطبيقك. احفظ الملف وأغلقه عندما تنتهي من إجراء التعديلات. لتثبيت اعتماديات مشروعك، نفّذ الأمر التالي: $ npm install سيؤدي ذلك إلى تثبيت الحزم التي أدرجتها في ملف package.json في مجلّد المشروع الخاص بك. يمكننا الآن الانتقال إلى بناء ملفات التطبيق. الخطوة الثانية: إنشاء ملفات التطبيق سنعمل على إنشاء موقع ويب يقدم للمستخدمين معلومات حول أسماك القرش. سيكون لدى لتطبيق نقطة دخول رئيسية و ملفّ app.js ومجلّد views يتضمن الأصول الثابتة للمشروع. ستوفر صفحة الهبوط index.html للمستخدمين بعض المعلومات الأولية ورابطًا نحو صفحة تحتوي على معلومات أكثر تفصيلًا عن أسماك القرش، sharks.html. سننشئ في مجلّد views كلًّا من صفحة الهبوط و الصفحة sharks.html. في البداية، افتح ملفّ app.js في مجلّد المشروع الرئيسي لتحديد مسارات المشروع: $ nano app.js سيعمل الجزء الأول من الملف على إنشاء تطبيق Express وكائنات الموجِّه، وتحديد المجلّد الأساسي والمنفذ كثوابت: الملف :~/node_project/app.js const express = require('express'); const app = express(); const router = express.Router(); const path = __dirname + '/views/'; const port = 8080; تحمّل الداّلة require الوحدة express التي نستخدمها فيما بعد لإنشاء الكائنات app و router. سيؤدي الكائن router وظيفة التوجيه الخاصة بالتطبيق، وعندما نحدد مسارات طرق HTTP، سنضيفها إلى هذا الكائن لتحديد كيفية تعامل التطبيق مع الطلبات. يعين هذا الجزء من الملف أيضًا ثابتتي المسار path والمنفذ port: *path: يحدد المجلّد الأساسي، والذي سيكون المجلّد الفرعي ل views داخل مجلّد المشروع الحالي. port: يطلب من التطبيق الاستماع على المنفذ 8080. بعد ذلك، حدّد مسارات التطبيق باستخدام الكائن router: الملف ~/node_project/app.js: router.use(function (req,res,next) { console.log('/' + req.method); next(); }); router.get('/', function(req,res){ res.sendFile(path + 'index.html'); }); router.get('/sharks', function(req,res){ res.sendFile(path + 'sharks.html'); }); تحمّل الدالّة router.use دالّة وسيطة تعمل على تسجيل طلبات الموجه وتمررها إلى مسارات التطبيق. تُحدّد هذه المسارات في الدوالّ الموالية، والتي تنصّ على أن تنفيذ الأمر GET على عنوان URL للمشروع الأساسي يجب أن يُرجع الصفحة index.html، بينما يجب أن يُرجع تنفيذ الأمر GET على المسار sharks/ الصفحة sharks.html. ختامًا، صل بين البرمجية الوسيطة router والأصول الثابتة للتطبيق واجعل التطبيق يستمع على المنفذ 8080: ~/node_project/app.js ... app.use(express.static(path)); app.use('/', router); app.listen(port, function () { console.log('Example app listening on port 8080!') }) سيبدو ملف app.js النهائي كما يلي: const express = require('express'); const app = express(); const router = express.Router(); const path = __dirname + '/views/'; const port = 8080; router.use(function (req,res,next) { console.log('/' + req.method); next(); }); router.get('/', function(req,res){ res.sendFile(path + 'index.html'); }); router.get('/sharks', function(req,res){ res.sendFile(path + 'sharks.html'); }); app.use(express.static(path)); app.use('/', router); app.listen(port, function () { console.log('Example app listening on port 8080!') }) احفظ الملف وأغلقه عند الانتهاء. بعد ذلك، دعنا نضيف بعض المحتوى الثابت إلى التطبيق. ابدأ بإنشاء المجلد views: $ mkdir views افتح ملف صفحة الهبوط index.html: $ nano views/index.html أضف الشيفرة التالية إلى الملف، الذي سيعمل على استيراد Bootstrap وإنشاء مكوّن jumbotron مع رابط إلى صفحة المعلومات المفصّلة sharks.html: الملف ~/node_project/views/index.html: <!DOCTYPE html> <html lang="en"> <head> <title>About Sharks</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> <link href="css/styles.css" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Merriweather:400,700" rel="stylesheet" type="text/css"> </head> <body> <nav class="navbar navbar-dark bg-dark navbar-static-top navbar-expand-md"> <div class="container"> <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> </button> <a class="navbar-brand" href="#">Everything Sharks</a> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav mr-auto"> <li class="active nav-item"><a href="/" class="nav-link">Home</a> </li> <li class="nav-item"><a href="/sharks" class="nav-link">Sharks</a> </li> </ul> </div> </div> </nav> <div class="jumbotron"> <div class="container"> <h1>Want to Learn About Sharks?</h1> <p>Are you ready to learn about sharks?</p> <br> <p><a class="btn btn-primary btn-lg" href="/sharks" role="button">Get Shark Info</a> </p> </div> </div> <div class="container"> <div class="row"> <div class="col-lg-6"> <h3>Not all sharks are alike</h3> <p>Though some are dangerous, sharks generally do not attack humans. Out of the 500 species known to researchers, only 30 have been known to attack humans. </p> </div> <div class="col-lg-6"> <h3>Sharks are ancient</h3> <p>There is evidence to suggest that sharks lived up to 400 million years ago. </p> </div> </div> </div> </body> </html> يتيح شريط التنقل في الأعلى للمستخدمين المرور بين الصفحة الرئيسية وصفحة أسماك القرش. في المكون الفرعي navbar-nav، نستخدم الصنف active في Bootstrap للإشارة إلى الصفحة الحالية للمستخدم. لقد حددنا أيضًا الطرق المؤدية إلى الصفحات الثابتة لدينا، والتي تتوافق مع الطرق التي حددناها في app.js: الملف ~/node_project/views/index.html: ... <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav mr-auto"> <li class="active nav-item"><a href="/" class="nav-link">Home</a> </li> <li class="nav-item"><a href="/sharks" class="nav-link">Sharks</a> </li> </ul> </div> ... بالإضافة إلى ذلك، أنشأنا رابطًا إلى صفحة معلومات سمك القرش في زر jumbotron الخاص بنا: الملف ~/node_project/views/index.html: ... <div class="jumbotron"> <div class="container"> <h1>Want to Learn About Sharks?</h1> <p>Are you ready to learn about sharks?</p> <br> <p><a class="btn btn-primary btn-lg" href="/sharks" role="button">Get Shark Info</a> </p> </div> </div> ... يوجد أيضًا رابط إلى ورقة أنماط مخصصة في عنصر الترويسة header: الملف ~/node_project/views/index.html: ... <link href="css/styles.css" rel="stylesheet"> ... سننشئ ورقة الأنماط هذه في نهاية هذه الخطوة. احفظ الملف وأغلقه عند الانتهاء. يمكننا، من خلال صفحة الهبوط، إنشاء صفحة معلومات أسماك القرش sharks.html، والتي ستوفر للمستخدمين المهتمين مزيدًا من المعلومات حول أسماك القرش. افتح الملف: $ nano views/sharks.html الملف ~/node_project/views/sharks.html: <!DOCTYPE html> <html lang="en"> <head> <title>About Sharks</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> <link href="css/styles.css" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Merriweather:400,700" rel="stylesheet" type="text/css"> </head> <nav class="navbar navbar-dark bg-dark navbar-static-top navbar-expand-md"> <div class="container"> <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> </button> <a class="navbar-brand" href="/">Everything Sharks</a> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav mr-auto"> <li class="nav-item"><a href="/" class="nav-link">Home</a> </li> <li class="active nav-item"><a href="/sharks" class="nav-link">Sharks</a> </li> </ul> </div> </div> </nav> <div class="jumbotron text-center"> <h1>Shark Info</h1> </div> <div class="container"> <div class="row"> <div class="col-lg-6"> <p> <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans. </div> <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark"> </p> </div> <div class="col-lg-6"> <p> <div class="caption">Other sharks are known to be friendly and welcoming!</div> <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark"> </p> </div> </div> </div> </html> لاحظ أنه في هذا الملف، نستخدم مرة أخرى الصنف active للإشارة إلى الصفحة الحالية. احفظ الملف وأغلقه عند الانتهاء. ختامًا ، أنشئ ورقة الأنماط المخصصة CSS التي ربطتها بالصفحتين index.html و sharks.html، وذلك بإنشاء ملف css أولاً في المجلّد views: $ mkdir views/css افتح ورقة الأنماط: nano views/css/styles.css أضف الشيفرة التالية التي ستحدد اللون والخط المطلوب لصفحاتنا: الملف ~/node_project/views/css/styles.css: .navbar { margin-bottom: 0; } body { background: #020A1B; color: #ffffff; font-family: 'Merriweather', sans-serif; } h1, h2 { font-weight: bold; } p { font-size: 16px; color: #ffffff; } .jumbotron { background: #0048CD; color: white; text-align: center; } .jumbotron p { color: white; font-size: 26px; } .btn-primary { color: #fff; text-color: #000000; border-color: white; margin-bottom: 5px; } img, video, audio { margin-top: 20px; max-width: 80%; } div.caption: { float: left; clear: both; } بالإضافة إلى تعيين الخط واللون، يحدّد هذا الملف أيضًا حجم الصور بتحديد عرض أقصى يبلغ 80٪. هذا سيمنعها من شغل مساحة في الصفحة أكبر مما نريد. احفظ الملف وأغلقه عند الانتهاء. بعد إنشاء ملفات التطبيق وتثبيت اعتماديات المشروع، تكون جاهزًا لتشغيل التطبيق. إذا كنت قد تابعت دليل إعداد الخادم الأولي المذكور في المتطلبات الأساسية، فسيكون لديك جدار حماية نشط يسمح فقط بحركة مرور SSH. للسماح بحركة المرور عبر المنفذ 8080، نفّذ الأمر التالي : $ sudo ufw allow 8080 لتشغيل التطبيق، تأكد من وجودك في المجلد الرئيسي للمشروع: $ cd ~/node_project ابدأ تشغيل التطبيق باستخدام node app.js: $ node app.js انتقل بمتصفّحك إلى http://your_server_ip:8080. وستظهر لك صفحة الهبوط التالية: انقر على زر الحصول على معلومات القرش Get shark info. وستظهر لك صفحة المعلومات التالية: لديك الآن تطبيق قيد التشغيل. عندما تكون جاهزًا، أوقف الخادم بكتابة CTRL+C. يمكننا الآن الانتقال إلى إنشاء ملف Dockerfile الذي سيتيح لنا إعادة إنشاء هذا التطبيق وتوسيع نطاقه حسب الرغبة. الخطوة الثالثة: كتابة ملف Dockerfile يحدد ملف Dockerfile الخاص بك ما سيتم تضمينه في حاوية التطبيق عند تنفيذه. ويتيح لك استخدام هذا الملفّ تحديد بيئة الحاوية وتجنب التناقضات مع الاعتماديات أو إصدارات وقت التشغيل. باتباع هذه الإرشادات حول إنشاء حاويات محسّنة، سنجعل صورة التطبيق أكثر فاعلية قدر الإمكان عن طريق تقليل عدد طبقات الصورة وتحديد وظيفة الصورة في غرض واحد هو إعادة إنشاء ملفات التطبيق والمحتوى الثابت. في المجلّد الرئيسي لمشروعك، أنشئ الملف Dockerfile: $ nano Dockerfile تُنشَأ صور Docker باستخدام سلسلة متتالية من الصور ذات الطبقات التي تعتمد على بعضها بعضًا. ستكون خطوتنا الأولى هي إضافة الصورة الأساسية للتطبيق والتي ستشكل نقطة انطلاق إنشاء التطبيق. لنستخدم الصورة node:10-alpine. ويمكننا الحصول على الصورة alpine من مشروع Alpine Linux لكي تساعدنا في تقليل حجم الصور لدينا. أضف تعليمات FROM التالية لتعيين الصورة الأساسية للتطبيق: الملف ~/node_project/Dockerfile: FROM node:10-alpine تتضمن هذه الصورة Node.js و npm. ويجب أن يبتدئ كل ملفّ Dockerfile بتعليمات FROM. بشكل افتراضي، تشتمل صورة Docker Node على مستخدم Node غير جذري يمكنك استخدامه لتجنب تشغيل حاوية التطبيق باستخدام الحساب الجذري. إنها ممارسة أمان موصى بها لتجنب تشغيل الحاويات باستخدام root وتقييد الصلاحيات داخل الحاوية في تلك المطلوبة فقط لتنفيذ عملياتها. لذلك، سوف نستخدم المجلّد الرئيسي لمستخدم Node كمجلّد العمل لتطبيقنا ونعيّنه كمستخدم داخل الحاوية. لمزيد من المعلومات حول أفضل الممارسات عند العمل مع صورة Docker Node، راجع دليل أفضل الممارسات هذا إذا كانت لغتك الانجليزية جيدة. لضبط الأذونات على شيفرة تطبيقنا في الحاوية، دعنا ننشئ المجلّد الفرعي node_modules في /home/node رفقة المجلّد app. سيضمن إنشاء هذه المجلّدات أنها تتوفّر على الأذونات التي نريدها، والتي ستكون مهمة عندما ننشئ وحدات node محلية في الحاوية باستخدام npm install. بالإضافة إلى إنشاء هذه المجلّدات، سنعطي ملكيتها للمستخدم node: -الملف ~/node_project/Dockerfile: ... RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app عيّن بعد ذلك مجلّد العمل للتطبيق على /home/node/app: الملف ~/node_project/Dockerfile : ... WORKDIR /home/node/app إذا لم يتم تعيين مجلّد عمل WORKDIR، فسوف يُنشئ Docker واحدًا افتراضيًا، لذلك من الجيد تعيينه بشكل صريح. بعد ذلك، انسخ ملفات package.json و package-lock.json (بالنسبة لملفات الإصدار الخامس npm فما فوق): الملف ~/node_project/Dockerfile: ... COPY package*.json ./ تتيح إضافة تعليمة COPY قبل تنفيذ npm install تثبيت أو نسخ شيفرة التطبيق الاستفادة من آلية التخزين المؤقت لـ Docker. في كل مرحلة من مراحل البناء، سيقوم Docker بالتحقق مما إذا كان يحتوي على طبقة تم تخزينها في ذاكرة التخزين المؤقت لتلك التعليمات المحددة. إذا غيّرنا الحزمة package.json، فسيُعاد بناء هذه الطبقة، لكن إذا لم نفعل ذلك، فسوف تسمح هذا التعليمة لـ Docker باستخدام طبقة الصورة الحالية وتخطي إعادة تثبيت وحدات node. للتأكد من أن جميع ملفات التطبيق مملوكة للمستخدم node غير الجذري، بما في ذلك محتويات المجلّد node_modules، حوّل المستخدم إلى node قبل تنفيذ npm install: الملف -/node_project/Dockerfile: ... USER node بعد نسخ اعتماديات المشروع وتبديل مستخدمنا، يمكننا تنفيذ install npm: الملف ~/node_project/Dockerfile: ... RUN npm install بعد ذلك، انسخ شيفرة تطبيقك مع الأذونات المناسبة إلى مجلّد التطبيق على الحاوية: الملف ~/node_project/Dockerfile: ... COPY --chown=node:node . . سيضمن هذا أن ملفات التطبيق مملوكة من قبل مستخدم node غير الجذري. أخيرًا، اعرض المنفذ 8080 على الحاوية وابدأ تشغيل التطبيق: الملف ~/node_project/Dockerfile: ... EXPOSE 8080 CMD [ "node", "app.js" ] لا ينشر الأمر EXPOSE المنفذَ، ولكنه يعمل بدلاً من ذلك كوسيلة لتوثيق المنافذ على الحاوية التي سيتم نشرها في وقت التشغيل. يقوم CMD بتنفيذ الأمر لبدء التطبيق، في هذه الحالة، node app.js. لاحظ أنه يجب أن يكون هناك تعليمة CMD واحدة فقط في كل Dockerfile. إذا ضمّنت أكثر من واحدة، فستُفعّل الأخيرة فقط. هناك العديد من الأشياء التي يمكنك القيام بها باستخدام Dockerfile. للحصول على قائمة كاملة بالتعليمات، يرجى الرجوع إلى التوثيق المرجعي لملفات Dockerfile. يكون الملفّ Dockerfile الكامل على هذا النحو: الملف ~/node_project/Dockerfile: FROM node:10-alpine RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app WORKDIR /home/node/app COPY package*.json ./ USER node RUN npm install COPY --chown=node:node . . EXPOSE 8080 CMD [ "node", "app.js" ] احفظ الملف وأغلقه عند الانتهاء من التحرير. قبل بناء صورة التطبيق، دعنا نضيف ملف .dockerignore ونظرًا لأنه يعمل بطريقة مشابهة لملف .gitignore، فإن ملف .dockerignore يحدّد ماهي الملفات والمجلّدات الموجودة في مجلّد مشروعك والتي لا ينبغي نسخها إلى حاويتك. افتح الملف dockerignore.: $ nano .dockerignore أضف داخل الملف وحدات node المحلية وسجلات npm وملف Dockerfile وملف dockerignore.: الملف ~/node_project/.dockerignore: node_modules npm-debug.log Dockerfile .dockerignore إذا كنت تعمل باستخدام Git، فستحتاج أيضًا إلى إضافة مجلّد git. وملف gitignore. احفظ الملف وأغلقه عند الانتهاء. أنت الآن جاهز لإنشاء صورة التطبيق باستخدام الأمر docker build. سيسمح لك استخدام الراية t- مع docker build بتعليم الصورة باسم سهل التذكّر. ونظرًا لأننا سنقوم بنقل الصورة إلى Docker Hub، فلنضمّن اسم مستخدم Docker Hub الخاص بنا في العلامة. سنعلّم الصورة باستخدام nodejs-image-demo، ولكن لا تتردد في استبدالها باسم تختاره. تذكر أيضًا استبدال your_dockerhub_username باسم مستخدم Docker Hub الخاص بك: $ docker build -t your_dockerhub_username/nodejs-image-demo . تحدّد النقطة . أن موضع البناء هو المجلّد الحالي. سوف يستغرق الأمر دقيقة أو دقيقتين لبناء الصورة. بمجرد اكتمال الأمر، يمكنك التحقق من صورك: $ docker images سيظهر لك في المخرجات ما يلي: output REPOSITORY TAG IMAGE ID CREATED SIZE your_dockerhub_username/nodejs-image-demo latest 1c723fb2ef12 8 seconds ago 73MB node 10-alpine f09e7c96b6de 3 weeks ago 70.7MB أصبح الآن من الممكن إنشاء حاوية بهذه الصورة باستخدام docker run. وسنضمّن ثلاث علامات مع هذا الأمر: -p : تنشر هذه الراية المنفذ على الحاوية وتقدّمه كمنفذ على المضيف host. سنستخدم المنفذ 80 على المضيف، ولكن يجب ألا تتردد في تعديل هذا عند الضرورة إذا كان لديك عملية أخرى تعمل على هذا المنفذ. لمزيد من المعلومات حول كيفية عمل هذا، راجع هذه النقاش في توثيقات Docker حول ربط المنافذ. -d : تشغّل هذه العلامة الحاوية في الخلفية. -name : هذا يتيح لنا إعطاء الحاوية اسمًا سهل التذكر. نفّذ الأمر التالي لبناء الحاوية: docker run --name nodejs-image-demo -p 80:8080 -d your_dockerhub_username/nodejs-image-demo بمجرد تشغيل حاويتك، يمكنك فحص قائمة الحاويات قيد التشغيل باستخدام docker ps: $ docker ps سيظهر لك الإخراج التالي: Output CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e50ad27074a7 your_dockerhub_username/nodejs-image-demo "node app.js" 8 seconds ago Up 7 seconds 0.0.0.0:80->8080/tcp nodejs-image-demo بعد تشغيل حاويتك، يمكنك الآن الولوج إلى تطبيقك بالانتقال بمتصفحك إلى http://your_server_ip. ستظهر لك صفحتك الهبوط مرة أخرى: الآن وبعد بناء صورة لتطبيقك، يمكنك رفعها إلى Docker Hub للاستخدام المستقبلي. الخطوة الرابعة: استخدام مستودع للعمل بالصور برفعك لصورة التطبيق الخاصة بك إلى سجلّ مثل Docker Hub، فأنت تجعلها متاحة للاستخدام المستقبلي عندما تكون بصدد بناء حاوياتك وتوسيع نطاقها. سنوضح كيف يعمل هذا عن طريق رفع صورة التطبيق إلى مستودع ثم استخدام الصورة لإعادة إنشاء حاويتنا. الخطوة الأولى لرفع الصورة هي تسجيل الدخول إلى حساب Docker Hub الذي أنشأته في المتطلبات الأساسية: $ docker login -u your_dockerhub_username أدخل كلمة مرور حساب Docker Hub، وسيؤدي التسجيل بهذه الطريقة إلى إنشاء ملف ~/.docker/config.json في المجلّد الرئيسي لمستخدمك بالبيانات الاعتمادية لDocker Hub. يمكنك الآن رفع صورة التطبيق إلى Docker Hub باستخدام العلامة التي أنشأتها مسبقًا، your_dockerhub_username/nodejs-image-demo $ docker push your_dockerhub_username/nodejs-image-demo دعنا نختبر الآن أهمّية سجلّ الصور بتدمير حاوية التطبيق الحالية والصورة وإعادة بنائها باستخدام الصورة التي في مستودعنا. استعرض أولاً حاوياتك التي توجد قيد التشغيل: $ docker ps سيظهر لك الإخراج التالي: Output CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e50ad27074a7 your_dockerhub_username/nodejs-image-demo "node app.js" 3 minutes ago Up 3 minutes 0.0.0.0:80->8080/tcp nodejs-image-demo باستخدام CONTAINER ID الظاهر في المخرجات، أوقف حاوية التطبيق قيد التشغيل. تأكد من استبدال المعرّف أدناه بمعرّف الحاوية CONTAINER ID: $ docker stop e50ad27074a7 اعرض قائمة صورك باستخدام الراية -a : $ docker images -a سيظهر لك الإخراج التالي مع اسم صورتك، your_dockerhub_username/nodejs-image-demo، إلى جانب صورة node وباقي الصور الأخرى الخاصة ببنائك: Output REPOSITORY TAG IMAGE ID CREATED SIZE your_dockerhub_username/nodejs-image-demo latest 1c723fb2ef12 7 minutes ago 73MB <none> <none> 2e3267d9ac02 4 minutes ago 72.9MB <none> <none> 8352b41730b9 4 minutes ago 73MB <none> <none> 5d58b92823cb 4 minutes ago 73MB <none> <none> 3f1e35d7062a 4 minutes ago 73MB <none> <none> 02176311e4d0 4 minutes ago 73MB <none> <none> 8e84b33edcda 4 minutes ago 70.7MB <none> <none> 6a5ed70f86f2 4 minutes ago 70.7MB <none> <none> 776b2637d3c1 4 minutes ago 70.7MB node 10-alpine f09e7c96b6de 3 weeks ago 70.7MB احذف الحاوية المتوقفة وجميع الصور، بما في ذلك الصور غير المستخدمة أو المعلّقة، باستخدام الأمر التالي: $ docker system prune -a اكتب y في المحثّ عند الإخراج لتأكيد رغبتك في إزالة الحاوية والصور المتوقفة. وينبغي التوضيح أن هذا سيؤدي أيضًا إلى حذف ذاكرة التخزين المؤقت للبناء. لقد حذفت الآن كلًا من الحاوية التي تشغّل صورة التطبيق والصورة نفسها. لمزيد من المعلومات حول إزالة حاويات Docker والصور، يرجى الاطلاع على كيفية التعامل مع الحاويات. بعد حذف كل الصور والحاويات الخاصة بك ، يمكنك الآن سحب صورة التطبيق من Docker Hub: $ docker pull your_dockerhub_username/nodejs-image-demo اعرض قائمة صورك مرة أخرى: $ docker images ستظهر لك صورة تطبيقك: Output REPOSITORY TAG IMAGE ID CREATED SIZE your_dockerhub_username/nodejs-image-demo latest 1c723fb2ef12 11 minutes ago 73MB يمكنك الآن إعادة بناء حاويتك باستخدام الأمر التالي من الخطوة 3: docker run --name nodejs-image-demo -p 80:8080 -d your_dockerhub_username/nodejs-image-demo اعرض قائمة الحاويات قيد التشغيل: $ docker ps Output CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f6bc2f50dff6 your_dockerhub_username/nodejs-image-demo "node app.js" 4 seconds ago Up 3 seconds 0.0.0.0:80->8080/tcp nodejs-image-demo تصفّح http://your_server_ip مرة أخرى لعرض التطبيق قيد التشغيل. الخلاصة في هذا الدرس، عملت على بناء تطبيق ويب ثابت باستخدام Express و Bootstrap، بالإضافة إلى صورة Docker لهذا التطبيق. استخدمت هذه الصورة لإنشاء حاوية ورفعت الصورة إلى Docker Hub. بعد ذلك، تمكنت من تدمير صورتك وحاوياتك وإعادة إنشائها باستخدام مستودع Docker Hub. ترجمة -وبتصرف- للمقال How To Build a Node.js Application with Docker لصاحبته Kathleen Juell