Vấn đề
Ít có điều gì gây ức chế hơn việc một máy chủ web từ chối khởi động chỉ vì một bản ghi DNS duy nhất. Khi bạn tải lại Nginx, bạn có thể thấy một lỗi chặn như sau:
[error] 1234#0: host not found in upstream "backend.example.com" in /etc/nginx/conf.d/default.conf:15
Theo mặc định, Nginx khá cứng nhắc. Nó cố gắng phân giải mọi tên miền trong các khối upstream hoặc các chỉ thị proxy_pass ngay khi khởi động. Nếu máy chủ DNS của bạn chậm, container backend chưa khởi động xong, hoặc tên miền đơn giản là chưa tồn tại, Nginx sẽ hoảng loạn và không thể khởi chạy. Điều này tạo ra vấn đề "con gà và quả trứng" trong nhiều thiết lập tự động.
TL;DR: Cách khắc phục nhanh
Để ngăn Nginx bị lỗi khi backend không thể kết nối, hãy đặt URL của backend vào một biến và chỉ định một resolver. Điều này buộc Nginx chỉ thực hiện tra cứu địa chỉ IP khi có một yêu cầu thực tế gửi đến.
location / {
resolver 8.8.8.8 valid=30s;
set $backend_servers "http://backend.example.com";
proxy_pass $backend_servers;
}
Tại sao Nginx không thể phân giải máy chủ backend
Nginx thường thực hiện tra cứu DNS một lần duy nhất trong quá trình tải cấu hình. Nếu việc tra cứu này thất bại, Nginx coi đó là một lỗi cú pháp nghiêm trọng. Hành vi này gây ra nhiều rắc rối trong một số tình huống:
- **Tình trạng tranh chấp trong Docker Compose:** Nginx thường khởi động nhanh hơn và cố gắng tìm container `backend` trước khi mạng nội bộ của Docker kịp đăng ký nó hoàn toàn.
- **Mở rộng quy mô động:** Nếu IP của backend thay đổi thường xuyên (phổ biến trong AWS Auto Scaling hoặc Kubernetes), Nginx sẽ tiếp tục gửi lưu lượng truy cập đến IP cũ đã được lưu trong bộ nhớ đệm cho đến khi bạn tải lại dịch vụ theo cách thủ công.
- **Sự cố mạng:** Một lỗi DNS nhỏ kéo dài 2 giây trong quá trình triển khai tự động có thể khiến toàn bộ trang web production của bạn bị ngoại tuyến.
Các bước khắc phục chi tiết
Cách 1: Sử dụng biến và Resolver (Khuyên dùng)
Khi bạn sử dụng một biến trong chỉ thị proxy_pass, Nginx sẽ thay đổi hành vi của nó. Nó sẽ bỏ qua việc kiểm tra lúc khởi động và đợi cho đến khi người dùng gửi yêu cầu mới thực hiện tra cứu DNS.
1. Định nghĩa Resolver: Bạn phải chỉ cho Nginx biết máy chủ DNS nào cần truy vấn. Đối với môi trường Docker, hãy sử dụng DNS nội bộ tại 127.0.0.11. Đối với các máy chủ công cộng, Google (8.8.8.8) hoặc Cloudflare (1.1.1.1) là những lựa chọn tiêu chuẩn.
2. Ánh xạ Host vào một biến: Sử dụng chỉ thị set để lưu trữ URL backend của bạn.
server {
listen 80;
server_name proxy.example.com;
# Sử dụng DNS nội bộ của Docker với bộ nhớ đệm 10 giây
resolver 127.0.0.11 valid=10s;
location / {
# Lưu trữ URL trong một biến sẽ kích hoạt phân giải động
set $upstream_endpoint http://backend.example.com:8080;
proxy_pass $upstream_endpoint;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Cách 2: Cấu hình cứng trong tệp /etc/hosts
Nếu backend của bạn sử dụng một địa chỉ IP tĩnh không bao giờ thay đổi, bạn có thể bỏ qua hoàn toàn DNS. Việc ánh xạ tên máy chủ theo cách thủ công trong tệp hosts cục bộ là một cách khắc phục "low-tech" đáng tin cậy cho các thiết lập nhỏ.
- Mở cấu hình: `sudo nano /etc/hosts`
- Thêm ánh xạ của bạn: `10.0.0.50 backend.example.com`
- Xác minh cú pháp: `sudo nginx -t`
Cảnh báo: Tránh sử dụng cách này trong Docker hoặc Kubernetes. IP của container thay đổi mỗi khi pod hoặc container khởi động lại.
Cách 3: Điều chỉnh logic khởi động của Docker
Trong môi trường Docker, cờ depends_on trong tệp docker-compose.yml đảm bảo các container khởi động theo thứ tự. Tuy nhiên, nó không đợi ứng dụng bên trong container ở trạng thái "sẵn sàng". Nếu Nginx vẫn bị lỗi, hãy kết hợp depends_on với mẹo sử dụng biến ở Cách 1 để đảm bảo Nginx khởi động bất kể trạng thái của backend.
Xác minh: Kiểm tra kết quả
Sau khi bạn đã cập nhật cấu hình, hãy xác minh rằng Nginx không còn dễ bị lỗi nữa.
1. Kiểm tra cấu hình:
sudo nginx -t
Nếu bạn đã sử dụng Cách 1, lệnh này giờ đây sẽ báo cáo test is successful ngay cả khi máy chủ backend của bạn đã tắt hoàn toàn.
2. Theo dõi nhật ký trực tiếp:
Theo dõi nhật ký trong khi gửi một yêu cầu thông qua curl.
sudo tail -f /var/log/nginx/error.log
Nếu backend bị sập, bạn sẽ thấy lỗi 502 Bad Gateway trong trình duyệt, nhưng Nginx vẫn sẽ tiếp tục chạy. Đây là một kết quả tốt hơn nhiều so với việc toàn bộ dịch vụ không thể khởi động.
3. Xác thực cập nhật trong thời gian chạy:
Thiết lập một giá trị TTL ngắn như valid=5s. Thay đổi IP backend của bạn tại nhà cung cấp DNS. Không cần khởi động lại Nginx, hãy đợi năm giây và kiểm tra nhật ký của bạn. Nginx sẽ tự động nhận diện đích đến mới.
Các sai lầm thường gặp
- **Lỗi "No Resolver":** Nếu bạn sử dụng một biến trong `proxy_pass` nhưng bỏ qua dòng `resolver`, Nginx sẽ báo lỗi rằng nó không có cách nào để tra cứu tên miền.
- **Cổng 53 bị chặn:** Đảm bảo tường lửa của bạn cho phép lưu lượng truy cập đi ra trên Cổng 53 (UDP và TCP). Nếu Nginx không thể kết nối với resolver, mọi yêu cầu sẽ dẫn đến tình trạng hết hạn thời gian.
- **Định dạng URI:** Khi sử dụng các biến, Nginx xử lý các dấu gạch chéo cuối theo cách khác. Nếu logic proxy của bạn phức tạp, hãy kiểm tra kỹ các đường dẫn URL để đảm bảo `/api/v1` không vô tình trở thành `/v1`.

