Podman + Tailscale: Khi DNS Bị Chặn Và Container Không Thể Nói Chuyện Với Nhau
Ghi lại quá trình debug lỗi 'could not translate host name postgres to address' khi chạy Podman rootful trên VPS có cài Tailscale — từ triệu chứng, phân tích từng lớp, đến fix dứt điểm.
Mục lục
Container không thể ping nhau dù cùng một network. DNS trả về lỗi dù aardvark-dns đang chạy bình thường. Đây là hành trình debug một lỗi tưởng chừng đơn giản nhưng ẩn sau nhiều lớp.
1. Triệu chứng ban đầu
Sau khi chạy podman compose -f compose.production.yml up, hai lỗi xuất hiện liên tiếp:
Lỗi 1 — Nginx không khởi động được:
nginx: [emerg] host not found in upstream "app:9000"
tiemtaphoa_nginx exited with code 0
Lỗi 2 — App không kết nối được database:
SQLSTATE[08006] [7] could not translate host name "postgres" to address: Try again
Cả hai lỗi đều có chung một nguyên nhân gốc: DNS resolution thất bại.
2. Kiến trúc hệ thống
Stack bao gồm 5 services chạy trên cùng một custom bridge network (sms_network):
nginx → app (PHP-FPM) → postgres
→ redis
→ mongodb
Môi trường:
- OS: Debian trên VPS
- Container runtime: Podman rootful
- Compose:
podman compose(dùng docker-compose plugin) - VPN: Tailscale cài sẵn trên VPS
3. Phân tích từng lớp
Lớp 1: Race condition khi khởi động
Nguyên nhân đầu tiên của lỗi nginx là thứ tự khởi động. depends_on mặc định trong Compose chỉ chờ container start, không chờ service bên trong ready.
Nginx khởi động, resolve hostname app:9000, nhưng PHP-FPM chưa lắng nghe → lỗi.
Fix: Thêm healthcheck cho từng service và dùng condition: service_healthy.
# app service
healthcheck:
test: ["CMD-SHELL", "nc -z localhost 9000 || exit 1"]
interval: 10s
timeout: 5s
retries: 15
start_period: 60s # đủ thời gian cho composer install + migrate
# nginx depends_on
depends_on:
app:
condition: service_healthy
start_period: 60s quan trọng vì entrypoint chạy composer install và php artisan migrate trước khi khởi động PHP-FPM — có thể mất 30–60 giây.
Tương tự cho postgres, redis, mongodb:
# postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-postgres} -d ${DB_DATABASE:-sms_system}"]
interval: 5s
retries: 10
start_period: 15s
# redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 10
# mongodb
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
retries: 10
start_period: 20s
Lớp 2: Tailscale chiếm DNS của host
Sau khi fix healthcheck, lỗi postgres vẫn xảy ra. Kiểm tra /etc/resolv.conf trên host:
cat /etc/resolv.conf
# Generated by resolvconf
search taile0ba15.ts.net
nameserver 100.100.100.100
nameserver fd7a:115c:a1e0::53
Tailscale đã thay thế toàn bộ DNS của server bằng MagicDNS (100.100.100.100). Container kế thừa config này và không thể resolve hostname nội bộ như postgres, redis.
Fix:
tailscale set --accept-dns=false
Lệnh này tắt MagicDNS, trả DNS về cho hệ điều hành quản lý. Sau đó /etc/resolv.conf sẽ trở về dùng nameserver thực (8.8.8.8 hoặc DNS của datacenter).
Lớp 3: Aardvark-dns bị chặn bởi iptables
Đây là lớp sâu nhất và khó nhận ra nhất.
Sau khi fix Tailscale, container vẫn không resolve được hostname. Kiểm tra từng bước:
Bước 1 — Xác nhận IP hoạt động:
podman exec -it tiemtaphoa_app sh -c "nc -z 10.89.0.2 5432 && echo ok"
# ok ✓
Kết nối qua IP hoạt động tốt. Vấn đề nằm ở DNS, không phải network.
Bước 2 — Kiểm tra resolv.conf trong container:
podman exec -it tiemtaphoa_app cat /etc/resolv.conf
search dns.podman
nameserver 10.89.0.1
Container trỏ đúng về Podman’s internal DNS (10.89.0.1). Nhưng DNS vẫn không hoạt động.
Bước 3 — Kiểm tra DNS resolve trực tiếp:
podman exec -it tiemtaphoa_app sh -c "getent hosts postgres"
# (không có output — resolve thất bại)
Bước 4 — Xác nhận aardvark-dns đang chạy:
ps aux | grep aardvark
# /usr/lib/podman/aardvark-dns --config /run/containers/networks/aardvark-dns -p 53 run ✓
Bước 5 — Kiểm tra aardvark-dns có đăng ký đúng hostname:
cat /run/containers/networks/aardvark-dns/tiemtaphoa_system_sms_network
10.89.0.1
839c7df... 10.89.0.2 tiemtaphoa_pgsql,postgres,...
c83ddd8... 10.89.0.3 tiemtaphoa_mongodb,mongodb,...
707db16... 10.89.0.4 tiemtaphoa_redis,redis,...
e57ade4... 10.89.0.5 tiemtaphoa_app,app,...
Tất cả hostname đều có. DNS server đang chạy. Nhưng container không nhận được response.
Bước 6 — Xác nhận aardvark-dns lắng nghe đúng port:
ss -ulnp | grep :53
# UNCONN 0 0 10.89.0.1:53 0.0.0.0:* users:(("aardvark-dns",...)) ✓
Bước 7 — Kiểm tra iptables:
iptables -L INPUT -n -v | grep -E "53|DROP|REJECT"
Chain INPUT (policy DROP 10343 packets, 635K bytes)
Đây là nguyên nhân thực sự.
INPUT policy DROP — tất cả traffic vào host đều bị drop trừ khi có rule ACCEPT tường minh. Khi container gửi DNS query đến 10.89.0.1:53, packet đi vào host qua interface podman1 và bị drop trước khi aardvark-dns nhận được.
4. Sơ đồ luồng lỗi
Container
│
│ DNS query → 10.89.0.1:53
▼
podman1 (bridge interface)
│
▼
iptables INPUT chain
│
├─ policy: DROP ← packet bị drop tại đây
│
▼ (không bao giờ tới)
aardvark-dns
5. Fix dứt điểm
Thêm rule cho phép DNS traffic từ Podman bridge vào host:
iptables -I INPUT -i podman1 -p udp --dport 53 -j ACCEPT
iptables -I INPUT -i podman1 -p tcp --dport 53 -j ACCEPT
Xác nhận hoạt động:
podman exec -it tiemtaphoa_app sh -c "getent hosts postgres"
# 10.89.0.2 postgres ✓
Lưu rule để không mất sau khi reboot:
apt install iptables-persistent -y
netfilter-persistent save
6. Tóm tắt toàn bộ chuỗi fix
| # | Vấn đề | Nguyên nhân | Fix |
|---|---|---|---|
| 1 | Nginx crash khi start | Race condition, app chưa ready | Healthcheck + condition: service_healthy |
| 2 | App crash, postgres DNS fail | Tailscale chiếm DNS | tailscale set --accept-dns=false |
| 3 | DNS vẫn fail dù aardvark-dns chạy | iptables INPUT policy DROP chặn UDP 53 | Thêm rule ACCEPT cho interface podman1 |
7. Bài học
Podman rootful ≠ Docker
Podman rootful dùng aardvark-dns để resolve hostname giữa các container. DNS server này chạy trên host và lắng nghe trên gateway của mỗi network (ví dụ 10.89.0.1:53). Nếu iptables chặn traffic vào host, DNS chết — dù toàn bộ config trông hoàn toàn bình thường.
Docker dùng embedded DNS server chạy bên trong network namespace riêng, không đi qua iptables INPUT chain của host.
Tailscale thay đổi nhiều hơn bạn nghĩ
Tailscale với MagicDNS không chỉ thêm DNS server — nó thay thế hoàn toàn /etc/resolv.conf. Mọi container kế thừa config này sẽ resolve qua 100.100.100.100, không tìm thấy hostname nội bộ.
Luôn chạy tailscale set --accept-dns=false trên VPS dùng cho container workload.
Debug DNS theo thứ tự từ ngoài vào trong
# 1. IP có ping được không?
nc -z <ip> <port>
# 2. DNS server có chạy không?
ps aux | grep aardvark
# 3. DNS có đăng ký hostname không?
cat /run/containers/networks/aardvark-dns/<network>
# 4. DNS có lắng nghe đúng port?
ss -ulnp | grep :53
# 5. Firewall có chặn không?
iptables -L INPUT -n -v
Nếu bỏ qua bước 5, có thể mất nhiều giờ debug dù mọi thứ trông đúng ở bước 1–4.
Môi trường: Debian, Podman 5.x rootful, podman compose (docker-compose plugin), aardvark-dns 1.14.0, Tailscale.