Написать в техподдержку Позвонить нам
Админпанель Выход

Содержание статьи:

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

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

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

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

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

    Примечание

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

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

    kubectl logs pod_name

    Примечание

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

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

    kubectl logs pod_name --previous

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

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

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

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

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

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

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

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

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

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

    Примечание

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

    1. Узнайте storage class, доступные в кластере Kubernetes:
    admin@k8s:~$ kubectl get sc 
    NAME            PROVISIONER            AGE
    hdd (default)   kubernetes.io/cinder   103d
    hdd-retain      kubernetes.io/cinder   103d
    ssd             kubernetes.io/cinder   103d
    ssd-retain      kubernetes.io/cinder   103d
    1. Установите Elasticsearch в кластер Kubernetes с заданными переменными:
      helm install stable/elasticsearch \
            --name elastic \
            --set client.replicas=1 \
            --set master.replicas=1 \
            --set data.replicas=1 \
            --set master.persistence.storageClass=hdd \
            --set data.persistence.storageClass=hdd \
            --set master.podDisruptionBudget.minAvailable=1 \
            --set resources.requests.memory=4Gi \
            --set cluster.env.MINIMUM_MASTER_NODES=1 \
            --set cluster.env.RECOVER_AFTER_MASTER_NODES=1 \
            --set cluster.env.EXPECTED_MASTER_NODES=1 \
            --namespace kube-logging
      В результате будет установлен кластер Elasticsearch, состоящий из 1 мастер-ноды, 1 ноды хранения данных и 1 ноды клиента.
    2. Убедитесь, что все поды готовы к работе:
      kubectl get po -n kube-logging
      NAME                                           READY   STATUS    RESTARTS   AGE
      elastic-elasticsearch-client-c74598797-9m7pm   1/1     Running  
      elastic-elasticsearch-data-0                   1/1     Running  
      elastic-elasticsearch-master-0                 1/1     Running  
    3. Узнайте название сервисов в  kube-logging:
      kubectl get svc -n kube-logging
    NAME                              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
    elastic-elasticsearch-client      ClusterIP   10.233.8.213   <none>        9200/TCP   11m
    elastic-elasticsearch-discovery   ClusterIP   None           <none>        9300/TCP   11m
    Сервис elastic-elasticsearch-client будет использоваться для связки с kibana и fluentd. Дашборд Kibana установим также с помощью helm, но пропишем название сервиса elastic-elasticsearch-client в переменные его helm-чарта.
    1. Скачайте переменные helm-чарта kibana для редактирования:
    helm fetch --untar stable/kibana
    1. Перейдите в каталог kibana и отредактируйте файл values.yaml:
    cd kibana/ && vim values.yaml
    1. Впишите в секцию elasticsearch hosts имя сервиса elastic-elasticsearch-client:
    35 files:
    36   kibana.yml:
    37     ## Default Kibana configuration from kibana-docker.
    38     server.name: kibana
    39     server.host: "0"
    40     ## For kibana < 6.6, use elasticsearch.url instead
    41     elasticsearch.hosts: http://elastic-elasticsearch-client:9200
    1. Установите Kibana с модифицированными параметрами:
    helm install stable/kibana \
         --name kibana \
         --namespace kube-logging \
         -f values.yaml
    1. Убедитесь, что под стартовал, и пробросьте на свою локальную машину порт 5601 пода Kibana для доступа к дашборду:
    kubectl get pod -n kube-logging 

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

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


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

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

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

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

    1. Создайте configmap для fluentd со следующим содержанием:
    kind: ConfigMap
    apiVersion: v1
    data:
      containers.input.conf: |-
        <source>
          type tail
          path /var/log/containers/*.log
          pos_file /var/log/es-containers.log.pos
          time_format %Y-%m-%dT%H:%M:%S.%NZ
          tag kubernetes.*
          read_from_head true
          format multi_format
          <pattern>
            format json
            time_key time
            time_format %Y-%m-%dT%H:%M:%S.%NZ
          </pattern>
          <pattern>
            format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/
            time_format %Y-%m-%dT%H:%M:%S.%N%:z
          </pattern>
        </source>
      system.input.conf: |-
        <source>
          type tail
          format /^time="(?<time>[^)]*)" level=(?<severity>[^ ]*) msg="(?<message>[^"]*)"( err="(?<error>[^"]*)")?( statusCode=($<status_code>\d+))?/
          path /var/log/docker.log
          pos_file /var/log/es-docker.log.pos
          tag docker
        </source>
    
        <source>
          type tail
          format none
          path /var/log/etcd.log
          pos_file /var/log/es-etcd.log.pos
          tag etcd
        </source>
    
    
        <source>
          type tail
          format multiline
          multiline_flush_interval 5s
          format_firstline /^\w\d{4}/
          format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
          time_format %m%d %H:%M:%S.%N
          path /var/log/kubelet.log
          pos_file /var/log/es-kubelet.log.pos
          tag kubelet
        </source>
    
        <source>
          type tail
          format multiline
          multiline_flush_interval 5s
          format_firstline /^\w\d{4}/
          format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
          time_format %m%d %H:%M:%S.%N
          path /var/log/kube-proxy.log
          pos_file /var/log/es-kube-proxy.log.pos
          tag kube-proxy
        </source>
    
        <source>
          type tail
          format multiline
          multiline_flush_interval 5s
          format_firstline /^\w\d{4}/
          format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
          time_format %m%d %H:%M:%S.%N
          path /var/log/kube-apiserver.log
          pos_file /var/log/es-kube-apiserver.log.pos
          tag kube-apiserver
        </source>
    
    
        <source>
          type tail
          format multiline
          multiline_flush_interval 5s
          format_firstline /^\w\d{4}/
          format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
          time_format %m%d %H:%M:%S.%N
          path /var/log/kube-controller-manager.log
          pos_file /var/log/es-kube-controller-manager.log.pos
          tag kube-controller-manager
        </source>
    
        <source>
          type tail
          format multiline
          multiline_flush_interval 5s
          format_firstline /^\w\d{4}/
          format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
          time_format %m%d %H:%M:%S.%N
          path /var/log/kube-scheduler.log
          pos_file /var/log/es-kube-scheduler.log.pos
          tag kube-scheduler
        </source>
    
        <source>
          type tail
          format multiline
          multiline_flush_interval 5s
          format_firstline /^\w\d{4}/
          format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
          time_format %m%d %H:%M:%S.%N
          path /var/log/rescheduler.log
          pos_file /var/log/es-rescheduler.log.pos
          tag rescheduler
        </source>
    
        # Logs from systemd-journal for interesting services.
        <source>
          type systemd
          filters [{ "_SYSTEMD_UNIT": "docker.service" }]
          pos_file /var/log/gcp-journald-docker.pos
          read_from_head true
          tag docker
        </source>
    
        <source>
          type systemd
          filters [{ "_SYSTEMD_UNIT": "kubelet.service" }]
          pos_file /var/log/gcp-journald-kubelet.pos
          read_from_head true
          tag kubelet
        </source>
    
        <source>
          type systemd
          filters [{ "_SYSTEMD_UNIT": "node-problem-detector.service" }]
          pos_file /var/log/gcp-journald-node-problem-detector.pos
          read_from_head true
          tag node-problem-detector
        </source>
      forward.input.conf: |-
        # Takes the messages sent over TCP
        <source>
          type forward
        </source>
    
        <match **>
           type elasticsearch
           log_level info
           include_tag_key true
           host elastic-elasticsearch-client
           port 9200
           logstash_format true
           logstash_prefix fluentd
           # Set the chunk limits.
           buffer_chunk_limit 2M
           buffer_queue_limit 8
           flush_interval 5s
           # Never wait longer than 5 minutes between retries.
           max_retry_wait 30
           # Disable the limit on the number of retries (retry forever).
           disable_retry_limit
           # Use multiple threads for processing.
           num_threads 2
        </match>
    metadata:
      name: fluentd-es-config-v0.1.1
      namespace: kube-logging
      labels:
        addonmanager.kubernetes.io/mode: Reconcile
    1. В kubernetes примените configmap:
      kubectl apply -f fluentd-cm.yaml

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

    1. Для fluentd создайте файл sa-r-crb.yaml с описанием service account, role и rolebinding:
      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
    2. Задеплойте ресурсы в кластер:
    kubectl apply -f sa-r-crb.yaml
    serviceaccount/fluentd created
    clusterrole.rbac.authorization.k8s.io/fluentd created
    clusterrolebinding.rbac.authorization.k8s.io/fluentd created

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

    1. Создайте манифест fluentd-daemonset.yaml с следующим содержанием:
    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: "elastic-elasticsearch-client"
              - 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
            - name: config-volume
              mountPath: /etc/fluent/config.d
          terminationGracePeriodSeconds: 30
          volumes:
          - name: varlog
            hostPath:
              path: /var/log
          - name: varlibdockercontainers
            hostPath:
              path: /var/lib/docker/containers
          - name: config-volume
            configMap:
                name: fluentd-es-config-v0.1.1
    
    
    1. Примените манифест в kubernetes:
      kubectl apply -f fluentd-daemonset.yaml

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

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

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


    1. Выберите вкладку Index Patterns, нажмите кнопку Create index pattern и введите название logstash-*
    2. Нажмите кнопку Next Step и выберите Сreate Index Pattern:

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

    Обратная связь

    Возникли проблемы или остались вопросы? Напишите нам, мы будем рады 


    Полезна ли была эта статья?