Установка стека EFK на Kubernetes

При запуске нескольких сервисов и приложений в кластере Kubernetes централизованный стек для логирования на уровне кластера может помочь вам быстро отсортировать и проанализировать большой объем данных из логов, создаваемых вашими подами. Одним из популярных решений для централизованного логирования является стек EFK – Elasticsearch, Fluentd и Kibana.

Elasticsearch – это распределенная и масштабируемая поисковая система, работающая в режиме реального времени, которая обеспечивает полнотекстовый и структурированный поиск, а также аналитику. Она обычно используется для индексации и поиска в больших объемах данных логов, но также может использоваться для поиска различных типов документов.

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

В этом мануале мы будем использовать Fluentd для сбора, преобразования и отправки логов в Elasticsearch. Fluentd – это популярный сборщик данных с открытым исходным кодом. На нодах Kubernetes он будет отслеживать логи контейнера, фильтровать и преобразовывать данные логов и передавать их в кластер Elasticsearch, где они будут проиндексированы и сохранены.

Мы начнем с настройки и запуска масштабируемого кластера Elasticsearch, а затем создадим Kubernetes сервис и развертывание Kibana. В конце мы настроим Fluentd как DaemonSet, чтобы он работал на каждой рабочей ноде Kubernetes.

Требования

  • Кластер Kubernetes 1.10+ с управлением доступом на основе ролей (RBAC).
  • Убедитесь, что в вашем кластере достаточно ресурсов для развертывания стека EFK, а если нет, то масштабируйте кластер, добавив рабочие ноды. Мы развернем кластер Elasticsearch из 3 подов (вы можете уменьшить до 1, если необходимо), а также один Kibana под. Каждая рабочая нода также будет запускать под Fluentd. Кластер в этом руководстве состоит из 3 рабочих нод и control plane (области управления).
  • Инструмент командной строки kubectl, установленный на локальную машину и подключенный к кластеру. Инструкции можно найти в официальной документации.

1: Создание пространства имен

Прежде чем мы развернем кластер Elasticsearch, мы создадим пространство имен, в которое установим все наши инструменты логирования. Kubernetes позволяет разделять объекты, работающие в кластере, используя абстракцию «виртуального кластера» — пространства имен. В этом мануале мы создадим пространство имен kube-logging, в которое установим компоненты стека EFK. Это пространство имен также позволит быстро очистить и удалить стек логирования без потери функций кластера Kubernetes.

Для начала исследуйте существующие пространства имен в вашем кластере, используя kubectl:

kubectl get namespaces

Вы должны увидеть три базовых пространства имен, предустановленные в кластере Kubernetes.

NAME          STATUS    AGE
default       Active    5m
kube-system   Active    5m
kube-public   Active    5m

Пространство имен default содержит объекты, которые создаются без указания пространства имен. Пространство имен kube-system содержит объекты, созданные и используемые системой Kubernetes, такие как kube-dns, kube-proxy и kubernetes-dashboard. Рекомендуется поддерживать чистоту этого пространства и не засорять его посторонними нагрузками.

Пространство kube-public – это еще одно автоматически созданное пространство имен, которое можно использовать для хранения объектов, которые должны быть доступны для чтения во всем кластере, даже для неаутентифицированных пользователей.

Чтобы создать пространство имен kube-logging, сначала откройте и отредактируйте файл kube-logging.yaml:

nano kube-logging.yaml

Вставьте в файл:

kind: Namespace
apiVersion: v1
metadata:
name: kube-logging

Сохраните и закройте файл.

Здесь нужно указать тип объекта Kubernetes как объект Namespace. Чтобы узнать больше об объектах Namespace, ознакомьтесь с разделом Пространства имен в официальной документации Kubernetes. Также нужно указать версию API Kubernetes, используемую для создания объекта (v1), и даем ему имя, kube-logging.

После того как вы создали файл пространства kube-logging.yaml, создайте само пространство имен, используя kubectl create с флагом –f, который указывает имя файла:

kubectl create -f kube-logging.yaml
namespace/kube-logging created

Убедитесь, что пространство имен создано:

kubectl get namespaces

Вы должны увидеть пространство имен kube-logging:

NAME           STATUS    AGE
default        Active    23m
kube-logging   Active    1m
kube-public    Active    23m
kube-system    Active    23m

2: Создание Elasticsearch StatefulSet

Теперь, когда у вас есть пространство имен для размещения стека, можно начать развертывать его компоненты. Сначала мы развернем 3-узловой кластер Elasticsearch.

В этом мануале мы используем 3 пода Elasticsearch, чтобы избежать проблемы расщепления мощностей, возникающей в высокодоступных многоузловых кластерах. Расщепление мощностей, или split-brain – это ситуация, в которой одна или несколько нод не могут связываться с другими, и при этом есть две или больше ведущих нод. Чтобы узнать больше, обратитесь к документации.

Одним из ключевых моментов является значение параметра Discover.zen.minimum_master_nodes: это должно быть значение N/2 + 1 (округление в случае дробных чисел), где N — количество мастер нод кластере Elasticsearch. Для 3-узлового кластера это значение 2. Таким образом, если одна нода временно отключается от кластера, две других могут выбрать нового мастера, и кластер может продолжить работу, пока последняя нода пытается восстановиться. Важно помнить об этом параметре при масштабировании кластера Elasticsearch.

Создание headless сервиса

Теперь создайте headless сервис Kubernetes по имени elasticsearch, который определит домен DNS для 3 подов. Headless сервис не выполняет балансировку нагрузки и не имеет статического IP-адреса. Чтобы узнать больше о headless сервисах, обратитесь к официальной документации Kubernetes.

Откройте файл elasticsearch_svc.yaml :

nano elasticsearch_svc.yaml

Вставьте в файл:

kind: Service
apiVersion: v1
metadata:
name: elasticsearch
namespace: kube-logging
labels:
app: elasticsearch
spec:
selector:
app: elasticsearch
clusterIP: None
ports:
- port: 9200
name: rest
- port: 9300
name: inter-node

Сохраните и закройте файл.

Мы определяем Service под названием elasticsearch в пространстве имен kube-logging и присваиваем ему метку app: elasticsearch. Затем мы устанавливаем для .spec.selector значение app: elasticsearch, чтобы сервис выбирал блоки с такой меткой. Когда мы связываем Elasticsearch StatefulSet с этим сервисом, сервис возвращает записи DNS A, которые указывают на блоки Elasticsearch с меткой app: elasticsearch.

Затем определяется параметр clusterIP: None, что делает сервис headless. В конце определяются порты 9200 и 9300, которые используются для взаимодействия с REST API и для связи между нодами соответственно.

Создайте сервис, используя kubectl:

kubectl create -f elasticsearch_svc.yaml
service/elasticsearch created

Еще раз убедитесь, что сервис успешно создан, с помощью kubectl get:

kubectl get services --namespace=kube-logging

Вы должны увидеть следующее:

NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)             AGE
elasticsearch   ClusterIP   None         <none>        9200/TCP,9300/TCP   26s

Теперь, когда headless сервис и стабильный домен .elasticsearch.kube-logging.svc.cluster.local готовы, можно приступить к созданию StatefulSet.

Создание StatefulSet

Kubernetes StatefulSet позволяет назначать стабильные идентификаторы для подов и предоставлять им постоянное хранилище. Elasticsearch оно необходимо для сохранения данных при изменении расписания пода и перезапуске. Чтобы узнать больше о рабочей нагрузке StatefulSet, обратитесь к странице Statefulsets в документации Kubernetes.

Откройте файл rubbersearch_statefulset.yaml в редакторе:

nano elasticsearch_statefulset.yaml

Мы будем перемещаться по определению объекта StatefulSet раздел за разделом, вставляя блоки в этот файл.

Начните с такого блока:

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: es-cluster
namespace: kube-logging
spec:
serviceName: elasticsearch
replicas: 3
selector:
matchLabels:
app: elasticsearch
template:
metadata:
labels:
app: elasticsearch

В этом блоке определяется StatefulSet по имени es-cluster в пространстве имен kube-logging. Затем он связывается с ранее созданным сервисом elasticsearch через поле serviceName. Благодаря этому каждый под в StatefulSet будет доступен по DNS-адресу: es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local, где [0,1,2] соответствует назначенному целому порядковому числу пода.

Мы указываем 3 реплики (Pods) и устанавливаем селектор matchLabels на app: elasticseach, который затем отражается в разделе .spec.template.metadata . Поля .spec.selector.matchLabels и .spec.template.metadata.labels  должны совпадать.

Теперь мы можем перейти к спецификации объекта. Вставьте следующий блок сразу под предыдущим блоком:

. . .
spec:
containers:
- name: elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.3
resources:
limits:
cpu: 1000m
requests:
cpu: 100m
ports:
- containerPort: 9200
name: rest
protocol: TCP
- containerPort: 9300
name: inter-node
protocol: TCP
volumeMounts:
- name: data
mountPath: /usr/share/elasticsearch/data
env:
- name: cluster.name
value: k8s-logs
- name: node.name
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: discovery.zen.ping.unicast.hosts
value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch"
- name: discovery.zen.minimum_master_nodes
value: "2"
- name: ES_JAVA_OPTS
value: "-Xms512m -Xmx512m"

Здесь определяются поды в StatefulSet. Контейнеры получают имя elasticsearch. Также здесь определяется образ docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.3. На этом этапе вы можете изменить этот тег образа и указать ваш собственный внутренний образ Elasticsearch или другую версию. Обратите внимание, что для целей данного мануала был протестирован только Elasticsearch 6.4.3.

Суффикс -oss гарантирует, что мы используем версию Elasticsearch с открытым исходным кодом. Если вы хотите использовать версию по умолчанию, содержащую X-Pack (которая включает в себя бесплатную лицензию), опустите суффикс -oss. Обратите внимание, что вам придется немного изменить дальнейшие действия в этом руководстве, чтобы учесть добавленную аутентификацию, предоставляемую X-Pack.

Затем в поле resources указывается, что контейнеру требуется как минимум 0,1 vCPU и он может использовать до 1 vCPU (это ограничивает использование ресурсов пода при обработке большого захвата или скачка нагрузки). Вы должны изменить эти значения в зависимости от ожидаемой нагрузки и доступных ресурсов. Чтобы узнать больше о ресурсах и ограничениях, обратитесь к официальной документации Kubernetes.

Далее открываются порты 9200 и 9300 (для REST API и связи между нодами соответственно). Мы указываем вызываемый том volumeMount под названием data, который будет монтировать PersistentVolume по имени data в контейнер по пути /usr/share/elasticsearch/data. Мы определим VolumeClaims для этого StatefulSet немного позже.

А в конце нужно определить некоторые переменные среды в контейнере:

  • cluster.name: название кластера Elasticsearch, в данном случае — k8s-logs.
  • node.name: имя ноды, в которой мы установили .metadata.name с помощью valueFrom. Это преобразуется в es-cluster- [0,1,2], в зависимости от присвоенного порядкового номера ноды.
  • discovery.zen.ping.unicast.hosts: это поле устанавливает метод определения для подключения нод друг к другу в кластере Elasticsearch. Мы используем метод unicast, который определяет статический список хостов кластера. В этом мануале благодаря headless сервису поды получили домены es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local, потому эту переменную нужно установить аналогичным образом. Используя локальное пространство имен Kubernetes DNS, мы можем сократить это до es-cluster-[0,1,2].elasticsearch. Чтобы узнать больше об определении Elasticsearch, читайте документацию.

discovery.zen.minimum_master_nodes: здесь мы устанавливаем значение (N/2) + 1, где N – количество серверов, отвечающих требованиям мастера в кластере. В этом мануале есть 3 ноды Elasticsearch, поэтому мы установим тут значение 2 (округляя до ближайшего целого числа). Чтобы узнать больше об этом параметре, обратитесь к официальной документации Elasticsearch

ES_JAVA_OPTS: здесь мы установим -Xms512m -Xmx512m, что говорит JVM использовать минимальный и максимальный размер динамической памяти 512 МБ. Вы должны настроить эти параметры в зависимости от потребностей и ресурсов вашего кластера. Чтобы узнать больше, обратитесь к документации.

Следующий блок выглядит так:

. . .
initContainers:
- name: fix-permissions
image: busybox
command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
securityContext:
privileged: true
volumeMounts:
- name: data
mountPath: /usr/share/elasticsearch/data
- name: increase-vm-max-map
image: busybox
command: ["sysctl", "-w", "vm.max_map_count=262144"]
securityContext:
privileged: true
- name: increase-fd-ulimit
image: busybox
command: ["sh", "-c", "ulimit -n 65536"]
securityContext:
privileged: true

В этом блоке мы определяем несколько контейнеров Init, которые запускаются перед основным контейнером приложения elasticsearch. Эти контейнеры запускаются до завершения в порядке их определения. Чтобы узнать больше о контейнерах Init, обратитесь к официальной документации Kubernetes.

Первый контейнер, fix-permissions, запускает команду chown, чтобы передать права собственности на каталог данных Elasticsearch 1000:1000 (UID пользователя Elasticsearch). По умолчанию Kubernetes монтирует каталог данных как root, что делает его недоступным для Elasticsearch. Чтобы узнать больше об этом, обратитесь к этому разделу документации Elasticsearch.

Второй контейнер, increase-vm-max-map, запускает команду, которая увеличивает ограничения операционной системы в mmap, значение которого по умолчанию может быть слишком низким, что приводит к ошибкам нехватки памяти. Чтобы узнать больше об этом, обратитесь к официальной документации Elasticsearch.

Следующий контейнер, increase-fd-ulimit, запускает команду ulimit для увеличения максимального числа дескрипторов открытых файлов. Чтобы узнать больше, ознакомьтесь с официальной документацией Elasticsearch.

Примечание: В заметках Elasticsearch Notes for Production также упоминается отключение свопа по соображениям производительности. В зависимости от установки или провайдера Kubernetes, он может быть уже отключен. Чтобы проверить это, выполните exec в работающем контейнере и запустите cat /proc/swaps для просмотра списка активных устройств подкачки. Если вы ничего не видите, своп отключен.

Теперь, когда основной контейнер приложения и контейнеры Init, которые запускаются перед ним для настройки ОС контейнера, определены, можно добавить последний фрагмент в файл определения объекта StatefulSet: volumeClaimTemplates.

Вставьте следующий блок volumeClaimTemplate:

. . .
volumeClaimTemplates:
- metadata:
name: data
labels:
app: elasticsearch
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: do-block-storage
resources:
requests:
storage: 100Gi

В этом блоке мы определяем StateCullSet VolumeClaimTemplates. Kubernetes будет использовать это для создания PersistentVolumes для модулей. В приведенном выше блоке мы назвали его data (это имя, на которое мы ссылаемся в ранее определенном volumeMounts) и присвоили ему ту же метку app: elasticsearch, что и нашему StatefulSet.

Затем определяется режим доступа –ReadWriteOnce, что означает, что он может монтироваться только для чтения-записи одной нодой. Укажите свой класс хранилища в зависимости от того, где находится кластер Kubernetes. Чтобы узнать больше, обратитесь к документации.

В конце указывается, что каждый PersistentVolume должен быть размером 100 ГБ. Измените это значение в зависимости от ваших потребностей.

Полная спецификация StatefulSet должна выглядеть примерно так:

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: es-cluster
namespace: kube-logging
spec:
serviceName: elasticsearch
replicas: 3
selector:
matchLabels:
app: elasticsearch
template:
metadata:
labels:
app: elasticsearch
spec:
containers:
- name: elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.3
resources:
limits:
cpu: 1000m
requests:
cpu: 100m
ports:
- containerPort: 9200
name: rest
protocol: TCP
- containerPort: 9300
name: inter-node
protocol: TCP
volumeMounts:
- name: data
mountPath: /usr/share/elasticsearch/data
env:
- name: cluster.name
value: k8s-logs
- name: node.name
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: discovery.zen.ping.unicast.hosts
value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch"
- name: discovery.zen.minimum_master_nodes
value: "2"
- name: ES_JAVA_OPTS
value: "-Xms512m -Xmx512m"
initContainers:
- name: fix-permissions
image: busybox
command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
securityContext:
privileged: true
volumeMounts:
- name: data
mountPath: /usr/share/elasticsearch/data
- name: increase-vm-max-map
image: busybox
command: ["sysctl", "-w", "vm.max_map_count=262144"]
securityContext:
privileged: true
- name: increase-fd-ulimit
image: busybox
command: ["sh", "-c", "ulimit -n 65536"]
securityContext:
privileged: true
volumeClaimTemplates:
- metadata:
name: data
labels:
app: elasticsearch
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: do-block-storage
resources:
requests:
storage: 100Gi

Сохраните и закройте файл.

Теперь разверните StatefulSet с помощью kubectl:

kubectl create -f elasticsearch_statefulset.yaml

Вы должны увидеть следующий вывод:

statefulset.apps/es-cluster created

Вы можете отслеживать развертывание StatefulSet, используя kubectl rollout status:

kubectl rollout status sts/es-cluster --namespace=kube-logging

По мере развертывания кластера вы должны увидеть следующий вывод:

Waiting for 3 pods to be ready...
Waiting for 2 pods to be ready...
Waiting for 1 pods to be ready...
partitioned roll out complete: 3 new pods have been updated...

После развертывания всех подов вы можете убедиться, что кластер Elasticsearch работает правильно, выполнив запрос к REST API.

Для этого сначала перенаправьте локальный порт 9200 на порт 9200 на одной из нод Elasticsearch (es-cluster-0), используя kubectl port-forward:

kubectl port-forward es-cluster-0 9200:9200 --namespace=kube-logging

В отдельном терминале отправьте запрос curl в REST API:

curl http://localhost:9200/_cluster/state?pretty

Вы должны получить такой вывод:

{
"cluster_name" : "k8s-logs",
"compressed_size_in_bytes" : 348,
"cluster_uuid" : "QD06dK7CQgids-GQZooNVw",
"version" : 3,
"state_uuid" : "mjNIWXAzQVuxNNOQ7xR-qg",
"master_node" : "IdM5B7cUQWqFgIHXBp0JDg",
"blocks" : { },
"nodes" : {
"u7DoTpMmSCixOoictzHItA" : {
"name" : "es-cluster-1",
"ephemeral_id" : "ZlBflnXKRMC4RvEACHIVdg",
"transport_address" : "10.244.8.2:9300",
"attributes" : { }
},
"IdM5B7cUQWqFgIHXBp0JDg" : {
"name" : "es-cluster-0",
"ephemeral_id" : "JTk1FDdFQuWbSFAtBxdxAQ",
"transport_address" : "10.244.44.3:9300",
"attributes" : { }
},
"R8E7xcSUSbGbgrhAdyAKmQ" : {
"name" : "es-cluster-2",
"ephemeral_id" : "9wv6ke71Qqy9vk2LgJTqaA",
"transport_address" : "10.244.40.4:9300",
"attributes" : { }
}
},
...

Это значит, что кластер Elasticsearch по имени k8s-logs с тремя нодами (es-cluster-0, es-cluster-1 и es-cluster-2) успешно создан. Текущим мастером является es-cluster-0.

Теперь, когда кластер Elasticsearch запущен и работает, можно перейти к настройке интерфейса Kibana.

3: Создание развертывания и сервиса Kibana

Чтобы запустить Kibana в Kubernetes, мы создадим сервис и развертывание kibana, состоящее из одной реплики пода. Вы можете масштабировать количество реплик в зависимости от ваших потребностей и при желании указать тип сервиса LoadBalancer для балансировки запросов между подами развертывания.

В этот раз сервис и развертывание можно создать в рамках одного файла.

nano kibana.yaml

Вставьте в файл следующее:

apiVersion: v1
kind: Service
metadata:
name: kibana
namespace: kube-logging
labels:
app: kibana
spec:
ports:
- port: 5601
selector:
app: kibana
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kibana
namespace: kube-logging
labels:
app: kibana
spec:
replicas: 1
selector:
matchLabels:
app: kibana
template:
metadata:
labels:
app: kibana
spec:
containers:
- name: kibana
image: docker.elastic.co/kibana/kibana-oss:6.4.3
resources:
limits:
cpu: 1000m
requests:
cpu: 100m
env:
- name: ELASTICSEARCH_URL
value: http://elasticsearch:9200
ports:
- containerPort: 5601

Сохраните и закройте файл.

В этой спецификации мы определили сервис под названием kibana в пространстве имен kube-logging и присвоили ему метку app: kibana.

Мы также указали, что он должен быть доступен по порту 5601, и используем метку app: kibana для выбора целевых подов сервиса.

В спецификации развертывания мы определяем развертывание под названием kibana и указываем, что нам нужна одна реплика пода.

Далее используется образ docker.elastic.co/kibana/kibana-oss:6.4.3. На этом этапе вы можете указать другой образ Kibana. Мы снова используем суффикс -oss, чтобы указать, что нам нужна версия с открытым исходным кодом.

Под должен иметь минимум 0,1 и максимум 1 vCPU. Вы можете изменить эти параметры в зависимости от ожидаемой нагрузки и доступных ресурсов.

Далее мы используем переменную среды ELASTICSEARCH_URL, чтобы определить конечную точку и порт кластера Elasticsearch. Используя Kubernetes DNS, эта конечная точка соответствует имени сервиса elasticsearch. Этот домен будет преобразован в список IP-адресов для 3 подов Elasticsearch. Чтобы узнать больше о Kubernetes DNS, обратитесь к этому разделу.

Потом мы указываем порт контейнера Kibana – 5601, куда сервис kibana будет направлять запросы.

Если вы довольны конфигурацией Kibana, вы можете развернуть сервис и развертывание, используя kubectl:

kubectl create -f kibana.yaml

Вы должны увидеть следующий вывод:

service/kibana created
deployment.apps/kibana created

Вы можете проверить успешность развертывания, выполнив следующую команду:

kubectl rollout status deployment/kibana --namespace=kube-logging

Вы увидите следующий вывод:

deployment "kibana" successfully rolled out

Чтобы получить доступ к интерфейсу Kibana, можно еще раз перенаправить локальный порт на ноду Kubernetes, где работает Kibana. Получите детали пода Kibana с помощью kubectl get:

kubectl get pods --namespace=kube-logging
NAME                      READY     STATUS    RESTARTS   AGE
es-cluster-0              1/1       Running   0          55m
es-cluster-1              1/1       Running   0          54m
es-cluster-2              1/1       Running   0          54m
kibana-6c9fb4b5b7-plbg2   1/1       Running   0          4m27s

Здесь мы видим, что под Kibana называется kibana-6c9fb4b5b7-plbg2.

Перенаправьте локальный порт 5601 на порт 5601 на этом поде:

kubectl port-forward kibana-6c9fb4b5b7-plbg2 5601:5601 --namespace=kube-logging
Forwarding from 127.0.0.1:5601 -> 5601
Forwarding from [::1]:5601 -> 5601

Затем в браузере откройте:

http://localhost:5601

Если вы видите приветственную страницу Kibana, вы успешно развернули Kibana в своем кластере Kubernetes.

Теперь вы можете перейти к развертыванию последнего компонента стека EFK: сборщика логов Fluentd.

4: Создание DaemonSet для Fluentd

Теперь мы настроим Fluentd как DaemonSet (это тип рабочей нагрузки Kubernetes, который запускает копию данного пода на каждой ноде в кластере Kubernetes). Используя этот контроллер DaemonSet, мы развернем под агента логирования Fluentd на каждой ноде кластера. Чтобы узнать больше об этой архитектуре логирования, обратитесь к этому разделу документации.

В Kubernetes контейнерные приложения, которые регистрируют данные в stdout и stderr, перенаправляют свои потоки логов в файлы JSON на нодах. Под Fluentd будет отслеживать эти файлы, фильтровать события логов, преобразовывать данные и отправлять их на бэкэнд Elasticsearch, который мы развернули в разделе 2.

В дополнение к логам контейнеров агент Fluentd будет отслеживать логи системных компонентов Kubernetes (kubelet, kube-proxy и Docker). Чтобы увидеть полный список источников, выбранных агентом Fluentd, обратитесь к конфигурационному файлу kubernetes.conf. Больше о логировании в кластерах Kubernetes можно узнать в официальной документации Kubernetes.

Для начала откройте файл fluentd.yaml в текстовом редакторе:

nano fluentd.yaml

Добавим в него определения объектов Kubernetes блок за блоком. В этом руководстве мы используем спецификацию Fluentd DaemonSet, предоставленную сопроводителями Fluentd. Еще один их полезный ресурс – это Kubernetes Logging.

Сначала вставьте следующее определение ServiceAccount:

apiVersion: v1
kind: ServiceAccount
metadata:
name: fluentd
namespace: kube-logging
labels:
app: fluentd

Это создаст Service Account под названием fluentd, который под Fluentd будет использовать для доступа к Kubernetes API. Он создается в пространстве имен kube-logging и получает метку app: fluentd. Больше информации можно найти в документации Kubernetes.

Затем вставьте следующий блок ClusterRole.

. . .
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: fluentd
labels:
app: fluentd
rules:
- apiGroups:
- ""
resources:
- pods
- namespaces
verbs:
- get
- list
- watch

Этот блок ClusterRole по имени fluentd получает права get, list и watch для объектов pods и namespaces. ClusterRoles позволяет предоставить доступ к ресурсам Kubernetes, таким как ноды. Чтобы узнать больше, обратитесь к официальной документации Kubernetes.

Теперь вставьте следующий блок ClusterRoleBinding:

. . .
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: fluentd
roleRef:
kind: ClusterRole
name: fluentd
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: fluentd
namespace: kube-logging

Этот блок определяет ClusterRoleBinding под названием fluentd, который привязывает ClusterRole fluentd к сервис-аккаунту fluentd. Это передает последнему права, перечисленные в ClusterRole fluentd.

Теперь добавьте спецификацию DaemonSet:

. . .
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
namespace: kube-logging
labels:
app: fluentd

Это определяет DaemonSet по имени fluentd в пространстве имен kube-logging и присваивает ему метку app: fluentd.

Затем добавьте такой раздел:

. . .
spec:
selector:
matchLabels:
app: fluentd
template:
metadata:
labels:
app: fluentd
spec:
serviceAccount: fluentd
serviceAccountName: fluentd
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: fluentd
image: fluent/fluentd-kubernetes-daemonset:v0.12-debian-elasticsearch
env:
- name:  FLUENT_ELASTICSEARCH_HOST
value: "elasticsearch.kube-logging.svc.cluster.local"
- name:  FLUENT_ELASTICSEARCH_PORT
value: "9200"
- name: FLUENT_ELASTICSEARCH_SCHEME
value: "http"
- name: FLUENT_UID
value: "0"

Мы используем метку app: fluentd из .metadata.labels, а затем присваиваем DaemonSet сервис-аккаунту fluentd. Также мы выбрали app: fluentd в качестве пода, управляемого этим DaemonSet.

Далее определяется параметр NoSchedule. Он развертывает DaemonSet среди мастеров Kubernetes. Если вы не хотите запускать модуль Fluentd на своих мастер-нодах, удалите этот параметр. Больше можно узнать в документации Kubernetes.

Далее нужно определить контейнер пода fluentd.

Для этого используется официальный образ Debian v0.12, предоставленный сопроводителями Fluentd. Если вы хотите использовать другой образ или версию Fluentd, измените тег образа в спецификации контейнера. Dockerfile и содержимое этого образа доступны в репозитории kubernetes-daemonset на Gitub.

Далее мы настроим Fluentd, используя переменные среды:

  • FLUENT_ELASTICSEARCH_HOST: здесь нужно указать адрес headless сервиса Elasticsearch, определенный ранее: elasticsearch.kube-logging.svc.cluster.local. Это преобразует список IP-адресов для 3 подов Elasticsearch. Фактически хост Elasticsearch, скорее всего, будет первым IP-адресом в этом списке. Чтобы распределить логи по кластеру, нужно изменить конфигурацию плагина вывода Elasticsearch. Чтобы узнать больше об этом плагине, обратитесь к документации.
  • FLUENT_ELASTICSEARCH_PORT: здесь нужно указать порт Elasticsearch, который мы указали ранее, 9200.
  • FLUENT_ELASTICSEARCH_SCHEME: установите http.
  • FLUENT_UID: значение 0 (суперпользователь) позволяет Fluentd получить доступ к файлам /var/log.

Теперь добавьте такой блок:

. . .
resources:
limits:
memory: 512Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
terminationGracePeriodSeconds: 30
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers

Здесь определяются лимиты в 512 Мб памяти для пода FluentD. Также этот блок определяет минимальные ресурсы пода – 0.1vCPU и 200 Мб памяти. Вы можете настроить ограничения ресурсов и запросы в зависимости от ожидаемого объема лога и доступных ресурсов.

Затем /var/log и /var/lib/docker/containers монтируются в контейнер, используя volumeMounts varlog и varlibdockercontainers. Эти тома определяются в конце блока.

Последний параметр в этом блоке – это TerminationGracePeriodSeconds, который дает Fluentd 30 секунд для корректного отключения при получении сигнала SIGTERM. Через 30 секунд на контейнеры подается сигнал SIGKILL. Значение по умолчанию для TerminationGracePeriodSeconds составляет 30 секунд, поэтому в большинстве случаев этот параметр можно не указывать. Чтобы узнать больше о корректном отключении Kubernetes, ознакомьтесь с рекомендациями Google.

Спецификация Fluentd в результате выглядит так:

apiVersion: v1
kind: ServiceAccount
metadata:
name: fluentd
namespace: kube-logging
labels:
app: fluentd
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: fluentd
labels:
app: fluentd
rules:
- apiGroups:
- ""
resources:
- pods
- namespaces
verbs:
- get
- list
- watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: fluentd
roleRef:
kind: ClusterRole
name: fluentd
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: fluentd
namespace: kube-logging
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
namespace: kube-logging
labels:
app: fluentd
spec:
selector:
matchLabels:
app: fluentd
template:
metadata:
labels:
app: fluentd
spec:
serviceAccount: fluentd
serviceAccountName: fluentd
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: fluentd
image: fluent/fluentd-kubernetes-daemonset:v0.12-debian-elasticsearch
env:
- name:  FLUENT_ELASTICSEARCH_HOST
value: "elasticsearch.kube-logging.svc.cluster.local"
- name:  FLUENT_ELASTICSEARCH_PORT
value: "9200"
- name: FLUENT_ELASTICSEARCH_SCHEME
value: "http"
- name: FLUENT_UID
value: "0"
resources:
limits:
memory: 512Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
terminationGracePeriodSeconds: 30
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers

После завершения настройки Fluentd DaemonSet сохраните и закройте файл.

Теперь разверните DaemonSet с помощью kubectl:

kubectl create -f fluentd.yaml

Вы должны увидеть следующий вывод:

serviceaccount/fluentd created
clusterrole.rbac.authorization.k8s.io/fluentd created
clusterrolebinding.rbac.authorization.k8s.io/fluentd created
daemonset.extensions/fluentd created

Убедитесь, что DaemonSet успешно развернут.

kubectl get ds --namespace=kube-logging

Вы увидите:

NAME      DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
fluentd   3         3         3         3            3           <none>          58s

Это значит, что сейчас работают 3 пода fluentd, а это соответствует количеству нод в кластере Kubernetes.

Теперь можно проверить Kibana, чтобы убедиться, что данные логов правильно собираются и отправляются в Elasticsearch.

Оставьте kubectl port-forward открытым и перейдите по адресу:

http://localhost:5601

Нажмите Discover в меню навигации слева. На экране появится страница создания индекса.

Она позволяет определить индексы Elasticsearch, которые вы хотите изучать в Kibana. Чтобы узнать больше, обратитесь к документации Kibana. Сейчас мы просто используем шаблон logstash- * для захвата всех данных логов в кластере Elasticsearch. Введите logstash- * в текстовое поле и нажмите Next step.

Вы попадете на страницу настройки индекса.

Она позволяет определить, какое поле Kibana будет использовать для фильтрации данных лога по времени. В выпадающем списке выберите поле @timestamp и нажмите Create index pattern.

Теперь нажмите Discover в меню навигации слева.

Вы должны увидеть гистограмму и некоторые последние записи в логе.

Теперь вы знаете, что развернутый стек EFK в Kubernetes работает правильно.

5: Тестирование логирования контейнеров (опционально)

Чтобы продемонстрировать базовый вариант использования Kibana для изучения последних логов пода, мы развернем под простого счетчика, который последовательно передает числа в стандартный вывод.

Сначала создайте под. Откройте файл counter.yaml в редакторе:

nano counter.yaml

Затем вставьте следующие спецификации пода:

apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox
args: [/bin/sh, -c,
'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']

Сохраните и закройте файл.

Это простейший под counter, который запускает цикл while, выводя числа по порядку.

Разверните этот под.

kubectl create -f counter.yaml

Когда под будет развернут, вернитесь в панель управления Kibana.

На странице Discover в строке поиска введите kubernetes.pod_name:counter. Это фильтрует данные логов для подов с именем counter.

Далее будет список записей лога для пода counter.

Вы можете кликнуть любую из записей лога, чтобы увидеть дополнительные метаданные (имя контейнера, ноду Kubernetes, пространство имен и т.д.).

Заключение

Перед развертыванием этого стека централизованного логирования в производственном кластере Kubernetes лучше всего распределить и ограничить ресурсы. Вы также можете использовать образ с поддержкой X-Pack со встроенным мониторингом и безопасностью.

Архитектура логирования, которую мы здесь использовали, состоит из 3 подов Elasticsearch, одного пода Kibana (без балансировки нагрузки) и набора подов Fluentd, развернутых как DaemonSet. Вы можете масштабировать эту настройку в зависимости от вашей среды производства. Чтобы узнать больше о масштабировании стека Elasticsearch и Kibana, обратитесь к этому мануалу.

Kubernetes также поддерживает более сложные архитектуры агентов логирования, которые могут лучше подойти в вашем случае. Больше информации можно найти в документации Kubernetes.

Tags: , , , ,