Различия
Показаны различия между двумя версиями страницы.
| Предыдущая версия справа и слева Предыдущая версия Следующая версия | Предыдущая версия | ||
| software:monitoring:fluent-bit-loki-grafana [2025/11/24 13:25] – удалено - внешнее изменение (Дата неизвестна) 127.0.0.1 | software:monitoring:fluent-bit-loki-grafana [2025/11/25 22:48] (текущий) – ↷ Операцией перемещения обновлены ссылки mirocow | ||
|---|---|---|---|
| Строка 1: | Строка 1: | ||
| + | {{tag> | ||
| + | ====== Docker Swarm + Fluent Bit + Loki + Grafana ====== | ||
| + | |||
| + | ===== Версии: | ||
| + | |||
| + | * [[: | ||
| + | * grafana/ | ||
| + | * [[software: | ||
| + | |||
| + | ===== Настройки ===== | ||
| + | |||
| + | ==== [[ loki_config ]] ==== | ||
| + | |||
| + | <code yaml> | ||
| + | auth_enabled: | ||
| + | |||
| + | server: | ||
| + | http_listen_port: | ||
| + | |||
| + | common: | ||
| + | instance_addr: | ||
| + | path_prefix: | ||
| + | storage: | ||
| + | filesystem: | ||
| + | chunks_directory: | ||
| + | rules_directory: | ||
| + | replication_factor: | ||
| + | ring: | ||
| + | kvstore: | ||
| + | store: inmemory | ||
| + | |||
| + | schema_config: | ||
| + | configs: | ||
| + | - from: 2020-10-24 | ||
| + | store: tsdb | ||
| + | object_store: | ||
| + | schema: v13 | ||
| + | index: | ||
| + | prefix: index_ | ||
| + | period: 24h | ||
| + | |||
| + | ruler: | ||
| + | alertmanager_url: | ||
| + | |||
| + | limits_config: | ||
| + | retention_period: | ||
| + | reject_old_samples: | ||
| + | reject_old_samples_max_age: | ||
| + | allow_structured_metadata: | ||
| + | max_query_length: | ||
| + | | ||
| + | ingester: | ||
| + | lifecycler: | ||
| + | ring: | ||
| + | kvstore: | ||
| + | store: inmemory | ||
| + | replication_factor: | ||
| + | final_sleep: | ||
| + | chunk_idle_period: | ||
| + | max_chunk_age: | ||
| + | chunk_target_size: | ||
| + | chunk_retain_period: | ||
| + | |||
| + | table_manager: | ||
| + | retention_deletes_enabled: | ||
| + | retention_period: | ||
| + | </ | ||
| + | |||
| + | ==== [[ fluent_bit_config ]] ==== | ||
| + | |||
| + | <code ini> | ||
| + | [SERVICE] | ||
| + | flush 1 | ||
| + | log_level | ||
| + | daemon | ||
| + | storage.path | ||
| + | storage.sync | ||
| + | storage.checksum | ||
| + | storage.max_chunks_up | ||
| + | storage.backlog.mem_limit 10M | ||
| + | parsers_file | ||
| + | http_server | ||
| + | http_listen | ||
| + | http_port | ||
| + | |||
| + | [INPUT] | ||
| + | Name tail | ||
| + | Path / | ||
| + | Parser | ||
| + | Refresh_Interval | ||
| + | # | ||
| + | Docker_Mode | ||
| + | Tag | ||
| + | Tag_Regex | ||
| + | Mem_Buf_Limit | ||
| + | Skip_Long_Lines | ||
| + | DB / | ||
| + | DB.sync | ||
| + | Storage.Type | ||
| + | Read_from_Head | ||
| + | |||
| + | [INPUT] | ||
| + | Name systemd | ||
| + | Tag | ||
| + | Systemd_Filter | ||
| + | DB / | ||
| + | Read_From_Tail | ||
| + | |||
| + | # Получаем метаданные контейнера через Lua | ||
| + | [FILTER] | ||
| + | name lua | ||
| + | match | ||
| + | script | ||
| + | call enrich_with_docker_metadata | ||
| + | |||
| + | # Для отладки скрипта | ||
| + | # [OUTPUT] | ||
| + | # | ||
| + | # | ||
| + | # | ||
| + | |||
| + | # Копируем нужные поля в docker.* ДО создания data.* | ||
| + | [FILTER] | ||
| + | name modify | ||
| + | match | ||
| + | copy docker.hostname hostname | ||
| + | copy docker.container_started started | ||
| + | copy docker.container_name container_name | ||
| + | copy docker.container_name service_name | ||
| + | copy docker.container_id container_id | ||
| + | copy docker.state state | ||
| + | copy docker.stream stream | ||
| + | copy docker.label_project project | ||
| + | copy docker.label_service service | ||
| + | copy docker.label_logging logging | ||
| + | copy docker.label_logging_jobname logging_jobname | ||
| + | |||
| + | [FILTER] | ||
| + | name grep | ||
| + | match | ||
| + | Exclude | ||
| + | Exclude | ||
| + | Exclude | ||
| + | Exclude | ||
| + | Exclude | ||
| + | Exclude | ||
| + | |||
| + | # Создаем data.* ТОЛЬКО если logging = enabled | ||
| + | [FILTER] | ||
| + | name rewrite_tag | ||
| + | match | ||
| + | rule $logging ^enabled$ data.$container_id true | ||
| + | |||
| + | # Очищаем data.* - оставляем ТОЛЬКО нужные поля | ||
| + | [FILTER] | ||
| + | name record_modifier | ||
| + | match | ||
| + | whitelist_key | ||
| + | whitelist_key | ||
| + | whitelist_key | ||
| + | whitelist_key | ||
| + | whitelist_key | ||
| + | whitelist_key | ||
| + | whitelist_key | ||
| + | whitelist_key | ||
| + | whitelist_key | ||
| + | whitelist_key | ||
| + | whitelist_key | ||
| + | whitelist_key | ||
| + | whitelist_key | ||
| + | |||
| + | # Добавляем метаданные Swarm | ||
| + | [FILTER] | ||
| + | name modify | ||
| + | match | ||
| + | set | ||
| + | set | ||
| + | set | ||
| + | |||
| + | # [OUTPUT] | ||
| + | # | ||
| + | # | ||
| + | # | ||
| + | # | ||
| + | # | ||
| + | # | ||
| + | # | ||
| + | # | ||
| + | |||
| + | [OUTPUT] | ||
| + | name stdout | ||
| + | match | ||
| + | format | ||
| + | </ | ||
| + | |||
| + | * storage.path: | ||
| + | * storage.type filesystem: включает файловую буферизацию для input-плагина, | ||
| + | * storage.max_chunks_up: | ||
| + | * storage.backlog.mem_limit: | ||
| + | * parser=PARSER | ||
| + | * multiline.parser=MULTILINE_PARSER | ||
| + | |||
| + | ==== [[ fluent_bit_parser ]] ==== | ||
| + | |||
| + | <code ini> | ||
| + | [PARSER] | ||
| + | Name docker | ||
| + | Format | ||
| + | Time_Key | ||
| + | Time_Format %Y-%m-%dT%H: | ||
| + | Time_Keep | ||
| + | </ | ||
| + | |||
| + | ==== Plugins ==== | ||
| + | |||
| + | === docker_metadata === | ||
| + | |||
| + | docker-metadata.lua | ||
| + | <code lua> | ||
| + | -- https:// | ||
| + | -- A few little tweak was made to parse Docker Swarm metadata with fluent-bit (@ziwon) | ||
| + | DOCKER_VAR_DIR = '/ | ||
| + | DOCKER_CONTAINER_CONFIG_FILE = '/ | ||
| + | CACHE_TTL_SEC = 300 | ||
| + | |||
| + | -- Key-value pairs to get metadata. | ||
| + | DOCKER_CONTAINER_METADATA = { | ||
| + | [' | ||
| + | [' | ||
| + | [' | ||
| + | [' | ||
| + | [' | ||
| + | [' | ||
| + | [' | ||
| + | } | ||
| + | |||
| + | -- Additional metadata for Swarm | ||
| + | DOCKER_CONTAINER_CHILD_METADATA = { | ||
| + | [' | ||
| + | [' | ||
| + | [' | ||
| + | } | ||
| + | |||
| + | cache = {} | ||
| + | |||
| + | -- Print table in a recursive way | ||
| + | -- https:// | ||
| + | function tprint (tbl, indent) | ||
| + | if not indent then indent = 0 end | ||
| + | for k, v in pairs(tbl) do | ||
| + | formatting = string.rep(" | ||
| + | if type(v) == " | ||
| + | print(formatting) | ||
| + | tprint(v, indent+1) | ||
| + | else | ||
| + | print(formatting .. v) | ||
| + | end | ||
| + | end | ||
| + | end | ||
| + | |||
| + | -- Apply regular expression map to the given string | ||
| + | function apply_regex_map(data_tbl, | ||
| + | if str then | ||
| + | for key, regex in pairs(reg_tbl) do | ||
| + | data_tbl[key] = func(str, regex) | ||
| + | end | ||
| + | else | ||
| + | for key, regex in pairs(reg_tbl) do | ||
| + | local tbl = {} | ||
| + | for k, v in func(data_tbl[key], | ||
| + | tbl[k] = v | ||
| + | end | ||
| + | data_tbl[key] = tbl | ||
| + | end | ||
| + | end | ||
| + | return data_tbl | ||
| + | end | ||
| + | |||
| + | -- Get container id from tag | ||
| + | function get_container_id_from_tag(tag) | ||
| + | return tag: | ||
| + | end | ||
| + | |||
| + | -- Gets metadata from config.v2.json file for container | ||
| + | function get_container_metadata_from_disk(container_id) | ||
| + | local docker_config_file = DOCKER_VAR_DIR .. container_id .. DOCKER_CONTAINER_CONFIG_FILE | ||
| + | fl = io.open(docker_config_file, | ||
| + | if fl == nil then | ||
| + | return nil | ||
| + | end | ||
| + | |||
| + | -- parse json file and create record for cache | ||
| + | local data = { time = os.time() } | ||
| + | local reg_match = string.match | ||
| + | local reg_gmatch = string.gmatch | ||
| + | for line in fl:lines() do | ||
| + | data = apply_regex_map( | ||
| + | data, | ||
| + | DOCKER_CONTAINER_METADATA, | ||
| + | reg_match, | ||
| + | line | ||
| + | ) | ||
| + | data = apply_regex_map( | ||
| + | data, | ||
| + | DOCKER_CONTAINER_CHILD_METADATA, | ||
| + | reg_gmatch | ||
| + | ) | ||
| + | end | ||
| + | fl:close() | ||
| + | |||
| + | if next(data) == nil then | ||
| + | return nil | ||
| + | else | ||
| + | return data | ||
| + | end | ||
| + | end | ||
| + | |||
| + | function set_log_level(record) | ||
| + | local log_msg = record[' | ||
| + | local lower_msg = log_msg: | ||
| + | |||
| + | if lower_msg: | ||
| + | elseif lower_msg: | ||
| + | elseif lower_msg: | ||
| + | elseif lower_msg: | ||
| + | else record[' | ||
| + | end | ||
| + | |||
| + | function enrich_with_docker_metadata(tag, | ||
| + | -- print(" | ||
| + | |||
| + | -- Get container id from tag | ||
| + | container_id = get_container_id_from_tag(tag) | ||
| + | |||
| + | if not container_id then | ||
| + | -- print(" | ||
| + | return 0, 0, 0 | ||
| + | end | ||
| + | |||
| + | -- Add container_id to record | ||
| + | new_record = record | ||
| + | new_record[' | ||
| + | |||
| + | -- Check if we have fresh cache record for container | ||
| + | local cached_data = cache[container_id] | ||
| + | if cached_data == nil or ( os.time() - cached_data[' | ||
| + | cached_data = get_container_metadata_from_disk(container_id) | ||
| + | cache[container_id] = cached_data | ||
| + | new_record[' | ||
| + | else | ||
| + | new_record[' | ||
| + | end | ||
| + | |||
| + | -- Metadata found in cache or got from disk, enrich record | ||
| + | if cached_data then | ||
| + | for key, regex in pairs(DOCKER_CONTAINER_METADATA) do | ||
| + | new_record[key] = cached_data[key] | ||
| + | end | ||
| + | |||
| + | for key, regex in pairs(DOCKER_CONTAINER_CHILD_METADATA) do | ||
| + | new_record[key] = cached_data[key] | ||
| + | end | ||
| + | end | ||
| + | |||
| + | -- Extracted labels | ||
| + | if cached_data and cached_data[' | ||
| + | new_record[' | ||
| + | new_record[' | ||
| + | new_record[' | ||
| + | new_record[' | ||
| + | end | ||
| + | |||
| + | -- Set log level based on log content | ||
| + | set_log_level(new_record) | ||
| + | |||
| + | -- print(" | ||
| + | return 1, timestamp, new_record | ||
| + | end | ||
| + | </ | ||
| + | |||
| + | ===== Docker Compose ===== | ||
| + | |||
| + | |||
| + | monitoring | ||
| + | <code yaml> | ||
| + | version: " | ||
| + | |||
| + | x-logging: & | ||
| + | driver: " | ||
| + | options: | ||
| + | max-size: " | ||
| + | max-file: " | ||
| + | tag: " | ||
| + | |||
| + | x-labels: & | ||
| + | logging: " | ||
| + | logging_jobname: | ||
| + | monitoring: " | ||
| + | |||
| + | services: | ||
| + | loki: | ||
| + | image: grafana/ | ||
| + | ports: | ||
| + | - " | ||
| + | configs: | ||
| + | - source: loki_config | ||
| + | target: / | ||
| + | volumes: | ||
| + | - loki_data:/ | ||
| + | command: -config.file=/ | ||
| + | networks: | ||
| + | - monitoring | ||
| + | deploy: | ||
| + | placement: | ||
| + | constraints: | ||
| + | - node.role == manager | ||
| + | logging: *default-logging | ||
| + | labels: | ||
| + | <<: *default-labels | ||
| + | service: " | ||
| + | component: " | ||
| + | |||
| + | fluent-bit: | ||
| + | image: fluent/ | ||
| + | configs: | ||
| + | - source: fluent_bit_config | ||
| + | target: / | ||
| + | - source: fluent_bit_parser | ||
| + | target: / | ||
| + | - source: docker_metadata | ||
| + | target: / | ||
| + | environment: | ||
| + | - NODE_ID={{.Node.ID}} | ||
| + | - NODE_NAME={{.Node.Hostname}} | ||
| + | networks: | ||
| + | - monitoring | ||
| + | volumes: | ||
| + | - flb_storage:/ | ||
| + | - / | ||
| + | deploy: | ||
| + | mode: global | ||
| + | logging: *default-logging | ||
| + | labels: | ||
| + | <<: *default-labels | ||
| + | service: " | ||
| + | component: " | ||
| + | |||
| + | grafana: | ||
| + | image: grafana/ | ||
| + | ports: | ||
| + | - " | ||
| + | environment: | ||
| + | - GF_SECURITY_ADMIN_PASSWORD=admin | ||
| + | - GF_SECURITY_ADMIN_USER=admin | ||
| + | volumes: | ||
| + | - grafana_data:/ | ||
| + | networks: | ||
| + | - monitoring | ||
| + | deploy: | ||
| + | placement: | ||
| + | constraints: | ||
| + | - node.role == manager | ||
| + | logging: *default-logging | ||
| + | labels: | ||
| + | <<: *default-labels | ||
| + | service: " | ||
| + | component: " | ||
| + | |||
| + | configs: | ||
| + | loki_config: | ||
| + | external: true | ||
| + | fluent_bit_config: | ||
| + | external: true | ||
| + | fluent_bit_parser: | ||
| + | external: true | ||
| + | docker_metadata: | ||
| + | external: true | ||
| + | |||
| + | networks: | ||
| + | monitoring: | ||
| + | driver: overlay | ||
| + | |||
| + | volumes: | ||
| + | flb_storage: | ||
| + | driver: local | ||
| + | loki_data: | ||
| + | driver: local | ||
| + | grafana_data: | ||
| + | driver: local | ||
| + | </ | ||
| + | |||
| + | ===== Встраивание в docker-compose ===== | ||
| + | |||
| + | <code yaml> | ||
| + | x-logging: & | ||
| + | driver: " | ||
| + | options: | ||
| + | max-size: " | ||
| + | max-file: " | ||
| + | tag: " | ||
| + | |||
| + | x-labels: & | ||
| + | logging: " | ||
| + | logging_jobname: | ||
| + | monitoring: " | ||
| + | |||
| + | services: | ||
| + | |||
| + | loki: | ||
| + | logging: *default-logging | ||
| + | labels: | ||
| + | <<: *default-labels | ||
| + | service: " | ||
| + | component: " | ||
| + | </ | ||