Giới thiệu
Trong phát triển backend hiện đại, hầu hết các hệ thống hoạt động trong các môi trường cách ly—thông thường là các container. Một backend điển hình bao gồm nhiều dịch vụ cần giao tiếp với nhau. Các công cụ điều phối như Kubernetes và Docker Compose cung cấp DNS nội bộ để các dịch vụ có thể kết nối với nhau bằng tên máy chủ. Điều này rất tiện lợi và thường tạo cảm giác như ma thuật.
Thách thức: Phát hiện dịch vụ trong container
Khi bạn cần truy cập dịch vụ của mình thông qua một URL công khai, điều này sẽ tạo ra một thách thức. Hãy tưởng tượng có một reverse proxy đang đứng trước mọi thứ, với Keycloak phía sau và oauth2-proxy xử lý xác thực thông qua Keycloak. oauth2-proxy cần:
--redirect-url— URL mà trình duyệt truy cập--oidc-issuer-url— URL mà proxy sử dụng để lấy token
Để tránh các vấn đề CSRF, bạn cũng cần thiết lập --cookie-secure=true. Vì các khách hàng truy cập Keycloak thông qua reverse proxy, URL redirect phải chỉ vào proxy; URL phát hành cũng nên chỉ vào Keycloak. Bạn có thể sử dụng tên DNS nội bộ cho URL phát hành, nhưng điều này sẽ phá vỡ các kiểm tra CSRF—cả hai URL phải chia sẻ cùng một tên máy chủ, điều này thường là một miền công khai mà bạn không có trong môi trường phát triển cục bộ. Đây là một tình huống khó xử.
Tại sao tên máy chủ không khớp gây ra lỗi “CSRF”
Trong quá trình OAuth/OIDC, proxy của bạn thiết lập một giá trị ngắn hạn (state/nonce) trong cookie trên chính máy chủ mà người dùng đang truy cập (ví dụ: auth.local.test). Khi Keycloak chuyển hướng lại, proxy phải so sánh state trong URL callback với bản sao được lưu trong cookie đó. Việc so sánh này là sự bảo vệ CSRF.
Nếu bạn trộn lẫn các máy chủ—ví dụ, trình duyệt truy cập https://auth.local.test nhưng nhà phát hành của bạn là http://keycloak:8080—trình duyệt sẽ không gửi cookie đến máy chủ khác. Máy chủ khác => phạm vi cookie khác. Thêm vào đó, --cookie-secure=true có nghĩa là cookie chỉ được gửi qua HTTPS, vì vậy bất kỳ bước HTTP nào cũng sẽ làm mất nó. Các quy tắc SameSite hiện đại cũng coi các máy chủ khác nhau là “cross-site”, điều này càng ngăn cản cookie đi cùng. Proxy không thể tìm thấy cookie mà nó đã thiết lập, kiểm tra trạng thái không thành công, và bạn sẽ nhận được lỗi CSRF.
Đây là lý do tại sao việc giải quyết tên “công khai” đến container của bạn cục bộ lại hiệu quả như vậy: mỗi bước đều thấy cùng một máy chủ, vì vậy trình duyệt gửi cookie đúng và kiểm tra CSRF thành công.
Các giải pháp hiện có
Tại thời điểm này, bạn hoặc là giả lập tên miền trong /etc/hosts hoặc tìm kiếm một công cụ ánh xạ tên container đến tên máy chủ. Tôi đã bắt đầu với dự án devdns thông minh và thậm chí đã thử tự động hóa việc cập nhật tệp hosts khi Docker khởi động/dừng. Nó hoạt động, nhưng tệp hosts rất dễ bị hỏng và dễ dàng bị ghi đè. Tôi muốn một cái gì đó hoạt động như DNS thực mà không cần chỉnh sửa tệp.
Một cách tiếp cận tốt hơn: Máy chủ DNS cục bộ cho các container
Chạy một máy chủ DNS cục bộ theo dõi các container đang chạy. Nếu một truy vấn khớp với tên (hoặc bí danh) của một container, hãy trả về IP của container đó. Nếu không, hãy chuyển tiếp đến các DNS công khai (Google, Cloudflare, v.v.). API của Docker rất tuyệt vời trong Go, và miekg/dns làm cho DNS trở nên đơn giản, vì vậy tôi đã xây dựng một máy chủ nhỏ trong Go. Bạn có thể tìm thấy mã nguồn tại đây.
Cách thức hoạt động (tóm tắt)
- Trình duyệt yêu cầu DNS cho example.com.
- Bộ giải quyết cục bộ kiểm tra xem có container nào được đặt tên/bí danh là example.com đang chạy hay không.
- Nếu có → trả về IP của container. Nếu không → chuyển tiếp đến DNS công cộng và trả về IP đó.
Cách sử dụng máy chủ DNS cục bộ
Khi bạn chạy máy chủ DNS cục bộ (ví dụ, trên cổng 53), nó sẽ tự động giải quyết tên container thành địa chỉ IP của chúng. Đây là một ví dụ đơn giản sử dụng Docker Compose:
services:
ubuntu:
image: ubuntu:latest
container_name: github.com
command: ["sleep", "infinity"]
Sau khi khởi động tệp Compose này, bất kỳ truy vấn DNS nào cho github.com sẽ được giải quyết thành địa chỉ IP của container ubuntu. Ví dụ, chạy dig github.com sẽ trả về:
; <<>> DiG 9.20.4-3ubuntu1.2-Ubuntu <<>> github.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44158
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;github.com. IN A
;; ANSWER SECTION:
github.com. 0 IN A 172.21.0.2
;; Query time: 2 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Sun Sep 07 19:24:24 CEST 2025
;; MSG SIZE rcvd: 55
Lưu ý rằng địa chỉ IP trong phần trả lời khớp với IP của container. Bạn có thể xác nhận điều này bằng:
docker compose ps -q ubuntu | xargs docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
172.21.0.2
Cấu hình hệ thống của bạn để sử dụng máy chủ DNS
Để sử dụng máy chủ DNS này, hãy cấu hình hệ thống của bạn để trỏ đến nó. Ví dụ, nếu sử dụng systemd-resolved:
sudo resolvectl dns <INTERFACE> 127.0.0.1:5300
Thay đổi này là tạm thời và sẽ được đặt lại khi khởi động lại. Để hoàn tác thủ công:
sudo systemctl restart systemd-resolved
Kết luận
Môi trường phát triển cục bộ thường gặp rắc rối khi các phần của stack thấy các tên máy chủ khác nhau. Một máy chủ DNS cục bộ nhỏ có thể khắc phục điều đó: giải quyết tên container thành IP của chúng, chuyển tiếp mọi thứ khác lên trên, và môi trường phát triển của bạn bắt đầu hoạt động giống như sản xuất mà không cần hack.
Tại sao điều này hữu ích cho bạn
- Một tên máy chủ xuyên suốt → ít bất ngờ về xác thực/cookie hơn.
- Không cần chỉnh sửa hosts thủ công.
- Hoạt động với Compose ngay lập tức; dễ dàng xác minh với dig.
Các phương pháp tốt nhất
- Sử dụng tên miền không thực: Tránh sử dụng tên miền thực trong môi trường phát triển để giảm thiểu sự nhầm lẫn.
- Kiểm tra thường xuyên: Đảm bảo rằng dịch vụ của bạn hoạt động đúng cách bằng cách thực hiện các kiểm tra định kỳ.
- Sử dụng các công cụ tự động hóa: Tận dụng các công cụ tự động hóa để quản lý cấu hình DNS và container.
Các cạm bẫy thường gặp
- Quên cấu hình DNS: Đảm bảo rằng máy chủ DNS cục bộ được cấu hình đúng.
- Sử dụng nhiều môi trường phát triển: Nếu bạn làm việc trên nhiều dự án, hãy chắc chắn rằng cấu hình DNS không bị xung đột.
Mẹo hiệu suất
- Giảm thiểu yêu cầu DNS: Giảm thiểu số lượng yêu cầu DNS bằng cách sử dụng cache.
- Kiểm tra hiệu suất: Theo dõi hiệu suất của máy chủ DNS để đảm bảo rằng nó không trở thành điểm nghẽn.
Khắc phục sự cố
- Không thể giải quyết tên miền: Kiểm tra xem máy chủ DNS có đang chạy và có được cấu hình đúng hay không.
- Lỗi CSRF: Đảm bảo rằng các URL phát hành và redirect khớp với nhau để tránh lỗi CSRF.