11.04.2023 8 min read

تصحيح سلس: عزّز تطوير Python وNode.js باستخدام Docker Compose وVS Code

بواسطة Dženan Džafić

دليل لربط مصحّح VS Code بحاويات Docker Compose لتطبيقات Python (FastAPI) وNode.js (Nest.js)، لتحسين تجربة المطوّر لديك.

launch.json file inside our .vscode configuration folder

لطالما صارعت مشكلة ربط مصحّح VS Code (Debugger) بحاويات معينة داخل ملف docker-compose خلال الأشهر الثلاثة الماضية. يستند هذا المقال إلى تجربتي الشخصية في العمل على مشاريع متعددة باستخدام NEST.js وFastAPI.

سيغطي المقال إعداد ملف docker-compose، وكيفية كتابة إعدادات VS Code للاستماع إلى منافذ التصحيح المكشوفة، وكيف يمكنك استخدام ذلك لتحسين تجربة المطوّر في مشروعك.

الكود الكامل لكلا المثالين متوفر على مستودعي على Github.

المشكلة

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

مع نمو المشروع في التعقيد وحدوث أخطاء غير متوقعة، احتجت لإيجاد طريقة للتغلب على هذه المشكلة، وربط مصحّح VS Code بطريقة ما بنسخة حاوية قيد التشغيل.

الحل

عندما تبدأ مشروعًا كمطوّر، تحتاج للتأكد من أن تجربة المطوّر جيدة، وأن جميع الأدوات المستخدَمة تعمل بشكل صحيح، وتُستخدَم بأقصى إمكاناتها.

كنت أعدّ مشروعًا بلغة Python سيستخدم FastAPI كواجهة خلفية، وPostgreSQL كقاعدة بياناته. كان بإمكاني بدء تطوير التطبيق دون عزله في حاوية فورًا، وتطويره على بيئتي المحلية، لكن ذلك كان سيسبب لي صداعًا مستقبلًا، وسيكون كابوسًا إعداده على جهاز شخص آخر.

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

أعطاني هذا فكرة: تطبيق هذا الحل على جميع المشاريع التي أعمل عليها حاليًا.

سأبدأ بتنفيذ FastAPI أولًا؛ وإذا لم يكن هذا يهمك، يمكنك التخطي لقراءة تنفيذ Nest.js.

تكامل FastAPI

إذا أردت متابعة الشرح، يمكنك استنساخ المستودع من GitHub.

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

  • عزل التطبيق في حاوية (Dockerizing)
  • إعداد docker-compose
  • إعداد VS Code
  • تشغيل التطبيق مع ربط المصحّح

عزل التطبيق في حاوية

FROM python:3.8.10

WORKDIR /app

COPY ./requirements.txt ./

RUN pip install --no-cache-dir --upgrade -r ./requirements.txt

COPY . /app

في مثالنا، نستخدم نسخة مبسطة من Dockerfile، لأن تطبيقنا هو مجرد نقطة نهاية بسيطة تعيد بعض البيانات الثابتة، لكن إذا كان تطبيقك أكبر يمكنك الرجوع إلى موقع FastAPI الرسمي، حيث يتناولون Dockerfile بالتفصيل.

إعداد docker-compose

سنسمي هذا الملف docker-compose.debug.yaml.، لتمييزه عن ملف docker-compose الرئيسي.

version: '3.9'
services:
  postgres:
    container_name: postgres
    image: postgres:latest
    env_file: docker.env
    networks:
      - local-network
  api:
    container_name: api
    build: 
      context: .
      dockerfile: Dockerfile
    env_file: docker.env
    ports:
      - 8000:8000
      - 5678:5678
    networks:
      - local-network
    volumes:
      - .:/app
      - postgres_data:/var/lib/postgresql/data
    command: ["sh", "-c", "pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 -m uvicorn src.main:app --host 0.0.0.0 --port 8000 --reload"]
volumes: 
  postgres_data:
    driver: local
networks:
  local-network:
    name: local-network

داخل ملف compose هذا، حددنا عدة أشياء سنتناولها لفهم ما يجري خلف الكواليس بشكل كامل.

أول خاصية من المستوى الأعلى هي الإصدار (version) حيث نحدد إصدار compose الذي نريد استخدامه، يمكنك قراءة المزيد عن ذلك هنا.

الآن نصل إلى قسم الخدمات (services) لدينا، حيث نحدد جميع الخدمات التي سيبنيها compose عندما نطلب منه ذلك. هنا تطبيقنا محدد تحت خاصية api، لكنني أضفت أيضًا postgres حتى نمتلك قاعدة بيانات للاستخدام المستقبلي.

build: 
  context: .
  dockerfile: Dockerfile

الشيء الرئيسي هنا هو api، حيث نحدد قاموس البناء (build) لتحديد اسم Dockerfile، والمجلد الذي تُؤخذ منه ملفات التطبيق.

كما نحدد ملف .env. تحت خاصية env_file، حيث توجد أسرارنا، والمنافذ (ports) المكشوفة من الحاوية، وستكون هذه أهم جزء من الإعداد.

بعد ذلك، نحدد الشبكة الافتراضية التي ستُنشر عليها خدماتنا، ننتقل بعدها إلى volumes لدينا:

volumes:
  - .:/app

تحت العنصر الأول، نربط مجلد العمل لدينا بمجلد الحاوية البعيدة، تتيح هذه الخطوة لـ compose ربط كل تغيير في بيئتنا المحلية بالحاوية البعيدة، وعندما نفعّل إعادة التحميل الفوري (hot-reload) ستُطبَّق التغييرات تلقائيًا.

الآن إلى الجزء الرئيسي من خدمة api؛ الأمر (command):

"sh", "-c", "pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 -m uvicorn src.main:app --host 0.0.0.0 --port 8000 --reload"

نثبّت حزمة debugpy لتفعيل التصحيح لـ Python، ومع علامة --wait-for-client نوجّه debugpy لينتظر قبل ربط نفسه حتى يبدأ العميل، في حالتنا - uvicorn.

بعد بدء uvicorn، نربط debugpy بالمنفذ الثاني المكشوف 5678.

الآن بعد إعداد كل ما يخص ملف compose، لنُعدّ VS Code.

إعداد VS Code

ملف launch.json داخل مجلد إعدادات .vscode لدينا

أنشئ مجلدًا في دليلك الأعلى باسم .vscode.، وفيه أنشئ ملفًا باسم launch.json.. تأكد أن الملف يحتوي على الكود التالي:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python: Remote Attach to FastApi",
      "type": "python",
      "request": "attach",
      "port": 5678,
      "host": "localhost",
      "pathMappings": [
        {
          "localRoot": "${workspaceFolder}",
          "remoteRoot": "/app"
        }
      ]
    }
  ]
}

في كائن الإعدادات، نحدد لـ VS Code أننا نريد الربط بـ عملية موجودة على localhost، المنفذ 5678.. كما نحدد ربط المسارات (path mappings) المطابقة لمصفوفة volumes في خدمة api..

.:/app

localRoot في pathMappings يعادل الـ . في العنصر الأول من volumes، والذي يحدد، كما يوحي الاسم، مجلد مساحة العمل حيث يقع تطبيقنا على بيئتنا المحلية.

remoteRoot هو المسار إلى المجلد على الحاوية البعيدة. في ملف compose، يمثّل المسار /app.

تشغيل التطبيق مع ربط المصحّح

الآن بعد أن حددنا الإعدادات، لنشغّل compose ونربط بالعملية.

لتشغيل compose، اكتب الأمر التالي:

docker-compose -f "docker-compose.debug.yaml" up --build

بعد بناء التطبيق، انتقل إلى VS Code واضغط F5 لربط مصحّح VS Code. الآن أضف نقطة توقف (breakpoint) وحاول الوصول إلى تطبيقك على المنفذ http://localhost:8000..

Execution hits the breakpoint in VS Code

يصل التنفيذ إلى نقطة التوقف في VS Code

تكامل Nest.js

الكود الخاص بتكامل Nest.js متوفر على هذا الرابط.

ستكون هذه العملية مشابهة لتلك التي قمنا بها مع FastAPI. الفرق هنا هو أن nest.js (node.js) يأتي مع مصحّحه الخاص، لذا لسنا بحاجة لتثبيت أي حزم إضافية لربط مصحّح VS Code.

سنتبع نفس البنية التي اتبعناها مع FastAPI:

  • عزل التطبيق في حاوية
  • إعداد docker-compose
  • إعداد VS Code
  • تشغيل التطبيق مع ربط المصحّح

عزل التطبيق في حاوية

عندما يتعلق الأمر بعزل تطبيق nest في حاوية، لم أهتم كثيرًا بـ Dockerfile وبنيته، لأن تركيز هذا المقال هو عرض تكامل مصحّح VS Code مع docker-compose.

FROM node:19.6.0

WORKDIR /app

COPY . .

RUN yarn build

كما ترى: إنه Dockerfile بسيط يبني التطبيق.

إعداد docker-compose

كما هو الحال مع compose، إنه مطابق تقريبًا لذلك الخاص بـ Python، مع بعض التعديلات الطفيفة؛ التي سأناقشها بعد أن ترى الكود:

version: '3.9'
services:
  postgres:
    container_name: postgres
    image: postgres:latest
    env_file: docker.env
    networks:
      - local-network
  api:
    container_name: api
    build: 
      context: .
      dockerfile: Dockerfile
    env_file: docker.env
    networks:
      - local-network
    volumes:
      - /app/node_modules
      - .:/app
      - postgres_data:/var/lib/postgresql/data
    ports:
      - 3000:3000
      - 9229:9229
    command: yarn start:debug
volumes: 
  postgres_data:
    driver: local
networks:
   local-network:
      name: local-network

كما في الشرح السابق، نحدد خدمتين: postgres وapi؛ يمكن أن تخدمانا مستقبلًا عندما نريد تخزين بعض البيانات في قاعدة البيانات.

build: 
  context: .
  dockerfile: Dockerfile

خدمة api ذات أهمية خاصة لنا، حيث حددنا الطريقة التي نريد بها بناء Dockerfile والمجلد الذي نريد أخذ الملفات منه (context).

volumes:
  - /app/node_modules
  - .:/app

مصفوفة volumes لـ nestjs تختلف عن تلك في FastAPI، لأن لدينا node_modules.. هذا المجلد مُتجاهَل (ignored) في مصفوفة volumes الخاصة بملف compose، لأننا نمتلكها بالفعل عندما نبني الصورة (image) لدينا ونشغّل الحاوية.

العنصر الثاني في مجلد volumes سيتيح لنا استنساخ التغييرات على بيئتنا المحلية إلى الحاوية، وعندما نضبط "أمر التشغيل"؛ سيكون لدينا إعادة تحميل فورية (hot reload) على الحاوية.

لتشغيل تطبيقنا في compose، نحتاج لضبط الأمر لتشغيل التطبيق في وضع التصحيح. انتقل إلى package.json. لديك، وتحت كائن scripts، ابحث عن الأمر "start:debug"، واضبطه كما تراه في مقتطف الكود أعلاه.

في مقتطف الكود أعلاه، نخبر nest بتشغيل التطبيق مع تفعيل وضع التصحيح وكشف المفتّش (inspector) على المنفذ 9229 على localhost، كما أضفنا العلامة --watch لـ إعادة التحميل الفوري.

ports:
  - 3000:3000
  - 9229:9229
command: yarn start:debug

وأخيرًا نكشف منفذين للحاوية؛ أحدهما للوصول إلى التطبيق عبر متصفح الويب، والآخر للوصول إليه عبر المفتّش.

في النهاية، نشغّل التطبيق بالأمر الذي أنشأناه في package.json..

إعداد VS Code

launch.json file inside our .vscode configuration folder

ملف launch.json داخل مجلد إعدادات .vscode لدينا

في المستوى الأعلى من المشروع، ننشئ مجلد .vscode. مع ملف launch.json.، حيث سنضيف إعدادات VS Code لتشغيل المصحّح.

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "attach",
      "name": "Docker: Attach to Nest",
      "protocol": "inspector",
      "address": "localhost",
      "port": 9229,
      "sourceMaps": true,
      "restart": true,
      "localRoot": "${workspaceFolder}",
      "remoteRoot": "/app",
      "skipFiles": ["<node_internals>/**"]
    }
  ]
}

داخل الكائن، نحدد نوع التطبيق الذي نشغّله، والطريقة التي نريد بها الوصول إلى منفذ التصحيح. في حالتنا: نربط أنفسنا بـ localhost على المنفذ 9229 بـ بروتوكول inspector.

بعدها نضيف sourceMaps بقيمة true ونربط بيئتنا المحلية بالبيئة التي تعمل داخل الحاوية.

عندما يُعاد تحميل التطبيق فوريًا عند التغيير، نريد أيضًا إعادة ربط مصحّحنا بـ nest لأنه سيفقد الاتصال بمجرد إعادة التحميل.

تشغيل التطبيق مع ربط المصحّح

بنفس الطريقة التي شغّلنا بها ملف compose الخاص بـ Python، نشغّله هنا أيضًا.

الآن بعد أن حددنا الإعدادات، لنشغّل compose، ونربط بالعملية.

لتشغيل compose، اكتب الأمر التالي:

docker-compose -f "docker-compose.debug.yaml" up --build

بعد بناء التطبيق، انتقل إلى VS Code واضغط F5 لربط مصحّح VS Code. الآن أضف نقطة توقف وحاول الوصول إلى تطبيقك على المنفذ http://localhost:3000..

Execution hits the breakpoint in VS Code

يصل التنفيذ إلى نقطة التوقف في VS Code

قراءات إضافية عرض الكل
الخطوة التالية

طبّق هذه الأفكار على منتجك

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