Khi hạ tầng mở rộng từ một máy chủ đơn lẻ lên một cụm gồm vài VPS phục vụ các tầng ứng dụng khác nhau (Web server, API backend, Database, Queue worker), việc SSH vào từng máy để kiểm tra file log bằng tail -f hoặc journalctl trở nên chậm chạp và dễ bỏ sót thông tin khi xảy ra sự cố liên chuỗi.
Tuy nhiên, các giải pháp thu thập log tập trung truyền thống như ELK Stack (Elasticsearch, Logstash, Kibana) đòi hỏi cấu hình phần cứng rất lớn, tiêu tốn từ 4GB đến 8GB RAM chỉ để duy trì bộ máy chỉ mục. Với các cụm máy chủ quy mô nhỏ, sự kết hợp giữa Vector (bộ gom log siêu nhẹ viết bằng Rust) và Grafana Loki (hệ thống lưu trữ log tối ưu theo nhãn) mang lại khả năng quản lý log tập trung mượt mà trên các gói VPS chỉ từ 1GB đến 2GB RAM. Đây cũng là một minh chứng tiêu biểu cho bài toán cân đối giữa chi phí bản quyền SaaS và chi phí hạ tầng trong tiêu chí tự host công cụ nội bộ trên VPS.
1. Tại sao ELK quá nặng và Grafana Loki là lựa chọn phù hợp cho VPS nhỏ?
Để lựa chọn đúng công cụ, người vận hành cần hiểu rõ sự khác biệt căn bản về mặt kỹ thuật giữa cơ chế lập chỉ mục của Elasticsearch và Grafana Loki.
Sự cồng kềnh của Elasticsearch (ELK Stack)
Elasticsearch hoạt động dựa trên cấu trúc Inverted Index (chỉ mục đảo). Khi một dòng log được gửi vào:
- Bộ phân tích (Analyzer) sẽ bóc tách toàn bộ câu chữ thành từng từ khóa riêng lẻ (Tokens).
- Hệ thống lập chỉ mục cho từng từ khóa đó để phục vụ tìm kiếm văn bản toàn phần (Full-text Search).
Cơ chế này mang lại khả năng tìm kiếm từ ngữ cực nhanh, nhưng cái giá phải trả về tài nguyên là rất lớn:
- Ngốn bộ nhớ RAM: Elasticsearch chạy trên nền Java Virtual Machine (JVM). Để xử lý luồng log liên tục mà không bị dừng đột ngột do bộ thu gom rác (GC pause), JVM yêu cầu heap memory tối thiểu từ 2GB đến 4GB, đẩy tổng RAM thực tế của máy chủ lên 6GB – 8GB.
- Phình to dung lượng ổ đĩa: Dung lượng của tệp chỉ mục Inverted Index thường tương đương 50% đến 120% kích thước của chính file log gốc. Nếu bạn có 10GB log, hệ thống cần thêm 5GB đến 12GB dung lượng đĩa chỉ để chứa chỉ mục.
Triết lý thiết kế tinh gọn của Grafana Loki
Grafana Loki được phát triển với khẩu hiệu "Like Prometheus, but for logs". Loki giải quyết bài toán log theo hướng hoàn toàn khác:
- Không lập chỉ mục nội dung log: Thay vì phân tích từng từ trong dòng log, Loki chỉ lập chỉ mục cho các cặp nhãn siêu dữ liệu (Metadata Labels). Ví dụ:
host="vps-web-01",app="nginx",env="production". - Nén dữ liệu dạng khối (Chunks): Toàn bộ nội dung văn bản thuần của các dòng log cùng nhãn được gom lại thành các khối tệp tin nén bằng thuật toán Snappy hoặc Gzip, sau đó lưu trực tiếp vào ổ đĩa cứng hoặc Object Storage (Cloudflare R2 / AWS S3).
- Quét dữ liệu khi truy vấn (Grep on read): Khi bạn tìm kiếm một từ khóa lỗi, Loki sử dụng nhãn để khoanh vùng đúng khối dữ liệu của máy chủ đó, rồi thực thi thao tác quét luồng tương tự như lệnh
greptrên Linux ở tốc độ cao.
Nhờ triết lý này, kích thước chỉ mục của Loki chỉ chiếm chưa đến 1% dung lượng log, giúp máy chủ Loki hoạt động ổn định và mượt mà trên một VPS chỉ có 1GB đến 2GB RAM.
Bảng so sánh tài nguyên trên cụm 3 – 5 VPS
| Tiêu chí kỹ thuật | ELK Stack (Elasticsearch + Filebeat) | Grafana Loki + Vector |
|---|---|---|
| RAM máy chủ trung tâm | 4GB – 8GB+ (Yêu cầu JVM Heap) | 500MB – 1.5GB RAM |
| RAM trên từng VPS vệ tinh | 80MB – 150MB (Filebeat / Logstash) | 15MB – 35MB (Vector viết bằng Rust) |
| Kích thước chỉ mục lưu trữ | 50% – 100% dung lượng log | Dưới 1% dung lượng log |
| Chi phí hạ tầng VPS | Tốn kém (cần VPS lớn 8GB RAM) | Tiết kiệm (chạy chung trên VPS 2GB RAM) |
| Khả năng trực quan hóa | Kibana | Grafana (tích hợp chung với Metrics) |
2. Kiến trúc luồng log: VPS Vệ tinh (Vector) → VPS Trung tâm (Loki)
Mô hình triển khai cho cụm từ 3 đến 5 máy chủ VPS được phân định thành hai vai trò rõ ràng:
- Máy chủ Log trung tâm (Central Logging VPS):
- Chạy dịch vụ Grafana Loki để tiếp nhận, nén và lưu trữ các khối log.
- Chạy dịch vụ Grafana để quản trị viên truy vấn, theo dõi bảng điều khiển và thiết lập cảnh báo.
- Cổng API tiếp nhận log của Loki là
3100. Để đảm bảo an toàn tuyệt đối, cổng này chỉ được phép lắng nghe trên mạng riêng ảo nội bộ, bạn có thể tham khảo thiết lập Cloudflare Tunnel hay WireGuard cho truy cập nội bộ để không mở cổng ra Internet công cộng.
- Các máy chủ vệ tinh (Satellite / Edge VPS):
- Mỗi máy chủ chỉ cần cài đặt một tiến trình nền duy nhất là Vector Agent.
- Vector đọc trực tiếp các luồng log:
- Log từ các Docker Container thông qua Docker Socket
/var/run/docker.sock. - Log từ các dịch vụ hệ thống thông qua
systemd-journald. - Các file log văn bản thuần nếu có (như
/var/log/nginx/access.log). - Vector tự động chuẩn hóa dữ liệu, gắn thêm nhãn nhận diện máy chủ (ví dụ
host="vps-api-02") và gửi qua giao thức HTTP POST về cổng3100của Loki.
3. Cài đặt cụm trung tâm: Grafana Loki và Grafana
Trên máy chủ VPS được chọn làm trung tâm quản lý log, bạn tạo thư mục triển khai và file cấu hình Docker Compose.
Tạo thư mục làm việc:
mkdir -p /opt/loki-stack/config /opt/loki-stack/data/loki
cd /opt/loki-stackTạo file cấu hình Loki: config/loki-config.yaml
Loki cần một file cấu hình định nghĩa cơ chế lưu trữ cục bộ (Filesystem) và chính sách dọn dẹp log:
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
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 # 7 ngày
retention_period: 14d # Giữ log tối đa 14 ngày
compactor:
working_directory: /loki/compactor
retention_enabled: true
compactor_ring:
kvstore:
store: inmemory
delete_request_store: filesystemĐiểm quan trọng trong cấu hình trên:
schema_configsử dụngtsdb: Định dạng TSDB mới của Loki giúp tăng tốc độ tra cứu nhãn và tiêu thụ cực ít bộ nhớ so với cấu hình BoltDB cũ.compactor.retention_enabled: true: Kích hoạt tiến trình dọn dẹp log tự động. Các khối log cũ hơn 14 ngày (14d) sẽ tự động được xóa khỏi ổ đĩa để giải phóng dung lượng.
Tạo file docker-compose.yml
services:
loki:
image: grafana/loki:3.0.0
container_name: loki
restart: unless-stopped
ports:
- "127.0.0.1:3100:3100" # Chỉ lắng nghe trên localhost hoặc mạng riêng nội bộ
volumes:
- ./config/loki-config.yaml:/etc/loki/local-config.yaml:ro
- ./data/loki:/loki
command: -config.file=/etc/loki/local-config.yaml
deploy:
resources:
limits:
memory: 1024M
grafana:
image: grafana/grafana:11.0.0
container_name: grafana
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=MatKhauQuanTriRatManh123
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- ./data/grafana:/var/lib/grafana
deploy:
resources:
limits:
memory: 512MKhởi động cụm dịch vụ:
docker compose up -dSau khi khởi động, truy cập Grafana qua cổng 3000 (đặt sau Nginx Reverse Proxy có SSL). Vào mục Connections -> Data sources -> Add data source, chọn Loki và điền URL: http://loki:3100. Nhấn Save & test để xác nhận kết nối thành công.
4. Cài đặt Vector agent trên các máy chủ VPS vệ tinh
Trên từng VPS vệ tinh cần đẩy log về trung tâm, Vector là lựa chọn tối ưu vì được viết bằng ngôn ngữ Rust, không có tiến trình Garbage Collection làm lag CPU và chiếm chưa tới 35MB RAM.
Cài đặt Vector trực tiếp trên Ubuntu/Debian
Chạy lệnh cài đặt chính thức từ kho gói của Vector:
curl -1sLf 'https://repositories.timber.io/public/vector/cfg/setup/bash.deb.sh' | sudo -E bash
sudo apt-get install -y vectorCấu hình pipeline thu thập: /etc/vector/vector.yaml
Vector sử dụng mô hình Pipeline gồm 3 khối: Sources (nguồn log) → Transforms (tiền xử lý dữ liệu) → Sinks (đích đẩy đến).
Chỉnh sửa file cấu hình /etc/vector/vector.yaml:
# 1. NGUỒN THU THẬP DỮ LIỆU
sources:
# Thu thập log của toàn bộ Docker container trên VPS này
docker_source:
type: docker_logs
# Thu thập log hệ thống Linux từ systemd
journal_source:
type: journald
exclude_units:
- "vector.service"
# 2. XỬ LÝ VÀ CHUẨN HÓA DỮ LIỆU (TRANSFORMS)
transforms:
process_docker:
type: remap
inputs:
- docker_source
source: |
.host = "vps-web-01" # Đặt tên định danh cho VPS này
.job = "docker"
.container = .container_name
process_journal:
type: remap
inputs:
- journal_source
source: |
.host = "vps-web-01"
.job = "systemd"
.service = .SYSTEMD_UNIT || "kernel"
# 3. ĐÍCH GỬI LOG VỀ LOKI TRUNG TÂM (SINKS)
sinks:
loki_sink:
type: loki
inputs:
- process_docker
- process_journal
endpoint: "http://10.8.0.1:3100" # IP nội bộ của VPS trung tâm chạy Loki
compression: gzip # Nén dữ liệu trước khi truyền qua mạng
encoding:
codec: text
labels:
host: "{{ host }}"
job: "{{ job }}"
container: "{{ container }}"
service: "{{ service }}"Khởi động và kích hoạt dịch vụ Vector:
sudo systemctl restart vector
sudo systemctl enable vectorKiểm tra trạng thái hoạt động:
sudo systemctl status vector
# Xem log của chính Vector để xác nhận kết nối tới Loki thành công:
journalctl -u vector -fNếu cấu hình đúng, bạn sẽ thấy thông báo Vector đã kết nối thành công tới endpoint của Loki và bắt đầu gửi các đợt dữ liệu (batches) được nén bằng gzip.
5. Thực hành tra cứu sự cố với ngôn ngữ LogQL trên Grafana
Sau khi các máy chủ vệ tinh đã đẩy log về Loki, bạn mở giao diện Grafana, truy cập mục Explore và chọn Data Source là Loki. Tại đây, bạn sử dụng ngôn ngữ LogQL để truy vấn.
Lọc dữ liệu theo nhãn luồng (Stream Selector)
Cú pháp cơ bản bắt đầu bằng việc chỉ định nhãn trong dấu ngoặc nhọn {}:
# Xem toàn bộ log của máy chủ web 01
{host="vps-web-01"}
# Xem log của một container cụ thể trên máy chủ API
{host="vps-api-02", container="payment-service"}Nhờ cơ chế lập chỉ mục nhãn, câu lệnh trên sẽ trả về kết quả gần như tức thì vì Loki chỉ trỏ đúng vào khối dữ liệu của container đó, bỏ qua hàng triệu dòng log của các dịch vụ khác.
Lọc dòng theo chuỗi ký tự (Line Filter)
Bạn có thể kết hợp tìm kiếm chuỗi nội dung văn bản tương tự như lệnh grep:
|=: Dòng có chứa chuỗi ký tự!=: Dòng không chứa chuỗi ký tự|~: Khớp biểu thức chính quy (Regex)!~: Không khớp biểu thức chính quy
Ví dụ thực tế:
# Tìm tất cả các dòng log có chứa mã lỗi 500 trên VPS web
{host="vps-web-01", job="docker"} |= " 500 "
# Tìm lỗi kết nối cơ sở dữ liệu nhưng loại trừ các thông báo healthcheck định kỳ
{job="docker"} |= "database" != "healthcheck"Phân tích log định dạng JSON (JSON Parser)
Nếu ứng dụng của bạn ghi log ở định dạng cấu trúc JSON (như chuẩn log của Node.js Winston, Go Zap hay Python Loguru):
{"level": "error","ts": 1712912400,"msg": "Payment failed","user_id": 4521,"status": 502}Bạn có thể dùng parser | json của LogQL để trích xuất trực tiếp các trường dữ liệu và lọc theo giá trị số:
{container="payment-service"} | json | status >= 500 | level = "error"Loki sẽ tự động bóc tách trường status và level trong từng dòng JSON để đối chiếu điều kiện, giúp việc khoanh vùng lỗi giao dịch diễn ra chỉ trong vài giây.
Đếm tần suất lỗi phục vụ biểu đồ và cảnh báo (Metric Queries)
LogQL cho phép biến đổi các dòng log văn bản thành các chỉ số số học (metrics) theo thời gian bằng hàm rate() hoặc count_over_time():
# Tính số lượng lỗi 500 phát sinh mỗi giây trong khoảng thời gian 5 phút gần nhất
sum by (host) (rate({job="docker"} |= " 500 " [5m]))Câu truy vấn này có thể được gắn thẳng vào một Panel biểu đồ trên Grafana Dashboard hoặc dùng làm điều kiện kích hoạt thông báo tự động (Grafana Alerting) gửi tin nhắn cảnh báo về Telegram/Slack khi tần suất lỗi vượt ngưỡng 5 lỗi/phút.
Với kiến trúc kết hợp giữa Grafana Loki và Vector, bạn sở hữu một hệ thống thu thập và phân tích log tập trung chuyên nghiệp, linh hoạt và toàn diện cho cụm VPS nội bộ mà không cần phải chi trả hàng trăm USD tiền bản quyền SaaS hay đầu tư cụm máy chủ RAM lớn cho Elasticsearch. Kết hợp hệ thống này với giải pháp vận hành Uptime Kuma trên VPS để giám sát tính sẵn sàng của endpoint, bạn sẽ có một bức tranh hoàn chỉnh về tình trạng sức khỏe và nhật ký hoạt động của toàn bộ hạ tầng.



