Series Docker & ELK Stack cho Laravel #3: Kiến trúc Zero-blocking với Filebeat
Đối với hệ thống Metro với hàng triệu lượt quẹt thẻ, độ trễ (latency) là kẻ thù số một. Ở Bài 2, chúng ta đã nhận ra rằng việc để PHP mở kết nối HTTP trực tiếp đến Elasticsearch mỗi khi ghi log là một "tử huyệt" hiệu năng, có thể làm kẹt cứng (blocking) toàn bộ luồng xử lý API.
Hôm nay, chúng ta sẽ thiết kế một kiến trúc chuẩn DevOps: Zero-blocking Logging. Chúng ta sẽ tước bỏ quyền gọi API của Laravel, bắt nó ghi log ra file với tốc độ của ổ cứng, và nhường lại nhiệm vụ "chở hàng" nặng nhọc cho một con "bọ" siêu tốc mang tên Filebeat.
1. Filebeat là gì và tại sao lại cần nó?
Filebeat là một phần mềm mã nguồn mở của Elastic, được viết bằng ngôn ngữ Go (Golang) nên cực kỳ nhẹ, ăn rất ít RAM và CPU.
Nhiệm vụ duy nhất của nó là nằm vùng trên Server, dùng "đôi mắt" quan sát liên tục (tail) một thư mục chứa file log. Bất cứ khi nào Laravel sinh ra một dòng log mới, Filebeat sẽ lập tức chộp lấy, gom thành một lô (batch) và bắn bất đồng bộ (asynchronous) sang Elasticsearch.
Nhờ vậy, Laravel chỉ việc làm thao tác rẻ tiền nhất: Ghi text xuống ổ cứng (I/O) và lập tức trả Response cho khách hàng, không cần quan tâm Elasticsearch có đang sống hay chết.
2. Ép Laravel nói tiếng "JSON thuần"
Trước tiên, chúng ta cần cấu hình Laravel ghi log ra một file riêng biệt bằng định dạng JSON. Mở config/logging.php và thêm một channel mới tên là filebeat:
use Monolog\Formatter\JsonFormatter;
return [
// ...
'channels' => [
'filebeat' => [
'driver' => 'daily',
'path' => storage_path('logs/kibana.log'), // Ghi ra file kibana.log
'level' => env('LOG_LEVEL', 'debug'),
'days' => 7,
// Ép Monolog định dạng toàn bộ mảng Context thành 1 dòng JSON duy nhất
'formatter' => JsonFormatter::class,
],
],
];
Bây giờ, khi bạn gọi lệnh \Log::channel('filebeat')->info("Tap In", ['station' => 'S01', 'uid' => 'E1F4A']);, trong file storage/logs/kibana.log sẽ xuất hiện một chuỗi JSON hợp lệ.
3. Cấu hình "Người vận chuyển" Filebeat
Tạo một file có tên là filebeat.yml nằm ở thư mục gốc dự án (cùng cấp với docker-compose.yml) để định nghĩa luật chơi cho con bọ này:
# 1. ĐẦU VÀO (Inputs): Quan sát file nào?
filebeat.inputs:
- type: filestream
id: afc-metro-logs
enabled: true
paths:
- /var/log/laravel/*.log # Đường dẫn này sẽ được map trong Docker
# Báo cho Filebeat biết đây là file JSON để nó bóc tách từng key (station, uid...)
parsers:
- ndjson:
target: ""
add_error_key: true
# 2. ĐẦU RA (Outputs): Bắn đi đâu?
output.elasticsearch:
hosts: ["http://elasticsearch:9200"]
# Đặt tên Index cho dữ liệu trong Elasticsearch (Tự động chia theo ngày)
index: "afc-logs-%{+yyyy.MM.dd}"
# 3. Cấu hình Template tĩnh
setup.template.name: "afc-logs"
setup.template.pattern: "afc-logs-*"
setup.ilm.enabled: false
4. Lắp ráp Filebeat vào hạ tầng Docker Compose
Mở file docker-compose.yml mà chúng ta đã làm ở Bài 1, và khai báo thêm service filebeat vào bên dưới kibana:
filebeat:
image: docker.elastic.co/beats/filebeat:8.10.2
container_name: afc_filebeat
user: root # Bắt buộc chạy bằng quyền root để đọc được file map từ bên ngoài
volumes:
# Map file cấu hình Filebeat vào trong Container
- ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
# Dẫn đường cho Filebeat đi vào kho chứa Log của Laravel
- ./storage/logs:/var/log/laravel:ro
networks:
- elk_network
depends_on:
- elasticsearch
restart: unless-stopped
Giải thích cơ chế Volumes: Dòng ./storage/logs:/var/log/laravel:ro là cầu nối sinh tử. Dù Laravel và Filebeat chạy trên hai môi trường (hoặc container) khác nhau, chúng vẫn nhìn thấy chung một file. Chữ :ro (Read-only) ở cuối đảm bảo Filebeat chỉ có quyền "nhìn và đọc", tuyệt đối không thể vô tình xóa mất file log của Laravel.
5. Kích hoạt và Tận hưởng sức mạnh
Bây giờ, chỉ việc gõ lệnh khởi động lại cụm Docker:
docker-compose up -d
Từ giây phút này, kiến trúc của bạn đã thay đổi hoàn toàn:
- Hành khách quẹt thẻ tại ga S01.
- Code Laravel chạy hàm xử lý vé trong 10ms, ghi JSON xuống storage/logs/kibana-2026-07-11.log mất 1ms, tổng 11ms trả kết quả mở cửa tự động (Turnstile) thành công.
- Trong bóng tối, Filebeat ngay lập tức đánh hơi thấy dòng dữ liệu mới. Nó ngầm đóng gói dòng JSON đó và phi thẳng sang cổng 9200 của Elasticsearch.
- Elasticsearch lưu trữ dòng log, Indexing lại để sẵn sàng cho Kibana lấy dữ liệu vẽ biểu đồ.
Bạn đã sở hữu một đường ống dữ liệu (Data Pipeline) chuẩn mực của các tập đoàn công nghệ lớn. Tốc độ, an toàn, không bị tắc nghẽn, và cực kỳ dễ mở rộng.
All rights reserved