راهنمای استفاده از Docker و Node.js – از صفر تا صد


اغلب افرادی که با داکر کار میکنند پس از مدتی به آن عادت میکنند و در نهایت طرفدار آن میشوند، زیرا داکر موجب بهبود فرایند کارها، به خصوص فرایند ایجاد اپلیکیشن از فاز توسعه تا فاز اجرایی (production) میشود. در این مقاله به بررسی رابطه Docker و Node.js میپردازیم و در مورد 3 مرحله ایجاد یک اپلیکیشن Node صحبت خواهیم کرد که داکر میتواند بهبود زیادی در آنها ایجاد کند:
- بهینهسازی فرایند خلق اپلیکیشن
- نرمالسازی محیطها
- بهبود یکپارچگی و ارائه
بینشهایی که در این راهنما ارائه میشوند، در مورد چیزهایی غیر از Node.js نیز قابل استفاده هستند که شامل تجربیات مختلف در توسعه اپلیکیشنهای کوچک تا بزرگ مقیاس میشود. یک مقاله آموزشی جامع دیگر نیز برای آموزش داکر در مجله فرادرس منتشر شده است که مطالعه آن به علاقهمندان پیشنهاد میشود:
1. بهینهسازی محیط اجرایی با داکر
یکی از مهمترین ویژگیهای داکر این است که اپلیکیشن شما را به نحوی بستهبندی میکند که میتوان آن را در هر محیط سازگار با داکر توزیع کرد. در این حالت، تصویر داکر باید شامل همه چیزهایی باشد که برای اجرای اپلیکیشن ضروری هستند.
اما هنگامی که تیم توسعه، اپلیکیشن را در محیط production با استفاده از داکر عرضه میکند، برخی بهینهسازیهای خاص هستند که میتوانند به بهبود عملکرد آن، افزایش امنیت و کاهش ردپای بسته کمک کنند.
استفاده از تصویرهای مبتنی بر Alpine
لینوکس Alpine یک توزیع سبک لینوکس مبتنی بر musl libc و busybox است. مزیت اصلی استفاده از Alpine، اندازه تصویر داکر است. حجم alpine برابر با 24 مگابایت است؛ در حالی که وزن node:latest برابر با 267 مگابایت است. وزن سبک توزیع آلپاین همچنین باعث شده که سطح حمله هکرها به آن پایینتر باشد.
البته باید توجه داشته باشید، همچنان که در ریپازیتوری node-alpine (+) بیان شده است، در صورت استفاده از برخی کامپایلرها به خصوص glibc ممکن است با برخی مشکلات مواجه شوید. اما در صورتی که درون کانتینر از یک مجموعه منفرد (مانند Node) استفاده میکنید، این وضعیت نباید روی اپلیکیشن شما تأثیر بگذارد. این همان وضعیتی است که برای اپلیکیشنهای بومی کلاود توصیه میشود.
تنها چیزهایی که اپلیکیشن باید اجرا کند را در Image آن بگنجانید
معنی جمله فوق آن است که باید صرفاً وابستگیها production را در Image قرار دهید و از قرار دادن وابستگیهای توسعه خودداری کنید.
RUN npm install --only=production
همچنین از یک فایل dockerignore. برای پاک کردن مواردی که در production لازم نیستند استفاده کنید. این فایلها برای مثال شامل node_modules هستند که درون Dockerfile، فایلهای تست، مستندات، خود فایلهای داکر و غیره قرار دارند.
اگر از یک transpiler مانند Babel جهت بهرهگیری از ES6 یا ساختارهای جدیدتر در اپلیکیشن Node خود استفاده میکنید، در این صورت بخش transpile را در اسکریپت npm run build درون Dockerfile انجام دهید و پس از بیلد شدن موفق فایلهای اجرایی، سورس آن را حذف کنید. این مراحل در صورت استفاده از بیلدهای چندمرحلهای داکر که در اینجا (+) توضیح داده شده است به روش مناسبتری صورت میگیرد.
npm install را پیش از کپی کردن سورس به Image کانتینر اجرا کنید
بدین ترتیب فایل زمان اجرای داکر میتواند لایه volume را که شامل همه وابستگیهای زیر لایه شامل منابع است کش کند. این بدان معنی است که اگر کد منبع با تناوب بیشتری نسبت به پیکربندی وابستگی بهروزرسانی شود، زمان بیلد داکر بسیار سریعتر از میانگین خواهد بود.
مستندات رسمی داکر یک راهنمای واضح در مورد شیوه ساخت یک تصویر داکر برای اپلیکیشن node ارائه کردهاند که در این آدرس (+) میتوانید ملاحظه کنید.
از نسخه مشخصی از Image داکر Node استفاده کنید
هر چند ممکن است شما اطلاع نداشته باشید، اما اپلیکیشن شما احتمالاً رابطه نزدیکی با برخی نسخههای خاص از فایل زمان اجرای (runtime) زبان برنامهنویسی Node (یا هر مجموعه دیگر) دارد. برای جلوگیری از کرش کردن اپلیکیشن هنگام بهروزرسانی ران-تایم در طی ساخت بیلد جدید داکر باید نسخه دقیق Node را که میخواهید در پلتفرم production اجرا شود تعیین کنید.
در ادامه برخی فایلهای اولیه مورد نیاز برای یک اپلیکیشن Node داکرسازی شده که از ES6 و Babel به عنوان یک transpiler استفاده میکند ارائه شدهاند:
2. نرمالسازی محیطها با Docker Compose
Docker compose ابزاری است که از سوی داکر برای تعریف کل مجموعه اپلیکیشن ارائه شده است. این تعریف شامل سرویسهای اپلیکیشن، پایگاههای داده، لایه کش و غیره میشود که به صورت کانتینرهایی در یک فایل واحد به نام docker-compose.yml مورد اشاره قرار گرفتهاند و حالت این کانتینرها نیز به صورت منابع زیرساختی (دیسکها، شبکه و غیره) به وسیله CLI مدیریت میشود.
نکته جالب در مورد docker-compose این است که اجرای یک محیط شِبه production در محیط توسعه را آسان ساخته است. تصور کنید اپلیکیشنی دارید که شامل کامپوننتهای زیر است:
- یک API در زبان Node.js
- ارتباط با پایگاه داده MySQL
- استفاده از Redis به عنوان یک لایه کش و نشست
- استفاده از Traefik به عنوان یک پروکسی معکوس برای API
توجه کنید که Traefik یک پروکسی معکوس دینامیک است که میتواند کانتینرهای وب اجرایی را بررسی کرده و آنها را به صورت درجا پروکسی معکوس کند. Docker compose امکان راهاندازی این مجموعه در همه محیطها اعم از توسعه، staging و production را به روشی ساده و کاملاً سازماندهی شده فراهم میسازد. مراحل مورد نیاز برای تسهیل راهاندازی iso-production و سازماندهی کردن پیکربندی بین محیطهای مختلف به صورت زیر است:
استفاده از کتابخانه پیکربندی برای اپلیکیشن Node.js
این کتابخانه امکان ذخیرهسازی پیکربندی در یک مکان متمرکز و اُوراید کردن آن به روشهای مختلف مانند فایلهای env. یا متغیرهای محیطی را فراهم میسازد. توصیه میشود بدین منظور از ابزار convict که از سوی موزیلا ارائه شده استفاده کنید.
بدین ترتیب تنها چیزی که هنگام اجرای اپلیکیشن Node در محیطهای مختلف باید تغییر یابد یک فایل env. یا فهرستی از متغیرهای محیطی است. در مثال ما این پیکربندی باید شامل دست کم اطلاعات اتصال MySQL و Redis باشد.
تعریف کردن کل پیکربندی مجموعه در یک مکان منفرد
این کار در یک اسکریپت محیط منبع دهی شده یا در یک فایل env. ممکن است. روش دوم به دلیل این که باعث میشود از سوی docker-compose نیز قابل خواندن باشد، فرایند را سادهتر میسازد. در مورد مثال مطرح شده، این فایل باید دست کم شامل برخی متغیرها به صورت فایل پیکربندی در اپلیکیشن Node.js باشد.
ایجاد فایل docker-compose.yml با استفاده از متغیرها
Docker Compose میتواند جایگزین متغیرهای محیطی در فایل پیکربندی شود (+). این وضعیت در مواردی که یک فایل docker-compose منفرد در همه محیطها وجود داشته باشد، آسانتر است. تنها تفاوت بین محیطهای توسعه و پروداکشن این است که در محیط توسعه از یک Dockerfile متفاوت برای اپلیکیشن Node.js استفاده کردهایم و از این رو میتوانیم تغییرات Dockerfile را به صورت بارگذاری زنده در مورد کد داشته باشیم. در ادامه فایلهای docker-compose.yml و docker-compose.dev.yml، فایل env. و Dockerfile برای محیط توسعه ارائه شدهاند.
فایل env. به صورت زیر است:
MYSQL_ROOT_PASSWORD=123456 MYSQL_DATABASE=database APP_HOST=app.test.localhost.tv
فایل Dockerfile-env به صورت زیر است:
FROM node:9-alpine WORKDIR /home/node/app # Install deps COPY./package*./ RUN npm install && \ npm cache clean --force COPY.. # Expose ports (for orchestrators and dynamic reverse proxies) EXPOSE 3000 # Start the app CMD npm start
فایل docker-compose.yml به صورت زیر است:
version: '3' services: reverse-proxy: image: traefik # The official Traefik docker image command: --api --docker.exposedbydefault=false # Enables the web UI and tells Træfik to listen to docker, without exposing by default ports: - "80:80" # The HTTP port - "8080:8080" # The Web UI (enabled by --api) volumes: - /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events db: image: mysql:5 restart: always environment: - MYSQL_ROOT_PASSWORD - MYSQL_DATABASE redis: image: redis:alpine app: build: . environment: - DB_HOST=db - DB_NAME=${MYSQL_DATABASE} - DB_USER=root - DB_PASSWORD=${MYSQL_ROOT_PASSWORD} - REDIS_HOST=redis labels: - "traefik.enable=true" - "traefik.frontend.rule=Host:${APP_HOST}" depends_on: - db - redis
فایل docker-compose.dev.yml به صورت زیر است:
version: '3' services: app: build: dockerfile: Dockerfile-dev context: . command: npm run dev volumes: - "./src:/home/node/app/src"
در بخش app فایل میبینید که از localhost.tv استفاده کردهایم که یک سرور DNS ریموت مناسب است که همه موارد localhost.tv.* را به localhost شما متصل میکند. دلیل استفاده از آن این است که از مسیر نسبی برای نقطه انتهایی اپلیکیشن (مانند localhost/api) استفاده نکنیم، چون این حالت همواره هنگام انتقال به محیط پروداکشن با عوارض جانبی همراه است. برای نمونه لینکهای جاسازی شده، مسیریابی درونی و موارد دیگر با مشکل مواجه میشوند.
یک Dockerfile مجزا برای تصویر توسعه کمی آزاردهنده به نظر میرسد، چون باعث میشود پیکربندی توسعه همانند پیکربندی پروداکشن نباشد و از این رو در زمان توزیع اپلیکیشن در محیط دیگر به مقداری کار (و ریسک) نیاز دارد. تا به اکنون تنها راهحل برای این موضوع استفاده از یک سیستم قالببندی برای دینامیک ساختن Dockerfile بوده است. منظور از قالببندی، یک اسکریپت ساده یا پیچیده است که ابزارهایی مانند ansible را ارائه میکند.
زمانی که همه فایلها تنظیم شدند، میتوانید با استفاده از دستور زیر، مجموعه (Stack) خود را در محیط توسعه اجرا کنید. دقت کنید که ابتدا باید کانتینر اپلیکیشن را از فایل Dockerfile-dev بسازید:
docker-compose -f docker-compose.yml -f docker-compose.dev.yml build
سپس مجموعه را با دستور زیر اجرا کنید:
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up –d
اینک شما یک فایل زمان اجرا به صورت محیط توسعه داکر سازی شده و با پروکسی معکوس به صورت iso-production دارید که Node.js در آن به صورت زنده بارگذاری مجدد میشود. مثال کامل را میتوانید در این آدرس (+) ملاحظه کنید.
3. تحویل بیدردسر و یکپارچهسازی با CI/CD
اینک که یک محیط اپلیکیشن پرتابل و قابل سفارشیسازی داریم، میتوانیم از آن برای همه مراحل پیوسته یکپارچهسازی و توزیع استفاده کنیم. مراحل مورد نیاز برای هر پروژه برای تست با استفاده از داکر و Node.js به صورت زیر است:
تستهای واحد را با ساختن تصویر داکر اجرا کنید. همچنین میتوانید به این منظور یک تصویر سفارشی به صورت زیر بسازید:
# Use the builder image as base image FROM builder # Copy the test files COPY tests tests Override the NODE_ENV environment variable to 'dev', in order to get required test packages ENV NODE_ENV dev # 1. Get test packages; AND # 2. Install our test framework - mocha RUN npm update && \ npm install -g mocha # Override the command, to run the test instead of the application CMD ["mocha", "tests/test.js", "--reporter", "spec"]
در ادامه میتوانید مقدار بازگشتی تابع اجرای داکر را برای تشخیص این که CI pipeline قابل اجرا است یا نه بررسی کنید:
- تستهای یکپارچهسازی را با استفاده از docker-compose درون ابزار CI اجرا کنید. مثلاً میتوانید از docker-compose برای عملیاتی ساختن همه مجموعه استفاده کنید و یک نقطه انتهایی خاص را فراخوانی کنید تا مطمئن شوید که اپلیکیشن Node.js میتواند به طور صحیحی به اجزای مورد نیاز مانند پایگاه داده و ردیس دسترسی یابد.
- تستهای API را با استفاده از docker-compose درون ابزار CI اجرا کنید. همچنین میتوانید از ابزارهایی مانند Sequelize (+) برای وارد کردن دادهها درون پایگاه داده پیش از تست استفاده کنید.
همه این مراحل را در صورتی که امکان اجرای یک محیط داکرسازی شده وجود داشته باشد، درون ارائهدهنده CI مانند Jenkins، Gitlab-CI و Travis اجرا کنید. برای نمونه در Gitlab-CI میتوانید این تصویر (+) را اجرا کنید. تصویر فوق یک داکر درون تصویر داکر است که شامل docker-compose است.
سخن پایانی
امیدوارم مطالبی که در این مقاله ارائه شدند، برای افرادی که قصد دارند از داکر برای اپلیکیشنهای مبتنی بر Node.js در محیطهای توسعه و پروداکشن استفاده کنند، مفید باشند. این نوشته به هیچ وجه فهرست جامعی از الزامات محسوب نمیشود؛ اما قصد ما پیشنهاد یک چشمانداز در مورد شیوه استفاده از ابزارهای کانتینر در جهت بهبود فرایند ساخت اپلیکیشنهای مدرن بوده است.
اگر این مطلب برایتان مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- آموزش داکر (Docker) برای توسعه دهندگان
- مجموعه آموزشهای مهندسی نرم افزار
- اکوسیستم داکر (Docker) — کامپوننت های رایج
- شبکه بندی و ارتباط ها در اکوسیستم داکر — راهنمای جامع
- زمان بندی و هماهنگی (Orchestration) در اکوسیستم داکر – راهنمای جامع
==
مقاله ی بسیار بسیار کامل و کاربردی ای بود
ممنونم از شما