Fluent Bit, Graylog, Docker сбор логов с контейнеров.

Сегодня хочется поделиться о том как собирать и парсить логи в современном мире. На сайте уже есть одна статья о Fluent Bit, но сегодня речь пойдет о GrayLog. Мы не затрагиваем тему сравнений агрегации логов или сравнении программных продуктов для этого. Это в первую очередь опыт работы с конкретным продуктом.

Как уже повелось, все действия будем производить в контейнерах  Docker. И первое с чего стоит начать, это непосредственно с установки GrayLog, идеально воспользоваться документацией с официального сайта: https://docs.graylog.org/docs/docker все очень подробно описано, в целом, я думаю, у вас не возникнет вопросов. В качестве хранения своей конфигурации и настроек GrayLog использует MongoDB, а в качестве хранения самих логов Elasticsearch при чем не старше определенной версии, но думаю вы об этом прочитали в документации. И так, что бы не ходить далеко создаем наш docker-compose.yaml с необходимыми для GrayLog компонентами.

version: '3.8'
services:

  elasticsearch:
    image: elasticsearch:6.8.23
    container_name: elasticsearch
    hostname: elasticsearch
    restart: always
    environment:
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    cap_drop:
      - NET_ADMIN
      - SYS_ADMIN
    volumes:
      - ./elasticsearch:/usr/share/elasticsearch/data
    networks:
      - graylog

  mongo:
    image: mongo:5.0.11
    container_name: mongodb
    hostname: mongodb
    restart: always
    ulimits:
      memlock:
        soft: -1
        hard: -1
    cap_drop:
      - NET_ADMIN
      - SYS_ADMIN
    volumes:
      - ./mongodb:/data/db
    networks:
      - graylog

  graylog:
    image: graylog/graylog:4.2
    container_name: graylog
    hostname: graylog
    entrypoint: /usr/bin/tini -- wait-for-it elasticsearch:9200 --  /docker-entrypoint.sh
    restart: always
    depends_on:
      - mongo
      - elasticsearch
    ulimits:
      memlock:
        soft: -1
        hard: -1
    cap_drop:
      - NET_ADMIN
      - SYS_ADMIN
    volumes:
      - ./src/graylog/graylog.conf:/usr/share/graylog/data/config/graylog.conf
    ports:
      - 9000:9000
      - 1514:1514
      - 1514:1514/udp
      - 12201:12201
      - 12201:12201/udp
    networks:
      - graylog

Нужно помнить о тех требованиях которые предъявляют компоненты, в частности Elasticsearch будет требовать максимального количества областей памяти, которое вы можете задать с помощью: sysctl -w vm.max_map_count=262144

В данном примере я использую свой собственный файл конфигурации graylog.conf, он очень хорошо описан комментариями что бы понять какие настройки и как их производить, лучше всего перед редактированием прочитать: https://docs.graylog.org/docs/server-conf, если вы используете docker образ по-умолчанию, то все настройки будут по-умолчанию как в документации. Можно так же воспользоваться переменными окружения, и передать необходимые параметры контейнеру, но мне было интересно посмотреть сам файл и настроить его вручную, затем передать его в контейнер. Я не буду описать весь файл тут, он слишком велик для этого, но остановлюсь на самых основных моментах которые стоит поправить.

# Необходимо сгенерировать пароль длинной не менее 16 символов: pwgen -N 1 -s 96
password_secret = QTF370msQVFW5Sq0juYw8uYP

# По-умолчанию имя пользователя будет admin, и таким же будет пароль, если ничего не менять, но лучше сделать это сразу, что бы мамкины хакеры не одолевали в процессе.
root_username = yakunin

# Так же, вы должны сгенерировать отпечаток пароля, это делается для сохранения сессии: echo -n yourpassword | shasum -a 256
root_password_sha2 = 2d27bea1609d7171a150a1a0f3ac8fc00edab138c6e2eafc4fa195711eb38541

# Укажите свой емалй и временную зону для удобства.
root_email = "me@yakunin.dev"
root_timezone = Europe/Moscow

# Я бы рекомендовал спрятать GrayLog за Nginx и не выпускал бы сам интерфейс наружу, но в качестве примера мы будем слушать любые адреса на устройстве.
http_bind_address = 0.0.0.0:9000

# Так как мы запускаем наш стек в docker, обращение к компонентам указываем по dns именам контейнеров.
elasticsearch_hosts = http://elasticsearch:9200

# Тоже самое касается и MongoDB
mongodb_uri = mongodb://mongo/graylog

Вообщем-то это самое главное для того что бы запустить наш стек, но я настоятельно рекомендую так же настроить размеры журналов и другие не маловажные параметры. Перед там как запустить наш проект, создадим папки для хранения данных и назначим на них необходимые права. А так же создадим папку src куда будем помещать необходимые нам файлы конфигурации для дальнейшей работы, я помещаю туда graylog.conf. И можно запускать проект, если вы копируете отсюда конфигурацию то вам необходимо создать свой файл graylog.conf и положить его в папку src/graylog/graylog.conf

> mkdir elasticsearch mongodb
> chmod 0775 elasticsearch mongodb
> mkdir -p src/graylog

> docker compose up --detach
[+] Running 4/4
 ⠿ Network graylog_graylog  Created                                                                                                                    0.0s
 ⠿ Container mongodb        Started                                                                                                                    0.7s
 ⠿ Container elasticsearch  Started                                                                                                                    0.6s
 ⠿ Container graylog        Started

> docker ps -a
CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS                    PORTS    NAMES
04b4fee88a91   graylog/graylog:4.2    "/usr/bin/tini -- wa…"   50 seconds ago   Up 49 seconds (healthy)   0.0.0.0:1514->1514/tcp    graylog
dba18a2fd378   elasticsearch:6.8.23   "/usr/local/bin/dock…"   50 seconds ago   Up 49 seconds             9200/tcp, 9300/tcp    elasticsearch
57c8958e643f   mongo:5.0.11           "docker-entrypoint.s…"   50 seconds ago   Up 49 seconds             27017/tcp    mongodb

Наш стек запустился, если что-то пошло не так, смотрите логи docker logs -f $container_name. Если все хорошо, то на вашем хосте по порту 9000 будет доступен веб интерфейс GrayLog с тем логином и паролем который вы сделали на этапе генерации graylog.conf, заходим в веб интерфейс https://127.0.0.1:9000

После того как мы получили доступ, нам необходимо настроить приемник, их у GrayLog большое множество, но мы будем использовать GELF, это адаптированный json формат. Подробнее: https://docs.graylog.org/docs/gelf, для того что бы создать входящий поток переходим во вкладку System — > Inputs

Если все вышло правильно, то должно получится следующее:

Теперь Graylog готов к приему сообщений. Далее нам необходимо настроить отправку логов от контейнера в Graylog. У docker есть нативный драйвер fluentd, который далее распарсит и передаст логи в Graylog. Хорошо, доработаем наш docker-compose.yaml до следующего вида:

version: '3.8'
services:

  elasticsearch:
    image: elasticsearch:6.8.23
    container_name: elasticsearch
    hostname: elasticsearch
    restart: always
    environment:
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ports:
      - 9200:9200
    ulimits:
      memlock:
        soft: -1
        hard: -1
    cap_drop:
      - NET_ADMIN
      - SYS_ADMIN
    volumes:
      - ./elasticsearch:/usr/share/elasticsearch/data
    networks:
      - graylog

  mongo:
    image: mongo:5.0.11
    container_name: mongodb
    hostname: mongodb
    restart: always
    ulimits:
      memlock:
        soft: -1
        hard: -1
    cap_drop:
      - NET_ADMIN
      - SYS_ADMIN
    volumes:
      - ./mongodb:/data/db
    networks:
      - graylog

  graylog:
    image: graylog/graylog:4.2
    container_name: graylog
    hostname: graylog
    entrypoint: /usr/bin/tini -- wait-for-it elasticsearch:9200 --  /docker-entrypoint.sh
    restart: always
    depends_on:
      - mongo
      - elasticsearch
    ulimits:
      memlock:
        soft: -1
        hard: -1
    cap_drop:
      - NET_ADMIN
      - SYS_ADMIN
    volumes:
      - ./src/graylog/graylog.conf:/usr/share/graylog/data/config/graylog.conf
    ports:
      - 9000:9000
      - 1514:1514
      - 1514:1514/udp
      - 12201:12201
      - 12201:12201/udp
    networks:
      - graylog

  fluent-bit:
    image: fluent/fluent-bit
    container_name: fluent-bit
    hostname: fluent-bit
    ulimits:
      memlock:
        soft: -1
        hard: -1
    cap_drop:
      - NET_ADMIN
      - SYS_ADMIN
    ports:
      - 127.0.0.1:24224:24224
      - 127.0.0.1:24224:24224/udp
    volumes:
      - ./src/fluentbit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf
      - ./src/fluentbit/parsers.conf:/fluent-bit/etc/parsers.conf
    networks:
      - graylog

  nginx:
    image: nginx
    container_name: nginx
    hostname: nginx
    ulimits:
      memlock:
        soft: -1
        hard: -1
    cap_drop:
      - NET_ADMIN
      - SYS_ADMIN
    ports:
      - 80:80
    depends_on:
      - fluent-bit
    logging:
      driver: "fluentd"
      options:
        fluentd-address: 127.0.0.1:24224
        tag: nginx.logs
    networks:
      - graylog

networks:
    graylog:
      driver: bridge

В первую очередь обратите внимание на то, что контейнеру nginx мы добавили драйвер вывода логов в fluentd, это очень удобно,а так же добавили ему тег, который он будет подписывать в начало. Это удобно когда у вас много сервисов. Далее. Мы запускаем сам fluent bit, который будет принимать логи от nginx обрабатывать их и отдавать в Graylog. Рассмотрим его конфигурационные файлы.

cat src/fluentbit/fluent-bit.conf 
[SERVICE]
    flush            1
    log_Level        info
    daemon           off
    http_server      on
    http_listen      127.0.0.1
    http_port        2020
    storage.metrics  on
    parsers_File     parsers.conf

[INPUT]
    Name              forward
    Listen            0.0.0.0
    Port              24224
    Buffer_Chunk_Size 1M
    Buffer_Max_Size   6M

[OUTPUT]
    Name                    gelf
    Match                   nginx.logs
    Host                    graylog
    Port                    12201
    Mode                    tcp
    Gelf_Short_Message_Key  log

[OUTPUT]
    Name stdout
    Match *

[FILTER]
    Name record_modifier
    Match nginx.logs
    Allowlist_key log

[FILTER]
    Name parser
    Match nginx.logs
    Key_Name log
    Parser nginx_parser
    Reserve_Data On
    Preserve_Key On

[FILTER]
    Name modify
    Match nginx.logs
    Add hostname ws.yakunin.dev
    Add log_type nginx_logs

В целом описание всех директив можно прочитать на сайте fluent bit. И это нужно сделать! 😉 Но я поясню немного конфигурационный файл. И так, у нас есть входящий поток который мы вызываем по средствам forward, что подразумевает пересылку логов от контейнера к точке назначения. Далее мы говорим что мы хотим использовать в качестве точки назначения graylog, в формате gelf и обрабатывать все что приходит к нам с тегом nginx.logs. Далее мы пропускаем данные через фильтры, первый убирает все поля кроме поля log, это удобно что бы не забивать elasticsearch лишней информацией, далее второй фильтр парсит лог файл разбивая его на необходимые нам блоки. И последний добавляет 2 поля, с какого хоста мы отправляем наши логи и что это за логи. Теперь все готово, можно запускать стек.

> docker compose up --detach 
[+] Running 6/6
 ⠿ Network graylog_graylog  Created                                                                                                                                                                0.1s
 ⠿ Container mongodb        Started                                                                                                                                                                0.8s
 ⠿ Container fluent-bit     Started                                                                                                                                                                0.8s
 ⠿ Container elasticsearch  Started                                                                                                                                                                0.7s
 ⠿ Container graylog        Started                                                                                                                                                                1.4s
 ⠿ Container nginx          Started                                                                                                                                                                1.2s
ws➜  graylog  ᐅ  docker ps -a
CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS                            PORTS      NAMES
acb887ff0c8a   graylog/graylog:4.2    "/usr/bin/tini -- wa…"   5 seconds ago   Up 3 seconds (health: starting)   0.0.0.0:1514->1514/tcp, :::1514->1514/tcp, 0.0.0.0:9000->9000/tcp, 0.0.0.0:1514->1514/udp      graylog
d8c66b6185e8   nginx                  "/docker-entrypoint.…"   5 seconds ago   Up 4 seconds                      0.0.0.0:80->80/tcp, :::80->80/tcp      nginx
0c396307dc1a   fluent/fluent-bit      "/fluent-bit/bin/flu…"   5 seconds ago   Up 4 seconds                      2020/tcp, 127.0.0.1:24224->24224/tcp, 127.0.0.1:24224->24224/udp      fluent-bit
a9aaeb34a082   elasticsearch:6.8.23   "/usr/local/bin/dock…"   5 seconds ago   Up 4 seconds                      0.0.0.0:9200->9200/tcp, :::9200->9200/tcp, 9300/tcp      elasticsearch
6bcd4ac161e1   mongo:5.0.11           "docker-entrypoint.s…"   5 seconds ago   Up 4 seconds                      27017/tcp      mongodb

Теперь делаем запрос к nginx, можно их командной строки:

> curl 127.0.0.1:80

После чего смотрим что у нас говорит fluent bit.

> docker logs -f fluent-bit

[0] nginx.logs: [1663857110.000000000, {"remote"=>"172.18.0.1", "host"=>"-", "user"=>"-", "time"=>"22/Sep/2022:14:31:50 +0000", "method"=>"GET", "path"=>"/", "code"=>"200", "size"=>"615", "referer"=>"-",
 "agent"=>"curl/7.81.0", "log"=>"172.18.0.1 - - [22/Sep/2022:14:31:50 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.81.0" "-"", "hostname"=>"ws.yakunin.dev", "log_type"=>"nginx_logs"}]

Отлично, тогда переходим в интерфейс, Search и в странице поиска выбираем временной промежуток, далее на изображение линзы, и видим наш запрос.

Если нажать на результат поиска, то можно увидеть как fluent bit добавил все необходимые поля а так же распарсил наш лог. Все это конечно же происходит на лету.

Вот собственно и все, таким образом вы можете добавить другие сервисы, добавить к ним теги, отдельно можно поговорить об парсинге, но об этом лучше написать другую статью. Надеюсь это было полезно. Как обычно, все файлы можно найти тут: https://git.yakunin.dev/yakunin/docker/-/tree/main/graylog-docker-fluentbit

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *