Kubernetes Loglarını Filebeat ile Logstash’e Gönderme

elifcan cakmak
Kod Gemisi
Published in
4 min readSep 17, 2019

Kubernetes üzerinde çalıştırdığınız uygulamaların loglarını elasticsearch ile takip etmek istiyorsanız, bu logları toplamanın birden fazla yöntemi var. Bunlardan öne çıkan ilk iki tanesi Filebeat ve Fluentd. Biz standart olarak sistemlerimizde Filebeat kullanıyoruz. Kubernetes Cluster’ımızda uygulamaların dev ortamları çalıştığı için bu uygulamaların loglarının takibini merkezi bir yerden yapmak istedik.

Bunu yapabilmek için üç yöntem var: Birincisi Cluster içindeki her node’a ayrı ayrı Filebeat kurmak. Bu yöntem kolay olsa da işi yapan üzerinde ekstra iş yükü olmasına sebep oluyor. İkincisi Cluster’ın master node’una external olarak Filebeat kurmak. Bu yöntemi tercih ettiğiniz zaman filebeat.yml dosyasında ekstra host ve config dosyası gibi bilgileri belirtmeniz gerekiyor. Üçüncü yöntem ise Filebeat’i direkt olarak Cluster içinde çalıştırmak. Bana en mantıklı gelen bu yol olduğu için bu şekilde ilerledim.

Öncelikle Filebeat’i Cluster içinde çalıştırarak logları direkt olarak Elasticsearch’e yollamak istiyorsanız, resmi dokümandaki kurulumu takip edebilirsiniz:

Bizim amacımız yolladığımız logları parse ederek Kibana üzerinde daha kolay filtrelenebilir hale getirmek olduğu için logları Logstash’e göndermek istedik. Bunu yapmak istediğimiz zaman filebeat.yml üzerinde değişiklik yapmamız gerekiyor. Bu yüzden ben kendi filebeat image’ımı oluşturma yoluna gittim. Öncelikle aşağıdaki gibi bir filebeat.yml dosyası oluşturmamız gerekiyor:

filebeat.inputs:
- type: container
stream: stdout
tags: ["container"]
paths:
- /var/log/containers/*.log
# parse multiline logs such as exceptions
multiline.pattern: '^((\d{2}-[A-Z][a-z]{2}-\d{4})|(\d{4}-\d{2}-\d{2})|(\d{2}:\d{2}:\d{2}.\d{3}))
multiline.negate: true
multiline.match: after
filebeat.config.modules:
# Glob pattern for configuration loading
path: ${path.config}/modules.d/*.yml
# Set to true to enable config reloading
reload.enabled: false
setup.template.settings:
index.number_of_shards: 1
output.logstash:
# The Logstash hosts
hosts: ["elastic.example.com:5044"]
processors:
# add kubernetes fields such as kubernetes.container.name
- add_kubernetes_metadata:
in_cluster: true

Burada dikkat ederseniz in_cluster: true veriyoruz. Eğer Filebeat’i Cluster dışında kurmuş olsaydık aşağıdakileri eklememiz gerekecekti:

in_cluster: false
host: <hostname>
kube_config: ${HOME}/.kube/config

Ardından Dockerfile’ı oluşturuyoruz:

FROM centos:6RUN curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.3.1-x86_64.rpm && \
rpm -i filebeat-7.3.1-x86_64.rpm && \
rm -f filebeat-7.3.1-x86_64.rpm && \
mkdir -p /etc/filebeat
COPY filebeat.yml /etc/filebeat/filebeat.ymlRUN chmod 600 /etc/filebeat/filebeat.ymlENTRYPOINT ["/usr/share/filebeat/bin/filebeat", "-e", "-v"]
CMD ["-c", "/etc/filebeat/filebeat.yml"]

Akabinde docker build -t filebeat . komutu ile image’ı oluşturuyoruz. Oluşturduğumuz image’ı herhangi bir Docker repository’sine yolluyoruz. Ben kendi image’ımı docker hub’da tutuyorum. Yukarıdaki konfigürasyon ile oluşturulmuş image’ı docker pull elfcan/filebeat komutu ile çekebilirsiniz.

Bunun akabinde filebeat-kubernetes.yml dosyasını oluşturuyoruz:

---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-config
namespace: filebeat
labels:
k8s-app: filebeat
data:
filebeat.yml: |-
filebeat.inputs:
- type: container
paths:
- /var/log/containers/*.log
processors:
- add_kubernetes_metadata:
in_cluster: true
host: ${NODE_NAME}
matchers:
- logs_path:
logs_path: "/var/log/containers/"
output.logstash:
hosts: ["elastic.example.com:5044"]
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: filebeat
namespace: filebeat
labels:
k8s-app: filebeat
spec:
template:
metadata:
labels:
k8s-app: filebeat
spec:
serviceAccountName: filebeat
terminationGracePeriodSeconds: 30
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- name: filebeat
image: elfcan/filebeat
args: [
"-c", "/etc/filebeat.yml",
"-e",
]
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
securityContext:
runAsUser: 0
# If using Red Hat OpenShift uncomment this:
#privileged: true
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 100Mi
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: varlog
mountPath: /var/log
readOnly: true
volumes:
- name: config
configMap:
defaultMode: 0600
name: filebeat-config
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- name: varlog
hostPath:
path: /var/log
- name: data
hostPath:
path: /var/lib/filebeat-data
type: DirectoryOrCreate
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: filebeat
subjects:
- kind: ServiceAccount
name: filebeat
namespace: filebeat
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: [""] # "" indicates the core API group
resources:
- namespaces
- pods
verbs:
- get
- watch
- list
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: filebeat
namespace: filebeat
labels:
k8s-app: filebeat
---

Bu yml dosyasını Kubernetes master node’unda kubectl create -f filebeat-kubernetes.yml komutu ile çalıştırdığımızda Kubernetes her node’da bir pod çalışacak şekilde deployment’ı yapıyor. Bu node’larda çalışan pod’lar ise bu sunuculardaki logları toplayıp logstash’e yolluyor.

Biz spring boot uygulamalarının loglarını toplamak istediğimiz için logstash tarafında buna uygun bir filtre oluşturduk. Toplamak istediğimiz logların formatı şu şekildeydi:

Logstash’da buna uygun grok ile aşağıdaki filtreyi yazdık:

%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:loglevel}\s+\[%{DATA:servicename},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:log_message}

Yazdığınız filtrenin doğruluğundan emin olmak için grok debugger uygulamasını kullanabilirsiniz. Buna göre logstash üzerindeki konfigürasyon dosyamız (filebeat-input.conf) aşağıdaki gibi oldu. İlk grok filtresi, ikinci filtreden geçemeyen formattaki loglar için hatanın ne olduğunu anlayabilmemiz adına stacktrace’i tag olarak ekliyor. İkinci filtre de yukarıda yazdığım gibi bizim Spring Boot uygulamalarımızın logları için çalışıyor.

input {
beats {
port => 5044
}
}
filter {
if [message] =~ "\tat" {
grok {
match => ["message", "^(\tat)"]
add_tag => ["stacktrace"]
}
}
grok {
match => [ "message",
"%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:loglevel}\s+\[%{DATA:servicename},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:log_message}"
]
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
user => "user"
password => "password"
}
}

Burada dikkat edilmesi gereken bir durumla karşılaştık. Spring Boot uygulama loglarını console’a renkli bir şekilde basıyor. Kubernetes de bu renkleri ansi code olarak işleyip log dosyalarına bu şekilde basıyor:

\u001b[2m2019-09-16 09:41:29.281\u001b[0;39m \u001b[33m WARN [,9dd5d3f6aab005e9,9dd5d3f6aab005e9,false]\u001b[0;39m \u001b[35m1\u001b[0;39m \u001b[2m---\u001b[0;39m \u001b[2m[io-8080-exec-70]\u001b[0;39m \u001b[36mc.k.g.user.ForgotPasswordDtoValidator   \u001b[0;39m \u001b[2m:\u001b[0;39m forgot password mail has requested by a non-user email address: testing@kodgemisi.com\r\n"

Bu renk kodları da filtrenin çalışmamasına sebep oluyor. Bu yüzden uygulamaları çalıştırırken bu renk kodlarını disable ettik:

global düzeyde:
export SPRING_OUTPUT_ANSI_ENABLED=NEVER
uygulama bazında application.yml üzerinde:
spring.output.ansi.enabled=NEVER

Bunu yaptıktan sonra herhangi bir sorun kalmadı ve Kibana üzerinde aşağıdaki gibi logları filtreleyebilir hale geldik:

--

--