DOCKER IN ACTION
Vài năm trở lại đây, Docker dường như trở thành software platform quốc dân, đi đâu cũng nghe thấy, người người Docker, nhà nhà Docker, tài liệu cũng muôn hình muôn dạng, đầy đủ trên internet. Vì vậy, trong bài viết này, mình sẽ không đi chi tiết về nguồn gốc, lịch sử, feature của Docker nữa, mà mình hướng dẫn các bạn tạo được một dockerfile, đóng gói ứng dụng thành image và lưu trữ trên các Container Registries, ở đây mình sử dụng luôn dịch vụ của docker – Docker Hub, cũng như cách lấy chúng về và sử dụng chúng.
Bài viết này sẽ cùng đi từ những steps cơ bản nhất và mình sẽ cố gắng giải thích dễ hiểu nhất để cho mọi người dù mới cũng sẽ dễ tiếp cận nhất có thể. Chúng ta cùng bắt đầu nhé!
INSTALL DOCKER
Tất nhiên rồi, để sử dụng Docker thì đầu tiên chúng ta phải cài đặt docker trên máy của mình. Trên trang chủ của Docker đã hướng dẫn chi tiết cách cài đặt cho các hệ điều hành: window, MacOS chip Intel, MacOS chip Apple và làm theo hướng dẫn theo từng step chi tiết trên trang chủ
Sau khi cài thành công, biểu tượng con cá voi đặc trưng của docker sẽ hiển thị ở trên thanh công cụ của từng hệ điều hành
Trên window
Trên macos
Docker Preferences
Docker preferences cho phép người dùng thiết lập các tính của Docker như: cài đặt, cập nhật, phiên bản, đăng nhập vào Docker Hub, v.v..
BUILD APP
Trong step này, chúng ta tạo một app NodeJs cơ bản và tạo Image cho chính nó.
$ cd [path]
$ npm init -y
$ npm install ronin-server ronin-mocks
$ touch server.js
Trong app ta tạo một file server.js có nội dung như sau:
const ronin = require( 'ronin-server' )
const mocks = require( 'ronin-mocks' )
const server = ronin.server()
server.use( '/', mocks.server( server.Router(), false, true ) )
server.start()
Run ứng dụng để test
$ npm start
$ curl --request POST \
--url http://localhost:8000/test \
--header 'content-type: application/json' \
--data '{"msg": "testing" }'
{"code":"success","payload":[{"msg":"testing","id":"31f23305-f5d0-4b4f-a16f-6f4c8ec93cf1","createDate":"2020-08-28T21:53:07.157Z"}]}
$ curl http://localhost:8000/test
{"code":"success","meta":{"total":1,"count":1},"payload":[{"msg":"testing","id":"31f23305-f5d0-4b4f-a16f-6f4c8ec93cf1","createDate":"2020-08-28T21:53:07.157Z"}]}
CREATE DOCKERFILE
Có thể hình dung, DockerFile là nơi chứa tất cả các chỉ thị cho Docker, sau khi đọc các chỉ thị này Docker sẽ build các Docker Image một cách tự động.
Đầu tiên, trong app của mình, chúng ta tạo một file có tên là Dockerfile và mở nó bằng editor
# Comment
INSTRUCTION arguments
- Các INSTRUCTION là các chỉ thị, được docker quy định. Khi khai báo, các bạn phải viết chữ in hoa.
- Các arguments là đoạn nội dung mà chỉ thị sẽ làm gì.
Đầu tiên, để khởi tạo Docker file, chúng ta cần sử dụng FROM để tạo base.
FROM node:12.18.1
Docker images có kế thừa từ các images khác. Cho nên thay vì tự tạo và config, chúng ta sử dụng official NodeJs image đã có sẵn đầy đủ packages và config, đó chính là một feature nổi bật của Docker. Có thể hiểu giống như tính kế thừa của class, khi ta sử dụng From, Docker sẽ include toàn bộ chức năng từ node:12.18.1 vào image của chúng mình.
NODE_ENV dùng để khai báo môi trường, ở đây chúng ta chọn production để cải thiện hiệu năng
ENV NODE_ENV=production
WORKDIR dùng để đặt thư mục đang làm việc cho các chỉ thị khác
WORKDIR /app
Trước khi run npm install, để thêm package.json và package-lock.json file vào image. Chúng ta sử dụng COPY để thực hiện việc này. Command này có 2 parameters chính, param đầu tiên chỉ ra những file nào mình muốn copy vào image. Param thứ 2 chỉ chỗ chứa chúng. Chúng ta sẽ copy package.json và package-lock.json vào /app
COPY ["package.json", "package-lock.json*", "./"]
Sau khi đã có package.json Bây giờ RUN command sẽ dùng để chạy npm install trong quá trình build image. Nó giống như việc chúng ta tự chạy npm install trên máy của chúng mình , nhưng mà lần này Node modules sẽ được cài vào node_modules ở trong image.
RUN npm install --production
Vậy là chúng ta đã có Node image theo version 12.18.1 cũng như cài các dependencies. Giờ chúng ta cần COPY source code vào image. COPY command sẽ thực hiện việc này như việc copy package.json ở trên.
COPY . .
COPY . . sẽ copy tất cả files ở trong image. Vậy là chúng ta đã có đầy đủ môi trường và source code, chúng ta cần CMD để khai báo câu lệnh sẽ chạy khi image của chúng ta nằm bên trong một container.
CMD [ "npm", "start" ]
Nội dung của Dockerfile hoàn chỉnh:
FROM node:12.18.1
ENV NODE_ENV=production
WORKDIR /app
COPY ["package.json", "package-lock.json*", "./"]
RUN npm install --production
COPY . .
CMD [ "npm", "start" ]
BUILD IMAGE
Sau khi đã có Dockerfile, bây giờ sử dụng docker build để build Docker image.
$ docker build -t node-docker .
-t hay –tag dùng để đánh tên của image
. dùng để trỏ vào vị trí chứa Dockerfile
Các command bên trong Dockerfile sẽ được chạy lần lượt theo từng step như bên dưới.
$ docker build -t node-docker .
Sending build context to Docker daemon 13.58MB
Step 1/7 : FROM node:12.18.1
---> f5be1883c8e0
Step 2/7 : ENV NODE_ENV=production
---> Running in 86e5f2cb18af
Removing intermediate container 86e5f2cb18af
---> 244a33a1a837
Step 3/7 : WORKDIR /app
---> Running in df3aff9d4ef3
Removing intermediate container df3aff9d4ef3
---> 3a8345aa777c
Step 4/7 : COPY ["package.json", "package-lock.json*", "./"]
---> 89a965a4fa52
Step 5/7 : RUN npm install --production
---> Running in 9f3069e64e66
added 110 packages from 68 contributors and audited 110 packages in 6.571s
1 package is looking for funding
run `npm fund` for details
found 0 vulnerabilities
Removing intermediate container 9f3069e64e66
---> 61456c61522e
Step 6/7 : COPY . .
---> 00cf7979e39c
Step 7/7 : CMD [ "npm", "start" ]
---> Running in b30b66b19356
Removing intermediate container b30b66b19356
---> f96a1ad745b5
Successfully built f96a1ad745b5
Successfully tagged node-docker:latest
Vậy là chúng ta đã tạo một image với id là f96a1ad745b5. Cùng kiểm tra trên local:
$ docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
node-docker latest f96a1ad745b5 24 seconds ago 946MB
Có thể thấy được một image tên node-docker có id là f96a1ad745b5 đã được tạo vào 24 seconds trước.
RUN CONTAINER
Giờ cùng run và kiểm tra xem app của chúng ta có chạy đúng hay không.
$ docker run node-docker
Image chỉ có thể đọc không thể đổi. Khi image được docker khởi chạy thì sẽ tạo ra các container ( phiên bản thực thi ), có thể ghi các dữ liệu vào trong nó . Vậy container là một phiên bản chạy của image.
Sử dụng một terminal khác để kiểm tra xem đã có container hay chưa:
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
46e8149a09a0 node-docker "docker-entrypoint.s…" 3 seconds ago Up 2 seconds hungry_buck
Cùng test thử app:
curl --request POST \
--url http://localhost:8000/test \
--header 'content-type: application/json' \
--data '{
"msg": "testing"
}'
curl: (7) Failed to connect to localhost port 8000: Connection refused
curl: (7) Failed to connect to localhost port 8000: Connection refused xảy ra bởi vì chúng ta không thể kết nối tới port 8000, container đang chạy ở trong network của chính nó, cho nên chúng ta cần thiết lập để có thể chuyển từ port 8000 của local sang port 8000 của app.
Stop container bằng cách sử dụng container id của nó:
$ docker container stop 46e8149a09a0
46e8149a09a0
và khởi chạy lại cùng với sử dụng --publish
hay -p
cùng với format [host port]:[container port]
$ docker run -p 8000:8000 node-docker
Cùng check xem container đã chạy đúng port hay chưa. Sử dụng một terminal mới.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a8dbca7da298 node-docker "docker-entrypoint.s…" 26 minutes ago Up 26 minutes 0.0.0.0:8000->8000/tcp agitated_austin
Giờ chúng ta cùng test thử:
$ curl --request POST \
--url http://localhost:8000/test \
--header 'content-type: application/json' \
--data '{"msg": "testing" }'
{"code":"success","payload":[{"msg":"testing","id":"31f23305-f5d0-4b4f-a16f-6f4c8ec93cf1","createDate":"2020-08-28T21:53:07.157Z"}]}
{"code":"success","payload":[{"msg":"testing","id":"549a62c1-4be1-4120-aad1-e4f98983357a","createDate":"2021-07-29T18:23:24.484Z"}]}zsh: no matches found: code:success,payload:[msg:testing]
$ curl --request GET localhost:8000/test
{"code":"success","meta":{"total":1,"count":1},"payload":[{"msg":"testing","id":"a1198b85-93d2-45de-8897-5c988315f703","createDate":"2021-07-29T16:56:14.416Z"}]}%
Vậy là app của chúng ta đã chạy trên container thành công.
CONTAINER REGISTRY – DOCKER HUB
Container Registry là các dịch vụ máy chủ cho phép lưu trữ các image container của cá nhân, công ty, team, … do các bên thứ 3 cung cấp hoặc tự build nội bộ. Khi lưu trữ container image trên hệ thống container registry thì chúng ta có thể sử dụng chúng cho rất nhiều khâu trong vòng đời phát triển ứng dụng sản phẩm. Hiện nay có rất nhiều dịch vụ của các nhà cung cấp nổi tiếng như:
* Azure Container Registry
* Docker Hub
* Quay Enterprise
* Google Container Registry.
* AWS Container Registry
Trong bài viết này chúng ta sẽ sử dụng dịch vụ của Docker : Docker hub để lưu trữ và tái sử dụng image của mình đã tạo ở các step trên.
Tạo tài khoản và repository trên Docker hub
Đầu tiên chúng ta tạo một tài khoản trên https://hub.docker.com/ và https://hub.docker.com/repository/create để tạo một repository.
Sau khi tạo xong, vào repository chúng ta sẽ thấy các thông tin của repo vừa tạo.
Sau đó, hãy login vào docker trên máy tính của mình bằng tài khoản vừa tạo ở step trên.
Login xong, chúng ta cũng có thể truy cập vào repository hay là kiểm tra đã có repository trên docker hub của mình hay chưa.
Đầu tiên chúng ta commit để lấy container đang chạy và lưu trạng thái hiện tại của nó dưới dạng một image.
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
$ docker commit a8dbca7da298 docker-node:version1
sha256:7141cb7f50914fc085ec54dc74440729eace432ecba185a555abb63dc53840d6
Lệnh docker commit
trả về một hàm băm sha256. Hàm băm này sẽ có thể được dùng để tạo image, chi tiết sẽ nói ở các phần tiếp theo. Khi thực hiện lệnh này, một image mới sẽ được tạo ra với tag như đã khai báo ở trên.
$ docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-node version1 7141cb7f5091 1 minute ago 946MB
Thẻ (tag) là một giá trị ID (số hoặc chữ) được gắn kèm thông tin theo Docker Image trong một repository để đánh dấu thể hiện thông tin khác nhau giữa các Docker Image. Giờ mình tiến hành đánh thẻ tag cho image cũng như gán chúng với repository đã tạo trên Docker Hub và đánh tag cho nó.
docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
$ docker tag docker-node:version1 trunglk16/fv-node-docker:latest
Cuối cùng chúng ta tiến hành đẩy image lên Docker Hub:
$ docker push trunglk16/fv-node-docker:latest
The push refers to repository [docker.io/trunglk16/fv-node-docker]
f52aaa6bb13b: Pushed
86a87980981f: Pushed
4e67f619eb46: Pushed
858eb3847c98: Pushed
788c5e75ec08: Pushed
dc48ece44f3c: Mounted from library/node
798326960eac: Mounted from library/node
dacaab4534e4: Mounted from library/node
bc17cd405095: Mounted from library/node
ee854067fbbd: Mounted from library/node
740ffea5d5c3: Mounted from library/node
eac9ead92b24: Mounted from library/node
23bca356262f: Mounted from library/node
8354d5896557: Mounted from library/node
lastest: digest: sha256:aa85c7e3ddd2edc238f1d002e9f01fbd636d76903d88dfa3e715d21b154cc14b size: 3259
Cùng kiểm tra trên Docker Hub:
Vậy là chúng ta đã tạo và lưu trữ trên Docker Hub thành công. Bây giờ chúng ta thử pull về và chạy thử nhé.
Sử dụng docker pull để pull image về:
$ docker pull trunglk16/fv-node-docker:latest
latest: Pulling from trunglk16/fv-node-docker
Digest: sha256:a466d0939a91281e78d82af2cdb8d1fbdcdf52302569c33a0bed798c4e0ee78c
Status: Image is up to date for trunglk16/fv-node-docker:latest
docker.io/trunglk16/fv-node-docker:latest
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
trunglk16/fv-node-docker latest f07359ba63b7 28 minutes ago 946MB
Tiến hành chạy thử image vừa pull về:
$ docker run trunglk16/fv-node-docker
Test thử:
$ curl --request POST \
--url http://localhost:8000/test \
--header 'content-type: application/json' \
--data '{"msg": "testing" }'
{"code":"success","payload":[{"msg":"testing","id":"31f23305-f5d0-4b4f-a16f-6f4c8ec93cf1","createDate":"2020-08-28T21:53:07.157Z"}]}
{"code":"success","payload":[{"msg":"testing","id":"549a62c1-4be1-4120-aad1-e4f98983357a","createDate":"2021-07-29T18:23:24.484Z"}]}zsh: no matches found: code:success,payload:[msg:testing]
$ curl http://localhost:8000/test
{"code":"success","meta":{"total":1,"count":1},"payload":[{"msg":"testing","id":"549a62c1-4be1-4120-aad1-e4f98983357a","createDate":"2021-07-29T18:23:24.484Z"}]}%
Work rồi, vậy là chúng ta đã hoàn thành một cycle về việc tạo một app, đưa nó vào Docker, đưa nó lên Docker Hub, tải nó về và chạy. Hi vọng bài viết của mình sẽ giúp mọi người hiểu rõ hơn về Docker, rất mong nhận được sự đóng góp của mọi người để mình có thể hoàn thiện bài viết hơn cũng như cho ra những phần tiếp theo của series này.
REFERENCES:
https://docs.docker.com/get-started/