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

۳۵۹ بازدید
آخرین به‌روزرسانی: ۲۷ شهریور ۱۴۰۲
زمان مطالعه: ۹ دقیقه
راهنمای استفاده از Docker و Node.js — از صفر تا صد

اغلب افرادی که با داکر کار می‌کنند پس از مدتی به آن عادت می‌کنند و در نهایت طرفدار آن می‌شوند، زیرا داکر موجب بهبود فرایند کارها، به خصوص فرایند ایجاد اپلیکیشن از فاز توسعه تا فاز اجرایی (production) می‌شود. در این مقاله به بررسی رابطه Docker و Node.js می‌پردازیم و در مورد 3 مرحله ایجاد یک اپلیکیشن Node صحبت خواهیم کرد که داکر می‌تواند بهبود زیادی در آن‌ها ایجاد کند:

  1. بهینه‌سازی فرایند خلق اپلیکیشن
  2. نرمال‌سازی محیط‌ها
  3. بهبود یکپارچگی و ارائه

بینش‌هایی که در این راهنما ارائه می‌شوند، در مورد چیزهایی غیر از 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 استفاده می‌کند ارائه شده‌اند:

1{
2    "presets": [
3        "env"
4    ]
5}
1MYSQL_ROOT_PASSWORD=123456
2MYSQL_DATABASE=database
1version: '3'
2
3services:
4  reverse-proxy:
5    image: traefik # The official Traefik docker image
6    command: --api --docker.exposedbydefault=false # Enables the web UI and tells Træfik to listen to docker, without exposing by default
7    ports:
8      - "80:80"     # The HTTP port
9      - "8080:8080" # The Web UI (enabled by --api)
10    volumes:
11      - /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events
12
13  db:
14    image: mysql:5
15    restart: always
16    environment:
17      - MYSQL_ROOT_PASSWORD
18      - MYSQL_DATABASE
19
20  redis:
21    image: redis:alpine
22
23  app:
24    build:
25      dockerfile: Dockerfile-dev
26      context: .
27    command: npm run dev
28    volumes:
29      - "./src:/home/node/app/src"
30    environment:
31      - DB_HOST=db
32      - DB_NAME=${MYSQL_DATABASE}
33      - DB_USER=root
34      - DB_PASSWORD=${MYSQL_ROOT_PASSWORD}
35      - REDIS_HOST=redis
36    labels:
37      - "traefik.enable=true"
38      - "traefik.frontend.rule=Host:app.test.localhost.tv"
39    depends_on:
40      - db
41- redis
1###############################################################################
2# Step 1 : Builder image
3#
4FROM node:9-alpine AS builder
5
6# Define working directory and copy source
7WORKDIR /home/node/app
8COPY . .
9# Install dependencies and build whatever you have to build 
10# (babel, grunt, webpack, etc.)
11RUN npm install && npm run build
12
13###############################################################################
14# Step 2 : Run image
15#
16FROM node:9-alpine
17ENV NODE_ENV=production
18WORKDIR /home/node/app
19
20# Install deps for production only
21COPY ./package* ./
22RUN npm install && \
23    npm cache clean --force
24# Copy builded source from the upper builder stage
25COPY --from=builder /home/node/app/build ./build
26
27# Expose ports (for orchestrators and dynamic reverse proxies)
28EXPOSE 3000
29
30# Start the app
31CMD npm start
1FROM node:9-alpine
2
3WORKDIR /home/node/app
4
5# Install deps
6COPY ./package* ./
7RUN npm install && \
8    npm cache clean --force
9
10COPY . .
11
12# Expose ports (for orchestrators and dynamic reverse proxies)
13EXPOSE 3000
14
15# Start the app
16CMD npm start
1import express from 'express'
2
3const app = express()
4
5app.get('/', function (req, res) {
6  res.send('Hello World!')
7})
8
9app.listen(3000, function () {
10  console.log('Example app listening on port 3000!')
11})
1{
2  "name": "Docker-node",
3  "version": "1.0.0",
4  "description": "",
5  "main": "index.js",
6  "scripts": {
7    "test": "echo \"Error: no test specified\" && exit 1",
8    "clean": "rm -rf build && mkdir build",
9    "build-babel": "babel -d ./build ./src -s",
10    "build": "npm run clean && npm run build-babel",
11    "start": "node ./build/index.js",
12    "dev": "babel-node src/index.js"
13  },
14  "keywords": [],
15  "author": "",
16  "license": "ISC",
17  "devDependencies": {
18    "babel-cli": "^6.26.0",
19    "babel-preset-env": "^1.7.0"
20  },
21  "dependencies": {
22    "express": "^4.16.3"
23  }
24}

2. نرمال‌سازی محیط‌ها با Docker Compose

Docker compose ابزاری است که از سوی داکر برای تعریف کل مجموعه اپلیکیشن ارائه شده است. این تعریف شامل سرویس‌های اپلیکیشن، پایگاه‌های داده، لایه کش و غیره می‌شود که به صورت کانتینرهایی در یک فایل واحد به نام docker-compose.yml مورد اشاره قرار گرفته‌اند و حالت این کانتینرها نیز به صورت منابع زیرساختی (دیسک‌ها، شبکه و غیره) به وسیله CLI مدیریت می‌شود.

نکته جالب در مورد docker-compose این است که اجرای یک محیط شِبه production در محیط توسعه را آسان ساخته است. تصور کنید اپلیکیشنی دارید که شامل کامپوننت‌های زیر است:

  1. یک API در زبان Node.js
  2. ارتباط با پایگاه داده MySQL
  3. استفاده از Redis به عنوان یک لایه کش و نشست
  4. استفاده از 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 در محیط‌های توسعه و پروداکشن استفاده کنند، مفید باشند. این نوشته به هیچ وجه فهرست جامعی از الزامات محسوب نمی‌شود؛ اما قصد ما پیشنهاد یک چشم‌انداز در مورد شیوه استفاده از ابزارهای کانتینر در جهت بهبود فرایند ساخت اپلیکیشن‌های مدرن بوده است.

اگر این مطلب برایتان مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
guillaumejacquart
۱ دیدگاه برای «راهنمای استفاده از Docker و Node.js — از صفر تا صد»

مقاله ی بسیار بسیار کامل و کاربردی ای بود
ممنونم از شما

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *