Skip to content

🎯 Mục tiêu

🎯 Sau bài này bạn sẽ nắm được:

  • Tư duy triage khi đối mặt với incident production — bình tĩnh, có hệ thống, không đoán mò
  • 5 first-response commands cần chạy ngay khi SSH vào server gặp sự cố
  • Playbook chi tiết cho 5 incident phổ biến nhất: Disk Full, CPU Spike, OOM Killer, Port Conflict, Service Failure
  • Monitoring basics — ngưỡng cảnh báo và công cụ giám sát từ miễn phí đến production-grade
  • Anti-pattern "The Reboot Fix" và tại sao nó phá hoại hệ thống dài hạn

Production Incident Playbook — Xử Lý Sự Cố Production

Lúc 2 giờ sáng, PagerDuty reo. Server production trả về 502. Hàng nghìn user đang bị ảnh hưởng. Bạn SSH vào server — và không biết bắt đầu từ đâu.

Đây là khoảnh khắc mà sự khác biệt giữa junior và senior engineer thể hiện rõ nhất. Không phải ở kiến thức kỹ thuật — mà ở quy trình xử lý có hệ thống. Bài này trang bị cho bạn playbook thực chiến, copy-paste được, cho 5 incident phổ biến nhất trên Linux server.


🧠 Phần 1: Tư Duy Triage — Đừng Hoảng Loạn

Incident response không phải về tốc độ gõ lệnh — mà về tốc độ ra quyết định đúng. Quy trình 6 bước dưới đây là kim chỉ nam cho mọi tình huống:

Quy trình 6 bước

  1. Breathe — Hít thở → Panic khiến bạn gõ lệnh sai. Chậm 30 giây để suy nghĩ rõ ràng tốt hơn là nhanh 30 giây rồi phá hỏng thêm.

  2. Gather Data — Thu thập dữ liệu → Đừng đoán. Đo lường. Chạy các diagnostic commands trước khi kết luận bất kỳ điều gì.

  3. Contain — Ngăn chặn lan rộng → Scale out thêm instance, redirect traffic sang server khỏe mạnh, bật maintenance page. Mục tiêu: giảm blast radius.

  4. Fix — Sửa chữa → Áp dụng thay đổi nhỏ nhất có thể giải quyết vấn đề. Đây không phải lúc để refactor hay "tiện thể sửa luôn cái kia".

  5. Verify — Xác nhận → Confirm rằng fix thực sự hoạt động. Kiểm tra metrics, test endpoint, xem logs — đừng giả định fix đã work chỉ vì bạn đã chạy lệnh.

  6. Document — Ghi chép → Post-mortem trong vòng 48 giờ. Timeline, root cause, actions taken, lessons learned. Đây là cách team tránh lặp lại sai lầm.

⛔ Quy Tắc Sống Còn

KHÔNG BAO GIỜ chạy lệnh destructive (rm, kill -9, DROP TABLE) khi đang hoảng loạn. Đọc lại lệnh 2 lần trước khi nhấn Enter. Một lệnh rm -rf / gõ nhầm sẽ biến incident nhỏ thành thảm họa không thể phục hồi.


🔍 Phần 2: First Response Commands

Khi SSH vào server gặp sự cố, chạy tuần tự 5 nhóm lệnh sau để nhanh chóng có bức tranh tổng thể. Copy-paste cả block — không cần nhớ từng lệnh:

bash
# ============================================
# FIRST RESPONSE — Chạy ngay khi SSH vào server
# ============================================

# 1. Quick overview: load average, uptime, ai đang login
uptime
who

# 2. CPU & Memory tổng quan
top -b -n 1 | head -20
free -h

# 3. Disk usage — kiểm tra partition nào đang đầy
df -h
du -sh /var/log/* | sort -rh | head -10

# 4. Lỗi gần đây — xem có gì bất thường
journalctl -p err --since "1 hour ago" | tail -30
dmesg | tail -20

# 5. Network — ports đang listen và tổng connections
ss -tlnp          # Listening ports
ss -tn | wc -l    # Total connections hiện tại

📌 Mẹo thực chiến

Lưu block lệnh trên thành script ~/first-response.sh trên mọi server. Khi incident xảy ra, chỉ cần chạy bash ~/first-response.sh thay vì nhớ từng lệnh trong lúc căng thẳng.

Sau khi chạy xong 5 nhóm lệnh, bạn sẽ biết ngay vấn đề thuộc dạng nào: Disk, CPU, Memory, Network, hay Service. Từ đó chọn đúng playbook bên dưới.


🚨 Phần 3: Incident Playbooks

Incident #1: Disk Full (100%)

Triệu chứng: Ứng dụng không ghi được log, database từ chối write, deploy thất bại.

bash
# Xác nhận disk full
$ df -h /
Filesystem  Size  Used  Avail  Use%  Mounted on
/dev/sda1   50G   50G    0      100%  /

Điều tra — tìm thủ phạm chiếm disk:

bash
# Top 5 thư mục nặng nhất trong /var
$ du -sh /var/* | sort -rh | head -5
30G    /var/log
15G    /var/lib/docker
3G     /var/cache

# File log lớn nhất
$ ls -lhS /var/log/*.log | head -5

# Tìm log files > 1GB
$ find /var/log -name "*.log" -size +1G

# Kiểm tra systemd journal chiếm bao nhiêu
$ journalctl --disk-usage

Emergency fix — giải phóng disk ngay lập tức:

bash
# Dọn journal cũ (giữ lại 2 ngày gần nhất)
sudo journalctl --vacuum-time=2d

# Ép logrotate chạy ngay
sudo logrotate -f /etc/logrotate.conf

# Dọn Docker artifacts không dùng (⚠️ xóa unused images + volumes)
docker system prune -a --volumes

# Tìm file đã bị xóa nhưng vẫn chiếm disk (process vẫn giữ file handle)
sudo lsof +L1 | grep deleted
# → Nếu tìm thấy, restart process đó để giải phóng disk

⚠️ Cạm bẫy

Vấn đề: Bạn rm một file log 20GB, nhưng df -h vẫn báo disk full.

Nguyên nhân: Process vẫn giữ file descriptor mở. File bị xóa khỏi directory listing nhưng disk space chưa được giải phóng cho đến khi process đóng file handle.

Giải pháp: Dùng lsof +L1 | grep deleted để tìm, sau đó restart process tương ứng. Hoặc nếu không muốn restart, truncate file: > /path/to/file.log (xóa nội dung mà không xóa file).

Phòng ngừa dài hạn:

  • Cấu hình logrotate cho mọi ứng dụng — rotate hàng ngày, giữ 7 ngày, nén bằng gzip
  • Set alert khi disk usage > 80% (ở 80% bạn có thời gian xử lý, ở 95% bạn đang cháy nhà)
  • Đặt SystemMaxUse=500M trong /etc/systemd/journald.conf để giới hạn journal size
  • Schedule docker system prune chạy weekly qua cron

Incident #2: CPU Spike (100%)

Triệu chứng: Server phản hồi chậm, SSH lag, load average cao bất thường.

bash
# Tìm process chiếm CPU nhiều nhất
$ top -b -n 1 -o %CPU | head -15
# hoặc
$ ps aux --sort=-%cpu | head -10

Điều tra sâu hơn:

bash
# Nếu là app của bạn — xem nó đang làm gì
$ strace -p <PID> -c    # Tổng hợp system calls (chạy ~10 giây rồi Ctrl+C)

# Kiểm tra thread count và memory của process
$ cat /proc/<PID>/status | grep -E "Threads|VmRSS|Name"

# CPU profiling — xem CPU đang bận ở function nào
$ timeout 30 perf top -p <PID>

Nguyên nhân thường gặp:

Nguyên nhânDấu hiệu nhận biếtCách xử lý
Infinite loop trong code1 process chiếm 100% 1 core liên tụcDebug code, deploy hotfix
Regex backtrackingCPU spike khi xử lý input cụ thểTối ưu regex, thêm timeout
Unindexed DB queryDB process chiếm CPU, slow query logThêm index, optimize query
Crypto miner (bị hack)Process lạ, tên ngẫu nhiên, ẩn trong crontabIsolate server, incident response security
Fork bombProcess count tăng nhanh, `:(){ ::& };:`

Emergency fix:

bash
# Giảm priority của process (cho server thở)
$ renice +19 -p <PID>

# Giới hạn CPU bằng cgroup (không cần kill)
$ sudo systemctl set-property myapp.service CPUQuota=50%

# Nuclear option — chỉ dùng khi cần thiết
$ kill <PID>          # SIGTERM — cho process shutdown gracefully
$ kill -9 <PID>       # SIGKILL — chỉ khi SIGTERM không work sau 30 giây

Incident #3: OOM Killer Strikes

Triệu chứng: Service tự nhiên biến mất, không crash log, application restart bất thường.

bash
# Kiểm tra OOM killer có hoạt động không
$ dmesg | grep -i "oom\|killed"
# Output mẫu:
# [12345.678] Out of memory: Killed process 1234 (myapp) total-vm:4096000kB score 850

# Process nào đang dùng nhiều memory nhất
$ ps aux --sort=-%mem | head -10

# Tổng quan memory hệ thống
$ free -h
$ cat /proc/meminfo | grep -E "MemTotal|MemFree|MemAvailable|SwapTotal|SwapFree"

OOM Score — ai bị kill trước?

bash
# Xem OOM score của một process (càng cao càng dễ bị kill)
$ cat /proc/<PID>/oom_score

# Bảo vệ process quan trọng khỏi OOM killer (set score = -1000)
$ echo -1000 | sudo tee /proc/<PID>/oom_score_adj

Emergency fix:

bash
# 1. Restart service bị kill
$ sudo systemctl restart myapp

# 2. Kiểm tra xem có process nào đang leak memory không
$ watch -n 5 'ps aux --sort=-%mem | head -10'
# → Quan sát trong 5 phút, nếu memory liên tục tăng → memory leak

Phòng ngừa dài hạn:

  • Set MemoryMax=2G trong systemd unit file để giới hạn memory per service
  • Set MemoryHigh=1.5G để kernel bắt đầu throttle trước khi chạm limit
  • Monitor memory trends — leak thường tăng dần theo thời gian
  • Dùng swap làm safety net: sudo fallocate -l 2G /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
  • Investigate root cause: profiler (Valgrind, Go pprof, Python tracemalloc)

Incident #4: Port Already In Use

Triệu chứng: Service không start được, log báo "Address already in use".

bash
# Khi start service bị lỗi
$ sudo systemctl start myapp
# → Job for myapp.service failed
# → "Error: listen EADDRINUSE: address already in use :::8000"

# Tìm process đang chiếm port
$ ss -tlnp | grep 8000
LISTEN  0  128  *:8000  *:*  users:(("python",pid=1234,fd=5))

Xử lý theo tình huống:

bash
# Trường hợp 1: Process cũ chưa tắt hẳn (stale process)
$ kill 1234                          # Gửi SIGTERM
$ sleep 2                            # Chờ graceful shutdown
$ ss -tlnp | grep 8000              # Kiểm tra port đã được giải phóng
$ sudo systemctl start myapp         # Start lại service

# Trường hợp 2: 2 service cùng dùng 1 port (misconfiguration)
$ ss -tlnp | grep 8000              # Xem service nào đang dùng
# → Đổi port trong config của 1 trong 2 service

# Trường hợp 3: TIME_WAIT socket giữ port
$ ss -tn | grep 8000 | grep TIME-WAIT
# → Chờ 60 giây hoặc set SO_REUSEADDR trong code

📌 Phòng ngừa port conflict

Luôn dùng ExecStartPre trong systemd unit để kiểm tra port trước khi start:

ini
[Service]
ExecStartPre=/bin/sh -c '! ss -tlnp | grep -q ":8000 "'
ExecStart=/usr/bin/myapp --port 8000

Nếu port đã bị chiếm, service sẽ fail ngay ở ExecStartPre với error message rõ ràng thay vì crash bí ẩn.


Incident #5: Service Won't Start

Triệu chứng: systemctl start myapp thất bại, service ở trạng thái failed.

bash
# Bước 1: Xem status và exit code
$ systemctl status myapp
# → Active: failed (Result: exit-code)
# → Process: 1234 ExecStart=/usr/bin/myapp (code=exited, status=1/FAILURE)

# Bước 2: Đọc log chi tiết
$ journalctl -u myapp -n 50 --no-pager

# Bước 3: Thử chạy manual để xem error rõ hơn
$ sudo -u myapp-user /usr/bin/myapp --config /etc/myapp/config.yaml

Nguyên nhân thường gặp và cách fix:

bash
# 1. Missing dependency (module, library, config file)
$ journalctl -u myapp | grep -i "not found\|no such\|missing\|import"
# Fix: cài dependency thiếu, kiểm tra path trong config

# 2. Permission denied
$ journalctl -u myapp | grep -i "permission denied\|access denied"
# Fix: kiểm tra ownership và permissions
$ ls -la /etc/myapp/config.yaml      # Config readable?
$ ls -la /var/log/myapp/             # Log dir writable?
$ sudo chown -R myapp-user:myapp-group /var/log/myapp/

# 3. Port conflict (xem Incident #4)
$ journalctl -u myapp | grep -i "address already in use\|EADDRINUSE"

# 4. Environment variable thiếu
$ systemctl show myapp | grep Environment
# Fix: thêm vào .service file hoặc EnvironmentFile

Flowchart chẩn đoán:

Service failed to start

  ├── journalctl có error message rõ ràng?
  │   ├── Có → Đọc message, fix theo hướng dẫn cụ thể
  │   └── Không → Kiểm tra ExecStart path, User, WorkingDirectory

  ├── Permission denied?
  │   └── Kiểm tra chown/chmod trên files và directories liên quan
  │       $ namei -l /path/to/file    # Xem permission từng level

  ├── Port đang bị chiếm?
  │   └── ss -tlnp | grep <PORT> → kill stale process hoặc đổi port

  ├── Dependency chưa ready?
  │   └── Kiểm tra After= và Wants= trong .service file
  │       Thêm: After=network.target postgresql.service

  └── Vẫn không rõ nguyên nhân?
      └── Chạy binary bằng tay (manual) dưới cùng user:
          $ sudo -u myapp-user /usr/bin/myapp
          → Error hiện ra trực tiếp trên terminal

📊 Phần 4: Monitoring Basics — Phòng Bệnh Hơn Chữa Bệnh

Incident response giỏi nhất là không có incident. Monitoring giúp bạn phát hiện vấn đề trước khi nó trở thành sự cố.

Ngưỡng cảnh báo cần thiết lập

Metric⚠️ Warning🔴 CriticalGhi chú
CPUSustained > 80%Sustained > 95%Spike ngắn (< 1 phút) thường không đáng lo
Memory> 85%> 95%Xem trend — tăng dần = memory leak
Disk> 80%> 90%Ở 80% bạn có thời gian, ở 95% bạn đang chữa cháy
Load Average> số CPU cores> 2× CPU coresnproc để xem số cores
Process CountTăng bất thường > 20%Tăng > 50%Fork bomb hoặc connection leak
Open Connections> expected baseline> 3× baselineDDoS, connection leak, hoặc slow backend

Công cụ monitoring — từ miễn phí đến production-grade

Tầng 1 — Miễn phí, có sẵn trên mọi Linux server:

bash
# Real-time CPU, memory, processes
$ htop                    # Interactive (cần cài: apt install htop)
$ top -b -n 1 | head -20 # Non-interactive, scriptable

# Disk usage
$ df -h                   # Partition usage
$ iostat -x 1 5           # Disk I/O (cần cài: apt install sysstat)

# Network
$ ss -tlnp                # Listening ports
$ ss -s                   # Connection statistics
$ iftop                   # Real-time bandwidth (cần cài: apt install iftop)

# Logs
$ journalctl -f           # Follow system logs real-time
$ tail -f /var/log/syslog # Follow syslog

Tầng 2 — Open-source, self-hosted:

Công cụVai tròĐặc điểm
node_exporterThu thập metrics từ LinuxLightweight agent, chạy trên mỗi server
PrometheusLưu trữ và query metricsPull-based, PromQL, alerting rules
GrafanaDashboard và visualizationKết nối Prometheus, đẹp, customizable
AlertmanagerGửi alertEmail, Slack, PagerDuty integration

Tầng 3 — SaaS, production-grade:

  • Datadog — All-in-one: metrics, logs, APM, tracing
  • New Relic — APM mạnh, tốt cho application-level monitoring
  • AWS CloudWatch — Native cho AWS infrastructure
  • Grafana Cloud — Managed Prometheus + Grafana, free tier hào phóng

🎓 Lời khuyên từ thực chiến

Đừng cố setup Prometheus + Grafana từ ngày đầu nếu bạn chỉ có 1-2 server. Bắt đầu với một script cron đơn giản:

bash
# /usr/local/bin/health-check.sh — chạy mỗi 5 phút qua cron
#!/bin/bash
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | tr -d '%')
MEM_USAGE=$(free | awk '/Mem/{printf "%.0f", $3/$2*100}')
LOAD=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | xargs)

if [ "$DISK_USAGE" -gt 80 ] || [ "$MEM_USAGE" -gt 85 ]; then
    echo "ALERT: disk=${DISK_USAGE}% mem=${MEM_USAGE}% load=${LOAD}" \
        | mail -s "Server Alert: $(hostname)" oncall@team.com
fi

Monitoring đơn giản mà chạy tốt hơn monitoring phức tạp mà chưa setup xong.


✍️ Phần 5: Bài Tập Nhanh

Bài 1: Nhận diện incident từ monitoring output

Đọc output dưới đây và xác định đây là loại incident nào, cùng hành động tiếp theo:

bash
$ uptime
 14:23:07 up 45 days, load average: 0.50, 0.45, 0.40

$ free -h
              total   used   free   shared   buff/cache   available
Mem:          16Gi    15Gi   200Mi  100Mi    800Mi        500Mi
Swap:         0B      0B     0B

$ dmesg | tail -5
[3456789.123] Out of memory: Killed process 8812 (java) total-vm:12345678kB
[3456789.124] oom_reaper: reaped process 8812 (java), now anon-rss:0kB
💡 Đáp án Bài 1

Incident type: OOM Killer Strikes (Incident #3)

Dấu hiệu:

  • Memory gần hết: 15Gi/16Gi used, chỉ còn 500Mi available
  • Không có swap (0B) — không có safety net
  • dmesg ghi rõ "Out of memory: Killed process 8812 (java)"

Hành động tiếp theo:

  1. sudo systemctl restart java-app — restart service bị kill
  2. ps aux --sort=-%mem | head -10 — kiểm tra process nào đang dùng nhiều memory
  3. Thêm swap: sudo fallocate -l 4G /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
  4. Set MemoryMax=8G trong systemd unit file cho java app
  5. Investigate memory leak trong Java app (heap dump, profiler)

Bài 2: Đọc journalctl, tìm root cause

bash
$ systemctl status webapp
 webapp.service - Web Application
     Active: failed (Result: exit-code) since Mon 2024-01-15 03:12:45 UTC
     Process: 9901 ExecStart=/opt/webapp/bin/start.sh (code=exited, status=1)

$ journalctl -u webapp -n 10 --no-pager
Jan 15 03:12:44 prod-01 start.sh[9901]: Loading config from /etc/webapp/app.conf
Jan 15 03:12:44 prod-01 start.sh[9901]: Config loaded successfully
Jan 15 03:12:45 prod-01 start.sh[9901]: Starting HTTP server on :3000...
Jan 15 03:12:45 prod-01 start.sh[9901]: Error: listen tcp :3000: bind: address already in use
Jan 15 03:12:45 prod-01 systemd[1]: webapp.service: Main process exited, code=exited, status=1
💡 Đáp án Bài 2

Root cause: Port 3000 đang bị process khác chiếm (Incident #4 — Port Already In Use)

Hành động:

bash
# Tìm process chiếm port 3000
$ ss -tlnp | grep 3000
# → Xác định process, kill nếu là stale process

$ kill <PID>
$ sudo systemctl start webapp

Lưu ý: Config load thành công, chỉ fail ở bước bind port → không phải lỗi config hay permission.


⚠️ Phần 6: Production Anti-Pattern — "The Reboot Fix"

⚠️ Cạm bẫy

Kịch bản quen thuộc:

  1. Có gì đó bị hỏng → "Thôi reboot đi cho nhanh"
  2. Reboot xong → hoạt động lại bình thường → Tuyên bố "đã fix"
  3. 3 ngày sau → sự cố lặp lại → "Reboot lần nữa thôi"
  4. Vòng lặp tiếp tục cho đến khi sự cố xảy ra lúc 3 giờ sáng ngày Black Friday

Tại sao đây là anti-pattern nguy hiểm:

  • Root cause không được tìm ra — vấn đề sẽ tái diễn, chỉ là khi nào
  • Mất toàn bộ evidence — sau reboot, memory state, process state, connection state đều biến mất. Bạn mất forensics data
  • Tất cả connections bị drop — user đang active bị disconnect, transaction bị abort
  • Cache lạnh — sau reboot, cache rỗng, database bị hit nặng, response time tăng vọt (cold start)
  • Downtime tích lũy — mỗi reboot = 2-5 phút downtime. 10 lần reboot/tháng = 30-50 phút mất mát

Cách tiếp cận đúng:

  1. Thu thập data TRƯỚC KHI fix (screenshot top, free -h, dmesg, journalctl)
  2. Diagnose root cause bằng playbook phía trên
  3. Fix root cause — không phải triệu chứng
  4. Reboot chỉ khi là bước cuối cùng và không còn lựa chọn nào khác (ví dụ: kernel update yêu cầu reboot)
  5. Sau khi fix, viết post-mortem để team cùng học