CLOUD & MANAGED SERVICESKUBERNETES CLOUD
02/12/2022 • Ugur Akkar

How to: een EFK-stack implementeren in Kubernetes met xpack-beveiliging

In deze Kubernetes-tutorial leer je hoe je best een EFK-stack instelt op Kubernetes met xpack-beveiligingsfunctie, ingeschakeld voor logstreaming, loganalyse en logmonitoring.

Wanneer meerdere applicaties en services op een Kubernetes-cluster worden uitgevoerd, is het logischer om al je Kubernetes Cluster-logboeken naar één gecentraliseerde logboekinfrastructuur te streamen voor eenvoudige logboekanalyse. Dit kan jou helpen om snel de grote hoeveelheid loggegevens die door je pods worden geproduceerd, te doorzoeken en te analyseren. Door de xpack-beveiligingsfunctie in te schakelen, kan je het volgende maken en beheren: gebruikers, rollen, weergaven, .... . Dit geeft de mogelijkheid om bepaalde toestemming te geven om een ​​subset van applicatielogboeken (indexen) te bekijken, te bewerken, aan te maken, ....

Een populaire gecentraliseerde oplossing voor logboekregistratie is de Elasticsearch-, Fluentd- en Kibana-stack (EFK).

Wat doet elk onderdeel?

  • Elasticsearch: vangt binnenkomende gegevens op en slaat ze op in indexen.
  • Fluentd: volgt applicaties in jouw cluster en stuurt deze rechtstreeks naar Elasticsearch.
  • Kibana: maakt het mogelijk om logs te bekijken, query's uit te voeren, een eigen dashboard te maken, … vanuit Elasticsearch Indexes (data).

Elastic heeft onlangs verklaard dat beveiligingsfuncties standaard met de basislicentie worden gedistribueerd.

Elastic heeft een aantal beveiligingsfuncties gratis uitgebracht als onderdeel van de standaarddistributie (basislicentie) vanaf Elastic Stack 6.8 en 7.1. Dit nieuwe functieaanbod omvat de mogelijkheid om netwerkverkeer te versleutelen met SSL, gebruikers aan te maken en te beheren, rollen te definiëren die toegang op index- en clusterniveau beschermen en Kibana volledig te beveiligen.

1. Vereisten

Voordat we met deze handleiding kunnen beginnen, moet je ervoor zorgen dat je de volgende zaken ter beschikking hebt:

  • Een Kubernetes 1.10+ cluster met op rollen gebaseerd toegangsbeheer (RBAC) ingeschakeld.
  • Het kubectl-opdrachtregelprogramma dat is geïnstalleerd op uw lokale computer en is geconfigureerd om verbinding te maken met uw cluster.
  • (Optioneel) SealedSecret Controller geïmplementeerd in het cluster en kubeseal geïnstalleerd op uw lokale computer.

Zodra je deze componenten hebt ingesteld, ben je klaar om met deze handleiding te beginnen. Let's go!

2. Creëren van de Namespaces

Laten we beginnen met het maken van de nodigde name spaces voor elke toepassing.


Elasticsearch Namespace:

kind: Namespace
apiVersion: v1
metadata:
  name: elasticsearch
  group: elasticsearch

FluentD namespace:

kind: Namespace
apiVersion: v1
metadata:
  name: fluentd
  group: fluentd

Kibana namespace:

kind: Namespace
apiVersion: v1
metadata:
  name: fluentd
  group: fluentd

Once we have created the yaml files, we can deploy the yaml files to the cluster:

Zodra we de yaml-bestanden hebben gecreëerd, kunnen we de yaml-bestanden implementeren in de cluster:

kubectl create -f elasticsearch.yaml -f kibana.yaml -f fluentd.yaml 

De volgende output zou moeten verschijnen:

namespace/elasticsearch created
namespace/kibana created
namespace/fluentd created

We kunnen valideren of de namespaces succesvol zijn gemaakt door de volgende opdracht uit te voeren:

kubectl get namespaces

De volgende output zou moeten verschijnen:

NAME           STATUS    AGE
default        Active    15d
kube-system    Active    15d
elasticsearch  Active    1m
kibana         Active    1m
fluentd        Active    1m

3.  Elasticsearch Statefulset implementeren

Eerst moeten we Elasticsearch implementeren. Elasticsearch is de kerncomponent in de stack, Fluentd en Kibana kunnen niet werken zonder ElasticSearch.

Je vindt meer informatie over Elasticsearch door op deze link te klikken: https://www.elastic.co/what-is/elasticsearch

3.1 Serviceaccount maken

Laten we eerst beginnen met het maken van de RBAC-resources. We zullen de Elasticsearch ServiceAccount voldoende toestemming geven om de cluster te verkennen en naar andere Elasticsearch-knooppunten te zoeken.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: elasticsearch
  namespace: elasticsearch
  labels:
    app: elasticsearch

We hebben ons ServiceAccount, nu moeten we de ClusterRole creëren en deze binden aan de elasticsearch ServiceAccount.

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: elasticsearch
  labels:
    k8s-app: elasticsearch
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
rules:
- apiGroups:
  - ""
  resources:
  - "services"
  - "namespaces"
  - "endpoints"
  verbs:
  - "get"

Het binden aan de ServiceAccount.

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: elasticsearch
  name: elasticsearch
  labels:
    k8s-app: elasticsearch
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
subjects:
- kind: ServiceAccount
  name: elasticsearch
  namespace: elasticsearch
  apiGroup: ""
roleRef:
  kind: ClusterRole
  name: elasticsearch
  apiGroup: ""

3.2 Headless-services creëren

Voor de volgende stap hebben we een serviceresource in het cluster nodig. We zullen een Headless Service-resource maken met de naam elasticsearch in de naamruimte elasticsearch. Wanneer we onze Elasticsearch StatefulSet koppelen aan deze Service, retourneert de Service DNS A-records (service-name.namespace.svc.cluster.local) vanaf dat punt naar Elasticsearch Pods met de app: elasticsearch label. We zullen deze DNS-records later configureren voor onze Statefulset, dus Elasticsearch zal naar deze knooppunten zoeken.

apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
  namespace: elasticsearch
  labels:
    app: elasticsearch
spec:
  selector:
    app: elasticsearch
  ports:
    - name: rest
      port: 9200
      targetPort: 9200
    - name: transport
      port: 9300
      targetPort: 9300

Laten we onze yaml-bestanden implementeren in het cluster:

kubectl create -f Service.yaml -f ServiceAccount.yaml -f ClusterRole.yaml -f ClusterRoleBinding.yaml

Laten we nu kijken of de elasticsearch-service met succes is geïmplementeerd:

kubectl get services -n elasticsearch

De volgende output zou moeten verschijnen:

NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)             AGE
elasticsearch   ClusterIP   None         <none>        9200/TCP,9300/TCP   1m

3.3 Statefulset maken

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: elasticsearch
  namespace: elasticsearch
spec:
  serviceName: elasticsearch
  replicas: 3
  updateStrategy:
    type: OnDelete
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      securityContext:
        fsGroup: 1000
      initContainers:
        - name: increase-vm-max-map
          image: busybox
          imagePullPolicy: IfNotPresent
          securityContext:
            privileged: true
          command: [ "sysctl", "-w", "vm.max_map_count=262144" ]
      containers:
        - name: elasticsearch
          image: docker.elastic.co/elasticsearch/elasticsearch:8.3.2
          env:
            - name: node.name
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: NODE_MASTER
              valueFrom:
                configMapKeyRef:
                  name: elasticsearch-config
                  key: NODE_MASTER
            - name: NODE_DATA
              valueFrom:
                configMapKeyRef:
                  name: elasticsearch-config
                  key: NODE_DATA
            - name: NUMBER_OF_MASTERS
              valueFrom:
                configMapKeyRef:
                  name: elasticsearch-config
                  key: NUMBER_OF_MASTERS
            - name: NUMBER_OF_REPLICAS
              valueFrom:
                configMapKeyRef:
                  name: elasticsearch-config
                  key: NUMBER_OF_REPLICAS
            - name: ES_JAVA_OPTS
              valueFrom:
                configMapKeyRef:
                  name: elasticsearch-config
                  key: ES_JAVA_OPTS
            - name: ES_PORT
              value: "9200"
            - name: ELASTIC_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: elastic-credentials
                  key: ELASTIC_PASSWORD
          ports:
            - containerPort: 9200
              name: rest
              protocol: TCP
            - containerPort: 9300
              name: transport
              protocol: TCP
          volumeMounts:
            - name: elasticsearch-data
              mountPath: /usr/share/elasticsearch/data
            - name: elasticsearch-yml
              mountPath: /usr/share/elasticsearch/config/elasticsearch.yml
              subPath: elasticsearch.yml
        resources:
            requests:
                cpu: "1000m"
                memory: "2Gi"
            limits:
                cpu: "1000m"
                memory: "2Gi"      
        volumes:
        - name: elasticsearch-yml
          configMap:
            name: elasticsearch-config
            items:
              - key: elasticsearch.yml
                path: elasticsearch.yml
  volumeClaimTemplates:
    - metadata:
        name: elasticsearch-data
        annotations:
          volume.beta.kubernetes.io/storage-class: gp3
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: 50Gi

We hebben enkele omgevingsvariabelen gedefinieerd in onze Statefulset-resources. Sommige variabelen zijn van ConfigMap en sommige van een geheim.

Secret bevat het wachtwoord van de Elasticsearch admin-gebruiker.

Voer de volgende opdracht uit om een ​​yaml-bestand te maken voor het elasticsearch-beheerderswachtwoord:

# Create SealedSecret for the admin elasticsearch password
kubectl -n elasticsearch create secret generic elastic-credentials \  
  --from-literal=ELASTIC_PASSWORD='STRONG-PASSWORD' \
  --dry-run=client -o yaml | ${KUBESEAL_BINARY} --cert ${KUBESEAL_CERT_PATH} --format yaml > SealedSecret-ElasticCredentials.yaml

Indien je geen SealedSecret-controller hebt, kan je een geheime bron maken door de volgende opdracht uit te voeren:

# Create SealedSecret for the admin elasticsearch password
kubectl -n elasticsearch create secret generic elastic-credentials \  
  --from-literal=ELASTIC_PASSWORD='STRONG-PASSWORD' \
  --dry-run=client -o yaml > SealedSecret-ElasticCredentials.yaml

Met de bovenstaande opdracht wordt het yaml-bestand gemaakt dat in het cluster moet worden geïmplementeerd.

3.4 ConfigMap maken

De Configmap bevat het elasticsearch.yml-blok met extra Elasticsearch-configuratie. We voegen onze Service DNS-records toe aan onze discovery.seed_hosts, Elasticsearch zoekt naar extra knooppunten.

Dit blok wordt op de pod gemount onder de locatie /usr/share/elasticsearch/config/elasticsearch.yml.

apiVersion: v1
kind: ConfigMap
metadata:
  name: elasticsearch-config
  namespace: elasticsearch
data:
  elasticsearch.yml: |
    cluster.name: "elasticsearch"
    bootstrap.memory_lock: false
    xpack.license.self_generated.type: basic
    network.host: "0.0.0.0"
    logger.org.elasticsearch.transport: error
    logger.org.elasticsearch.discovery: error
    discovery.seed_hosts:
       - elasticsearch-0.elasticsearch.elasticsearch.svc.cluster.local:9300
       - elasticsearch-1.elasticsearch.elasticsearch.svc.cluster.local:9300
       - elasticsearch-2.elasticsearch.elasticsearch.svc.cluster.local:9300
    cluster.initial_master_nodes:
       - elasticsearch-0
       - elasticsearch-1
       - elasticsearch-2
  NODE_MASTER: "true"
  NODE_DATA: "true"
  NUMBER_OF_MASTERS: "3"
  NUMBER_OF_REPLICAS: "2"

Deze volumekoppeling wordt ook gedeclareerd in statefulset.yaml:

          volumeMounts:
            - name: elasticsearch-data
              mountPath: /usr/share/elasticsearch/data
            - name: elasticsearch-yml
              mountPath: /usr/share/elasticsearch/config/elasticsearch.yml
              subPath: elasticsearch.yml

Implementeer al je yaml-bestanden en zorg ervoor dat Elasticsearch zonder problemen werkt. Als Elasticsearch niet correct werkt, kan je de containerlogboeken volgen of de Pod/Statefulset beschrijven.

kubectl create -f ConfigMap.yaml -f Statefulset.yaml

De volgende output zou moeten verschijnen:

configmap/elasticsearch-config created
statefulset/elasticsearch created

Laten we eens kijken of de elasticsearch statefulset met succes is geïmplementeerd.

kubectl get pod -n elasticsearch

NAMESPACE       NAME                                         READY   STATUS      RESTARTS       AGE
elasticsearch   elasticsearch-0                              1/1     Running     0              2m
elasticsearch   elasticsearch-1                              1/1     Running     0              1m
elasticsearch   elasticsearch-2                              1/1     Running     0              30sc

3.5 xpack functie inschakelen

3.5.1 Certificaten genereren

Elasticsearch start niet wanneer de beveiligingsfunctie is ingeschakeld zonder dat de beveiligingsconfiguratie is geconfigureerd!

Voordat we de beveiligingsfunctie kunnen inschakelen, moeten we certificaten voor ElasticSearch-knooppunten genereren. Elasticsearch-knooppunten communiceren veilig met elkaar.

Voer de volgende opdrachten uit in de elasticsearch-container.

kubectl -n elasticsearch exec -ti elasticsearch-0 -- bash
 
# Create certificates
elasticsearch-certutil ca --out /tmp/elastic-stack-ca.p12 --pass ''
elasticsearch-certutil cert --name security-master --dns security-master --ca /tmp/elastic-stack-ca.p12 --pass '' --ca-pass '' --out /tmp/elastic-certificates.p12
 
# copy certificates to local machine
sudo kubectl cp elasticsearch/elasticsearch-0:/tmp/elastic-stack-ca.p12 ./elastic-stack-ca.p12
sudo kubectl cp elasticsearch/elasticsearch-0:/tmp/elastic-certificates.p12 ./elastic-certificates.p12
 
# Validate and extract PEM
openssl pkcs12 -nodes -passin pass:'' -in elastic-certificates.p12 -out elastic-certificate.pem

Zodra we ons certificaat hebben gegenereerd en gekopieerd van de container naar onze lokale machine, maken we een SealedSecret van het PEM-bestand. We zullen dit PEM-bestand later aan de container koppelen.

# Create SealedSecret for the P12 file
kubectl -n elasticsearch create secret generic elastic-certificate-pem \
  --from-file=elastic-certificates.p12 \
  --dry-run=client -o yaml | ${KUBESEAL_BINARY} --cert ${KUBESEAL_CERT_PATH} --format yaml > SealedSecret-ElasticCertificates.yaml

Indien je geen SealedSecret-controller hebt, kan je een geheime bron maken door de volgende opdracht uit te voeren.

# Create SealedSecret for the P12 file
kubectl -n elasticsearch create secret generic elastic-certificate-pem \
  --from-file=elastic-certificates.p12 \
  --dry-run=client -o yaml > SealedSecret-ElasticCertificates.yaml

Met de bovenstaande opdracht wordt het yaml-bestand gemaakt dat in het cluster moet worden geïmplementeerd.

3.5.2 beveiligingsfuncties van xpack inschakelen

Wanneer je het certificaat met succes hebt ontwikkeld en geïmplementeerd in het cluster, kunnen we nu beveiligingsfuncties inschakelen.

Voeg de volgende configuratie toe aan de elasticsearch.yml-configuratie in het bestand ConfigMap.yaml:

    xpack.license.self_generated.type: basic
    xpack.security.enabled: true
    xpack.security.transport.ssl.enabled: true
    xpack.security.transport.ssl.verification_mode: certificate
    xpack.security.transport.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
    xpack.security.transport.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
    xpack.security.http.ssl.enabled: false
    xpack.security.http.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
    xpack.security.http.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12

Zodra je xpack.security.enabled instelt op true, worden xpack-beveiligingsfuncties ingeschakeld. Maar alleen deze instelling inschakelen is niet voldoende. We moeten ook onze nieuw gegenereerde certificaten koppelen en configureren.

Koppel het geheim dat de certificaten bevat aan de StatefulSet:

          volumeMounts:
            - name: elasticsearch-data
              mountPath: /usr/share/elasticsearch/data
            - name: elasticsearch-yml
              mountPath: /usr/share/elasticsearch/config/elasticsearch.yml
              subPath: elasticsearch.yml
            - name: elastic-certificates
              mountPath: /usr/share/elasticsearch/config/certs
           
          .....
          .....
       
      volumes:
        - name: elasticsearch-yml
          configMap:
            name: elasticsearch-config
            items:
              - key: elasticsearch.yml
                path: elasticsearch.yml
        - name: elastic-certificates
          secret:
            secretName: elastic-certificates

Bewaar en vervang de ConfigMap en Statefulset. Wacht tot alle pods zijn beëindigd en opnieuw zijn gestart. Indien de pods niet automatisch opnieuw worden opgestart, schaal de statefulset omlaag en schaal deze daarna weer omhoog:

kubectl -n elasticsearch scale statefulset elasticsearch --replicas 0
#wait till all nodes are deleted
 
kubectl -n elasticsearch scale statefulset elasticsearch --replicas 3

Tail logs en zorg ervoor dat Elasticsearch goed werkt. Als Elasticsearch niet correct werkt, kan je de containerlogboeken volgen of de Pod/Statefulset beschrijven.

Uw bestand ConfigMap.yaml en Statefulset.yaml zou er als volgt uit moeten zien.

3.5.3 Statefulset

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: elasticsearch
  namespace: elasticsearch
spec:
  serviceName: elasticsearch
  replicas: 3
  updateStrategy:
    type: OnDelete
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      securityContext:
        fsGroup: 1000
      initContainers:
        - name: increase-vm-max-map
          image: busybox
          imagePullPolicy: IfNotPresent
          securityContext:
            privileged: true
          command: [ "sysctl", "-w", "vm.max_map_count=262144" ]
      containers:
        - name: elasticsearch
          image: "defined_in_kustomization"
          env:
            - name: node.name
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: NODE_MASTER
              valueFrom:
                configMapKeyRef:
                  name: elasticsearch-config
                  key: NODE_MASTER
            - name: NODE_DATA
              valueFrom:
                configMapKeyRef:
                  name: elasticsearch-config
                  key: NODE_DATA
            - name: NUMBER_OF_MASTERS
              valueFrom:
                configMapKeyRef:
                  name: elasticsearch-config
                  key: NUMBER_OF_MASTERS
            - name: NUMBER_OF_REPLICAS
              valueFrom:
                configMapKeyRef:
                  name: elasticsearch-config
                  key: NUMBER_OF_REPLICAS
            - name: ES_JAVA_OPTS
              valueFrom:
                configMapKeyRef:
                  name: elasticsearch-config
                  key: ES_JAVA_OPTS
            - name: ES_PORT
              value: "9200"
            - name: ELASTIC_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: elastic-credentials
                  key: ELASTIC_PASSWORD
          ports:
            - containerPort: 9200
              name: rest
              protocol: TCP
            - containerPort: 9300
              name: transport
              protocol: TCP
          volumeMounts:
            - name: elasticsearch-data
              mountPath: /usr/share/elasticsearch/data
            - name: elasticsearch-yml
              mountPath: /usr/share/elasticsearch/config/elasticsearch.yml
              subPath: elasticsearch.yml
            - name: elastic-certificates
              mountPath: /usr/share/elasticsearch/config/certs
          resources:
            requests:
              cpu: "1000m"
              memory: "2Gi"
            limits:
              cpu: "1000m"
              memory: "2Gi"
      volumes:
        - name: elasticsearch-yml
          configMap:
            name: elasticsearch-config
            items:
              - key: elasticsearch.yml
                path: elasticsearch.yml
        - name: elastic-certificates
          secret:
            secretName: elastic-certificates
  volumeClaimTemplates:
    - metadata:
        name: elasticsearch-data
        annotations:
          volume.beta.kubernetes.io/storage-class: gp3
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: 50Gi
3.5.3.1 Readinessprobe

Als je de readinessprobe wil toevoegen, voeg het volgende toe aan je Statefulset.yaml:

readinessProbe:
 exec:
   command:
     - /bin/bash
     - -c
     - |-
       health=$(curl -s -o /dev/null -u elastic:${ELASTIC_PASSWORD} --write-out "%{http_code}"  localhost:9200/_cluster/health?local=true)
       if [[ ${health} -ne 200 ]]; then exit 1; fi
 initialDelaySeconds: 5

3.5.4 Configmap

apiVersion: v1
kind: ConfigMap
metadata:
  name: elasticsearch-config
  namespace: elasticsearch
data:
  elasticsearch.yml: |
    cluster.name: "elasticsearch"
    bootstrap.memory_lock: false
    xpack.license.self_generated.type: basic
    xpack.monitoring.collection.enabled: true
    xpack.security.http.ssl.enabled: false
    xpack.security.transport.ssl.enabled: true
    xpack.security.transport.ssl.verification_mode: certificate
    xpack.security.transport.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
    xpack.security.transport.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
    xpack.security.http.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
    xpack.security.http.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
    network.host: "0.0.0.0"
    logger.org.elasticsearch.transport: error
    logger.org.elasticsearch.discovery: error
    discovery.seed_hosts:
       - elasticsearch-0.elasticsearch.elasticsearch.svc.cluster.local:9300
       - elasticsearch-1.elasticsearch.elasticsearch.svc.cluster.local:9300
       - elasticsearch-2.elasticsearch.elasticsearch.svc.cluster.local:9300
    cluster.initial_master_nodes:
       - elasticsearch-0
       - elasticsearch-1
       - elasticsearch-2
  NODE_MASTER: "true"
  NODE_DATA: "true"
  NUMBER_OF_MASTERS: "3"
  NUMBER_OF_REPLICAS: "2"
  ES_JAVA_OPTS: "-Djava.net.preferIPv4Stack=true -Xms1750m -Xmx1750m"

4. Fluentd DaemonSet inzetten

Nu is het tijd om containerlogboeken naar Elasticsearch te sturen. We hebben onze FluentD-naamruimte al gemaakt.

Je vindt meer informatie over FluentD door op de volgende link te klikken: https://www.fluentd.org/

4.1 ServiceAccount aanmaken

Laten we opnieuw beginnen met het maken van de RBAC-resources. We zullen de FluentD ServiceAccount voldoende toestemming geven om de cluster- en staartcontainerlogboeken te verkennen.

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

Vervolgens ClusterRole en bind het aan de vloeiende ServiceAccount.

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

Bind het nu aan het serviceaccount.

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

4.2 ConfigMap maken

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: fluentd
  labels:
    app: fluentd
spec:
  selector:
    matchLabels:
      app: fluentd
  template:
    metadata:
      labels:
        app: fluentd
    spec:
      serviceAccountName: fluentd
      containers:
        - name: fluentd
          image: "defined_in_kustomization"
          env:
            - name: FLUENT_ELASTICSEARCH_HOST
              valueFrom:
                configMapKeyRef:
                  name: fluentd-config
                  key: FLUENT_ELASTICSEARCH_HOST
            - name: FLUENT_ELASTICSEARCH_PORT
              value: "9200"
            - name: FLUENT_ELASTICSEARCH_SCHEME
              value: "http"
            - name: FLUENTD_SYSTEMD_CONF
              value: disable
            - name: K8S_NODE_NAME
              valueFrom:
                configMapKeyRef:
                  name: fluentd-config
                  key: K8S_NODE_NAME
            - name: FLUENT_ELASTICSEARCH_USER
              valueFrom:
                configMapKeyRef:
                  name: fluentd-config
                  key: FLUENT_ELASTICSEARCH_USER
            - name: FLUENT_ELASTICSEARCH_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: fluentd-credentials
                  key: FLUENT_ELASTICSEARCH_PASSWORD
          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: fluentd-config
              mountPath: /fluentd/etc
      terminationGracePeriodSeconds: 30
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
        - name: varlibdockercontainers
          hostPath:
            path: /var/lib/docker/containers
        - name: fluentd-config
          configMap:
            name: fluentd-config
            items:
              - key: fluent.conf
                path: fluent.conf

4.3 DaemonSet maken

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: fluentd
  labels:
    app: fluentd
spec:
  selector:
    matchLabels:
      app: fluentd
  template:
    metadata:
      labels:
        app: fluentd
    spec:
      serviceAccountName: fluentd
      containers:
        - name: fluentd
          image: "defined_in_kustomization"
          env:
            - name: FLUENT_ELASTICSEARCH_HOST
              valueFrom:
                configMapKeyRef:
                  name: fluentd-config
                  key: FLUENT_ELASTICSEARCH_HOST
            - name: FLUENT_ELASTICSEARCH_PORT
              value: "9200"
            - name: FLUENT_ELASTICSEARCH_SCHEME
              value: "http"
            - name: FLUENTD_SYSTEMD_CONF
              value: disable
            - name: K8S_NODE_NAME
              valueFrom:
                configMapKeyRef:
                  name: fluentd-config
                  key: K8S_NODE_NAME
            - name: FLUENT_ELASTICSEARCH_USER
              valueFrom:
                configMapKeyRef:
                  name: fluentd-config
                  key: FLUENT_ELASTICSEARCH_USER
            - name: FLUENT_ELASTICSEARCH_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: fluentd-credentials
                  key: FLUENT_ELASTICSEARCH_PASSWORD
          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: fluentd-config
              mountPath: /fluentd/etc
      terminationGracePeriodSeconds: 30
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
        - name: varlibdockercontainers
          hostPath:
            path: /var/lib/docker/containers
        - name: fluentd-config
          configMap:
            name: fluentd-config
            items:
              - key: fluent.conf
                path: fluent.conf

Het FLUENT_ELASTICSEARCH_PASSWORD is hetzelfde wachtwoord dat we hebben gedefinieerd in de Elasticsearch-configuratie (ELASTIC_PASSWORD).

Definieer de omgevingsvariabelen in het bestand ConfigMap.yaml, we definiëren ook enkele aangepaste Fluentd-configuraties. Deze configuratie wordt op de Fluentd-container gemonteerd.

Implementeer al jouw yaml-bestanden en zorg ervoor dat Fluentd zonder problemen werkt. Als Fluentd niet correct werkt, kan je de containerlogboeken volgen of de Pod/DaemonSet beschrijven.

5. Kibana-implementatie implementeren

Nu hebben we opslag (Elasticsearch) en gegevensstroom (Fluentd). Vervolgens hebben we Kibana nodig om de gegevens in te zien / te bewerken / ….

De configuratie van Kibana is bijna hetzelfde als Elasticsearch en Fluentd. We definiëren enkele omgevingsvariabelen om onze Kibana-configuratie te maken.

Hoofdzakelijk zijn deze configuraties:

  • gebruikersnaam en wachtwoord van de kibana-gebruiker
  • verbindingsinstellingen naar elastic search
  • hetzelfde Elasticsearch-certificaat is gekoppeld

5.1 Kibana referenties maken

Gebruikersnaam en paswoord van Kibana

Wanneer we Kibana lanceren, is de eerste vraag die Kibana ons stelt: "Wat is de gebruikersnaam en het wachtwoord van de Kibana-gebruiker". De gebruikersnaam is kibana_system. Het wachtwoord kan worden opgehaald vanuit de Elasticsearch-container.

Exec naar een van de Elasticsearch-containers en voer de volgende opdracht uit:

kubectl -n elasticsearch exec -ti elasticsearch-0 -- bash
 
#This will generate a random string. Save the password!
./bin/elasticsearch-reset-password -u kibana_system

Maak een SealedSecret en implementeer deze met het opgegeven wachtwoord:

kubectl -n kibana create secret generic kibana-credentials \
  --from-literal=ELASTICSEARCH_PASSWORD='XXXXX' \
  --dry-run=client -o yaml | ${KUBESEAL_BINARY} --cert ${KUBESEAL_CERT_PATH} --format yaml > SealedSecret-KibanaCredentials.yaml

Indien je geen SealedSecret-controller hebt, kan je een geheime bron maken door de volgende opdracht uit te voeren.

kubectl -n kibana create secret generic kibana-credentials \
  --from-literal=ELASTICSEARCH_PASSWORD='XXXXX' \
  --dry-run=client -o yaml > SealedSecret-KibanaCredentials.yaml

Met de bovenstaande opdracht wordt het yaml-bestand gemaakt dat in het cluster moet worden geïmplementeerd.

5.2 Dienst creëren

Nu is het tijd om de serviceresource te maken. Kibana is toegankelijk via poort 5601 en gebruikt de app: kibana-label om de doelpods van de service te selecteren.

apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: kibana
  labels:
    app: kibana
spec:
  selector:
    app: kibana
  ports:
    - name: http
      port: 80
      targetPort: 5601

5.3 Implementatie creëren

Zorg ervoor dat het ELASTICSEARCH_PASSWORD is gedefinieerd in de omgevingsvariabele en het juiste geheim leest.

We moeten ook een nieuw geheim maken van hetzelfde PEM-certificaat dat we bij de eerste stap hebben gegenereerd om later op de container te monteren.

# Create SealedSecret for the P12 file
kubectl -n kibana create secret generic elastic-certificate-pem \
  --from-file=elastic-certificates.p12 \
  --dry-run=client -o yaml | ${KUBESEAL_BINARY} --cert ${KUBESEAL_CERT_PATH} --format yaml > SealedSecret-KibanaCertificates.yaml

Indien je geen SealedSecret-controller hebt, kan je een geheime bron maken door de volgende opdracht uit te voeren.

# Create SealedSecret for the P12 file
kubectl -n kibana create secret generic elastic-certificate-pem \
  --from-file=elastic-certificates.p12 \
  --dry-run=client -o yaml > SealedSecret-KibanaCertificates.yaml

Met de bovenstaande opdracht wordt het yaml-bestand gemaakt dat in het cluster moet worden geïmplementeerd.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: kibana
  labels:
    app: kibana
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - kibana
              topologyKey: kubernetes.io/hostname
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - kibana
                topologyKey: topology.kubernetes.io/zone
      containers:
        - name: kibana
          image: docker.elastic.co/kibana/kibana:8.3.2
          resources:
             limits:
              cpu: 1000m
            requests:
              cpu: 100m
          env:
            - name: ELASTICSEARCH_HOSTS
              valueFrom:
                configMapKeyRef:
                  name: kibana-config
                  key: ELASTICSEARCH_HOSTS
            - name: SERVER_NAME
              valueFrom:
                configMapKeyRef:
                  name: kibana-config
                  key: SERVER_NAME
            - name: ELASTICSEARCH_USERNAME
              valueFrom:
                configMapKeyRef:
                  name: kibana-config
                  key: ELASTICSEARCH_USERNAME
            - name: ELASTICSEARCH_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: kibana-credentials
                  key: ELASTICSEARCH_PASSWORD
          ports:
            - containerPort: 5601
              name: http
              protocol: TCP
          volumeMounts:
            - name: kibana-certificates
              mountPath: /usr/share/kibana/config/certs
            - name: kibana-yml
              mountPath: /usr/share/kibana/config/kibana.yml
              subPath: kibana.yml
      volumes:
        - name: kibana-certificates
          secret:
            secretName: kibana-certificates
        - name: kibana-yml
          configMap:
            name: kibana-config
            items:
              - key: kibana.yml
                path: kibana.yml

5.4 ConfigMap maken

apiVersion: v1
kind: ConfigMap
metadata:
  name: kibana-config
  namespace: kibana
data:
  kibana.yml: |
    #
    # ** THIS IS AN AUTO-GENERATED FILE **
    #
     
    # Default Kibana configuration for docker target
    server.host: "0.0.0.0"
    server.shutdownTimeout: "5s"
    elasticsearch.hosts: [ "http://elasticsearch:9200" ]
    monitoring.ui.container.elasticsearch.enabled: true
    xpack.encryptedSavedObjects.encryptionKey: f9cd92d3834129433fb0404740b5e89c
    xpack.reporting.encryptionKey: e3de4fcf3fb5e6f973ce024121ead576
    xpack.security.encryptionKey: 4afebd157537e0f1b2c0b8deddff6b68
  SERVER_NAME: "kibana.example.com"
  ELASTICSEARCH_HOSTS: "http://elasticsearch.elasticsearch.svc.cluster.local:9200"
  ELASTICSEARCH_USERNAME: "kibana_system"

Implementeer alle configuraties in de repository en volg de logboeken van Kibana.

En de installatie is klaar! 🎉

Je kan nu jouw indexen toevoegen, gebruikers configureren, rollen configureren, ... en logboeken controleren!

6. Conclusie

In deze Kubernetes-zelfstudie hebben we laten zien hoe Elasticsearch, Fluentd en Kibana (EFK Stack) op een Kubernetes-cluster kunnen worden ingesteld en geconfigureerd.

Centraliseer en maak het leven van de ontwikkelaars gemakkelijk door containerlogboeken zichtbaar te maken in één gecentraliseerde logboekinfrastructuur!

Als je deze tutorial interessant vond en in de toekomst meer informatie wil over deze onderwerpen, volg ons dan op LinkedIn!