Deploy ứng dụng load balancer sử dụng Nginx với Docker
Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh
Load balancer là một khái niệm về cân bằng tải, được sử dụng để giúp ứng dụng của chúng ta có thể handle một số lượng lớn các request từ người dùng. Chúng ta sẽ deploy ứng dụng của mình lên nhiều máy khác nhau, behind một load balancer, các request từ người dùng sẽ gọi tới load balancer và sẽ được load balancer forward tới một trong các máy này… Nginx là một trong những web server có thể hỗ trợ chúng ta hiện thực phần load balancer đó các bạn! Cụ thể như thế nào? Trong bài viết này, mình sẽ hướng dẫn các bạn deploy ứng dụng load balancer sử dụng Nginx với Docker các bạn nhé!
Xem thêm các việc làm Java lương cao trên TopDev
Để làm ví dụ cho bài viết này, mình sẽ sử dụng Docker Compose để mô phỏng một mô hình deploy sử dụng load balancer. Mình sẽ deploy ứng dụng ví dụ trong bài viết Giới thiệu về Docker Compose trên 3 container khác nhau sử dụng chung một PostgreSQL, một Nginx container làm nhiệm vụ load balancer forward request của người dùng tới 1 trong 3 container này:
Ứng dụng ví dụ
Với ứng dụng ví dụ, để biết Ngnix đang forward request của người dùng tới instance nào, mình sẽ add thêm code trong phương thức helloDockerCompose() của request “/hello” in ra dòng log “Received request …” như sau:
package com.huongdanjava.springboot; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 org.springframework.web.bind.annotation.RestController; import com.zaxxer.hikari.HikariDataSource; @SpringBootApplication @RestController public class SpringBootDockerComposeApplication { private static final Logger LOGGER = LoggerFactory.getLogger(SpringBootDockerComposeApplication.class); @Autowired private DataSource dataSource; @RequestMapping("/hello") public String helloDockerCompose() { LOGGER.info("Received request ..."); 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); } }
Khi chạy ứng dụng, chúng ta sẽ take a look vào Console của Docker Compose để biết là request đang được forward tới container nào.
Hãy build lại Docker Image cho ứng dụng ví dụ của chúng ta các bạn nhé!
Cấu hình của ứng dụng ví dụ cho 3 container tương ứng với 3 services trong tập tin docker-compose.yml, trong bài viết Giới thiệu về Docker Compose sẽ như sau:
spring_boot_docker_compose_1: image: spring-boot-docker-compose container_name: spring_boot_docker_compose_1 depends_on: - postgresql environment: DATABASE_USERNAME: "khanh" DATABASE_PASSWORD: "123456" DATABASE_HOST: "postgresql" DATABASE_NAME: "test" DATABASE_PORT: 5432 ports: - 8081:8080 networks: - huongdanjava spring_boot_docker_compose_2: image: spring-boot-docker-compose container_name: spring_boot_docker_compose_2 depends_on: - postgresql environment: DATABASE_USERNAME: "khanh" DATABASE_PASSWORD: "123456" DATABASE_HOST: "postgresql" DATABASE_NAME: "test" DATABASE_PORT: 5432 ports: - 8082:8080 networks: - huongdanjava spring_boot_docker_compose_3: image: spring-boot-docker-compose container_name: spring_boot_docker_compose_3 depends_on: - postgresql environment: DATABASE_USERNAME: "khanh" DATABASE_PASSWORD: "123456" DATABASE_HOST: "postgresql" DATABASE_NAME: "test" DATABASE_PORT: 5432 ports: - 8083:8080 networks: - huongdanjava
Mỗi container sẽ expose port khác nhau bao gồm: 8081, 8082 và 8083.
Cấu hình của PostgreSQL database trong tập tin docker-compose.yaml, không đổi các bạn nhé!
Nginx
Trước khi đi vào chi tiết cấu hình load balancer với Nginx như thế nào, chúng ta sẽ điểm sơ qua một số thuật toán được sử dụng để hiện thực load balancer các bạn nhé! Chúng ta có một số thuật toán cơ bản sau:
- Round Robin: các request từ người dùng sẽ được forward lần lượt đến tất cả các máy theo thứ tự. Có nghĩa là ứng dụng của chúng ta được deploy lên bao nhiêu máy thì request của người dùng sẽ lần lượt được forward đến tất cả các máy này. Request đầu tiên sẽ vào máy 1, request thứ 2 sẽ vào máy 2, … sau khi máy cuối cùng được sử dụng thì request tiếp theo sẽ vào máy 1.
- Weighted Round Robin: tương tự như thuật toán Round Robin nhưng các máy deploy ứng dụng sẽ có cấu hình khác nhau. Máy nào có cấu hình cao hơn sẽ được đánh trọng số cao hơn và nhận được nhiều request hơn các bạn nhé!
- Dynamic Round Robin: thuật toán này giống như Weighted Round Robin nhưng có sự khác biệt là trọng số của các máy sẽ không cố định. Cấu hình của các máy sẽ được kiểm tra liên tục, do đó trọng số sẽ thay đổi liên tục.
- Fastest: thuật toán này dựa vào thời gian response của máy trong setup load balancer. Máy nào có thời gian response nhanh hơn sẽ được chọn để forward request.
- Least Connections: máy nào có ít kết nối nhất sẽ được forward request tới.
Trong bài viết này, mình sẽ sử dụng thuật toán Round Robin với Nginx để làm ví dụ các bạn nhé!
Mình sẽ build một custom Nginx Docker image với Dockerfile có nội dung như sau:
FROM nginx:latest RUN rm /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d/default.conf
trong đó, tập tin nginx.conf có nội dung như sau:
upstream apps { server 172.17.0.1:8081; server 172.17.0.1:8082; server 172.17.0.1:8083; } server { location / { proxy_pass http://apps; } }
Trong tập tin nginx.conf trên, mình đã sử dụng reverse proxy của Nginx với directive upstream để hiện thực load balancer. Khi một request từ người dùng đến, request này sẽ được forward một trong 3 server được khai báo bên trong directive upstream này. Mặc định thì Nginx hỗ trợ thuật toán round-robin nên các bạn không cần khai báo gì thêm ngoài thông tin của 3 server.
Cũng cần nói thêm là khi start các container 172.17.0.1 là địa chỉ IP mặc định khi các container được start, nên như các bạn thấy, mình sử dụng 172.17.0.1 cho 3 container của ứng dụng ví dụ, với các port khác nhau như đã khai báo trong tập tin docker-compose.yaml.
Các bạn cần build custom Docker image này bằng câu lệnh sau:
docker build -t nginx:0.0.1 .
Kết quả:
Sau khi đã có Docker Image của Nginx, các bạn có thể khai báo một service cho Nginx trong tập tin docker-compose.yaml như sau:
nginx: image: nginx:0.0.1 ports: - 80:80
Đến đây thì chúng ta đã hoàn thành việc cấu hình để deploy load balancer cho ứng dụng ví dụ của chúng ta. Nội dung của tập tin docker-compose.yaml lúc này 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_1: image: spring-boot-docker-compose container_name: spring_boot_docker_compose_1 depends_on: - postgresql environment: DATABASE_USERNAME: "khanh" DATABASE_PASSWORD: "123456" DATABASE_HOST: "postgresql" DATABASE_NAME: "test" DATABASE_PORT: 5432 ports: - 8081:8080 networks: - huongdanjava spring_boot_docker_compose_2: image: spring-boot-docker-compose container_name: spring_boot_docker_compose_2 depends_on: - postgresql environment: DATABASE_USERNAME: "khanh" DATABASE_PASSWORD: "123456" DATABASE_HOST: "postgresql" DATABASE_NAME: "test" DATABASE_PORT: 5432 ports: - 8082:8080 networks: - huongdanjava spring_boot_docker_compose_3: image: spring-boot-docker-compose container_name: spring_boot_docker_compose_3 depends_on: - postgresql environment: DATABASE_USERNAME: "khanh" DATABASE_PASSWORD: "123456" DATABASE_HOST: "postgresql" DATABASE_NAME: "test" DATABASE_PORT: 5432 ports: - 8083:8080 networks: - huongdanjava nginx: image: nginx:0.0.1 ports: - 80:80 networks: huongdanjava: driver: bridge
Chạy tập tin này với docker compose up, các bạn sẽ thấy kết quả như sau:
Lúc này, nếu các bạn request tới http://localhost/hello lần thứ nhất, các bạn sẽ thấy Console của Docker Compose có kết quả như sau:
Container spring_boot_docker_compose_1 đã handle request này.
Lần thứ 2:
Lần này thì container spring_boot_docker_compose_2 handle request.
Lần thứ 3:
Và lần thứ tư sẽ quay lại spring_boot_docker_compose_1:
Đúng như expectation của chúng ta rồi đó các bạn!
Bài viết gốc được đăng tải tại huongdanjava.com
Có thể bạn quan tâm:
- Kubernetes là gì? Cùng tìm hiểu cách hoạt động
- DevOps là gì? Cần học gì để trở thành DevOps
- Load Balancers I – Stateful App Servers và những điều nên biết
Xem thêm Việc làm IT hấp dẫn trên TopDev
- B BenQ RD Series – Dòng Màn Hình Lập Trình 4k+ Đầu Tiên Trên Thế Giới
- i iOS 18 có gì mới? Có nên cập nhật iOS 18 cho iPhone của bạn?
- G Gamma AI là gì? Cách tạo slide chuyên nghiệp chỉ trong vài phút
- P Power BI là gì? Vì sao doanh nghiệp nên sử dụng PBI?
- K KICC HCMC x TOPDEV – Bước đệm nâng tầm sự nghiệp cho nhân tài IT Việt Nam
- T Trello là gì? Cách sử dụng Trello để quản lý công việc
- T TOP 10 SỰ KIỆN CÔNG NGHỆ THƯỜNG NIÊN KHÔNG NÊN BỎ LỠ
- T Tìm hiểu Laptop AI – So sánh Laptop AI với Laptop thường
- M MySQL vs MS SQL Server: Phân biệt hai RDBMS phổ biến nhất
- S SearchGPT là gì? Công cụ tìm kiếm mới có thể đánh bại Google?