من فکر می‌کنم انعطاف‌پذیری و مزیت‌هایی که کانتینرهای داکر فراهم می‌کنن برای هیچ‌کسی پوشیده نیست!

نکته جالبی که وجود داره اینه که با وجود الگوهای جدید معماری نرم‌افزار،  مثل خدمات میکروسرویس‌ها یا معماری بدون سرور، باز هم نیاز به سرویس‌هایی برای اجرای برنامه‌ها به صورت مستقل و ایزوله (کانتینرها) کاملا برجسته است. 

حتی برای شرایط پیچیده‌تر و به هم پیوسته‌تر، ابزارهای مکملی مثل docker-compose وجود داره که با مدیریت و پیوند کانتینرها، کار رو برای برنامه‌نویس‌ها آسون می‌کنن.

با این حال، همونطور که روز‌به‌روز فناوری در حال توسعه و پیشرفته، روش‌ها و روال‌های توسعه‌ي نرم‌افزار هم باید به همون ترتیب، پیشرفت کنن. در غیر این صورت، مقدار قابل توجهی از زمان برای توسعه هدر میره و ممکنه باعث نرسیدن به هدف و شکست پروژه میشه.

 وقتی من حتی نمی‌تونم یک تست محلی رو با یک کلیک انجام بدم، چجوری می‌خوام با استفاده از یک کلیک برنامه رو مستقر کنم؟

در این مقاله، قصد داریم توضیح بدیم که Docker Compose چجوری بدون تغییر کد موجود، این امکان رو ایجاد می‌کنه که فایل‌هایی رو که تغییر کردن، مجددا بارگذاری کنیم.

نکته: فرض ما اینه که شما با داکر و Docker Compose آشنایی دارید.

اگر با این مفاهیم آشنایی ندارید، پیشنهاد می‌کنیم پست‌های داکر برای توسعه‌دهنده‌ها و Docker Compose رو مطالعه کنید.


مشکل از کجا شروع شد؟

قضیه از جایی شروع شد که هنگام اجرای پشته‌ی  Docker Compose به صورت محلی، با هر بار تغییری در کد برنامه، نیاز به Restart برنامه وجود داشت.

سناریوی زیر رو تصور کنید: 

شما در حال ایجاد یک برنامه وب با استفاده از یک چارچوب وب هستید، مثلا یک برنامه Django (ممکنه هر چارچوب وب دیگری به هر زبانی باشه).

به طور معمول، شما می‌تونید به راحتی با ابزارهای خط فرمان Django برنامه‌هاتون رو توسعه بدین. Django امکان بارگذاری مجدد برنامه‌ها رو هم بهتون میده.

با این حال، وسط توسعه برنامه تصمیم می‌گیرین که پایگاه‌داده‌تون رو از SQLite به Postgres تغییر بدین.

علاوه بر این، شما تسک‌های Celery به همراه ورکرهای اون، سرویس Flask برای کنترل عملکردها و در نهایت، پروکسی NginX رو هم اضافه کردین.

بدون کانتینرها، هر توسعه‌دهنده نیاز داره تا سرور Postgres‌، تسک‌های Celery، سرویس Flask  و برنامه Django را به طور جداگانه در محیط توسعه خودش اجرا کنه تا برنامه مورد‌نظرش درست  اجرا بشه.

 ایجاد همچین محیط توسعه‌ای برای یک توسعه‌دهنده‌ی تازه‌کار مثل یک کابوسه.

خب، راه‌حل چیه؟

جواب: استفاده از Docker Compose!

همه مواردی که گفتیم رو میشه توسط کانتینرها انجام داد و سرویس‌های مجزا می‌تونن از طریق docker-compose.yml تعریف بشن.

docker-compose.yml این امکان رو میده که تمامی این پشته رو با یک دستور، لینک و اجرا کنین.

بعد از اون، همه چیز در محیط ایزوله‌ی مرتبط به خودش اجرا می‌شه و نیازی به نگرانی شما در رابطه با محیط ‌های کار و وابستگی میان اون‌ها نیست.

اما حالا یک مساله‌ی دیگه وجود داره!

هر بار که تغییری در کد ایجاد می‌کنید، باید پشته docker-compose رو مجدداً راه‌اندازی کنید.

 این شامل جابجایی به ترمینال و خاتمه سرویس‌های docker-compose  که هم‌اکنون در حال اجرا هستند هم میشه.

در نتیجه شما باید صبر کنید تا برنامه یک بار خاتمه پیدا کنه و دوباره از اول راه‌اندازی بشه.

یعنی شما برای هر تغییری که در کدتون به وجود میارین، حدود ۱۵ ثانیه باید صبر کنید.

خب الان ممکنه شما بگین که ۱۵ ثانیه که زمان زیادی نیست! درسته، ما هم می‌دونیم!

اما وقتی که چندین و چند بار لازم باشه تغییری در کد برنامتون به وجود بیارین (که اصلا هم چیز غیرقابل انتظاری نیست)، این ۱۵ ثانیه‌ها روی هم جمع میشه و زمان قابل توجهی رو به خودش اختصاص میده.

از طرف دیگه، ما اینجا مدت زمانی که ذهن شما نیاز داره تا بعد از توقف دوباره روی موضوع تمرکز کنه رو حساب نکردیم!


راه حل

برای حل کردن مشکلی که در بالا توضیح داده شد، می‌تونیم از یک ابزار نظارت فایل به اسم watchdog
استفاده کنیم.

اما می‌خوایم روشی رو معرفی کنیم که:

  • مستقل از زبان
  • مستقل از پلتفرم
  • بدون نیاز به اصلاح کد موجود
  • بدون وابستگی

باشه.

با توجه به این موارد، Andreas Pogiatzis راه حل زیر را ارائه کرده:

یک ایمیج داکر عمومی که در اصل یک ایمیج داکر در داکر یا اصطلاحا DinD Image است. به همراه پایتون و watchdog که روی آن نصب شده. که این‌ها بعدا برای بارگذاری مجدد مورد استفاده قرار می‌گیرن.

از اونجایی که ایمیج ما یک ایمیج DinD هست، به کانتینرهایی که بر روی Host در حال اجرا هستن، دسترسی داره و می‌تونه اون‌ها رو Restart کنه. فقط لازمه که دایرکتوری کد اصلی برنامه از طریق متغیرهای محیط به درستی نصب و پیکربندی بشن.

اگر دوباره به نیازمندی‌هایی که ابتدای این بخش گفتیم، برگردیم؛ می‌بینیم که یک ایمیج داکر جداگانه، تمامی اون نیازمندی‌ها رو برطرف می‌کنه.

مستقل از زبان و مستقل از پلتفرمه، هیچگونه وابستگی نداره و می‌تونه به عنوان یک افزونه برای فایل docker-compose استفاده بشه، بدون نیازی به اصلاحات.

مخزن مربوط به پروژه در اینجا آمده است:


apogiatzis/docker-compose-livereloader


This docker-compose pattern, aims to provide plug and play live reloading functionality to docker-compose stacks. A…


github.com

شروع کار بارگذاری مجدد

شروع کار با بارگذاری مجدد و در لحظه‌ی docker-compose  بسیار ساده است.

اول باید یک فایل docker-compose  داشته باشید. ایده این است که یک فایل محلی docker-compose  ایجاد کنید که فایل‌ موجود شما رو گسترش بده و فقط بر روی سرویس بارگذاری مجدد شما در پشته کار کنه.

برای فعال کردن بارگذاری مجدد مراحل زیر رو دنبال کنید:

۱. یک فایل docker-compose.yml جدید بسازید:

docker-compose-with-reloading.yml — 

و موارد زیر رو بهش اضافه کنید:

version: '3'
services:
   service-name:
       container_name: <SERVICE_CONTAINER_NAME>
       volumes:
           - "<SOURCE CODE DIR>:<DIRECTORY_TO_MOUNT_CODE>"

این برای Override کردنِ سرویسیه که شما می‌خواین به صورت خودکار مجددا بارگذاری بشه.

service-name با نام فعلی مطابقت داشته باشه.

تگ volumes  اختیاریه و فقط در صورتی باید اون رو اضافه کنین که در سرویس docker-compose  فعلیتون، دایرکتوری کد اصلی برنامه رو قرار نداده باشین.

۲. ایمیج live-reloading رو به docker-composeای که تازه ایجاد کردین اضافه کنین تا با موارد زیر هم تطابق داشته باشه:

version: '3'
Services:
   Service-name:
       container_name: <SERVICE_CONTAINER_NAME>       volumes:
           - "<SOURCE CODE DIR>:<DIRECTORY_TO_MOUNT_CODE>"   live-reloader:
       image: apogiatzis/livereloading
       container_name: livereloader
       privileged: true
       Environment:
         - RELOAD_DELAY=1.5              # seconds
         - RELOAD_CONTAINER=<SERVICE_CONTAINER_NAME>
       Volumes:
         - "/var/run/docker.sock:/var/run/docker.sock"
         - "<SOURCE CODE DIR>:<DIRECTORY_TO_MOUNT_CODE>"

چند نکته‌ی مهم در اینجا وجود داره. به پارامترهای پیکربندی توجه کنید:

RELOAD_DELAY زمان قبل از شروع مجدد رو مشخص می‌کنه، بنابراین تغییرات مداوم فایل فقط در یک راه اندازی مجدد انجام می‌شه. پیش فرض 1.5 ثانیه است.

RELOAD_CONTAINER اسم کانتینریه که قراره در فایل تغییرات ایجاد کنه. این باید با اسم که در container_name فوق مشخص شده، مطابقت داشته باشه.

RELOAD_DIR یک پارامتر اختیاریه که صریحاً می‌تونه دایرکتوری مورد نظر رو تماشا کنه. در صورت عدم ارائه‌، ایمیج از پوشه root در زمان نصب در بخش volumes  استفاده می‌کنه.

3.  Docker-compose را اجرا کنید.

برای اجرای docker-comppos با بارگذاری مجدد، از دستور زیر استفاده کنید:

docker-compose -f docker-compose.yml -f docker-compose-with-reloading.yml up
Modify the command accordingly to match your docker-compose files.

دستور رو مطابق با اون اصلاح کنید تا با فایل‌های docker-compose شما مطابقت داشته باشه.


یک مثال نهایی

اگر دستورالعمل‌ها به اندازه کافی روشن نیستن، می‌تونید نمونه ارائه شده در مخزن مربوطه GitHub رو مشاهده کنید:


apogiatzis/docker-compose-livereloader
This docker-compose pattern, aims to provide plug and play live reloading functionality to docker-compose stacks. A…


github.com

منبع