We are going to deploy Prometheus to monitor Kubernetes nodes and more.
Pre-requisites
We are using our Kubernetes homelab to deploy Prometheus.
A working NFS server is required to create persistent volumes. Note that NFS server configuration is not covered in this article, but the way we set it up can be found here.
Our NFS server IP address is 10.11.1.20, and we have the following export configured for Prometheus:
/mnt/storage-k8s/nfs/prometheus
The owner:group of the NFS folder is set to 65534:65534, because of Prometheus deployment runAsUser: 65534.
Download Files from GitHub
Configuration files used in this article are hosted on GitHub. Clone the following repository:
$ git clone https://github.com/lisenet/kubernetes-homelab.git $ cd ./kubernetes-homelab/
TLDR; Install and Configure Prometheus: All in One Go
Create a monitoring namespace:
$ kubectl create ns monitoring
Create everything with a single command:
$ kubectl apply -f ./kubernetes/prometheus/
Note to self: this could be a Helm chart.
Install and Configure Prometheus: Step by Step
Step by step instructions. Note that this homelab project is under development, therefore please refer to GitHub for any source code changes.
Create a Namespace
Create a monitoring namespace:
$ kubectl create ns monitoring
Create a Cluster Role and a Cluster Role Binding
Allow Prometheus fetch metrics from Kubernetes API:
$ kubectl apply -f ./kubernetes/prometheus/prometheus-cluster-role.yml
This is what the code looks like:
--- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: prometheus rules: - apiGroups: [""] resources: - nodes - nodes/proxy - services - endpoints - pods verbs: ["get", "list", "watch"] - apiGroups: - extensions resources: - ingresses verbs: ["get", "list", "watch"] - nonResourceURLs: ["/metrics"] verbs: ["get"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: prometheus roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: prometheus subjects: - kind: ServiceAccount name: default namespace: monitoring
Create a Config Map
Prometheus scrapes metrics from instrumented jobs. The config map is the place to define your scrape config, what needs monitoring and how often.
My scrape config will not match yours, and it is therefore important that you modify the file prometheus-config-map.yml to meet your environment needs.
$ kubectl apply -f ./kubernetes/prometheus/prometheus-config-map.yml
This is what the code looks like:
---
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-server-conf
labels:
name: prometheus-server-conf
namespace: monitoring
data:
prometheus.rules: |-
groups:
- name: node.alerts
rules:
- alert: KubernetesHostHighCPUUsage
expr: 100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
for: 15m
labels:
severity: warning
context: node
annotations:
summary: High load on node
description: "Node {{ $labels.instance }} has more than 90% CPU load"
- alert: KubernetesNodeDiskUsagePercentage
expr: (100 - 100 * sum(node_filesystem_avail_bytes{device!~"tmpfs|by-uuid",fstype=~"xfs|ext"} / node_filesystem_size_bytes{device!~"tmpfs|by-uuid",fstype=~"xfs|ext"}) BY (instance,device)) > 85
for: 5m
labels:
severity: warning
context: node
annotations:
description: Node disk usage above 85%
summary: Disk usage on target {{ $labels.instance }} at 85%
- alert: KubernetesNodeContainerOOMKilled
expr: sum by (instance) (changes(node_vmstat_oom_kill[24h])) > 3
labels:
severity: warning
context: node
annotations:
description: More than 3 OOM killed pods on a node within 24h
summary: More than 3 OOM killed pods on node {{ $labels.instance }} within 24h
prometheus.yml: |-
global:
evaluation_interval: 60s
scrape_interval: 15s
scrape_timeout: 10s
rule_files:
- /etc/prometheus/prometheus.rules
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
- 'alertmanager.monitoring.svc:9093'
scrape_configs:
- job_name: 'kubernetes-apiservers'
kubernetes_sd_configs:
- role: endpoints
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
action: keep
regex: default;kubernetes;https
#--------------------------------------------
# Scrape config for nodes (kubelet).
#--------------------------------------------
- job_name: 'kubernetes-nodes'
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/${1}/proxy/metrics
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
#--------------------------------------------
# Scrape config for service endpoints. This will
# scrape node-exporter and kube-state-metrics services.
# You need this for the following Grafana dashboards:
# - Kubernetes Cluster Summary
# - Node Exporter Full
#--------------------------------------------
- job_name: kubernetes-service-endpoints
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- action: keep
regex: true
source_labels:
- __meta_kubernetes_service_annotation_prometheus_io_scrape
- action: replace
regex: (https?)
source_labels:
- __meta_kubernetes_service_annotation_prometheus_io_scheme
target_label: __scheme__
- action: replace
regex: (.+)
source_labels:
- __meta_kubernetes_service_annotation_prometheus_io_path
target_label: __metrics_path__
- action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
source_labels:
- __address__
- __meta_kubernetes_service_annotation_prometheus_io_port
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- action: replace
source_labels:
- __meta_kubernetes_namespace
target_label: kubernetes_namespace
- action: replace
source_labels:
- __meta_kubernetes_service_name
target_label: kubernetes_name
- action: replace
source_labels:
- __meta_kubernetes_pod_node_name
target_label: kubernetes_node
#--------------------------------------------
# Scrape config for pods.
#--------------------------------------------
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: kubernetes_pod_name
#--------------------------------------------
# Scrape static config for homelab.
#--------------------------------------------
- job_name: 'dns-master'
static_configs:
- targets: ['10.11.1.2:9153']
labels:
alias: admin1
- job_name: 'dns-slave1'
static_configs:
- targets: ['10.11.1.3:9153']
labels:
alias: admin2
- job_name: 'etcd'
static_configs:
- targets: ['10.11.1.31:2381','10.11.1.32:2381','10.11.1.33:2381']
- job_name: 'haproxy'
static_configs:
- targets: ['10.11.1.30:9101']
- job_name: 'admin1'
static_configs:
- targets: ['10.11.1.2:9100']
labels:
alias: admin1
- job_name: 'admin2'
static_configs:
- targets: ['10.11.1.3:9100']
labels:
alias: admin2
- job_name: 'kvm1'
static_configs:
- targets: ['10.11.1.21:9100']
labels:
alias: kvm1
- job_name: 'kvm2'
static_configs:
- targets: ['10.11.1.22:9100']
labels:
alias: kvm2
- job_name: 'kvm3'
static_configs:
- targets: ['10.11.1.23:9100']
labels:
alias: kvm3
- job_name: 'k8s-master1'
static_configs:
- targets: ['10.11.1.31:9100']
labels:
alias: k8s-master1
- job_name: 'k8s-master2'
static_configs:
- targets: ['10.11.1.32:9100']
labels:
alias: k8s-master2
- job_name: 'k8s-master3'
static_configs:
- targets: ['10.11.1.33:9100']
labels:
alias: k8s-master3
- job_name: 'k8s-node1'
static_configs:
- targets: ['10.11.1.34:9100']
labels:
alias: k8s-node1
- job_name: 'k8s-node2'
static_configs:
- targets: ['10.11.1.35:9100']
labels:
alias: k8s-node2
- job_name: 'k8s-node3'
static_configs:
- targets: ['10.11.1.36:9100']
labels:
alias: k8s-node3
- job_name: 'mikrotik-exporter'
scrape_interval: 30s
static_configs:
- targets:
- 10.11.1.1 # your Mikrotik router IP you wish to monitor
metrics_path: /metrics
params:
module: [my_router]
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: mikrotik-exporter:9436
Our Prometheus config map contains Alertmanager configuration that we’ll cover in this article.
Create a Persistent Volume
We want to keep monitoring data and store it on a persistent volume.
$ kubectl apply -f ./kubernetes/prometheus/prometheus-pv.yml
This is what the code looks like:
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-prometheus
namespace: monitoring
spec:
capacity:
storage: 32Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
mountOptions:
- hard
- nfsvers=4.1
nfs:
path: /mnt/storage-k8s/nfs/prometheus
server: 10.11.1.20
Create a Persistent Volume Claim
Allow Prometheus to request persistent storage.
$ kubectl apply -f ./prometheus/prometheus-pvc.yml
This is what the code looks like:
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc-prometheus
namespace: monitoring
spec:
storageClassName: nfs
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 32Gi
Create a Deployment Configuration
$ kubectl apply -f ./prometheus/prometheus-deployment.yml
This is what the code looks like:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
namespace: monitoring
labels:
app: prometheus-server
spec:
replicas: 1
selector:
matchLabels:
app: prometheus-server
template:
metadata:
labels:
app: prometheus-server
spec:
securityContext:
fsGroup: 65534
runAsGroup: 65534
runAsNonRoot: true
runAsUser: 65534
containers:
- name: prometheus
image: prom/prometheus:v2.24.1
imagePullPolicy: IfNotPresent
args:
- "--storage.tsdb.retention.time=28d"
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus/"
ports:
- containerPort: 9090
protocol: TCP
resources:
limits:
memory: "1Gi"
cpu: "1000m"
requests:
memory: "500Mi"
cpu: "100m"
volumeMounts:
- name: prometheus-config-volume
mountPath: /etc/prometheus/
readOnly: true
- name: prometheus-storage-volume
mountPath: /prometheus/
# See https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
#initContainers:
# - name: fix-nfs-permissions
# image: busybox
# command: ["sh", "-c", "chown -R 65534:65534 /prometheus"]
# securityContext:
# runAsUser: 0
# runAsNonRoot: false
# volumeMounts:
# - name: prometheus-storage-volume
# mountPath: /prometheus
restartPolicy: Always
terminationGracePeriodSeconds: 60
volumes:
- name: prometheus-config-volume
configMap:
defaultMode: 420
name: prometheus-server-conf
- name: prometheus-storage-volume
persistentVolumeClaim:
claimName: nfs-pvc-prometheus
Create a Service
$ kubectl apply -f ./prometheus/prometheus-service.yml
This is what the code looks like:
---
apiVersion: v1
kind: Service
metadata:
name: prometheus-service
namespace: monitoring
annotations:
prometheus.io/scrape: 'true'
prometheus.io/port: '9090'
labels:
app: prometheus-server
spec:
selector:
app: prometheus-server
type: NodePort
ports:
- port: 9090
targetPort: 9090
nodePort: 30000
Check Monitoring Namespace
$ kubectl -n monitoring get all -l app=prometheus-server NAME READY STATUS RESTARTS AGE pod/prometheus-7d8dc64fdc-2wzt2 1/1 Running 0 6d1h NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/prometheus-service NodePort 10.104.220.139 none 9090:30000/TCP 14d NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/prometheus 1/1 1 1 6d5h NAME DESIRED CURRENT READY AGE replicaset.apps/prometheus-7d8dc64fdc 1 1 1 6d3h
We can access the Prometheus dashboard by using its service node port 30000.


What’s Next?
We will configure Grafana to use Prometheus as a data source.
