Cluster level логирование в kubernetes с помощью rsyslog

Rsyslog - высокопроизводительный обработчик логов. Может использоваться в качестве агента на уровне сервера и агента журналирования на уровне пода.

Содержание

Архитектура и принцип действия

Изначально проектировался как syslog процесс. За время жизни эволюционировал в универсальный инструмент с расширяемым модульным дизайном. Использует язык ruby для конфигурирования.

Архитектура Rsyslog состоит из 3-х основных модулей: ввода, обработки и вывода. Модули реализуются в виде внешних подключаемых зависимостей - плагинов.

Архитектура rsyslog

Плагины ввода

Позволяют получать сообщения из различных источников. К основным можно отнести:

  • journal - журнал операционной системы;
  • klog - журнал ядра операционной системы;
  • file - чтение файла из файловой системы.

Обработка сообщения

Позволяют преобразовывать входящие сообщения. К основным можно отнести:

  • mmkubernetes - позволяет дополнять входящие сообщения метаданными о поде;
  • mmjsonparse - позволяет модифицировать входящие json сообщения;
  • mmfields - позволяет выделять поля из входящих сообщений и использовать их для записи в другой формат;

Плагины вывода

Позволяют отправлять сообщения в различные системы хранения. К основным можно отнести:

  • file - записывать сообщения в файл;
  • pgsql - сохранять сообщения в PostgreSQL;
  • elasticsearch - сохранять сообщения в Elasticsearch;
  • mongodb - сохранять сообщения в MongoDB.

Установка в kubernetes

Существует несколько подходов к cluster-level журналированию в kubernetes. Rsyslog можно применять в любом, но для упрощения примера будет использован подход журналирования на уровне сервера.

Подготовка хостовой операционной системы

В зависимости от версии kubernetes могут быть использованы разные драйверы записи логов docker контейнера. Определить какой драйвер использован можно с помощью команды docker info:

~ % docker info --format '{{.LoggingDriver}}'
journald

Если используется драйвер journald требуется включить перенаправление логов в unix socket в файле/etc/systemd/journald.conf:

[Journal]
ForwardToSyslog=yes

После чего перезапустить сервис systemd-journald:

~ % systemctl restart systemd-journald

Подготовка docker образа rsyslog

За основу взят образ rsyslog-docker. Для получения метаинформации о поде из api-server дополнительно требуется плагин mm-kubernetes, поэтому его необходимо дополнительно установить в базовый образ:

FROM ubuntu:20.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -y update \
  && apt-get -y upgrade \
  && apt-get -y install software-properties-common curl \
  && add-apt-repository -y ppa:adiscon/v8-stable \
  && apt-get -y update \
  && apt-get -y install libfastjson4 \
              rsyslog \
              rsyslog-mmkubernetes \
              rsyslog-elasticsearch \
              rsyslog-imptcp \
              rsyslog-relp \
              rsyslog-mmjsonparse \
              rsyslog-mmutf8fix \
              rsyslog-omstdout \
  && apt-get clean \
  && rm -r /etc/rsyslog.conf
ADD rsyslog.conf /etc/rsyslog.conf
#VOLUME  /rsyslog-bin
RUN mkdir /rsyslog-bin \
  && cp /usr/sbin/rsyslogd /usr/lib/rsyslog/* /rsyslog-bin

EXPOSE  10514

WORKDIR /rsyslog-bin
CMD ["/rsyslog-bin/rsyslogd", "-n", "-f/etc/rsyslog.conf", "-M."]

И подготовить файл конфигурации rsyslog.conf с настроенным шаблоном записи:

module(load="imuxsock" SysSock.Annotate="on" SysSock.ParseTrusted="on")

# Модуль mmkubernetes использует адрес https://kubernetes.default.svc.cluster.local/.
# Параметр KubernetesURL позволяет его изменить
module(load="mmkubernetes"
  tls.cacert="/run/secrets/kubernetes.io/serviceaccount/ca.crt"
  tokenfile="/run/secrets/kubernetes.io/serviceaccount/token"
# По умолчанию лейблы и аннотации не добавляются к сообщениям
  annotation_match=["."])
module(load="omstdout")

# Для сбора логов используется unix socket /run/systemd/journal/syslog. 
# Эта директория должна быть смонтирована из хостовой операционной системы
input(type="imuxsock" Socket="/run/systemd/journal/syslog" CreatePath="on")

# Запрос информации о поде у api-server
action(type="mmkubernetes")

# Объявление шаблона для записи сообщений
template(name="k8s-msg" type="list" option.jsonf="on") {
  property(outname="priority" name="PRI" format="jsonf")
  property(outname="@timestamp" name="TIMESTAMP" dateFormat="rfc3339" date.inUTC="on" format="jsonf")
  property(outname="namespace" name="!kubernetes!namespace_name" format="jsonf")
  property(outname="pod_name" name="!kubernetes!pod_name" format="jsonf")
  property(outname="pod_id" name="!kubernetes!pod_id" format="jsonf")
  property(outname="container_name" name="!kubernetes!container_name" format="jsonf")
  property(outname="container_id" name="!kubernetes!container_id" format="jsonf")
  property(name="msg" droplastlf="on" format="jsonf")
}

# Запись обработанных сообщений в файл
action(type="omfile" file="/var/log/rsyslog/k8s.log" template="k8s-msg")

Сборка docker образа выполняется с помощью команды docker build:

~ % docker build -f Dockerfile influunt/rsyslog:8.36.0-3.7

Образ можно опубликовать в docker hub, или экспортировать для дальнейшего распространения по узлам:

~ % docker save influunt/rsyslog:8.36.0-3.7 > rsyslog-8.36.0-3.7.tar
~ % docker load < rsyslog-8.36.0-3.7.tar

Подготовка манифестов и установка

Для журналирования на уровне сервера лучше всего подходит daemon set. При добавлении новых узлов в кластер kubernetes сам позаботится о запуске rsyslog.

Манифест основан на ранее собранном образе influunt/rsyslog:8.36.0-3.7 и монтирует хост директорию /run/systemd/journal для интеграции rsyslog с systemd-journald через unix socket файл /run/systemd/journal/syslog:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: rsyslog
  namespace: kube-system
  labels:
    k8s-app: rsyslog
spec:
  selector:
    matchLabels:
      k8s-app: rsyslog
  template:
    metadata:
      labels:
        k8s-app: rsyslog
    spec:
      priorityClassName: system-node-critical
      containers:
      - name: rsyslog
        image: influunt/rsyslog:8.36.0-3.7
        securityContext:
          privileged: true
        env:
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        volumeMounts:
        - name: journal-run
          mountPath: /run/systemd/journal/
      volumes:
      - name: journal-run
        hostPath:
          path: /run/systemd/journal/

Установка и применение изменений производится с помощью команды kubectl apply:

~ % kubectl apply -f rsyslog-ds.yaml

и после этого убедиться в успехе установки:

~ % kubectl -n kube-system get ds rsyslog
NAME      DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
rsyslog   1         1         1       1            1           <none>          27s
~ % kubectl -n kube-system get pods -lk8s-app=rsyslog
NAME            READY   STATUS    RESTARTS   AGE
rsyslog-qjt5d   1/1     Running   0          30s
~ % kubectl -n kube-system exec -ti rsyslog-qjt5d  -- tail -f /var/log/rsyslog/cee.log
{"priority":"14", "@timestamp":"2021-02-18T19:04:39.057120+00:00", "namespace":"kube-system", "pod_name":"ingress-nginx-b55d9556b-g54s8", "pod_id":"b55d9556b-g54s8", "container_name":"rsyslog", "container_id":"0394f7d4a7e9b369c31dfd788c1a098eee289e726ae1afce63dcbb080c562d68", "msg":" 10.10.0.1 - - [18\/Feb\/2021:19:04:39 +0000] \"GET \/ HTTP\/1.1\" 200 6 \"\" \"kube-probe\/1.20\""}
...

Выводы

Тема логирования с помощью rsyslog обширна и не может быть раскрыта в рамках одной статьи. Разнообразие плагинов вывода позволяет использовать различные системы хранения. Для построения отказоустойчивой схемы можно передавать журналы через персистентную очередь Kafka или RabbitMQ.

Недостаток rsyslog в схеме cluster level журналирования - неустойчивость к отказам api server. Метаинформация, запрошенная у api server, кэшируется на все время жизни пода. Она не перезапрашивается в случае ошибки при ее запросе или её обновлении.