ELK Stack — Kubernetes Logging Exercises

12 minute read

Practice Exercises

Five hands-on exercises that cover everything from this chapter. Each builds on real-world Kubernetes logging scenarios with increasing difficulty.

Prerequisites: A running Kubernetes cluster (Minikube, kind, or managed), kubectl configured, and a basic understanding of Elasticsearch and Kibana.


Exercise 1 — Deploy Filebeat DaemonSet

Difficulty: ⭐⭐ Beginner

Scenario

Your team has a Kubernetes cluster with several microservices running across 3 nodes. Currently, developers SSH into nodes to read logs. Your task is to deploy Filebeat as a DaemonSet so that all container logs are shipped to Elasticsearch automatically.

Requirements

  1. Create a namespace called elastic-system
  2. Create a Filebeat ConfigMap with the container input type reading from /var/log/containers/*.log
  3. Create a ServiceAccount, ClusterRole, and ClusterRoleBinding for Filebeat
  4. Deploy Filebeat as a DaemonSet with proper volume mounts
  5. Configure the add_kubernetes_metadata processor
  6. Verify Filebeat is running on all nodes
  7. Confirm logs are appearing in Elasticsearch
Show Solution

Step 1 — Create the namespace:

kubectl create namespace elastic-system

Step 2 — Apply the complete manifest:

# filebeat-complete.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-config
  namespace: elastic-system
data:
  filebeat.yml: |-
    filebeat.inputs:
      - type: container
        paths:
          - /var/log/containers/*.log
        processors:
          - add_kubernetes_metadata:
              host: ${NODE_NAME}
              matchers:
                - logs_path:
                    logs_path: "/var/log/containers/"

    output.elasticsearch:
      hosts: ["http://elasticsearch.elastic-system:9200"]
      index: "k8s-logs-%{+yyyy.MM.dd}"

    setup.template.name: "k8s-logs"
    setup.template.pattern: "k8s-logs-*"
    setup.ilm.enabled: false

    logging.level: info
    logging.to_stderr: true
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: filebeat
  namespace: elastic-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: filebeat
rules:
  - apiGroups: [""]
    resources: ["namespaces", "pods", "nodes"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["replicasets"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: filebeat
subjects:
  - kind: ServiceAccount
    name: filebeat
    namespace: elastic-system
roleRef:
  kind: ClusterRole
  name: filebeat
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: filebeat
  namespace: elastic-system
spec:
  selector:
    matchLabels:
      app: filebeat
  template:
    metadata:
      labels:
        app: filebeat
    spec:
      serviceAccountName: filebeat
      terminationGracePeriodSeconds: 30
      containers:
        - name: filebeat
          image: docker.elastic.co/beats/filebeat:8.13.0
          args: ["-c", "/etc/filebeat/filebeat.yml", "-e"]
          env:
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
          securityContext:
            runAsUser: 0
          resources:
            limits:
              memory: 200Mi
              cpu: 200m
            requests:
              memory: 100Mi
              cpu: 100m
          volumeMounts:
            - name: config
              mountPath: /etc/filebeat
              readOnly: true
            - name: varlogcontainers
              mountPath: /var/log/containers
              readOnly: true
            - name: varlogpods
              mountPath: /var/log/pods
              readOnly: true
            - name: data
              mountPath: /usr/share/filebeat/data
      volumes:
        - name: config
          configMap:
            name: filebeat-config
        - name: varlogcontainers
          hostPath:
            path: /var/log/containers
        - name: varlogpods
          hostPath:
            path: /var/log/pods
        - name: data
          hostPath:
            path: /var/lib/filebeat-data
            type: DirectoryOrCreate
kubectl apply -f filebeat-complete.yaml

Step 3 — Verify:

# Check DaemonSet
kubectl get daemonset filebeat -n elastic-system

# Check pods are running on all nodes
kubectl get pods -n elastic-system -l app=filebeat -o wide

# Check Filebeat logs for errors
kubectl logs -l app=filebeat -n elastic-system --tail=20

# Verify indices in Elasticsearch
curl -s http://localhost:9200/_cat/indices/k8s-logs-* | head -5

Exercise 2 — Autodiscover for Multi-App Logging

Difficulty: ⭐⭐⭐ Intermediate

Scenario

Your cluster runs three applications: an Nginx reverse proxy, a Java Spring Boot API, and a Python worker. Each produces logs in a different format. You need to configure Filebeat autodiscover so that each application’s logs are parsed correctly — Nginx access logs are dissected, Java stack traces are grouped into multi-line events, and Python logs are parsed as JSON.

Requirements

  1. Deploy three sample applications (provided below)
  2. Configure Filebeat autodiscover with the kubernetes provider
  3. Use templates with conditions to match each app by its Kubernetes labels
  4. Parse Nginx access logs using the dissect processor
  5. Handle Java multi-line stack traces
  6. Parse Python JSON logs
  7. Verify in Kibana that each application’s logs have structured fields

Sample Applications

# sample-apps.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-proxy
  namespace: demo
  labels:
    app: nginx-proxy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-proxy
  template:
    metadata:
      labels:
        app: nginx-proxy
        log-format: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:alpine
          ports:
            - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: java-api
  namespace: demo
  labels:
    app: java-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: java-api
  template:
    metadata:
      labels:
        app: java-api
        log-format: java
    spec:
      containers:
        - name: api
          image: openjdk:17-slim
          command: ["sh", "-c", "while true; do echo 'INFO  [main] com.example.App - Request processed'; sleep 5; done"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: python-worker
  namespace: demo
  labels:
    app: python-worker
spec:
  replicas: 1
  selector:
    matchLabels:
      app: python-worker
  template:
    metadata:
      labels:
        app: python-worker
        log-format: json
    spec:
      containers:
        - name: worker
          image: python:3.12-slim
          command: ["python3", "-c", "import json,time\nwhile True:\n    print(json.dumps({'level':'INFO','service':'worker','message':'Job completed','duration_ms':142})); time.sleep(5)"]
Show Solution

Step 1 — Deploy sample apps:

kubectl create namespace demo
kubectl apply -f sample-apps.yaml

Step 2 — Update Filebeat ConfigMap with autodiscover:

apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-config
  namespace: elastic-system
data:
  filebeat.yml: |-
    filebeat.autodiscover:
      providers:
        - type: kubernetes
          node: ${NODE_NAME}
          templates:
            # Nginx access logs — dissect into structured fields
            - condition:
                equals:
                  kubernetes.labels.log-format: "nginx"
              config:
                - type: container
                  paths:
                    - /var/log/containers/*-${data.container.id}.log
                  processors:
                    - dissect:
                        tokenizer: '%{client_ip} - %{user} [%{timestamp}] "%{method} %{path} %{protocol}" %{status} %{bytes} "%{referrer}" "%{user_agent}"'
                        field: "message"
                        target_prefix: "nginx"
                    - add_kubernetes_metadata:
                        host: ${NODE_NAME}
                        matchers:
                          - logs_path:
                              logs_path: "/var/log/containers/"

            # Java logs — multi-line for stack traces
            - condition:
                equals:
                  kubernetes.labels.log-format: "java"
              config:
                - type: container
                  paths:
                    - /var/log/containers/*-${data.container.id}.log
                  multiline.type: pattern
                  multiline.pattern: '^\s+(at|\.{3}|Caused by)'
                  multiline.negate: false
                  multiline.match: after
                  processors:
                    - add_kubernetes_metadata:
                        host: ${NODE_NAME}
                        matchers:
                          - logs_path:
                              logs_path: "/var/log/containers/"

            # Python JSON logs — parse JSON body
            - condition:
                equals:
                  kubernetes.labels.log-format: "json"
              config:
                - type: container
                  paths:
                    - /var/log/containers/*-${data.container.id}.log
                  json.keys_under_root: true
                  json.add_error_key: true
                  json.message_key: message
                  processors:
                    - add_kubernetes_metadata:
                        host: ${NODE_NAME}
                        matchers:
                          - logs_path:
                              logs_path: "/var/log/containers/"

    output.elasticsearch:
      hosts: ["http://elasticsearch.elastic-system:9200"]
      index: "k8s-logs-%{+yyyy.MM.dd}"

    setup.template.name: "k8s-logs"
    setup.template.pattern: "k8s-logs-*"
    setup.ilm.enabled: false

Step 3 — Apply and restart:

kubectl apply -f filebeat-configmap.yaml

# Restart Filebeat pods to pick up new config
kubectl rollout restart daemonset/filebeat -n elastic-system

Step 4 — Verify:

# Check Filebeat logs for autodiscover events
kubectl logs -l app=filebeat -n elastic-system | grep -i "autodiscover"

# Query Elasticsearch for nginx structured fields
curl -s http://localhost:9200/k8s-logs-*/_search -H "Content-Type: application/json" -d '{
  "query": { "exists": { "field": "nginx.status" } },
  "size": 1
}' | python3 -m json.tool

# Query for Python JSON fields
curl -s http://localhost:9200/k8s-logs-*/_search -H "Content-Type: application/json" -d '{
  "query": { "match": { "service": "worker" } },
  "size": 1
}' | python3 -m json.tool

Exercise 3 — Deploy ECK Operator + Elasticsearch + Kibana

Difficulty: ⭐⭐⭐ Intermediate

Scenario

Instead of managing Elasticsearch and Kibana with raw StatefulSets and Deployments, you want to use the ECK operator for production-grade management. Deploy a 3-node Elasticsearch cluster and a single Kibana instance using ECK CRDs.

Requirements

  1. Install ECK CRDs and the operator
  2. Deploy a 3-node Elasticsearch cluster with 50Gi storage per node
  3. Deploy Kibana connected to the Elasticsearch cluster
  4. Retrieve the elastic user password
  5. Port-forward Kibana and log in
  6. Verify cluster health is green
Show Solution

Step 1 — Install ECK:

# Install CRDs
kubectl create -f https://download.elastic.co/downloads/eck/2.12.1/crds.yaml

# Install operator
kubectl apply -f https://download.elastic.co/downloads/eck/2.12.1/operator.yaml

# Verify
kubectl get pods -n elastic-system -l control-plane=elastic-operator

Step 2 — Deploy Elasticsearch:

# elasticsearch.yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: elasticsearch
  namespace: elastic-system
spec:
  version: 8.13.0
  nodeSets:
    - name: default
      count: 3
      config:
        node.store.allow_mmap: false
      podTemplate:
        spec:
          containers:
            - name: elasticsearch
              resources:
                requests:
                  memory: 2Gi
                  cpu: 1
                limits:
                  memory: 2Gi
                  cpu: 2
      volumeClaimTemplates:
        - metadata:
            name: elasticsearch-data
          spec:
            accessModes: ["ReadWriteOnce"]
            resources:
              requests:
                storage: 50Gi
kubectl apply -f elasticsearch.yaml

# Wait for green health
kubectl get elasticsearch -n elastic-system -w

Step 3 — Deploy Kibana:

# kibana.yaml
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: kibana
  namespace: elastic-system
spec:
  version: 8.13.0
  count: 1
  elasticsearchRef:
    name: elasticsearch
  podTemplate:
    spec:
      containers:
        - name: kibana
          resources:
            requests:
              memory: 1Gi
              cpu: 500m
            limits:
              memory: 1Gi
              cpu: 1
kubectl apply -f kibana.yaml
kubectl get kibana -n elastic-system -w

Step 4 — Get password and access:

# Get the elastic password
PASSWORD=$(kubectl get secret elasticsearch-es-elastic-user \
  -n elastic-system \
  -o go-template='{{.data.elastic | base64decode}}')
echo "Elastic password: $PASSWORD"

# Port-forward Kibana
kubectl port-forward svc/kibana-kb-http -n elastic-system 5601:5601

# Open https://localhost:5601
# Login: elastic / <password from above>

Step 5 — Verify cluster health:

curl -sk https://localhost:9200/_cluster/health \
  -u "elastic:$PASSWORD" | python3 -m json.tool

Expected output:

{
    "cluster_name": "elasticsearch",
    "status": "green",
    "number_of_nodes": 3,
    "number_of_data_nodes": 3,
    "active_primary_shards": 5,
    "active_shards": 10
}

Exercise 4 — Fluent Bit as Filebeat Alternative

Difficulty: ⭐⭐⭐⭐ Advanced

Scenario

Your ops team wants to evaluate Fluent Bit as a lighter alternative to Filebeat. Deploy Fluent Bit as a DaemonSet, configure it to enrich logs with Kubernetes metadata, and ship them to Elasticsearch. Compare resource usage with Filebeat.

Requirements

  1. Create a logging namespace
  2. Deploy Fluent Bit as a DaemonSet with RBAC
  3. Configure the kubernetes filter for metadata enrichment
  4. Configure the es output to send to Elasticsearch
  5. Parse CRI log format with a custom parser
  6. Generate test logs and verify they appear in Elasticsearch
  7. Compare memory and CPU usage between Fluent Bit and Filebeat pods
Show Solution

Step 1 — Create namespace and deploy:

kubectl create namespace logging

Step 2 — Apply the complete Fluent Bit manifest:

# fluent-bit-complete.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: logging
data:
  fluent-bit.conf: |
    [SERVICE]
        Flush         5
        Daemon        Off
        Log_Level     info
        Parsers_File  parsers.conf
        HTTP_Server   On
        HTTP_Listen   0.0.0.0
        HTTP_Port     2020

    [INPUT]
        Name              tail
        Tag               kube.*
        Path              /var/log/containers/*.log
        Parser            cri
        DB                /var/log/flb_kube.db
        Mem_Buf_Limit     5MB
        Skip_Long_Lines   On
        Refresh_Interval  10

    [FILTER]
        Name                kubernetes
        Match               kube.*
        Kube_URL            https://kubernetes.default.svc:443
        Kube_CA_File        /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        Kube_Token_File     /var/run/secrets/kubernetes.io/serviceaccount/token
        Kube_Tag_Prefix     kube.var.log.containers.
        Merge_Log           On
        K8S-Logging.Parser  On

    [OUTPUT]
        Name            es
        Match           *
        Host            elasticsearch-es-http.elastic-system
        Port            9200
        Logstash_Format On
        Logstash_Prefix fluent-bit-k8s
        Retry_Limit     False
        Replace_Dots    On
        tls             Off
        Suppress_Type_Name On

  parsers.conf: |
    [PARSER]
        Name        cri
        Format      regex
        Regex       ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<message>.*)$
        Time_Key    time
        Time_Format %Y-%m-%dT%H:%M:%S.%L%z
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluent-bit
  namespace: logging
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluent-bit
rules:
  - apiGroups: [""]
    resources: ["namespaces", "pods"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: fluent-bit
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: fluent-bit
subjects:
  - kind: ServiceAccount
    name: fluent-bit
    namespace: logging
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: logging
spec:
  selector:
    matchLabels:
      app: fluent-bit
  template:
    metadata:
      labels:
        app: fluent-bit
    spec:
      serviceAccountName: fluent-bit
      terminationGracePeriodSeconds: 10
      containers:
        - name: fluent-bit
          image: fluent/fluent-bit:3.0
          ports:
            - containerPort: 2020
              name: http-metrics
          volumeMounts:
            - name: config
              mountPath: /fluent-bit/etc/
            - name: varlog
              mountPath: /var/log
              readOnly: true
            - name: varlogcontainers
              mountPath: /var/log/containers
              readOnly: true
            - name: varlogpods
              mountPath: /var/log/pods
              readOnly: true
          resources:
            limits:
              memory: 100Mi
              cpu: 200m
            requests:
              memory: 50Mi
              cpu: 50m
      volumes:
        - name: config
          configMap:
            name: fluent-bit-config
        - name: varlog
          hostPath:
            path: /var/log
        - name: varlogcontainers
          hostPath:
            path: /var/log/containers
        - name: varlogpods
          hostPath:
            path: /var/log/pods
kubectl apply -f fluent-bit-complete.yaml

Step 3 — Generate test logs:

# Deploy a log generator
kubectl run log-generator --image=busybox --restart=Never \
  --command -- sh -c 'while true; do echo "{\"level\":\"INFO\",\"msg\":\"test log from fluent-bit exercise\",\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"; sleep 2; done'

Step 4 — Verify in Elasticsearch:

# Check Fluent Bit logs for errors
kubectl logs -l app=fluent-bit -n logging --tail=20

# Check for new indices
curl -s http://localhost:9200/_cat/indices/fluent-bit-k8s-* | head -5

# Search for test logs
curl -s http://localhost:9200/fluent-bit-k8s-*/_search -H "Content-Type: application/json" -d '{
  "query": { "match": { "message": "test log from fluent-bit" } },
  "size": 3
}' | python3 -m json.tool

Step 5 — Compare resource usage:

# Check Fluent Bit memory and CPU
kubectl top pods -n logging -l app=fluent-bit

# Check Filebeat memory and CPU (if deployed)
kubectl top pods -n elastic-system -l app=filebeat

# Expected comparison:
# Fluent Bit: ~3-10 MB memory, ~5m CPU
# Filebeat:   ~30-60 MB memory, ~20m CPU

Exercise 5 — Implement Log Retention with ILM for K8s Logs

Difficulty: ⭐⭐⭐⭐⭐ Advanced

Scenario

Your production cluster generates ~50 GB of logs per day. Management requires 90-day retention for production logs and 7-day retention for development logs. You need to implement ILM policies, use the hot-warm-cold architecture, and set up snapshots before deletion.

Requirements

  1. Create an ILM policy for production logs: hot (2 days) → warm (14 days) → cold (30 days) → delete (90 days)
  2. Create an ILM policy for development logs: hot (1 day) → delete (7 days)
  3. Create index templates for k8s-production-* and k8s-development-*
  4. Bootstrap the initial indices with write aliases
  5. Configure Filebeat to route logs by namespace
  6. Create a snapshot repository and SLM policy
  7. Verify ILM policies are applied and indices transition correctly
Show Solution

Step 1 — Create ILM policies:

# Production ILM policy
curl -X PUT "http://localhost:9200/_ilm/policy/k8s-production-policy" \
  -H "Content-Type: application/json" -d '{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_primary_shard_size": "30gb",
            "max_age": "1d"
          },
          "set_priority": { "priority": 100 }
        }
      },
      "warm": {
        "min_age": "2d",
        "actions": {
          "shrink": { "number_of_shards": 1 },
          "forcemerge": { "max_num_segments": 1 },
          "allocate": { "require": { "data": "warm" } },
          "set_priority": { "priority": 50 }
        }
      },
      "cold": {
        "min_age": "14d",
        "actions": {
          "allocate": { "require": { "data": "cold" } },
          "set_priority": { "priority": 0 }
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": { "delete": {} }
      }
    }
  }
}'

# Development ILM policy
curl -X PUT "http://localhost:9200/_ilm/policy/k8s-development-policy" \
  -H "Content-Type: application/json" -d '{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_primary_shard_size": "20gb",
            "max_age": "1d"
          },
          "set_priority": { "priority": 100 }
        }
      },
      "delete": {
        "min_age": "7d",
        "actions": { "delete": {} }
      }
    }
  }
}'

Step 2 — Create index templates:

# Production template
curl -X PUT "http://localhost:9200/_index_template/k8s-production-template" \
  -H "Content-Type: application/json" -d '{
  "index_patterns": ["k8s-production-*"],
  "template": {
    "settings": {
      "index.lifecycle.name": "k8s-production-policy",
      "index.lifecycle.rollover_alias": "k8s-production",
      "number_of_shards": 2,
      "number_of_replicas": 1
    }
  }
}'

# Development template
curl -X PUT "http://localhost:9200/_index_template/k8s-development-template" \
  -H "Content-Type: application/json" -d '{
  "index_patterns": ["k8s-development-*"],
  "template": {
    "settings": {
      "index.lifecycle.name": "k8s-development-policy",
      "index.lifecycle.rollover_alias": "k8s-development",
      "number_of_shards": 1,
      "number_of_replicas": 0
    }
  }
}'

Step 3 — Bootstrap initial indices:

# Production
curl -X PUT "http://localhost:9200/k8s-production-000001" \
  -H "Content-Type: application/json" -d '{
  "aliases": {
    "k8s-production": { "is_write_index": true }
  }
}'

# Development
curl -X PUT "http://localhost:9200/k8s-development-000001" \
  -H "Content-Type: application/json" -d '{
  "aliases": {
    "k8s-development": { "is_write_index": true }
  }
}'

Step 4 — Configure Filebeat to route by namespace:

apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-config
  namespace: elastic-system
data:
  filebeat.yml: |-
    filebeat.inputs:
      - type: container
        paths:
          - /var/log/containers/*.log
        processors:
          - add_kubernetes_metadata:
              host: ${NODE_NAME}
              matchers:
                - logs_path:
                    logs_path: "/var/log/containers/"

    output.elasticsearch:
      hosts: ["http://elasticsearch.elastic-system:9200"]
      indices:
        - index: "k8s-production"
          when.contains:
            kubernetes.namespace: "production"
        - index: "k8s-development"
          when.contains:
            kubernetes.namespace: "development"
        - index: "k8s-logs-%{+yyyy.MM.dd}"

    setup.ilm.enabled: false
    setup.template.enabled: false
kubectl apply -f filebeat-configmap.yaml
kubectl rollout restart daemonset/filebeat -n elastic-system

Step 5 — Set up snapshot repository and SLM:

# Register snapshot repository (example: filesystem for local testing)
curl -X PUT "http://localhost:9200/_snapshot/k8s-backup" \
  -H "Content-Type: application/json" -d '{
  "type": "fs",
  "settings": {
    "location": "/usr/share/elasticsearch/backups"
  }
}'

# Create SLM policy
curl -X PUT "http://localhost:9200/_slm/policy/nightly-k8s-snapshots" \
  -H "Content-Type: application/json" -d '{
  "schedule": "0 30 2 * * ?",
  "name": "<k8s-snap-{now/d}>",
  "repository": "k8s-backup",
  "config": {
    "indices": ["k8s-production-*", "k8s-development-*"],
    "ignore_unavailable": true,
    "include_global_state": false
  },
  "retention": {
    "expire_after": "90d",
    "min_count": 5,
    "max_count": 50
  }
}'

Step 6 — Verify:

# Check ILM policy status
curl -s "http://localhost:9200/k8s-production-000001/_ilm/explain" | python3 -m json.tool

# Check ILM policies exist
curl -s "http://localhost:9200/_ilm/policy/k8s-production-policy" | python3 -m json.tool

# Check index aliases
curl -s "http://localhost:9200/_aliases" | python3 -m json.tool

# Manually trigger SLM for testing
curl -X POST "http://localhost:9200/_slm/policy/nightly-k8s-snapshots/_execute"

# List snapshots
curl -s "http://localhost:9200/_snapshot/k8s-backup/_all" | python3 -m json.tool

Summary

Exercise Skill Practiced Difficulty
1 Filebeat DaemonSet basics ⭐⭐
2 Autodiscover + multi-format parsing ⭐⭐⭐
3 ECK operator deployment ⭐⭐⭐
4 Fluent Bit as alternative shipper ⭐⭐⭐⭐
5 ILM + retention + snapshots ⭐⭐⭐⭐⭐

Back to Kubernetes Logging — Chapter Home