In today’s cloud-native landscape, Kubernetes has revolutionized container management, providing organizations with powerful tools to deploy, scale, and manage applications efficiently. With so many components working together like pods, nodes, and services - it’s crucial to keep an eye on performance, resource usage, and potential issues.
Monitoring helps us to detect problems early, optimize workloads and ensure everything runs smoothly. In this article we will take a quick look how to monitor your Kubernetes cluster using Prometheus Stack (Prometheus, Grafana, Alert Manager) and we will also implement Horizontal Pod Autoscaler (HPA) using custom metrics from Prometheus Adapter.
But before diving into the steps, it’s important to understand the fundamental terms.
What is Kubernetes?
Kubernetes is a platform that helps you to run and manage application in containers. Containers are like mini virtual machines, but they are faster and use fewer resources. Kubernetes acts as coordinator, ensuring that your containers are running where they need to, scaling them up and down depending on the demand, and keeping everything running smoothly.
What is Metric?
A metric is basically a way to measure something. In tech, it’s used to track how well something is performing. For example, we might measure how much CPU or memory our app is using, or how long it takes to respond. These numbers help us keep an eye on things, find problems, and make sure everything runs smoothly.
What is Prometheus?
Prometheus is a tool that helps us collect and store metrics from our applications or systems. It constantly checks how things are performing like tracking how much CPU or memory our app is using and saves that data. we can then use Prometheus to analyze this data, spot any issues, and make sure everything is running as it should.
What is Grafana?
Grafana is a tool that helps us visualize data in a clear and interactive way. It takes the metrics collected by tools like Prometheus and turns them into easy-to-understand charts and graphs. This makes it simple to see how your applications or systems are performing at a glance.
What is Alertmanager?
Alertmanager is a tool that works with Prometheus to manage alerts. When something goes wrong in your system—like high CPU usage or an app going down—Prometheus triggers an alert. Alertmanager helps organize these alerts by grouping, silencing, or routing them to the right people or channels (like email, Slack, etc.).
What is HPA?
HPA (Horizontal Pod Autoscaler) is a feature in Kubernetes that automatically adjusts the number of pods running in our application based on demand.If the app is getting more traffic and needs more resources, HPA will increase the number of pods. If the traffic drops, HPA will reduce the number of pods to save resources.
What is Prometheus Adapter?
Prometheus Adapter acts as a bridge between Prometheus and Kubernetes, letting Kubernetes use the metrics (such as CPU usage, memory, etc.) from Prometheus to make decisions about scaling resources.
Tools
Tools | Versions |
Kubelet | v1.32.1 |
Kubectl | v1.32.1 |
Kubeadm | 1.32 |
Containerd | 1.7.25 |
Prometheus | 3.1.0 |
Grafana | 11.5.1 |
Alertmanager | 0.28.0 |
Prometheus Adapter | v0.12.0 |
Helm | v3.17.0 |
Topology
Workflow
Preflight
*Execute on all nodes
Change hostname
sudo hostnamectl set-hostname master sudo hostnamectl set-hostname worker1 sudo hostnamectl set-hostname worker2
Map hostname to /etc/hosts
192.168.3.10 master 192.168.3.20 worker1 192.168.3.30 worker2
Create & distribute SSH Key
#Generate key ssh-keygen -t rsa #Distribute key ssh-copy-id student@master ssh-copy-id student@worker1 ssh-copy-id student@worker2
Kubernetes Cluster Provisioning
*Execute on all nodes
Upgrade Package and install the dependencies
sudo apt update sudo apt upgrade -y sudo apt autoremove -y #install dependencies sudo apt install -y curl gnupg2 software-properties-common apt-transport-https ca-certificates
Install Containerd as a container runtime
Add gpg key and Docker repository to your local machine
#Add gpg key sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmour -o /etc/apt/trusted.gpg.d/docker.gpg #Add repository sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
Update repository and install the constainerd
sudo apt update sudo apt install -y containerd.io
Configure containerd to use systemd as cgroup driver
# Generate default configuration for containerd containerd config default | sudo tee /etc/containerd/config.toml >/dev/null 2>&1 # Configure systemd cgroup driver in configuration sudo sed -i 's/SystemdCgroup \= false/SystemdCgroup \= true/g' /etc/containerd/config.toml
Restart & enable containerd service
sudo systemctl restart containerd sudo systemctl enable containerd
Add Kernel configuration
# enable overlay and br_netfilter modules cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF # reload kernel modules sudo modprobe overlay sudo modprobe br_netfilter
Add IPtables configuration
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF # apply sysctl without reboot sudo sysctl --system
Install Kubectl, Kubelet, and Kubeadm
Add gpg key and Kubernetes repository
# add gpg key curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg sudo chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg # add repository echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo chmod 644 /etc/apt/sources.list.d/kubernetes.list
Install kubectl, kubelet & kubeadm
sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl
*Execute only in master node
Initialize Cluster
# stop swap first sudo swapoff -a # initialize cluster sudo kubeadm init --pod-network-cidr=192.168.0.0/16
Copy admin configuration so that we can manage cluster without “sudo”
mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config
Install calico pod network
# deploy calico operator kubectl create -f https://docs.projectcalico.org/manifests/tigera-operator.yaml # apply custom resource kubectl create -f https://docs.projectcalico.org/manifests/custom-resources.yaml
If your cidr network is 192.168.0.0/16 then you don't need to change anything in the custom resource file but if it is different then you need to adjust the configuration by changing the cidr value in the ipPools section.
Verify config & cluster info
kubectl config view kubectl cluster-info
Show token and token ca-cert-hash
sudo kubeadm token list sudo openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
*Execute on worker nodes
Join Node Worker to Cluster
swapon -s
sudo swapoff -a
sudo kubeadm join --token [TOKEN] [NODE-MASTER]:6443 --discovery-token-ca-cert-hash sha256:[TOKEN-CA-CERT-HASH]
Install Prometheus Stack
*Execute on Master node
Create namespace monitoring
# create namespace monitoring kubectl create namespace monitoring # navigate to monitoring namespace kubectl config set-context --current --namespace=monitoring
Install Helm
# Add gpg key curl -s https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null # install dependency sudo apt-get install apt-transport-https --yes # Add repository echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list # Update repository & install Helm sudo apt update sudo apt install helm
Add Repository for helm charts
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts # Update repository helm repo update
Install prometheus stack
helm install prometheus-stack prometheus-community/kube-prometheus-stack
prometheus-stack : release name
prometheus-community : repository name
kube-prometheus-stack : name of the chart
List resources that has been created by operator
kubectl get all ... NAME READY STATUS RESTARTS AGE pod/alertmanager-prometheus-stack-kube-prom-alertmanager-0 2/2 Running 0 7d19h pod/prometheus-prometheus-stack-kube-prom-prometheus-0 2/2 Running 0 7d19h pod/prometheus-stack-grafana-695cdf5b57-kmg5j 3/3 Running 0 7d19h pod/prometheus-stack-kube-prom-operator-5f6bff8545-g2kj9 1/1 Running 1 (2d ago) 7d19h pod/prometheus-stack-kube-state-metrics-8587b679d9-nwjqg 1/1 Running 11 (47h ago) 7d19h pod/prometheus-stack-prometheus-node-exporter-b75xw 1/1 Running 1 (2d ago) 7d19h pod/prometheus-stack-prometheus-node-exporter-nx94g 1/1 Running 1 (2d ago) 7d19h pod/prometheus-stack-prometheus-node-exporter-qhzpb 1/1 Running 0 7d19h NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/alertmanager-operated ClusterIP None <none> 9093/TCP,9094/TCP,9094/UDP 7d19h service/prometheus-operated ClusterIP None <none> 9090/TCP 7d19h service/prometheus-stack-grafana ClusterIP 10.107.67.74 <none> 80/TCP 7d19h service/prometheus-stack-kube-prom-alertmanager ClusterIP 10.110.179.13 <none> 9093/TCP,8080/TCP 7d19h service/prometheus-stack-kube-prom-operator ClusterIP 10.105.35.42 <none> 443/TCP 7d19h service/prometheus-stack-kube-prom-prometheus ClusterIP 10.108.203.207 <none> 9090/TCP,8080/TCP 7d19h service/prometheus-stack-kube-state-metrics ClusterIP 10.108.48.148 <none> 8080/TCP 7d19h service/prometheus-stack-prometheus-node-exporter ClusterIP 10.96.64.162 <none> 9100/TCP 7d19h NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE daemonset.apps/prometheus-stack-prometheus-node-exporter 3 3 3 3 3 kubernetes.io/os=linux 7d19h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/prometheus-stack-grafana 1/1 1 1 7d19h deployment.apps/prometheus-stack-kube-prom-operator 1/1 1 1 7d19h deployment.apps/prometheus-stack-kube-state-metrics 1/1 1 1 7d19h NAME DESIRED CURRENT READY AGE replicaset.apps/prometheus-stack-grafana-695cdf5b57 1 1 1 7d19h replicaset.apps/prometheus-stack-kube-prom-operator-5f6bff8545 1 1 1 7d19h replicaset.apps/prometheus-stack-kube-state-metrics-8587b679d9 1 1 1 7d19h NAME READY AGE statefulset.apps/alertmanager-prometheus-stack-kube-prom-alertmanager 1/1 7d19h statefulset.apps/prometheus-prometheus-stack-kube-prom-prometheus 1/1 7d19h
Expose Service Prometheus & Grafana
kubectl expose service prometheus-stack-grafana --name grafana-access-external --type=NodePort kubectl expose service prometheus-stack-kube-prom-prometheus --name prometheus-access-external --type=NodePort
Access your Prometheus and Grafana using port that has been created from the service (e.g. 192.168.3.10:32548)
Execute this command to get the default user and password for grafana
kubectl get secret --namespace monitoring prometheus-stack-grafana -o jsonpath='{.data.admin-user}' | base64 --decode kubectl get secret --namespace monitoring prometheus-stack-grafana -o jsonpath='{.data.admin-password}' | base64 --decode
Create Visualization
Create new folder named ‘Simulasi‘ to group the dashboard
Create first new dashboard and add visualization
Add visualization named ‘Total CPU Usage in percent’
Add your query metrics in query textbox based on your needs. here I will create visualization to monitor the total cpu usage in the entire cluster using node_cpu_seconds_total{mode=”idle”} divided by node_cpu_seconds_total in 5m range. Choose the visualization type as Stat and add the title. To look exactly like this, you can navigate to “Stat styles” > “Color mode” and choose Background Gradient, “Stat styles” > “Graph mode” and choose None . For the percent sign you have to change the Unit, go to “Standard options” > “Unit and choose” percent (0-100)
Add visualization named Total Memory Usage in percent
Add visualization named CPU Usage per Node [5m]
Legend is used to overrides the display name based on label or custom text.
Add visualization named Memory Usage per Node
Add visualization named Nodes Status
by default if a node is on the value is 1, while off the value is 0. To change to text UP and DOWN, it is necessary to add value mappings
Add visualization named All Pod’s Status
Repeat this query for each phase (Pending, Failed, etc.)
Add visualization named Pod per Namespace
Add visualization named Total PV & PVC
Add visualization named
Save Dashboard named Monitor-Cluster-Wide
Overall visualization of the Monitor-Cluster-Wide dashboard
Create second Dashboard named Monitor-Namespace-Pod
Create a variable to group panels by labels such as namespace, instance etc.
Result:
Add visualization named CPU Usage per Pod
Add visualization named Memory Usage per Pod
Add visualization named Pod Status
Add visualization named Total Service
Add visualization named Total DaemonSet & StatefulSet
Add visualization named Total Deployment
Add visualization named Total ConfigMap & Secret
Add visualization named Total Network Policy & Rules
Repeat query for the egres rule
Add visualization named Job Status
Repeat query for the succeeded status
Add visualization named HPA: Current Replicas vs Desired Replicas
Create Contact Points (Which platforms that alerts will be sent)
Here we will use 2 platforms (Discord & WhatsApp)
Test alerts
Discord
WhatsApp
Create alert for HPA panel
go to “Alert” > “New alert rule”
Select query A (current replicas status - match with query on panel ) as alert condition
Configure Thresholds to evaluate the data that comes from query A so that if the current replica is above 2 then the alerts will be sent
Select the Contact Point and save the alerts rule
Deploy Prometheus Adapter
* Execute only on master node
Create values.yaml file to configure the custom metrics for prometheus adapter
prometheus: url: "http://prometheus-stack-kube-prom-prometheus.monitoring.svc.cluster.local" port: 9090 rules: default: false custom: - seriesQuery: '{__name__=~"^container_cpu_usage_seconds_total$"}' resources: overrides: namespace: {resource: "namespace"} pod: {resource: "pod"} name: matches: "^(.*)" as: "custom_cpu_usage" metricsQuery: "sum(rate(container_cpu_usage_seconds_total{<<.LabelMatchers>>}[5m])) by (<<.GroupBy>>)"
Install Prometheus Adapater using Helm Charts
helm install prometheus-adapater prometheus-community/prometheus-adapter -f values.yaml
Verify the custom metrics
We can check the custom metrics that has been exposed at /apis/custom.metrics.k8s.io
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 | jq . ... { "kind": "APIResourceList", "apiVersion": "v1", "groupVersion": "custom.metrics.k8s.io/v1beta1", "resources": [ { "name": "namespaces/custom_cpu_usage", "singularName": "", "namespaced": false, "kind": "MetricValueList", "verbs": [ "get" ] }, { "name": "pods/custom_cpu_usage", "singularName": "", "namespaced": true, "kind": "MetricValueList", "verbs": [ "get" ] } ] }
Testing
Create deployment named uji-hpa-deploy with nginx as image container
# save this configuration as uji-deploy.yaml apiVersion: apps/v1 kind: Deployment metadata: name: uji-hpa-deploy spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: container-nginx image: nginx:latest ports: - containerPort: 80 resources: requests: cpu: "100m" limits: cpu: "300m" ... # apply configuration kubectl apply -f uji-deploy.yaml
Create service for the deployment
kubectl expose deployment uji-hpa-deploy --type=clusterip --port=80
Create HPA with custom metrics
This HPA will create replica from the deployment if the cpu usage is more than 300m (0,3 core)
# save this configuration as uji-hpa.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: uji-hpa namespace: default spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: uji-hpa-deploy minReplicas: 2 maxReplicas: 20 metrics: - type: Pods pods: metric: name: custom_cpu_usage target: type: AverageValue averageValue: 300m ... # apply configuration kubectl apply -f uji-hpa.yaml
Check the HPA
kubectl get hpa -n default ... NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE my-monitor-hpa Deployment/my-deploy-app cpu: 0%/50% 2 20 2 2d19h my-monitor-hpa2 Deployment/my-deploy-app2 cpu: 0%/70% 2 20 2 2d17h uji-hpa Deployment/uji-hpa-deploy 0/300m 2 20 2 2d
Test the scaling process using hey command
hey -z 2m -c 5000 -m GET http://<IP SERVICE uji-hpa-deploy>
Monitor the scaling process from the dashboard
Here we can see that the desired replica is 3 and the alert rules is firing now (heart sign)
Check the alerts on your contact points
Discord
You will get Resolved notification if the alert rules is no longer active (not firing)