Giới thiệu về Docker Compose

Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh

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é!

  20 trường hợp sử dụng lệnh Docker cho developer
  Cách tạo một Docker đơn giản cho Node.JS

Ứ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:

Giới thiệu về Docker Compose

Kết quả:

Giới thiệu về Docker Compose

SpringBootDockerComposeApplication:

package com.huongdanjava.springboot;

import javax.sql.DataSource;

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.RequestMapping;

import com.zaxxer.hikari.HikariDataSource;

@SpringBootApplication
public class SpringBootDockerComposeApplication {

@Autowired
private DataSource dataSource;

@RequestMapping("/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

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:

Giới thiệu về Docker Compose

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ụ:

Giới thiệu về Docker Compose

Nội dung tập tin Dockerfile cho ứng dụng của mình như sau:

FROM openjdk:11.0.11-jre

VOLUME /tmp

ADD target/spring-boot-docker-compose-0.0.1-SNAPSHOT.jar app.jar

ENTRYPOINT exec java -jar app.jar

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:

Giới thiệu về Docker Compose

Đầu tiên, các bạn có thể sử dụng thuộc tính version để định nghĩa version của Docker Compose mà chúng ta sẽ sử dụng. Các bạn có thể tham khảo về version của Docker Compose tại đây.

Mình sẽ khai báo sử dụng Docker Compose version 3.8 như sau:

version: '3.8'

Mỗi Docker Container 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:

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.
  • restart: các bạn xem thêm ở đây. Thông thường chúng ta sẽ cấu hình cho thuộc tính này là on-failure và số lần restart nếu container start failed.
  • 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:
postgresql: 
image: postgres:13.3
container_name: postgres
restart: on-failure:5
environment:
POSTGRES_PASSWORD: "123456"
POSTGRES_USER: "khanh"
POSTGRES_DB: "test"
volumes:
- /Users/khanh/data:/var/lib/postgresql/data
ports:
- 5432:5432
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òn ứng dụng ví dụ, mình sẽ định nghĩa như sau:

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:

networks:
huongdanjava:
driver: bridge

Toàn bộ nội dung của tập tin docker-compose.yml như sau:

version: '3.8'

services:
postgresql: 
image: postgres:13.3
container_name: postgres
restart: on-failure:5
environment:
POSTGRES_PASSWORD: "123456"
POSTGRES_USER: "khanh"
POSTGRES_DB: "test"
volumes:
- /Users/khanh/data:/var/lib/postgresql/data
ports:
- 5432:5432
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:

mvn clean package -DskipTests && docker build -t spring-boot-docker-compose .

Kết quả:

Giới thiệu về Docker Compose

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:

docker compose up

Kết quả:

Giới thiệu về Docker Compose