November 3

Централизованное логирование

В данной статье мы рассмотрим вопрос централизованного логирования с использованием filebeat (гребаный спойлер) и graylog. В какой-то момент мы заметили, что машин в нашей инфраструктуре стало достаточно много, и чтобы посмотреть логи, приходилось иногда заходить на несколько машин и мучительно искать по множеству контейнеров, в этот момент мы поняли, что дальше жить так нельзя.

Постановка задачи стояла следующим образом:

  1. Чем собирать логи
  2. Где хранить
  3. Нужно не потерять логи при недоступности центрального хранилища логов

Ранее мы уже пробовали централизованно собирать логи vector (https://vector.dev/), но нам хотелось иметь удобный веб-интерфейс для просмотра. А vector веб-интерфейса не имеет. В качестве веб-интерфейса, а заодно и местом централизованного хранения, мы выбрали graylog, фактически сейчас единственное open source решение, которое имеет обширный функционал и удобство использования. На момент написания статьи vector “из коробки” не умеет отправлять в gelf формате в graylog, но в одном из issues на github один энтузиаст смог сконфигурировать vector нужным образом (https://github.com/vectordotdev/vector/issues/4868#issuecomment-782740639). В общем vector в качестве решения для сбора логов отбросили.

Прежде чем отвечать на вопрос “Чем собирать”, нужно немного сказать про инфраструктуру: в инфраструктуре у нас все приложения запускаются в docker контейнерах, машин на момент написания у нас уже 16, а самих контейнеров около 60, контейнеры в свою очередь находятся под управлением nomad. Также важным моментом является то, что несколько машин в нашей инфраструктуре являются прерываемыми, они один раз в сутки перезапускаются. С инфраструктурой разобрались, теперь нужно определяться, чем собирать. Сперва наш взгляд упал на fluentd, fluentbit, т.к. они умели отправлять в gelf формате. В процессе настройки fluentd почему-то не слал больше 30 сообщений, разобраться, почему, нам так и не удалось, при использовании fluentbit в какой-то момент использования docker logging driver gelf и настройки fluentbit c output gelf выяснилось, что при недоступности fluentbit в момент рестарта контейнеров (на прерываемых машинах контейнеры один раз в сутки перезапускаются) они не запустятся и становятся неработоспособными. Мы поняли, что чем проще - тем лучше, и решили, что нужно собирать из файлов. В свою очередь настроили fluentbit для сбора из файлов, но fluentbit почему-то тоже доставлял не все логи, аналогично с fluentd определить, почему так происходит, не удалось. И в итоге мы остановились на filebeat в качестве сборщика логов (https://www.elastic.co/guide/en/beats/filebeat/current/how-filebeat-works.html), а graylog в качестве хранилища логов.

Еще один важный момент, который нужно учесть, это то, что graylog находится в другой инфраструктуре, т.е. прямой сетевой связности между graylog сервером и машинами, на которых запущен filebeat, нет. Т.о. мы связали это все через гейт, на котором у нас находится traefik (смотри диаграмму ниже). Теперь перейдем к настройке. Graylog мы запустили на отдельной машине в обычном docker-compose.yml файле.

Graylog docker-compose.yml

version: "2.3"

services:

 es01:
   image: elasticsearch:7.10.1
   restart: unless-stopped
   network_mode: host
   ulimits:
     memlock:
       soft: -1
       hard: -1
     nofile:
       soft: 65536
       hard: 65536
   environment:
       - "ES_JAVA_OPTS=-Xms5g -Xmx5g"
       - ELASTIC_PASSWORD=test
       - discovery.type=single-node
       - cluster.max_shards_per_node=20000
       - search.max_open_scroll_context=9000
   volumes:
     - /etc/localtime:/etc/localtime:ro
     - /graylog/data/es01:/usr/share/elasticsearch/data
   labels:
     - "SERVICE_CHECK_HTTP=/_cluster/health"
     - "SERVICE_CHECK_INTERVAL=40s"
     - "SERVICE_CHECK_TIMEOUT=3s"
     - "SERVICE_CHECK_DEREGISTER_AFTER=10m"

 mongodb:
   image: mongo:4.2
   restart: unless-stopped
   network_mode: host
   volumes:
     - /graylog/data/mongo:/data/db

 graylog:
   image: graylog/graylog:4.3.3
   restart: unless-stopped
   depends_on:
     - mongodb
     - es01
   network_mode: host
   volumes:
     - /graylog/data/graylog:/usr/share/graylog/data
   environment:
     - GRAYLOG_PASSWORD_SECRET=test
     - GRAYLOG_ROOT_PASSWORD_SHA2=shashasha
     - GRAYLOG_HTTP_EXTERNAL_URI=http://192.168.2.2:9000/
   entrypoint: /usr/bin/tini -- wait-for-it 172.22.202.104:9200 --  /docker-entrypoint.sh
   labels:
     - "SERVICE_TAGS=hostname=graylog,traefik.enable=true,\
        traefik.http.routers.graylog.rule=Host(`graylog.dev`),\
        traefik.http.routers.graylog.entrypoints=websecure,\
        traefik.http.routers.graylog.tls=true"
     - SERVICE_NAME=graylog
     - SERVICE_CHECK_DEREGISTER_AFTER=10m
     - SERVICE_9000_NAME=graylog
     - SERVICE_9000_CHECK_TCP=true
     - SERVICE_9000_CHECK_INTERVAL=40s
     - SERVICE_9000_CHECK_TIMEOUT=5s
     - SERVICE_9000_CHECK_DEREGISTER_AFTER=10m

Перейдем к конфигурации filebeat

Конфиг filebeat.yml у нас простейший

filebeat.inputs:
- type: container
  paths:
    - /var/lib/docker/containers/*/*.log
  processors:
    - add_docker_metadata:
        match_source_index: 4 
output.logstash:
   hosts: 
     - graylog_host:graylog_port

Тут все просто, мы просто отправляем все логи всех контейнеров на хост graylog, в свою очередь graylog_host - это у нас хост с traefik, а в конфигурации traefik уже указан сам graylog, т.к. с гейта есть прямая сетевая связность с graylog.

Т.к. у нас nomad, то filebeat мы деплоим тоже в nomad, далее конфигурация filebeat job для деплоя его в nomad. Единственное, что здесь стоит отметить, так это то, что в env параметры GRAYLOG_HOST, GRAYLOG_PORT мы передаем ip адрес и порт на traefik. Также стоит обратить внимание, что тип job у нас system, что запускает filebeat на всех машинах. Также мы монтируем в контейнер filebeat каталог /filebeat/registry, для сохранения registry файла. Это необходимо для того, чтобы при отказе output filebeat отслеживал последние изменения, чтобы после того, как output вернется в рабочее состояние, доставить туда логи. Более подробно про registry файл можно почитать в документации filebeat https://www.elastic.co/guide/en/beats/filebeat/current/how-filebeat-works.html#_how_does_filebeat_keep_the_state_of_files

job "filebeat" {
 datacenters = ["a", "b", "c"]
 namespace   = "default"
 update {
   max_parallel      = 1
   auto_revert       = true
   auto_promote      = false
   canary            = 0
 }
 type = "system"
 group "logging" {
   restart {
     mode = "delay"
     attempts = 2
     interval = "1m"
     delay = "30s"
   }
   task "filebeat" {
     driver = "docker"
     kill_timeout = "30s"
     leader = true
     user = "root"
     env {
       SERVICE_IGNORE = "true"
       GRAYLOG_HOST = "GRAYLOG_HOST"
       GRAYLOG_PORT = "GRAYLOG_PORT"
     }
     config {
       auth {
         username = "test"
         password = "test"
       }
       image = "docker.registry/filebeat:7.10.1"
       force_pull = true
       volumes = [
         "/var/lib/docker/containers:/var/lib/docker/containers:ro",
         "/var/run/docker.sock:/var/run/docker.sock:ro",
         "/filebeat/registry:/filebeat/registry:rw"
       ]
     }
     service {
       name = "filebeat"
       tags = [ "filebeat", "logging", "hostname=${attr.unique.hostname}" ]
       check_restart {
         grace = "1m"
         ignore_warnings = false
       }
     }
     logs {
       max_files     = 3
       max_file_size = 10
     }
     resources {
       memory = 100
       memory_max = 150
     }
   }
 }
}

Теперь перейдем к конфигурации traefik, здесь лишь фрагмент конфига traefik.yml, мы добавляем еще один entrypoint для graylog.

…………
entryPoints:
 web:
   address: ":80"
   forwardedHeaders:
     insecure: true

 websecure:
   address: ":443"
   http:
     middlewares:
       - sslheader@file
       - trimwww@file


 graylog:
   address: ":11111"
…………

Также не забываем сконфигурировать порт для работы с graylog в конфиге job nomad для traefik, ниже указан фрагмент traefik job.

job "traefik" {
 datacenters = [ "a", "b", "c"]

…………………

 type = "system"
  
 group "traefik" {
   network {
     mode = "host"

     port "http" {
       static = 80
       to = 80
       host_network = "private"
     }
      
     port "public" {
               static = 443
               to = 443
       host_network = "public"
     }
      
     port "ui" {
               to = 8080
       host_network = "private"
     }

     port "graylog" {
       static = 11111
       to = "11111"
       host_network = "private"
     }

   }

   task "traefik" {
     driver = "docker"
     kill_timeout = "30s"
     leader = true

………………

     config {
       image = "traefik:v2.8.7"
       force_pull = true
       ports = ["http", "public", "ui",
        "graylog"
       ]
…………………
  
}

Так выглядят entrypoints в веб-интерфейсе traefik.

Конфигурация роутера для graylog осуществляется в tcp router, вот как это выглядит в веб-интерфейсе traefik:

Также нужно сконфигурировать сервис (в терминологии traefik), куда мы будем отправлять логи в graylog, именно в конфигурации сервиса в traefik указывается сам ip и порт graylog сервера.

В веб-интерфейсе graylog вот так конфигурируется input (в терминологии graylog), т.е. входная точка, которая будет принимать логи на стороне graylog. Тут видно, что тип input у нас Beats.

Вот так выглядит сам веб-интерфейс graylog, где мы уже ищем конкретные логи:

Вот так достаточно просто (конечно после нескольких десятков часов исследований) настраивается централизованный сбор логов. Мы пойдем посмотрим, что у нас там в логах, а вы пока stay tuned.