اذهب إلى المحتوى

أساسيات تنسيق الحاويات


ابراهيم الخضور

نتعرف في هذا المقال على وضع تطبيق ويب بأكمله مع جميع خدماته ضمن حاويات دوكر Docker وتنسيقها وضبط إعداداتها.

استخدام الحاويات مع React

سنحاول أن ننشئ تطبيق React ونضعه ضمن حاوية تاليًا، لهذا سنختار npm مديرَا للحزم علمًا أن yarn هو المدير الافتراضي لبرنامج create-react-app:

$ npx create-react-app hello-front --use-npm
  ...

  Happy hacking!

يُثبت create-react-app كل الاعتماديات اللازمة، فلا حاجة لتنفيذ الأمر npm install. ستكون الخطوة الثانية تحويل شيفرة JavaScript و CSS إلى ملفات ساكنة جاهزة لمرحلة الإنتاج. أمّا create-react-app فلديه build ليكون سكربت npm، لهذا سنستفيد من هذه الناحية:

$ npm run build
  ...

  Creating an optimized production build...
  ...
  The build folder is ready to be deployed.
  ...

أما الخطوة الأخيرة هي التفكير بطريقة للعمل مع خادم لتقديم الملفات الساكنة. يمكننا الاستفادة من express.static مع خادم Express لهذا الغرض، لهذا سأترك الموضوع تمرينًا لك، وسننتقل بدلًا من ذلك إلى كتابة ملف Dockerfile:

FROM node:16

WORKDIR /usr/src/app

COPY . .

RUN npm ci

RUN npm run build

يبدو ما كتبناه صحيحًا تقريبًا، لهذا سنبنيه ونقيّم مسارنا، والهدف أن نبني التمرين دون أخطاء. سنحاول بعد ذلك أن نتحقق من وجود الملفات داخل الحاوية من خلال أوامر "bash".

$ docker build . -t hello-front
  [+] Building 172.4s (10/10) FINISHED 

$ docker run -it hello-front bash

root@98fa9483ee85:/usr/src/app# ls
  Dockerfile  README.md  build  node_modules  package-lock.json  package.json  public  src

root@98fa9483ee85:/usr/src/app# ls build/
  asset-manifest.json  favicon.ico  index.html  logo192.png  logo512.png  manifest.json  robots.txt  static

الخيار الصحيح لتخديم الملفات الساكنة ضمن الحاوية هو serve نظرًا لوجود Node ضمن الحاوية. لنحاول تثبيت serve لتخديم الملفات الساكنة ونحن داخل الحاوية:

root@98fa9483ee85:/usr/src/app# npm install -g serve

  added 88 packages, and audited 89 packages in 6s

root@98fa9483ee85:/usr/src/app# serve build

   ┌───────────────────────────────────┐
                                      
      Serving!                        
                                      
      Local:  http://localhost:5000   │
                                      
   └───────────────────────────────────┘

أغلق الحاوية باستخدام الاختصار "Ctrl+C" لإضافة بعض التوجيهات إلى ملف Dockerfile.

يتحوّل تثبيت serve إلى عملية تشغيل RUN في ملف Dockerfile، وهكذا ستُثبّت الاعتمادية أثناء عملية البناء، وسيكون أمر تخديم مجلد البناء هو نفسه أمر تشغيل الحاوية:

FROM node:16

WORKDIR /usr/src/app

COPY . .

RUN npm ci

RUN npm run build

RUN npm install -g serve
CMD ["serve", "build"]

يتضمن الأمر ‍‍CMD الآن قوسين مربعين ونكون بذلك قد استخدمنا ما يُعرف بالشكل التنفيذي exec form من الأمر ‍‍CMD، علمًا أنه يُكتب بثلاثة أشكال لكن الشكل السابق هو المُفضّل.

عندما نبني الصورة الآن باستخدام الأمر:

docker build . -t hello-front

ثم تشغيلها باستخدام الأمر:

docker run -p 5000:3000 hello-front

سيكون التطبيق متاحًا على العنوان "http://localhost:5000".

استخدام المراحل المتعددة

على الرغم من أن serve خيار صحيح لكن بالإمكان إيجاد بديل أفضل، إذ أن الغاية هنا هي إنشاء صورة لا تحتوي أي شيء غير مطلوب وبأقل عدد من الاعتماديات، وبالتالي ينخفض احتمال توقف الصورة عن العمل أو أن تصبح عرضةً للهجمات مع الوقت.

صُمّمت عملية البناء متعددة المراحل Multi-stage builds لتفصل عملية البناء إلى مراحل مختلفة، ومن الممكن حينها تحديد ملفات الصورة التي يُسمح لها بالانتقال من مرحلة إلى أخرى، كما يتيح ذلك أيضًا إمكانية التحكم بحجم الصورة، فلن نحتاج إلى جميع الملفات الجانبية التي تنتج عن عملية البناء ضمن الصورة الناتجة. إنّ الصورة الأصغر أسرع في التحميل والتنزيل وتساعد في تخفيض عدد نقاط الضعف في برنامجك.

عند استخدام البناء متعدد المراحل، بالإمكان الاعتماد على حلول تتبع نهج المحاولة والخطأ مثل استخدام الخادم Nginx في إدارة الملفات الساكنة دون عناء شديد. تدلنا صفحة استخدام Nginx مع Docker Hub على المعلومات الضرورية لفتح المنافذ واستضافة محتوى ساكن.

لنستخدم الآن ملف Dockerfile السابق بعد تغيير ما يلي التعليمة FROM لإضافة اسم المرحلة:

# called build-stage الأولى الآن إلى مرحلة تُدعى FROM تشير تعليمة  
FROM node:16 AS build-stage
WORKDIR /usr/src/app

COPY . .

RUN npm ci

RUN npm run build
# هذه مرحلة جديدة الآن، وسيختفي كل شيء قبلها ما عدا الملفات التي نريد نسخها
FROM nginx:1.20-alpine
# /usr/share/nginx/html إلى build-stage نسخ مجلد البناء من 
# docker hub page وجد الموقع الوجهة من خلال صفحة شروحات 
COPY --from=build-stage /usr/src/app/build /usr/share/nginx/html

لقد صرحنا أيضًا عن مرحلة أخرى تُنقل إليها فقط الملفات الضرورية من المرحلة الأولى (المجلد "build" الذي يضم المحتوى الساكن).

بعد بنائها ثانيةً، ستكون الصورة جاهزة لتخديم المحتوى الساكن. إن المنفذ الافتراضي لخادم Nginx هو 80 وبالتالي سينفع تنفيذ الأمر p 8000:80-، لهذا لا بد من تغيير معاملات أمر التشغيل قليلًا.

تنطوي عملية البناء متعددة المراحل على تحسينات داخلية قد تؤثر على عملية البناء، إذ تتجاوز عملية البناء متعددة المراحل مثلًا المراحل التي لم تُستخدم، وبالتالي علينا تمرير بعض البيانات إلى المرحلة القادمة إن أردنا استخدام مرحلة ما لتبديل جزء من خط البناء مثل الاختبارات أو التنبيهات. لهذا الأمر تبريراته في بعض الحالات كأن تَنسخ الشيفرة من مرحلة الاختبار إلى مرحلة البناء كي تضمن بناء الشيفرة المُختبرة.

التمرينان 12.13 و 12.14

حاول أن تحل التمرينين التاليين:

التمرين 12.13: الواجهة الامامية لتطبيق المهام

لقد وصلنا أخيرًا إلى الواجهة الأمامية، لهذا عليك أن تلقي نظرة على محتويات المجلد "todo-app/todo-frontend" وتقرأ ملف "اقرأني README". ابدأ بتشغيل الواجهة الأمامية خارج الحاوية، وتأكد من تناغم عملها مع الواجهة الخلفية. ضع التطبيق ضمن الحاوية بعد ذلك أنشئ الملف "todo-app/todo-frontend/Dockerfile" ثم استخدم التعليمة ENV لتمرير متغير البيئة REACT_APP_BACKEND_URL إلى التطبيق وشغّله مع الواجهة الخلفية. ينبغي أن تعمل الواجهة الخلفية حاليًا خارج الحاوية. وانتبه إلى ضرورة ضبط المتغير REACT_APP_BACKEND_URL قبل بناء الواجهة الأمامية وإلا لن يُعرَّف ضمن الشيفرة.

التمرين 12.14: إجراء الاختبارات أثناء عملية البناء

من الميزات الهامة للبناء متعدد المراحل، استخدام مرحلة البناء لإجراء الاختبارات على التطبيق testing. فإن أخفقت مرحلة الاختبار ستُخفق عملية البناء بأكملها. لكن تنفيذ الاختبارات جميعها خلال عملية بناء الصورة ليست فكرة جيدة؛ لهذا قد تكون الاختبارات المتعلقة بالحاوية هي الأنسب.

استخلص مكوّن Todo يمثل مهمة واحدة من الشيفرة ، ثم اكتب اختبارًا للمكوّن الجديد وأضف تنفيذ الاختبارات إلى عملية البناء.

نفّذ الاختبار من خلال الأمر CI=true npm test وإلا سيبدأ برنامج create-react-app بمراقبة التغييرات مسببًا توقف خط العمل pipeline.

بإمكانك إضافة مرحلة بناء جديدة لإجراء الاختبار إن أردت ذلك. لكن تذكر في هذه الحالة أن تقرأ آخر فقرة قبل التمرين 12.13 مجددًا.

التطوير ضمن الحاويات

لننقل الآن تطبيق المهام بأكمله إلى حاوية. وإليك بعض الأسباب التي قد تدفعنا إلى ذلك:

  • للإبقاء على تماثل بيئة العمل بين التطوير والإنتاج تفاديًا للثغرات التي تظهر فقط في بيئة التشغيل.
  • لتفادي الاختلافات بين المطورين وبيئات عملهم الخاصة والتي تقود إلى صعوبات أثناء تطوير التطبيق.
  • لمساعدة أعضاء الفريق الجدد على البدء بتثبيت بيئة تشغيل الحاوية دون الحاجة إلى أي شيء آخر.

في المقابل، قد نواجه سلوكًا غير معهود عندما لا نشغّل التطبيق كما اعتدنا، ولهذا لا بد من فعل ما يلي على الأقل لنقل التطبيق إلى الحاوية:

  • تشغيل التطبيق في وضع التطوير.
  • الوصول إلى الشيفرة من خلال برنامج VSCode.

لنبدأ بالواجهة الأمامية، وطالما أن ملف Dockerfile لمرحلة التطوير سيختلف تمامًا عن ملف Dockerfile لنسخة الإنتاج، سننشئ ملف جديد اسمه "dev.Dockerfile".

لنشغّل create-react-app في وضع التطوير ومن المفترض أن يكون الأمر بسيطًا على النحو التالي:

FROM node:16

WORKDIR /usr/src/app

COPY . .
# طالما سنعمل في وضع التطوير npm install إلى npm ci غيّر 

RUN npm install
# npm start إن أمر التشغيل في وضع التطوير هو 

CMD ["npm", "start"]

تُستخدم الراية f- أثناء البناء لتحديد الملف الذي يُستخدم، وإلا سيقع الاختيار على الملف Dockerfile، لهذا يكون أمر بناء الصورة على النحو التالي:

docker build -f ./dev.Dockerfile -t hello-front-dev .

سيُخدَّم create-react-app على المنفذ 3000، لهذا يمكنك اختباره بتشغيل الحاوية على هذا المنفذ.

تقتضي المهمة الثانية الوصول إلى الملفات باستخدام VSCode، وهناك على الأقل طريقتان لإنجاز الأمر:

  • باستخدام الموسِّع Visual Studio Code Remote - Containers.
  • باستخدام الأقراص volumes، وبنفس طريقة تخزين البيانات في قاعدة البيانات.

لنتجاوز المهمة الثانية كوننا سنضطر فيها إلى التعامل مع محررات أخرى، ولنجرب تشغيل الحاوية مع الراية v-، فإذا جرى كل شيء على ما يرام ننقل الإعدادات إلى ملف docker-compose. لاستخدام تلك الراية علينا تزويدها بالمجلد الحالي من خلال تنفيذ الأمر pwd الذي يعطي المسار إلى المجلد الحالي. حاول أن تنفِّذ ذلك من خلال الأمر (echo $(pwd في واجهة سطر الأوامر لديك وبالترتيب التالي:

$ docker run -p 3000:3000 -v "$(pwd):/usr/src/app/" hello-front-dev

  Compiled successfully!

  You can now view hello-front in the browser.

بإمكاننا الآن تعديل الملف "src/App.js" وستُعرض التغييرات مباشرةً على المتصفح. سننقل تاليًا الإعدادات إلى الملف "docker-compose.yml" الذي ينبغي أن يكون موجودًا في جذر المشروع:

services:
  app:
    image: hello-front-dev
    build:
      context: . # يختار السياق هذا المجلد ليكون سياق البناء 
      dockerfile: dev.Dockerfile # الذي سيُستخدم Dockerfile لاختيار ملف 
    volumes:
      - ./:/usr/src/app # يمكن أن يكون المسار نسبي, so ./ is enough to say 
                        # ./ لهذا يكفي استخدام 
                        #docker-compose.yml للقول أنه نفس مكان وجود الملف

    ports:
      - 3000:3000
    container_name: hello-front-dev # hello-front-dev لتسمية الحاوية بالاسم 

يمكننا بهذه الإعدادات الآن تشغيل التطبيق في وضع التطوير من خلال الأمر docker-compose up، ولن تحتاج حتى إلى تثبيت Node.

يسبب تثبيت اعتماديات جديدة عدة مشاكل في إعداد بيئة تطوير كهذه، لهذا ستجد أن تثبيت الاعتمادية الجديدة ضمن الحاوية هو أحد الخيارات الجيدة. فبلدلًا من تنفيذ الأمر التالي مثلًا npm install axios ، ثبّت هذه الاعتمادية ضمن الحاوية التي تعمل من خلال الأمر docker exec hello-front-dev npm install axios أو أضفها إلى الملف ثم نفِّذ الأمر docker build من جديد.

التمرين 12.15: إعداد بيئة تطوير الواجهة الأمامية

أنشئ الملف واستخدم الأقراص لتمكين تطوير الواجهة الأمامية لتطبيق المهام عندما يعمل ضمن الحاوية.

التواصل بين الحاويات في شبكة Docker

تهيئ الأداة Docker شبكةً بين الحاويات وتضيف خادمًا لأسماء النطاقات DNS لربط أي حاويتين بسهولة. دعونا إذًا نضيف خدمة جديدة إلى وسنرى كيف تعمل الشبكة وخادم DNS.

سنستخدم الحزمة التنفيذية Busybox التي تضم مجموعةً من الأدوات التي قد تحتاجها وتُعرف هذه الحزمة باسم "سكين الجيش السويسري الخاصة بنظام Linux المدمج". لهذا يمكننا بالتأكيد الاستفادة منها.

تساعدنا Busybox في تنقيح إعداداتنا، لهذا إن لم تتمكن من حل التمرين السابق، عليك استخدام Busybox لمعرفة ما يعمل من إعداداتك وما لا يعمل. لنختبر ما قلناه الآن. تتواجد تلك الحاويات ضمن شبكة ويمكنك الربط بينها بسهولة، ويمكن إضافة Busybox إلى الخلطة بتغيير الملف "docker-compose.yml" إلى:

services:
  app:
    image: hello-front-dev
    build:
      context: .
      dockerfile: dev.Dockerfile
    volumes:
      - ./:/usr/src/app
    ports:
      - 3000:3000
    container_name: hello-front-dev
  Debug-helper:
    image: busybox

لن تتضمن حاوية Busybox على أية عمليات تجري ضمنها لذلك يمكننا تنفيذ الأمر exec. عندها سيبدو الخرج الناتج عن تنفيذ التعليمة docker-compose up على النحو التالي:

$ docker-compose up
  Pulling debug-helper (busybox:)...
  latest: Pulling from library/busybox
  8ec32b265e94: Pull complete
  Digest: sha256:b37dd066f59a4961024cf4bed74cae5e68ac26b48807292bd12198afa3ecb778
  Status: Downloaded newer image for busybox:latest
  Starting hello-front-dev          ... done
  Creating react-app_debug-helper_1 ... done
  Attaching to react-app_debug-helper_1, hello-front-dev
  react-app_debug-helper_1 exited with code 0

  hello-front-dev | 
  hello-front-dev | > react-app@0.1.0 start
  hello-front-dev | > react-scripts start

هذا الخرج متوقع كون الحزمة هي مجموعة أدوات مثل غيرها. لنستخدم الحزمة في إرسال طلب إلى الحاوية "hello-front-dev" لنرى كيف يعمل خادم DNS. بإمكاننا تنفيذ الطلب wget أثناء عمل الحاوية فهو أداةٌ موجودةٌ ضمن Busybox مهمتها إرسال طلب إلى الحاوية hello-front-dev من مساعد التنقيح debug-helper.

يمكننا استخدام الأمر docker-compose run SERVICE COMMAND لتنفيذ خدمة مع أمر محدد، ويتطلب استخدام الأمر wget السابق الراية O- تليها - لنقل الاستجابة إلى مجرى الخرج:

$ docker-compose run debug-helper wget -O - http://app:3000

  Creating react-app_debug-helper_run ... done
  Connecting to hello-front-dev:3000 (172.26.0.2:3000)
  writing to stdout
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="utf-8" />
      ...

يُعد عنوان URL الجزء المهم هنا، فهو يشير إلى أننا اتصلنا بالخدمة "hello-front-dev" والمنفذ 3000. لقد منحنا الحاوية اسمها "hello-front-dev" باستخدام التعليمة container_name في ملف "docker-compose"، أما المنفذ فهو المنفذ المتاح للوصول إلى التطبيق ضمن الحاوية. ولا حاجة لنشر المنفذ كي تتصل به بقية الخدمات الموجودة على نفس الشبكة، فالمنافذ المحددة في الملف "docker-compose" هي للوصول الداخلي وحسب.

دعونا نغيّر رقم المنفذ في الملف "docker-compose.yml" لتوضيح الأمر:

services:
  app:
    image: hello-front-dev
    build:
      context: .
      dockerfile: dev.Dockerfile
    volumes:
      - ./:/usr/src/app
    ports:
      - 3210:3000
    container_name: hello-front-dev
  debug-helper:
    image: busybox

سيُتاح التطبيق على الحاسوب المضيف وعلى العنوان http://localhost:3210 عند تنفيذ الأمر docker-compose up، لكن التطبيق لا يزال يعمل وفقًا للأمر السابق docker-compose run debug-helper wget -O - http://app:3000 طالما أن المنفذ هو 3000 أيضًا ضمن شبكة دوكر.

01_docker_network.png

يطلب الأمر docker-compose run -كما تشرح الصورة السابقة-من مساعد التنقيح أن يرسل طلبًا ضمن شبكة دوكر docker بينما يرسل المتصفح في الجهاز المضيف الطلب من خارج الشبكة.

أما الآن وقد علمت سهولة إيجاد الخدمات في الملف "docker-compose.yml" وليس لدينا أي شيء للتنقيح، سنزيل مساعد التنقيح و نُعيد المنافذ إلى 3000:3000 في الملف "docker-compose.yml".

التمرين 12.16: تشغيل الواجهة الخلفية لتطبيق المهام ضمن حاوية التطوير

استخدم الأقراص والمكتبة Nodemon لتمكين عملية تطوير الواجهة الخلفية لتطبيق المهام وهي تعمل ضمن الحاوية. أنشئ الملف "todo-backend/dev.Dockerfile" وعدّل الملف "todo-backend/docker-compose.dev.yml".

عليك إعادة التفكير أيضًا في الاتصالات بين الواجهة الخلفية و قاعدة البيانات MongoDB، أو Redis. ولحسن الحظ يدعم docker-compose استخدام متحولات بيئة يمكن تمريرها إلى التطبيق:

services:
  server:
    image: ...
    volumes:
      - ...
    ports:
      - ...
    environment: 
      - REDIS_URL=...
      - MONGO_URL=...

وُضعت عناوين URL للخادم المحلي بطريقة خاطئة عمدًا وعليك وضع القيم الصحيحة. وتذكر أن تراقب دائمًا ما يحدث على شاشة الطرفية، فقد ستلمّح رسائل الخطأ إلى مكان المشكلة إن حدث خلل ما.

إليك هذه الصورة التوضيحة التي قد تنفع في توضيح الاتصالات ضمن شبكة docker:

02_docker_network_connections.png

التواصل بين الحاويات في بيئة أكثر حيوية

سنضيف تاليًا خادم وكيل معكوس reverse proxy إلى الملف " docker-compose.yml". واستنادًا إلى ويكيبيديا:

اقتباس

الخادم الوكيل المعكوس هو نوع من أنواع الخوادم الوكيلة التي يُستخدم لإحضار الموارد إلى المستخدم نيابة عن العميل من خادم أو أكثر. تُعاد هذه الموارد إلى العميل ويبدو كأن مصدرها الأصلي هو الخادم الوكيل المعكوس نفسه.

سيكون الخادم الوكيل المعكوس في حالتنا نقطة دخول مفردة إلى تطبيقنا، أم الهدف النهائي فهو إعداد واجهة React الأمامية و واجهة Express الخلفية معًا خلف الخادم والوكيل المعكوس.

لديك عدة خيارات تساعدك في إنجاز الخادم الوكيل، مثل Traefik و Caddy و Nginx و Apache وقد رُتبت من الأحدث إلى الأقدم ظهورًا، لكن خيارنا سيكون Nginx.

لنضع الآن الواجهة الأمامية المتمثلة بالحاوية "hello-frontend" خلف الخادم الوكيل المعكوس. لهذا عليك إنشاء الملف "nginx.conf" في جذر المشروع واتّبع القالب التالي بمثابة نقطة انطلاق. لا بُد من إنجاز بعض التعديلات الثانوية لتشغيل التطبيق:

# الأحداث مطلوبة لكن لا بأس بالاعتماد على الأحداث الافتراضية
events { }

# 80 يستمع إلى المنفذ http خادم 
http {
  server {
    listen 80;

    # (/) تُعالج الطلبات التي تبدأ بالمحرف 
       location / {
      # نحتاج الأسطر الثلاثة التالية للتحميل المباشر للتغييرات

      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection 'upgrade';
      # http://localhost:3000 تُوجَّه الطلبات مباشرةً إلى العنوان 
      proxy_pass http://localhost:3000;
    }
  }
}

أنشئ تاليًا خدمة Nginx ضمن الملف "docker-compose.yml"، ثم أضف قرصًا كما ورد في إرشادات الصفحة الرسمية للأداة Docker Hub حيث يكون الجانب الأيمن على الشكل هو: etc/nginx/nginx.conf:ro/:،إذ يدل التصريح ro على أن القرص للقراءة فقط:

services:
  app:
    # ...
  nginx:
    image: nginx:1.20.1
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    ports:
      - 8080:80
    container_name: reverse-proxy
    depends_on:
      - app # انتظر حاوية الواجهة الخلفية حتى تُقلع

يمكنك الآن تنفيذ الأمر ومراقبة نتيجة العمل:

$ docker container ls
CONTAINER ID   IMAGE             COMMAND                  CREATED         STATUS         PORTS                                       NAMES
a02ae58f3e8d   nginx:1.20.1      "/docker-entrypoint.…"   4 minutes ago   Up 4 minutes   0.0.0.0:8080->80/tcp, :::8080->80/tcp       reverse-proxy
5ee0284566b4   hello-front-dev   "docker-entrypoint.s…"   4 minutes ago   Up 4 minutes   0.0.0.0:3000->3000/tcp, :::3000->3000/tcp   hello-front-dev

عند الانتقال إلى العنوان http://localhost:8080 ستظهر صفحة الحالة 502 المألوفة، وهذا لأن توجيه الطلبات إلى العنوان http://localhost:3000 لن يقود إلى أي مكان، لأن حاوية الخادم Nginx لا تحتوي أي تطبيق يعمل على المنفذ 3000. إن مصطلح "خادم محلي" يُستخدم عادة للدلالة على الحاسوب الحالي الذي نلج إليه، بينما يكون الخادم المحلي فريدًا في عالم الحاويات لكل حاوية ويقود إلى الحاوية نفسها.

لنختبر ذلك بالدخول إلى الحاوية Nginx واستخدام الأداة curl لإرسال طلب إلى التطبيق نفسه، وتشابه هذه الأداة من حيث الطريقة التي نستخدمها الأداة wget لكنها لا تحتاج أية رايات:

$ docker exec -it reverse-proxy bash  

root@374f9e62bfa8:/# curl http://localhost:80
  <html>
  <head><title>502 Bad Gateway</title></head>
  ...

يمكننا الاستفادة من الشبكة التي يهيئها docker-compose عند تنفيذ الأمر docker-compose up، فهي تضيف كل الحاويات الموجودة في الملف "docker-compose.yml" إلى الشبكة. يتأكد خادم DNS من إمكانية إيجاد بقية الحاويات، وتُمنح كل منها اسمان الأول اسم الخدمة والآخر اسم الحاوية.

طالما أننا ضمن الحاوية، سنختبر خادم DNS، لنستخدم إذًا curl لطلب الخدمة التي تُدعى (app) على المنفذ 3000

root@374f9e62bfa8:/# curl http://app:3000
  <!DOCTYPE html>
  <html lang="en">
    <head>
    ...
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    ...

هذا كل ما في الأمر. لنبدل الآن عنوان proxy_pass في الملف "nginx.conf" بهذا العنوان (http://app:3000).

إن ظهرت الصفحة 502 مجددًا ، تأكد من بناء create-react-app أولًا، واقرأ الخرج الناتج عن تنفيذ الأمر docker-compose up.

أمر آخر: لقد أضفنا الخيار depends_on إلى الإعدادات لنتأكد أن حاوية "nginx" لن تعمل قبل حاوية الواجهة الأمامية "app":

services:
  app:
    # ...
  nginx:
    image: nginx:1.20.1
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    ports:
      - 8080:80
    container_name: reverse-proxy
    Depends_on:
      - app

إن لم نفرض تسلسل الإقلاع هذا باستخدام الخيار depends_on فقد نقع في خطر فشل إقلاع لأنه يحاول تحليل أسماء نطاقات DNS التي يُشار إليها في ملف الإعدادات:

http {
  server {
    listen 80;

    location / {
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection 'upgrade';

      proxy_pass http://app:3000;
    }
  }
}

تجدر الملاحظة أن الخيار depends_on لا يضمن أن تكون الخدمة في الحاوية جاهزةً للعمل، بل يضمن أن الحاوية قد بدأت العمل وأضيف المُدخل الخاص بها إلى خادم DNS، لكن إذا أردت من خدمة أن تنتظر أخرى حتى تجهز، فعليك الاطلاع على حلول أخرى.

التمرينات 12.17 - 12.19

حاول إنجاز التمرينات التالية:

التمرين 12.17: إعداد خادم وكيل معكوس Nginx أمام الواجهة الأمامية لتطبيق المهام

سنحاول في هذا التمرين وضع خادم Nginx أمام الواجهتين الأمامية والخلفية لتطبيق المهام todo-app. سنبدأ بإنشاء ملف docker-compose جديد "todo-app/docker-compose.dev.yml" وملف تهيئة Nginx يحمل الاسم "todo-app/nginx.conf".

todo-app
├── todo-frontend
├── todo-backend
├── nginx.conf
└── docker-compose.dev.yml

أضف الخدمتين nginx و todo-app/todo-frontend/dev.Dockerfile إلى الملف "todo-app/docker-compose.dev.yml".

03_todo_front_end_proxy.png

التمرين 12.18: إعداد خادم ليكون أمام الواجهة الخلفية لتطبيق المهام

أضف الخدمة todo-backend إلى الملف "*todo-app/docker-compose.dev.yml" في وضع التطوير، ثم أضف مكانًا جديدًا للملف كي تُخدَّم الطلبات إلى العنوان "api/" عبر الخادم الوكيل إلى الواجهة الخلفية. قد يكون القالب التالي مناسبًا لإنجاز الأمر:

  server {
    listen 80;

    # (/) تُعالج الطلبات التي تبدأ بالمحرف 
    location / {
      # نحتاج الأسطر الثلاثة التالية للتحميل المباشر للتغييرات

      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection 'upgrade';

       # http://localhost:3000 تُوجَّه الطلبات مباشرةً إلى العنوان 
      proxy_pass http://localhost:3000;
    }

    # (/api/) تُعالج الطلبات التي تبدأ بالمسار 
    location /api/ {
      ...
    }
  }

للتوجيه proxy_pass ميزةٌ مهمة عندما تُضاف إليه الشرطة المائلة الزائدة "/"، وطالما أننا نستخدم المسار "api/" لتحديد المكان علمًا أن تطبيق الواجهة الخلفية سيجيب فقط على العنوان "/" أو "todos/" فلا بد من إزالة "api/" من الطلب. وبكلمات أخرى، حتى لو أرسل المتصفح الطلب GET إلى العنوان "api/todos/1/" نريد من الخادم Nginx أن ينقل الطلب بالوكالة إلى العنوان "todos/1/". لتنفيذ الأمر لا بد من إضافة شرطة مائلة "/" زائدة إلى العنوان في نهاية التوجيه proxy_pass. انتبه للموضوع جيدًا فقد تقضي ساعات في البحث عن حل لمشاكل سببها إهمال الشرطة الزائدة.

وهذه إحدى المشاكل الشائعة:

06_nginx_trailing_slash_stackoverflow.png

إليك هذه الصورة التوضيحة التي قد تنفع في توضيح الأمر عندما تواجهك المشاكل:

04_todo_back_end_proxy.png

التمرين 12.19: ربط الخدمتين todo-frontend و todo-backend

سلّم في هذا التمرين بيئة التطوير بأكملها بما في ذلك تطبيقي Express و React وملفات Dockerfiles والملف "docker-compose.yml".

تأكد بدايةً من عمل الواجهة الأمامية مع الواجهة الخلفية، وسيتطلب ذلك تغييرات في متغير البيئة REACT_APP_BACKEND_URL. وإذا كانت الواجهة جاهزةً للعمل خلال التمرين السابق يمكنك تجاوز هذه الخطوة.

تأكد من أن بيئة التطوير تعمل الآن بكامل طاقتها، أي:

  • أن تعمل جميع ميزات تطبيق المهام.
  • عندما تغيّر الشيفرة المصدرية لا بُد أن تظهر نتيجة التغيرات مباشرةً في حال كان التغيير في الواجهة الأمامية وبإعادة تحميل التطبيق إن كان التغيير في الواجهة الخلفية.

أدوات لمرحلة الإنتاج

التعامل مع الحاويات ممتعٌ في مرحلة التطوير، لكن أفضل حالات الاستخدام ستكون في مرحلة الإنتاج، إذ توجد أدوات أكثر قدرة من docker-compose في تشغيل الحاويات في مرحلة الإنتاج.

تسمح لنا أداة تنسيق الحاويات Kubernetes مثلًا بإدارة الحاويات ضمن مستوى جديد بالكامل، وتخفي تلك الأدوات عمومًا التجهيزات الفيزيائية المستخدمة مما يجعل المطورين أقل انشغالًا بأمور البنية التحتية.

إن كنت ترغب في الاطلاع أكثر على الحاويات باستخدام Docker فعليك بالمنهاج DevOps with Docker، وكذلك المنهاج DevOps with Kubernetes لتتعلم تنسيق الحاويات باستخدام Kubernetes.

التمرينات 12.20-12.22

حاول إنجاز التمرينات التالية:

التمرين 12.20

أنشئ نسخة إنتاج من الملف "todo-app/docker-compose.yml" تضم كل الخدمات: Nginx و todo-backend و todo-frontend و MongoDB و Redis. استخدم الملف "Dockerfiles" بدلًا من "dev.Dockerfiles" وتأكد من تشغيل التطبيق في وضع الإنتاج.

استخدم الهيكلية التالية في هذا التمرين:

todo-app
├── todo-frontend
├── todo-backend
├── nginx.conf
├── docker-compose.dev.yml
└── docker-compose.yml

التمرين 12.21

أنشئ بيئة تطوير تعتمد على الحاويات مشابهة لما فعلنا، وذلك لأحد التطبيقات التي مرّت معك خلال منهاجنا أو التي أنشاتها في أوقات فراغك. اجعل هيكلية تطبيقك ضمن مستودع التسليم على النحو التالي:

└── my-app
    ├── frontend
    |    └── dev.Dockerfile
    ├── backend
    |    └── dev.Dockerfile
    └── docker-compose.dev.yml

التمرين 12.22

أنهِ القسم بإعداد نسخة إنتاج تعتمد على الحاويات خاصة بالتطبيق الذي اخترته. جعل هيكلية تطبيقك ضمن مستودع التسليم على النحو التالي:

└── my-app
    ├── frontend
    |    ├── dev.Dockerfile
    |    └── Dockerfile
    ├── backend
    |    └── dev.Dockerfile
    |    └── Dockerfile
    ├── docker-compose.dev.yml
    └── docker-compose.yml

ترجمة -وبتصرف- للفصل basics of orchestration من سلسلة Deep Dive Into Modern Web Development

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...