إذا كنت تعمل على تطوير تطبيقك بنشاط، فإن استخدام 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
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.