Постепенно эволюционируя, каждая организация переходит от ручного grep логов к более современным инструментам для сбора, анализа логов. Если вы работаете с kubernetes, где приложение может масштабироваться горизонтально и вертикально, вас просто вынуждают отказаться от старой парадигмы сбора логов. В текущее время существует большое количество вариантов систем централизованного логирования, причем каждый выбирает наиболее приемлемый вариант для себя. В данной статье пойдет речь о наиболее популярном и зарекомендовавшем себя стэке Elasticsearch + Kibana + Fluentd в связке с плагином OpenDistro Security. Данный стэк полностью open source, что придает ему особую популярность.

Проблематика

Нам необходимо было наладить сборку логов с кластера kubernetes.

Требования:

  • Использование только открытых решений

  • Сделать очистку логов.

  • Аутентификация на базе LDAP

Данный материал предполагает:

  • Имеется установленный kuberenetes 1.18 или выше (ниже версию не проверяли)

  • Установленный пакетный менеджер helm версии 3

Немного о helm chart

Наиболее популярным способом установки приложения в kubernetes является helm. Helm это пакетный менеджер, с помощью которого можно подготовить набор компонентов для установки и связать их вместe, дополнив необходимыми метриками и конфигурацией.

В своей практике мы используем helm chart от компании bitnami(подразделение vmware)

Преимущества такого решения:

  • собраны open source продукты.

  • стандарты по разработке чатов и докер образов

  • красивая документация

  • проект живой и активно поддерживается сообществом

Выбор стека

Во многом выбор стека технологий определило время. С большой долей вероятностью два года назад мы бы деплоили ELK стек вместо EFK и не использовали helm chart.

Fluentd часто упоминается в документации, широко распространен, имеет большое количество плагинов, на все случаи жизни. К тому же у нас есть человек, который после обучение в rebrain и очень хотел внедрить fluentd.

Elasticsearch и kibana поставляются под открытой лицензией, однако плагины безопасности и другие "вкусности" идут под иной лицензией. Компания Amazon выпустила набор плагинов Open Distro, которые покрывают оставшийся функционал под открытой лицензией.

Схема выглядит примерно так

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

Минимальный деплой EFK стека (без Security)

Сборка EFK стека была произведена по статье Collect and Analyze Log Data for a Kubernetes Cluster with Bitnami's Elasticsearch, Fluentd and Kibana Charts. Компоменты упакованы в отдельный чат. Исходники можно взять здесь и произвести командой

helm dependency update
helm upgrade --install efk . -f values-minimal.yaml

Из исходников values-minimal.yaml

elasticsearch:
  volumePermissions:
    enabled: true

kibana:
  volumePermissions:
    enabled: true
  elasticsearch:
    hosts:
    - "efk-elasticsearch-coordinating-only"
    port: 9200
# Пропишите свой хост, если используете ингресс
  ingress:
    enabled: true
    hostname: kibana.local
# Либо 
  service:
    type: NodePort
    port: 30010
fluentd:
  aggregator:
    enabled: true
    configMap: elasticsearch-output
    extraEnv:
    - name: ELASTICSEARCH_HOST
      value: "efk-elasticsearch-coordinating-only"
    - name: ELASTICSEARCH_PORT
      value: "9200"
  forwarder:
#    Чтение логов с диска /var/log/containers/* отключено
    enabled: false
    configMap: apache-log-parser
    extraEnv:
    - name: FLUENTD_DAEMON_USER
      value: root
    - name: FLUENTD_DAEMON_GROUP
      value: root

Кибана будет доступна по адресу http://$(minikube ip):30010/, либо http://kibana.local.

Как видим компонент fluentd forwarder не включен. Перед включением парсинга, я рекомендовал бы настроить на определенные логи, иначе еластику может быть послано слишком большое количество логов. Правила для парсинга описаны в файле apache-log-parser.yaml.

Как отправить логи в EFK

Существует много способов, для начала предлагаем либо включить fluentd forwarder, либо воспользоваться простейшим приложением на python. Ниже пример для bare metal. Исправьте FLUENT_HOST FLUENT_PORT на ваши значения.

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld
  labels:
    app: helloworld
spec:
  replicas: 1
  template:
    metadata:
      name: helloworld
      labels:
        app: helloworld
    spec:
      containers:
        - name: helloworld
          image: sergbs/django-hello-world:1
          imagePullPolicy: Always
          ports:
            - containerPort: 8000
          env:
            - name: FLUENT_HOST
              value: "efk-fluentd-headless"
            - name: FLUENT_PORT
              value: "24224"
  selector:
    matchLabels:
      app: helloworld
---
apiVersion: v1
kind: Service
metadata:
  name: helloworld
spec:
  selector:
    app: helloworld
  ports:
    - port: 8000
      nodePort: 30011
  type: NodePort
EOF

По ссылке http://$(minikube ip):30011/ Будет выведено "Hello, world!" И лог уйдет в elastic

Пример

Включить fluentd forwarder, он создаст daemon set, т.е. запустится на каждой ноде вашего кубернетеса и будет читать логи docker container-ов.

Добавить в ваш докер контейнер драйвер fluentd, тем более можно добавлять более одного драйвера

Добавить в ваше приложение библиотеку, которая будет напрямую логировать. Пример приложения на python. Используйте его, как отладочное средство при установке EFK.

И как показывает практика, логировать напрямую эффективный, но далеко не самый надежный способ. Даже если вы логируете сразу в fluentd и в консоль. В случае потери конекта, во fluentd часть логов просто не смогут попасть и будут потеряны для него навсегда. Поэтому наиболее надежный способ, это считывать логи с диска для отправки в EFK.

Очистка логов

Для очистки логов используется curator. Его можно включить, добавив в values-minimal.yaml :

elasticsearch:
  curator:
    enabled: true

По умолчанию его конфигурация уже предусматривает удаление индекса старше 90 дней, это можно увидеть в конфигурации внутри подчата efk/charts/elasticsearch-12.6.1.tgz!/elasticsearch/values.yaml

configMaps:
  # Delete indices older than 90 days
  action_file_yml: |-
      ... 
	unit: days
	unit_count: 90

Security

Как обычно security доставляет основную боль при настройке и использовании. Но если ваша организация чуть подросла, это необходимый шаг. Стандартом де факто при настройке безопасности является интеграция с LDAP. Официальные плагины от еластика выходят не под открытой лицензией, поэтому приходится использовать плагин Open Distro. Далее продемонстрируем, как его можно запустить.

Сборка elasticsearch c плагином opendistro

Вот проект в котором собирали docker images.

Для установки плагина, необходимо, чтобы версия elasticsearch соответствовала версии плагина.

В quickstart плагина рекомендуется установить install_demo_configuration.sh с демо сертификатами.

FROM bitnami/elasticsearch:7.10.0

RUN elasticsearch-plugin install -b https://d3g5vo6xdbdb9a.cloudfront.net/downloads/elasticsearch-plugins/opendistro-security/opendistro_security-1.12.0.0.zip
RUN touch /opt/bitnami/elasticsearch/config/elasticsearch.yml

USER root
RUN /bin/bash /opt/bitnami/elasticsearch/plugins/opendistro_security/tools/install_demo_configuration.sh -y -i
RUN mv /opt/bitnami/elasticsearch/config/elasticsearch.yml /opt/bitnami/elasticsearch/config/my_elasticsearch.yml
COPY my_elasticsearch.yml /opt/bitnami/elasticsearch/config/my_elasticsearch.yml
USER 1001

Есть небольшая магия, ввиду, того что плагин дополняет elasticsearch.yml, а контейнеры bitnami только при старте генерируют этот файл. Дополнительные же настройки они просят передавать через my_elasticsearch.yml

В my_elasticsearch.yml мы изменили настройку, это позволит нам обращаться к рестам elasticsearch по http.

# turn off REST layer 
tlsopendistro_security.ssl.http.enabled: false

Сделано это в демонстрационных целях, для облегчения запуска плагина. Если вы захотите включить Rest Layer tls придется добавлять соответствующие настройки во все компоненты, которые общаются с elasticsearch.

Запуск docker-compose с LDAP интеграцией

Запустим проект с помощью docker-compose up , и залогинимся на http://0:5601 под admin/admin

Теперь у нас есть вкладка Security

Можно посмотреть настройку LDAP через интерфейс кибаны

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

docker exec -it elasticsearch /bin/bash
I have no name!@68ac2255bb85:/$ ./securityadmin_demo.sh
Open Distro Security Admin v7
Will connect to localhost:9300 ... done
Connected as CN=kirk,OU=client,O=client,L=test,C=de
Elasticsearch Version: 7.10.0
Open Distro Security Version: 1.12.0.0
Contacting elasticsearch cluster 'elasticsearch' and wait for YELLOW clusterstate ...
Clustername: elasticsearch
Clusterstate: YELLOW
Number of nodes: 1
Number of data nodes: 1
.opendistro_security index already exists, so we do not need to create one.
Populate config from /opt/bitnami/elasticsearch/plugins/opendistro_security/securityconfig/
Will update '_doc/config' with /opt/bitnami/elasticsearch/plugins/opendistro_security/securityconfig/config.yml 
   SUCC: Configuration for 'config' created or updated
Will update '_doc/roles' with /opt/bitnami/elasticsearch/plugins/opendistro_security/securityconfig/roles.yml 
   SUCC: Configuration for 'roles' created or updated
Will update '_doc/rolesmapping' with /opt/bitnami/elasticsearch/plugins/opendistro_security/securityconfig/roles_mapping.yml 
   SUCC: Configuration for 'rolesmapping' created or updated
Will update '_doc/internalusers' with /opt/bitnami/elasticsearch/plugins/opendistro_security/securityconfig/internal_users.yml 
   SUCC: Configuration for 'internalusers' created or updated
Will update '_doc/actiongroups' with /opt/bitnami/elasticsearch/plugins/opendistro_security/securityconfig/action_groups.yml 
   SUCC: Configuration for 'actiongroups' created or updated
Will update '_doc/tenants' with /opt/bitnami/elasticsearch/plugins/opendistro_security/securityconfig/tenants.yml 
   SUCC: Configuration for 'tenants' created or updated
Will update '_doc/nodesdn' with /opt/bitnami/elasticsearch/plugins/opendistro_security/securityconfig/nodes_dn.yml 
   SUCC: Configuration for 'nodesdn' created or updated
Will update '_doc/whitelist' with /opt/bitnami/elasticsearch/plugins/opendistro_security/securityconfig/whitelist.yml 
   SUCC: Configuration for 'whitelist' created or updated
Will update '_doc/audit' with /opt/bitnami/elasticsearch/plugins/opendistro_security/securityconfig/audit.yml 
   SUCC: Configuration for 'audit' created or updated
Done with success
I have no name!@68ac2255bb85:/$ 

настройка Ldap

Для настройки интеграции с LDAP необходимо заполнить соответствующие секции в elastisearch-opendistro-sec/config.yml, ссылка на официальную документацию по authentication

config:
  dynamic:
    authc:
      # тут можно настроить авторизацию	      
    authz:
      # а здесь аутентификацию	

В случае с Active Directory, необходимо будет изменить конфигурационный файл примерно так:

    authc:
      # тут можно настроить авторизацию    
      ldap:
        ...
            hosts:
              - ldaphost:389
            bind_dn: cn=LDAP,ou=Example,dc=example,dc=ru
            password: CHANGEME
            userbase: 'DC=example,DC=ru'
            usersearch: '(sAMAccountName={0})'
            username_attribute: sAMAccountName

Не забудьте воспользоваться securityadmin.sh после изменения конфигурации. Процедура была описана в предыдущем параграфе.

Установка EFK + Opendistro в kubernetes

Вернемся к проекту с kubernetes, установим проект командой

helm upgrade --install efk . -f values.yaml  

Нам необходимо будет

Для настройка OpenDistro Security plugin мы скопировали файл конфигурации, которые поместим в секреты kubernetes, в values.yaml добавился блок:

  extraVolumes:
  - name: config
    secret:
      secretName: opendistro-config
      items:
        - key: config.yml
          path: config.yml
  - name: roles-mapping
    secret:
      secretName: opendistro-config
      items:
        - key: roles_mapping.yml
          path: roles_mapping.yml
  extraVolumeMounts:
    - mountPath: /opt/bitnami/elasticsearch/plugins/opendistro_security/securityconfig/config.yml
      subPath: config.yml
      name: config
    - mountPath: /opt/bitnami/elasticsearch/plugins/opendistro_security/securityconfig/roles_mapping.yml
      subPath: roles_mapping.yml
      name: roles-mapping

Чтобы настройки применялись при команде helm upgrade, мы сделали job, который будет запускаться при каждой команде helm upgrade

apiVersion: batch/v1
kind: Job
metadata:
    name: opendistro-config-reload
    labels:
        app.kubernetes.io/managed-by: {{.Release.Service | quote }}
        app.kubernetes.io/instance: {{.Release.Name | quote }}
    annotations:
        "helm.sh/hook": post-upgrade
        "helm.sh/hook-delete-policy": hook-succeeded
spec:
    template:
        metadata:
            name: config-reload
            labels:
                app.kubernetes.io/managed-by: {{.Release.Service | quote }}
                app.kubernetes.io/instance: {{.Release.Name | quote }}
        spec:
            initContainers:
                - name: "wait-for-db"
                  image: "alpine:3.6"
                  command:
                      - 'sh'
                      - '-c'
                      - >
                          until nc -z -w 2 efk-elasticsearch-coordinating-only 9300 && echo elastic is ok; do
                            sleep 2;
                          done;
            containers:
                - name: opendistro-config-reload
                  image: "{{ .Values.elasticsearch.image.registry }}/{{ .Values.elasticsearch.image.repository}}:{{ .Values.elasticsearch.image.tag }}"
                  imagePullPolicy: {{ .Values.elasticsearch.image.pullPolicy | quote }}
                {{- if .Values.elasticsearch.master.securityContext.enabled }}
                  securityContext:
                    runAsUser: {{ .Values.elasticsearch.master.securityContext.runAsUser }}
                 {{- end }}
                  command:
                    - 'bash'
                    - '-c'
                    - >
                       "/opt/bitnami/elasticsearch/plugins/opendistro_security/tools/securityadmin.sh" -h efk-elasticsearch-coordinating-only -cd "/opt/bitnami/elasticsearch/plugins/opendistro_security/securityconfig" -icl -key "/opt/bitnami/elasticsearch/config/kirk-key.pem" -cert "/opt/bitnami/elasticsearch/config/kirk.pem" -cacert "/opt/bitnami/elasticsearch/config/root-ca.pem" -nhnv
            restartPolicy: Never
    backoffLimit: 1

Итоги

Если вы собираетесь организовать централизованную сборку логов для приложений под управление kubernetes, данный вариант мог бы стать вполне обоснованным решением. Все продукты поставляются под открытыми лицензиями. В статье приведена заготовка из которой можно создать production ready решение. Спасибо за внимание!