/ Kubernetes

Kubernetes Logs an Elasticsearch senden

Eine Möglichkeit Logs von in Kubernetes laufenden Apps an Elasticsearch zu senden ist es, mit Filebeat die entsprechenden Docker-Log-Dateien auszuwerten und an Logstash weiter zu senden. Filebeat kann auch als Deamon im Kubernetes-Cluster gestartet werden, für die Erarbeitung einer ersten Konfiguration habe ich Filebat jedoch direkt auf einer Kubernetes-Node installiert.

Logs der Kubernetes-Apps könnten auch direkt an Elasticsearch gesendet werden. Logstash ist aber in der Lage aus Log-Strings brauchbare und durchsuchbare Daten zu generieren oder auch Logs zu kombinieren. Außerdem bildet er einen wichtigen Puffer vor Elasticsearch.

Die Filebeat-Konfiguration

Die Erarbeitete Konfiguration unterteilt sich in vier Bereiche

  • Globale Einstellungen
  • Konfiguration der zu sammelnden Logdaten (filebeat.inputs)
  • Anreicherung der Logdaten (processors)
  • Konfiguration des Endpunkts an den die Daten weitergeicht werden (output)

Die filebeat.yml hat dann folgenden Aufbau:

# global settings
filebeat.registry_file: /var/log/filebeat.registry
logging.level: debug

# input
filebeat.inputs:
  - type: log
    paths:
      - /var/lib/docker/containers/*/*.log
    symlinks: true
    json.keys_under_root: true
    json.message_key: log

# enhance logs
processors:
- add_kubernetes_metadata:
    in_cluster: false
    host: kubernetes-node
    kube_config: /root/.kube/config

# output
output.logstash:
  hosts: [ "10.0.0.5:5044" ]

Globale Einstellungen

In den globalen Einstellungen wird die Filebeat-Regsitry definiert, sowie der Loging-Level. In der Registry wird der Status über die bereits übermittelten Logdaten festgehalten.

Input

Im weiteren Block der Konfigurationsdatei werden die zu sammelnden Dateien konfiguriert. Der Dateipfad, unter dem alle Docker- und somit auch Kubernetes-Logs erreichbar sind, ist die wichtigste Einstellung in diesem Block.

Prozessor

Filebeat stellt Prozessoren zur Verfügung mit denen man Log-Einträge filtern oder anreichern kann. Für unserern Zweck bietet sich der Prozessor "add_kubernetes_metadata", der Logdaten mit Kubernetes-Informationen, wie dem Pod-Namen oder den Kubernetes-Labels anreichert. Die dafür benötigten Einstellungen sind die Angabe, ob Filebeat im Kubernetes-Cluster läuft, der Kubernetes-Hostname, sowie der Pfad zur Kubectl-Konfigurationsdatei (Nicht benötigt, wenn Filebeat im Cluster läuft).

Output

Die erfassten Daten werden an Logstash gesendet. Im weiteren Verarbeitungsschritt möchte ich, aus den Log-Messages weitere durchsuchbare Felder extrahieren.

Am Ende sollten in Kibana (Logstash ist bei mir bereits mit Elasticsearch Verbunden und leitet alle Logs 1zu1 weiter), Logs erscheinen und Kubernetes-Parameter mit aufgelistet sein:

kibana-kubernetes-logs

Filebeat im Cluster

Als nächstes kann Filebeat mit der erarbeiteteten Konfiguration im Kubernetes-Cluster betrieben werden. Dafür erstellen wir eine Kubernetes-Konfigurationsdatei mit den Kubernetes-Objekten "ConfigMap", "DeamonSet", "ServiceAccount", "ClusterRole" und "ClusterRoleBinding"

ConfigMap

Der Inhalt der filebeat.yml wird in einer ConfigMap gespeichert.

DeamonSet

Filebeat wird als DeamonSet gestartet. So wird gewährleistet, dass eine Instanz von Filebeat auf jeder Kubernetes-Node gestartet wird.

ServiceAccount, ClusterRole, ClusterRoleBinding

Ein ServiceAccount für Filebeat wird erstellt. Er wird mit passenden Rechten (ClusterRole) ausgestattet um innerhalb des Clusters benötigte Informationen zu Pods auszulesen um Log-Einträge mit Kubernetes-Informationen anzureichern. Die Zuordnung der ClusterRole zum ServiceAccount übernimmt ClusterRoleBinding.

Die Konfiguration

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-config
  namespace: kube-system
  labels:
    k8s-app: filebeat
    kubernetes.io/cluster-service: "true"
data:
  filebeat.yml: |-
    filebeat.registry_file: /var/log/filebeat.registry
    logging.level: error
    filebeat.inputs:
      - type: log
        paths:
          - /var/lib/docker/containers/*/*.log
        symlinks: true
        json.keys_under_root: true
        json.message_key: log
    processors:
    - add_kubernetes_metadata:
        in_cluster: true
    output.logstash:
      hosts: [ "10.0.0.5:5044" ]
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: filebeat
  namespace: kube-system
  labels:
    k8s-app: filebeat
    kubernetes.io/cluster-service: "true"
spec:
  template:
    metadata:
      labels:
        k8s-app: filebeat
        kubernetes.io/cluster-service: "true"
    spec:
      serviceAccountName: filebeat
      terminationGracePeriodSeconds: 30
      containers:
      - name: filebeat
        image: docker.elastic.co/beats/filebeat:6.6
        imagePullPolicy: Always
        args: [
          "-c", "/etc/filebeat.yml",
          "-e",
        ]
        securityContext:
          runAsUser: 0
        volumeMounts:
        - name: config
          mountPath: /etc/filebeat.yml
          readOnly: true
          subPath: filebeat.yml
        - name: data
          mountPath: /usr/share/filebeat/data
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
        - name: dockersock
          mountPath: /var/run/docker.sock
      volumes:
      - name: config
        configMap:
          name: filebeat-config
      - name: data
        emptyDir: {}
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: dockersock
        hostPath:
          path: /var/run/docker.sock
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: filebeat
subjects:
- kind: ServiceAccount
  name: filebeat
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: filebeat
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: filebeat
  labels:
    k8s-app: filebeat
rules:
- apiGroups: [""]
  resources:
  - namespaces
  - pods
  verbs:
  - get
  - watch
  - list
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: filebeat
  namespace: kube-system
  labels:
    k8s-app: filebeat

Logstash

Im nächsten Schritt müssen Log-Arten in Logstash erkannt und zu durchsuchbaren Datensätzen transformiert werden.

Dafür wird zunächst beispielhaft eine in Kubernetes laufende App mit dem Label "php" ausgezeichnet (Selektor: kubernetes.labels.app). Dann können im Block "filter" der Logstash-Konfiguration die zu der App gehörenden Apps gematcht werden, um sie zu einzelnen Keys zu transformieren.

Ein Logeintrag der App (Apache-Access-Log) sieht wie folgt aus:

10.0.0.123 - - [14/Feb/2019:23:03:42 +0000] "GET /index.html HTTP/1.1" 200 - "https://www.homepage.de" "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0"

Mit dem Grok-Filter kann er in einzelne durchsuchbare Keys zerlegt werden:

filter {
  if [kubernetes][labels][app] == "php" {
    grok {
      match => { "log" => "%{IPORHOST:client} %{USER:user} %{USER:auth} \[%{HTTPDATE:timestamp}\] \"%{WORD:method} %{DATA:url} HTTP/%{NUMBER:httpversion}\" %{NUMBER:response:int} (?:-|%{NUMBER:bytes:int}) %{QS:referrer} %{QS:agent}" }
    }
  }
}

Für jede neue App kann eine weitere If-Abfrage ergänzt und eine passende Grok-Anweisung ertstellt werden.

Quellen:
https://www.elastic.co/guide/en/beats/metricbeat/6.6/add-kubernetes-metadata.html
https://www.elastic.co/guide/en/beats/filebeat/master/running-on-kubernetes.html