Deploy ELK Stack với Docker
Cập nhật gần nhất: 10/11/2024
Hello các bạn lại là mình đây 👋👋 Chúc các bạn có kì nghỉ 30/4-1/5 vui vẻ và an toàn 🇻🇳🇻🇳🇻🇳🇻🇳🇻🇳
Tiếp tục series học Docker và CICD của mình, hôm nay ta sẽ cùng nhau làm một bài "tàu nhanh" setup ELK Stack bao gồm Elastic Search, Logstash, Kibana và Filebeat để monitor log của ứng dụng NodeJS. Và từ đó ta thấy được ứng dụng Docker vào thực tế sẽ làm giảm cực kì đáng kể thời gian setup infra
(infrastructure) nhiều như thế nào nhé
Lên tàu thôi nào anh em ơi 🛳️🛳️🛳️🛳️🛳️
Chuẩn bị
Như thường lệ thì đầu tiên các bạn clone source code của mình ở đây, nhánh master và folder docker-elk-node nhé
Đầu tiên ta chạy thử lên xem project có gì nha:
docker compose up -d
Sau đó ta mở trình duyệt ở địa chỉ http://localhost:3000
hoặc http://localhost:3000/users
sẽ thấy như sau:
Ta thử F5 vài cái nhé.
Giờ xem tổng quan project có gì nha:
- Ở bài này ta có app NodeJS, rất đơn giản
- Mỗi khi có request vào server (load trang, CSS/JS, API...) thì sẽ ghi vào file log trên server, nằm ở folder
logs
{"level":"\u001b[32minfo\u001b[39m","message":"::ffff:192.168.65.1 - - [29/Apr/2024:14:31:57 +0000] \"GET / HTTP/1.1\" 200 204 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\"\n","timestamp":"2024-04-29T14:31:57.843Z"}
{"level":"\u001b[32minfo\u001b[39m","message":"::ffff:192.168.65.1 - - [29/Apr/2024:14:31:57 +0000] \"GET /stylesheets/style.css HTTP/1.1\" 200 111 \"http://localhost:3000/\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\"\n","timestamp":"2024-04-29T14:31:57.853Z"}
{"level":"\u001b[32minfo\u001b[39m","message":"::ffff:192.168.65.1 - - [29/Apr/2024:14:31:58 +0000] \"GET / HTTP/1.1\" 304 - \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\"\n","timestamp":"2024-04-29T14:31:58.407Z"}
Ở trên mỗi dòng log của chúng ta là 1 JSON object được cấu hình mặc định bởi library
morgan
, bạn có thể xem thêm ở fileapp.js
nhé
Trong bài hôm nay ta sẽ setup để đẩy phần log kia sang ELK và query/monitor nhé. Triển thôi nào 🚀🚀
ELK Stack với Filebeat
Kiến thức vỡ lòng
Cho bạn nào chưa biết thì ELK là một set bao gồm 3 thành phần:
- Elastic Search: distributed, chuyên để lưu, search và phân tích data
- Logstash: là 1 cái data processing pipeline, chuyên nhận data từ nhiều nguồn, transform sau đó gửi nó đi - thường là tới Elasticsearch
- Kibana: là tool chuyên để visualize data trên web UI
- Filebeat thì là một thanh niên shipper, chuyên đi ship log, đẩy đi các nơi, trong bài này là đẩy sang Logstash
Bộ ELK rất phổ biến và được nhiều người dùng vì độ mạnh mẽ của nó, khả năng scale tốt và rất nhiều tính năng 💪💪💪
Trong bài này thì ta sẽ setup ELK với flow như sau:
User truy cập app NodeJS -> Ghi ra log file -> mount log file vào Filebeat -> đẩy log qua Logstash, xử lý log chút -> đẩy tiếp qua Elastic -> từ Kibana truy vấn vào Elastic lấy data và hiển thị lên UI
Phần chính
Đầu tiên các bạn tạo cho mình folder elk
ở root folder project để lưu cấu hình cho bài này và mount volume nữa nhé
Trong folder elk
đầu tiên các bạn tạo mình file filebeat.yml
với nội dung như sau:
filebeat.inputs:
- type: filestream
id: my-log-input
paths:
- "/var/log/server/*.log"
output.logstash:
hosts: ["logstash:5044"]
Ở trên ta có cấu hình cho Filebeat với nội dung như sau:
- Ta định nghĩa 1
input
với type làfilestream
, với type này thì filebeat sẽ đọc từng dòng (line by line từ các file log của ta) - filestream yêu cầu ta cần có
id
nên ta sẽ cho nó 1 cái id nào đó, cần phải unique nhé các bạn - Tiếp đó là ta có
paths
khai báo đường dẫn mà ta lưu file log để Filebeat có thể đọc - Cuối cùng là khai báo địa chỉ của Logstash để Filebeat gửi log sang
Tiếp theo, vẫn ở folder elk
ta tạo file logstash.conf
với nội dung như sau:
input {
beats {
port => 5044
}
}
filter {
json {
source => "message"
target => "parseJson"
}
grok {
match => {
"message" => "%{IPV6:ipv6}:%{IPV4:ipv4} - - \[%{DATA:parsed_timestamp}\] \\"%{WORD:verb} %{URIPATHPARAM:request} HTTP/%{NUMBER:httpversion}\\" %{NUMBER:response}"
}
}
date {
match => [ "parsed_timestamp" , "dd/MMM/yyyy:HH:mm:ss Z" ]
}
}
output {
elasticsearch {
hosts => "${ELASTIC_HOSTS}"
user => "${ELASTIC_USER}"
password => "${ELASTIC_PASSWORD}"
index => "my-log-%{+YYYY.MM.dd}"
}
}
- Ở trên, đầu tiên ta khai báo Beats plugin để nhận log từ Filebeat, Logstash sẽ listen ở port
5044
và Filebeat sẽ gửi log vào đây - Tiếp đó ta có
filter
để xử lý data chút trước khi gửi sang Elasticsearch - Đầu tiên ta có filter
json
, dùng để parse từng dòng log nhận được thành JSON object phía nodejs lưu là object rồi nhưng vì nó là log nên gửi sang Logstash nó chỉ như 1 text bình thường thôi nên cần phải parse. Lúc parse thì đọc lấy trườngmessage
và lưu nó vào trườngparseJson
(mình lấy tên khác để khi demo cho các bạn được rõ 😁 - Tiếp theo ta có
grok
>match
đây là một cái matching tool dùng khá ổn để ta có thể extract lấy thông tin từ trongmessage
theo 1 cái pattern mà ta mong muốn. Kiểu đưa dữ liệu từ "unstructure" thành "structure" - Sau đó ta có filter
date
, nó sẽ parse trườngparsed_timestamp
mà ta lấy được từ cáigrok > match
bên trên, parse từ string thành date, để truy vấn cho tiện - Cuối cùng là ta khai báo địa chỉ của elasticsearch để Logstash gửi log sau khi xử lý sang. Log khi gửi tới Elastic sẽ được index với tên theo dạng
my-log-{thời_gian}
, mỗi ngày sẽ có một index riêng. Ở đây ta cũng cần phải có thông tin user/pass của user elastic để xác thực với elastic search, thông tin này ta sẽ lấy ở biến môi trường tí nữa ta truyền vào
Sau đó vẫn ở trong elk
các bạn tạo cho mình folder data
để lát lưu data cho elastic nha
Oke rồi đó, hiện tại thì folder elk
của ta có 2 file như sau:
Giờ ta tạo file docker-compose.yml
ở root folder project nha:
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- 3000:3000
volumes:
- ./logs:/app/logs
elastic:
image: docker.elastic.co/elasticsearch/elasticsearch:8.12.2
environment:
- ELASTIC_PASSWORD=myelasticpass
- KIBANA_PASSWORD=kibanapass
- discovery.type=single-node
- xpack.security.http.ssl.enabled=false
- cluster.routing.allocation.disk.threshold_enabled=false
volumes:
- ./elk/data:/usr/share/elasticsearch/data
logstash:
image: docker.elastic.co/logstash/logstash:8.12.2
volumes:
- ./elk/logstash.conf:/usr/share/logstash/pipeline/logstash.conf
environment:
- ELASTIC_USER=elastic
- ELASTIC_PASSWORD=myelasticpass
- ELASTIC_HOSTS=http://elastic:9200
depends_on:
- elastic
kibana:
image: docker.elastic.co/kibana/kibana:8.12.2
environment:
SERVER_NAME: kibana
ELASTICSEARCH_HOSTS: '["http://elastic:9200"]'
ELASTICSEARCH_USERNAME: kibana_system
ELASTICSEARCH_PASSWORD: kibanapass
ports:
- "5601:5601"
depends_on:
- elastic
filebeat:
image: docker.elastic.co/beats/filebeat:8.12.2
volumes:
- ./elk/filebeat.yml:/usr/share/filebeat/filebeat.yml
- ./logs:/var/log/server
Một trong những điều đầu tiên ta chú ý là giờ khi viết
docker-compose.yml
thì ở đầu file ta không cần khai báoversion: '3.x'
nữa rồi 😎
Ở trên ta có những thứ sau:
- service
elastic
có limit RAM là 1G, có các biến môi trường để set password cho elastic, cho kibana (tí nữa ta phải chui vào để đổi pass cho kibana), và ở bài này ta chỉ có 1 node elastic nên ta phải códiscovery.type=single-node
. Ta cũng cần phải cóxpack.security.http.ssl.enabled=false
nếu không thì khi kibana gọi tới elastic nó sẽ báo lỗi SSLreceived plaintext http traffic on an https channel
hoặchttp client did not trust this server's certificate
. Ta cũng set thêmcluster.routing.allocation.disk.threshold_enabled=false
bởi vì nếu chẳng may máy các bạn mà disk sắp đầy (> 90%) thì cũng sẽ gặp lỗihigh disk watermark [90%] exceeded on
- service
logstash
, không có gì đặc biệt lắm mình đã giải thích hết ở trên phần cấu hình Logstash rồi, các bạn tự thẩm nha kibana
: ta cần một số biến môi trường nhưELASTICSEARCH_HOSTS
để cho kibana biết nơi nó cần gọi tới để query lấy data, kibana sẽ cần xác thực với elastic và ta sẽ dùng tới userkibana_system
. Chú ý rằng ta phải dùng userkibana_system
này nhé, đó là user mà kibana dùng để giao tiếp với elastic- cuối cùng là Filebeat: ta mount cấu hình filebeat và folder logs được ghi bởi NodeJS vào
Chú ý rằng user
kibana_system
chưa có pass và tí nữa sau khi start elastic ta phải chui vào đó để set pass cho nó nha
Lằng nhằng quá, start lên thôi nào 😂😂
docker compose up -d
Chú ý: với các bạn đang chạy Docker trên Linux thì khi start Filebeat có thể gặp ra lỗi Exiting: error loading config file: config file ("filebeat.yml") can only be writable by the owner but the permissions are "-rw-rw-r--" (to fix the permissions use: 'chmod go-w /usr/share/filebeat/filebeat.yml')
. Thì các bạn sửa lại docker-compose.yml
service filebeat
cho mình chút nhé:
filebeat:
image: docker.elastic.co/beats/filebeat:8.12.2
command: filebeat -e -strict.perms=false
volumes:
- ./elk/filebeat.yml:/usr/share/filebeat/filebeat.yml
- ./logs:/var/log/server
Oke tiếp thôi nào, nếu giờ ta truy cập luôn vào Kibana ở http://localhost:5601
sẽ thấy như sau:
Check log Kibana sẽ thấy như sau:
docker compose logs kibana
Kibana không xác thực được với Elastic 😭😭😭
Lí do thì như bên trên mình đã nói, ta cần set password cho user kibana_system
😊
Ta chui vào container elastic
và set pass cho kibana_system
nha:
docker compose exec elastic sh
curl -X POST -u "elastic:${ELASTIC_PASSWORD}" -H "Content-Type: application/json" http://localhost:9200/_security/user/kibana_system/_password -d "{ \"password\": \"${KIBANA_PASSWORD}\" }"
Sau khi đổi pass xong thì ta F5 lại phía Kibana thấy như sau là oke rồi nhé 😎:
Ta login với account elastic/myelasticpass
nha. Sau khi login thành công thì ta vào được giao diện chính:
Ở đó ta có ti tỉ thứ, nhưng bài này ta làm đơn giản thôi nha 😁
Ở phía tay trái các bạn bấm menu và chọn Discover:
Sau đó ta bấm Create data view
:
Tiếp theo ta nhập thông tin như trong hình:
Name
: là tên dashboard của bạn, ta thích đặt như nào cũng đượcIndex pattern
: là pattern Elastic index của chúng ta, lấy từ filelogstash.conf
mà ta đã làm từ trước. Ở đây ta để giá trị làmy-log-*
Timestamp field
thì ta lấy từparseJson.timestamp
để thời gian khớp với bên NodeJS, vì@timestamp
thì nó lại là thời gian phíalogstash
nhận được event - thời gian đó có thể không khớp với NodeJS vì Filebeat thườngbatch
event và gửi 1 loạt sau mỗi khoảng thời gian
Cuối cùng là ta bấm Save data view to Kibana
. Ngay sau đó ta tới trang query của dashboard ta vừa tạo:
Hiện tại thì chưa có tí data nào, các bạn quay trở lại trình duyệt ở địa chỉ app của chúng ta http://localhost:3000
và F5 nhiều nhiều vào tí để lấy log nhé. Sau đó ta chờ 1 chút để Filebeat gửi log đi và ta quay trở lại Kibana F5 sẽ thấy như sau:
Yeah data lên rồi 🎉🎉🎉🎉🎉
Từ đây ta có thể query tuỳ ý theo các field mà ta đã xử lý trước từ phía logstash nhé, ví dụ như ở đây mình query theo IPV4:
Vọc vạch
Bài này các bạn tự vọc vạch nha, vì nó có nhiều thứ quá hay ho quá không biết vọc từ đâu 🤣🤣🤣 (và giữ cho bài này ngắn không lê thê như các bài khác 😂😂)
Nếu có vấn đề gì các bạn cứ comment cho mình biết nha
bài này có chữ
Deploy
nhưng không phải deploy lên server HTTPS các thứ đâu nhé 😁
Nhìn lại
Ta để ý rằng với Docker thì việc triển khai cả 1 stack ELK - Filebeat - Application (nodejs) đã đơn giản đi rất rất rất nhiều. Nếu ta cài trực tiếp vào máy gốc theo cách truyền thống thì chắc máy của chúng ta nát trước khi thành công 🥲
Kết bài
Phewwww, một bài cực ngắn tráng miệng sau những bài lê thê từ đầu series. Qua bài này ta đã biết cách setup ELK với Filebeat để lấy log từ ứng dụng của chúng ta để từ đó ta có thể query/monitor một cách rất trực quan qua Kibana
Thực tế là mình cũng đang dùng cách này để monitor cho app chạy trên production của mình và thấy khá là hiệu quả, setup không nhiều và đủ phục vụ mục đích của mình
Chúc các bạn ngày mới vui vẻ và hẹn gặp lại các bạn vào những bài sau 😇😇😇
All rights reserved