Splunk Universal Forwarder в докере как сборщик системных логов +7




Splunk является одним из нескольких наиболее узнаваемых коммерческих продуктов для сбора и анализа логов. Даже сейчас, когда продажи в России больше не производятся, это не повод не писать инструкции/how-to по этому продукту.

Задача: собирать системные логи с docker нод в Splunk не меняя конфигурацию хост-машины

Начать хотелось бы с официального подхода, который выглядит странноватым при использовании докера.
Ссылка на Docker hub
Что же мы имеем:

1. Пуллим образ

$ docker pull splunk/universalforwarder:latest

2. Стартуем контейнер с нужными параметрами

$ docker run -d  -p 9997:9997 -e 'SPLUNK_START_ARGS=--accept-license' -e 'SPLUNK_PASSWORD=<password>' splunk/universalforwarder:latest

3. Заходим в контейнер

docker exec -it <container-id> /bin/bash

Далее нас просят пройти по известному адресу в документацию.

И конфигурировать контейнер после его запуска:


./splunk add forward-server <host name or ip address>:<listening port>
./splunk add monitor /var/log
./splunk restart

Подождите. Что?

Но на этом сюрпризы не заканчиваются. Если вы запустите контейнер из официального образа в интерактивном режиме, то увидите следующее:

Немного разочарования

$ docker run -it -p 9997:9997 -e 'SPLUNK_START_ARGS=--accept-license' -e 'SPLUNK_PASSWORD=password' splunk/universalforwarder:latest

PLAY [Run default Splunk provisioning] *******************************************************************************************************************************************************************************************************
Tuesday 09 April 2019  13:40:38 +0000 (0:00:00.096)       0:00:00.096 *********

TASK [Gathering Facts] ***********************************************************************************************************************************************************************************************************************
ok: [localhost]
Tuesday 09 April 2019  13:40:39 +0000 (0:00:01.520)       0:00:01.616 *********

TASK [Get actual hostname] *******************************************************************************************************************************************************************************************************************
changed: [localhost]
Tuesday 09 April 2019  13:40:40 +0000 (0:00:00.599)       0:00:02.215 *********
Tuesday 09 April 2019  13:40:40 +0000 (0:00:00.054)       0:00:02.270 *********

TASK [set_fact] ******************************************************************************************************************************************************************************************************************************
ok: [localhost]
Tuesday 09 April 2019  13:40:40 +0000 (0:00:00.075)       0:00:02.346 *********
Tuesday 09 April 2019  13:40:40 +0000 (0:00:00.067)       0:00:02.413 *********
Tuesday 09 April 2019  13:40:40 +0000 (0:00:00.060)       0:00:02.473 *********
Tuesday 09 April 2019  13:40:40 +0000 (0:00:00.051)       0:00:02.525 *********
Tuesday 09 April 2019  13:40:40 +0000 (0:00:00.056)       0:00:02.582 *********
Tuesday 09 April 2019  13:40:41 +0000 (0:00:00.216)       0:00:02.798 *********
included: /opt/ansible/roles/splunk_common/tasks/change_splunk_directory_owner.yml for localhost
Tuesday 09 April 2019  13:40:41 +0000 (0:00:00.087)       0:00:02.886 *********

TASK [splunk_common : Update Splunk directory owner] *****************************************************************************************************************************************************************************************
ok: [localhost]
Tuesday 09 April 2019  13:40:41 +0000 (0:00:00.324)       0:00:03.210 *********
included: /opt/ansible/roles/splunk_common/tasks/get_facts.yml for localhost
Tuesday 09 April 2019  13:40:41 +0000 (0:00:00.094)       0:00:03.305 *********

ну и так далее...

Отлично. В образе даже нет артефакта. То есть, каждый раз при запуске будет тратиться время, чтобы выкачать архив с бинарниками, распаковать и настроить.
А как же docker-way и всё такое?

Нет, спасибо. Мы пойдём другим путём. Что, если все эти операции мы выполним на этапе сборки? Тогда поехали!

Чтобы долго не тянуть, покажу сразу итоговый образ:

Dockerfile
# Тут у кого какие предпочтения
FROM centos:7

# Задаём переменные, чтобы каждый раз при старте не указывать их
ENV SPLUNK_HOME /splunkforwarder
ENV SPLUNK_ROLE splunk_heavy_forwarder
ENV SPLUNK_PASSWORD changeme
ENV SPLUNK_START_ARGS --accept-license

# Ставим пакеты
# wget - чтобы скачать артефакты
# expect - понадобится для первоначального запуска Splunk на этапе сборки
# jq - используется в скриптах, которые собирают статистику докера
RUN yum install -y epel-release     && yum install -y wget expect jq

# Качаем, распаковываем, удаляем
RUN wget -O splunkforwarder-7.2.4-8a94541dcfac-Linux-x86_64.tgz 'https://www.splunk.com/bin/splunk/DownloadActivityServlet?architecture=x86_64&platform=linux&version=7.2.4&product=universalforwarder&filename=splunkforwarder-7.2.4-8a94541dcfac-Linux-x86_64.tgz&wget=true'     && wget -O docker-18.09.3.tgz 'https://download.docker.com/linux/static/stable/x86_64/docker-18.09.3.tgz'     && tar -xvf splunkforwarder-7.2.4-8a94541dcfac-Linux-x86_64.tgz     && tar -xvf docker-18.09.3.tgz      && rm -f splunkforwarder-7.2.4-8a94541dcfac-Linux-x86_64.tgz     && rm -f docker-18.09.3.tgz

# С shell скриптами всё понятно, а вот inputs.conf, splunkclouduf.spl и first_start.sh нуждаются в пояснении. Об этом расскажу после source тэга.
COPY [ "inputs.conf", "docker-stats/props.conf", "/splunkforwarder/etc/system/local/" ]
COPY [ "docker-stats/docker_events.sh", "docker-stats/docker_inspect.sh", "docker-stats/docker_stats.sh", "docker-stats/docker_top.sh", "/splunkforwarder/bin/scripts/" ]
COPY splunkclouduf.spl /splunkclouduf.spl
COPY first_start.sh /splunkforwarder/bin/

#  Даём права на исполнение, добавляем пользователя и выполняем первоначальную настройку
RUN chmod +x /splunkforwarder/bin/scripts/*.sh     && groupadd -r splunk     && useradd -r -m -g splunk splunk     && echo "%sudo ALL=NOPASSWD:ALL" >> /etc/sudoers     && chown -R splunk:splunk $SPLUNK_HOME     && /splunkforwarder/bin/first_start.sh     && /splunkforwarder/bin/splunk install app /splunkclouduf.spl -auth admin:changeme     && /splunkforwarder/bin/splunk restart

# Копируем инит скрипты
COPY [ "init/entrypoint.sh", "init/checkstate.sh", "/sbin/" ]

# По желанию. Кому нужно локально иметь конфиги/логи, кому нет.
VOLUME [ "/splunkforwarder/etc", "/splunkforwarder/var" ]

HEALTHCHECK --interval=30s --timeout=30s --start-period=3m --retries=5 CMD /sbin/checkstate.sh || exit 1

ENTRYPOINT [ "/sbin/entrypoint.sh" ]
CMD [ "start-service" ]

И так, что же содержится в

first_start.sh
#!/usr/bin/expect -f
set timeout -1
spawn /splunkforwarder/bin/splunk start --accept-license
expect "Please enter an administrator username: "
send -- "admin\r"
expect "Please enter a new password: "
send -- "changeme\r"
expect "Please confirm new password: "
send -- "changeme\r"
expect eof

При первом старте, Splunk просит задать ему логин/пароль, НО эти данные используются только для выполнения административных команд этой конкретной инсталляции, то есть, внутри контейнера. В нашем случае же, мы просто хотим запустить контейнер, чтобы всё работало и логи лились рекой. Конечно, это хардкод, но других способов я не нашёл.

Дальше по сценарию выполняется

/splunkforwarder/bin/splunk install app /splunkclouduf.spl -auth admin:changeme

splunkclouduf.spl — Это файл креды для Splunk Universal Forwarder, которые можно загрузить из веб интерфейса.

Куда нажимать чтобы скачать (в картинках)



Это обычный архив, который можно распаковать. Внутри — сертификаты и пароль для подключения к нашему SplunkCloud и outputs.conf со списком наших input инстансов. Этот файл будет актуален до тех пор, пока вы не переустановите свою инсталляцию Splunk или не добавите input нод, если инсталляция on-premise. Поэтому ничего страшного нет в том, чтобы добавить его внутрь контейнера.

И последнее — restart. Да, для применения изменений, нужно его рестартануть.

В наш inputs.conf добавляем логи, которые мы хотим отправлять в Splunk. Необязательно добавлять этот файл в образ, если вы, к примеру, раскидываете конфиги через puppet. Главное лишь в том, чтобы Forwarder видел конфиги, при старте демона, иначе будет нужен ./splunk restart.

А что за docker stats скрипты? На гитхабе есть старое решение от outcoldman, скрипты взяты оттуда и доработаны для работы с актуальными версиями Docker (ce-17.*) и Splunk (7.*).

С полученными данными можно строить такие

дэшборды: (пара картинок)



Исходный код дэшей лежит в репе, указанной в конце статьи. Обратите внимание, что там присутствует 2 select поля: 1 — выбор индекса (ищутся по маске), выбор хоста/контейнера. Маску индекса скорее всего вам придётся обновить, в зависимости от имён, которые вы используете.

В завершение, хочу обратить внимание на функцию start() в

entrypoint.sh
start() {
    trap teardown EXIT
	if [ -z $SPLUNK_INDEX ]; then
	echo "'SPLUNK_INDEX' env variable is empty or not defined. Should be 'dev' or 'prd'." >&2
	exit 1
	else
	sed -e "s/@index@/$SPLUNK_INDEX/" -i ${SPLUNK_HOME}/etc/system/local/inputs.conf
	fi
	sed -e "s/@hostname@/$(cat /etc/hostname)/" -i ${SPLUNK_HOME}/etc/system/local/inputs.conf
    sh -c "echo 'starting' > /tmp/splunk-container.state"
	${SPLUNK_HOME}/bin/splunk start
    watch_for_failure
}

В моём случае, для каждого окружения и каждой отдельной сущности, будь то приложение в контейнере или хост-машина, мы используем отдельный индекс. Так не будет страдать скорость поиска при значительном накоплении данных. Для именования индексов используется простое правило: <environment_name>_<service/application/etc>. Поэтому, чтобы контейнер был универсальным, мы перед запуском непосредственно демона, заменяем sed-ом wildcard на имя окружения. Переменная с именем окружения передается через переменные окружения. Звучит забавно.

Также стоит отметить момент, что по какой-то причине на Splunk не влияет наличие docker параметра hostname. Он всё равно будет упорото слать логи с id своего контейнера в поле host. Как решение, можно монтировать /etc/hostname с хост-машины и при старте делать замену, аналогичную именам индексов.

Пример docker-compose.yml
version: '2'
services:
  splunk-forwarder:
    image: "${IMAGE_REPO}/docker-stats-splunk-forwarder:${IMAGE_VERSION}"
    environment:
      SPLUNK_INDEX: ${ENVIRONMENT}
    volumes:
    - /etc/hostname:/etc/hostname:ro
    - /var/log:/var/log
    - /var/run/docker.sock:/var/run/docker.sock:ro


Итог

Да, возможно, решение не идеальное и уж точно не универсальное для всех, так как присутствует много «хардкода». Но на основе него каждый может собрать свой образ и положить его в свой приватный артифактори, если, так уж случилось, вам необходим Splunk Forwarder именно в докере.

Ссылки:

Решение из статьи
Решение от outcoldman вдохновившее переиспользовать часть функционала
Оф. документация по настройке Universal Forwarder




К сожалению, не доступен сервер mySQL