Nhập môn docker - Dockerizing a Node.js web app and Mysql

Nội dung:

  • Mở đầu
  • Tại sao nên dùng Docker
  • Cài đặt Docker
  • Các thành phần cơ bản của Docker
  • Cài đặt và cấu hình Docker-compose
  • Demo ứng dụng chat with Nodejs and mysql
  • Kết luận
  • Tài liệu tham khảo

Mở đầu

Khi bắt đầu tìm hiểu về docker, mình đã đọc nhiều bài blog và đều có một hoang mang là docker thật sự ghê gớm cùng với nhiều khái niệm khó nuốt. Với người có ít kinh nghiệm làm việc với máy ảo, ít kinh nghiệm về công nghệ ảo hóa thì thật sự đọc qua mấy bài blog vẫn chưa hiểu docker giúp ích gì cho đời.

Đến đây nếu có bạn nào cũng có tình trạng như mình, hay mới bắt đầu tìm hiểu về docker thì cũng đừng hoang mang nhé. Mình sẽ chia sẻ lại một cách chi tiết nhất những gì đã tìm hiểu được, và vận dụng nó như thế nào qua một demo cụ thể. Tất nhiên là bài viết này chỉ mang lại kiến thức cơ bản thôi, để tìm hiểu sâu hơn thì sẽ có các bài viết khác. Bài viết này không trình bày cụ thể các khái niệm hay là cách cài đặt v.v, Nhưng mình sẽ hướng dẫn thông qua các blog mình đã tìm hiểu, Vì đơn giản mình có viết cũng chưa chắc đầy đủ bằng họ. Vậy nên các bạn cứ yên tâm, hy vọng blog này có ích cho bạn.

I. Tại sao nên dùng Docker

Đầu tiên xin trích dẫn 2 blog mình đã đọc qua và cho là nó rất hữu ích cho newbie như mình.

  1. Tôi đã dùng Docker như thế nào?

  2. Docker cho người mới bắt đầu (P1)

Mình tóm tắt lại một chút:

Giả sử bạn search trên mạng có một project viết bằng scala và bạn muốn chạy thử nó. Cách thông thường là cài môi trường phục vụ cho việc chạy project scala rồi get code về rồi chạy. Ngoài cách đó ra thì có cách gì hay hơn không? với lại chỉ muốn chạy nó lên xem như thế nào thôi mà phải cài đặt nhiều thứ trên máy thì mất công quá.
Docker sẽ giúp bạn chỉ với vài dòng lệnh, quá tuyệt vời phải không.

Docker làm được gì

  • Giả lập môi trường trên server ở dưới máy local
  • Dùng thử một hệ điều hành mới
  • Chia sẻ môi trường lập trình cho nhóm làm việc
  • Test song song
  • Test app trên một hệ điều hành khác
  • Thử cấu hình cân bằng tải
  • Thử các tool auto deployment
  • v.v

Sau khi đọc xong 2 bài blog thì các bạn đã có hứng thú tìm hiểu docker chưa. Chúng ta bắt đầu chiến thôi.

II. Cài đặt docker

Các bạn có thể tìm cách cài đặt docker tại trang chủ. hoặc tham khảo bài viết bên dưới. Trường hợp của mình cài trên máy ảo Centos: https://docs.docker.com/engine/installation/linux/docker-ce/centos/

Docker cho người mới bắt đầu (P2)

III. Các thành phần cơ bản của Docker

Cũng thông qua bài viết Docker cho người mới bắt đầu (P2), đến đây có lẽ các bạn đã phần nào hình dung các thành phần cơ bản của docker như Docker images, container, Docker engine, Docker hub (nhớ tạo cho mình 1 account docker hub nhé).

IV. Cài đặt và cấu hình Docker-compose

Bây giờ bạn đã nắm được nhưng kiến thức và thao tác cơ bản với docker rồi chứ. Trước khi trình bày về phần kiến thức hơi nâng cao chút ( Docker-compose) cùng ôn lại một số lệnh docker chút nhé:

https://kipalog.com/posts/Cac-lenh-co-ban-voi-docker

Các bạn đã biết cách pull một images có sẵn từ docker hub về, có thể build một image do mình tự tạo. Các bạn build image bằng cách nào? Đã sử dụng Dockerfile chưa? nếu chưa thì các bạn cần xem qua cách build một image sử dụng Dockerfile: mời bạn tham khảo bài viết dưới đây:

https://viblo.asia/p/huong-dan-build-mot-image-don-gian-tu-dockerfile-gVQvlQAVGZJ

https://viblo.asia/p/docker-tao-docker-images-tu-dockerfile-3P0lPORvZox

file Dockerfile đơn giản hay phức tạp thì về cơ bản các bạn cũng đã biết nó là gì, dùng để làm gì và dùng như thế nào. Chúng ta bắt đầu tìm hiều về Docker composer.

Bài toán: Giả sử bạn muốn xây dựng môi web với nodejs và mysql. Bạn có thể cài đặt nodejs, mysql v.v và chạy trong một container duy nhất. Tuy nhiên đó có phải là cách tốt chưa? với ứng dụng nhỏ thì nó không phải là vấn đề gì lớn cả. Nhưng với ứng dụng lớn, sự tách biệt server là điều hết sức cần thiết.

Việc xây dựng toàn bộ môi trường chạy Web vào một container có thể chưa phải là điều bạn muốn. Bạn muốn chia môi trường này thành các module khác nhau để có thể tái sử dụng ở các dự án khác. Nhu cầu cơ bản nhất là tách biệt cơ sở dữ liệu và server Web để các dự án khác nhau có thể dùng chung một container cơ sở dữ liệu đó. Docker Compose có thể giúp bạn. Nó sẽ giúp chúng ta có thể cấu hình và chạy nhiều container cùng một lúc cho một dự án (hay bao nhiêu dự án thì tuỳ bạn).

Trước hết, cần phải cài đặt Docker Compose, Các bạn tham khảo ở đây:

https://docs.docker.com/compose/install/

Thông tin về Docker Compose, bạn có thể tham khảo thêm ở đây . Nói chung là tôi không muốn giới thiệu nhiều về nó, áp dụng nó vào thực tiễn sẽ là dễ hiểu nhất. Chúng ta sẽ đi qua một demo cụ thể.

V. Demo ứng dụng chat with Nodejs and mysql

Nói sơ qua về ứng dụng chat mình sắp docker hóa sau đây, Nó là web chat sử dụng nodejs, framework express, socket io và CSDL mysql. Bài bog trước mình có demo ứng dụng này, tất cả đều được cài đặt ở local. Bây giờ cũng là ứng dụng đó, sau khi bạn get code của mình về và chỉ một lệnh duy nhất: docker-compose up mọi thứ sẽ sẵn sàng cho bạn.

Bạn có thể xem qua phần demo của ứng dụng trước ở bài blog: https://vietnamlab.vn/blog/2017/05/09/chat-application-with-nodejs-socket-io-mysql-and-express-framework/

1. Get source code project chat app

https://github.com/phongnx1/docker

Các file như:

  • docker\NodeJS_ChatApp\Dockerfile
  • docker\docker-compose.yml
  • docker\NodeJS_ChatApp\startup.sh

Data mình cũng chuẩn bị sẵn luôn: docker\storage

Và bây giờ mình lần lượt gải thích nội dung từng file:

File docker\NodeJS_ChatApp\Dockerfile

FROM centos:6
RUN yum -y update
RUN yum -y groupinstall "Development Tools"
RUN yum -y install cronie
# gpg keys listed at https://github.com/nodejs/node
RUN set -ex \
  && for key in \
    9554F04D7259F04124DE6B476D5A82AC7E37093B \
    94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \
    0034A06D9D9B0064CE8ADF6BF1747F4AD2306D93 \
    FD3A5288F042B6850C66B31F09FE44734EB7990E \
    71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \
    DD8F2338BAE7501E3DD5AC78C273792F7D83545D \
    B9AE9905FFD7803F25714661B63B535A4C206CA9 \
    C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
  ; do \
    gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \
  done

ENV NPM_CONFIG_LOGLEVEL info
ENV NODE_VERSION 4.4.5

RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
  && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
  && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
  && grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
  && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
  && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt

CMD [ "node" ]
RUN mkdir -p /usr/src/app
COPY ./NodeJS_ChatApp /usr/src/app
WORKDIR /usr/src/app
RUN chmod +x startup.sh
RUN npm install -g nodemon

Giải thích: Các câu lệnh trên sẽ lần lượt lấy image CentOS 6 làm gốc, cài các lib dev cần thiết, download và cài node phiên bản 4.4.5, sau đó copy thư mục ./NodeJS_ChatApp vào thư mục /usr/src/app và set thư mục mặc định chạy là usr/src/app. Sau khi copy set quyền execute cho file startup.sh và cài đặt nodemon

Việc copy thư mục hiện hành và set thư mục mặc định sẽ giúp các bạn việc sau: code của các bạn ở trên thư mục chứa file Dockerfile sẽ được copy vào container trên Docker và có thể dùng để chạy Node server. 2 lệnh set chmod cho startup.sh và cài đặt nodemon là optional, cái này mình sẽ giải thích sau, đại khái là giúp bạn dev sướng hơn :D

Vậy là đã xong file Dockerfile. Tiếp đến sẽ là Docker-compose

2. Cấu hình Docker-compose

Sau khi có file Dockerfile, mình sẽ sử dụng file image này cho web server (NodeJS, Express), còn Database mình sẽ sử dụng image mysql có sẵn của Docker. Tuy nhiên việc cài đặt, rồi link giữa 2 container khá là lằng nhằng, mất thời gian, nên mình sẽ sử dụng tool có tên là Docker-compose để làm hộ mình.

Giá trị của Docker-compose sẽ là giúp các bạn khởi chạy nhiều container, liên kết chúng vào với nhau thông qua 1 file setting, giúp chúng ta không cần viết script mỗi lần khởi chạy nữa, chỉ cần một câu lệnh docker-compose up là môi trường của bạn sẽ sẵn sàng để làm việc.

Không nói nhiều nữa, dưới đây là nội dung file docker-compose.yml cần thiết cho việc chạy docker-compose:

version: '2'
services:
  mysql:
    image: mysql
    container_name: test-mysql
    ports:
      - 6603:3306
    environment:
      MYSQL_ROOT_PASSWORD: "123456"
      MYSQL_DATABASE: "test"
      MYSQL_USER: "testuser1"
      MYSQL_PASSWORD: "123456"
    volumes:
      - ./storage/docker/mysql-data:/var/lib/mysql
  web:
    build:
      context: .
      dockerfile: NodeJS_ChatApp/Dockerfile
    container_name: test-app
    working_dir: /usr/src/app
    command: /bin/bash startup.sh
    volumes:
      - ./NodeJS_ChatApp:/usr/src/app
    ports:
      - "32768:3000"
    depends_on:
      - mysql

Chi tiết ý nghĩa của từng dòng lệnh các bạn có thể tìm hiểu trên google, ở đây ý nghĩa của chúng đại khái là:

  • Tạo 2 service có tên là mysql và web
  • Service mysql sử dụng image có tên là mysql, container_name: test-mysql.
  • Port của mysql là 3306 ở trong container, ở ngoài hệ điều hành thật là 6603
  • Mình có thiết lập một số biến môi trường như: pass của root: 123456, tạo sẵn một database: test, tạo 1 account: testuser1, pass: 123456
  • data mình thực hiện link: ./storage/docker/mysql-data:/var/lib/mysql tức là đồng bộ thư mục trên hệ điều hành hiện tại của mình ./storage/docker/mysql-data với thư mục chứa data trong server mysql /var/lib/mysql. Khi get code về các bạn đã thấy thư mục này có rồi (mình đã tạo sẵn data cho bạn rồi, vậy nên khi up lên là nó link tới và có data luôn)
  • Service web sử dụng image build từ thư mục NodeJS_ChatApp/ (là thư mục chứa file Dockerfile ở trên)
  • Service web có thư mục làm việc là /usr/src/app, có volumes là thư mục ./NodeJS_ChatApp đến thư mục /usr/src/app, nhằm mục đích khi thay đổi nội dung trên thư mục hiện tại ( code ), thì dữ liệu sẽ lập tức được cập nhật trên thư mục /usr/src/app
  • Service web có command khởi chạy là /bin/bash startup.sh
  • Service web phụ thuộc vào service mysql, nếu service mysql không chạy thì service web sẽ không hoạt động.

Vậy là xong, bạn đã tạo ra 2 service có thể dùng để chạy server NodeJS và server database (mysql), còn một bước cuối cùng sẽ là file startup.sh.
File docker\NodeJS_ChatApp\startup.sh

if [ ! -d /usr/src/app/node_modules ]; then
  echo "Install dependencies..."
  cd /usr/src/app && npm install --no-bin-links
fi
cd /usr/src/app && nodemon -L index.js

File này sẽ là file khởi chạy của service web mỗi khi docker-compose up.
Mục đích của file này là sẽ chạy lệnh npm install nếu như không tìm thấy thư mục node_modules trong thư mục hiện tại, với các đặc điểm sau:

  • Cài đặt các node_modules cho việc phát triển một cách tự động
  • Tránh việc phải upload node_modules lên repo
  • Vì cài đặt bên trong container nên nếu muốn update node_modules, cần vào container -> suy nghĩ kỹ trước khi update =))

Sau khi xong xuôi các file trên, các bạn sẽ có một môi trường chạy ExpressJS cùng Mysql để phát triển.

Một lưu ý nữa là mình dùng nodemon để khởi động web server với câu lệnh : nodemon -L index.js. Trong trường hợp webservice của các bạn sử dụng file khởi chạy là file khác, mời đổi lại tên file.
Đồng thời việc sử dụng nodemon sẽ giúp code của các bạn ngay khi có thay đổi sẽ được cập nhật lên web service bằng cách... khởi động lại web service :D
Vì thời gian khởi động webservice của Express rất là không đáng kể, nên bạn có thể bật Sublime, bấm Ctrl + S liên tục để xem output thông báo server khởi động lại của ExpressJS :D

3. Demo

Mình thực hiện các thao tác trên hệ máy ảo CentOS, nếu bạn đang demo ứng dụng này ở một hệ điều hành khác cũng đừng lo lắng. chỉ cần đã cài đặt docker và docker-composer thì OK, bắt đầu nào!!!

di chuyển đến thư mục chưa file docker-compose.yml(thư mục bạn get code về: docker), chạy lệnh: docker-compose up

Bạn sẽ phải đợi một tý, vì nó phải tải máy ảo CentOS, mysql v.v nên sẽ hơi lâu một tý. Kết quả sau khi chạy xong, nó sẽ run file startup.sh như hình bên dưới:

Server đã start lên rồi. Vấn đề bây giờ là, chúng ta sẽ truy cập ứng dụng chứng ta ở ip nào, cổng nào, tương tự với mysql cũng vậy.

Tạm thời để server start như thế, chúng ta mở một cửa sổ docker mới và thực hiện kiểm tra các image nó tạo ra, các container dc tạo ra. (bạn cũng có thể stop các container bằng cách Ctr+C 2 lần rồi chạy lại lệnh up nhưng lần này chúng ta option -d để cho container chạy ở chế độ nền: docker-compose up -d).

Cùng thực hiện kiểm tra các images và comtainer nào!

Chúng ta có 3 images được tạo ra và 2 container đang ở trang thái up.

Kiểm tra ip của mysql nhé: gõ lệnh: docker inspect test-mysql

Các bạn kiểm tra kết nối trong file NodeJS_ChatApp\index.js đã đúng chưa nhé, nếu chưa đúng thì sửa lại. Về vấn đề IP của các container khi tạo ra, mình vẫn đang trong quá trình tìm hiểu vì nó cũng khá lằng nhằng, tạm thời thế đã. file index.js

// Tạo kết nối với Database
var pool = mysql.createPool({
host: '172.19.0.2',
user: 'testuser1',
password: '123456',
database: 'test'
});

Sau khia sửa xong thì bây giờ bạn đã biết app kết nối với mysql qua ip nào rồi. Giả sử bây giờ bạn muốn dùng để connect với mysql phục vụ cho việc thêm/sửa/xóa data v.v thì kết nối như thế nào?


Các bạn lưu ý phần IP Address mình bôi màu đỏ, đó là địa chỉ ip của máy ảo CentOS mình đang dùng (máy mà mình dùng để cài docker). Nếu bạn đang chạy docker ở local thì thay chổ đó là localhost hoặc là 127.0.0.1 nhé. Port: 6603, username: testuser1, pass: 123456 cái này mình đã setting ở file docker compose rồi.

Bây giờ thì xem kết quả nào: truy cập địa chỉ: http://192.168.33.56:32768/

  • 192.168.33.56 là địa chỉ máy ảo chạy docker của mình (máy ảo CentOS). nếu bạn chạy ở local thì thay bằng localhost hoặc là 127.0.0.1 nhé.
  • 32768: là port được mapping với port 3000 trong container test-app(cái này cũng được setting ở file docker compose).

Mình đã chuẩn bị sẵn data rồi, nếu bạn muốn thêm thì connect đến mysql như hướng dẫn trên hoặc vào trực tiếp:

docker exec -it test-mysql mysql -u testuser1 -p

Nhập pass đã được setting: 123456

Chạy lệnh thêm data:

INSERT INTO `member` (`account`, `password`) VALUES
('lan', '123456'),
('phong', '123456'),
('giang', '123456'),
('sang', '123456');
COMMIT;

4. Kết quả

Kết luận

  • Mình xin phép được dừng bài tìm hiểu tại đây, có lẽ cũng có kha khá kiến thức cơ bản về docker để mình cùng các bạn có thể mày mò tiếp với nó rồi
  • Cảm ơn các bạn đã dành thời gian đọc bài viết của mình. Thời gian mình tìm hiểu về docker chưa được nhiều nên có thể có nhiều sai sót, rất mong nhận được sự góp ý từ mọi người để sửa đổi nội dung bài viết được tốt hơn.
  • Nếu các bạn đã từng biết qua về vagrant, ansible rồi thì sẽ tiếp cận nhanh hơn nữa. Dự án hiện tại của mình cũng đang sử dụng vagrant, ansible và mình cảm thấy nó thật tuyệt vời. Đến với docker còn cảm thấy nhiều thứ còn tuyệt vời hơn nữa.
  • Sắp tới mình cũng sẽ nghiên cứu sâu hơn về docker, và sẽ tiếp tục chia sẻ đến các bạn.

Tài liệu tham khảo

#### Link Nội bộ
  1. https://vietnamlab.vn/blog/2017/03/25/docker-co-gi-hay/
  2. https://vietnamlab.vn/blog/2017/05/31/docker-hoa-de-dang-voi-docker-compose/

Link Tham khảo bên ngoài

  1. https://viblo.asia/p/docker-doi-voi-lap-trinh-vien-web-PDOkqLAKejx
  2. http://blog.appconus.com/2016/04/17/docker-cho-nguoi-moi-bat-dau-p2/#Cac_thanh_phan_co_ban_cua_Docker
  3. https://viblo.asia/p/tao-moi-truong-develop-nodejs-voi-docker-nwmGyMJgGoW
  4. http://blog.appconus.com/2016/04/15/docker-cho-nguoi-moi-bat-dau-p1/