Tối ưu hóa hệ thống ghi nhật ký Loki

Hiện tại công ty mình đang triển khai và vận hành một số ứng dụng trên Kubernetes và KubeFlow. Trong quá trình vận hành hệ thống, bên mình có gặp một lỗi về logging system khá là hay ho, muốn chia sẻ lại với mọi người, để nếu có ai gặp phải lỗi này thì cũng đỡ mất công tìm hiểu nhé.

Môi trường, bối cảnh

Logging và monitoring stack bên mình sử dụng là Promtail + Loki + prometheus. Trong đó:

  • Promtail version 2.0.0
  • Loki version 2.2.0
    • storage lưu chunks sử dụng filesystem
    • index bằng boltDB-shipper

Vấn đề gặp phải

Vào 1 ngày nọ, bên mình nhận được vấn đề Loki dừng thu thập thêm log của các pod.

Kiểm tra phần log của Loki service thì liên tục báo lỗi

level=error ts=202X-XX-XXT14:31:00.379Z caller=flush.go:199 org_id=fake msg="failed to flush user" err="open /A/B/chunks/ZmFrZS82YWQ3ODRlMWU4N2VlMjMzOjEYzViZTgw: file too large"

Điều tra nguyên nhân

Bước 1:

Với message lỗi như trên, phản xạ đầu tiên là check mountpoint size chứa log của Loki bằng lệnh “df -h” và “df -i” quen thuộc

→ Cả dung lượng và số lượng inodes trên mountpoint đều ở mức chỉ vài %, không phải nguyên nhân

Bước 2:

Khoanh vùng lỗi bằng cách thử tạo 1 file trong thư mục log của Loki bằng lệnh “touch demo.txt”

→ Gặp lỗi tương tự, nhưng lần này lỗi được báo trên dòng lệnh terminal

→ Vậy vấn đề nằm ở filesystem, không phải do Loki

Bước 3:

Mountpoint này được cấu hình từ 1 volume từ NAS NetApp. Bọn mình có search đc message lỗi tương tự của NetApp NAS tại đây và tìm hiểu thêm tại đây về tham số max_dir_size.

max_dir_size là tham số của NetApp để giới hạn size của 1 directory trong 1 volume, default là 320 MB.

Bọn mình check lại directory size của Loki log dir bằng lệnh cơ bản “ls -l”

$ ls -l
-rwxr-xr-x  1 user group      98404 Nov  8  2021 application.png     
drwxr-xr-x  3 user group       4096 Apr 13  2022 backup
-rw-rw-r--  1 user group    2221823 Aug 30  2021 ems.log.0000000009.txt  
-rwxr-xr-x  1 user group    1174298 Feb 12  2020 putty.log
drwxrwxr-x  4 user group      12288 Sep 17  2021 logs

Cột thứ năm hiển thị dung lượng được sử dụng cho mỗi tệp, tính theo byte.

hoặc dùng lệnh stat

$ stat /etc
  File: /etc
  Size: 12288           Blocks: 24         IO Block: 4096   directory
Device: 10301h/66305d   Inode: 29491201    Links: 141
Access: (0755/drwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2023-03-13 12:25:28.004794546 +0900
Modify: 2023-03-13 12:23:23.982249160 +0900
Change: 2023-03-13 12:23:23.982249160 +0900
 Birth: -

Size: 12288 là directory size

Sau khi check, bên mình confirm đúng directory size của Loki logging dir ~ 320 MB (chỗ này bọn mình ko screenshot lại)

Khi bọn mình move thử 1 vài file ra khỏi directory thì có thể tạo được file mới trong directory này

→ Như vậy, bọn mình đã xác định vấn đề trực tiếp

Phiên bản loki đang sử dụng là 2.2.0 sử dụng storage là filesystem và schema config là v11

schema_config:
  configs:
  - from: 20YY-MM-DD
    store: boltdb-shipper
    object_store: filesystem
    schema: v11
    index:
      prefix: index_
      period: 24h

các file chunk được đặt trong cùng 1 folder với tên là encode base64 của đoạn mã sau:

<user>/<fingerprint>:<start>:<end>:<checksum>
VD: ZmFrZS83YTI1N2M5ZWI2YTYyMDkwOjE2OWYzMTZiODE2OjE2OWYzMTZiODYyOjdkNzMyNGQy

Việc lưu file như này có thể khiến folder bị đạt giới hạn max_dir_size  khi số lượng file lớn

Trong trường hợp lỗi mà bọn mình gặp phải, số lượng file xấp xỉ 2,1 triệu

Tại sao số lượng chunk file lại nhiều như vậy ?

Analyze-labels

Promtail thu thập các log từ các pod, đồng thời thu thập metadata từ các pod này để tạo thành các label set, và dùng label set như 1 dạng multi-part index để query nội dung của các log.

Với mỗi 1 tập giá trị của label set, Loki sẽ tạo ra 1 log stream, và tương ứng mỗi log stream sẽ sinh ra *ít nhất* 1 chunk file trong logging dir.

Kể cả nếu pod chứa ít label key, nhưng value của label key có quá nhiều (high cardinality) thì Loki vẫn sẽ sinh ra rất nhiều log stream → log chunk files.

Vì vậy, đầu tiên bọn mình sử dụng tool logcli để tìm hiểu xem label nào sinh ra nhiều stream nhất.

logcli series {} --analyze-labels --since 400h
Total Streams:  74600
Unique Labels:  39
 
Label Name                           Unique Values  Found In Streams
filename                             54845          74600
pod                                  1305           74600
job_name                             752            2026
pod_template_hash                    135            72522
namespace                            131            74600
container                            108            74600
AAA_app                              83             62712
app                                  20             9855
service_istio_io_canonical_name      13             555
replica_index                        7              2026
controller_revision_hash             6              50
statefulset_kubernetes_io_pod_name   4              18
pod_template_generation              2              32
....

Ta để ý thấy label “filename” góp mặt trong đa phần các stream được sinh ra. Như vậy, nếu ta bỏ được label “filename” thì có thể sẽ giảm được số chunk file đáng kể

“filename” ở đây là tên (full path) của file log trên node đó. (vd: /var/log/pod/aabb/1.log)

Tuy nhiên, đến đây ta chưa thể xác định được về mặt số lượng thì liệu apply hot fix bỏ “filename” thì sẽ giảm đc bao nhiêu % số chunk file.

Vì vậy, bọn mình đã lèm thêm 1 bước tiếp theo

Chunks label inspection

Chúng ta cần phải xem xét kỹ hơn thông tin về label set của các file chunks, dung lượng, số dòng log và thời gian tạo của các file này

Loki đã có sẵn 1 tool dùng để decode các thông tin này: chunks-inspect nhưng tool mới chỉ chạy trên từng file và output đang hơi thừa.

Chỉnh sửa lại tool này một chút để quét toàn bộ file trong folder và xuất ra các thông tin dạng CSV: chunks-folder-inspect

Cách chạy

go build .
./chunks-inspect YOUR_FOLDER > output.csv

Kết quả sau khi chạy: trung bình trong 1 tháng số lượng file sinh ra là 900 ngàn, trong đó số lượng file chỉ có duy nhất 1 dòng log là 800 ngàn

với các file này sự khác biệt giữa các label set chỉ là khác nhau về “filename”

Bắt tay vào xử lý

Lý do khiến cho label “filename” xuất hiện trong labelset của các dòng log vì đoạn config trong promtail

scrape_configs:
- job_name: abcxyz
  pipeline_stages:
  - docker: {}
  kubernetes_sd_configs:
  - role: pod
  relabel_configs:
  - replacement: /var/log/pods/*$1/*.log
    separator: /
    source_labels:
      - __meta_kubernetes_pod_uid
      - __meta_kubernetes_pod_container_name
    target_label: __path__

Với config này:

+ Promtail sẽ discover các pods trong k8s và tạo ra các target tương ứng cho phần relabel_configs

+ Trong phần relabel, Promtail sẽ ghép value của các label uid và container_name lại và assign “/var/log/pods/*<uid_value>/<container_name_value>/*.log” vào biến “__path__”

+ Biến “__path__” là 1 biến đặc biệt trong promtail, phần pipeline_stages docker sẽ sử dụng biến này để tìm đến các log file với format của Docker để parse. Sau đó, nó sẽ relabel biến này thành “filename”

Bọn mình ko cần label ở mức “filename”, do vậy có thể loại bỏ label “filename” khỏi promtail để gom các stream của cùng 1 pod lại thành 1 stream duy nhất bằng cách chỉnh sửa config 

pipeline_stages:
- docker: {}
- labeldrop: [filename]

Sau khi apply config này số lượng các file 1 dòng vẫn còn khá nhiều. Kiểm tra mốc thời gian tạo file chunks của cùng 1 label set

Ta có thông số như hình dưới:

Hiện tại config về chunk_idle_period của loki đang là 15 phút do vậy các file sẽ có thời gian tạo cách nhau > 15 phút.

Nhưng phần lớn các pod sau khoảng 30 phút mới đẩy về log do vậy cần setup lại chunk_idle_period

Tính toán cho thấy nếu set thành 40 phút sẽ cover được 87% các pod.

config:
  auth_enabled: false
  ingester:
    chunk_idle_period: 40m

Kết quả

Sau khi chỉnh sửa các setup của promtail và loki số lượng file sinh ra mỗi ngày giảm từ ~20K xuống ~5k

Key takeaways:

  • Monitor số lượng chunk file của Loki (vd bằng prometheus) để phát hiện / cảnh báo trước khi lỗi
  • Trong trường hợp số lượng log file vượt quá dự kiến, phân tích theo 2 chiều:
    • Cardinality của label set: sử dụng logcli, tìm kiếm label có cardinality cao nhất, xem xét có cần thiết sử dụng label đó ko (trong câu lệnh query)
## clone from github và chạy
git clone https://github.com/grafana/loki.git
cd loki
make logcli
 
## copy file build vào thư mục /usr/local...
cp cmd/logcli/logcli /usr/local/bin/logcli

Nếu sử dụng loki instance thì phải khai báo địa chỉ loki ví dụ

export LOKI_ADDR=http://localhost:3100

Reference

https://kb.netapp.com/Advice_and_Troubleshooting/Data_Storage_Systems/FAS_Systems/%22File_too_large%22_error_messages_when_maxdirsize_limit_is_reached
https://kb.netapp.com/Advice_and_Troubleshooting/Data_Storage_Software/ONTAP_OS/What_is_maxdirsize
https://github.com/grafana/loki/tree/main/cmd/chunks-inspect
https://github.com/piggy2303/chunks-folder-inspect
https://grafana.com/docs/loki/latest/clients/promtail/scraping/
https://grafana.com/docs/loki/v2.7.x/tools/logcli/
https://grafana.com/docs/loki/latest/best-practices/

You may also like...

0 0 đánh giá
Đánh giá bài viết
Theo dõi
Thông báo của
guest
0 Góp ý
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất
Phản hồi nội tuyến
Xem tất cả bình luận
0
Rất thích suy nghĩ của bạn, hãy bình luận.x