π‘ Local Monitoring Stack for Kubernetes
π‘ Local Monitoring Stack for Kubernetes (Prometheus + Grafana + Kafka)
https://github.com/leobip/monitoring.git
Welcome! π This project sets up a local monitoring stack based on Prometheus, Grafana, and Kafka (with Kafka UI), using Helm charts and persistent volumes for durability.
Whether youβre experimenting with Kubernetes, developing custom controllers, or just want to see your clusterβs activity in real time β this guide has got you covered. By the end, youβll have a working environment where:
Prometheus collects metrics from your apps and infrastructure π§²
Grafana helps you visualize those metrics with beautiful dashboards π
Kafka acts as a telemetry backbone, and Kafka-UI lets you explore the events flowing through it π
This setup runs entirely on your local machine, making it ideal for testing and development β no cloud account or external services required!
π What Youβll Get
Once deployed, your local monitoring stack will include:
β A Prometheus instance, scraping metrics on port 30090
β A Grafana dashboard, accessible at localhost:30095
β A Kafka broker with persistent volumes
β Kafka UI at localhost:30096 to inspect topics and messages
β A health check script to ensure everything is up and running
π¦ Prometheus + Grafana
Weβll use the official Helm charts from Bitnami and Prometheus Community, with a few tweaks for local development and persistence. See values files for config details.
π§± Kafka + Kafka UI
This section helps you install Kafka in plaintext mode, along with a lightweight UI to browse topics and messages.
Persistent volumes are enabled so your topics and messages stick around across Minikube restarts.
π§± Folder Structure
monitoring/
βββ deploy-all.sh # Script to install all components
βββ pv/ # Persistent volume manifests
β βββ kafka-pv.yaml
β βββ prometheus-pv.yaml
β βββ grafana-pv.yaml
βββ values/ # Helm values for each component
βββ kafka-values.yaml
βββ kafka-ui-values.yaml
βββ prometheus-values.yaml
βββ grafana-values.yaml
π οΈ Prerequisites
Make sure you have the following installed:
This setup assumes youβre running Kubernetes locally with Minikube.
π Installation
1.β Start Minikube
minikube start --memory=4g --cpus=2
2.- Add Helm repositories
- This step is added in the script: Just uncomment de section
3.- Run the install script
cd monitoring
chmod +x deploy-all.sh
./deploy-all.sh
- This script will:
- Create a monitoring namespace.
- Apply persistent volumes from pv/.
π Access to Monitoring Tools
| Tool | External Access (NodePort) | Internal Access (Cluster DNS) | Important Notes |
|---|---|---|---|
| Grafana | http://192.168.49.2:30095 | grafana.monitoring.svc.cluster.local |
- User: admin - Get password: kubectl get secret -n monitoring grafana -o jsonpath="{.data.admin-password}" \| base64 --decode |
| Kafka | See ports with:kubectl get svc -n monitoring -l "app.kubernetes.io/instance=kafka,app.kubernetes.io/component=kafka,pod" -o jsonpath='{.items[*].spec.ports[0].nodePort}' |
- Client: kafka.monitoring.svc.cluster.local:9092- Brokers: kafka-controller-0/1/2.kafka-controller-headless.monitoring.svc.cluster.local:9092 |
- KRaft enabled - EXTERNAL listener enabled - Run a client with: kubectl run kafka-client --rm -it --image docker.io/bitnami/kafka:4.0.0-debian-12-r8 -n monitoring -- bash |
| Prometheus | Run:export NODE_PORT=$(kubectl get svc -n monitoring prometheus-server -o jsonpath="{.spec.ports[0].nodePort}")export NODE_IP=$(kubectl get nodes -o jsonpath="{.items[0].status.addresses[0].address}")echo http://$NODE_IP:$NODE_PORT |
prometheus-server.monitoring.svc.cluster.local |
- Server and AlertManager available - AlertManager: port-forward: kubectl port-forward -n monitoring svc/prometheus-alertmanager 9093- PushGateway: port-forward: kubectl port-forward -n monitoring svc/prometheus-prometheus-pushgateway 9091 |
β Additional Notes
- Minikube IP: 192.168.49.2 (verify with minikube ip if needed)
- Namespace: monitoring
- PVCs applied: grafana-pvc, kafka-pv, prometheus-pvc
π― Verify Your Setup
Letβs make sure everything is working! Youβll check that Prometheus, Grafana, Kafka, and Kafka UI are all running and accessible via NodePorts.
π Accessing Tools from Host (Minikube + Docker on macOS)
- By default, services exposed via NodePort in Minikube may not be directly accessible from your host when using the Docker driver on macOS. This is due to networking limitations: the NodePort is exposed inside the Minikube VM/container, not on your host machineβs network.
Get the name of the tools pods
β― kubectl get svc -n monitoring
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
grafana NodePort 10.102.51.68 <none> 80:30095/TCP 29m
kafka ClusterIP 10.100.118.216 <none> 9092/TCP,9095/TCP 46m
kafka-controller-0-external NodePort 10.109.227.109 192.168.49.2 9094:30092/TCP 46m
kafka-controller-1-external NodePort 10.97.149.130 192.168.49.2 9094:30093/TCP 46m
kafka-controller-2-external NodePort 10.99.194.34 192.168.49.2 9094:30094/TCP 46m
kafka-controller-headless ClusterIP None <none> 9094/TCP,9092/TCP,9093/TCP 46m
kafka-jmx-metrics ClusterIP 10.99.29.148 <none> 5556/TCP 46m
prometheus-alertmanager ClusterIP 10.103.180.130 <none> 9093/TCP 46m
prometheus-alertmanager-headless ClusterIP None <none> 9093/TCP 46m
prometheus-kube-state-metrics ClusterIP 10.102.171.116 <none> 8080/TCP 46m
prometheus-prometheus-node-exporter ClusterIP 10.97.14.115 <none> 9100/TCP 46m
prometheus-prometheus-pushgateway ClusterIP 10.101.189.12 <none> 9091/TCP 46m
prometheus-server NodePort 10.104.67.195 <none> 80:30090/TCP 46m
Example
kubectl get svc -n monitoring grafana
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
grafana NodePort 10.102.51.68 <none> 80:30095/TCP 34m
kubectl get svc -n monitoring prometheus-server
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
prometheus NodePort 10.98.27.101 <none> 9090:31090/TCP 45m
kubectl get svc -n monitoring kafka-ui
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kafka-ui NodePort 10.96.220.89 <none> 8080:30096/TCP 19m
# β οΈ IP may change on restart (check again)
minikube ip
192.168.49.2
- You might expect
curl <http://192.168.49.2:30095>to work, but it doesnβt respond.
π§ͺ Option 1: minikube service (Temporary)
# Grafana
minikube service grafana -n monitoring
# Prometheus
minikube service prometheus-server -n monitoring
# Kafka-ui
minikube service kafka-ui -n monitoring
- This opens a temporary proxy and shows a localhost URL like:
# Grafana
|------------|---------|-------------|---------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|------------|---------|-------------|---------------------------|
| monitoring | grafana | service/80 | http://192.168.49.2:30095 |
|------------|---------|-------------|---------------------------|
π Starting tunnel for service grafana.
|------------|---------|-------------|------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|------------|---------|-------------|------------------------|
| monitoring | grafana | | http://127.0.0.1:56851 |
|------------|---------|-------------|------------------------|
π Opening service monitoring/grafana in default browser...
β Because you are using a Docker driver on darwin, the terminal needs to be open to run it.
# Prometheus
|------------|-------------------|-------------|---------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|------------|-------------------|-------------|---------------------------|
| monitoring | prometheus-server | http/80 | http://192.168.49.2:30090 |
|------------|-------------------|-------------|---------------------------|
π Starting tunnel for service prometheus-server.
|------------|-------------------|-------------|------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|------------|-------------------|-------------|------------------------|
| monitoring | prometheus-server | | http://127.0.0.1:56409 |
|------------|-------------------|-------------|------------------------|
π Opening service monitoring/prometheus-server in default browser...
β Because you are using a Docker driver on darwin, the terminal needs to be open to run it.
# kafka
|------------|----------|-------------|---------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|------------|----------|-------------|---------------------------|
| monitoring | kafka-ui | http/8080 | http://192.168.49.2:30096 |
|------------|----------|-------------|---------------------------|
π Starting tunnel for service kafka-ui.
|------------|----------|-------------|------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|------------|----------|-------------|------------------------|
| monitoring | kafka-ui | | http://127.0.0.1:64903 |
|------------|----------|-------------|------------------------|
- β
Works immediately, opens browser
- β Needs terminal to stay open (as it runs a local tunnel)
- β Not script-friendly or persistent
persistent
π Option 2: kubectl port-forward (Persistent while running)
- You can forward the Grafana service to a local port
- With Lens, Openlens, K9s, etc
- Or via kubectl cmd in terminal
kubectl port-forward -n monitoring svc/grafana 30095:80
kubectl port-forward -n monitoring svc/prometheus 9090:9090
kubectl port-forward svc/kafka-ui 8080:8080 -n monitoring
-
NOTE: Yopu have to stop the temporary proxy with ctrl-c before stop minikube, and execute again on restart
-
Then visit (In this example):
# grafana
http://localhost:30095
# prometheus
http://localhost:9090
# kafka
http://localhost:9093
- Login
- Grafana:
- user: admin
- password: admin***
- Grafana:
- β
Works reliably
- β Still requires the terminal to stay open
- β Better suited for dev workflows, or when using tools like Lens/K9s which manage this automatically
π‘ Option 3: minikube tunnel (Recommended for real external access)
- Only if you define type: as LoadBalancer instead of NodePort
minikube tunnel
- Exposes NodePort and LoadBalancer services to your macOS host.
- Runs in background (but requires admin privileges).
- Makes the minikube ip + NodePort combination work:
curl http://192.168.49.2:30095
...
- β Best if you want persistent access via actual cluster IP
- β οΈ Youβll need to keep the tunnel running in a terminal
Summary
| Method | Persistent | Scriptable | Requires open terminal | Notes |
|---|---|---|---|---|
minikube service |
β | β | β | Great for quick UI testing |
kubectl port-forward |
β | β | β | Ideal during dev/debug |
minikube tunnel |
β | β | β (background) | Best for stable external access |
Tools Summary
| Service | Method | Persistent | Host Access | Notes |
|---|---|---|---|---|
| Grafana | minikube service |
β | β | UI test only |
| Β | kubectl port-fwd |
β | β | Dev access |
| Β | minikube tunnel |
β | β | Needed for NodePort from host |
| Prometheus | Same as above | Same | Same | Accessible at port 9090 |
| Kafka | port-fwd 9093 |
β | β (TLS) | For testing with TLS listener |
| Β | minikube tunnel |
β | β | Needed for TLS access from host tools |
π Stopping and Restarting Minikube Safely
β»οΈ Minikube Lifecycle (Shutdown / Restart)
To safely shut down and restart your monitoring stack without losing data or encountering errors:
β Stop Minikube
- Use minikube stop instead of deleting the cluster:
- This safely shuts down the VM/container.
- PVCs and all service configurations remain intact.
minikube stop
β Start Again Later
- This restores the full state, including your deployed services, PVCs, and Helm releases.
- All NodePorts and persistent data remain available.
minikube start
β οΈ Do Not Use
- β This deletes all volumes, pods, secrets, configs β use only if you want a clean reset.
minikube delete
π§ͺ Check Status
kubectl get pods -n monitoring
- If pods donβt come up correctly (e.g., CrashLoopBackOff), you may need to:
- Reapply deploy-all.sh
-
Re-check Minikube disk availability with:
minikube ssh df -h
π Persistent Volumes & Restarting Notes
| Component | Persistent? | How Itβs Stored | Notes |
|---|---|---|---|
| Prometheus | β | PVC β HostPath on VM | Config & scraped metrics preserved across reboots |
| Grafana | β | PVC (grafana-pvc) | Dashboards, settings are saved |
| Kafka | β | PVC per broker/controller | Topic data survives restart. Must wait for all brokers to rejoin. |
| Kafka-UI | β | Ephemeral | Will restart fresh; doesnβt affect Kafka state |
π Optional: Restart deploy-all.sh (if needed)
- You can safely re-run the script to reapply Helm charts and PVCs:
- π‘ Helm is idempotent β it will upgrade existing releases without data loss if PVCs exist.
./deploy-all.sh
π Tip: Back Up Persistent Data (Optional)
- To snapshot your PVCs before restarting or for backup purposes:
kubectl get pvc -n monitoring
- For example:
kubectl cp monitoring/prometheus-server-0:/opt/bitnami/prometheus/data ./backup-prometheus-data
π§ Final Tip: Automate Health Checks (Optional)
To quickly check if your monitoring stack is up and running, you can either:
β Option A: Use the health check script (recommended)
- Run the provided script to verify key components like Prometheus, Grafana, Kafka and Kafka-UI:
./check-health.sh
- Response
β― ./check-health.sh
β³ Checking health of monitoring components in namespace: monitoring
π prometheus-server...
deployment "prometheus-server" successfully rolled out
β
prometheus-server is healthy
π grafana...
deployment "grafana" successfully rolled out
β
grafana is healthy
π kafka-ui...
deployment "kafka-ui" successfully rolled out
β
kafka-ui is healthy
π kafka-controller...
statefulset rolling update complete 3 pods at revision kafka-controller-98bc6557b...
β
kafka-controller is healthy
β
Health check completed.
- If components are not found or in a bad state, the script will print warnings accordingly.
- You can edit the script to match the names of your deployments or statefulsets, depending on your YAMLs.
π Option B: Check manually with kubectl
If you prefer manual inspection or want to verify specific resources:
- List all pods in the monitoring namespace:
kubectl get pods -n monitoring
- You should see something like:
NAME READY STATUS RESTARTS AGE
grafana-57554dd88-rc8z4 1/1 Running 0 3h11m
kafka-controller-0 1/1 Running 0 175m
kafka-controller-1 1/1 Running 0 175m
kafka-controller-2 1/1 Running 0 175m
kafka-ui-5448964747-ds2bd 1/1 Running 0 171m
prometheus-alertmanager-0 1/1 Running 1 (4h59m ago) 24h
prometheus-kube-state-metrics-7f796b7d44-89mjd 1/1 Running 1 (4h59m ago) 24h
prometheus-prometheus-node-exporter-cltc4 1/1 Running 1 (4h59m ago) 24h
prometheus-prometheus-pushgateway-d4f8cb767-nwtn9 1/1 Running 1 (4h59m ago) 24h
prometheus-server-79798b4ff6-7g55g 2/2 Running 2 (4h59m ago) 24h
...
- Check services and their ports:
kubectl get svc -n monitoring
- Look for NodePort services exposing the UIs:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
grafana NodePort 10.104.174.27 <none> 80:30095/TCP 3h11m
kafka ClusterIP 10.106.201.77 <none> 9092/TCP,9095/TCP 175m
kafka-controller-0-external NodePort 10.105.143.201 192.168.49.2 9094:30092/TCP 175m
kafka-controller-1-external NodePort 10.101.82.105 192.168.49.2 9094:30093/TCP 175m
kafka-controller-2-external NodePort 10.97.107.152 192.168.49.2 9094:30094/TCP 175m
kafka-controller-headless ClusterIP None <none> 9094/TCP,9092/TCP,9093/TCP 175m
kafka-ui NodePort 10.100.56.227 <none> 8080:30096/TCP 171m
prometheus-alertmanager ClusterIP 10.103.180.130 <none> 9093/TCP 24h
prometheus-alertmanager-headless ClusterIP None <none> 9093/TCP 24h
prometheus-kube-state-metrics ClusterIP 10.102.171.116 <none> 8080/TCP 24h
prometheus-prometheus-node-exporter ClusterIP 10.97.14.115 <none> 9100/TCP 24h
prometheus-prometheus-pushgateway ClusterIP 10.101.189.12 <none> 9091/TCP 24h
prometheus-server NodePort 10.104.67.195 <none> 80:30090/TCP 24h
- Then, access the dashboards using
http://<minikube-ip>:<nodeport>. For example:- Grafana: http://localhost:30095
- Prometheus: http://localhost:30090
- Kafka UI: http://localhost:30096
- Use minikube ip to get your cluster IP if needed.