Khi cụm máy chủ chỉ có vài máy, nhớ IP từng máy không khó. Khi con số lên vài chục, gồm balancer, minion, database, máy build, máy giám sát, việc ghi IP vào script, vào file .env hay vào đầu người vận hành bắt đầu sinh lỗi. Tài liệu này hướng dẫn dựng DNS nội bộ bằng BIND để đặt tên và quản lý hàng chục Cloud VPS: cấu trúc zone, quy ước đặt tên, cách đẩy DNS cho máy làm việc qua VPN, quy trình sửa zone và những lỗi hay gặp khi quản lý zone bằng tay.
1. Vì sao cần DNS nội bộ
Trong mô hình ba tầng Balancer – Minion – Database, chỉ balancer có IP public. Minion và database chỉ có IP private, mọi giao tiếp giữa các tầng đi qua mạng private. Nếu không có tên, mỗi nơi cần biết "database của site này ở đâu" sẽ phải chép một địa chỉ 10.0.0.x:
- Khối
upstreamtrong file cấu hình site trên balancer. DB_HOSTtrong.envcủa từng website.- Script backup, script deploy, file
~/.ssh/configcủa từng người.
Một IP đổi (dựng lại máy, chuyển sang Cloud VPS mới) là phải đi sửa ở tất cả những chỗ đó. Tệ hơn, IP không nói gì về vai trò: nhìn 10.0.0.37 không ai biết đó là balancer hay database. DNS nội bộ giải quyết cả hai: tên mang nghĩa, và khi IP đổi chỉ cần sửa một bản ghi.
2. DNS nội bộ hoạt động thế nào
DNS nội bộ là một (hoặc vài) máy chủ DNS chỉ phục vụ trong mạng private, giữ một zone riêng không công khai ra Internet. Trong tài liệu này, zone của DNS nội bộ được gọi là internal.example. Ba khái niệm cần nắm:
- Zone thuận (forward zone): ánh xạ tên sang IP bằng bản ghi
A, và tên phụ sang tên chính bằngCNAME. - Zone ngược (reverse zone): ánh xạ IP sang tên bằng bản ghi
PTR, dùng khi log hay công cụ cần hiển thị tên thay vì IP. - Master và secondary: zone được sửa trên máy master. Máy secondary kéo bản sao qua zone transfer và tiếp tục phục vụ khi master gặp sự cố. Secondary biết zone đã đổi nhờ số serial trong bản ghi SOA tăng lên.
Khi một máy hỏi db-1.internal.example, resolver trên máy đó gửi truy vấn tới máy chủ DNS nội bộ khai trong /etc/resolv.conf, nhận về IP private và cache kết quả trong khoảng thời gian bằng TTL của bản ghi.
3. Cấu trúc repo và zone DNS nội bộ
Nên quản lý zone của DNS nội bộ trong một repo git ngay từ đầu, để mọi thay đổi đều có lịch sử và có thể review. Cấu trúc tối thiểu:
dns-repo/
├── named.conf.local # khai báo 2 zone
└── zones/
├── internal.example # zone thuận: A, CNAME
└── 10.in-addr.arpa # zone ngược: PTRFile named.conf.local trên máy master của DNS nội bộ:
zone "internal.example" {
type master;
file "/etc/bind/zones/internal.example";
notify yes;
also-notify { 10.0.0.3; }; # báo secondary ngay khi zone đổi
allow-transfer { 10.0.0.3; }; # chỉ secondary được kéo zone
};
zone "10.in-addr.arpa" {
type master;
file "/etc/bind/zones/10.in-addr.arpa";
allow-transfer { 10.0.0.3; };
};Dòng allow-transfer là bắt buộc: nếu bỏ trống, bất kỳ ai trong mạng cũng có thể tải toàn bộ danh bạ máy bằng một lệnh dig AXFR. Phần đầu zone thuận mẫu:
$TTL 3600
@ IN SOA internal.example. dns1.internal.example. (
42 ; serial: tăng mỗi lần sửa
3600 ; refresh
900 ; retry
864000 ; expire
300 ) ; negative cache TTL
IN NS dns1.internal.example.
IN NS dns2.internal.example.
dns1 IN A 10.0.0.2
dns2 IN A 10.0.0.3
vpn IN CNAME dns2
; BALANCER -------------------------------------------------
lb-1 IN A 10.0.0.11
; MINION ---------------------------------------------------
app-php74-1 IN A 10.0.0.21
app-php82-1 IN A 10.0.0.22
; DATABASE -------------------------------------------------
db-mysql80-1 IN A 10.0.0.31
; DỊCH VỤ --------------------------------------------------
ops IN A 10.0.0.40
build IN CNAME opsSerial chỉ cần là số nguyên tăng dần. Ví dụ trên dùng bộ đếm đơn giản; nhiều nơi chọn định dạng YYYYMMDDNN (ngày sửa cộng hai chữ số thứ tự trong ngày) để nhìn vào là biết zone được sửa lần cuối khi nào. Chọn kiểu nào cũng được, miễn là không bao giờ giảm.
4. Quy ước đặt tên máy trong DNS nội bộ
Cách đặt tên đơn giản nhất là vai trò + số thứ tự (minion1, db3). Cách này ổn khi cụm còn nhỏ, nhưng sẽ bộc lộ ba điểm yếu khi cụm lớn lên:
- Tên không nói phiên bản. Máy chạy PHP 7.2 hay 7.4? Phải SSH vào mới biết, trong khi đây là câu hỏi đầu tiên khi deploy một site.
- Số bị "thủng". Máy gỡ đi để lại khoảng trống, còn quy ước ngầm "minion số N đi cặp với database số N" bị phá khi một database phục vụ nhiều minion.
- Tên theo dự án lẫn vào. Tên kiểu
minion<tên-dự-án>dễ nhớ với người làm dự án đó nhưng không theo khuôn nào, script không đoán được.
Quy ước khuyến nghị có dạng vai-trò-runtime-số:
| Mẫu tên | Ví dụ | Đọc tên biết gì |
|---|---|---|
lb-N | lb-1 | Balancer có IP public, chạy nginx và certbot |
app-phpXY-N | app-php74-1 | Minion chạy PHP 7.4, máy thứ nhất của nhóm |
db-mysqlXY-N | db-mysql80-1 | Máy database MySQL 8.0 |
| Tên dịch vụ | ops, vpn, build | Máy có một chức năng duy nhất |
Đổi tên một máy đang chạy không thể làm một lần, vì script và cấu hình đang tham chiếu tên cũ. Cách an toàn là thêm tên mới dạng CNAME trỏ về tên cũ, chuyển dần các nơi tham chiếu sang tên mới, rồi mới đảo ngược: tên mới thành bản ghi A, tên cũ thành alias, cuối cùng xoá alias khi không còn ai dùng.
5. DNS nội bộ cho máy làm việc qua VPN
Người vận hành không ngồi trong mạng private. Máy làm việc kết nối vào cụm qua VPN, và để gõ được ssh app-php74-1 thì máy đó cũng phải hỏi được DNS nội bộ. Với OpenVPN, máy chủ đẩy DNS cho client bằng push:
# /etc/openvpn/server.conf (trích)
push "route 10.0.0.0 255.255.0.0"
push "dhcp-option DNS 10.0.0.2"
push "dhcp-option DNS 10.0.0.3"
push "dhcp-option DOMAIN internal.example"Dòng DOMAIN cho phép gõ tên ngắn: ssh app-php74-1 được resolver tự nối thành app-php74-1.internal.example. Hai lưu ý:
- Cách client áp dụng
dhcp-optionkhác nhau theo hệ điều hành. Windows nhận trực tiếp. Trên Linux thường phải có script cập nhật resolver (ví dụupdate-resolv-conf), thiếu nó thì VPN vẫn lên nhưng tên không resolve được. - Trên chính các Cloud VPS,
/etc/resolv.confphải khai DNS nội bộ trước DNS public, kèmsearch internal.example. Nếu không, lệnhmysql -h db-mysql80-1sẽ báo không tìm thấy host.
6. Lợi ích cho script vận hành
Khi tên đã ổn định trong DNS nội bộ, script chỉ cần biết tên máy, không cần biết IP:
# ~/.ssh/config: một khối cho mọi máy trong zone
Host lb-* app-* db-*
User deploy
IdentityFile ~/.ssh/id_ops
# chạy cùng một lệnh trên cả nhóm minion PHP 7.4
for h in app-php74-1 app-php74-2 app-php74-3; do
ssh "$h" 'hostname; systemctl is-active nginx'
done
# .env của website trỏ database bằng tên
DB_HOST=db-mysql80-1.internal.exampleKhi .env trỏ database bằng tên, việc dựng lại database trên Cloud VPS mới chỉ còn là đổi một bản ghi A và đợi hết TTL. Các website tiếp tục kết nối đúng mà không cần sửa từng file. Tên cũng xuất hiện trong output của script, nên log kiểu backup db-mysql80-1: OK dễ đọc hơn nhiều so với một dãy IP.
7. Quy trình sửa zone
Mọi thay đổi DNS nội bộ nên đi qua git, sau đó mới áp dụng lên master:
# 1. sửa zone, TĂNG SERIAL ở cả zone thuận và zone ngược
# 2. kiểm tra cú pháp trước khi nạp
named-checkconf /etc/bind/named.conf
named-checkzone internal.example /etc/bind/zones/internal.example
named-checkzone 10.in-addr.arpa /etc/bind/zones/10.in-addr.arpa
# 3. nạp lại zone, không cần restart named
rndc reload internal.example
rndc reload 10.in-addr.arpa
# 4. hỏi thẳng cả hai máy DNS, so serial
dig @10.0.0.2 internal.example SOA +short
dig @10.0.0.3 internal.example SOA +short
dig @10.0.0.2 app-php74-1.internal.example +short
dig @10.0.0.2 -x 10.0.0.21 +short8. Lỗi thường gặp khi tự quản DNS nội bộ
- Quên tăng serial. Master đã có bản ghi mới nhưng secondary không kéo về, vì serial không đổi. Dấu hiệu:
digvào hai máy DNS cho hai kết quả khác nhau. - Sửa zone thuận nhưng quên zone ngược (hoặc ngược lại). Tên resolve được nhưng reverse lookup trả rỗng, hoặc ngược lại.
- A và PTR lệch nhau. Bản ghi
Atrỏ một IP, cònPTRlại gán tên đó cho IP của máy khác. Tên thuận vẫn đúng nên khó phát hiện, nhưng log hiển thị theo reverse DNS sẽ báo sai tên máy. - TTL dài. TTL tính bằng ngày làm máy khác giữ IP cũ rất lâu. Với hạ tầng nội bộ hay đổi IP, nên dùng TTL vừa phải, và hạ TTL của riêng bản ghi sắp đổi xuống vài phút từ trước khi chuyển máy.
- Secondary không nằm trong NS. Với
notify yes, BIND chỉ gửi NOTIFY cho các máy có trong bản ghi NS hoặc khai bằngalso-notify. Thiếu cả hai, secondary chỉ cập nhật theo chu kỳrefresh. - Máy đã gỡ nhưng tên vẫn còn. Script vẫn SSH vào một IP mà sau này có thể được cấp cho máy khác. Gỡ máy phải đi kèm xoá cả bản ghi
AlẫnPTR.
Các khuyến nghị về serial, TTL và PTR trong RFC 1912 về các lỗi cấu hình DNS thường gặp vẫn còn nguyên giá trị.
9. Checklist và đánh đổi khi tự quản DNS nội bộ
Checklist mỗi lần sửa zone DNS nội bộ:
- Tăng serial ở mọi zone có thay đổi.
- Thêm hoặc xoá đồng thời bản ghi
AvàPTR. - Chạy
named-checkconfvànamed-checkzonetrước khi reload. - So serial trên master và secondary, kiểm tra
digthuận và ngược. - Commit thay đổi vào git kèm mô tả ngắn lý do.
| Được | Mất |
|---|---|
Script, cấu hình, .env dùng tên thay vì IP | Thêm một dịch vụ phải giữ sống: DNS chết thì mọi lệnh gọi bằng tên đều lỗi |
| Lịch sử thay đổi đầy đủ trong git | Sửa zone bằng tay, dễ quên serial, quên PTR |
| Hoàn toàn trong mạng private, không phụ thuộc bên ngoài | Máy hay dịch vụ không dùng resolver nội bộ sẽ không gọi được bằng tên |
Với một đội vận hành nhỏ, BIND cộng git là đủ dùng lâu dài. Điều quan trọng nhất không nằm ở phần mềm mà ở kỷ luật: mọi máy mới đều có tên trong DNS nội bộ trước khi được dùng, và mọi máy gỡ đi đều bị xoá tên.
Bài tiếp theo
Tầng Balancer: Nginx reverse proxy, SSL offload và định tuyến domain. Cấu trúc thư mục cấu hình trên balancer, các header proxy bắt buộc, timeout, gzip, HTTPS, và cách phân biệt 502 với 504.
