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 1-zu-1 weiter), Logs erscheinen.
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