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à imageapp:latest
. Vì image này có thể chưa tồn tại, trườngbuild
đượ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ườngports
khai báo liên kết cổng sẽ áp dụng cho container, tương tự như flag-p
củadocker 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ụngdocker 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ởidocker 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ạydocker 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ụngdocker 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ởidocker 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ệnhlogs
. 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ườngREDIS_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.
- Ảnh đại diện bài viết -