Tìm kiếm trang web

Cách Cài đặt và Sử dụng Docker Compose trên Ubuntu


Giới thiệu

Docker đơn giản hóa quá trình quản lý các tiến trình ứng dụng trong các container. Mặc dù container có một số điểm tương đồng với máy ảo, nhưng chúng nhẹ hơn và tiết kiệm tài nguyên hơn. Điều này cho phép các nhà phát triển tách môi trường ứng dụng thành nhiều dịch vụ riêng biệt.

Đối với các ứng dụng phụ thuộc vào nhiều dịch vụ, việc điều phối tất cả các container để khởi động, giao tiếp và tắt cùng nhau có thể nhanh chóng trở nên khó quản lý. Docker Compose là một công cụ cho phép bạn chạy các môi trường ứng dụng đa container dựa trên các định nghĩa được đặt trong tệp YAML. Nó sử dụng các định nghĩa dịch vụ để xây dựng các môi trường có thể tùy chỉnh đầy đủ với nhiều container có thể chia sẻ mạng và khối dữ liệu.

Hướng dẫn này sẽ hướng dẫn bạn cài đặt Docker Compose trên một máy chủ Ubuntu và chạy một container đơn giản. Từ đó, bạn sẽ học cách xây dựng một môi trường đa dịch vụ sử dụng ứng dụng WordPress và cơ sở dữ liệu MySQL. Chúng tôi cũng sẽ đề cập đến các chủ đề nâng cao hơn, bao gồm việc mở rộng các dịch vụ, định nghĩa mạng tùy chỉnh và sử dụng các chỉ dẫn include theo mô-đun. Cuối cùng, bài viết này cung cấp hướng dẫn chuyển đổi từ docker-compose (v1) cũ sang docker compose (v2) hiện đại và một phần chi tiết về cách khắc phục các sự cố phổ biến như xung đột cổng và lỗi quyền.

Điểm quan trọng:

  • Docker Compose là một công cụ để định nghĩa và chạy các ứng dụng nhiều container bằng cách sử dụng một tệp YAML duy nhất để cấu hình dịch vụ, mạng và lưu trữ.
  • Docker Compose được cài đặt trên Ubuntu bằng cách thêm kho chính thức của Docker và sử dụng apt để cài đặt gói docker-compose-plugin.
  • Lệnh hiện đại là docker compose (có dấu cách). Lệnh này thay thế công cụ v1 docker-compose (có dấu gạch ngang) đã lỗi thời.
  • Sử dụng docker compose up -d để khởi động ứng dụng ở chế độ nền và docker compose down để dừng và gỡ bỏ tất cả các container và mạng của nó.
  • Chỉ thị depends_on giúp kiểm soát thứ tự khởi động của các dịch vụ, chẳng hạn như đảm bảo một container cơ sở dữ liệu khởi động trước một container ứng dụng web.
  • Những vấn đề thường gặp nhất là “Permission Denied” (giải quyết bằng cách thêm người dùng của bạn vào nhóm docker), xung đột cổng (giải quyết bằng cách thay đổi cổng máy chủ trong file YAML) và lỗi cú pháp YAML (giải quyết bằng cách chỉnh sửa thụt đầu dòng).
  • Bạn có thể chạy nhiều phiên bản của một dịch vụ bằng cách sử dụng lệnh docker compose up --scale <tên_dịch_vụ>=<số_lượng>.
  • Chỉ thị include cho phép bạn chia một tệp docker-compose.yml lớn thành các tệp cấu hình nhỏ hơn, dễ quản lý hơn.

Điều kiện tiên quyết

Để theo dõi bài viết này, bạn sẽ cần:

  • Truy cập vào một máy Ubuntu cục bộ hoặc máy chủ phát triển với tư cách người dùng không phải root nhưng có quyền sudo. Nếu bạn đang sử dụng máy chủ từ xa, nên có tường lửa đang hoạt động. Để thiết lập những điều này, vui lòng tham khảo Hướng dẫn Cài đặt Máy chủ Ban đầu của chúng tôi cho Ubuntu.
  • Docker đã được cài đặt trên máy chủ hoặc máy tính cục bộ của bạn, theo Bước 1 và Bước 2 của Hướng dẫn Cài đặt Docker trên Ubuntu – Hướng dẫn Từng bước.

Lưu ý: Bắt đầu từ Docker Compose v2, Docker đã chuyển sang sử dụng lệnh plugin CLI compose, thay cho docker-compose ban đầu như được hướng dẫn trong Hướng dẫn Cài đặt Docker Compose trên Ubuntu (Hướng dẫn Từng Bước). Mặc dù cách cài đặt có khác nhau, nhưng về cơ bản cách sử dụng thực tế là loại bỏ dấu gạch ngang trong các lệnh docker-compose để trở thành docker compose. Để biết chi tiết về sự tương thích đầy đủ, hãy tham khảo tài liệu chính thức của Docker về sự tương thích lệnh giữa compose mới và docker-compose cũ.

Bước 1 — Cài đặt Docker Compose

Có hai cách để cài đặt Docker Compose trên Ubuntu:

  • Cài đặt bằng kho lưu trữ
  • Cài đặt thủ công

Chúng ta sẽ thảo luận cả hai cách trong phần này.

Cài đặt Docker Compose bằng kho apt

Trước tiên, hãy thiết lập kho apt của Docker.

# Add Docker's official GPG key:

sudo apt-get update

sudo apt-get install ca-certificates curl

sudo install -m 0755 -d /etc/apt/keyrings

sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc

sudo chmod a+r /etc/apt/keyrings/docker.asc



# Add the repository to Apt sources:

echo \

  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \

  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \

  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update

Bây giờ, bạn có thể cài đặt Docker Compose bằng cách sử dụng lệnh sau:

sudo apt install docker-compose-plugin

Docker Compose hiện đã được cài đặt thành công trên hệ thống của bạn. Để xác minh rằng việc cài đặt đã thành công, bạn có thể chạy:

docker compose version

Bạn sẽ thấy kết quả tương tự như sau:

Docker Compose version v2.3.3

Cài đặt Docker Compose thủ công

Để đảm bảo bạn nhận được phiên bản ổn định cập nhật nhất của Docker Compose, bạn sẽ tải phần mềm này từ kho lưu trữ chính thức trên Github của nó.

Trước tiên, hãy xác nhận phiên bản mới nhất có sẵn trên trang phát hành của họ. Tại thời điểm viết bài này, phiên bản ổn định hiện tại nhất là v2.40.2.

Sử dụng lệnh sau để tải xuống:

mkdir -p ~/.docker/cli-plugins/

curl -SL https://github.com/docker/compose/releases/download/v2.40.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose

Tiếp theo, thiết lập quyền thích hợp để lệnh docker compose có thể thực thi được:

chmod +x ~/.docker/cli-plugins/docker-compose

Trong phần tiếp theo, bạn sẽ thấy cách thiết lập một tệp docker-compose.yml và khởi chạy một môi trường chứa bằng công cụ này.

Bước 2 — Thiết lập tệp docker-compose.yml

Để minh họa cách thiết lập một tệp docker-compose.yml và làm việc với Docker Compose, bạn sẽ tạo một môi trường máy chủ web sử dụng hình ảnh Nginx chính thức từ Docker Hub, kho lưu trữ Docker công cộng. Môi trường được đóng gói trong container này sẽ phục vụ một tệp HTML tĩnh duy nhất.

Bắt đầu bằng cách tạo một thư mục mới trong thư mục chính của bạn, sau đó di chuyển vào thư mục đó:

mkdir ~/compose-demo

cd ~/compose-demo

Trong thư mục này, thiết lập một thư mục ứng dụng để làm gốc tài liệu cho môi trường Nginx của bạn:

mkdir app

Sử dụng trình soạn thảo văn bản mà bạn ưa thích, tạo một tệp index.html mới trong thư mục app:

nano app/index.html

Đặt nội dung sau vào tệp này:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Docker Compose Demo</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/dark.min.css">
</head>
<body>

    <h1>This is a Docker Compose Demo Page.</h1>
    <p>This content is being served by an Nginx container.</p>

</body>
</html>

Lưu và đóng tệp khi bạn đã xong. Nếu bạn đang sử dụng nano, bạn có thể làm điều đó bằng cách nhấn CTRL+X, sau đó nhấn Y và ENTER để xác nhận.

Tiếp theo, tạo tệp docker-compose.yml:

nano docker-compose.yml

Chèn nội dung sau vào file docker-compose.yml của bạn:

services:
  web:
    image: nginx:alpine
    ports:
      - "8000:80"
    volumes:
      - ./app:/usr/share/nginx/html

Trong Docker Compose hiện đại, trường version là tùy chọn và thường bị bỏ qua, vì Compose có thể tự động phát hiện phiên bản cấu hình. Ví dụ trên không bao gồm trường version, đây là cách tiếp cận được khuyến nghị cho hầu hết các dự án mới. Bạn chỉ cần chỉ định version để tương thích với các dự án cũ.

Sau đó, bạn có khối services, nơi bạn thiết lập các dịch vụ là một phần của môi trường này. Trong trường hợp của bạn, bạn có một dịch vụ duy nhất gọi là web. Dịch vụ này sử dụng hình ảnh nginx:alpine và thiết lập chuyển hướng cổng với chỉ thị ports. Tất cả các yêu cầu trên cổng 8000 của máy host (hệ thống từ nơi bạn đang chạy Docker Compose) sẽ được chuyển hướng đến container web trên cổng 80, nơi Nginx sẽ đang chạy.

Chỉ thị volumes sẽ tạo một ổ đĩa chia sẻ giữa máy chủ và container. Điều này sẽ chia sẻ thư mục app cục bộ với container, và ổ đĩa này sẽ được đặt tại /usr/share/nginx/html bên trong container, từ đó sẽ ghi đè thư mục gốc mặc định của Nginx.

Lưu và đóng tệp.

Bạn đã thiết lập một trang demo và một tệp docker-compose.yml để tạo một môi trường máy chủ web được chứa trong container sẽ phục vụ trang đó. Trong bước tiếp theo, bạn sẽ khởi động môi trường này bằng Docker Compose.

Bước 3 — Chạy Docker Compose

Với tệp docker-compose.yml đã sẵn sàng, bạn có thể thực hiện Docker Compose để khởi động môi trường của mình. Lệnh sau sẽ tải xuống các hình ảnh Docker cần thiết, tạo một container cho dịch vụ web và chạy môi trường container ở chế độ nền:

docker compose up -d

Docker Compose sẽ đầu tiên tìm kiếm hình ảnh đã định nghĩa trên hệ thống cục bộ của bạn, và nếu không tìm thấy hình ảnh đó, nó sẽ tải hình ảnh từ Docker Hub. Bạn sẽ thấy kết quả như sau:

Creating network "compose-demo_default" with the default driver
Pulling web (nginx:alpine)...
alpine: Pulling from library/nginx
cbdbe7a5bc2a: Pull complete
10c113fb0c77: Pull complete
9ba64393807b: Pull complete
c829a9c40ab2: Pull complete
61d685417b2f: Pull complete
Digest: sha256:57254039c6313fe8c53f1acbf15657ec9616a813397b74b063e32443427c5502
Status: Downloaded newer image for nginx:alpine
Creating compose-demo_web_1 ... done

Lưu ý: Nếu bạn gặp lỗi “permission denied” khi chạy docker compose up, điều này thường có nghĩa là người dùng không phải root của bạn không có quyền truy cập vào socket của Docker daemon.

Theo mặc định, daemon Docker sẽ gắn với một socket Unix (/var/run/docker.sock) mà người sở hữu là người dùng root. Để khắc phục điều này, bạn phải thêm người dùng không phải root của mình vào nhóm docker, nhóm này được tạo trong quá trình cài đặt Docker.

Chạy lệnh sau để thêm người dùng của bạn vào nhóm docker:

sudo usermod -aG docker ${USER}

Sau khi chạy lệnh này, bạn sẽ cần đăng xuất và đăng nhập lại để các thay đổi nhóm có hiệu lực. Bạn cũng có thể kích hoạt các thay đổi cho phiên làm việc terminal hiện tại bằng cách gõ:

newgrp docker

Lệnh này sẽ giải quyết bất kỳ lỗi quyền nào liên quan đến Docker socket. Để xem hướng dẫn chi tiết, vui lòng tham khảo Bước 2 của Hướng dẫn Cài đặt Docker trên Ubuntu – Hướng dẫn Từng Bước.

Môi trường của bạn hiện đang chạy nền. Để xác nhận rằng container đang hoạt động, bạn có thể chạy:

docker compose ps

Lệnh này sẽ hiển thị thông tin về các container đang chạy và trạng thái của chúng, cũng như bất kỳ chuyển tiếp cổng nào đang được áp dụng:

       Name                     Command               State          Ports        
----------------------------------------------------------------------------------
compose-demo_web_1   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:8000->80/tcp

Bây giờ bạn có thể truy cập ứng dụng demo bằng cách trỏ trình duyệt của mình đến localhost:8000 nếu bạn đang chạy demo này trên máy cục bộ, hoặc tên_miền_hoặc_IP_của_máy_chủ_của_bạn:8000 nếu bạn đang chạy demo này trên máy chủ từ xa.

Bạn sẽ thấy một trang như thế này:

Volume chia sẻ mà bạn đã thiết lập trong file docker-compose.yml sẽ giữ các file trong thư mục app của bạn đồng bộ với thư mục gốc tài liệu của container. Nếu bạn thực hiện bất kỳ thay đổi nào đối với file index.html, chúng sẽ được container tự động nhận và do đó sẽ hiển thị trên trình duyệt của bạn khi bạn tải lại trang.

Trong bước tiếp theo, bạn sẽ thấy cách quản lý môi trường container của mình bằng các lệnh Docker Compose.

Bước 4 — Làm quen với các lệnh Docker Compose

Bạn đã thấy cách thiết lập một tệp docker-compose.yml và khởi chạy môi trường của mình với docker compose up. Bây giờ bạn sẽ thấy cách sử dụng các lệnh Docker Compose để quản lý và tương tác với môi trường chứa trong container của bạn.

Để kiểm tra các nhật ký được tạo bởi container Nginx của bạn, bạn có thể sử dụng lệnh logs:

docker compose logs

Bạn sẽ thấy kết quả tương tự như sau:

Attaching to compose-demo_web_1
web_1  | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
web_1  | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
web_1  | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
web_1  | 10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
web_1  | 10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
web_1  | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
web_1  | /docker-entrypoint.sh: Configuration complete; ready for start up
web_1  | 172.22.0.1 - - [02/Jun/2020:10:47:13 +0000] "GET / HTTP/1.1" 200 353 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36" "-"

Nếu bạn muốn tạm dừng việc thực thi môi trường mà không thay đổi trạng thái hiện tại của các container, bạn có thể sử dụng:

docker compose pause
Pausing compose-demo_web_1 ... done

Để tiếp tục thực thi sau khi tạm dừng:

docker compose unpause
Unpausing compose-demo_web_1 ... done

Lệnh stop sẽ chấm dứt việc thực thi container, nhưng nó sẽ không xóa bất kỳ dữ liệu nào liên quan đến các container của bạn:

docker compose stop
Stopping compose-demo_web_1 ... done

Nếu bạn muốn xóa các container, mạng và volume liên quan đến môi trường được container hóa này, hãy sử dụng lệnh down:

docker compose down
Removing compose-demo_web_1 ... done
Removing network compose-demo_default

Lưu ý rằng việc này sẽ không xóa hình ảnh cơ sở mà Docker Compose sử dụng để khởi tạo môi trường của bạn (trong trường hợp của bạn, nginx:alpine). Theo cách này, mỗi khi bạn khởi động lại môi trường với docker compose up, quá trình sẽ nhanh hơn nhiều vì hình ảnh đã có sẵn trên hệ thống của bạn.

Trong trường hợp bạn cũng muốn xoá ảnh cơ sở khỏi hệ thống của mình, bạn có thể sử dụng:

docker image rm nginx:alpine
Untagged: nginx:alpine
Untagged: nginx@sha256:b89a6ccbda39576ad23fd079978c967cecc6b170db6e7ff8a769bf2259a71912
Deleted: sha256:7d0cdcc60a96a5124763fddf5d534d058ad7d0d8d4c3b8be2aefedf4267d0270
Deleted: sha256:05a0eaca15d731e0029a7604ef54f0dda3b736d4e987e6ac87b91ac7aac03ab1
Deleted: sha256:c6bbc4bdac396583641cb44cd35126b2c195be8fe1ac5e6c577c14752bbe9157
Deleted: sha256:35789b1e1a362b0da8392ca7d5759ef08b9a6b7141cc1521570f984dc7905eb6
Deleted: sha256:a3efaa65ec344c882fe5d543a392a54c4ceacd1efd91662d06964211b1be4c08
Deleted: sha256:3e207b409db364b595ba862cdc12be96dcdad8e36c59a03b7b3b61c946a5741a

Lưu ý: Vui lòng tham khảo hướng dẫn của chúng tôi về Cách Cài đặt Docker trên Ubuntu – Hướng dẫn Từng Bước để có thêm tài liệu chi tiết về các lệnh Docker.

Bước 5 — Xây dựng Ứng dụng Đa Dịch vụ

Sức mạnh thực sự của Docker Compose nằm ở việc quản lý nhiều dịch vụ hoạt động cùng nhau. Ví dụ về Nginx chỉ là một dịch vụ đơn lẻ. Hãy tạo một ứng dụng thực tế hơn với nhiều dịch vụ: một trang web WordPress kết nối với cơ sở dữ liệu MySQL.

Cấu hình này bao gồm hai dịch vụ: wordpress (chạy ứng dụng) và db (chạy cơ sở dữ liệu). Chúng ta cũng sẽ sử dụng Docker volumes để đảm bảo dữ liệu cơ sở dữ liệu được giữ lại ngay cả khi container bị xóa.

Hãy tạo một thư mục mới cho ứng dụng này:

mkdir -p ~/compose-demo/wordpressapp

cd ~/compose-demo/wordpressapp

Trong ví dụ này, chúng ta không cần bất kỳ tệp cục bộ nào, vì các hình ảnh sẽ chứa tất cả phần mềm cần thiết.

Tạo một tệp docker-compose.yml mới:

nano docker-compose.yml

Dán cấu hình sau. Tệp này phức tạp hơn, vì vậy chúng ta sẽ xem xét từng phần.

services:
  db:
    image: mysql:8.0
    container_name: mysql_db
    volumes:
      - db_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: <^your_root_password_here<^>
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress_user
      MYSQL_PASSWORD: <^your_wordpress_password_here<^>
    restart: unless-stopped

  wordpress:
    image: wordpress:latest
    container_name: wordpress_app
    ports:
      - "8001:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress_user
      WORDPRESS_DB_PASSWORD: <^your_wordpress_password_here<^>
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - ./wp_content:/var/www/html/wp-content
    depends_on:
      - db
    restart: unless-stopped

volumes:
  db_data:

Lưu ý: Chúng tôi đã mã hóa cứng các giá trị mật khẩu ở đây để minh họa. Khi sử dụng trong môi trường thực tế, hãy chắc chắn sử dụng các biến môi trường trong tệp .env để tránh việc lộ thông tin đăng nhập của bạn.

Lưu và đóng tệp. Hãy nhớ thay thế các chỗ giữ chỗ <^...^> bằng mật khẩu mạnh và an toàn.

Hãy phân tích các chỉ thị mới trong tập tin này:

  • services: Khối này vẫn định nghĩa các container của chúng ta. Hiện tại chúng ta có hai container: db và wordpress.
  • image: Chúng tôi đang sử dụng mysql:8.0 cho cơ sở dữ liệu và hình ảnh wordpress:latest cho ứng dụng.
  • container_name: Thiết lập một tên cụ thể, dễ đọc cho container, dễ tham chiếu hơn so với các tên được tạo tự động.
  • environment: Đây là danh sách các biến môi trường được truyền vào container. Hình ảnh mysql sử dụng chúng để thiết lập mật khẩu root và tạo cơ sở dữ liệu cùng người dùng ban đầu. Hình ảnh wordpress sử dụng chúng để biết cách kết nối với cơ sở dữ liệu của nó.
  • volumes (cấp độ dịch vụ):

    • Đối với dịch vụ db, db_data:/var/lib/mysql ánh xạ một volume do Docker quản lý có tên db_data tới thư mục dữ liệu MySQL bên trong container. Điều này giữ cho dữ liệu của bạn an toàn.
    • Đối với dịch vụ wordpress, ./wp_content:/var/www/html/wp-content ánh xạ một thư mục cục bộ (wp_content) tới thư mục nội dung của WordPress. Điều này cho phép bạn chỉnh sửa trực tiếp các giao diện và plugin từ máy chủ của bạn.
  • depends_on: Đây là lệnh cho Compose biết khởi động dịch vụ db trước khi khởi động dịch vụ wordpress. Điều này rất quan trọng, vì WordPress sẽ không thể hoạt động nếu không tìm thấy cơ sở dữ liệu khi khởi động.
  • volumes (cấp cao nhất): Khối này định nghĩa các volume có tên. db_data: tạo một volume do Docker quản lý, đây là cách ưu tiên để xử lý dữ liệu bền vững.

Bây giờ, hãy khởi chạy ứng dụng đa dịch vụ này:

docker compose up -d

Compose sẽ kéo cả hai hình ảnh mysql và wordpress, sau đó tạo các container, bắt đầu với dịch vụ db trước.

Creating network "compose-demo_default" with the default driver
Creating volume "compose-demo_db_data" with default driver
Pulling db (mysql:8.0)...
...
Pulling wordpress (wordpress:latest)...
...
Creating mysql_db        ... done
Creating wordpress_app ... done

Bây giờ bạn có thể truy cập trang WordPress mới của mình bằng cách điều hướng đến localhost:8001 hoặc <^your_server_domain_or_IP<^>:8001 trong trình duyệt. Bạn sẽ thấy màn hình cài đặt WordPress. Để có một ví dụ chi tiết hơn, hãy xem bài viết của chúng tôi về Cách Cài Đặt WordPress Với Docker Compose

Bước 6 — Sử dụng Docker Compose cho phát triển AI/ML

Ngoài các ứng dụng web, Docker Compose là một công cụ cực kỳ hữu ích để tạo ra các môi trường khoa học dữ liệu và học máy (AI/ML) có thể tái tạo. Các dự án AI/ML nổi tiếng với các phụ thuộc phức tạp, bao gồm các phiên bản Python cụ thể, các thư viện như TensorFlow hoặc PyTorch, và các trình điều khiển cấp hệ thống như Bộ công cụ NVIDIA CUDA. Docker Compose ghi lại toàn bộ môi trường này trong các tệp cấu hình, giải quyết vấn đề "nó chỉ hoạt động trên máy của tôi", điều này rất quan trọng cho nghiên cứu có thể tái tạo.

Trong ví dụ này, bạn sẽ tạo một môi trường AI/ML đa dịch vụ bao gồm:

  • JupyterLab: Một môi trường để phát triển tương tác, được xây dựng từ một Dockerfile tùy chỉnh.
  • PostgreSQL: Một container cơ sở dữ liệu để lưu trữ kết quả thí nghiệm và siêu dữ liệu.
  • Truy cập GPU: Container JupyterLab sẽ được cấu hình để truy cập GPU NVIDIA trên máy chủ, một yêu cầu phổ biến cho việc huấn luyện mô hình.

Yêu cầu trước: Ví dụ này yêu cầu máy chủ của bạn có GPU NVIDIA và đã cài đặt NVIDIA Container Toolkit. Nếu không có, container sẽ không khởi động khi yêu cầu tài nguyên GPU.

Cấu trúc tệp

Đầu tiên, tạo một thư mục cho dự án của bạn. Bên trong đó, bạn sẽ tạo một tệp docker-compose.yml, một thư mục jupyter, một Dockerfile cho Jupyter và một tệp requirements.txt. Cấu trúc sẽ trông giống như sau:

ai-project/
├── docker-compose.yml
└── jupyter/
    ├── Dockerfile
    └── requirements.txt

Tạo các tệp thành phần

1. Tệp requirements.txt

Tệp này liệt kê các gói Python cho môi trường khoa học dữ liệu của bạn.

nano jupyter/requirements.txt

Thêm các gói cần thiết của bạn. Trong ví dụ này, chúng ta sẽ bao gồm các thư viện cho việc xử lý dữ liệu, kết nối cơ sở dữ liệu và một framework học sâu.

pandas
scikit-learn
tensorflow
jupyterlab
psycopg2-binary

Lưu và đóng tệp.

2. Dockerfile

Tệp này định nghĩa dịch vụ JupyterLab tùy chỉnh của bạn. Nó sử dụng một hình ảnh Jupyter chính thức làm cơ sở và cài đặt các gói từ requirements.txt.

nano jupyter/Dockerfile

Dán nội dung sau:

# Start from a base image that includes Jupyter and scientific libraries
FROM jupyter/scipy-notebook:latest

# Copy our local requirements file into the container
COPY requirements.txt /tmp/requirements.txt

# Install the Python packages
RUN pip install --no-cache-dir -r /tmp/requirements.txt

Tệp này hướng dẫn Docker sử dụng jupyter/scipy-notebook làm điểm khởi đầu, sao chép requirements.txt của bạn vào trong container, và sau đó sử dụng pip để cài đặt các gói.

Định nghĩa môi trường với docker-compose.yml

Bây giờ, tạo tệp docker-compose.yml chính. Tệp này sẽ điều phối cả dịch vụ db và dịch vụ jupyter tùy chỉnh của bạn.

nano docker-compose.yml

Dán cấu hình sau đây, thay thế các chỗ giữ chỗ <^...^> bằng thông tin đăng nhập bảo mật của bạn.

services:
  db:
    image: postgres:15-alpine
    container_name: ai_postgres_db
    environment:
      POSTGRES_USER: <^your_db_user^>
      POSTGRES_PASSWORD: <^your_db_pass^>
      POSTGRES_DB: experiments
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U <^your_db_user^> -d experiments"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped
    networks:
      - ai_net

  jupyter:
    build: ./jupyter
    container_name: ai_jupyter_lab
    ports:
      - "8888:8888"
    volumes:
      - ./notebooks:/home/jovyan/work
    environment:
      - POSTGRES_HOST=db
      - POSTGRES_USER=<^your_db_user^>
      - POSTGRES_PASSWORD=<^your_db_pass^>
      - POSTGRES_DB=experiments
    restart: unless-stopped
    networks:
      - ai_net
    depends_on:
      db:
        condition: service_healthy
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

volumes:
  postgres_data:
    name: ai_project_data

networks:
  ai_net:
    driver: bridge

Tệp cấu hình này giới thiệu một số khái niệm quan trọng:

  • build: ./jupyter: Điều này cho Docker Compose biết để xây dựng một image tùy chỉnh cho dịch vụ jupyter. Nó sẽ tìm một Dockerfile bên trong thư mục ./jupyter được chỉ định.
  • volumes: - ./notebooks:/home/jovyan/work: Dòng này gắn kết một thư mục cục bộ có tên notebooks vào trong container. Điều này đảm bảo tất cả các notebook Jupyter bạn tạo sẽ được lưu trực tiếp trên máy chủ của bạn, giữ nguyên chúng ngay cả khi container bị xóa.
  • healthcheck: Như đã thấy trong phần khắc phục sự cố, kiểm tra này xác nhận rằng dịch vụ db không chỉ đang chạy, mà còn sẵn sàng chấp nhận các kết nối trước khi các dịch vụ khác bắt đầu.
  • depends_on: ... condition: service_healthy: Điều này chỉ dẫn dịch vụ jupyter chờ cho đến khi kiểm tra sức khỏe của db thành công trước khi khởi động.
  • deploy.resources.reservations.devices: Đây là phần khối yêu cầu truy cập GPU.

    • driver: nvidia: Chỉ định driver của máy chủ để sử dụng.
    • count: 1: Yêu cầu một GPU.
    • capabilities: [gpu]: Đảm bảo rằng container có các khả năng cần thiết để sử dụng GPU.

Vận hành Môi trường AI/ML

Khi các tệp của bạn đã sẵn sàng, bạn có thể xây dựng và chạy các dịch vụ.

  1. Từ thư mục ai-project của bạn, chạy lệnh docker compose up. Bạn phải thêm cờ --build lần đầu tiên để cho Compose biết xây dựng hình ảnh jupyter tùy chỉnh của bạn.

    docker compose up -d --build
  2. Compose sẽ đầu tiên xây dựng hình ảnh jupyter (có thể mất vài phút khi nó tải TensorFlow), sau đó kéo hình ảnh postgres, và cuối cùng khởi động cả hai container.

    [+] Building 5.8s (9/9) FINISHED
     => [internal] load build definition from Dockerfile
    ...
    [+] Running 3/3
     ✔ Network ai-project_ai_net        Created
     ✔ Container ai_postgres_db         Started
     ✔ Container ai_jupyter_lab         Started
  3. Bây giờ bạn có thể truy cập giao diện JupyterLab bằng cách vào http://localhost:8888 (hoặc <^your_server_ip^>:8888) trong trình duyệt của bạn. Bạn sẽ được yêu cầu nhập mã thông báo, mà bạn có thể lấy từ nhật ký của container:

    docker compose logs jupyter

    Tìm một dòng tương tự như http://127.0.0.1:8888/lab?token=a1b2c3d4e5f6...

  4. Bên trong một notebook Jupyter, bạn giờ có thể kết nối với cơ sở dữ liệu của mình bằng hostname db và thông tin đăng nhập mà bạn đã cung cấp. Môi trường của bạn cũng có quyền truy cập vào GPU của máy chủ để huấn luyện mô hình.

Mở rộng Dịch vụ và Sử dụng Mạng Tùy chỉnh

Docker Compose bao gồm các tính năng để mở rộng dịch vụ và quản lý các mạng mà chúng kết nối.

Mở rộng Dịch vụ với các Bản sao

Hãy tưởng tượng máy chủ web Nginx của bạn từ Bước 2 đang nhận quá nhiều lưu lượng. Bạn có thể mở rộng dịch vụ web để chạy nhiều phiên bản container. Docker Compose có thể quản lý việc này một cách tự động.

Có hai cách chính để mở rộng một dịch vụ.

1. Sử dụng khóa replicas (Ưu tiên trong v2)

Bạn có thể xác định số lượng instance mong muốn trực tiếp trong tệp docker-compose.yml của mình bằng cách sử dụng các khóa deploy và replicas. Tính năng này ban đầu là một phần của Docker Swarm nhưng hiện nay đã có sẵn cho các triển khai Compose tiêu chuẩn.

Chỉnh sửa docker-compose.yml của Nginx của bạn từ Bước 2:

services:
  web:
    image: nginx:alpine
    ports:
      - "8000:80"
    volumes:
      - ./app:/usr/share/nginx/html
    deploy:
      replicas: 3

Khi bạn chạy docker compose up -d, Compose sẽ tạo ba container web. Tuy nhiên, bạn sẽ gặp một vấn đề: cả ba container sẽ cố gắng liên kết với cổng host 8000. Điều này sẽ gây ra lỗi “cổng đã được cấp phát” cho container thứ hai và thứ ba. Để giải quyết điều này trong môi trường sản xuất, bạn nên loại bỏ việc ánh xạ ports từ dịch vụ web. Bằng cách này, các container web chỉ có thể truy cập bên trong mạng Docker. Một dịch vụ cân bằng tải riêng biệt sẽ là dịch vụ duy nhất có cổng công khai, và nó sẽ phân phối lưu lượng đến ba bản sao web.

Một cấu hình ví dụ sẽ trông như thế này (đây là ví dụ nâng cao):

services:
  web:
    image: nginx:alpine
    # No 'ports' mapping here.
    # The service is only accessible inside the 'web-net' network.
    volumes:
      - ./app:/usr/share/nginx/html
    deploy:
      replicas: 3
    networks:
      - web-net

  load_balancer:
    image: nginx:latest
    ports:
      - "80:80" # The load_balancer is the only service with a public port.
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf # A config file that load balances to 'web'.
    networks:
      - web-net
    depends_on:
      - web

networks:
  web-net:

Trong cấu hình này, load_balancer lắng nghe trên cổng 80 và chuyển tiếp các yêu cầu đến các web_1, web_2 và web_3 nội bộ.

Khóa replicas này hữu ích nhất khi được sử dụng với một reverse proxy (như Traefik hoặc một instance Nginx khác) có thể cân bằng tải các yêu cầu giữa các bản sao trong mạng Docker, mà không cần mỗi bản sao phải mở một cổng trên máy chủ.

2. Sử dụng cờ --scale

Một phương pháp phổ biến để mở rộng là cờ --scale, được kế thừa từ Compose v1. Cờ này được áp dụng khi chạy và ghi đè mọi khóa replicas.

Tuy nhiên, bạn phải cẩn thận với các định nghĩa cổng. Nếu dịch vụ của bạn định nghĩa ánh xạ cổng máy chủ cố định (ví dụ, "8000:80"), việc chạy docker compose up --scale sẽ gây ra lỗi cho container thứ hai và thứ ba vì tất cả chúng đều cố gắng liên kết với cùng một cổng máy chủ.

Để sử dụng --scale cho các dịch vụ mở cổng, bạn không được sử dụng ánh xạ cổng host cố định.

  • Tùy chọn 1: Ánh xạ tới Cổng Máy chủ Ngẫu nhiên (Tốt cho Phát triển)

    Bạn có thể sửa đổi docker-compose.yml của mình để chỉ định riêng cổng của container. Điều này sẽ cho Docker biết ánh xạ cổng 80 trong mỗi container tới một cổng ngẫu nhiên có sẵn trên máy chủ của bạn.

    services:
      web:
        image: nginx:alpine
        ports:
          - "80" # No fixed host port
        volumes:
          - ./app:/usr/share/nginx/html

    Bây giờ, khi bạn chạy lệnh scale:

    docker compose up -d --scale web=3

    Chạy docker compose ps để xem kết quả. Mỗi container sẽ chạy trên một cổng máy chủ khác nhau được gán ngẫu nhiên:

          Name                     Command               State          Ports        
    ----------------------------------------------------------------------------------
    compose-demo_web_1   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:49154->80/tcp
    compose-demo-web_2   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:49155->80/tcp
    compose-demo-web_3   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:49156->80/tcp
  • docker-compose.yml - Đúng cho --scale

  • Lựa chọn 2: Sử dụng Reverse Proxy (Tốt cho môi trường sản xuất)

    Giải pháp khác, như đã đề cập trong phần replicas, là hoàn toàn loại bỏ chỉ thị ports khỏi dịch vụ web. Sau đó, bạn sẽ sử dụng một container cân bằng tải riêng (chỉ có cổng công cộng duy nhất) để quản lý và phân phối lưu lượng đến các bản sao đã mở rộng trong mạng Docker.

    Để dừng và xóa cả ba container, lệnh vẫn giữ nguyên:

    docker compose down

Cấu hình mạng tùy chỉnh

Theo mặc định, Docker Compose tạo một mạng bridge duy nhất cho ứng dụng của bạn. Mỗi dịch vụ trong tệp đều được gắn vào mạng này, đó là lý do tại sao container wordpress có thể tìm thấy container db chỉ bằng cách sử dụng tên dịch vụ của nó (db).

Tuy nhiên, bạn có thể định nghĩa các mạng tùy chỉnh riêng của mình để kiểm soát và cách ly tốt hơn.

Hãy chỉnh sửa ví dụ WordPress để sử dụng một mạng bridge tùy chỉnh.

services:
  db:
    image: mysql:8.0
    ...
    networks:
      - app_net

  wordpress:
    image: wordpress:latest
    ...
    ports:
      - "8001:80"
    depends_on:
      - db
    networks:
      - app_net

volumes:
  db_data:

networks:
  app_net:
    driver: bridge

Đây là những gì chúng tôi đã thêm:

  • networks (cấp cao nhất): Điều này định nghĩa một mạng mới có tên app_net và chỉ định rằng nó nên sử dụng trình điều khiển bridge tiêu chuẩn.
  • networks (cấp độ dịch vụ): Dưới cả db và wordpress, khóa này gắn chúng vào mạng app_net của chúng tôi.

Nếu bạn có một dịch vụ thứ ba (như một công cụ phân tích) mà bạn không kết nối với app_net, nó sẽ hoàn toàn bị cô lập và không thể giao tiếp với các container db hoặc wordpress.

Đối với cụm nhiều máy chủ với Docker Swarm, bạn sẽ thay đổi driver từ bridge sang overlay. Bridge driver dùng để giao tiếp giữa các container trên một máy chủ, đây là chuẩn cho hầu hết các trường hợp sử dụng Docker Compose.

Sử dụng các tệp cấu hình mô-đun

Khi các ứng dụng của bạn phát triển, tệp docker-compose.yml của bạn có thể trở nên lớn và khó quản lý. Docker Compose hỗ trợ chỉ thị include, cho phép bạn tách cấu hình của mình ra nhiều tệp.

Hãy tưởng tượng bạn muốn tách riêng định nghĩa WordPress và cơ sở dữ liệu của mình, và có thể có một docker-compose.override.yml chung cho các thiết lập chỉ dành cho phát triển (như liên kết một cổng trên cơ sở dữ liệu).

Cấu trúc thư mục của bạn có thể trông như sau:

compose-demo/
├── docker-compose.yml
├── docker-compose.db.yml
├── docker-compose.web.yml

Tệp này sẽ chỉ định nghĩa dịch vụ db.

services:
  db:
    image: mysql:8.0
    container_name: mysql_db
    volumes:
      - db_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: <^your_root_password_here<^>
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress_user
      MYSQL_PASSWORD: <^your_wordpress_password_here<^>
    restart: unless-stopped

volumes:
  db_data:

Tệp này sẽ chỉ định nghĩa dịch vụ wordpress.

services:
  wordpress:
    image: wordpress:latest
    container_name: wordpress_app
    ports:
      - "8001:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress_user
      WORDPRESS_DB_PASSWORD: <^your_wordpress_password_here<^>
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - ./wp_content:/var/www/html/wp-content
    depends_on:
      - db
    restart: unless-stopped

Bây giờ, tệp docker-compose.yml chính của bạn trở nên rất đơn giản. Nó chỉ sử dụng include để lấy các tệp khác vào.

include:
  - docker-compose.db.yml
  - docker-compose.web.yml

Khi bạn chạy docker compose up -d, Compose sẽ đọc cả ba tệp, hợp nhất các cấu hình và khởi động các dịch vụ db và wordpress giống hệt như thể chúng được định nghĩa trong một tệp duy nhất. Cách tiếp cận này giúp cấu hình của bạn trở nên mô-đun hơn và có thể tái sử dụng nhiều lần.

Lưu ý: Chỉ thị include: yêu cầu Docker Compose phiên bản 2.20 trở lên. Nếu hệ thống của bạn đang dùng phiên bản cũ hơn, bạn có thể kết hợp các tệp bằng cách sử dụng docker compose -f docker-compose.db.yml -f docker-compose.web.yml up.

Di chuyển từ docker-compose (v1) sang docker compose (v2)

Lệnh docker compose mà bạn đã cài đặt ở Bước 1 là “Compose v2.” Đây là một plugin dựa trên Go được tích hợp trực tiếp vào CLI của Docker.

Phiên bản gốc, “Compose v1,” là một công cụ Python riêng biệt được gọi bằng docker-compose (có dấu gạch ngang). Tính đến tháng 7 năm 2023, Compose v1 không còn được hỗ trợ và đã bị ngừng sử dụng.

Các thay đổi chính và khả năng tương thích ngược

  • Lệnh: Thay đổi rõ ràng nhất là lệnh. Dấu gạch ngang (docker-compose) giờ được thay bằng dấu cách (docker compose).
  • Cài đặt: v1 được cài đặt bằng pip. v2 đi kèm với Docker Desktop hoặc được cài đặt như một plugin CLI, như bạn đã làm ở Bước 1.
  • Tệp YAML: Các tệp docker-compose.yml của bạn gần như tương thích ngược 100%. Compose v2 hoàn toàn hỗ trợ các phiên bản tệp 3.x. Bạn không cần phải thay đổi các tệp YAML của mình để di chuyển, chỉ cần thay đổi các lệnh của bạn.
  • --project-name flag: Cờ -p vẫn hoạt động, nhưng cờ đầy đủ bây giờ là --project-name thay vì --project_name.

Hướng dẫn Di cư Lệnh

Hầu hết các lệnh đều giống nhau, chỉ khác là dấu gạch nối đã bị loại bỏ. Dưới đây là một bảng so sánh các lệnh v1 phổ biến với các lệnh tương đương của v2.

Lệnh docker-compose (phiên bản 1)Lệnh docker compose (v2)Ghi chú
docker-compose up -d
docker compose up -d
Không thay đổi về cú pháp.
docker-compose down
docker compose down
Không thay đổi về cú pháp.
docker-compose ps
docker compose ps
Không thay đổi về cú pháp.
docker-compose logs
docker compose logs
Không thay đổi về cú pháp.
docker-compose stop
docker compose stop
Không thay đổi về cú pháp.
docker-compose build
docker compose build
Không thay đổi về cú pháp.
docker-compose exec web bash
docker compose exec web bash
Không thay đổi về cú pháp.
docker-compose run web bash
docker compose run web bash
run tạo một container mới. exec chạy trong một container đã tồn tại.
docker-compose up --scale web=3
docker compose up --scale web=3
Cờ --scale vẫn được hỗ trợ trong v2.

Như bạn có thể thấy, đối với hầu hết các sử dụng hàng ngày, sự thay đổi duy nhất là docker-compose -> docker compose. Nếu bạn sử dụng các script, bạn có thể cập nhật chúng bằng cách đơn giản là loại bỏ dấu gạch ngang.

Nếu bạn vẫn còn cài đặt docker-compose v1 cũ, bạn có thể gỡ bỏ nó để tránh nhầm lẫn:

sudo pip3 uninstall docker-compose

Hoặc, nếu nó được cài đặt bởi apt:

sudo apt remove docker-compose

Khắc phục các sự cố phổ biến với Docker Compose

Khi làm việc với Docker Compose, bạn có thể gặp phải các vấn đề liên quan đến cú pháp tệp, quyền truy cập hoặc xung đột khi chạy container. Hầu hết các sự cố có thể được chẩn đoán và giải quyết bằng cách kiểm tra cấu hình, quyền truy cập và nhật ký container một cách có hệ thống.

Hãy xem một số lỗi phổ biến nhất và cách khắc phục chúng.

Quyền bị từ chối (Docker Socket)

Đây là một trong những lỗi thường gặp nhất đối với người dùng Docker mới. Bạn chạy docker compose up và thấy thông báo lỗi liên quan đến socket của Docker daemon.

permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
  • Vấn đề: Người dùng không phải root của bạn không có quyền để giao tiếp với Docker daemon, vốn chạy dưới quyền root.

  • Giải pháp: Bạn phải thêm người dùng của mình vào nhóm docker, nhóm này đã được tạo trong quá trình cài đặt Docker.

    1. Thêm người dùng của bạn vào nhóm docker:

      sudo usermod -aG docker ${USER}
    2. Để quyền thành viên nhóm mới có hiệu lực, bạn phải đăng xuất và đăng nhập lại.

    3. Ngoài ra, bạn có thể kích hoạt các thay đổi nhóm cho phiên đầu cuối hiện tại của mình bằng cách gõ:

      newgrp docker

    Điều này nên giải quyết mọi lỗi về quyền liên quan đến socket của Docker.

Lỗi Cú pháp và Thụt lề trong YAML

Docker Compose không chạy được và báo cáo rằng tệp docker-compose.yml của bạn không hợp lệ.

ERROR: The Compose file './docker-compose.yml' is invalid because:
services.web.ports contains an invalid type, it should be a list

Hoặc :

mapping values are not allowed in this context at line 5
  • Vấn đề: Tệp docker-compose.yml phụ thuộc vào cú pháp YAML nghiêm ngặt. Sai lầm phổ biến nhất là thụt lề không chính xác. YAML sử dụng khoảng trắng, không phải tab, để định nghĩa cấu trúc.
  • Giải pháp: Hãy kiểm tra cẩn thận việc thụt lề trong file docker-compose.yml của bạn.

    • Tiêu chuẩn là hai khoảng trắng cho mỗi cấp độ thụt lề.
    • Đảm bảo tất cả các khóa ở cùng cấp (như version, services, volumes) bắt đầu ở cùng một cột.
    • Đảm bảo tất cả các khóa con (như image, ports, volumes dưới một dịch vụ) được thụt vào nhiều hơn hai khoảng trắng so với dịch vụ cha của chúng (ví dụ: web).
    • Sử dụng trình soạn thảo mã với công cụ kiểm tra YAML để tự động đánh dấu các lỗi cú pháp.

Xung đột cảng

Bạn cố gắng khởi động môi trường của mình, nhưng lệnh thất bại với thông báo lỗi rằng địa chỉ đã được sử dụng.

Error starting userland proxy: listen tcp 0.0.0.0:8000: bind: address already in use
  • Vấn đề: Một tiến trình khác trên máy chủ của bạn đã đang lắng nghe trên cổng mà bạn đang cố gắng ánh xạ (trong trường hợp này là cổng 8000). Điều này thường là một container Docker khác hoặc một máy chủ phát triển cục bộ.

  • Giải pháp: Bạn có hai lựa chọn:

    1. Dừng tiến trình khác. Bạn có thể tìm tiến trình đang sử dụng cổng bằng lệnh sau:

      sudo lsof -i :8000

      Nếu đó là một container Docker khác, hãy dừng nó bằng docker stop <container_id>.

    2. Thay đổi cổng host trong tệp docker-compose.yml của bạn. Đây thường là cách sửa đơn giản nhất. Thay đổi ánh xạ ports từ "8000:80" sang một cổng khác, chẳng hạn như "8001:80".

      ...
      ports:
        - "8001:80" # Changed from 8000
      ...
    3. docker-compose.yml

Dịch vụ gặp lỗi với Kết nối bị từ chối (depends_on)

Trong một ứng dụng đa dịch vụ (như ví dụ WordPress và MySQL), container wordpress khởi động nhưng nhật ký của nó hiển thị Connection refused hoặc MySQL server has gone away khi cố gắng kết nối với dịch vụ db.

  • Vấn đề: Bạn đã sử dụng depends_on, nhưng chỉ thị này chỉ chờ container db khởi động. Nó không chờ ứng dụng MySQL bên trong container được khởi tạo hoàn toàn và sẵn sàng nhận kết nối.

  • Giải pháp: Ứng dụng (trong trường hợp này là WordPress) phải được cấu hình để thử kết nối lại. Hầu hết các hình ảnh hiện đại đều có logic thử lại tích hợp sẵn. Đối với những hình ảnh không có, bạn phải triển khai kiểm tra sức khỏe.

    Bạn có thể thêm một healthcheck vào dịch vụ db của bạn. Dịch vụ wordpress sau đó có thể được cấu hình trong depends_on để chờ cơ sở dữ liệu trở nên “khỏe mạnh,” chứ không chỉ “đã khởi động.”

Ví dụ kiểm tra sức khỏe cho MySQL:

services:
  db:
    image: mysql:8.0
    ...
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p<^your_root_password_here<^>"]
      interval: 10s
      timeout: 5s
      retries: 5

  wordpress:
    image: wordpress:latest
    ...
    depends_on:
      db:
        condition: service_healthy # This now waits for the healthcheck
    ...

Lỗi với Volume và Bind Mount

Bạn có thể gặp lỗi quyền trong nhật ký container của mình, hoặc nhận thấy rằng thư mục dữ liệu của container trống.

Quyền truy cập Tệp/Thư mục

  • Vấn đề: Container chạy dưới một người dùng cụ thể (ví dụ, www-data với ID Người dùng 33), nhưng thư mục trên máy chủ mà bạn gắn vào do người dùng của bạn sở hữu (ví dụ, ubuntu với ID Người dùng 1000). Người dùng trong container không có quyền ghi vào thư mục trên máy chủ.

  • Giải pháp: Thay đổi quyền sở hữu của thư mục host để khớp với ID người dùng bên trong container. Bạn có thể tìm ID người dùng của container bằng cách chạy docker compose exec <service_name> id. Ví dụ, nếu ID là 33 (thường dùng cho www-data):

    sudo chown -R 33:33 ./wp_content

Vấn đề về đường dẫn tương đối

  • Vấn đề: docker-compose.yml của bạn sử dụng đường dẫn tương đối như ./app, nhưng tệp index.html của bạn không được phục vụ.

  • Giải pháp: Docker Compose diễn giải các đường dẫn tương đối từ thư mục mà bạn chạy lệnh docker compose up. Luôn chạy các lệnh Compose từ cùng một thư mục chứa tệp docker-compose.yml của bạn.

Mạng và Khám phá Dịch vụ

Nhật ký container ứng dụng của bạn hiển thị Host not found hoặc Could not resolve host: db.

  • Vấn đề: Container wordpress của bạn không thể tìm thấy container db trên mạng.
  • Giải pháp: Theo mặc định, Docker Compose tạo một mạng và gắn tất cả các dịch vụ vào đó. Các dịch vụ có thể tìm thấy nhau bằng cách sử dụng tên dịch vụ của chúng.

    1. Kiểm tra Tên Dịch vụ: Đảm bảo tên máy chủ trong cấu hình ứng dụng của bạn (ví dụ: biến môi trường WORDPRESS_DB_HOST) giống hệt tên dịch vụ trong docker-compose.yml của bạn (ví dụ: db).

    2. Kiểm tra Mạng: Sử dụng docker compose ps để tìm tên dự án của bạn (ví dụ, compose-demo). Sau đó, kiểm tra mạng mặc định:

      docker network inspect compose-demo_default

      Dữ liệu xuất ra JSON sẽ liệt kê tất cả các container được kết nối với mạng này. Nếu một trong các dịch vụ của bạn bị thiếu, hãy kiểm tra tệp docker-compose.yml của bạn để xem có bất kỳ cấu hình networks tùy chỉnh nào có thể đang cô lập nó không.

Mâu thuẫn lệnh (v1 so với v2)

Bạn gõ docker-compose up (có dấu gạch nối) và thấy lỗi “không tìm thấy lệnh”.

docker-compose: command not found
  • Vấn đề: Bạn đang cố gắng sử dụng lệnh cho Compose v1, một công cụ Python đã bị ngừng hỗ trợ. Hướng dẫn này cài đặt Compose v2, một plugin được tích hợp trực tiếp vào Docker CLI.
  • Giải pháp: Sử dụng lệnh đúng bằng cách thay dấu gạch ngang (-) bằng một khoảng trắng.
docker-compose (v1)docker compose (v2)
docker-compose updocker compose up
docker-compose downdocker compose down
docker-compose psdocker compose ps
docker-compose logsdocker compose logs

Luôn sử dụng docker compose (có khoảng trắng) khi làm theo hướng dẫn này.

Câu hỏi thường gặp

1. Docker Compose được sử dụng để làm gì?

Docker Compose là một công cụ để định nghĩa và chạy các ứng dụng Docker nhiều container.

Nó sử dụng một tệp YAML duy nhất (mặc định là docker-compose.yml) để cấu hình tất cả các thành phần của ứng dụng của bạn, được gọi là dịch vụ. Tệp này cũng định nghĩa các mạng cho phép các dịch vụ giao tiếp với nhau và các volume được sử dụng cho dữ liệu lâu dài.

Với một tệp duy nhất này, bạn có thể quản lý toàn bộ ngăn xếp ứng dụng của mình bằng các lệnh đơn giản:

  • docker compose up khởi động và chạy toàn bộ ứng dụng của bạn, bao gồm tất cả các container, mạng và volume được chỉ định.
  • docker compose down dừng và xóa tất cả các container, mạng và volume được tạo bởi ứng dụng của bạn.

Nó hữu ích nhất cho việc quản lý các ứng dụng cần nhiều thành phần, chẳng hạn như một trang web cần máy chủ web (như Nginx), phần backend của ứng dụng (như WordPress), và cơ sở dữ liệu (như MySQL).

2. Tôi cài đặt Docker Compose trên Ubuntu như thế nào?

Phương pháp được khuyến nghị là cài đặt Docker Compose như một plugin cho Docker CLI. Điều này được thực hiện bằng cách cài đặt gói docker-compose-plugin từ kho apt chính thức của Docker.

  1. Đầu tiên, hãy đảm bảo rằng bạn đã theo dõi tài liệu chính thức của Docker để thiết lập kho apt của Docker trên hệ thống Ubuntu của bạn.

  2. Cập nhật danh sách gói của bạn:

    sudo apt update
  3. Cài đặt plugin Docker Compose:

    sudo apt install docker-compose-plugin
  4. Xác minh việc cài đặt bằng cách kiểm tra phiên bản. Lệnh sử dụng một dấu cách, không phải dấu gạch ngang.

    docker compose version

3. Làm thế nào để tôi cập nhật Docker Compose lên phiên bản mới nhất?

Bởi vì Docker Compose (v2) được cài đặt như một gói hệ thống sử dụng apt, bạn có thể cập nhật nó bằng quy trình cập nhật phần mềm tiêu chuẩn của Ubuntu.

  1. Cập nhật chỉ mục gói cục bộ của bạn:

    sudo apt update
  2. Chạy nâng cấp toàn hệ thống, bao gồm cả plugin Compose:

    sudo apt upgrade

    Ngoài ra, nếu bạn chỉ muốn cập nhật chính plugin, bạn có thể chạy:

    sudo apt install --only-upgrade docker-compose-plugin

4. Docker Compose có thể được sử dụng trong các môi trường sản xuất không?

Đúng, Docker Compose thường được sử dụng trong môi trường sản xuất, đặc biệt cho các ứng dụng chạy trên một máy chủ duy nhất.

Nó cung cấp một cách tiếp cận đơn giản để định nghĩa, triển khai và quản lý vòng đời của các dịch vụ, mạng và dung lượng lưu trữ của ứng dụng của bạn. Đối với triển khai trên một máy chủ duy nhất, đây là một giải pháp rất hiệu quả và dễ quản lý.

Đối với các tình huống phức tạp hơn đòi hỏi phải điều phối các container trên nhiều máy chủ (một cụm), các công cụ khác thường được sử dụng hơn. Bao gồm Docker Swarm (sử dụng cú pháp tệp Compose tương tự) và Kubernetes (là tiêu chuẩn ngành cho việc điều phối container quy mô lớn).

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

Bảng này tổng hợp các khác biệt chính giữa Docker CLI và Docker Compose CLI.

Tính năngdocker (Công cụ Docker)docker compose (Plugin Compose)
Phạm viQuản lý các đối tượng Docker cá nhân.Quản lý một ứng dụng hoàn chỉnh với nhiều container như một đơn vị duy nhất.
Sử dụng chínhXây dựng hình ảnh (docker build), chạy các container đơn (docker run), quản lý container (docker ps), hình ảnh, volumes và mạng.Điều phối nhiều dịch vụ được định nghĩa trong tệp docker-compose.yml.
LệnhCác lệnh cấp thấp tập trung vào một đối tượng. Ví dụ: docker run -d -p 80:80 -v ./:/app nginxCác lệnh cấp cao cho toàn bộ ứng dụng. Ví dụ: docker compose up
Sự tương tựMột khối xây dựng (một container riêng lẻ).Bản thiết kế và quản lý xây dựng cho toàn bộ tòa nhà (ứng dụng).

Tóm lại, bạn sử dụng docker để tương tác với một container đơn lẻ. Bạn sử dụng docker compose để quản lý toàn bộ ngăn xếp ứng dụng của mình (ví dụ: web, app, db) cùng một lúc.

6. Làm thế nào để chạy nhiều container với Docker Compose?

Bạn chạy nhiều container bằng cách định nghĩa chúng như dịch vụ trong tệp docker-compose.yml của bạn.

  1. Tạo một tệp có tên docker-compose.yml.

  2. Bên trong tệp này, sử dụng khóa services: để định nghĩa từng container mà bạn muốn chạy.

    Dưới đây là một ví dụ thực tế định nghĩa và chạy hai container: một trang WordPress và một cơ sở dữ liệu MySQL.

    services:
      db:
        # This is the first service (container)
        image: mysql:8.0
        environment:
          MYSQL_ROOT_PASSWORD: <^your_password_here<^>
          MYSQL_DATABASE: wordpress
        volumes:
          - db_data:/var/lib/mysql
    
      wordpress:
        # This is the second service (container)
        image: wordpress:latest
        ports:
          - "8001:80"
        environment:
          WORDPRESS_DB_HOST: db
          WORDPRESS_DB_PASSWORD: <^your_password_here<^>
        depends_on:
          - db
    
    volumes:
      db_data:
  3. docker-compose.yml

  4. Lưu tệp và mở một cửa sổ terminal trong cùng thư mục.

  5. Chạy một lệnh duy nhất để khởi động cả hai container:

    docker compose up -d

Docker Compose sẽ đọc tệp, tạo một mạng chung cho các dịch vụ, tải cả hai image mysql và wordpress, và khởi động một container cho mỗi dịch vụ.

7. Tại sao nên sử dụng Docker Compose cho phát triển?

Docker Compose là một công cụ tiêu chuẩn cho môi trường phát triển vì nó giải quyết được một số vấn đề phổ biến.

  • Môi Trường Nhất Quán: Nó đảm bảo mọi lập trình viên trong nhóm đều chạy các dịch vụ giống hệt nhau (cơ sở dữ liệu, bộ nhớ đệm, máy chủ web) với cùng phiên bản và cấu hình. Điều này được định nghĩa trong tệp docker-compose.yml, được lưu trữ trong hệ thống kiểm soát phiên bản. Điều này loại bỏ vấn đề “nó hoạt động trên máy của tôi”.

  • Sự đơn giản: Nó thay thế các tập lệnh cài đặt phức tạp và các lệnh docker run dài dòng. Một lập trình viên chỉ cần chạy docker compose up để khởi động toàn bộ ngăn xếp ứng dụng và docker compose down để dừng nó.

  • Cách ly dịch vụ: Các nhà phát triển có thể làm việc trên nhiều dự án trên cùng một máy mà không gặp xung đột phụ thuộc. Dự án A có thể sử dụng PostgreSQL 9.6 và Dự án B có thể sử dụng PostgreSQL 14, vì mỗi cơ sở dữ liệu chạy trong một container cách ly được quản lý bởi tệp Compose riêng của nó.

  • Kiểm thử tích hợp dễ dàng: Vì Compose khởi chạy tất cả các phụ thuộc của một ứng dụng cùng lúc, nó tạo ra một môi trường cục bộ hoàn hảo để chạy các bài kiểm thử tích hợp nhằm xác minh cách các dịch vụ tương tác với nhau.

Kết luận

Trong hướng dẫn này, bạn đã cài đặt Docker Compose và cấu hình một ứng dụng đa container hoàn chỉnh. Bạn bắt đầu với một tệp docker-compose.yml cơ bản cho máy chủ web Nginx và tiến tới một stack phức tạp, thực tế hơn liên quan đến ứng dụng WordPress và cơ sở dữ liệu MySQL. Bạn đã học cách quản lý toàn bộ vòng đời ứng dụng, từ xây dựng và chạy các dịch vụ cho đến dừng và xóa chúng.

Bây giờ bạn đã quen thuộc với các khái niệm chính để quản lý ứng dụng một cách hiệu quả, bao gồm việc mở rộng dịch vụ, định nghĩa mạng tùy chỉnh và tách cấu hình của bạn thành các tệp mô-đun bằng cách sử dụng chỉ thị include. Bằng cách tuân theo hướng dẫn di chuyển và các bước khắc phục sự cố, bạn cũng có thể giải quyết các vấn đề phổ biến như xung đột cổng, lỗi “Permission denied” và lỗi cú pháp YAML. Những kỹ năng được đề cập ở đây sẽ cho phép bạn xây dựng các môi trường phát triển một cách nhất quán, có khả năng tái tạo và triển khai các ứng dụng trên một máy chủ duy nhất với sự tự tin.

Để tham khảo đầy đủ tất cả các lệnh docker compose có sẵn, hãy xem tài liệu chính thức.

Để xem thêm các hướng dẫn liên quan đến Docker, hãy tham khảo các bài viết sau:

  • Cách Cài Đặt Docker trên Ubuntu – Hướng Dẫn Từng Bước
  • Cách Cài Đặt WordPress Với Docker Compose

Cảm ơn bạn đã học cùng cộng đồng Linux-Console.net.