Language: English | 日本語
Kubernetes History Inspector (KHI) can visualize a wealth of information using only kube-apiserver audit logs. This tutorial demonstrates how to visualize the state of Kubernetes resources with KHI by leveraging audit logs aggregated using Loki within an easy-to-prepare Kubernetes environment set up via kind.
Before you start, ensure you have the following tools installed:
First, we'll start from creating a kind Kubernetes cluster with the audit logs enabled.
kube-apisever records actions taken by users, administrators, and system components as its audit logs. The audit policy allow us to configure the kube-apiserver what to log. Create a file named audit-policy.yaml in a new directory called audit-policy with the following content:
# audit-policy/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
# Log metadata for sensitive resources like configmaps and secrets.
rules:
- level: Metadata
resources:
- group: "" # core API group
resources: ["configmaps", "secrets"]
# Log request and response bodies for all other resources.
# This provides detailed information for KHI's timeline view.
- level: RequestResponseAudit levels:
level: Metadata: Logs request metadata (requesting user, timestamp, resource, verb, etc.) but not the request or response body.level: RequestResponse: Logs the request and response bodies as well as the request metadata. This level provides the most detailed information.
Next, create a kind configuration file (e.g., kind-config.yaml) to define the cluster structure and enable audit logging by mounting the policy file and specifying audit log paths.
# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
# Mount the audit policy directory into the control-plane node
extraMounts:
- hostPath: audit-policy/ # Directory to audit-policy.yaml on your host machine
containerPath: /etc/kubernetes/audit # Path inside the control-plane container
readOnly: true
kubeadmConfigPatches:
- |
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
metadata:
name: config
apiServer:
extraArgs:
# Tell the API server where the audit policy is
audit-policy-file: "/etc/kubernetes/audit/audit-policy.yaml"
# Tell the API server where to write audit logs
audit-log-path: "/var/log/kubernetes/audit.log"
extraVolumes:
# Mount the audit policy directory into the apiserver pod
- name: audit-config
hostPath: /etc/kubernetes/audit
mountPath: /etc/kubernetes/audit
readOnly: true
pathType: Directory
# Mount the host log directory into the apiserver pod to write logs
- name: audit-logs
hostPath: /var/log/kubernetes
mountPath: /var/log/kubernetes
readOnly: false
pathType: DirectoryOrCreate # Creates the directory if it doesn't exist
- role: worker
- role: worker
- role: workerNow, create the cluster using the configuration file:
kind create cluster --config kind-config.yamlThis command will bootstrap a Kubernetes cluster with one control-plane node and three worker nodes, configured for audit logging.
This step is optional. If you already have a running Loki instance (self-hosted or Grafana Cloud), you can configure Fluent Bit to send logs there. Otherwise, deploy a simple Loki instance within the kind cluster for this tutorial.
Create a file named loki-values.yaml for the Loki Helm chart:
# loki-values.yaml
loki:
commonConfig:
replication_factor: 1
schemaConfig:
configs:
- from: "2025-01-01"
store: tsdb
object_store: s3
schema: v13
index:
prefix: loki_index_
period: 24h
minio:
enabled: true
deploymentMode: SingleBinary
singleBinary:
replicas: 1
backend:
replicas: 0
read:
replicas: 0
write:
replicas: 0Add the Grafana Helm repository and install Loki:
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
helm install loki grafana/loki -f loki-values.yaml --namespace khi --create-namespaceWait for all Loki pods (including MinIO if enabled) to reach the Running state:
kubectl get pods -n khi -l app.kubernetes.io/instance=lokiWe'll use Fluent Bit, a lightweight log processor and forwarder, deployed as a DaemonSet to collect logs from all nodes (including the control-plane) and send them to Loki.
Create a file named fluentbit-values.yaml:
# fluentbit-values.yaml
# Ensure Fluent Bit runs on the control-plane node to access audit logs
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
config:
inputs: |
[INPUT]
Name tail
Path /var/log/containers/*.log
multiline.parser cri
Tag kube.*
Read_from_Head On
Mem_Buf_Limit 5MB
Skip_Long_Lines On
[INPUT]
Name tail
Tag kubevar.audit
Path /mnt/audit/audit.log
Parser json
Read_from_Head On
DB /var/log/flb_audit.db
Mem_Buf_Limit 16MB
Buffer_Max_Size 8MB
Buffer_Chunk_Size 8MB
Refresh_Interval 10
filters: |
# Filter to add Kubernetes metadata to container logs
[FILTER]
Name kubernetes
Match kube.*
Merge_Log On
Keep_Log Off
K8S-Logging.Parser On
K8S-Logging.Exclude On
# Filter to add a 'job' label to audit logs for easier querying in Loki
[FILTER]
Name modify
Match kubevar.audit
Add job audit
outputs: |
[OUTPUT]
Name loki
Match *
Host loki-gateway.khi.svc.cluster.local
Port 80
Label_Keys $job,$kubernetes_namespace_name, $kubernetes_pod_name, $kubernetes_container_name
Tenant_Id KHI
# Volume to mount the kind-node's audit log directory
extraVolumes:
- name: auditlog
hostPath:
path: /var/log/kubernetes # Path on the node where audit logs are written
# Mount the audit log volume into the Fluent Bit container
extraVolumeMounts:
- name: auditlog
mountPath: /mnt/audit # Path inside the Fluent Bit container
readOnly: trueAdd the Fluent Helm repository and install Fluent Bit:
helm repo add fluent https://fluent.github.io/helm-charts
helm repo update
helm install fluentbit fluent/fluent-bit --values fluentbit-values.yaml --namespace khiWait for all Fluent Bit pods to become Running:
kubectl get pods -n khi -l app.kubernetes.io/instance=fluentbitTo have some data to inspect, let's perform some basic operations on the cluster. For example, create, scale, and delete an Nginx deployment:
# Create a deployment with 3 replicas
kubectl create deployment nginx --image nginx --replicas 3
# Scale up the deployment
kubectl scale deployment nginx --replicas 5
# Scale down the deployment
kubectl scale deployment nginx --replicas 1
# Delete the deployment
kubectl delete deployment nginxThe api server produces audit logs by the operations then Fluent bit collects them to forward them to Loki.
Now, retrieve the collected audit logs from Loki using logcli.
Make the Loki service accessible on your local machine:
kubectl port-forward --namespace khi service/loki-gateway 8000:80Keep this command running in a separate terminal.
Use logcli to query Loki for the audit logs and save them to a file named audit_log_export.jsonl. Adjust the --from and --to timestamps to encompass the time you ran the kubectl commands.
logcli query '{job="audit"}' \
--org-id=KHI \
--timezone=UTC \
--from="2025-04-08T00:00:00Z" \
--to="2025-04-09T00:00:00Z" \
--output=raw \
--limit=0 \
--addr=http://localhost:8000 \
-q > audit_log_export.jsonlExplanation:
'{job="audit"}': Selects logs with the labeljobset toaudit(added by our Fluent Bit filter).--org-id=KHI: Specify the tenant ID we configured in Fluent Bit.--timezone=UTC: Use UTC timezone for timestamps.--from,--to: Define the time range for the query (adjust as needed). Use RFC3339 format (e.g., YYYY-MM-DDTHH:MM:SSZ).--output=raw: Output only the log lines without timestamps or labels.--limit=0: Retrieve all matching log lines (no limit).--addr=http://localhost:8000: The address of the port-forwarded Loki service.
Note
We use --output=raw here because in this tutorial Fluent Bit was configured to send the raw JSON audit log line directly to Loki. If your logging pipeline parses the JSON and stores it as structured metadata within Loki (e.g., using Loki's JSON parser or pipeline stages), you cannot use --output=raw. In that case, you would need to query Loki and reconstruct the original JSONL format required by KHI yourself, potentially using logcli with a different output format (like --output=json) and processing the results with tools like jq.*
Finally, let's use KHI to inspect the exported audit logs.
Start the KHI server using Docker:
# You don't need to pass Google Cloud credentials to the container.
docker run --rm -p 127.0.0.1:8080:8080 gcr.io/kubernetes-history-inspector/release:latestYou should see output indicating the server is running:
global > INFO Initializing Kubernetes History Inspector...
global > INFO Starting Kubernetes History Inspector server...
Starting KHI server with listening 0.0.0.0:8080
For Cloud Shell users:
Click this address >> http://localhost:8080 << Click this address
(For users of the other environments: Access http://localhost:8080 with your browser. Consider SSH port-forwarding when you run KHI over SSH.)
Open your web browser and navigate to http://localhost:8080.
- Click on "New Inspection".
- Select "OSS Kubernetes Cluster" as the inspection type.
- In the "Input Parameters" section, under "File Upload", click "Browse" or drag-and-drop the
audit_log_export.jsonlfile you created. - Click "Upload" button, and wait for the file to be uploaded.
- Click the "Run" button.
- Wait for the inspection process to complete.
Once the inspection is finished, click the "Open" button to view the results. Explore the different views, especially the "Timeline" view. You should be able to see the events related to the Nginx deployment creation, scaling, and deletion, illustrating how the cluster state changed over time.
To remove the resources created during this tutorial, delete the kind cluster:
kind delete clusterThis concludes the tutorial on using KHI with an OSS Kubernetes cluster and Loki. You can adapt these steps to integrate KHI with your existing logging infrastructure.


