Поиск по сайту:

Как настроить стек журналов Elasticsearch, Fluentd и Kibana (EFK) в Kubernetes


Введение

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

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

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

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

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

Предпосылки

Прежде чем приступить к работе с этим руководством, убедитесь, что у вас есть следующее:

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

    Инструмент командной строки kubectl, установленный на вашем локальном компьютере и настроенный для подключения к вашему кластеру. Подробнее об установке kubectl можно прочитать в официальной документации.

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

    Шаг 1 — Создание пространства имен

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

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

    1. kubectl get namespaces

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

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

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

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

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

    1. nano kube-logging.yaml

    В редакторе вставьте следующий объект пространства имен YAML:

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

    Затем сохраните и закройте файл.

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

    Создав объектный файл пространства имен kube-logging.yaml, создайте пространство имен с помощью kubectl create с флагом имени файла -f:

    1. kubectl create -f kube-logging.yaml

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

    Output
    namespace/kube-logging created

    Затем вы можете подтвердить, что пространство имен было успешно создано:

    1. kubectl get namespaces

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

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

    Теперь мы можем развернуть кластер Elasticsearch в этом изолированном пространстве имен журналов.

    Шаг 2 — Создание Elasticsearch StatefulSet

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

    В этом руководстве мы используем 3 модуля Elasticsearch, чтобы избежать проблемы «разделения ресурсов», которая возникает в высокодоступных многоузловых кластерах. На высоком уровне «разделение ресурсов» — это то, что возникает, когда один или несколько узлы не могут взаимодействовать с другими, и избирается несколько «разделенных» мастеров. При 3 узлах, если один из них временно отключается от кластера, два других узла могут выбрать нового мастера, и кластер может продолжать функционировать, пока последний узел пытается повторно присоединиться. Чтобы узнать больше, обратитесь к настройкам голосования.

    Создание безголового сервиса

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

    Откройте файл с именем elasticsearch_svc.yaml в своем любимом редакторе:

    1. nano elasticsearch_svc.yaml

    Вставьте следующий сервис Kubernetes 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, чтобы служба выбирала модули с меткой app: elasticsearch. Когда мы связываем наш Elasticsearch StatefulSet с этой службой, служба возвращает записи DNS A, которые указывают на модули Elasticsearch с меткой app: elasticsearch.

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

    Создайте сервис с помощью kubectl:

    1. kubectl create -f elasticsearch_svc.yaml

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

    Output
    service/elasticsearch created

    Наконец, еще раз проверьте, что служба была успешно создана с помощью kubectl get:

    kubectl get services --namespace=kube-logging
    

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

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

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

    Создание StatefulSet

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

    Откройте файл с именем elasticsearch_statefulset.yaml в своем любимом редакторе:

    1. 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 replicas (Pods) и устанавливаем для селектора matchLabels значение app: elasticseach, которое затем отражаем в .spec. раздел template.metadata. Поля .spec.selector.matchLabels и .spec.template.metadata.labels должны совпадать.

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

    . . .
        spec:
          containers:
          - name: elasticsearch
            image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0
            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.seed_hosts
                value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch"
              - name: cluster.initial_master_nodes
                value: "es-cluster-0,es-cluster-1,es-cluster-2"
              - name: ES_JAVA_OPTS
                value: "-Xms512m -Xmx512m"
    

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

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

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

    Наконец, мы устанавливаем некоторые переменные среды в контейнере:

    • cluster.name: имя кластера Elasticsearch, которое в этом руководстве — k8s-logs.
    • node.name: имя узла, которое мы установили в поле .metadata.name с помощью valueFrom. Это приведет к es-cluster-[0,1,2], в зависимости от назначенного порядкового номера узла.
    • discovery.seed_hosts: в этом поле задается список подходящих узлов в кластере, которые будут запускать процесс обнаружения узлов. В этом руководстве, благодаря безголовому сервису, который мы настроили ранее, наши поды имеют домены вида es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local , поэтому мы устанавливаем эту переменную соответствующим образом. Используя разрешение DNS локального пространства имен Kubernetes, мы можем сократить его до es-cluster-[0,1,2].elasticsearch. Чтобы узнать больше об обнаружении Elasticsearch, обратитесь к официальной документации Elasticsearch.
    • cluster.initial_master_nodes: в этом поле также указывается список узлов, которые могут участвовать в процессе выбора главного узла. Обратите внимание, что для этого поля вы должны идентифицировать узлы по их node.name, а не по их именам хостов.
    • 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 Containers, которые запускаются перед основным контейнером приложения elasticsearch. Каждый из этих Init-контейнеров выполняется до завершения в том порядке, в котором они определены. Чтобы узнать больше об Init Containers, обратитесь к официальной документации 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 для производственного использования также упоминается отключение подкачки по соображениям производительности. В зависимости от вашей установки 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
    

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

    Затем мы указываем его режим доступа как ReadWriteOnce, что означает, что он может быть смонтирован только для чтения и записи одним узлом. В этом руководстве мы определяем класс хранилища как do-block-storage, поскольку в демонстрационных целях мы используем кластер DigitalOcean Kubernetes. Вы должны изменить это значение в зависимости от того, где вы используете свой кластер Kubernetes. Чтобы узнать больше, обратитесь к документации Persistent Volume.

    Наконец, мы указываем, что каждый 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:7.2.0
            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.seed_hosts
                value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch"
              - name: cluster.initial_master_nodes
                value: "es-cluster-0,es-cluster-1,es-cluster-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
    

    Когда вы будете удовлетворены конфигурацией Elasticsearch, сохраните и закройте файл.

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

    1. kubectl create -f elasticsearch_statefulset.yaml

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

    Output
    statefulset.apps/es-cluster created

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

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

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

    Output
    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 перенаправление портов:

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

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

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

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

    Output
    { "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 для службы, чтобы сбалансировать нагрузку запросов между модулями развертывания.

    На этот раз мы создадим сервис и развертывание в одном файле. Откройте файл с именем kibana.yaml в своем любимом редакторе:

    1. 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:7.2.0
            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 для выбора целевых модулей службы.

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

    Мы используем образ docker.elastic.co/kibana/kibana:7.2.0. На этом этапе вы можете использовать собственное личное или общедоступное изображение Kibana.

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

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

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

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

    1. kubectl create -f kibana.yaml

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

    Output
    service/kibana created deployment.apps/kibana created

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

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

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

    Output
    deployment "kibana" successfully rolled out

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

    1. kubectl get pods --namespace=kube-logging
    Output
    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 Pod называется kibana-6c9fb4b5b7-plbg2.

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

    1. kubectl port-forward kibana-6c9fb4b5b7-plbg2 5601:5601 --namespace=kube-logging

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

    Output
    Forwarding from 127.0.0.1:5601 -> 5601 Forwarding from [::1]:5601 -> 5601

    Теперь в веб-браузере перейдите по следующему URL-адресу:

    http://localhost:5601
    

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

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

    Шаг 4 — Создание набора демонов Fluentd

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

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

    Помимо журналов контейнеров, агент Fluentd будет отслеживать журналы системных компонентов Kubernetes, такие как журналы kubelet, kube-proxy и Docker. Чтобы увидеть полный список источников, за которыми следит агент ведения журнала Fluentd, обратитесь к разделу «Ведение журнала на уровне узла» в официальной документации Kubernetes.

    Начните с открытия файла fluent.yaml в вашем любимом текстовом редакторе:

    1. nano fluentd.yaml

    Мы снова будем вставлять определения объектов Kubernetes блок за блоком, предоставляя контекст по мере продвижения. В этом руководстве мы используем Kuberentes Fluentd.

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

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

    Здесь мы создаем учетную запись службы с именем fluentd, которую модули Fluentd будут использовать для доступа к API Kubernetes. Мы создаем его в пространстве имен kube-logging и снова присваиваем ему метку app: fluentd. Чтобы узнать больше об учетных записях служб в Kubernetes, обратитесь к разделу «Настройка учетных записей служб для модулей» в официальной документации 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 в области кластера, таким как Nodes. Чтобы узнать больше об управлении доступом на основе ролей и ролях кластера, обратитесь к разделу Использование авторизации RBAC в официальной документации 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, который привязывает fluentd ClusterRole к сервисному аккаунту fluentd. Это предоставляет учетной записи службы fluentd разрешения, перечисленные в роли кластера 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:v1.4.2-debian-elasticsearch-1.1
            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: FLUENTD_SYSTEMD_CONF
                value: disable
    

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

    Затем мы определяем допуск NoSchedule, чтобы соответствовать эквивалентному заражению на главных узлах Kubernetes. Это гарантирует, что DaemonSet также будет развернут на мастер-серверах Kubernetes. Если вы не хотите запускать модуль Fluentd на своих главных узлах, удалите это допущение. Чтобы узнать больше о недочетах и допусках Kubernetes, ознакомьтесь с \Taints and Tolerations в официальной документации Kubernetes.

    Затем мы начинаем определять контейнер Pod, который мы называем fluentd.

    Мы используем репозиторий fluentd-kubernetes-daemonset на Github.

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

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

    Наконец, вставьте в следующий раздел:

    . . .
            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,1 ВЦП и 200 МБ памяти. Вы можете настроить эти ограничения ресурсов и запросы в зависимости от ожидаемого объема журнала и доступных ресурсов.

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

    Последним параметром, который мы определяем в этом блоке, является terminationGracePeriodSeconds, который дает Fluentd 30 секунд для корректного закрытия после получения сигнала SIGTERM. Через 30 секунд контейнерам отправляется сигнал SIGKILL. Значение по умолчанию для terminationGracePeriodSeconds равно 30 с, поэтому в большинстве случаев этот параметр можно не указывать. Чтобы узнать больше о корректном завершении рабочих нагрузок Kubernetes, ознакомьтесь с «Лучшими практиками 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:v1.4.2-debian-elasticsearch-1.1
            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: FLUENTD_SYSTEMD_CONF
                value: disable
            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:

    1. kubectl create -f fluentd.yaml

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

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

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

    1. kubectl get ds --namespace=kube-logging

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

    Output
    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.

    Нажмите «Обнаружить» в меню навигации слева:

    Вы должны увидеть следующее окно конфигурации:

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

    Затем вы попадете на следующую страницу:

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

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

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

    На данный момент вы успешно настроили и развернули стек EFK в своем кластере Kubernetes. Чтобы узнать, как использовать Kibana для анализа данных журнала, обратитесь к Руководству пользователя Kibana.

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

    Шаг 5 (необязательно) — Тестирование ведения журнала контейнера

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

    Начнем с создания пода. Откройте файл с именем counter.yaml в вашем любимом редакторе:

    1. nano counter.yaml

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

    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, последовательно печатая числа.

    Разверните модуль counter с помощью kubectl:

    1. kubectl create -f counter.yaml

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

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

    Затем вы должны увидеть список записей журнала для модуля counter:

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

    Заключение

    В этом руководстве мы показали, как установить и настроить Elasticsearch, Fluentd и Kibana в кластере Kubernetes. Мы использовали минимальную архитектуру ведения журнала, состоящую из одного модуля агента ведения журнала, работающего на каждом рабочем узле Kubernetes.

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

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

    Kubernetes также позволяет создавать более сложные архитектуры агентов ведения журналов, которые могут лучше подходить для вашего варианта использования. Чтобы узнать больше, обратитесь к разделу «Архитектура ведения журналов» в документации Kubernetes.