Docker Compose là gì và cách sử dụng

Thứ Bảy, 22/06/2024 · 24 phút đọc

Trong bài viết này, chúng ta sẽ tìm hiểu về những kiến thức cơ bản của Docker Compose và cách sử dụng nó. Bài viết sẽ cung cấp một số ví dụ về việc sử dụng Compose để triển khai các ứng dụng phổ biến. Hãy bắt đầu nào!

Docker Compose là gì?

Docker Compose là một công cụ giúp việc tạo và chạy các ứng dụng đa container trở nên dễ dàng hơn. Nó tự động hóa quá trình quản lý nhiều container Docker cùng lúc, chẳng hạn như frontend của trang web, API và dịch vụ cơ sở dữ liệu.

Docker Compose cho phép bạn định nghĩa các container của ứng dụng như một mã trong một tệp YAML, và bạn có thể cam kết nó vào kho mã nguồn của mình. Khi bạn đã tạo xong tệp của mình (thường được đặt tên là docker-compose.yml), bạn có thể khởi động tất cả các container (được gọi là “dịch vụ”) chỉ với một lệnh Compose duy nhất.

So với việc khởi động và liên kết container bằng tay, Compose nhanh chóng, dễ dàng và có thể lặp lại. Các container của bạn sẽ chạy với cùng cấu hình mỗi lần – không có rủi ro quên đi một flag quan trọng trong lệnh docker run.

Compose tự động tạo một mạng Docker cho dự án của bạn, đảm bảo các container có thể giao tiếp với nhau. Nó cũng quản lý các volume lưu trữ của Docker, tự động gắn lại chúng sau khi một dịch vụ được khởi động lại hoặc thay thế.

Tại sao nên sử dụng Docker Compose?

Phần lớn các ứng dụng thực tế có nhiều dịch vụ với mối quan hệ phụ thuộc lẫn nhau – ví dụ, ứng dụng của bạn có thể chạy trong một container nhưng phụ thuộc vào một server cơ sở dữ liệu được triển khai trong một container khác. Hơn nữa, các dịch vụ thường cần được cấu hình với volume lưu trữ, biến môi trường, liên kết cổng và các thiết lập khác trước khi được triển khai.

Compose cho phép bạn đóng gói các yêu cầu này thành một “stack” container dành riêng cho ứng dụng của bạn. Sử dụng Compose để khởi động stack sẽ giúp tất cả container bắt đầu với các giá trị cấu hình bạn đã đặt trong tệp. Điều này cải thiện tính tiện lợi cho nhà phát triển, hỗ trợ việc tái sử dụng stack trong nhiều môi trường và giúp ngăn ngừa các lỗi cấu hình.

Sự khác biệt giữa Docker và Docker Compose là gì?

Docker là một công cụ containerization cung cấp giao diện dòng lệnh (CLI) để xây dựng, chạy và quản lý các container riêng lẻ trên máy chủ của bạn.

Compose là một công cụ mở rộng Docker với khả năng hỗ trợ quản lý đa container. Nó hỗ trợ các “stack” container được định nghĩa dưới dạng khai báo trong các tệp cấu hình cấp dự án.

Bạn có thể sử dụng Docker mà không cần Compose; tuy nhiên, việc sử dụng Compose khi phát triển hệ thống container hóa cho phép bạn triển khai ứng dụng trong bất kỳ môi trường nào chỉ với một lệnh duy nhất. Trong khi Docker CLI chỉ tương tác với một container tại một thời điểm, Compose tích hợp với dự án của bạn và biết mối quan hệ giữa các container.

Lợi ích của Docker Compose

Dưới đây là một số lợi ích của việc sử dụng Docker Compose:

  • Cấu hình nhanh và dễ dàng với tệp YAML
  • Triển khai trên một máy chủ duy nhất
  • Tăng năng suất
  • Bảo mật với các container cô lập

Hướng dẫn: Sử dụng Docker Compose

Hãy xem cách bắt đầu sử dụng Compose trong ứng dụng của riêng bạn. Chúng ta sẽ tạo một ứng dụng Node.js đơn giản yêu cầu kết nối tới server Redis chạy trong một container khác.

1. Kiểm tra xem Docker Compose đã được cài đặt

Trước đây, Docker Compose được phân phối dưới dạng một binary độc lập gọi là docker-compose, tách biệt với Docker Engine. Kể từ khi ra mắt Compose v2, lệnh này đã được tích hợp vào docker CLI với tên gọi docker compose. Phiên bản Compose v1 không còn được hỗ trợ.

Bạn nên có Docker Compose v2 nếu đang sử dụng phiên bản hiện đại của Docker Desktop hoặc Docker Engine. Bạn có thể kiểm tra bằng lệnh sau:

$ docker compose version
Docker Compose version v2.18.1

2. Tạo Ứng Dụng Của Bạn

Bắt đầu hướng dẫn này bằng cách sao chép mã sau và lưu nó vào app.js trong thư mục làm việc của bạn:

const express = require("express");
const {createClient: createRedisClient} = require("redis");

(async function () {
    const app = express();
    const redisClient = createRedisClient({
        url: `redis://redis:6379`
    });
    await redisClient.connect();
    app.get("/", async (request, response) => {
        const counterValue = await redisClient.get("counter");
        const newCounterValue = ((parseInt(counterValue) || 0) + 1);
        await redisClient.set("counter", newCounterValue);
        response.send(`Page loads: ${newCounterValue}`);
    });
    app.listen(80);
})();

Mã này sử dụng gói Express để tạo một ứng dụng theo dõi lượt truy cập đơn giản. Mỗi lần bạn truy cập ứng dụng, nó sẽ ghi nhận lượt truy cập vào Redis và hiển thị tổng số lần tải trang.

Sử dụng npm để cài đặt các phụ thuộc của ứng dụng:

$ npm install express redis

Tiếp theo, sao chép nội dung Dockerfile sau vào tệp Dockerfile trong thư mục làm việc của bạn:

FROM node:18-alpine

EXPOSE 80
WORKDIR /app

COPY package.json .
COPY package-lock.json .
RUN npm install

COPY app.js .

ENTRYPOINT ["node", "app.js"]

Compose sẽ xây dựng Dockerfile này sau đó để tạo Docker image cho ứng dụng của bạn.

3. Tạo Tệp Docker Compose

Bây giờ, bạn đã sẵn sàng thêm Compose vào dự án của mình. Ứng dụng này là một ví dụ tuyệt vời để sử dụng Compose vì bạn cần hai container để chạy thành công ứng dụng:

  • Container 1 – Ứng dụng server Node.js bạn đã tạo.
  • Container 2 – Một instance Redis để ứng dụng Node.js của bạn kết nối.

Việc tạo một tệp docker-compose.yml là bước đầu tiên trong việc sử dụng Compose. Sao chép nội dung sau và lưu vào tệp docker-compose.yml của bạn:

services:
  app:
    image: app:latest
    build:
      context: .
    ports:
      - ${APP_PORT:-80}:80
  redis:
    image: redis:6

Hãy cùng xem chi tiết:

  • Trường services cấp cao nhất là nơi bạn định nghĩa các container mà ứng dụng yêu cầu.
  • Có hai dịch vụ được chỉ định cho ứng dụng này: app (ứng dụng Node.js của bạn) và redis (server Redis của bạn).
  • Mỗi dịch vụ có một trường image xác định Docker image mà container sẽ chạy. Đối với dịch vụ app, đó là image app:latest. Vì image này có thể chưa tồn tại, trường build được đặt để thông báo cho Compose biết nó có thể xây dựng image từ thư mục làm việc (.) làm ngữ cảnh xây dựng. Dịch vụ redis đơn giản hơn vì nó chỉ cần tham chiếu đến image Redis chính thức trên Docker Hub.
  • Dịch vụ app có trường ports khai báo liên kết cổng sẽ áp dụng cho container, tương tự như flag -p của docker run. Một biến được sử dụng để xác định cổng, với giá trị mặc định là cổng 80.

4. Khởi Động Container

Bây giờ, bạn có thể sử dụng Compose để khởi động stack!

Chạy lệnh docker compose up để bắt đầu tất cả các dịch vụ trong tệp docker-compose.yml của bạn. Giống như khi sử dụng docker run, bạn nên thêm đối số -d để tách terminal và chạy các dịch vụ ở chế độ nền:

$ docker compose up -d

5. Quản Lý Stack Docker Compose Của Bạn

Sau khi bạn đã khởi động ứng dụng, bạn có thể sử dụng các lệnh Docker Compose khác để quản lý stack:

  • docker compose ps: Xem các container mà Compose đã tạo.
  • docker compose stop: Dừng tất cả container Docker được tạo bởi stack. Sử dụng docker compose start để khởi động lại chúng.
  • docker compose restart: Buộc khởi động lại các container trong stack.
  • docker compose down: Xóa các đối tượng được tạo bởi docker compose up, bao gồm container và mạng Docker.
  • docker compose logs: Xem đầu ra từ các container trong stack.

Hy vọng bài viết này giúp bạn hiểu rõ hơn về Docker Compose và cách sử dụng nó để triển khai các ứng dụng container hóa một cách hiệu quả. Nếu bạn có bất kỳ câu hỏi nào, hãy để lại bình luận bên dưới!

4. Khởi Động Container Của Bạn

Bây giờ, bạn có thể sử dụng Compose để khởi động stack!

Gọi lệnh docker compose up để bắt đầu tất cả các dịch vụ trong tệp docker-compose.yml của bạn. Tương tự như khi gọi docker run, bạn nên thêm đối số -d để tách terminal và chạy các dịch vụ ở chế độ nền:

$ docker compose up -d
[+] Building 0.5s (11/11) FINISHED
...
[+] Running 3/3
 ✔ Network node-redis_default    Created  0.1s 
 ✔ Container node-redis-redis-1  Started  0.7s 
 ✔ Container node-redis-app-1    Started  0.6s

Vì image của ứng dụng của bạn chưa tồn tại, Compose sẽ xây dựng nó từ Dockerfile. Sau đó, nó sẽ chạy stack của bạn bằng cách tạo một mạng Docker và khởi động các container.

Truy cập localhost trong trình duyệt để xem ứng dụng của bạn đang hoạt động.

Hãy thử làm mới trang vài lần – bạn sẽ thấy bộ đếm tăng lên khi mỗi lượt truy cập được ghi lại trong Redis.

Trong tệp app.js, chúng ta đã thiết lập URL cho Redis client là redis:6379. Tên hostname redis khớp với tên của dịch vụ Redis trong tệp docker-compose.yml.

Compose sử dụng tên của các dịch vụ để gán tên hostname cho container của bạn; vì các container đều thuộc cùng một mạng Docker, container ứng dụng của bạn có thể giải quyết hostname redis đến instance Redis.

5. Quản Lý Stack Docker Compose – Các Lệnh

Sau khi bạn đã khởi động ứng dụng của mình, bạn có thể sử dụng các lệnh Docker Compose khác để quản lý stack:

  • docker compose ps: Xem các container mà Compose đã tạo. Đầu ra sẽ tương tự như khi chạy docker ps:
$ docker compose ps
NAME                 IMAGE               COMMAND                  SERVICE             CREATED             STATUS              PORTS
node-redis-app-1     app:latest          "node app.js"            app                 12 minutes ago      Up 12 minutes       0.0.0.0:80->80/tcp, :::80->80/tcp
node-redis-redis-1   redis:6             "docker-entrypoint.s…"   redis               12 minutes ago      Up 12 minutes       6379/tcp
  • docker compose stop: Lệnh này sẽ dừng tất cả container Docker được tạo bởi stack. Sử dụng docker compose start để khởi động lại chúng.
  • docker compose restart: Lệnh này buộc khởi động lại ngay lập tức các container trong stack của bạn.
  • docker compose down: Sử dụng lệnh này để loại bỏ các đối tượng được tạo bởi docker compose up. Lệnh này sẽ phá hủy các container và mạng của stack.
  • docker compose logs: Xem đầu ra từ các container trong stack bằng lệnh logs. Lệnh này sẽ tập hợp các dòng đầu ra và lỗi từ tất cả các container trong stack. Mỗi dòng log sẽ được gắn thẻ với tên của container đã tạo ra nó.

6. Sử Dụng Compose Profiles

Đôi khi, một dịch vụ trong stack của bạn có thể là tùy chọn. Ví dụ, bạn có thể mở rộng ứng dụng demo để hỗ trợ sử dụng các engine cơ sở dữ liệu thay thế thay vì Redis. Khi một engine khác được sử dụng, bạn sẽ không cần đến container Redis.

Bạn có thể đáp ứng các yêu cầu này bằng cách sử dụng tính năng profiles của Compose. Gán các dịch vụ vào profile cho phép bạn kích hoạt chúng thủ công khi chạy các lệnh Compose:

services:
  app:
    image: app:latest
    build:
      context: .
    ports:
      - ${APP_PORT:-80}:80
  redis:
    image: redis:6
    profiles:
      - with-redis

Tệp docker-compose.yml này gán dịch vụ Redis vào một profile có tên with-redis. Bây giờ container Redis chỉ được xem xét khi bạn thêm cờ --profile with-redis với lệnh docker compose:

# Không khởi động Redis
$ docker compose up -d

# Sẽ khởi động Redis
$ docker compose --profile with-redis up -d

7. Hiểu Về Dự Án Docker Compose

Dự án là một khái niệm quan trọng trong Docker Compose v2. Dự án của bạn chính là tệp docker-compose.yml và các tài nguyên mà nó tạo ra.

Compose mặc định sử dụng tệp docker-compose.yml trong thư mục làm việc của bạn. Nó cho rằng tên của dự án sẽ bằng với tên của thư mục làm việc. Tên này sẽ được đặt trước các đối tượng Docker mà Compose tạo ra, chẳng hạn như container và mạng của bạn. Bạn có thể ghi đè tên dự án bằng cách thiết lập cờ --project-name của Compose hoặc thêm trường name ở cấp cao nhất trong tệp docker-compose.yml:

name: "demo-app"
services:
  ...

Bạn có thể chạy các lệnh Docker Compose từ ngoài thư mục làm việc của dự án bằng cách thiết lập cờ --profile-directory:

$ docker compose --profile-directory=/path/to/directory ps

Cờ này chấp nhận một đường dẫn đến tệp docker-compose.yml, hoặc một thư mục chứa tệp này.

8. Thiết Lập Biến Môi Trường Cho Docker Compose

Một trong những lợi ích của Docker Compose là khả năng dễ dàng thiết lập biến môi trường cho các dịch vụ.

Thay vì lặp lại các cờ docker run -e thủ công, bạn có thể định nghĩa các biến trong tệp docker-compose.yml, thiết lập giá trị mặc định và cho phép ghi đè đơn giản:

services:
  app:
    image: app:latest
    build:
      context: .
    environment:
      - DEV_MODE
      - REDIS_ENABLED=1
      - REDIS_HOST_URL=${REDIS_HOST:-redis}
    ports:
      - ${APP_PORT:-80}:80

Ví dụ này cho thấy một vài cách thiết lập biến:

  • DEV_MODE – Không cung cấp giá trị có nghĩa là Compose sẽ lấy giá trị từ biến môi trường đã thiết lập trong shell của bạn.
  • REDIS_ENABLED=1 – Thiết lập một giá trị cụ thể sẽ đảm bảo nó được sử dụng (trừ khi bị ghi đè sau này).
  • REDIS_HOST_URL=${REDIS_HOST:-redis} – Ví dụ interpolated này gán REDIS_HOST_URL giá trị của biến môi trường REDIS_HOST trong shell của bạn, và dùng giá trị mặc định là redis nếu không có.
  • ${APP_PORT:-80} – Biến môi trường thiết lập trong shell của bạn có thể được interpolated vào bất kỳ trường nào trong tệp docker-compose.yml, cho phép tùy chỉnh cấu hình stack một cách dễ dàng.

Bạn có thể ghi đè các giá trị này bằng cách tạo một tệp môi trường – hoặc .env, tự động được tải, hoặc một tệp khác mà bạn truyền vào cờ --env-file của Compose:

$ cat config.env
DEV_MODE=1
APP_PORT=8000

$ docker compose --env-file=config.env up -d

9. Điều Khiển Thứ Tự Khởi Động Dịch Vụ

Nhiều ứng dụng yêu cầu các thành phần của chúng phải đợi các phụ thuộc sẵn sàng—ví dụ, trong ứng dụng demo của chúng ta ở trên, ứng dụng Node sẽ bị lỗi nếu nó bắt đầu trước khi container Redis đã hoạt động.

Bạn có thể kiểm soát thứ tự các dịch vụ khởi động bằng cách thiết lập trường depends_on trong tệp docker-compose.yml:

services:
  app:
    image: app:latest
    build:
      context: .
    depends_on:
      - redis
    ports:
      - ${APP_PORT:-80}:80
  redis:
    image: redis:6

Bây giờ Compose sẽ trì hoãn việc khởi động dịch vụ app cho đến khi container redis đang chạy. Để đảm bảo an toàn hơn, bạn có thể chờ cho đến khi container vượt qua kiểm tra sức khỏe của nó bằng cách sử dụng dạng dài của depends_on:

services:
  app:
    image: app:latest
    build:
      context: .
    depends_on:
      redis:
        condition: service_healthy
    ports:
      - ${APP_PORT:-80}:80
  redis:
    image: redis:6

Ví Dụ Docker Compose

Bạn muốn xem Compose hoạt động như thế nào khi triển khai một số ứng dụng thực tế? Dưới đây là một số ví dụ!

WordPress (Apache/PHP và MySQL) với Docker Compose

WordPress là hệ thống quản lý nội dung website (CMS) phổ biến nhất. Nó là một ứng dụng PHP cần có kết nối cơ sở dữ liệu MySQL hoặc MariaDB. Do đó, có hai container cần triển khai với Docker:

  • Container ứng dụng WordPress – Cung cấp WordPress bằng PHP và server web Apache.
  • Container cơ sở dữ liệu MySQL – Chạy server cơ sở dữ liệu mà container WordPress sẽ kết nối tới.

Tệp docker-compose.yml sau đây có thể được sử dụng để tạo các container này và khởi động một trang WordPress hoạt động:

services:
  wordpress:
    image: wordpress:${WORDPRESS_TAG:-6.2}
    depends_on:
      - mysql
    ports:
      - ${WORDPRESS_PORT:-80}:80
    environment:
      - WORDPRESS_DB_HOST=mysql
      - WORDPRESS_DB_USER=wordpress
      - WORDPRESS_DB_PASSWORD=${DATABASE_USER_PASSWORD}
      - WORDPRESS_DB_NAME=wordpress
    volumes:
      - wordpress:/var/www/html
    restart: unless-stopped
  mysql:
    image: mysql:8.0
    environment:
      - MYSQL_DATABASE=wordpress
      - MYSQL_USER=wordpress
      - MYSQL_PASSWORD=${DATABASE_USER_PASSWORD}
      - MYSQL_RANDOM_ROOT_PASSWORD="1"
    volumes:
      - mysql:/var/lib/mysql
    restart: unless-stopped

volumes:
  wordpress:
  mysql:

Tệp Compose này chứa tất cả các cấu hình cần thiết để triển khai WordPress với kết nối tới cơ sở dữ liệu MySQL.

Các biến môi trường được thiết lập để cấu hình instance MySQL và cung cấp thông tin xác thực cho container WordPress.

Docker volume cũng được định nghĩa để lưu trữ dữ liệu liên tục được tạo bởi các container, độc lập với vòng đời của chúng.

Bây giờ bạn có thể khởi động MySQL với một lệnh đơn giản—biến môi trường duy nhất bạn cần là mật khẩu người dùng cơ sở dữ liệu WordPress:

$ DATABASE_USER_PASSWORD=abc123 docker compose up -d

Truy cập localhost trong trình duyệt để vào trang cài đặt của WordPress của bạn.

Prometheus và Grafana với Docker Compose

Prometheus là cơ sở dữ liệu chuỗi thời gian phổ biến dùng để thu thập số liệu từ các ứng dụng. Nó thường được kết hợp với Grafana, một nền tảng quan sát cho phép hiển thị dữ liệu từ Prometheus và các nguồn khác trên bảng điều khiển đồ họa.

Hãy sử dụng Docker Compose để triển khai và kết nối các ứng dụng này.

Đầu tiên, tạo tệp cấu hình Prometheus—tệp này cấu hình ứng dụng để tự thu thập số liệu của mình, cung cấp dữ liệu cho mục đích minh họa:

scrape_configs:
- job_name: prometheus
  honor_timestamps: true
  scrape_interval: 10s
  scrape_timeout: 5s
  metrics_path: /metrics
  scheme: http
  static_configs:
  - targets:
    - localhost:9090

Lưu tệp này vào prometheus/prometheus.yml trong thư mục làm việc của bạn.

Tiếp theo, tạo một tệp cấu hình cho Grafana để cấu hình ứng dụng với kết nối nguồn dữ liệu tới instance Prometheus của bạn:

apiVersion: 1

datasources:
- name: Prometheus
  type: prometheus
  url: http://prometheus:9090
  access: proxy
  isDefault: true
  editable: true

Tệp này nên được lưu vào grafana/grafana.yml trong thư mục làm việc của bạn.

Cuối cùng, sao chép tệp Compose sau và lưu nó vào docker-compose.yml:

services:
  prometheus:
    image: prom/prometheus:latest
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
    ports:
      - 9090:9090
    volumes:
      - ./prometheus:/etc/prometheus
      - prometheus:/prometheus
    restart: unless-stopped
  grafana:
    image: grafana/grafana:latest
    ports:
      - ${GRAFANA_PORT:-3000}:3000
    environment:
      - GF_SECURITY_ADMIN_USER=${GRAFANA_USER:-admin}
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-grafana}
    volumes:
      - ./grafana:/etc/grafana/provisioning/datasources
    restart: unless-stopped
volumes:
  prometheus:

Sử dụng lệnh docker compose up để khởi động các dịch vụ và có thể thiết lập thông tin xác thực người dùng tùy chỉnh cho tài khoản Grafana của bạn:

$ GRAFANA_USER=demo GRAFANA_PASSWORD=foobar docker compose up -d

Bây giờ truy cập localhost:3000 trong trình duyệt để đăng nhập vào Grafana.

Tổng kết

Trong bài viết này, đã trình bày về cách Docker Compose cho phép bạn làm việc với stack gồm nhiều container Docker, cách tạo tệp Compose và xem xét một số ví dụ với WordPress và Prometheus/Grafana.

Bây giờ bạn có thể sử dụng Compose để tương tác với các container của ứng dụng của mình, đồng thời tránh các lệnh docker run dễ gặp lỗi. Một lệnh docker compose up duy nhất sẽ khởi động tất cả container trong stack của bạn, đảm bảo cấu hình nhất quán trong bất kỳ môi trường nào.

Thẻ:

- Ảnh đại diện bài viết -

Không có bình luận nào

Bình luận!

Địa chỉ email của bạn sẽ không được công khai. Các trường bắt buộc được đánh dấu *.