Docker Compose - Cách viết docker-compose.yml

Docker Compose - Cách viết docker-compose.yml

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điều phối hoạt động của các container trong project.

3. Ứng dụng Full-stack trong Docker Compose

Full-stack application
  • 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-alpine
    COPY ./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:12
container_name: notes-db-dev
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: notesdb
POSTGRES_PASSWORD: secret
networks:
- backend
api:
build:
context: ./api
dockerfile: Dockerfile.dev
image: notes-api:dev
container_name: notes-api-dev
volumes:
- /home/node/app/node_modules
- ./api:/home/node/app
environment:
DB_HOST: db ## same as the database service name
DB_PORT: 5432
DB_USER: postgres
DB_DATABASE: notesdb
DB_PASSWORD: secret
networks:
- backend
client:
build:
context: ./client
dockerfile: Dockerfile.dev
image: notes-client:dev
container_name: notes-client-dev
volumes:
- /home/node/app/node_modules
- ./client:/home/node/app
networks:
- frontend
nginx:
build:
context: ./nginx
dockerfile: Dockerfile.dev
image: notes-router:dev
container_name: notes-router-dev
restart: unless-stopped
ports:
- 8080:80
networks:
- backend
- frontend
volumes:
db-data:
name: notes-db-dev-data
networks:
frontend:
name: fullstack-notes-application-network-frontend
driver: bridge
backend:
name: fullstack-notes-application-network-backend
driver: bridge

Networks:

networks:
frontend:
name: fullstack-notes-application-network-frontend
driver: bridge
backend:
name: fullstack-notes-application-network-backend
driver: 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 Compose
service: # 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:

image
  • 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
container_name
  • Chỉ định tên container tùy chỉnh, thay vì tên mặc định.
restart
  • 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.
environment
  • Thêm các biến môi trường
volumes
  • 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.
build
  • 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
ports Cấu hình cổng kết nối
  • 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/bash
    set -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
  • 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ên docker-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 mysql
      docker-compose up -d redis
      docker-compose up -d woker
    • Start main container:
      docker-compose run --rm -p 3000:3000 app rails s
  • 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 worker
dev:
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 prune
      This will remove:
      - all stopped containers
      - all networks not used by at least one container
      - all dangling images
      - all build cache

Reference:

Đăng nhận xét

Mới hơn Cũ hơn