+14

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:

Screenshot 2024-04-29 at 10.43.17 PM.png

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 ở file app.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:

ELK Stack.jpg

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ường message và lưu nó vào trường parseJson (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ừ trong message 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ường parsed_timestamp mà ta lấy được từ cái grok > 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:

Screenshot 2024-04-29 at 11.51.30 PM.png

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áo version: '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 SSL received plaintext http traffic on an https channel hoặc http client did not trust this server's certificate. Ta cũng set thêm cluster.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ỗi high 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 user kibana_system. Chú ý rằng ta phải dùng user kibana_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:

Screenshot 2024-04-30 at 12.00.17 AM.png

Check log Kibana sẽ thấy như sau:

docker compose logs kibana

Screenshot 2024-04-30 at 12.02.11 AM.png 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é 😎:

Screenshot 2024-04-30 at 10.13.55 PM.png

Ta login với account elastic/myelasticpassnha. Sau khi login thành công thì ta vào được giao diện chính:

Screenshot 2024-04-30 at 10.15.17 PM.png

Ở đó 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:

Screenshot 2024-04-30 at 10.16.17 PM.png

Sau đó ta bấm Create data view:

Screenshot 2024-04-30 at 10.19.10 PM.png

Tiếp theo ta nhập thông tin như trong hình:

Screenshot 2024-04-30 at 10.19.40 PM.png

  • Name: là tên dashboard của bạn, ta thích đặt như nào cũng được
  • Index pattern: là pattern Elastic index của chúng ta, lấy từ file logstash.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ía logstash nhận được event - thời gian đó có thể không khớp với NodeJS vì Filebeat thường batch 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:

Screenshot 2024-04-30 at 10.24.15 PM.png

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:

Screenshot 2024-04-30 at 10.25.28 PM.png

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:

Screenshot 2024-04-30 at 10.28.48 PM.png

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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí