Driving Manual or Automatic: Docker Hub vs. Helm for Deploying Prometheus on Kubernetes

July 9, 2020
Driving Manual or Automatic: Docker Hub vs. Helm for Deploying Prometheus on Kubernetes

    While the introduction of new abstraction layers has made creating and designing distributed systems a complex task, it hasn’t prevented an increase in their popularity. One of the tools in use for orchestrating container-based distributed systems is Kubernetes. One of the most effective Kubernetes monitoring options is Prometheus, a leading open-source monitoring solution for container environments. Prometheus, which ex-Googlers created at SoundCloud, the second project the Cloud Native Computing Foundation (CNCF) accepted, after Kubernetes itself.

    Some of Prometheus’ most important features are:

    • Its multidimensional data model with support from its time-series database
    • Its own query language, PromQL
    • Support for scraping metric data in pull mode, as well as the traditional push mode
    • Automatic service discovery (i.e. finding the targets of metric data)
    • Multiple integrations, such as with Kubernetes and other CNCF open source projects

    Once you’ve decided to implement Kubernetes, your next step is to decide how best to deploy Prometheus and related applications—such as Grafana—to the cluster. There are a few common patterns developers use to do this. This article will pit two of these patterns against each other: the manual way with kubectl and Docker Hub vs. the automated way with Helm.

    Deploying Applications to Kubernetes

    With creation of a deployment, the Kubernetes master schedules application instances that are defined in the deployment manifest. After creating the instances, deployment controllers start to monitor them and reschedule them if necessary (e.g., when a node goes down or is cordoned), providing a self-healing mechanism for the deployed applications.

    Many approaches are popular in deploying applications to Kubernetes, including:

    • YAML manifests
    • Packing managers like Helm, CNAB, and Cloudsmith
    • kubectl

    This tutorial will compare the “do it yourself” approach using Docker Hub, static manifests and kubectl to the orchestrated approach with Helm and predefined charts.

    Creating a Prometheus Deployment Manually with Docker Hub and kubectl

    In this section, we’ll cover deployment to a Kubernetes cluster with a static YAML manifest and Docker Hub images. YAML is a text format suitable for humans to specify configuration. In Kubernetes, YAML is used to define Kubernetes resources.

    Before getting started, you’ll need to have a running cluster with kubectl configured. In addition, you should create a separate namespace just for monitoring to make it easier to manage the monitoring stack. To create a namespace, run the command below:

    kubectl create namespace monitoring
    
    
    
    
    

    Prometheus needs access to resources’ metrics to get them from the Kubernetes control plane. For this, create a ClusterRole and ClusterRoleBinding by writing the following configuration to the file named role-config.yaml:

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
     name: prometheus
    rules:
    - apiGroups: [""]
     resources:
     - endpoints
     - nodes
     - nodes/proxy
     - pods
     - services
     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
    
    
    
    
    

    Then, run the following command:

    kubectl apply -f role-config.yaml
    
    
    
    
    

    The output should include:

    clusterrole.rbac.authorization.k8s.io/prometheus configured
    clusterrolebinding.rbac.authorization.k8s.io/prometheus configuredclusterrole.rbac.authorization.k8s.io/prometheus configured
    clusterrolebinding.rbac.authorization.k8s.io/prometheus configured
    
    
    
    
    

    Configuring Prometheus with Docker Hub on Kubernetes

    Next, configure Prometheus. Create a ConfigMap that will mount inside the Prometheus container. Write the following to configmap.yaml:

    apiVersion: v1
    kind: ConfigMap
    metadata:
     name: prometheus-server-conf
     labels:
       name: prometheus-server-conf
     namespace: monitoring
    data:
     prometheus.yml: |-
       global:
         scrape_interval: 5s
         evaluation_interval: 5s
       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
         - job_name: 'kubernetes-nodes'
           scheme: https
           tls_config:
             ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
           bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
           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
         - 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
         - job_name: 'kubernetes-cadvisor'
           scheme: https
           tls_config:
             ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
           bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
           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/cadvisor
          - job_name: 'kubernetes-service-endpoints'
            kubernetes_sd_configs:
            - role: endpoints
            relabel_configs:
            - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
              action: keep
              regex: true
            - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
              action: replace
              target_label: __scheme__
              regex: (https?)
            - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
              action: replace
              target_label: __metrics_path__
              regex: (.+)
            - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
              action: replace
              target_label: __address__
              regex: ([^:]+)(?::\d+)?;(\d+)
              replacement: $1:$2
            - action: labelmap
              regex: __meta_kubernetes_service_label_(.+)
            - source_labels: [__meta_kubernetes_namespace]
              action: replace
              target_label: kubernetes_namespace
            - source_labels: [__meta_kubernetes_service_name]
              action: replace
              target_label: kubernetes_name
    
    
    
    
    

    This ConfigMap is created in the “monitoring” namespace. It includes several job descriptions defining which metrics to scrape. Kubernetes-apiserver pulls data from the API servers, node metrics with kubernetes-nodes, and metrics for cAdvisor with kubernetes-cadvisor.

    Scraping Services and Pods with Prometheus

    To enable scraping from services and pods, add the annotations prometheus.io/scrape and prometheus.io/port to the metadata section in their definitions.

    Now that you have a ConfigMap configuration ready, apply it to the cluster with:

    kubectl apply -f configmap.yaml
    
    
    
    
    

    Create a deployment to read the metrics by generating a new file deployment.yaml and use the following configuration:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
     name: prometheus-deployment
     namespace: monitoring
    spec:
     replicas: 1
     template:
       metadata:
         labels:
           app: prometheus-server
       spec:
         containers:
         - name: prometheus
           image: prom/prometheus:v2.12.0
           args:
             - "--config.file=/etc/prometheus/prometheus.yml"
             - "--storage.tsdb.path=/prometheus/"
           ports:
             - containerPort: 9090
           volumeMounts:
             - name: prometheus-config-volume
               mountPath: /etc/prometheus/
             - name: prometheus-storage-volume
               mountPath: /prometheus/
         volumes:
           - name: prometheus-config-volume
             configMap:
               defaultMode: 420
               name: prometheus-server-conf
           - name: prometheus-storage-volume
             emptyDir: {}
    You may be wondering what all of this has to do with Docker Hub. The Docker Hub image sees its use as part of the configuration. It’s under the spec.template.spec.containers.image.

    When the pods are running, you should be able to access the Prometheus dashboard after proxying the application to your local machine using the following:

    kubectl port-forward $(kubectl get pods -n monitoring --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}') -n monitoring 8080:9090
    Open a new browser window, and go to http://localhost:8080. You should now see a Prometheus home page.

    Adding Alerts and More Metrics

    Keep in mind that this process does not establish a complete monitoring solution. There’s no alerting set up, there are no useful dashboards yet, and many of the metrics are still missing. More configuration needs to be done before you can be confident that your monitoring stack is ready for production usage.

    Deploying Prometheus with YAML manifests and Docker Hub images is time-consuming. The upside of the process is that it allows you to have total control over the configuration.

    Every service, deployment, and StatefulSet is under your direct influence. In contrast, with Helm charts you can only manipulate a few settings exposed in a values.yaml file.

    If you don’t rely much on community components you would need your own YAML anyway. In that case, Helm’s value would be less significant. Some also prefer to keep it simple and avoid dealing with Helm’s own stack; primarily with Helm’s server-side—Tiller—and some of its complexities around HA and RBAC, just to name a few.

    Nonetheless, many choose to go with higher-level automation and abstraction, so opt for Helm. We will cover that in the next section.

    Creating a Prometheus Deployment with Helm

    Helm is the most popular package manager users employ with Kubernetes, and is part of the CNCF, together with Kubernetes and Prometheus. Others, such as Cloudsmith and Cloud Native Application Bundles (CNAB), aren’t as popular.

    Helm Charts with Kubernetes and Prometheus

    Helm’s big advantage is a unique packaging format called Chart, a collection of files that describe Kubernetes resources. A single chart can deploy a single simple resource, like a pod or a cluster rule.

    However, charts usually deploy sets of microservices, whole application stacks, and cluster configurations as one manageable deployment. Helm uses a powerful templating engine that lets it control flow, environment variables, and descriptions.

    Of course, Prometheus and Grafana aren’t the only products that benefit from charts. For another deployment example, check out this tutorial to deploy the ELK Stack on Kubernetes with Helm.

    Helm Chart Repositories

    Helm also uses chart repositories, which are basically HTTP servers that store curated and managed charts. Repositories can be 1) public (whereas the community maintains them) or 2) private and self-hosted. Chart repositories allow you to install and implement an entire preconfigured monitoring stack (like Prometheus) with just a few commands.

    We’ll skip the Helm installation and configuration process here. If you’re interested in it, consult the official documentation.

    To add a repository and install Prometheus Operator, use the following commands:

    helm repo add stable https://kubernetes-charts.storage.googleapis.com/
    helm install stable/prometheus-operator --name-template po
    
    
    
    
    

    The Prometheus Operator repository consists of charts with the following services:

    • Prometheus-operator
    • Prometheus
    • Node-exporter
    • Kube-state-metrics and service monitors
    • Grafana with dashboards and alerts

    To view Grafana in your browser, start a port-forwarding using these commands:

    kubectl port-forward $(kubectl get pods -l=app.kubernetes.io/name=grafana -o=jsonpath='{.items[*].metadata.name}') 3000:3000

    Grafana should be visible at http://localhost:3000. You can log in using admin as the user name and prom-operator or admin as the password.

    For more info on this topic, check out our Best Practices for Monitoring Kubernetes using Grafana. Also see our tutorial on deploying InfluxDB and Grafana with Helm to K8s.

    Figure 1: A Grafana dashboard
    Figure 1: A Grafana dashboard

    Instead of spending hours writing YAML manifests, choosing the right Docker Hub image, and fixing bugs, you can install a powerful monitoring stack with just a few commands. Thanks to templates, you still have some control over configuration.

    Why Isn’t Helm’s Charts Repository Perfect?

    Using public repositories is tempting, especially when deadlines are looming. But, in reality, you have no control over their configurations or resources. What if you miss a vulnerability, or one of the chart authors maliciously planted a hidden threat?

    Consider this worst-case scenario: What if a component’s failure stops the production cluster from working?

    With limited knowledge of what actually applied to the cluster, debugging the platform during an outage would be a nightmare. To prevent it, you may consider using Helm with a private container registry with a curated list of home-grown charts alongside gated and approved community charts. This way, you get the benefits of community contributed content without the risks involved with losing control over the components.

    Summary

    This article covered the two main approaches to configuring Prometheus deployments on Kubernetes: manual deployment of static YAML manifests with kubectl and Docker Hub images, versus automated and dynamic deployment with Helm charts.

    Which one is the better choice? As is often the case, it really depends on your desired outcome.

    Creating your own manifests from Docker Hub images gives you greater control over what is actually deployed inside the Kubernetes cluster. Using predefined Helm charts from stable repositories is incomparably quicker and error-proof. Either way, you end up with a solution that you’ll have to maintain, so make sure you feel comfortable with it.

    Get started for free

    Completely free for 14 days, no strings attached.