Docker Compose
1. Docker Compose là gì?
- Docker-compose là 1 công cụ để định nghĩa và chạy nhiều container trên ứng dụng Docker.
-
Cho phép bạn tạo 1 file cấu hình có phần mở rộng của file là
.yml
hoặc.yaml
, nơi mà bạn sẽ định nghĩa các services trên ứng dụng của bạn và định nghĩa tất cả các bước, cấu hình cần thiết để xây dựng các image, up các container và liên kết chúng với nhau. Cuối cùng, 1 khi tất cả điều này được thực hiện hì bạn sẽ chỉ cần thiết lập tất cả với 1 câu lệnh duy nhất. - Ưu điểm: giúp tự động tải các image, thiết lập cấu hình tốt hơn rất nhiều so với Docker.
- Install Docker Compose: docs.docker.com hoặc coconcodedao.com
2. Cấu trúc thư mục cho config Docker Compose
|--- docker |--- entrypoint.sh |--- Dockerfile |--- docker-compose.yml
- docker / entrypoint.sh: Liệt kê những câu lệnh cần chạy sau khi bật container.
- Dockerfile: Chứa tập lệnh để build Docker Image.
- docker-compose.yml: Dùng để khai báo và điều phối hoạt động của các container trong project.
3. Ứng dụng Full-stack trong Docker Compose
- Như bạn thấy sơ đồ ở trên, tất cả các yêu cầu không nhận trực tiếp bởi server (front-end/backend) mà sẽ được nhận trước bởi một dịch vụ NGINX (như bô định tuyến - router).
- Sau đó, Nginx sẽ xem dữ liệu endpoint được yêu cầu có thông tin /api ở trong đó hay không. Nếu có, Nginx sẽ định tuyến yêu cầu đến back-end hoặc nếu không, Nginx sẽ định tuyến yêu cầu đến front-end.
- Bạn làm điều này vì khi bạn chạy một ứng dụng giao diện người dùng, nó không chạy bên trong container. Nó chạy trên trình duyệt, được phân phối từ một container. Do đó, mạng Compose không hoạt động như mong đợi và ứng dụng giao diện người dùng không tìm thấy dịch vụ api.
- Mặt khác, Nginx chạy bên trong 1 conatiner và có thể giao tiếp với các dịch vụ khác nhau trên toàn bộ ứng dụng.
-
nginx/Dockerfile
:FROM nginx:stable-alpineCOPY ./development.conf /etc/nginx/conf.d/default.conf -
nginx/nginx.conf
:upstream client {server client:8080;}upstream api {server api:3000;}server {location / {proxy_pass http://client;}location /sockjs-node {proxy_pass http://client;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "Upgrade";}location /api {rewrite /api/(.*) /$1 break;proxy_pass http://api;}}
version: "3.8"services:db:image: postgres:12container_name: notes-db-devvolumes:- db-data:/var/lib/postgresql/dataenvironment:POSTGRES_DB: notesdbPOSTGRES_PASSWORD: secretnetworks:- backendapi:build:context: ./apidockerfile: Dockerfile.devimage: notes-api:devcontainer_name: notes-api-devvolumes:- /home/node/app/node_modules- ./api:/home/node/appenvironment:DB_HOST: db ## same as the database service nameDB_PORT: 5432DB_USER: postgresDB_DATABASE: notesdbDB_PASSWORD: secretnetworks:- backendclient:build:context: ./clientdockerfile: Dockerfile.devimage: notes-client:devcontainer_name: notes-client-devvolumes:- /home/node/app/node_modules- ./client:/home/node/appnetworks:- frontendnginx:build:context: ./nginxdockerfile: Dockerfile.devimage: notes-router:devcontainer_name: notes-router-devrestart: unless-stoppedports:- 8080:80networks:- backend- frontendvolumes:db-data:name: notes-db-dev-datanetworks:frontend:name: fullstack-notes-application-network-frontenddriver: bridgebackend:name: fullstack-notes-application-network-backenddriver: bridge
Networks:
networks:frontend:name: fullstack-notes-application-network-frontenddriver: bridgebackend:name: fullstack-notes-application-network-backenddriver: bridge
- Code snippet định nghĩa 2 mạng cầu nối. Theo mặc định, Docker Compose tạo một mạng cầu nối và gắn tắt cả các container vào đó. Tuy nhiên, do muốn cách ly mạng nên sẽ phải định nghĩa hai mạng, một cho các dịch vụ front-end và một cho các dịch vụ back-end.
- Cũng cần phải thêm khối networks trong mỗi định nghĩa service. Bằng cách này dịch vụ api và dịch vụ db sẽ đợc gắn vào một mạng và dịch vụ client sẽ được gắn vào một mạng riêng biệt. Nhưng dịch vụ nginx sẽ được gắn vả cả hai mạng để nó có thể hoạt đư như mộ bộ định tuyến giữa các dịch vụ front-end và bach-end.
-
Build và run các dịch vụ với command:
$ docker-compose --file docker-compose.yml up --detach$ docker-compose -f docker-compose.yml up -d
4. Các chỉ thị trong Docker Compose
File
docker-compose.yml
được tổ chức gồm 4 phần:
version: ... # Phiên bản của Docker Composeservice: # Chứa các container. Mỗi service là tên của một container...:.........:......volumes: Gắn đường dẫn trên host machine được sử dụng trên container...:...networks: Sử dụng để cấu hình network cho ứng dụng...:...
Mỗi service sẽ có các thành phần sau:
- Chỉ ra image sẽ được dùng để build container.Tên image được chỉ định khi build một image trên máy host hoặc download từ Docker Hub
- Chỉ định tên container tùy chỉnh, thay vì tên mặc định.
- Giá trị mặc định là no, còn nếu bạn đặt là always thì container sẽ khởi động lại nếu mã thoát cho biết lỗi không thành công.
- Thêm các biến môi trường
- Chia sẻ dữ liệu giữa container (máy ảo) và host (máy thật) hoặc giữa các container với nhau.
- Sử dụng khi chúng ta không xây dựng container từ image có sẵn nữa mà xây dựng nó từ Dockerfile.
-
Nếu Dockerfile nằm cùng thư mục với docker-compose.yml thì
chỉ cần
build: .
-
Nếu bạn muốn đặt Dockerfile trong thư mục docker để cùng với
entrypoint.sh cho gọn thì sửa thành
build:context: ./dockerfile: docker/Dockerfile
- Có thể chỉ định cả 2 cổng (HOST:CONTAINER) tức là (cổng ở máy thật: cổng ở máy ảo) hoặc chỉ định mình cổng cho máy ảo thôi.
Viết entrypoint.sh
-
Chạy file
.sh
với bash#!/bin/bashset -e -
Xoá tiến trình cũ:
rm -f /my_app/tmp/pids/server.pid
-
Mỗi khi khởi chạy rails server sẽ có một mã id được sinh ra và
lưu vào file
tmp/pids/server.pid
để đánh dấu rằng đã tồn tại tiến trình rails đang chạy. - Khi bạn stop rails server thì rails sẽ xóa nội dung file này, tuy nhiên trong trường hợp bạn kill rails process thì rails server sẽ bị stop mà chưa kịp xóa nội dung file, và khi bạn start lại server thì sẽ gặp lỗi
- Như vậy, ta sẽ xóa thủ công nó luôn để đảm bảo không gặp lỗi này
-
Mỗi khi khởi chạy rails server sẽ có một mã id được sinh ra và
lưu vào file
-
Thực thi câu lệnh truyền vào.
exec "$@"
Tối ưu
Vấn đề
-
Khi
docker-compose down
sẽ tắt tất cả service -
Khi
docker-compose up
thì lại bật tất cả chúng lên. - Cứ tắt bật, tắt bật tất cả service liên tục như thế, trong project có ít service thì không có vấn đề gì nhưng nếu số lượng service lớn hơn (ví dụ mysql + redis + nginx ....) thì cũng mất kha khá thời gian đấy.
-
Bởi vì
docker-compose up
hiển thị logs tổng hợp của tất cả service nêndocker-compose up
sẽ không dừng hiển thị logs để chúng ta kiểm tra giá trị của các biến chẳng hạn, - Không vì một service nào mà dừng lại cả, như vậy là chúng ta không debug được.
Giải pháp
- Thay vì bật tất cả containers lên cùng một lúc, chúng ta sẽ bật các background containers lên trước (ví dụ: mysql, redis ...)
- Sau đó mới bật main container (ví dụ: app).
- Khi stop thì ta chỉ stop main container thôi, các background containers vẫn chạy ngầm bên dưới.
-
Câu lệnh:
-
Start background container( chỉ 1 lần khi mới mở máy tính lên):
docker-compose up -d mysqldocker-compose up -d redisdocker-compose up -d woker
-
Start main container:
docker-compose run --rm -p 3000:3000 app rails s
-
Start background container( chỉ 1 lần khi mới mở máy tính lên):
- Với cách này chúng ta sẽ luôn có background service already running , thời gian khởi động ứng dụng sẽ giảm xuống.
- docker-compose run sẽ dừng để bạn có thể debug, --rm sẽ xóa chính container này đi khi bạn stop nó.
Sử dụng Makefile
Thay vì gõ từng câu lệnh một như trên, bạn có thể viết 1
Makefile
up:docker-compose up -d mysql redis workerdev:docker-compose run --rm -p 3000:3000 app rails s
-
Khi đó, trên Terminal gõ
make up
để start các background containers, sau đó gõmake dev
để start main container. - Hơn nữa, khi có một member mới vào dự án và chưa rõ về Docker (có thể là member của team front end chẳng hạn), khi support setup project, bạn chỉ cần hướng dẫn các lệnh để chạy.
Notes:
- Docker image được xây dựng dựa trên các layers xếp chồng. Nếu câu lệnh trước đó đã được thực thi và tạo layer thì Docker sẽ sử dụng layer cũ đó chứ không tạo layer mới nữa, giúp giảm thời gian build image và nếu ở một layer có sự thay đổi thì kể từ layer đó trở về sau, tất cả sẽ được build lại. ==> Trong Dockerfile, những câu lệnh nào có khả năng thay đôỉ cao, bạn hãy đặt nó xuống dưới cùng.
- Quy định phiên bản của image chi tiết nhất có thể.
-
Hãy xóa bỏ những layers không cần thiết.
-
Những
dangling images
(các<none> image
) không còn hữu ích nữa, chúng cũng không được Docker tự động xóa, mà chúng ta sẽ xóa thủ công nó đi để giải phóng bộ nhớ. -
Commads:
# Kiểm tra danh sách image "lơ lửng":$ docker images -f "dangling=true" -q# Xoá các image "lơ lửng" - dangling image:$ sudo docker rmi -f $(docker images -f "dangling=true" -q)$ docker system pruneThis will remove:- all stopped containers- all networks not used by at least one container- all dangling images- all build cache
-
Những