Chúng ta sử dụng Dockerfile để cấu hình và build các Docker Image. Để run các Docker container từ các Docker Image này, các bạn có thể sử dụng command line hoặc Docker Compose. Lợi ích của Docker Compose là giúp các bạn có thể định nghĩa và run nhiều Docker container cùng một lúc. Cụ thể như thế nào? Mình sẽ hướng dẫn các bạn cách định nghĩa và run các Docker container với Docker Compose các bạn nhé!
Ứng dụng ví dụ
Để làm ví dụ cho bài viết này, mình sẽ tạo mới một Spring Boot project đơn giản, có khai báo sử dụng database nhưng sẽ không có thao tác nào đến database cả, tương tự như bài viết Deploy ứng dụng Spring Boot sử dụng Docker, như sau:
Kết quả:
SpringBootDockerComposeApplication:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package com.huongdanjava.springboot; import com.zaxxer.hikari.HikariDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.jdbc.metadata.HikariDataSourcePoolMetadata; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.sql.DataSource; @SpringBootApplication @RestController public class SpringBootDockerComposeApplication { @Autowired private DataSource dataSource; @GetMapping("/hello") public String helloDockerCompose() { Integer idleConnection = new HikariDataSourcePoolMetadata((HikariDataSource) dataSource).getIdle(); return "Hello Docker Compose! Idle connection to database is " + idleConnection; } public static void main(String[] args) { SpringApplication.run(SpringBootDockerComposeApplication.class, args); } } |
application.properties
1 2 3 |
spring.datasource.url=jdbc:postgresql://${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME} spring.datasource.username=${DATABASE_USERNAME} spring.datasource.password=${DATABASE_PASSWORD} |
Kết quả khi chạy ứng dụng rồi request tới địa chỉ http://localhost:8080/hello, như sau:
Xây dựng Dockerfile
Để có thể sử dụng Docker Compose, chúng ta cần build Docker Image cho ứng dụng của mình. Điểm khác biệt ở đây là chúng ta không cần phải khai báo biến môi trường của ứng dụng trong Dockerfile, chúng ta sẽ sử dụng Docker Compose để làm điều này.
Mình sẽ tạo mới một tập tin Dockerfile trong project ví dụ:
Nội dung tập tin Dockerfile cho ứng dụng của mình như sau:
1 2 3 4 |
FROM eclipse-temurin:21.0.4_7-jre VOLUME /tmp ADD target/spring-boot-docker-compose-0.0.1-SNAPSHOT.jar app.jar ENTRYPOINT exec java -jar app.jar |
Viết Docker Compose
Sau khi đã có Docker Image cho ứng dụng của mình, các bạn có thể sử dụng Docker Compose để deploy ứng dụng của mình. Như mình đã nói, lợi ích lớn nhất của Docker Compose là giúp chúng ta có thể cấu hình và chạy nhiều Docker Image cùng 1 lúc.
Để minh hoạ điều này, mình sẽ không sử dụng PostgreSQL ở máy local của mình nữa. Mình sẽ viết một Docker Compose có thể cài đặt mới một PostgreSQL và sau đó sẽ chạy ứng dụng của mình lên.
Nhưng trước tiên, chúng ta hãy nói một ít về Docker Compose đã các bạn nhé!
Docker Compose thông thường sử dụng tập tin .yml để định nghĩa.
Mặc định, Docker sử dụng tập tin có tên là docker-compose.yml để chạy Docker Compose, nên mình sẽ tạo mới một tập tin docker-compose.yml trong project ví dụ như sau:
Mỗi Docker Container sẽ là một service trong tập tin Docker Compose và chúng ta sẽ sử dụng thuộc tính services cùng với khai báo tên của từng service cho các Docker Container của chúng ta. Mình sẽ khai báo như sau:
1 2 3 4 |
services: postgresql: spring_boot_docker_compose: |
Nhiệm vụ của chúng ta là khai báo thông tin cho mỗi service. Các bạn có thể sử dụng một số thuộc tính phổ biến sau để khai báo cho mỗi service như sau:
- image: Docker Image mà chúng ta sẽ sử dụng để chạy container.
- container_name: tên của container.
- environment: khai báo các biến môi trường cần thiết cho mỗi container.
- volumes: mount thư mục giữa máy chạy Docker và container.
- ports: mapping port giữa máy chạy Docker và container.
- depends_on: chỉ ra sự phụ thuộc của service này với service khác trong Docker Compose. Container với name được khai báo trong thuộc tính này bắt buộc phải available trước thì service này mới được chạy.
- networks: định nghĩa một network dùng chung cho các container. Thông thường chúng ta sẽ sử dụng driver bridge cho phần network này.
Chúng ta sẽ định nghĩa service cho postgresql trước. Mình sẽ định nghĩa như sau:
1 2 3 4 5 6 7 8 9 10 11 |
postgresql: image: postgres:16.3 container_name: postgres environment: POSTGRES_PASSWORD: "123456" POSTGRES_USER: "khanh" POSTGRES_DB: "test" volumes: - /Users/khanhnguyenj/data:/var/lib/postgresql/data networks: - huongdanjava |
Các bạn có thể tham khảo thêm bài viết Cài đặt PostgreSQL server sử dụng Docker để hiểu thêm các cấu hình cho service postgresql này các bạn nhé!
Còn ứng dụng ví dụ, mình sẽ định nghĩa như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
spring_boot_docker_compose: image: spring-boot-docker-compose container_name: spring_boot_docker_compose depends_on: - postgresql environment: DATABASE_USERNAME: "khanh" DATABASE_PASSWORD: "123456" DATABASE_HOST: "postgresql" DATABASE_NAME: "test" DATABASE_PORT: 5432 ports: - 8080:8080 networks: - huongdanjava |
Vì phải có database thì ứng dụng ví dụ của chúng ta mới chạy được nên mình sử dụng thuộc tính depends_on trong service spring_boot_docker_compose để khai báo sự phụ thuộc này.
Cả 2 service mà mình đã định nghĩa ở trên sử dụng chung networks là huongdanjava với driver bridge. Chúng ta cần định nghĩa phần networks này như sau:
1 2 3 |
networks: huongdanjava: driver: bridge |
Toàn bộ nội dung của tập tin docker-compose.yml như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
services: postgresql: image: postgres:16.3 container_name: postgres environment: POSTGRES_PASSWORD: "123456" POSTGRES_USER: "khanh" POSTGRES_DB: "test" volumes: - /Users/khanh/data:/var/lib/postgresql/data networks: - huongdanjava spring_boot_docker_compose: image: spring-boot-docker-compose container_name: spring_boot_docker_compose depends_on: - postgresql environment: DATABASE_USERNAME: "khanh" DATABASE_PASSWORD: "123456" DATABASE_HOST: "postgresql" DATABASE_NAME: "test" DATABASE_PORT: 5432 ports: - 8080:8080 networks: - huongdanjava networks: huongdanjava: driver: bridge |
OK, đến đây thì chúng ta đã hoàn thành việc viết Docker Compose cho ứng dụng ví dụ này. Giờ chạy xem thế nào các bạn nhé!
Mình sẽ build Docker Image cho ứng dụng ví dụ trước:
1 |
mvn clean package -DskipTests && docker build -t spring-boot-docker-compose . |
Kết quả:
Bây giờ thì các bạn có thể chạy Docker Compose rồi. Chúng ta sẽ chạy câu lệnh sau:
1 |
docker compose up |
Kết quả:
Request tới địa chỉ http://localhost:8080/hello, các bạn sẽ thấy kết quả vẫn như trên.