HỌC BẰNG THỰC HÀNH
← Lộ trình học
01 · Linux Administration · BREAK/FIX

Break/Fix: API không nghe đúng port

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.

Cốt lõi

Tình huống

Đội frontend báo: gọi http://127.0.0.1:8081/ trên server thì nhận Connection refused. Người deploy khẳng định đã cấu hình port 8081 trong unit file và systemctl status kdc-api vẫn báo active (running).

Nhiệm vụ: khôi phục để kdc-api phục vụ trên port 8081, vẫn chạy dưới systemd, và viết RCA ngắn.

Vì sao cần học

"Dịch vụ đang chạy" và "dịch vụ đang phục vụ đúng chỗ" là hai việc khác nhau. Bài này luyện thói quen tin vào evidence của hệ thống đang chạy (ss, journalctl, systemctl show) thay vì tin vào file cấu hình mà bạn nghĩ đang có hiệu lực.

Mục tiêu

  • Đi theo trình tự Symptom → Scope → Layer → Evidence → Hypothesis → Fix.
  • Đọc socket đang nghe bằng ss -ltnp và log bằng journalctl.
  • Hiểu thứ tự ưu tiên giữa Environment= và EnvironmentFile= trong systemd.

Điều kiện tiên quyết

  • Bài Vận hành web service bằng systemd.

Tạo sự cố

Đọc kỹ script trước khi chạy. Script từ chối chạy nếu không thấy marker /etc/kdc-lab. Lưu nội dung vào ~/break-wrong-port.sh trên VM rồi chạy sudo bash ~/break-wrong-port.sh.

#!/usr/bin/env bash
# KDC Break/Fix: wrong port. 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 và cố ý cấu hình sai dịch vụ kdc-api. Tiếp tục? [y/N] " answer
[[ $answer == y ]] || exit 1

id -u kdc-api &>/dev/null || useradd --system --no-create-home --shell /usr/sbin/nologin kdc-api
install -d -m 755 /opt/kdc-api /etc/kdc
cat > /opt/kdc-api/app.py <<'PY'
import os
from http.server import BaseHTTPRequestHandler, HTTPServer


class Health(BaseHTTPRequestHandler):
    def do_GET(self):
        body = b"kdc-api ok\n"
        self.send_response(200)
        self.send_header("Content-Type", "text/plain")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)


bind, port = os.environ["BIND"], int(os.environ["PORT"])
print(f"kdc-api listening on {bind}:{port}", flush=True)
HTTPServer((bind, port), Health).serve_forever()
PY
cat > /etc/kdc/api.env <<'ENV'
# Runtime settings for kdc-api
BIND=127.0.0.1
PORT=8018
ENV
chmod 640 /etc/kdc/api.env
cat > /etc/systemd/system/kdc-api.service <<'UNIT'
[Unit]
Description=KDC demo API
After=network.target

[Service]
User=kdc-api
Environment=BIND=127.0.0.1 PORT=8081
EnvironmentFile=/etc/kdc/api.env
ExecStart=/usr/bin/python3 /opt/kdc-api/app.py
Restart=on-failure

[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now kdc-api.service
echo "Đã tạo sự cố. Bắt đầu từ: curl http://127.0.0.1:8081/"
Giải thích lệnh và tham số

sudo bash ~/break-wrong-port.sh chạy file script đã lưu bằng Bash với quyền quản trị. read -r -p "..." answer đọc xác nhận: -r giữ dấu backslash nguyên văn, -p in câu hỏi, answer là tên biến nhận câu trả lời. Chỉ nhập đúng y mới qua điều kiện [[ $answer == y ]]; Enter mặc định dừng.

id -u kdc-api chỉ in UID của user; &>/dev/null bỏ cả stdout/stderr, || useradd ... chỉ tạo user nếu chưa tìm thấy. Các khối <<'PY', <<'ENV', <<'UNIT' ghi file Python, cấu hình và unit; nội dung bên trong không phải lệnh terminal để chạy từng dòng. Các cấu trúc Bash này được giới thiệu ở bài tiến trình.

Ngay sau khi tạo sự cố, bấm Kiểm tra kết quả một lần: các tiêu chí phải chưa đạt. Đó là trạng thái xuất phát.

Điều tra

Không sửa gì trước khi có evidence. Ghi lại lệnh và kết quả quan trọng vào RCA khi đi.

  1. Symptom: tái hiện lỗi đúng như người báo.
  2. Scope: lỗi chỉ ở port 8081, hay dịch vụ không phục vụ ở đâu cả?
  3. Layer: tiến trình có chạy không, có socket nào đang nghe không, nghe ở đâu?
  4. Evidence: dịch vụ thật sự nhận cấu hình gì khi chạy?
  5. Hypothesis → Fix: sửa đúng một chỗ, restart, kiểm tra lại từ góc nhìn client.

Đọc các công cụ điều tra

ss -ltnp chọn socket đang nghe (-l), TCP (-t), IP/port số (-n), tiến trình (-p). Bộ lọc 'sport = :8081' giới hạn cổng local 8081, không sửa socket. journalctl -u <unit> -n 20 --no-pager đọc log gần nhất. systemctl show ... -p Environment -p EnvironmentFiles chọn hai thuộc tính để đối chiếu các nguồn cấu hình; việc đọc thuộc tính chưa thay đổi dịch vụ.

Gợi ý (chỉ mở khi bế tắc)

  • Mức 1, hướng nhìn: tiến trình đang chạy thật. Câu hỏi là nó đang nghe ở đâu.
  • Mức 2, công cụ: sudo ss -ltnp | grep python, journalctl -u kdc-api --no-pager -n 20, systemctl show kdc-api -p Environment -p EnvironmentFiles.
  • Mức 3, một bước: đọc mục EnvironmentFile= trong systemd.exec(5): biến đọc từ file ghi đè biến khai báo bằng Environment=. So sánh hai nguồn cấu hình.

Viết RCA

Tạo ~/kdc-labs/rca/wrong-port.md (lab root của agent):

# RCA: kdc-api không phục vụ trên port 8081

## Triệu chứng
## Evidence
## Nguyên nhân gốc
## Cách sửa
## Phòng tránh

Phần Phòng tránh nên trả lời: làm sao để lần sau chỉ có một nguồn cấu hình port, và kiểm tra nào sau deploy sẽ phát hiện lỗi này ngay?

Tự kiểm tra (Verification)

sudo ss -ltnp 'sport = :8081'
curl -s http://127.0.0.1:8081/
systemctl is-enabled kdc-api
Giải thích lệnh và tham số

ss -ltnp gộp socket đang lắng nghe (-l), TCP (-t), địa chỉ/cổng dạng số (-n) và tiến trình (-p). Mẫu sport = :8081 lọc cổng nguồn 8081; dấu nháy giữ nguyên mẫu. curl -s ẩn thanh tiến độ; systemctl is-enabled xem service có tự chạy khi boot.

Chọn server và bấm Kiểm tra kết quả.

Ghi chú production

  • Health check sau deploy phải gọi đúng endpoint client dùng, không chỉ hỏi systemctl is-active.
  • Một giá trị cấu hình chỉ nên có một nguồn. Khi buộc phải có nhiều lớp, ghi rõ thứ tự ưu tiên. Bạn sẽ gặp lại vấn đề này ở ConfigMap, biến môi trường và Helm values.

Dọn dẹp

sudo systemctl disable --now kdc-api.service
sudo rm -f /etc/systemd/system/kdc-api.service /etc/kdc/api.env
sudo rm -rf /opt/kdc-api
sudo systemctl daemon-reload
sudo userdel kdc-api
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. userdel xóa tài khoản lab; không có -r nên không xóa thêm home.

Tài liệu chính thức