Một balancer thường phục vụ nhiều website, mỗi site ghi access log và error log ra file riêng. Khi một site chậm, câu hỏi đầu tiên luôn là: IP nào đang gửi nhiều request nhất, có phải bot không, lỗi 5xx bắt đầu từ lúc nào? Nếu log nằm rải rác, muốn trả lời phải SSH vào từng balancer rồi grep, awk trên nhiều file. Tài liệu này hướng dẫn dùng Fluent Bit gom log truy cập nginx về Loki, tra cứu bằng LogQL và vẽ dashboard trên Grafana: kiến trúc, cấu hình mẫu, cách chọn label, câu truy vấn hay dùng và lỗi thường gặp.
1. Tổng quan: log rải trên nhiều balancer
Quy ước phổ biến là mỗi site một cặp file log, đặt tên theo domain:
/var/log/nginx/example.com.access # access log, định dạng combined
/var/log/nginx/example.com.error # error log của riêng site
/var/log/nginx/site-a.example.access
/var/log/nginx/site-a.example.error
...Tách file theo site tiện khi xem một site, nhưng gần như vô dụng khi cần nhìn cả cụm: không so được site nào đông nhất, không thấy một IP đang quét nhiều site cùng lúc. Cần một chỗ tập trung, truy vấn được theo site, balancer và thời gian.
2. Khái niệm: Fluent Bit, Loki và Grafana
- Fluent Bit là agent thu thập log viết bằng C. Nó chạy như một pipeline: input đọc dữ liệu (ví dụ
tailtheo dõi file), filter biến đổi bản ghi, output đẩy đi nơi khác. Fluent Bit có sẵn plugin output cho Loki. - Loki là hệ thống lưu log của Grafana. Khác Elasticsearch, Loki không đánh chỉ mục toàn văn mà chỉ đánh chỉ mục các label. Mỗi tổ hợp label khác nhau là một stream. Nội dung dòng log được nén thành chunk và chỉ được quét khi truy vấn.
- Grafana truy vấn Loki bằng LogQL, hiển thị thành bảng, biểu đồ và gửi cảnh báo.
Cách đánh chỉ mục này quyết định thiết kế phía sau: label phải ít và ổn định, còn IP, URL, status để trong dòng log.
Chọn Fluent Bit hay Grafana Alloy
| Tiêu chí | Fluent Bit | Grafana Alloy |
|---|---|---|
| Ngôn ngữ, bộ nhớ | C, RAM thường chỉ vài chục MB | Go, có garbage collector; trên máy nhỏ nên đặt GOMEMLIMIT và MemoryMax để tránh OOM |
| Phạm vi | Thu thập và đẩy log, nhiều output | Log, metric, trace trong một agent |
| Hợp với | VPS nhỏ, đã có Fluent Bit cho luồng log khác | Cụm cần gom cả metric và trace qua một agent |
Balancer thường là VPS nhỏ. Nếu máy đã chạy sẵn Fluent Bit (ví dụ để đẩy log WAF), hãy mở rộng cấu hình của nó thay vì thêm agent thứ hai chỉ để đọc file log.
3. Kiến trúc thu thập log
các balancer máy monitor (mạng private)
┌───────────────────────────────┐ ┌───────────────────────────┐
│ nginx │ │ Loki (single binary) │
│ /var/log/nginx/*.access ──┐ │ HTTP push │ tsdb schema v13 │
│ /var/log/nginx/*.error ──┼──┼─────────────▶│ filesystem, retention │
│ /var/log/coraza/audit.log ┘ │ :3100 ├───────────────────────────┤
│ Fluent Bit (tail + Lua) │ │ Grafana: dashboard, alert │
└───────────────────────────────┘ └───────────────────────────┘Loki và Grafana có thể chạy chung trên máy monitor đã có Prometheus. Balancer đẩy log qua mạng private tới cổng 3100, không mở cổng này ra Internet. Phần cấu hình Loki đáng chú ý:
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index: { prefix: index_, period: 24h }
limits_config:
reject_old_samples: true
reject_old_samples_max_age: 168h
retention_period: 14d
max_streams_per_user: 10000
compactor:
working_directory: /var/lib/loki/compactor
retention_enabled: true
retention_delete_delay: 2h
delete_request_store: filesystemretention_period chỉ có tác dụng khi compactor bật retention_enabled. Thiếu dòng đó, Loki giữ log mãi và đĩa máy monitor đầy dần.
4. Cấu hình Fluent Bit trên balancer
Ý tưởng chính: tên file đã chứa sẵn domain, nên không cần sửa từng file cấu hình site. Fluent Bit dùng Tag_Regex lấy domain từ đường dẫn file và gắn vào tag, sau đó một script Lua nhỏ chuyển tag thành trường host và log_type.
# /etc/fluent-bit/fluent-bit.conf (trích, phần log nginx)
[INPUT]
Name tail
Path /var/log/nginx/*.access
Tag_Regex /var/log/nginx/(?<domain>[^/]+)\.access$
Tag nginx.access.<domain>
Path_Key filename
Db /var/log/nginx-access-fluent-bit.db
Mem_Buf_Limit 15MB
Skip_Long_Lines On
Refresh_Interval 5
# khối [INPUT] thứ hai giống hệt cho *.error:
# Path /var/log/nginx/*.error, Tag nginx.error.<domain>, Db riêng
[FILTER]
Name lua
Match nginx.*
script /etc/fluent-bit/extract_nginx.lua
call cb_tag_extract
[OUTPUT]
Name loki
Match nginx.*
Host 10.0.0.120
Port 3100
Labels job=nginx-traffic, instance=balancer-a
Label_Keys $host, $log_type
Auto_Kubernetes_Labels off
Drop_Single_Key raw-- /etc/fluent-bit/extract_nginx.lua
function cb_tag_extract(tag, timestamp, record)
-- tag dạng nginx.access.example.com hoặc nginx.error.example.com
local log_type, domain = tag:match("^nginx%.([^%.]+)%.(.+)$")
if domain then
record["host"] = domain
record["log_type"] = log_type
end
record["filename"] = nil
return 1, timestamp, record
endBa chi tiết làm cấu hình Fluent Bit này chạy đúng:
Db: Fluent Bit lưu vị trí đã đọc (inode và offset) vào SQLite. Khi restart hoặc khi logrotate đổi tên file, nó đọc tiếp từ chỗ dừng, không gửi trùng và không bỏ sót.Label_Keys $host, $log_type: lấy hai trường do Lua tạo ra làm label. Biến lấy từ bản ghi phải có dấu$ở đầu, thiếu dấu này Fluent Bit hiểu là chuỗi tĩnh.- Xoá
filenametrong Lua: sau khi tách label, bản ghi chỉ còn một khoálog, vàDrop_Single_Key rawgửi đúng dòng log gốc lên Loki. Nếu còn thừa khoá, dòng log bị bọc thành JSON{"log":"...","filename":"..."}và mọi câu truy vấn bóc tách phía sau đều phải sửa theo.
Danh sách tham số đầy đủ nằm ở tài liệu output Loki của Fluent Bit. Kiểm tra cú pháp trước khi khởi động lại:
/opt/fluent-bit/bin/fluent-bit -c /etc/fluent-bit/fluent-bit.conf --dry-run
systemctl restart fluent-bit
journalctl -u fluent-bit -n 50 --no-pager | grep inotify_fs_add | tail -n 55. Định dạng log: combined hay JSON?
Nếu access log đang ở định dạng combined mặc định của nginx, không cần đổi: bóc tách lúc truy vấn bằng bộ phân tích pattern của LogQL.
| pattern `<ip> - - [<time>] "<method> <uri> <proto>" <status> <bytes> "<referer>" "<user_agent>"`Nếu dựng mới từ đầu, log JSON gọn hơn: Loki đọc bằng | json, không phụ thuộc thứ tự cột, dễ thêm trường như request_time:
log_format json_log escape=json
'{"time":"$time_iso8601","ip":"$remote_addr","host":"$host",'
'"method":"$request_method","uri":"$request_uri","status":$status,'
'"bytes":$body_bytes_sent,"rt":$request_time,"ua":"$http_user_agent"}';Cái giá phải trả: filter fail2ban và các script khác thường dùng regex khớp dòng combined. Đổi định dạng đồng nghĩa với viết lại và kiểm thử lại chúng. Trên cụm đang chạy, giữ combined và dùng pattern là đường ít rủi ro nhất.
6. Label trong Loki: ít mà chắc
Bộ label khuyến nghị chỉ gồm bốn thứ: job, instance (tên balancer), host (domain) và log_type (access hoặc error). Số stream xấp xỉ bằng số balancer nhân số site nhân hai, nằm xa giới hạn max_streams_per_user.
Tránh đưa IP, URL hay status vào label. Mỗi IP mới tạo một stream mới, lượng IP bot lớn sẽ làm Loki chậm và đầy chỉ mục. Đây là lỗi cardinality cao mà tài liệu về label của Loki cảnh báo rất rõ. IP và status vẫn truy vấn được, chỉ là bóc tách lúc đọc thay vì lúc ghi.
7. LogQL mẫu dùng hằng ngày
Top 10 IP gửi nhiều request nhất vào một site trong 1 giờ:
topk(10, sum by (ip) (
count_over_time(
{job="nginx-traffic", host="example.com", log_type="access"}
| pattern `<ip> - - <_>`
[1h]
)
))Tỉ lệ 5xx của từng site trong 5 phút:
sum by (host) (
count_over_time({job="nginx-traffic", log_type="access"}
| pattern `<_> "<_>" <status> <_>` | status >= 500 [5m])
)
/
sum by (host) (
count_over_time({job="nginx-traffic", log_type="access"} [5m])
)Tìm lỗi upstream trong error log:
{job="nginx-traffic", log_type="error"} |= "upstream timed out"Luôn chọn label trước ({...}) để Loki chỉ mở đúng các stream cần thiết, rồi mới lọc chuỗi. Cú pháp chi tiết xem ở tài liệu truy vấn log của Loki.
8. Dashboard theo dõi traffic từng website
Một dashboard traffic và lỗi nginx nên được provision từ file JSON trên máy monitor để dễ tái tạo, gồm bốn nhóm panel:
- Tổng quan: tổng request, số website có traffic, số lỗi 5xx, 4xx trong khoảng thời gian đang chọn.
- Lưu lượng: request/giây của các site đông nhất, phân bố mã HTTP theo thời gian.
- Xếp hạng: top website, top IP.
- Điều tra: lỗi 5xx theo từng site, bảng access log và error log trực tiếp.
Biến của dashboard là thứ dùng nhiều nhất: balancer và host lấy từ giá trị label, ô client_ip để xem lịch sử truy cập của một IP, và công tắc "chỉ pageview" lọc bỏ file tĩnh bằng !~ `\.(js|css|png|jpg|svg|woff2?)`.
9. Triển khai Fluent Bit hàng loạt
Chạy thử trên một balancer trước, sau đó đóng gói việc cài đặt thành một pipeline nhận tham số là tên balancer (cách tổ chức xem bài Jenkins làm "control plane" cho hàng trăm website). Thứ tự các bước:
- Kiểm tra SSH, nginx, gọi
/readycủa Loki, xem đĩa và RAM. - Backup
/etc/fluent-bit/và cấu hình logrotate vào thư mục có timestamp. - Cài Fluent Bit từ repo chính thức nếu máy chưa có.
- Đẩy script Lua, sinh
fluent-bit.confvớiinstanceđúng tên balancer, giữ nguyên các luồng log đã có (ví dụ log WAF). --dry-run, restart, kiểm tra service active và RAM.- Bước nào lỗi thì tự khôi phục cấu hình từ bản backup.
10. Lỗi thường gặp khi đưa log nginx lên Loki
| Triệu chứng | Nguyên nhân | Cách xử lý |
|---|---|---|
Trên Loki không thấy label host, log_type | Viết Label_Keys host, thiếu dấu $ | Dùng $host, $log_type |
Truy vấn pattern không ra kết quả | Dòng log bị bọc JSON vì bản ghi còn thừa khoá | Xoá khoá thừa trong Lua, hoặc thêm | json | line_format "{{.log}}" |
| Không thấy log cũ trước lúc cài | Mặc định tail đọc từ cuối file với file mới phát hiện | Chấp nhận, hoặc bật Read_from_Head nhưng Loki sẽ từ chối dòng cũ hơn reject_old_samples_max_age |
| Loki chậm, cảnh báo quá nhiều stream | Đưa IP, URL hoặc status vào label | Giữ label ít và cố định, bóc tách lúc truy vấn |
| Fluent Bit tạm ngừng đọc khi Loki bảo trì | Bộ đệm chạm Mem_Buf_Limit | Bình thường: nhờ Db, Fluent Bit đọc tiếp khi Loki trở lại |
11. Checklist
- Khảo sát server trước khi chọn agent, tận dụng agent đã có.
- Label là thiết kế, không phải chi tiết. Bốn label ổn định giúp Loki nhẹ, mọi thứ còn lại để LogQL bóc tách.
- Tận dụng quy ước đặt tên. Log đã tách theo domain thì Fluent Bit tự nhận site mới mà không phải sửa cấu hình.
- Bật retention ở compactor và theo dõi đĩa máy monitor.
- Dùng chung pipeline Fluent Bit cho audit log của WAF, nền tảng cho cảnh báo tấn công.
Cấu hình nginx phía balancer đã được giới thiệu ở bài Tầng Balancer: Nginx reverse proxy, SSL offload và định tuyến domain.
Bài tiếp theo
Coraza WAF và OWASP CRS 4 trên Nginx. Cách build module Coraza khớp nginx của Debian 12, chọn paranoia level và ngưỡng điểm, xử lý chặn nhầm, rồi đưa audit log về Loki để cảnh báo.
