Break/Fix: ổ log đầy nhưng không thấy file lớn
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-logger ghi log vào /mnt/kdc-logs và liên tục crash. Ca trực trước thấy ổ đầy nên đã xóa file debug-trace.log rất lớn, nhưng df vẫn báo 100%. Thư mục archive/ chứa log audit phải giữ 90 ngày theo chính sách, không được xóa.
Nhiệm vụ: giải phóng dung lượng, đưa kdc-logger chạy lại, giữ nguyên archive/, và viết RCA.
Vì sao cần học
Ổ đầy là một trong những sự cố production phổ biến nhất: database ngừng ghi, dịch vụ không tạo được file tạm, có khi không SSH vào được. Phản xạ "xóa file lớn nhất" thường sai: có thể xóa nhầm dữ liệu cần giữ, hoặc như ở đây, xóa xong mà dung lượng không được trả lại.
Mục tiêu
- Đọc
dfvàdu, giải thích vì sao hai con số có thể khác nhau. - Hiểu file bị xóa nhưng còn tiến trình mở thì vẫn chiếm dung lượng.
- Tìm tiến trình và unit đang giữ file, xử lý mà không reboot.
- Đưa dịch vụ đã vượt giới hạn restart chạy lại.
Điều kiện tiên quyết
- Bài Vận hành web service bằng systemd và Thêm ổ dữ liệu bằng LVM và fstab.
Tạo sự cố
Script tạo một filesystem 128 MB trên file loop, chỉ tồn tại tới lần reboot, nên không đụng tới ổ thật. Đọc kỹ script, lưu vào ~/break-disk-full.sh trên VM, chạy sudo bash ~/break-disk-full.sh.
#!/usr/bin/env bash
# KDC Break/Fix: disk full. 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 ổ loop /mnt/kdc-logs và làm đầy nó. Tiếp tục? [y/N] " answer
[[ $answer == y ]] || exit 1
img=/var/lib/kdc-logs.img
mnt=/mnt/kdc-logs
systemctl stop kdc-logger.service kdc-debug-trace.service 2>/dev/null || true
if mountpoint -q "$mnt"; then umount "$mnt"; fi
rm -f "$img"
truncate -s 128M "$img"
mkfs.ext4 -q -F -m 0 -L kdc-logs "$img"
install -d "$mnt"
mount -o loop "$img" "$mnt"
id -u kdc-logger &>/dev/null || useradd --system --no-create-home --shell /usr/sbin/nologin kdc-logger
install -d -m 755 "$mnt/archive"
for month in 07 08 09; do
head -c 10M /dev/zero > "$mnt/archive/audit-2026-$month.log"
done
install -o kdc-logger -g kdc-logger -m 644 /dev/null "$mnt/app.log"
install -d -m 755 /opt/kdc-logger
cat > /opt/kdc-logger/logger.py <<'PY'
import os
import sys
import time
from datetime import datetime
LOG_DIR = "/mnt/kdc-logs"
MIN_FREE = 5 * 1024 * 1024
with open(f"{LOG_DIR}/app.log", "a", buffering=1) as log:
while True:
disk = os.statvfs(LOG_DIR)
if disk.f_bavail * disk.f_frsize < MIN_FREE:
print(f"FATAL: less than 5 MiB free on {LOG_DIR}; stopping so no log line is lost", file=sys.stderr, flush=True)
sys.exit(1)
log.write(f"{datetime.now().isoformat()} INFO request handled\n")
time.sleep(1)
PY
cat > /opt/kdc-logger/debug_trace.py <<'PY'
import time
with open("/mnt/kdc-logs/debug-trace.log", "wb", buffering=0) as trace:
chunk = b"TRACE " * 174763
try:
while True:
trace.write(chunk)
except OSError:
pass
while True:
time.sleep(3600)
PY
systemd-run --unit=kdc-debug-trace --description="Debug trace started during an old incident" \
/usr/bin/python3 /opt/kdc-logger/debug_trace.py
for _ in $(seq 60); do
(( $(df --output=avail "$mnt" | tail -1) < 4096 )) && break
sleep 1
done
rm -f "$mnt/debug-trace.log"
cat > /etc/systemd/system/kdc-logger.service <<'UNIT'
[Unit]
Description=KDC demo logger
[Service]
User=kdc-logger
ExecStart=/usr/bin/python3 /opt/kdc-logger/logger.py
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now kdc-logger.service
echo "Đã tạo sự cố. Bắt đầu từ: systemctl status kdc-logger"
Giải thích lệnh và tham số
| Lệnh / tùy chọn trong script | Ý nghĩa |
|---|---|
mountpoint -q "$mnt" | Kiểm tra đường dẫn có là mountpoint; -q không in thông báo, dùng mã thoát trong if. |
truncate -s 128M "$img" | Đặt kích thước file image 128 MiB; file có thể là sparse file, chưa chiếm đủ dung lượng tương ứng. |
mkfs.ext4 -q -F -m 0 -L kdc-logs "$img" | -q giảm thông báo; -F cho phép format file thường; -m 0 không dành phần trăm block cho superuser; -L đặt label. Lệnh ghi filesystem mới vào file image lab. |
mount -o loop "$img" "$mnt" | -o nhận tùy chọn mount; loop dùng file image như thiết bị block qua loop device. |
head -c 10M /dev/zero | -c lấy số byte thay vì số dòng; tạo 10 MiB dữ liệu zero để làm file mẫu. |
install ... /dev/null "$mnt/app.log" | Tạo file rỗng từ nguồn /dev/null, đặt owner/group và quyền bằng các tùy chọn install. |
systemd-run --unit=... --description=... <lệnh> | Tạo unit tạm và chạy lệnh dưới systemd; hai tùy chọn đặt tên unit và mô tả. |
seq 60, break | Tạo dãy số 1–60 cho vòng chờ; break thoát vòng khi điều kiện đủ. |
df --output=avail | Chỉ lấy cột dung lượng khả dụng; trong môi trường GNU mặc định thông thường tính theo block 1 KiB. |
Script dùng image riêng /var/lib/kdc-logs.img để mô phỏng ổ đầy; không format ổ hệ điều hành. Khối Python ghi bằng <<'PY' là mã dịch vụ, không phải các lệnh shell để gõ từng dòng.
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: dịch vụ đang ở trạng thái nào, log của nó nói gì?
- Scope: ổ nào đầy? Các ổ khác có bị ảnh hưởng không?
- Layer: dung lượng "đã dùng" theo filesystem (
df) và theo tổng kích thước file nhìn thấy (du) có khớp nhau không? - Evidence: nếu không khớp, phần chênh lệch đang nằm ở đâu, ai giữ nó?
- Fix: xử lý đúng nguồn chiếm dung lượng, đưa dịch vụ chạy lại, kiểm tra từ góc nhìn dịch vụ.
Đọc các công cụ điều tra
du -sh <thư-mục>:ducộng dung lượng của file còn nhìn thấy,-sin tổng thay vì từng thư mục,-hdùng đơn vị dễ đọc.df -hđo dung lượng filesystem, nên hai kết quả có thể khác.lsof +aL1 /mnt/kdc-logs:+L1chọn file đang mở có link count nhỏ hơn 1, thường đã bị xóa tên;+akết hợp điều kiện đó với filesystem được chỉ định. Lệnh giúp xem file đã xóa nhưng vẫn mở trên đúng filesystem lab.find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n': tìm symlink FD có đích khớp mẫu deleted.-lnamexét nội dung symlink;-printfin đường dẫn%p, đích link%l, xuống dòng\n. Các dấu nháy giữ nguyên mẫu/định dạng.ps ... -p <PID>: chọn đúng PID;systemctl reset-failedxóa trạng thái failed và bộ đếm giới hạn khởi động lại, chưa tự start dịch vụ.
Các lệnh xem trạng thái giúp xác định nguồn dung lượng; tự chọn cách sửa sau khi thu đủ evidence.
Gợi ý (chỉ mở khi bế tắc)
- Mức 1, hướng nhìn:
rmchỉ xóa tên file. Dữ liệu chỉ được giải phóng khi không còn ai mở file. - Mức 2, công cụ:
df -h /mnt/kdc-logs,sudo du -sh /mnt/kdc-logs,sudo lsof +aL1 /mnt/kdc-logs. Không cólsofthì dùngsudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n'. - Mức 3, một bước: có PID rồi thì tìm unit sở hữu nó:
ps -o pid,unit,cmd -p <PID>. Dừng đúng unit đó. Sau đókdc-loggercó thể đã chạm giới hạn restart (start-limit-hit); dùngsystemctl reset-failedtrước khi start lại.
Viết RCA
Tạo ~/kdc-labs/rca/disk-full.md:
# RCA: /mnt/kdc-logs đầy, kdc-logger crash
## Triệu chứng
## Evidence
## Nguyên nhân gốc
## Cách sửa
## Phòng tránh
Trong Evidence, ghi lại con số df và du trước khi sửa và PID/unit giữ file. Trong Phòng tránh, nghĩ về: log rotation, giới hạn kích thước log debug, cảnh báo dung lượng trước khi đầy.
Tự kiểm tra (Verification)
df -h /mnt/kdc-logs
ls -l /mnt/kdc-logs/archive
systemctl is-active kdc-logger
tail -3 /mnt/kdc-logs/app.log
Giải thích lệnh và tham số
df -h xem dung lượng filesystem chứa đường dẫn với đơn vị dễ đọc (-h). ls -l liệt kê file chi tiết; tail -3 đọc ba dòng log cuối. systemctl is-active xem trạng thái service.
Chọn server và bấm Kiểm tra kết quả.
Ghi chú production
- Không xóa file log đang được ghi. Muốn giải phóng ngay mà không dừng tiến trình, truncate nội dung (
: > file) rồi xử lý nguyên nhân; xóa thì phải khởi động lại tiến trình đang giữ file. - Dùng
logrotatehoặc giới hạn củajournald(SystemMaxUse=) thay vì dọn tay. - Cảnh báo ở mức 80–85% để còn thời gian xử lý (Module 17).
Dọn dẹp
sudo systemctl stop kdc-debug-trace.service 2>/dev/null || true
sudo systemctl disable --now kdc-logger.service
if mountpoint -q /mnt/kdc-logs; then sudo umount /mnt/kdc-logs; fi
sudo rm -f /var/lib/kdc-logs.img /etc/systemd/system/kdc-logger.service
sudo rm -rf /opt/kdc-logger
sudo systemctl daemon-reload
sudo userdel kdc-logger
Giải thích lệnh và tham số
2>/dev/null bỏ stderr; || true cho phép tiếp tục khi lệnh dừng service phụ thất bại. mountpoint -q kiểm tra mountpoint bằng mã thoát, không in thông báo; if ...; then ...; fi chỉ unmount khi đang mount. 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.