VK Cloud logo

Как настроить централизованное логирование из Kubernetes с помощью Fluentd

Почему в Kubernetes необходимо специальное решение для логирования

Писать логи в контейнеры неудобно, потому что:

  • данные, генерируемые в контейнеризированных приложениях, существуют, пока существует контейнер. При перезагрузке docker-контейнера данные, в том числе логи приложения, удаляются;
  • логи нельзя ротировать в контейнере, так как ротация — это дополнительный процесс, помимо логируемого, а в одном контейнере не может быть запущено более одного процесса.

Ротация логов — процесс обработки, очищения, архивации и отправки логов с помощью утилит.

Чтобы можно было получить доступ к логам контейнеризированных приложений в Kubernetes, docker-контейнеры должны передавать свои логи в стандартные потоки вывода (stdout) и ошибок (stderr). По умолчанию docker logging driver пишет логи в json-файл на ноде, откуда их можно получить с помощью команды:

kubectl logs pod_name

Docker logging driver — механизм сбора логов, встроенный в docker-движок и поддерживающий множество инструментов ротации логов.

Когда жизненным циклом docker-контейнеров управляет оркестратор Kubernetes, поды с контейнерами часто и непредсказуемо создаются, перезагружаются и удаляются. Если это допускают настройки драйвера логирования docker, можно получить доступ к последним до перезагрузки логам пода с помощью аргумента --previous:

kubectl logs pod_name --previous

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

Поэтому для работы с логами приложений в Kubernetes необходима система, которая сможет собирать, агрегировать, сохранять и извлекать полезную информацию из логов. Для этой задачи подходит связка из поискового движка Elasticsearch, агента логирования Fluentd и дашборда Kibana — EFK-стек.

Схема системы логирования в кластере Kubernetes:

В зависимости от количества информации, необходимой в обработке Elasticsearch, можно выбрать различные пути инсталляции EFK-стека:

  • При ожидаемых нагрузках в десятки тысяч логов в секунду не рекомендуется устанавливать Elasticsearch в кластер Kubernetes. Для высоконагруженного решения в продакшене лучше выделить отдельные виртуальные машины под кластер самого Elasticsearch, а логи посылать из Kubernetes с помощью лог-агрегаторов.
  • Если система не производит десятки тысяч логов, и нужно наблюдать за логами на дев-, тест-окружениях, EFK-стек можно установить в кластер Kubernetes, поближе к приложениям.

В качестве агента сборщика логов используем fluentd — приложение для сбора, фильтрации и агрегации логов, написанное на языках C и Ruby и располагающее различными плагинами для расширения базового функционала приложения.

Организуем централизованную систему логирования для Kubernetes.

Установка Elasticsearch в Kubernetes c помощью Helm

  1. Создайте namespace kube-logging:
kubectl create ns kube-logging

При установке из Helm потребуется задать storage-class для приложения.

  1. Узнайте storage class, доступные в кластере Kubernetes:
1admin@k8s:~$ kubectl get sc 
2NAME            PROVISIONER            AGE
3hdd (default)   kubernetes.io/cinder   103d
4hdd-retain      kubernetes.io/cinder   103d
5ssd             kubernetes.io/cinder   103d
6ssd-retain      kubernetes.io/cinder   103d
  1. Установите Elasticsearch в кластер Kubernetes с заданными переменными:

    1helm install stable/elasticsearch \
    2      --name elastic \
    3      --set client.replicas=1 \
    4      --set master.replicas=1 \
    5      --set data.replicas=1 \
    6      --set master.persistence.storageClass=hdd \
    7      --set data.persistence.storageClass=hdd \
    8      --set master.podDisruptionBudget.minAvailable=1 \
    9      --set resources.requests.memory=4Gi \
    10      --set cluster.env.MINIMUM_MASTER_NODES=1 \
    11      --set cluster.env.RECOVER_AFTER_MASTER_NODES=1 \
    12      --set cluster.env.EXPECTED_MASTER_NODES=1 \
    13      --namespace kube-logging
    

    В результате будет установлен кластер Elasticsearch, состоящий из 1 мастер-ноды, 1 ноды хранения данных и 1 ноды клиента.

  2. Убедитесь, что все поды готовы к работе:

    kubectl get po -n kube-logging
    1NAME                                           READY   STATUS    RESTARTS   AGE
    2elastic-elasticsearch-client-c74598797-9m7pm   1/1     Running  
    3elastic-elasticsearch-data-0                   1/1     Running  
    4elastic-elasticsearch-master-0                 1/1     Running
  3. Узнайте название сервисов в kube-logging:

    kubectl get svc -n kube-logging
1NAME                              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
2elastic-elasticsearch-client      ClusterIP   10.233.8.213   <none>        9200/TCP   11m
3elastic-elasticsearch-discovery   ClusterIP   None           <none>        9300/TCP   11m

Сервис elastic-elasticsearch-client будет использоваться для связки с kibana и fluentd. Дашборд Kibana установим также с помощью helm, но пропишем название сервиса elastic-elasticsearch-client в переменные его helm-чарта.

  1. Скачайте переменные helm-чарта kibana для редактирования:
helm fetch --untar stable/kibana
  1. Перейдите в каталог kibana и отредактируйте файл values.yaml:
cd kibana/ && vim values.yaml
  1. Впишите в секцию elasticsearch hosts имя сервиса elastic-elasticsearch-client:
1files:
2   kibana.yml:
3     ## Default Kibana configuration from kibana-docker.
4     server.name: kibana
5     server.host: "0"
6     ## For kibana < 6.6, use elasticsearch.url instead
7     elasticsearch.hosts: http://elastic-elasticsearch-client:9200
  1. Установите Kibana с модифицированными параметрами:
1helm install stable/kibana \
2     --name kibana \
3     --namespace kube-logging \
4     -f values.yaml
  1. Убедитесь, что под стартовал, и пробросьте на свою локальную машину порт 5601 пода Kibana для доступа к дашборду:
kubectl get pod -n kube-logging

Впишите в следующую команду полное имя пода для kibana, полученное из вывода предыдущей команды, вместо kibana-pod_hash_id:

kubectl port-forward --namespace kube-logging kibana-pod_hash_id 5601:5601
  1. В адресной строке браузера укажите строку подключения к проброшенному дашборду Kibana:
localhost:5601

Установка агрегаторов логов fluentd в кластер kubernetes

Fluend в кластере Kubernetes умеет собирать не только логи подов, сервисов и хостов, но и метаданные, которые соотносятся с лейблами сущностей kubernetes.

Конфигурация fluentd содержит информацию об источниках для сбора логов, способах парсинга и фильтрации полезной информации, потребителях этой информации (в нашем случае такой потребитель — elasticsearch).

В конфигурационном файле fluentd в рамках элементов <source> указаны источники для сбора логов. В представленном ниже configmap указано, что fluentd будет собирать информацию из логов контейнеров приложений, из логов контейнеров самого kubernetes и системных логов нод kubernetes.

  1. Создайте configmap для fluentd со следующим содержанием:
1kind: ConfigMap
2apiVersion: v1
3data:
4  containers.input.conf: |-
5    <source>
6      @type tail
7      path /var/log/containers/*.log
8      pos_file /var/log/es-containers.log.pos
9      time_format %Y-%m-%dT%H:%M:%S.%NZ
10      tag kubernetes.*
11      read_from_head true
12      format multi_format
13      <pattern>
14        format json
15        time_key time
16        time_format %Y-%m-%dT%H:%M:%S.%NZ
17      </pattern>
18      <pattern>
19        format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/
20        time_format %Y-%m-%dT%H:%M:%S.%N%:z
21      </pattern>
22    </source>
23  system.input.conf: |-
24    <source>
25      @type tail
26      format /^time="(?<time>[^)]*)" level=(?<severity>[^ ]*) msg="(?<message>[^"]*)"( err="(?<error>[^"]*)")?( statusCode=($<status_code>\d+))?/
27      path /var/log/docker.log
28      pos_file /var/log/es-docker.log.pos
29      tag docker
30    </source>
31
32    <source>
33      @type tail
34      format none
35      path /var/log/etcd.log
36      pos_file /var/log/es-etcd.log.pos
37      tag etcd
38    </source>
39
40
41    <source>
42      @type tail
43      format multiline
44      multiline_flush_interval 5s
45      format_firstline /^\w\d{4}/
46      format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
47      time_format %m%d %H:%M:%S.%N
48      path /var/log/kubelet.log
49      pos_file /var/log/es-kubelet.log.pos
50      tag kubelet
51    </source>
52
53    <source>
54      @type tail
55      format multiline
56      multiline_flush_interval 5s
57      format_firstline /^\w\d{4}/
58      format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
59      time_format %m%d %H:%M:%S.%N
60      path /var/log/kube-proxy.log
61      pos_file /var/log/es-kube-proxy.log.pos
62      tag kube-proxy
63    </source>
64
65    <source>
66      @type tail
67      format multiline
68      multiline_flush_interval 5s
69      format_firstline /^\w\d{4}/
70      format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
71      time_format %m%d %H:%M:%S.%N
72      path /var/log/kube-apiserver.log
73      pos_file /var/log/es-kube-apiserver.log.pos
74      tag kube-apiserver
75    </source>
76
77
78    <source>
79      @type tail
80      format multiline
81      multiline_flush_interval 5s
82      format_firstline /^\w\d{4}/
83      format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
84      time_format %m%d %H:%M:%S.%N
85      path /var/log/kube-controller-manager.log
86      pos_file /var/log/es-kube-controller-manager.log.pos
87      tag kube-controller-manager
88    </source>
89
90    <source>
91      @type tail
92      format multiline
93      multiline_flush_interval 5s
94      format_firstline /^\w\d{4}/
95      format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
96      time_format %m%d %H:%M:%S.%N
97      path /var/log/kube-scheduler.log
98      pos_file /var/log/es-kube-scheduler.log.pos
99      tag kube-scheduler
100    </source>
101
102    <source>
103      @type tail
104      format multiline
105      multiline_flush_interval 5s
106      format_firstline /^\w\d{4}/
107      format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
108      time_format %m%d %H:%M:%S.%N
109      path /var/log/rescheduler.log
110      pos_file /var/log/es-rescheduler.log.pos
111      tag rescheduler
112    </source>
113
114    # Logs from systemd-journal for interesting services.
115    <source>
116      @type systemd
117      matches [{ "_SYSTEMD_UNIT": "docker.service" }]
118      pos_file /var/log/gcp-journald-docker.pos
119      read_from_head true
120      tag docker
121    </source>
122
123    <source>
124      @type systemd
125      matches [{ "_SYSTEMD_UNIT": "kubelet.service" }]
126      pos_file /var/log/gcp-journald-kubelet.pos
127      read_from_head true
128      tag kubelet
129    </source>
130
131    <source>
132      @type systemd
133      matches [{ "_SYSTEMD_UNIT": "node-problem-detector.service" }]
134      pos_file /var/log/gcp-journald-node-problem-detector.pos
135      read_from_head true
136      tag node-problem-detector
137    </source>
138  forward.input.conf: |-
139    # Takes the messages sent over TCP
140    <source>
141      @type forward
142    </source>
143
144    <match **>
145       @type elasticsearch
146       @log_level info
147       include_tag_key true
148       host elastic-elasticsearch-client
149       port 9200
150       logstash_format true
151       logstash_prefix fluentd
152       # Set the chunk limits
153       buffer_chunk_limit 2M
154       buffer_queue_limit 8
155       flush_interval 5s
156       # Never wait longer than 5 minutes between retries.
157       max_retry_wait 30
158       # Disable the limit on the number of retries (retry forever).
159       disable_retry_limit
160       # Use multiple threads for processing.
161       num_threads 2
162    </match>
163metadata:
164  name: fluentd-es-config-v0.1.1
165  namespace: kube-logging
166  labels:
167    addonmanager.kubernetes.io/mode: Reconcile
  1. В kubernetes примените configmap:

    kubectl apply -f fluentd-cm.yaml

Так как fluentd будет собирать информацию со всего кластера, ему потребуется доступ к ресурсам kubernetes. Для обеспечения этого доступа создайте для fluentd сервисный аккаунт, роль. Затем присвойте сервисному аккаунту роль.

  1. Для fluentd создайте файл sa-r-crb.yaml с описанием service account, role и rolebinding:

    1apiVersion: v1
    2kind: ServiceAccount
    3metadata:
    4   name: fluentd
    5   namespace: kube-logging
    6   labels:
    7     app: fluentd
    8---
    9apiVersion: rbac.authorization.k8s.io/v1
    10kind: ClusterRole
    11metadata:
    12  name: fluentd
    13  labels:
    14    app: fluentd
    15rules:
    16- apiGroups:
    17  - ""
    18  resources:
    19  - pods
    20  - namespaces
    21  verbs:
    22  - get
    23  - list
    24  - watch
    25---
    26kind: ClusterRoleBinding
    27apiVersion: rbac.authorization.k8s.io/v1
    28metadata:
    29  name: fluentd
    30roleRef:
    31  kind: ClusterRole
    32  name: fluentd
    33  apiGroup: rbac.authorization.k8s.io
    34subjects:
    35- kind: ServiceAccount
    36  name: fluentd
    37  namespace: kube-logging
  2. Задеплойте ресурсы в кластер:

kubectl apply -f sa-r-crb.yaml
1serviceaccount/fluentd created
2clusterrole.rbac.authorization.k8s.io/fluentd created
3clusterrolebinding.rbac.authorization.k8s.io/fluentd created

Внесите изменения в ограничение (constraint) для Gatekeeper. Эти изменения позволят читать логи с нод:

  1. Создайте файл fluent_patch.yaml в кластере и наполните его содержимым:

    1spec: 
    2match: 
    3    kinds: 
    4    - apiGroups: 
    5    - ""
    6    kinds: 
    7    - Pod
    8parameters: 
    9    allowedHostPaths: 
    10    - pathPrefix: /psp
    11        readOnly: true
    12    - pathPrefix: /var/log
    13        readOnly: true
    14    - pathPrefix: /var/log/containers
    15        readOnly: true
  2. Примените изменения, выполнив команду:

    kubectl patch k8spsphostfilesystem.constraints.gatekeeper.sh/psp-host-filesystem --patch-file fluent_patch.yaml --type merge

Установите fluentd. Поскольку fluentd необходимо установить на все ноды кластера, в качестве ресурса kubernetes выберите тип DaemonSet.

  1. Создайте манифест fluentd-daemonset.yaml со следующим содержанием (подходит для версии kubernetes ниже 1.22):
1apiVersion: apps/v1
2kind: DaemonSet
3metadata:
4  name: fluentd
5  namespace: kube-logging
6  labels:
7    app: fluentd
8spec:
9  selector:
10    matchLabels:
11      app: fluentd
12  template:
13    metadata:
14      labels:
15        app: fluentd
16    spec:
17      serviceAccount: fluentd
18      serviceAccountName: fluentd
19      tolerations:
20      - key: node-role.kubernetes.io/master
21        effect: NoSchedule
22      containers:
23      - name: fluentd
24        image: fluent/fluentd-kubernetes-daemonset:v1.4.2-debian-elasticsearch-1.1
25        env:
26          - name:  FLUENT_ELASTICSEARCH_HOST
27            value: "elastic-elasticsearch-client"
28          - name:  FLUENT_ELASTICSEARCH_PORT
29            value: "9200"
30          - name: FLUENT_ELASTICSEARCH_SCHEME
31            value: "http"
32          - name: FLUENTD_SYSTEMD_CONF
33            value: disable
34          - name: FLUENT_ELASTICSEARCH_SED_DISABLE
35            value: disable
36        resources:
37          limits:
38            memory: 512Mi
39          requests:
40            cpu: 100m
41            memory: 200Mi
42        volumeMounts:
43        - name: varlog
44          mountPath: /var/log
45        - name: varlibdockercontainers
46          mountPath: /var/lib/docker/containers
47          readOnly: true
48        - name: config-volume
49          mountPath: /fluentd/etc/conf.d
50      terminationGracePeriodSeconds: 3
51      volumes:
52      - name: varlog
53        hostPath:
54          path: /var/log
55      - name: varlibdockercontainers
56        hostPath:
57          path: /var/lib/docker/containers
58      - name: config-volume
59        configMap:
60            name: fluentd-es-config-v0.1.1
  1. Примените манифест в kubernetes:

    kubectl apply -f fluentd-daemonset.yaml

Доступ к логам через дашборд Kibana

Настройте индекс в UI Kibana:

  1. Перейдите на вкладку с дашбордом Kibana и выберите меню Management:

  2. Выберите вкладку Index Patterns, нажмите кнопку Create index pattern и введите название logstash-\*:

  3. Нажмите кнопку Next Step и выберите Create Index Pattern:

  1. Перейдите на вкладку Discover. Вы увидите логи из кластера Kubernetes: