Giữ lại một ứng dụng Node.js để phát triển với Docker Compose
Nếu bạn đang tích cực phát triển một ứng dụng, việc sử dụng Docker có thể đơn giản hóa quy trình làm việc và quy trình triển khai ứng dụng của bạn vào production . Làm việc với containers đang phát triển mang lại những lợi ích sau:- Các môi trường nhất quán, nghĩa là bạn có thể chọn ngôn ngữ và phụ thuộc bạn muốn cho dự án của bạn mà không cần lo lắng về xung đột hệ thống.
- Các môi trường được tách biệt, giúp khắc phục sự cố dễ dàng hơn và giới thiệu các thành viên mới trong group .
- Môi trường có tính di động, cho phép bạn đóng gói và chia sẻ mã của bạn với người khác.
Hướng dẫn này sẽ chỉ cho bạn cách cài đặt môi trường phát triển cho ứng dụng Node.js bằng Docker. Bạn sẽ tạo hai containers - một cho ứng dụng Node và một cho database MongoDB - với Docker Compose . Vì ứng dụng này hoạt động với Node và MongoDB, cài đặt của ta sẽ thực hiện như sau:
- Đồng bộ hóa mã ứng dụng trên server với mã trong containers để tạo điều kiện thay đổi trong quá trình phát triển.
- Đảm bảo rằng các thay đổi đối với mã ứng dụng hoạt động mà không cần khởi động lại.
- Tạo database được bảo vệ bằng password và user cho dữ liệu của ứng dụng.
- Kiên trì dữ liệu này.
Ở cuối hướng dẫn này, bạn sẽ có một ứng dụng thông tin cá mập đang hoạt động chạy trên containers Docker:
Yêu cầu
Để làm theo hướng dẫn này, bạn cần :
-  Một  server  phát triển chạy Ubuntu 18.04, cùng với một  user  không phải root có  quyền  sudovà một firewall đang hoạt động. Để được hướng dẫn về cách cài đặt những điều này, vui lòng xem hướng dẫn Cài đặt Server Ban đầu này.
- Docker được cài đặt trên server của bạn, làm theo các Bước 1 và 2 của Cách cài đặt và sử dụng Docker trên Ubuntu 18.04 .
- Docker Compose được cài đặt trên server của bạn, làm theo Bước 1 của Cách cài đặt Docker Compose trên Ubuntu 18.04 .
Bước 1 - Nhân bản dự án và sửa đổi dependencies
 Bước đầu tiên trong việc xây dựng  cài đặt  này sẽ là sao chép mã dự án và sửa đổi file  package.json của nó, bao gồm các phụ thuộc của dự án.  Ta  sẽ bổ sung thêm nodemon tới của dự án devDependencies , chỉ rõ rằng  ta  sẽ sử dụng nó trong quá trình phát triển. Chạy ứng dụng với nodemon  đảm bảo  nó sẽ được tự động khởi động lại  khi  nào bạn  áp dụng các thay đổi  đối với mã  của bạn .
 Đầu tiên, sao chép kho lưu trữ nodejs-mongo-mongoose từ tài khoản GitHub Cộng đồng DigitalOcean . Kho lưu trữ này bao gồm mã từ  cài đặt  được mô tả trong Cách tích hợp MongoDB với ứng dụng Node của bạn , phần này giải thích cách tích hợp database  MongoDB với ứng dụng Node hiện có bằng Mongoose .
 Sao node_project repository  vào một folder  có tên là node_project :
- git clone https://github.com/do-community/nodejs-mongo-mongoose.git node_project 
Điều hướng đến folder  node_project :
- cd node_project 
Mở file  package.json của dự án bằng nano   hoặc editor bạn quen dùng  :
- nano package.json 
Bên dưới các phần phụ thuộc của dự án và phía trên dấu ngoặc nhọn đóng, hãy tạo một đối tượng devDependencies mới bao gồm các nodemon :
... "dependencies": {     "ejs": "^2.6.1",     "express": "^4.16.4",     "mongoose": "^5.4.10"   },   "devDependencies": {     "nodemon": "^1.18.10"   }     } Lưu file khi bạn hoàn tất chỉnh sửa.
Với mã dự án tại chỗ và các phụ thuộc của nó đã được sửa đổi, bạn có thể chuyển sang cấu trúc lại mã cho một quy trình làm việc được tích hợp.
Bước 2 - Cấu hình ứng dụng của bạn để hoạt động với containers
Sửa đổi ứng dụng của ta cho một quy trình làm việc được chứa nghĩa là làm cho mã của ta trở nên module hơn. Các containers cung cấp khả năng di động giữa các môi trường và mã của ta phải phản ánh điều đó bằng cách được tách rời khỏi hệ điều hành cơ bản nhất có thể. Để làm điều này, ta sẽ cấu trúc lại mã của bạn để sử dụng nhiều hơn thuộc tính process.env của Node, thuộc tính này sẽ trả về một đối tượng với thông tin về môi trường user của bạn trong thời gian chạy. Ta có thể sử dụng đối tượng này trong mã của bạn để gán động thông tin cấu hình trong thời gian chạy với các biến môi trường.
 Hãy bắt đầu với app.js , điểm nhập ứng dụng chính của  ta . Mở tập tin:
- nano app.js 
Bên trong, bạn sẽ thấy định nghĩa cho hằng số port , cũng như hàm listen sử dụng hằng số này để chỉ định cổng mà ứng dụng sẽ lắng nghe:
... const port = 8080; ... app.listen(port, function () {   console.log('Example app listening on port 8080!'); }); Hãy xác định lại hằng số port để cho phép gán động trong thời gian chạy bằng cách sử dụng đối tượng process.env .  áp dụng các thay đổi  sau đối với định nghĩa không đổi và hàm listen :
... const port = process.env.PORT || 8080; ... app.listen(port, function () {   console.log(`Example app listening on ${port}!`); }); Định nghĩa hằng số mới của  ta  chỉ định port động bằng cách sử dụng giá trị được truyền vào lúc chạy hoặc 8080 . Tương tự như vậy,  ta  đã viết lại hàm listen để sử dụng một ký tự mẫu , sẽ nội suy giá trị cổng khi lắng nghe các kết nối. Bởi vì  ta  sẽ ánh xạ các cổng  của bạn  ở nơi khác, những bản sửa đổi này sẽ ngăn  ta  phải liên tục sửa đổi file  này khi môi trường của  ta  thay đổi.
Khi bạn hoàn tất chỉnh sửa, hãy lưu file .
 Tiếp theo,  ta  sẽ sửa đổi thông tin kết nối database   của bạn  để xóa bất kỳ thông tin đăng nhập cấu hình nào. Mở file  db.js , chứa thông tin sau:
- nano db.js 
Hiện tại, file thực hiện những việc sau:
- Nhập Mongoose, Trình lập bản đồ tài liệu đối tượng (ODM) mà ta đang sử dụng để tạo schemas và mô hình cho dữ liệu ứng dụng của ta .
- Đặt thông tin xác thực database làm hằng số, bao gồm tên user và password .
-  Kết nối với database  bằng phương thức mongoose.connect.
Để biết thêm thông tin về file , vui lòng xem Bước 3 của Cách tích hợp MongoDB với Ứng dụng Node của bạn .
Bước đầu tiên của ta khi sửa đổi file sẽ là xác định lại các hằng số bao gồm thông tin nhạy cảm. Hiện tại, các hằng số này trông giống như sau:
... const MONGO_USERNAME = 'sammy'; const MONGO_PASSWORD = 'your_password'; const MONGO_HOSTNAME = '127.0.0.1'; const MONGO_PORT = '27017'; const MONGO_DB = 'sharkinfo'; ... Thay vì mã hóa cứng thông tin này, bạn có thể sử dụng đối tượng process.env để nắm bắt các giá trị thời gian chạy cho các hằng số này. Sửa đổi khối để trông như thế này:
... const {   MONGO_USERNAME,   MONGO_PASSWORD,   MONGO_HOSTNAME,   MONGO_PORT,   MONGO_DB } = process.env; ... Lưu file khi bạn hoàn tất chỉnh sửa.
  Đến đây,  bạn đã sửa đổi db.js để hoạt động với các biến môi trường của ứng dụng, nhưng bạn vẫn cần một cách để chuyển các biến này vào ứng dụng  của bạn . Hãy tạo một file  .env với các giá trị mà bạn có thể chuyển vào ứng dụng  của bạn  trong thời gian chạy.
Mở tập tin:
- nano .env 
Tệp này sẽ bao gồm thông tin mà bạn đã xóa khỏi db.js : tên  user  và password  cho database  ứng dụng của bạn, cũng như cài đặt cổng và tên database . Hãy nhớ cập nhật tên  user , password  và tên database  được liệt kê ở đây với thông tin  của bạn :
MONGO_USERNAME=sammy MONGO_PASSWORD=your_password MONGO_PORT=27017 MONGO_DB=sharkinfo  Lưu ý   ta  đã xóa cài đặt  server  lưu trữ ban đầu xuất hiện trong db.js Bây giờ  ta  sẽ xác định  server   của bạn  ở cấp file  Docker Compose, cùng với thông tin khác về các dịch vụ và containers  của  ta .
Lưu file này khi bạn hoàn tất chỉnh sửa.
 Vì file  .env của bạn có chứa thông tin nhạy cảm,  bạn cần   đảm bảo  nó  có trong  các .dockerignore và .gitignore của dự án để nó không sao chép vào containers  hoặc điều khiển version  của bạn.
 Mở file  .dockerignore của bạn:
- nano .dockerignore 
Thêm dòng sau vào cuối file :
... .gitignore .env Lưu file khi bạn hoàn tất chỉnh sửa.
 Tệp .gitignore trong repository  lưu trữ này đã bao gồm .env , nhưng hãy kiểm tra xem nó có ở đó không:
- nano .gitignore 
... .env ... Đến đây, bạn đã extract thành công thông tin nhạy cảm từ mã dự án của bạn và thực hiện các biện pháp để kiểm soát cách thức và vị trí thông tin này được sao chép. Như vậy, bạn có thể tăng cường độ mạnh mẽ hơn cho mã kết nối database của bạn để tối ưu hóa nó cho quy trình làm việc được tích hợp.
Bước 3 - Sửa đổi Cài đặt Kết nối Database
Bước tiếp theo của ta sẽ là làm cho phương thức kết nối database của ta mạnh mẽ hơn bằng cách thêm mã xử lý các trường hợp ứng dụng của ta không kết nối được với database của ta . Giới thiệu mức độ phục hồi này cho mã ứng dụng của bạn là một phương pháp được khuyến khích khi làm việc với containers bằng Soạn thư.
 Mở db.js để chỉnh sửa:
- nano db.js 
Bạn sẽ thấy mã mà  ta  đã thêm trước đó, cùng với hằng số url cho URI kết nối của Mongo và phương thức connect Mongoose :
... const {   MONGO_USERNAME,   MONGO_PASSWORD,   MONGO_HOSTNAME,   MONGO_PORT,   MONGO_DB } = process.env;  const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;  mongoose.connect(url, {useNewUrlParser: true}); Hiện tại, phương thức connect của  ta  chấp nhận một tùy chọn yêu cầu Mongoose sử dụng trình phân tích cú pháp URL mới của Mongo. Hãy thêm một vài tùy chọn khác vào phương thức này để xác định các tham số cho các nỗ lực kết nối lại.  Ta  có thể làm điều này bằng cách tạo một hằng số options bao gồm thông tin liên quan, ngoài tùy chọn phân tích cú pháp URL mới. Bên dưới hằng số Mongo của bạn, hãy thêm định nghĩa sau cho hằng số options :
... const {   MONGO_USERNAME,   MONGO_PASSWORD,   MONGO_HOSTNAME,   MONGO_PORT,   MONGO_DB } = process.env;  const options = {   useNewUrlParser: true,   reconnectTries: Number.MAX_VALUE,   reconnectInterval: 500,    connectTimeoutMS: 10000, }; ... Các reconnectTries tùy chọn bảo Mongoose để tiếp tục cố gắng để kết nối vô thời hạn, trong khi reconnectInterval xác định khoảng thời gian giữa những nỗ lực kết nối trong mili giây. connectTimeoutMS xác định 10 giây là khoảng thời gian mà trình điều khiển Mongo sẽ đợi trước khi kết nối thất bại.
 Bây giờ  ta  có thể sử dụng hằng số options mới trong phương thức connect Mongoose để tinh chỉnh cài đặt kết nối Mongoose  của bạn .  Ta  cũng sẽ thêm một lời hứa để xử lý các lỗi kết nối tiềm ẩn.
 Hiện tại, phương thức connect Mongoose trông giống như sau:
... mongoose.connect(url, {useNewUrlParser: true}); Xóa phương thức connect hiện có và thay thế nó bằng mã sau, bao gồm hằng số options và một lời hứa:
... mongoose.connect(url, options).then( function() {   console.log('MongoDB is connected'); })   .catch( function(err) {   console.log(err); }); Trong trường hợp kết nối thành công, chức năng của  ta  ghi lại một thông báo thích hợp; nếu không nó sẽ catch và ghi lại lỗi, cho phép  ta  khắc phục sự cố.
Tệp đã hoàn thành sẽ giống như sau:
const mongoose = require('mongoose');  const {   MONGO_USERNAME,   MONGO_PASSWORD,   MONGO_HOSTNAME,   MONGO_PORT,   MONGO_DB } = process.env;  const options = {   useNewUrlParser: true,   reconnectTries: Number.MAX_VALUE,   reconnectInterval: 500,   connectTimeoutMS: 10000, };  const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;  mongoose.connect(url, options).then( function() {   console.log('MongoDB is connected'); })   .catch( function(err) {   console.log(err); }); Lưu file khi bạn đã chỉnh sửa xong.
Bây giờ, bạn đã thêm khả năng phục hồi vào mã ứng dụng của bạn để xử lý các trường hợp ứng dụng của bạn có thể không kết nối được với database của bạn. Với mã này, bạn có thể chuyển sang xác định các dịch vụ của bạn với Soạn thư.
Bước 4 - Xác định Dịch vụ với Docker Compose
 Với mã của bạn đã được cấu trúc lại, bạn đã sẵn sàng để ghi file  docker-compose.yml với các định nghĩa dịch vụ  của bạn . Dịch vụ trong Soạn là một containers  đang chạy và các định nghĩa dịch vụ - mà bạn sẽ đưa vào file  docker-compose.yml - chứa thông tin về cách mỗi  containers images   sẽ chạy. Công cụ Soạn thư cho phép bạn xác định nhiều dịch vụ để xây dựng các ứng dụng đa containers .
 Tuy nhiên, trước khi xác định các dịch vụ  của bạn ,  ta  sẽ thêm một công cụ vào dự án  của bạn  có tên là wait-for  đảm bảo  rằng ứng dụng của  ta  chỉ cố gắng kết nối với database  của  ta  khi  các việc  khởi động database  hoàn tất. Tập lệnh  shell  bọc này sử dụng netcat để thăm dò xem  server  và cổng cụ thể có chấp nhận kết nối TCP hay không. Sử dụng nó cho phép bạn kiểm soát nỗ lực của ứng dụng để kết nối với database  của bạn bằng cách kiểm tra xem database  đã sẵn sàng chấp nhận kết nối hay chưa.
 Mặc dù tính năng Soạn thư cho phép bạn chỉ định dependencies  giữa các dịch vụ bằng cách sử dụng tùy chọn depends_on , thứ tự này dựa trên việc containers  có đang chạy hay không chứ không phải là tính sẵn sàng của nó. Việc sử dụng depends_on sẽ không tối ưu cho  cài đặt  của  ta , vì  ta  muốn ứng dụng  của bạn  chỉ kết nối khi  các việc  khởi động database , bao gồm thêm  user  và password  vào database  xác thực admin , hoàn tất. Để biết thêm thông tin về cách sử dụng wait-for và các công cụ khác để kiểm soát thứ tự khởi động, vui lòng xem các khuyến nghị liên quan trong tài liệu Soạn .
 Mở một file  có tên là wait-for.sh :
- nano wait-for.sh 
Dán mã sau vào file để tạo chức năng bỏ phiếu:
#!/bin/sh  # original script: https://github.com/eficode/wait-for/blob/master/wait-for  TIMEOUT=15 QUIET=0  echoerr() {   if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi }  usage() {   exitcode="$1"   cat << USAGE >&2 Usage:   $cmdname host:port [-t timeout] [-- command args]   -q | --quiet                        Do not output any status messages   -t TIMEOUT | --timeout=timeout      Timeout in seconds, zero for no timeout   -- COMMAND ARGS                     Execute command with args after the test finishes USAGE   exit "$exitcode" }  wait_for() {   for i in `seq $TIMEOUT` ; do     nc -z "$HOST" "$PORT" > /dev/null 2>&1      result=$?     if [ $result -eq 0 ] ; then       if [ $# -gt 0 ] ; then         exec "$@"       fi       exit 0     fi     sleep 1   done   echo "Operation timed out" >&2   exit 1 }  while [ $# -gt 0 ] do   case "$1" in     *:* )     HOST=$(printf "%s\n" "$1"| cut -d : -f 1)     PORT=$(printf "%s\n" "$1"| cut -d : -f 2)     shift 1     ;;     -q | --quiet)     QUIET=1     shift 1     ;;     -t)     TIMEOUT="$2"     if [ "$TIMEOUT" = "" ]; then break; fi     shift 2     ;;     --timeout=*)     TIMEOUT="${1#*=}"     shift 1     ;;     --)     shift     break     ;;     --help)     usage 0     ;;     *)     echoerr "Unknown argument: $1"     usage 1     ;;   esac done  if [ "$HOST" = "" -o "$PORT" = "" ]; then   echoerr "Error: you need to provide a host and port to test."   usage 2 fi  wait_for "$@" Lưu file khi bạn hoàn tất việc thêm mã.
Làm cho tập lệnh có thể thực thi:
- chmod +x wait-for.sh 
Tiếp theo, mở file  docker-compose.yml :
- nano docker-compose.yml 
Đầu tiên, xác định dịch vụ ứng dụng nodejs bằng cách thêm mã sau vào file :
version: '3'  services:   nodejs:     build:       context: .       dockerfile: Dockerfile     image: nodejs     container_name: nodejs     restart: unless-stopped     env_file: .env     environment:       - MONGO_USERNAME=$MONGO_USERNAME       - MONGO_PASSWORD=$MONGO_PASSWORD       - MONGO_HOSTNAME=db       - MONGO_PORT=$MONGO_PORT       - MONGO_DB=$MONGO_DB      ports:       - "80:8080"     volumes:       - .:/home/node/app       - node_modules:/home/node/app/node_modules     networks:       - app-network     command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js Định nghĩa dịch vụ nodejs bao gồm các tùy chọn sau:
-  build: Tùy chọn này xác định các tùy chọn cấu hình, bao gồmcontextvàdockerfile, sẽ được áp dụng khi Compose xây dựng hình ảnh ứng dụng. Nếu bạn muốn sử dụng hình ảnh hiện có từ một nơi đăng ký như Docker Hub , bạn có thể sử dụng hướng dẫnimagethay thế, với thông tin về tên user , repository và thẻ hình ảnh của bạn.
-  context: Điều này xác định bối cảnh xây dựng cho bản dựng hình ảnh - trong trường hợp này là folder dự án hiện tại.
-  dockerfile: Điều này chỉ địnhDockerfiletrong folder dự án hiện tại của bạn làm file Compose sẽ sử dụng để xây dựng hình ảnh ứng dụng. Để biết thêm thông tin về file này, vui lòng xem Cách tạo ứng dụng Node.js bằng Docker .
-  image,container_name: Các tên này áp dụng cho hình ảnh và containers .
-  restart: Điều này xác định policy khởi động lại. Mặc định làno, nhưng ta đã đặt containers khởi động lại trừ khi nó bị dừng.
-  env_file: Điều này cho Soạn biết rằng ta muốn thêm các biến môi trường từ một file có tên là.env, nằm trong ngữ cảnh xây dựng của ta .
-  environment: Sử dụng tùy chọn này cho phép bạn thêm cài đặt kết nối Mongo mà bạn đã xác định trong file.env. Lưu ý ta không đặtNODE_ENVđểdevelopment, vì đây là hành vi mặc định của Express nếuNODE_ENVkhông được đặt. Khi chuyển sang production , bạn có thể đặt cài đặt này thànhproductionđể bật chế độ xem bộ nhớ đệm và ít thông báo lỗi dài dòng hơn . Cũng lưu ý ta đã chỉ định containers databasedblàm server lưu trữ, như đã thảo luận trong Bước 2 .
-  ports: Điều này ánh xạ cổng80trên server đến cổng8080trên container .
- volumes: Ta bao gồm hai loại mount ở đây:-  Đầu tiên là một liên kết  mount  gắn mã ứng dụng của  ta  trên  server  lưu trữ vào folder  /home/node/apptrên containers . Điều này sẽ tạo điều kiện phát triển nhanh chóng, vì bất kỳ thay đổi nào bạn thực hiện đối với mã server của bạn sẽ được điền ngay vào containers .
-  Thứ hai là một khối được đặt tên, node_modules. Khi Docker chạynpm installhướng dẫn liệt kê trong đơnDockerfile,npmsẽ tạo ra một mớinode_modulesfolder trên thùng bao gồm các gói cần thiết để chạy các ứng dụng. Tuy nhiên, mount mount mà ta vừa tạo sẽ ẩn foldernode_modulesmới được tạo này. Vìnode_modulestrên server trống, liên kết sẽ ánh xạ một folder trống đến containers , overrides foldernode_modulesmới và ngăn ứng dụng của ta khởi động. Dung lượngnode_modulesđược đặt tên giải quyết vấn đề này bằng cách duy trì nội dung của folder/home/node/app/node_modulesvà gắn nó vào containers , ẩn ràng buộc.
 - Hãy ghi nhớ những điểm sau khi sử dụng phương pháp này : -  Liên kết của bạn sẽ  mount  nội dung của folder  node_modulestrên containers vào server lưu trữ và folder này sẽ thuộc quyền sở hữu củaroot, vì ổ đĩa được đặt tên được tạo bởi Docker.
-  Nếu bạn có folder  node_modulestừ trước trên server , nó sẽ overrides foldernode_modulesđược tạo trên containers . Cài đặt mà ta đang xây dựng trong hướng dẫn này giả định bạn không có foldernode_modulestừ trước và bạn sẽ không làm việc vớinpmtrên server của bạn .Điều này phù hợp với cách tiếp cận mười hai yếu tố để phát triển ứng dụng , giúp giảm thiểu dependencies giữa các môi trường thực thi.
 
-  Đầu tiên là một liên kết  mount  gắn mã ứng dụng của  ta  trên  server  lưu trữ vào folder  
- networks: Điều này chỉ định rằng dịch vụ ứng dụng của ta sẽ tham gia- app-networkmạng, mà ta sẽ xác định ở cuối file .
- command: Tùy chọn này cho phép bạn đặt lệnh sẽ được thực hiện khi Compose chạy hình ảnh. Lưu ý điều này sẽ overrides hướng dẫn- CMDmà ta đặt trong- Dockerfileứng dụng của ta . Ở đây, ta đang chạy ứng dụng bằng tập lệnh- wait-fortập lệnh này sẽ thăm dò dịch vụ- dbtrên cổng- 27017để kiểm tra xem dịch vụ database đã sẵn sàng hay chưa. Sau khi kiểm tra mức độ sẵn sàng thành công, tập lệnh sẽ thực thi lệnh mà ta đã đặt,- /home/node/app/node_modules/.bin/nodemon app.js, để khởi động ứng dụng với- nodemon. Điều này sẽ đảm bảo bất kỳ thay đổi nào trong tương lai mà ta thực hiện đối với mã của bạn đều được reload mà ta không phải khởi động lại ứng dụng.
 Tiếp theo, tạo dịch vụ db bằng cách thêm mã sau vào bên dưới định nghĩa dịch vụ ứng dụng:
...   db:     image: mongo:4.1.8-xenial     container_name: db     restart: unless-stopped     env_file: .env     environment:       - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME       - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD     volumes:         - dbdata:/data/db        networks:       - app-network   Một số cài đặt  ta  đã xác định cho dịch vụ nodejs vẫn giữ nguyên, nhưng  ta  cũng đã  áp dụng các thay đổi  sau đối với định nghĩa image , environment và volumes :
-  image: Để tạo dịch vụ này, Compose sẽ lấy hình ảnh4.1.8-xenialMongo từ Docker Hub. Ta đang ghim một version cụ thể để tránh xung đột có thể xảy ra trong tương lai khi hình ảnh Mongo thay đổi. Để biết thêm thông tin về ghim version , vui lòng xem tài liệu Docker về các phương pháp hay nhất của Dockerfile .
-  MONGO_INITDB_ROOT_USERNAME,MONGO_INITDB_ROOT_PASSWORD: Hình ảnhmongocung cấp các biến môi trường này để bạn có thể sửa đổi quá trình khởi tạo version database của bạn .MONGO_INITDB_ROOT_USERNAMEvàMONGO_INITDB_ROOT_PASSWORDcùng nhau tạo userroottrong database xác thựcadminvà đảm bảo xác thực được bật khi containers khởi động. Ta đã đặtMONGO_INITDB_ROOT_USERNAMEvàMONGO_INITDB_ROOT_PASSWORDbằng cách sử dụng các giá trị từ file.envcủa ta , ta chuyển sang dịch vụdbbằng tùy chọnenv_file. Làm điều này nghĩa là user ứng dụngsammycủa ta sẽ là userroottrên cá thể database , có quyền truy cập vào tất cả các quyền quản trị và hoạt động của role đó. Khi làm việc trong production , bạn cần tạo một user ứng dụng chuyên dụng với các quyền trong phạm vi phù hợp. Lưu ý: Lưu ý các biến này sẽ không có hiệu lực nếu bạn khởi động containers với một folder dữ liệu hiện có tại chỗ.
-  dbdata:/data/db:dbdataổ đĩa được đặt tên sẽ giữ nguyên dữ liệu được lưu trữ trong thư mục dữ liệu mặc định của Mongo,/data/db. Điều này sẽ đảm bảo bạn không bị mất dữ liệu trong trường hợp bạn dừng hoặc xóa containers .
  Ta  cũng đã thêm dịch vụ db vào app-network mạng với tùy chọn networks .
Bước cuối cùng, hãy thêm định nghĩa dung lượng và mạng vào cuối file :
... networks:   app-network:     driver: bridge  volumes:   dbdata:   node_modules:   Mạng app-network mạng cầu nối do  user  xác định cho phép giao tiếp giữa các containers  của  ta  vì chúng nằm trên cùng một  server  Docker daemon. Điều này hợp lý hóa lưu lượng truy cập và giao tiếp trong ứng dụng, vì nó mở tất cả các cổng giữa các container trên cùng một mạng cầu, đồng thời không để lộ cổng nào ra thế giới bên ngoài. Do đó, các containers  db và nodejs của  ta  có thể giao tiếp với nhau và  ta  chỉ cần để lộ cổng 80 để truy cập front-end vào ứng dụng.
 Cấp cao nhất của  ta  volumes định nghĩa then chốt  dung lượng  dbdata và node_modules . Khi Docker tạo tập, nội dung của tập được lưu trữ trong một phần của hệ thống file   server , /var/lib/docker/volumes/ , được Docker quản lý. Nội dung của mỗi tập được lưu trữ trong một folder  dưới /var/lib/docker/volumes/ và được gắn vào bất kỳ containers  nào sử dụng tập. Bằng cách này, dữ liệu thông tin cá mập mà  user  của  ta  sẽ tạo sẽ tồn tại trong ổ đĩa dbdata ngay cả khi  ta  xóa và tạo lại containers  db .
 Tệp docker-compose.yml đã hoàn thành sẽ giống như sau:
version: '3'  services:   nodejs:     build:       context: .       dockerfile: Dockerfile     image: nodejs     container_name: nodejs     restart: unless-stopped     env_file: .env     environment:       - MONGO_USERNAME=$MONGO_USERNAME       - MONGO_PASSWORD=$MONGO_PASSWORD       - MONGO_HOSTNAME=db       - MONGO_PORT=$MONGO_PORT       - MONGO_DB=$MONGO_DB     ports:       - "80:8080"     volumes:       - .:/home/node/app       - node_modules:/home/node/app/node_modules     networks:       - app-network     command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js     db:     image: mongo:4.1.8-xenial     container_name: db     restart: unless-stopped     env_file: .env     environment:       - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME       - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD     volumes:            - dbdata:/data/db     networks:       - app-network    networks:   app-network:     driver: bridge  volumes:   dbdata:   node_modules:   Lưu file khi bạn hoàn tất chỉnh sửa.
Với các định nghĩa dịch vụ của bạn, bạn đã sẵn sàng để bắt đầu ứng dụng.
Bước 5 - Kiểm tra ứng dụng
 Với file  docker-compose.yml của bạn tại chỗ, bạn có thể tạo các dịch vụ  của bạn  bằng lệnh docker-compose up . Bạn cũng có thể kiểm tra xem dữ liệu  của bạn  có tồn tại hay không bằng cách dừng và xóa containers  bằng docker-compose down .
 Đầu tiên, xây dựng các  containers images   và tạo các dịch vụ bằng cách chạy docker-compose up với cờ -d , sau đó sẽ chạy các containers  nodejs và db trong nền:
- docker-compose up -d 
Bạn sẽ thấy kết quả xác nhận các dịch vụ của bạn đã được tạo:
Output... Creating db ... done Creating nodejs ... done Bạn cũng có thể nhận được thông tin chi tiết hơn về các quy trình khởi động bằng cách hiển thị kết quả log từ các dịch vụ:
- docker-compose logs  
Bạn sẽ thấy thông tin như thế này nếu mọi thứ đã bắt đầu đúng :
Output... nodejs    | [nodemon] starting `node app.js` nodejs    | Example app listening on 8080! nodejs    | MongoDB is connected ... db        | 2019-02-22T17:26:27.329+0000 I ACCESS   [conn2] Successfully authenticated as principal sammy on admin Bạn cũng có thể kiểm tra trạng thái của các containers   của bạn  bằng docker-compose ps :
- docker-compose ps 
Bạn sẽ thấy kết quả cho biết rằng các containers của bạn đang chạy:
Output Name               Command               State          Ports         ---------------------------------------------------------------------- db       docker-entrypoint.sh mongod      Up      27017/tcp            nodejs   ./wait-for.sh db:27017 --  ...   Up      0.0.0.0:80->8080/tcp Khi các dịch vụ của bạn đang chạy, bạn có thể truy cập http:// your_server_ip trong trình duyệt. Bạn sẽ thấy một trang đích giống như sau: 
Nhấp vào nút Nhận thông tin cá mập . Bạn sẽ thấy một trang có mẫu mục nhập, nơi bạn có thể nhập tên cá mập và mô tả về đặc điểm chung của con cá mập đó:
 Trong biểu mẫu, hãy thêm một con cá mập mà bạn chọn. Với mục đích của phần trình diễn này,  ta  sẽ thêm Megalodon Shark vào trường Shark Name và Ancient vào trường Shark Character : 
Bấm vào nút Gửi . Bạn sẽ thấy một trang với thông tin cá mập này được hiển thị lại cho bạn:
Bước cuối cùng, ta có thể kiểm tra xem dữ liệu bạn vừa nhập sẽ tồn tại nếu bạn xóa containers database của bạn .
Quay lại terminal của bạn, nhập lệnh sau để dừng và xóa containers và mạng của bạn:
- docker-compose down 
 Lưu ý   ta  không bao gồm tùy chọn --volumes ; do đó,  dung lượng  dbdata của  ta  không bị xóa.
Kết quả sau xác nhận containers và mạng của bạn đã bị xóa:
OutputStopping nodejs ... done Stopping db     ... done Removing nodejs ... done Removing db     ... done Removing network node_project_app-network Tạo lại các containers :
- docker-compose up -d 
Bây giờ quay trở lại biểu mẫu thông tin cá mập:
 Nhập một con cá mập mới mà bạn chọn.  Ta  sẽ đi với Whale Shark và Large : 
Khi bạn nhấp vào Gửi , bạn sẽ thấy rằng con cá mập mới đã được thêm vào bộ sưu tập cá mập trong database của bạn mà không làm mất dữ liệu bạn đã nhập:
Ứng dụng của bạn hiện đang chạy trên containers Docker với tính năng ổn định dữ liệu và đồng bộ hóa mã được bật.
Kết luận
  Theo  hướng dẫn này, bạn đã tạo  cài đặt  phát triển cho ứng dụng Node  của bạn  bằng cách sử dụng containers  Docker. Bạn đã làm cho dự án  của bạn  có tính module  và di động hơn bằng cách   extract   thông tin nhạy cảm và tách trạng thái của ứng dụng khỏi mã ứng dụng của bạn. Bạn cũng đã  cấu hình  file  docker-compose.yml mà bạn có thể sửa đổi khi nhu cầu phát triển và yêu cầu của bạn thay đổi.
Khi bạn phát triển, bạn có thể quan tâm đến việc tìm hiểu thêm về cách thiết kế các ứng dụng cho quy trình làm việc được chứa trong container và Cloud Native . Vui lòng xemỨng dụng kiến trúc cho Kubernetes và Ứng dụng hiện đại hóa cho Kubernetes để biết thêm thông tin về các chủ đề này.
Để tìm hiểu thêm về mã được sử dụng trong hướng dẫn này, vui lòng xem Cách tạo ứng dụng Node.js với Docker và Cách tích hợp MongoDB với ứng dụng Node của bạn . Để biết thông tin về cách triển khai ứng dụng Node với Reverse Proxy Nginx bằng cách sử dụng containers , vui lòng xem Cách bảo mật ứng dụng Node.js bị chứa bằng Nginx, Let's Encrypt và Docker Compose .
Các tin liên quan
Cách cài đặt và sử dụng Docker Compose trên CentOS 72019-01-23
Cách sử dụng Traefik làm reverse-proxy cho container Docker trên Debian 9
2019-01-08
Cách thiết lập registry Docker riêng trên Ubuntu 18.04
2019-01-07
Cách thiết lập triển khai nhiều node với Rancher 2.1, Kubernetes và Docker Machine trên Ubuntu 18.04
2019-01-03
Cách tạo ứng dụng Node.js với Docker
2018-11-29
Cách quản lý triển khai nhiều node với Máy Rancher và Docker trên Ubuntu 16.04
2018-10-30
Cách cài đặt và sử dụng Docker trên Ubuntu 16.04
2018-10-19
Cách cung cấp và quản lý server Docker từ xa bằng Máy Docker trên Ubuntu 18.04
2018-10-02
Cách cài đặt và bảo mật OpenFaaS bằng Docker Swarm trên Ubuntu 16.04
2018-09-19
Cách cài đặt Docker Compose trên Debian 9
2018-09-06
 

