Как настроить централизованное логирование из Kubernetes с помощью Fluentd
Писать логи в контейнеры неудобно, потому что:
- данные, генерируемые в контейнеризированных приложениях, существуют, пока существует контейнер. При перезагрузке 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.
- Создайте namespace
kube-logging
:
kubectl create ns kube-logging
При установке из Helm потребуется задать storage-class для приложения.
- Узнайте
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
-
Установите 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 ноды клиента.
-
Убедитесь, что все поды готовы к работе:
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
-
Узнайте название сервисов в 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-clien
t в переменные его helm-чарта.
- Скачайте переменные helm-чарта kibana для редактирования:
helm fetch --untar stable/kibana
- Перейдите в каталог kibana и отредактируйте файл
values.yaml
:
cd kibana/ && vim values.yaml
- Впишите в секцию
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
- Установите Kibana с модифицированными параметрами:
1helm install stable/kibana \ 2 --name kibana \ 3 --namespace kube-logging \ 4 -f values.yaml
- Убедитесь, что под стартовал, и пробросьте на свою локальную машину порт 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
- В адресной строке браузера укажите строку подключения к проброшенному дашборду Kibana:
localhost:5601

Fluend в кластере Kubernetes умеет собирать не только логи подов, сервисов и хостов, но и метаданные, которые соотносятся с лейблами сущностей kubernetes.
Конфигурация fluentd содержит информацию об источниках для сбора логов, способах парсинга и фильтрации полезной информации, потребителях этой информации (в нашем случае такой потребитель — elasticsearch).
В конфигурационном файле fluentd в рамках элементов <source>
указаны источники для сбора логов. В представленном ниже configmap указано, что fluentd будет собирать информацию из логов контейнеров приложений, из логов контейнеров самого kubernetes и системных логов нод kubernetes.
- Создайте 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
-
В kubernetes примените configmap:
kubectl apply -f fluentd-cm.yaml
Так как fluentd будет собирать информацию со всего кластера, ему потребуется доступ к ресурсам kubernetes. Для обеспечения этого доступа создайте для fluentd сервисный аккаунт, роль. Затем присвойте сервисному аккаунту роль.
-
Для 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
-
Задеплойте ресурсы в кластер:
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. Эти изменения позволят читать логи с нод:
-
Создайте файл
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
-
Примените изменения, выполнив команду:
kubectl patch k8spsphostfilesystem.constraints.gatekeeper.sh/psp-host-filesystem --patch-file fluent_patch.yaml --type merge
Установите fluentd. Поскольку fluentd необходимо установить на все ноды кластера, в качестве ресурса kubernetes выберите тип DaemonSet
.
- Создайте манифест
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
-
Примените манифест в kubernetes:
kubectl apply -f fluentd-daemonset.yaml
Настройте индекс в UI Kibana:
-
Перейдите на вкладку с дашбордом Kibana и выберите меню Management:
-
Выберите вкладку Index Patterns, нажмите кнопку Create index pattern и введите название
logstash-\*
: -
Нажмите кнопку Next Step и выберите Create Index Pattern:

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