3 giờ sáng, PagerDuty báo “ES cluster red, ingest dropped”. Bạn login, check: 2 node disk 96%, 1 node 78%. Index bị set read-only. Filebeat đang queue log trên 200 máy. Bạn có 30 phút trước khi buffer Filebeat overflow và bắt đầu drop log.

Đây là tình huống thật mọi DevOps quản lý ELK sẽ gặp. Bài này là runbook đã được proven trong nhiều incident: thứ tự bước, command cụ thể, và quyết định nào cần tránh khi áp lực cao.

Mục tiêu bài:

  • Hiểu cơ chế disk watermark của ES
  • Quy trình recovery từng bước cho disk full
  • Free disk an toàn không corrupt data
  • Rebalance shard sau khi recover
  • Pattern prevent tái diễn

Phần 1: Disk watermark của ES

ES có 3 watermark threshold:

WatermarkDefaultHành vi khi vượt
cluster.routing.allocation.disk.watermark.low85%Không assign shard mới về node này
cluster.routing.allocation.disk.watermark.high90%Move shard hiện có ra node khác
cluster.routing.allocation.disk.watermark.flood_stage95%Set tất cả index có shard ở node này thành read-only

Flood-stage là chốt cuối. Khi vượt, ES set index block read_only_allow_delete: true. Ingest fail, Kibana write fail, alert rule fail.

Read-only protect cluster khỏi corrupt (write vào disk hết = file truncate). Đây là feature, không phải bug. Nhưng nếu không biết, dễ panic gõ chmod/rm -rf linh tinh.

Check current state:

curl -sS "http://es:9200/_cat/allocation?v"
shards disk.indices disk.used disk.avail disk.total disk.percent host
   234         85gb     97gb        3gb      100gb           97 es-1
   234         84gb     92gb        8gb      100gb           92 es-2
   200         70gb     78gb       22gb      100gb           78 es-3

Node es-1 đang flood-stage. Cluster bị block write.

Phần 2: Verify block trước khi unblock

KHÔNG unblock trước khi free được disk. Unblock + write vào disk hết = ES crash, có thể corrupt segment.

Verify:

curl -sS "http://es:9200/_settings?include_defaults=true&filter_path=*.settings.index.blocks*"

Index nào bị block sẽ thấy:

{
  "app-logs-2026.05.16": {
    "settings": {
      "index": {
        "blocks": {"read_only_allow_delete": "true"}
      }
    }
  }
}

Phần 3: Phase 1, free disk

Step 1: Identify largest indices

curl -sS "http://es:9200/_cat/indices?v&s=store.size:desc&bytes=gb" | head -20
health index                pri rep docs store.size
green  app-logs-2026.04.15   5   1  10m  45gb
green  app-logs-2026.04.16   5   1  9.5m 42gb
green  app-logs-2026.04.17   5   1  9.8m 43gb
...

Index nào cũ nhất + lớn nhất là target.

Step 2: Snapshot trước khi xoá (BẮT BUỘC)

Đừng bao giờ xoá index không có snapshot, kể cả đêm khuya áp lực.

# Verify snapshot repository exist
curl -sS "http://es:9200/_snapshot/_all"

# Trigger snapshot
curl -X PUT "http://es:9200/_snapshot/s3-backup/incident-$(date +%Y%m%d-%H%M%S)" \
  -H 'Content-Type: application/json' \
  -d '{
    "indices": "app-logs-2026.04.15,app-logs-2026.04.16,app-logs-2026.04.17",
    "include_global_state": false,
    "metadata": {"reason": "pre-deletion-disk-full"}
  }'

Đợi snapshot xong (GET /_snapshot/s3-backup/_status) trước khi sang step kế.

Step 3: Delete old indices

curl -X DELETE "http://es:9200/app-logs-2026.04.15"
curl -X DELETE "http://es:9200/app-logs-2026.04.16"

Mỗi delete free ngay disk space. Đợi 30 giây, check _cat/allocation. Khi node thấp dưới 90%, sang phase 2.

Step 4: Alternative khi không có snapshot

Tình huống tệ nhất: chưa setup snapshot. Hai option:

Option A: scp data ra S3 bằng tay

# Trên node ES, copy index folder ra disk khác hoặc S3
INDEX_UUID=$(curl -sS "http://es:9200/_cat/indices/app-logs-2026.04.15?h=uuid")
tar czf /tmp/backup-old.tar.gz /var/lib/elasticsearch/nodes/0/indices/${INDEX_UUID}
aws s3 cp /tmp/backup-old.tar.gz s3://emergency-backup/

Sau đó xoá. Không pretty nhưng có rollback.

Option B: chấp nhận risk + delete

Chỉ làm khi business chấp nhận data loss của index cũ. Communicate trước.

Phần 4: Phase 2, remove read-only flag

Sau khi disk dưới flood_stage threshold (95%), ES KHÔNG tự gỡ block. Bạn phải gỡ thủ công:

curl -X PUT "http://es:9200/_all/_settings" \
  -H 'Content-Type: application/json' \
  -d '{"index.blocks.read_only_allow_delete": null}'

Hoặc per index:

curl -X PUT "http://es:9200/app-logs-*/_settings" \
  -H 'Content-Type: application/json' \
  -d '{"index.blocks.read_only_allow_delete": null}'

Verify ingest resumed:

# Tail log Filebeat hoặc Vector
journalctl -u filebeat -n 50 -f

Sau 1-2 phút thấy log mới index thành công = phục hồi xong write path.

Phần 5: Phase 3, rebalance

Disk được free nhưng shard có thể vẫn lệch (vì ES đã stop move shard khi vượt high watermark).

curl -sS "http://es:9200/_cat/allocation?v"
shards disk.indices disk.used disk.avail disk.total disk.percent host
   220         65gb     78gb       22gb      100gb           78 es-1
   215         63gb     76gb       24gb      100gb           76 es-2
   210         60gb     72gb       28gb      100gb           72 es-3

Ba node gần bằng nhau là OK. Nếu vẫn lệch:

# Force reroute thủ công
curl -X POST "http://es:9200/_cluster/reroute?retry_failed=true"

Hoặc move shard cụ thể:

curl -X POST "http://es:9200/_cluster/reroute" \
  -H 'Content-Type: application/json' \
  -d '{
    "commands": [{
      "move": {
        "index": "app-logs-2026.05.16",
        "shard": 0,
        "from_node": "es-1",
        "to_node": "es-3"
      }
    }]
  }'

Đợi _cluster/health về green.

Phần 6: Phase 4, postmortem và prevent

Sau incident, đừng đi ngủ. Setup prevention ngay khi còn nhớ context.

Action 1: ILM policy

Index Lifecycle Management auto-rotate index cũ. Pattern:

curl -X PUT "http://es:9200/_ilm/policy/app-logs-policy" \
  -H 'Content-Type: application/json' \
  -d '{
    "policy": {
      "phases": {
        "hot": {
          "actions": {
            "rollover": {"max_age": "1d", "max_size": "50gb"}
          }
        },
        "warm": {
          "min_age": "7d",
          "actions": {
            "shrink": {"number_of_shards": 1},
            "forcemerge": {"max_num_segments": 1}
          }
        },
        "cold": {
          "min_age": "30d",
          "actions": {
            "searchable_snapshot": {"snapshot_repository": "s3-backup"}
          }
        },
        "delete": {
          "min_age": "90d",
          "actions": {"delete": {}}
        }
      }
    }
  }'

Attach policy vào index template để index mới tự inherit. Không bao giờ phải xoá thủ công nữa.

Action 2: Monitoring alert disk

Tạo alert rule Kibana fire khi disk vượt 80%:

curl -X POST "http://kibana:5601/api/alerting/rule" \
  -H 'kbn-xsrf: true' \
  -H 'Authorization: ApiKey ...' \
  -d '{
    "name": "Disk usage warning",
    "rule_type_id": ".es-query",
    "consumer": "alerts",
    "schedule": {"interval": "5m"},
    "params": {
      "index": [".monitoring-es-*"],
      "esQuery": "{...query về node disk percent...}",
      "threshold": [80],
      "thresholdComparator": ">"
    },
    "actions": [...]
  }'

Hoặc Prometheus + Alertmanager nếu đã có infra đó.

Action 3: Auto-snapshot daily

curl -X PUT "http://es:9200/_slm/policy/daily-snapshot" \
  -H 'Content-Type: application/json' \
  -d '{
    "schedule": "0 30 1 * * ?",
    "name": "<daily-snap-{now/d}>",
    "repository": "s3-backup",
    "config": {
      "indices": ["app-logs-*"],
      "ignore_unavailable": true,
      "include_global_state": false
    },
    "retention": {
      "expire_after": "30d",
      "min_count": 7,
      "max_count": 30
    }
  }'

SLM (Snapshot Lifecycle Management) tự chạy daily, retain 30 ngày. Không bao giờ phải snapshot bằng tay trong incident nữa.

Action 4: Watermark tăng tạm thời

Trong incident, nếu muốn câu giờ trong khi free disk, có thể nâng watermark tạm:

curl -X PUT "http://es:9200/_cluster/settings" \
  -H 'Content-Type: application/json' \
  -d '{
    "transient": {
      "cluster.routing.allocation.disk.watermark.low": "92%",
      "cluster.routing.allocation.disk.watermark.high": "94%",
      "cluster.routing.allocation.disk.watermark.flood_stage": "97%"
    }
  }'

Quan trọng: dùng transient không persistent. Settings sẽ revert khi cluster restart. Đừng để override này tồn tại sau incident.

Phần 7: Shard imbalance không liên quan disk

Đôi khi disk OK nhưng shard vẫn lệch. Nguyên nhân:

Nguyên nhân 1: Custom routing

Index template có routing.required: true và data về cùng routing key:

{"settings": {"routing": {"required": true}}}

Data về cùng customer X = cùng shard X. Fix: bỏ custom routing hoặc reindex.

Nguyên nhân 2: Allocation awareness chưa balance

Nếu set cluster.routing.allocation.awareness.attributes, ES distribute theo attribute đó (vd: zone). Nếu zone không đều = shard lệch.

Check:

curl -sS "http://es:9200/_cluster/settings?include_defaults=true&flat_settings=true" \
  | grep awareness

Nguyên nhân 3: Node mới chưa nhận đủ shard

Vừa scale node mới, shard chưa kịp move. Đợi 30 phút và check lại. Force reroute nếu vẫn chưa:

curl -X POST "http://es:9200/_cluster/reroute?retry_failed=true"

Phần 8: Story thực tế

Một incident ELK production tôi xử lý đầu 2026: cluster 6 node, 4 node ở 94%, 2 node ở 60%. Lý do lệch: 4 node “old” thuộc một storage class cũ, 2 node “new” mới scale ra thuộc class khác, nhưng allocation awareness setup theo tag data_tier mặc định hot, tag mới cho 2 node là warm. Index app-logs-* chỉ allow allocate vào hot = không bao giờ rebalance sang node mới.

Quy trình xử lý:

  1. Free disk: delete 5 ngày cũ nhất sau khi snapshot (mất 15 phút)
  2. Unblock read-only (1 phút)
  3. Update index template, remove tier filter để index mới có thể allocate vào cả hot lẫn warm
  4. Manual move 30 shard từ node “old” sang node “new” qua reroute API
  5. Postmortem: ILM policy cũ không có warm phase, viết lại. SLM chưa setup, setup luôn.

Tổng thời gian recovery: 45 phút. Không mất data (snapshot OK). Lessons learned đi vào runbook chính thức.

Phần 9: Anti-pattern

Tránh tuyệt đối khi panic:

ActionTại sao tránh
rm -rf /var/lib/elasticsearch/...Corrupt cluster state. Dùng API delete.
Restart node ES khi disk fullCó thể không lên lại được. Free disk trước.
Disable replica để câu diskCó thể fail trong khi disable, mất primary. Snapshot trước.
Set number_of_replicas: 0 permanentlyLose redundancy hoàn toàn. Chỉ tạm.
Force allocate empty primaryMất data shard đó vĩnh viễn.
Ignore alert “warning” suốt 1 tuầnĐến lúc red mới hành động = quá muộn.

Cheatsheet

Tình huốngAction
Disk 90%+Snapshot + delete oldest indices
Index read-only sau floodFree disk -> PUT /_settings {"index.blocks.read_only_allow_delete": null}
Shard unassignedGET /_cluster/allocation/explain -> đọc reason
Hot shardReroute thủ công hoặc reindex
Cluster yellow sau scaleĐợi recovery xong, monitor _cat/recovery
Cluster redIdentify red index, check primary lost, snapshot restore
PreventionILM + SLM + Alert + tăng disk before 80%

Lời kết

Recovery disk full không khó nếu có quy trình. Nguy hiểm nằm ở panic + skip step. Bài này là cheatsheet để khi 3 giờ sáng PagerDuty kêu, bạn có cái để mở ra đọc thay vì hỏi Google “elasticsearch disk full how to fix” và copy command từ Stack Overflow đầu tiên.

Bài cuối series Kibana từ A đến Z sẽ là performance tuning chiều sâu: JVM heap sizing, field caps cache, merge throttling. Những thứ bạn chỉ chạm vào sau khi đã master các tầng trên. Đến đây là gần đích rồi, hẹn gặp ở bài 28.