EFK-Kubernetes cluster ve NFS storage class

Betül Aslan
7 min readJan 13, 2022

Bu yazıda, EFK’yı (Elasticsearch-Fluentd-Kibana) kubernetes cluster içerisinde deploy ederek, cluster dışındaki host makineler üzerinde çalışan custom uygulamalarımızın log’larını monitor edebilmeyi sağlayacağız.

Elasticsearch: Apache Lucene altyapısı üzerinde, Java programlama dili kullanılarak geliştirilmiş bir metin (full text) arama motoru ve analiz aracıdır.

Kibana: Elasticsearch’ün üstünde çalışan ve kullanıcılara verileri analiz etme ve görselleştirme olanağı sağlayan bir görselleştirme katmanıdır.

Fluentd: Ruby dilinde yazılmış,açık kaynaklı bir log toplayıcı ve işleyicidir.

Elasticsearch verileri bir db gibi tuttuğu için, bu dataları saklamak için ve pod crush olduktan sonra tekrar aynı verilere ulaşmak için bir persistance volume’e ihtiyaç duymaktadır. Elasticsearch için local storage class,nfs storage class veya ortamınıza göre başka provisioner’ları da kullanılabilirsiniz. Bizim kubernetes cluster’ımız 1 master node ve 2 worker node’dan oluştuğu için ve cloud provisioner’ımız olmadığı nfs kullanmayı tercih ettim.

NFS Server Kurulumu ve Configuration

1 makine nfs server 2 makinede nfs client olarak çalışacak. Node2'yi nfs server olarak seçtim. NFS için gerekli olan paketi kurduktan sonra, share edilecek dizin bilgisi(/root/nfs-share) ile bu dizini ortak kullanacak diğer makinelerin IP’lerini /etc/exports dosyası içerisine yetki tanımları (rw,no_root_squash) ile ekledik.

node2 :

apt-get install nfs-kernel-servermkdir /root/nfs-shareecho “/root/nfs-share 10.11.30.77/24(rw,no_root_squash)” >> /etc/exportsecho “/root/nfs-share 10.11.30.74/24(rw,no_root_squash)” >> /etc/exportsservice nfs-kernel-server restart

node1 ve node3:

NFS client için sadece bu paketin kurulması yeterli.

apt-get install nfs-common

node3'ü kubernetes master olarak kullanacağım bu neden ile buraya helm paketinin kurulu olması gerekli, host makinem ubuntu olduğu için helm’in kendi sayfasında paylaştığı şekilde kurulumumu yapıyorum.

curl https://baltocdn.com/helm/signing.asc | sudo apt-key add -
sudo apt-get install apt-transport-https --yes
echo "deb https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm
helm version
(output)version.BuildInfo{Version:"v3.7.2", GitCommit:"663a896f4a815053445eec4153677ddc24a0a361", GitTreeState:"clean", GoVersion:"go1.16.10"}
kubectl create clusterrolebinding add-on-cluster-admin --clusterrole=cluster-admin --serviceaccount=kube-system:defaulthelm install --set nfs.server=10.11.30.81 --set nfs.path=/root/nfs-share --name nfs-provisioner stable/nfs-client-provisioner

helm ile “nfs-client-provisioner”’ı kurduk. nfs-client adında bir storage class oluşturdu. Volume template’lerimiz içerisinde storage class olarak nfs-client’ı kullandığımız zaman bizim için pv ve pvc create etme işlemini otomatik olarak sağlayacaktır.

Elasticsearch Statefulset ve Servis Oluşturulması

Elasticsearch data persistance’a ihtiyaç duyduğu için statefulset yaml dosyası oluşturacağız. Oluşturduğumuz nfs storage class’ı statefulset dosyamızın içerisine yazıyoruz. “discovery.seed_hosts” ve “cluster.initial_master_nodes” tanımlarını cluster içerisindeki node sayımızı göre yapıyoruz. Statefulset pod’ları create ederken es-cluster-0, es-cluster-1,es-cluster-2 şeklinde oluşturur. Servis name olarak’ta “elasticsearch” tanımladığımız için, discovery.seed_hosts isimlendirmeleri şu formatta olur.

es-cluster-0.elasticsearch

kubectl apply -f es-statefulset.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
name: task-pv-volume
labels:
name: nfs-client # name can be anything
spec:
storageClassName: nfs-client # same storage class as pvc
capacity:
storage: 30Gi
accessModes:
- ReadWriteOnce
nfs:
server: 10.11.30.81
path: /root/nfs-share/
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: es-cluster
spec:
serviceName: elasticsearch
replicas: 3
selector:
matchLabels:
app: elasticsearch
template:
metadata:
labels:
app: elasticsearch
spec:
containers:
- name: elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch:7.16.0
resources:
limits:
cpu: 1000m
requests:
cpu: 100m
ports:
- containerPort: 9201
name: rest
protocol: TCP
- containerPort: 9301
name: inter-node
protocol: TCP
volumeMounts:
- name: data
mountPath: /usr/share/elasticsearch/data
env:
- name: cluster.name
value: k8s-logs
- name: node.name
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: discovery.seed_hosts
value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch"
- name: cluster.initial_master_nodes
value: "es-cluster-0,es-cluster-1,es-cluster-2"
- name: ES_JAVA_OPTS
value: "-Xms512m -Xmx512m"
- name: http.port
value: "9201"
- name: transport.port
value: "9301"
initContainers:
- name: fix-permissions
image: busybox
command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
securityContext:
privileged: true
volumeMounts:
- name: data
mountPath: /usr/share/elasticsearch/data
- name: increase-vm-max-map
image: busybox
command: ["sysctl", "-w", "vm.max_map_count=262144"]
securityContext:
privileged: true
- name: increase-fd-ulimit
image: busybox
command: ["sh", "-c", "ulimit -n 65536"]
securityContext:
privileged: true
volumeClaimTemplates:
- metadata:
name: data
labels:
app: elasticsearch
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: nfs-client
resources:
requests:
storage: 30Gi

Servisimizin k8s cluster dışından erişilebilir olamasını istediğimiz için node port olarak expose ediyoruz. Farklı expose seçenekleri de olabilirdi. (Load balance, ingress)

kubectl create-f es-service.yaml

apiVersion: v1
kind: Service
metadata:
name: elasticsearch
labels:
app: elasticsearch
spec:
selector:
app: elasticsearch
type: NodePort
ports:
- port: 9201
name: rest
- port: 9301
name: inter-node
nodePort: 30537

Tarayıcıdan “http://192.19.0.20:32309/” gittiğimizde;

Kibana Deployment ve Servis Oluşturulması

Kibana için deployment yaml dosyası oluşturuyoruz. Elasticsearch URL bilgisi mutlaka yazılmalı. K8s cluster içerisinde servis adları üzerinde erişim sağlayabiliriz. Resource kısımdaki cpu ve memory limitleri customize edilebilir.

Not: Elasticsearch versiyonu ile kibana versiyonu aynı olmalıdır.

kubectl create -f kibana-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: kibana
labels:
app: kibana
spec:
replicas: 1
selector:
matchLabels:
app: kibana
template:
metadata:
labels:
app: kibana
spec:
containers:
- name: kibana
image: docker.elastic.co/kibana/kibana:7.16.0
env:
- name: ELASTICSEARCH_URL
value: http://elasticsearch:9201
- name: ELASTICSEARCH_HOSTS
value: http://elasticsearch:9201
- name: SERVER.HOST
value: “0.0.0.0”
- name: SERVER.PORT
value: “5601”
- name: XPACK_SECURITY_ENABLED
value: “false”
- name: XPACK_MONITORING_UI_CONTAINER_ELASTICSEARCH_ENABLED
value: “true”
- name: BROWSERSLIST_IGNORE_OLD_DATA
value: “false”
- name: NODE_OPTIONS
value: “ - max-old-space-size=2048”
ports:
- containerPort: 5601
resources:
limits:
memory: “2Gi”
cpu: “1000m”
requests:
memory: “1Gi”
cpu: “500m”

kubectl create -f kibana-service.yaml

Servisimizi node port olarak expose ettik.

apiVersion: v1
kind: Service
metadata:
name: kibana
labels:
app: kibana
spec:
ports:
- port: 5601
name: rest
nodePort: 30539
selector:
app: kibana
type: NodePort

Tarayıcıdan “http://192.19.0.20:30539/” gittiğimizde;

Fluentd Deamonset ve Servis Oluşturulması

Fluentd bir agent olduğu için her node içerisinde bir tane pod olarak ayaklanması ve tüm node’lardaki pod’ların aynı config’e sahip olmasını bekleriz. Bunun için deamonset yaml dosyası oluşturacağız. Ayrıca cluster’a yeni bir node eklendiğinde de, aynı config ile yeni bir agent pod’u oluşturulur. Oncesinde servis account, cluster role ve binding işlemlerini yapalım.

Kubernetes api server ile iletişim kurması için ServiceAccount tanımlıyoruz. Ardından bu service account objemiz için cluster bazında role ve yetki tanımlaması yapıyoruz.

ServiceAccount

kubectl create -f fluentd-serviceaccount.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
name: fluentd
labels:
app: fluentd

Cluster role

kubectl create -f fluentd-clusterole.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: fluentd
labels:
app: fluentd
rules:
- apiGroups:
- “”
resources:
- pods
- namespaces
verbs:
- get
- list
- watch

Cluster Role Binding

kubectl create -f fluentd-clusterrolebind.yaml

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: default

Burada biraz daha K8s cluster’ı dışında çalışan custom uygulamalarımızın log’larının cluster’a forword edilip, Elasticsearch tarafın da index oluşturulması üzerinde duracağız. Bu neden ile default olarak kullanılan fluentd config dosyasını editlememiz gerekmektedir.

Config dosyası oluşturuyoruz. (fluent.conf) Bu config dosyasından bir configmap oluşturacağız.

kubectl create configmap vto-conf — from-file=fluent.conf

<source>
@type forward
port 24224
</source>
<match *>
@type copy
<store ignore_error>
@type stdout
</store>
<store ignore_error>
@type file
path /var/log/td-agent
</store>
<store>
@type elasticsearch
@id out_es
@log_level info
include_tag_key true
ssl_verify true
host elasticsearch
port 9201
scheme http
#logstash_format true
#logstash_prefix “vto”
#logstash_dateformat “%Y.%m”
include_timestamp true
index_name ${tag}
type_name ${tag}
reconnect_on_error true
reload_on_failure true
<buffer tag, time>
@type memory
timekey 3600
</buffer>
flush_interval 10s
</store>
<store>
@type stdout
</store>
</match>

Configmap’i oluşturduktan sonra, deamonset yaml dosyamız içerisinde yazmalıyız. Volume mount path olarak /fluentd/etc/fluent.conf yazıyoruz. Volumes bölümünde config map’e “vto-conf” yazıyoruz. Böylece pod içerisindeki servis ayağa kalktığı zaman fluent.conf dosyasının içeriği yukarıdaki gibi olacaktır.

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
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.14-debian-elasticsearch7–1
env:
— name: FLUENT_ELASTICSEARCH_HOST
value: “elasticsearch”
— name: FLUENT_ELASTICSEARCH_PORT
value: “9201”
— 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: /fluentd/etc/fluent.conf
subPath: fluent.conf
terminationGracePeriodSeconds: 30
volumes:
— name: config-volume
configMap:
name: vto-conf
— name: varlog
hostPath:
path: /var/log
— name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers

Fluentd service

Servisimizi node port olarak expose ediyoruz. 30540 port’u cluster dışından erişilebilir durumda olacaktır.

kubectl create -f fluentd-service.yaml

apiVersion: v1
kind: Service
metadata:
name: fluentd
labels:
app: fluentd
spec:
ports:
— port: 24224
name: rest
nodePort: 30540
selector:
app: fluentd
type: NodePort

Log’ların forword ediliği karşı makinelerde td-agent.conf dosyası içerisine forword port olarak 30540 yazmalı ve host’a cluster içerisindeki bir makinenin IP’si yazılmalı.

Sonuç olarak, elasticsearch üzerinde index’lerimiz oluştu. Custom uygulamamızdaki log’ları kibana üzerinde index oluşturarak, dashboard üzerinde monitor edebiliriz.

Betül ASLAN.

Devops Enginer

--

--