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.