Bài 2: Tích hợp Hot-Reload với Air trong môi trường Docker cho Golang
Ở Bài 1, chúng ta đã thiết lập được Dockerfile.dev cơ bản. Tuy nhiên, bạn sẽ nhanh chóng nhận ra một điểm bất tiện: mỗi khi bạn sửa một dòng code trong file .go, container vẫn tiếp tục chạy phiên bản cũ. Bạn buộc phải thủ công gõ lệnh docker-compose restart hoặc docker build, khiến tốc độ phát triển bị chậm lại đáng kể.
Trong các ngôn ngữ như Node.js hay Python, chúng ta có nodemon. Còn trong hệ sinh thái Golang, công cụ tiêu chuẩn công nghiệp để thực hiện tính năng Hot-reload (tự động biên dịch và khởi động lại ứng dụng khi có thay đổi file) chính là air.
Trong bài học này, chúng ta sẽ tích hợp air vào bên trong Docker Development Environment.
1. Tại sao lại cần air thay vì go run thông thường?
Khi dùng go run main.go, Go sẽ biên dịch mã nguồn thành một file thực thi tạm thời trong bộ nhớ rồi chạy nó. Nó không theo dõi sự thay đổi của file.
air hoạt động như một tiến trình giám sát (watcher):
-
Theo dõi các sự kiện thay đổi file (
.go,.env,.yaml, v.v.) trong thư mục dự án. -
Khi phát hiện file thay đổi, nó tự động gọi lệnh
go buildđể biên dịch lại file binary mới. -
Tắt tiến trình cũ đang chạy và khởi động tiến trình mới ngay lập tức mà không làm sập container.
2. Cấu hình file .air.toml (Tùy chỉnh hành vi Hot-reload)
Để air biết cách biên dịch và chạy ứng dụng của bạn, chúng ta cần một file cấu hình tên là .air.toml nằm ở thư mục gốc của dự án.
Hãy tạo file .air.toml với cấu hình tối ưu sau:
Ini, TOML
[root]
# Thư mục gốc chứa source code
"." = ""
[build]
# Lệnh biên dịch ứng dụng Go của bạn
# Giả sử file entrypoint là main.go và output file binary đặt tại tmp/main
cmd = "go build -o ./tmp/main ."
# Đường dẫn tới file binary sau khi biên dịch
bin = "./tmp/main"
# Các file hoặc thư mục bị loại bỏ (không kích hoạt reload khi thay đổi)
exclude_dir = ["assets", "tmp", "vendor", "testdata", ".git"]
# Chỉ theo dõi các file có phần mở rộng này
include_ext = ["go", "tpl", "tmpl", "html", "env"]
# Thời gian chờ (delay) tính bằng mili-giây trước khi thực hiện build lại
# (giúp tránh build liên tục khi bạn lưu nhiều file cùng lúc)
delay = 1000 # 1 giây
[log]
# Hiển thị thời gian trên log của air
time = true
[color]
app = ""
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"
3. Cập nhật Dockerfile.dev để cài đặt air
Bây giờ, chúng ta cập nhật lại Dockerfile.dev để tự động tải và cài đặt công cụ air vào bên trong image phát triển.
Dockerfile
FROM golang:1.22-alpine
# Cài đặt git và curl phục vụ việc tải công cụ
RUN apk add --no-cache git curl
# Cài đặt 'air' chính thức thông qua script cài đặt chuẩn
RUN curl -sSfL https://raw.githubusercontent.com/air-verse/air/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
WORKDIR /app
# Sao chép dependency files
COPY go.mod go.sum ./
RUN go mod download
EXPOSE 8080
# Thay vì dùng 'go run', ta dùng 'air' để khởi động ứng dụng
CMD ["air", "-c", ".air.toml"]
4. Kết nối với docker-compose.yml
Để Hot-reload hoạt động được, máy thật (Host) và container phải được đồng bộ mã nguồn thông qua Volume Mapping. Khi bạn bấm Ctrl+S trên máy tính cá nhân, file thay đổi sẽ lập tức xuất hiện bên trong container, kích hoạt air biên dịch lại.
Tạo file docker-compose.yml ở thư mục gốc:
YAML
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
container_name: go-dev-container
ports:
- "8080:8080"
volumes:
# Mount toàn bộ thư mục hiện tại vào /app trong container
- .:/app
# Đảm bảo thư mục cache của Go không bị đè bởi volume mapping
- go_mod_cache:/go/pkg/mod
environment:
- PORT=8080
restart: always
volumes:
go_mod_cache:
5. Kiểm tra thành quả
Khởi động môi trường phát triển của bạn bằng lệnh:
Bash
docker compose up --build
Bạn sẽ thấy air khởi động và chạy ứng dụng Go. Bây giờ, hãy thử mở một file .go bất kỳ, thêm một câu lệnh fmt.Println("Hot-reload hoạt động tuyệt vời!") và lưu lại.
Ngay lập tức trên Terminal của Docker, bạn sẽ thấy air thông báo phát hiện thay đổi, tự động biên dịch lại (building...) và khởi động phiên bản mới mà không cần bạn phải thao tác thủ công.
Tổng kết
Với air kết hợp cùng Docker Volume, chúng ta đã tái tạo lại được trải nghiệm phát triển mượt mà giống hệt như khi chạy trực tiếp trên máy vật lý, nhưng lại được bao bọc trong một môi trường cô lập, sạch sẽ và đồng nhất.
Tuy nhiên, ứng dụng thực tế không bao giờ đứng độc lập một mình. Nó cần kết nối với cơ sở dữ liệu (như PostgreSQL) và bộ nhớ đệm (như Redis).
Đó sẽ là nội dung của Bài 3: Xây dựng hệ sinh thái Microservices với Docker Compose.
All rights reserved