Break/Fix: health check timeout vì phân giải tên sai
Tạo sự cố trong phạm vi lab của bài, thu bằng chứng, sửa rồi kiểm tra.
Tình huống
Dịch vụ kdc-poller gọi health check của API tại http://api.kdc.lab:8095/health mỗi 3 giây. Sau đợt "chuyển API sang máy mới" tuần trước, log tràn ngập health check failed. Đội API khẳng định API vẫn chạy ngay trên máy này.
Nhiệm vụ: làm kdc-poller gọi được API, không sửa mã nguồn hay unit của poller, và viết RCA.
Vì sao cần học
"It's always DNS" là câu đùa quen thuộc của người vận hành, vì lỗi phân giải tên trông giống lỗi mạng, lỗi firewall hoặc lỗi ứng dụng. Kỹ năng then chốt là kiểm tra tên được phân giải theo đúng đường ứng dụng dùng, không theo công cụ bạn quen tay.
Mục tiêu
- Phân biệt timeout (gói tin không có trả lời) với connection refused (máy đích từ chối).
- Kiểm tra phân giải tên bằng
getent hosts, hiểu vì saodigcó thể cho kết quả khác. - Đọc thứ tự nguồn trong
/etc/nsswitch.confvà quy tắc dòng đầu tiên thắng trong/etc/hosts.
Điều kiện tiên quyết
- Bài Lệnh mạng và cấu hình bền vững bằng nmcli.
Tạo sự cố
Đọc kỹ script, lưu vào ~/break-dns.sh trên VM, chạy sudo bash ~/break-dns.sh.
#!/usr/bin/env bash
# KDC Break/Fix: DNS. Chỉ chạy trên VM lab có /etc/kdc-lab.
set -euo pipefail
[[ $EUID -eq 0 ]] || { echo "Chạy bằng sudo." >&2; exit 1; }
grep -qs '^KDC-LAB' /etc/kdc-lab || { echo "Không thấy marker /etc/kdc-lab; script chỉ chạy trên máy lab." >&2; exit 1; }
read -r -p "Script sẽ tạo kdc-api-v2, kdc-poller và sửa /etc/hosts. Tiếp tục? [y/N] " answer
[[ $answer == y ]] || exit 1
for name in kdc-api-v2 kdc-poller; do
id -u "$name" &>/dev/null || useradd --system --no-create-home --shell /usr/sbin/nologin "$name"
done
install -d -m 755 /opt/kdc-dns
cat > /opt/kdc-dns/api.py <<'PY'
from http.server import BaseHTTPRequestHandler, HTTPServer
class Health(BaseHTTPRequestHandler):
def do_GET(self):
body = b"ok\n"
self.send_response(200 if self.path == "/health" else 404)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
HTTPServer(("127.0.0.1", 8095), Health).serve_forever()
PY
cat > /opt/kdc-dns/poller.sh <<'SH'
#!/usr/bin/bash
while :; do
if curl -fsS --max-time 2 "http://${API_HOST}:8095/health" >/dev/null; then
date --iso-8601=seconds > "$STATE_DIRECTORY/last-ok"
else
echo "health check to ${API_HOST} failed" >&2
fi
sleep 3
done
SH
chmod 755 /opt/kdc-dns/poller.sh
cat > /etc/systemd/system/kdc-api-v2.service <<'UNIT'
[Unit]
Description=KDC API v2
[Service]
User=kdc-api-v2
ExecStart=/usr/bin/python3 /opt/kdc-dns/api.py
Restart=on-failure
[Install]
WantedBy=multi-user.target
UNIT
cat > /etc/systemd/system/kdc-poller.service <<'UNIT'
[Unit]
Description=KDC health poller
[Service]
User=kdc-poller
Environment=API_HOST=api.kdc.lab
StateDirectory=kdc-poller
ExecStart=/opt/kdc-dns/poller.sh
Restart=on-failure
[Install]
WantedBy=multi-user.target
UNIT
# Ghi đè tại chỗ để giữ nguyên inode và SELinux label của /etc/hosts.
grep -v 'api\.kdc\.lab' /etc/hosts > /tmp/kdc-hosts || true
cat /tmp/kdc-hosts > /etc/hosts && rm -f /tmp/kdc-hosts
echo '10.255.255.1 api.kdc.lab # API cũ, đã chuyển đi' >> /etc/hosts
rm -rf /var/lib/kdc-poller
systemctl daemon-reload
systemctl enable --now kdc-api-v2.service kdc-poller.service
echo "Đã tạo sự cố. Bắt đầu từ: journalctl -u kdc-poller -f"
Giải thích lệnh và tham số
curl -fsS --max-time 2 <URL> gộp -f (trả lỗi khi HTTP báo lỗi từ 400), -s (ẩn progress), -S (vẫn in lỗi khi dùng silent); --max-time 2 giới hạn toàn bộ lần gọi ở 2 giây. grep -v 'api\.kdc\.lab' giữ các dòng không khớp tên này; \. trong regex là dấu chấm thật. Script dùng kết quả đó để ghi lại /etc/hosts tại chỗ rồi thêm dòng mẫu gây sự cố.
>/dev/null bỏ nội dung phản hồi curl nhưng vẫn dùng được mã thoát trong if; >&2 gửi thông báo lỗi tới stderr. sleep 3 chờ giữa các lần kiểm tra. Đây là script tạo sự cố đã có xác nhận, không chạy từng dòng cấu hình bằng tay.
Bấm Kiểm tra kết quả một lần để thấy trạng thái xuất phát.
Điều tra
- Symptom: log của poller nói gì, mỗi lần thất bại mất bao lâu?
- Scope: API có trả lời khi gọi bằng địa chỉ IP không?
- Layer: lỗi nằm ở kết nối TCP hay ở bước trước đó?
- Evidence: tên
api.kdc.labđược phân giải ra đâu, từ nguồn nào? - Fix: sửa nguồn phân giải, xác minh bằng chính cách ứng dụng phân giải tên.
Đọc các công cụ điều tra
curl -v <URL> in chi tiết kết nối/request/response để xác định điểm lỗi; -v ở curl là verbose. getent hosts <tên> dùng cơ chế phân giải của hệ điều hành, còn dig <tên> và nslookup <tên> hỏi DNS server, thường không đọc /etc/hosts. grep -n kdc.lab /etc/hosts thêm số dòng (-n) để dễ tìm vị trí cấu hình. resolvectl flush-caches xóa cache của systemd-resolved khi dịch vụ này đang được sử dụng; không cần chạy nếu máy không dùng nó.
Gợi ý (chỉ mở khi bế tắc)
- Mức 1, hướng nhìn: mỗi lần thất bại mất đúng 2 giây: đó là timeout, không phải bị từ chối.
- Mức 2, công cụ:
curl -v http://127.0.0.1:8095/health,getent hosts api.kdc.lab,grep hosts /etc/nsswitch.conf,grep -n kdc.lab /etc/hosts. - Mức 3, một bước: trong
/etc/hosts, dòng đầu tiên khớp tên sẽ thắng. Thêm dòng đúng ở cuối file không có tác dụng khi dòng cũ vẫn còn.
Viết RCA
Tạo ~/kdc-labs/rca/dns.md:
# RCA: kdc-poller timeout khi gọi api.kdc.lab
## Triệu chứng
## Evidence
## Nguyên nhân gốc
## Cách sửa
## Phòng tránh
Trong Phòng tránh, nghĩ về: ai được sửa /etc/hosts, vì sao nên dùng DNS nội bộ, và TTL/cache ảnh hưởng thế nào khi đổi bản ghi DNS thật.
Tự kiểm tra (Verification)
getent hosts api.kdc.lab
journalctl -u kdc-poller -n 5 --no-pager
sudo cat /var/lib/kdc-poller/last-ok
Giải thích lệnh và tham số
journalctl -u kdc-poller lọc log theo unit (-u); -n 5 lấy năm dòng gần nhất, --no-pager in trực tiếp ra terminal. getent hosts tra cứu tên theo cấu hình hệ thống; cat đọc marker lần thành công.
Chọn server và bấm Kiểm tra kết quả.
Ghi chú production
digvànslookupchỉ hỏi DNS server, bỏ qua/etc/hostsvà cache cục bộ. Ứng dụng dùng thư viện hệ thống, nêngetent hostsmới phản ánh đúng.- Trên máy có
systemd-resolvedhoặcnscd, kết quả có thể bị cache; xóa cache sau khi sửa (resolvectl flush-caches). - Khi chuyển dịch vụ sang máy mới, hạ TTL của bản ghi DNS trước vài giờ để client cập nhật nhanh. Module 02 sẽ thực hành.
Dọn dẹp
sudo systemctl disable --now kdc-poller.service kdc-api-v2.service
sudo rm -f /etc/systemd/system/kdc-poller.service /etc/systemd/system/kdc-api-v2.service
sudo systemctl daemon-reload
sudo sed -i '/api\.kdc\.lab/d' /etc/hosts
sudo rm -rf /opt/kdc-dns /var/lib/kdc-poller
sudo userdel kdc-poller; sudo userdel kdc-api-v2
Giải thích lệnh và tham số
systemctl disable --now bỏ cấu hình tự chạy khi boot và dừng service ngay. daemon-reload yêu cầu systemd đọc lại unit file sau khi sửa/xóa. rm -f xóa file và không hỏi xác nhận; -r thêm xóa đệ quy thư mục cùng nội dung, nên -rf gộp hai tùy chọn này. sed -i sửa /etc/hosts tại chỗ; /api\.kdc\.lab/d xóa dòng khớp hostname lab. userdel xóa các tài khoản lab.