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

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.

Cốt lõi

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 df và 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, breakTạo dãy số 1–60 cho vòng chờ; break thoát vòng khi điều kiện đủ.
df --output=availChỉ 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

  1. Symptom: dịch vụ đang ở trạng thái nào, log của nó nói gì?
  2. Scope: ổ nào đầy? Các ổ khác có bị ảnh hưởng không?
  3. 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?
  4. Evidence: nếu không khớp, phần chênh lệch đang nằm ở đâu, ai giữ nó?
  5. 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>: du cộng dung lượng của file còn nhìn thấy, -s in tổng thay vì từng thư mục, -h dù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: +L1 chọn file đang mở có link count nhỏ hơn 1, thường đã bị xóa tên; +a kế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. -lname xét nội dung symlink; -printf in đườ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-failed xó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: rm chỉ 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ó lsof thì dùng sudo 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-logger có thể đã chạm giới hạn restart (start-limit-hit); dùng systemctl reset-failed trướ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 logrotate hoặc giới hạn của journald (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.

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