<p>We have an Istio cluster and we are trying to configure horizontal pod autoscale for Kubernetes. We want to use the request count as our custom metric for hpa. How can we utilise Istio's Prometheus for the same purpose?</p>

<p>This question turned out to be  much more complex than I expected, but finally here I am with the answer.</p>

<ol>
<li><p>First of all, you need to configure your application to provide custom metrics. It is on the developing application side. Here is an example, how to make it with Go language: <a href="https://mycodesmells.com/post/watching-metrics-with-prometheus" rel="nofollow noreferrer">Watching Metrics With Prometheus</a></p></li>
<li><p>Secondly, you need to define and deploy a Deployment of the application (or a Pod, or whatever you want) to Kubernetes, example:</p>

<pre><code>apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: podinfo
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: podinfo
      annotations:
        prometheus.io/scrape: 'true'
    spec:
      containers:
      - name: podinfod
        image: stefanprodan/podinfo:0.0.1
        imagePullPolicy: Always
        command:
          - ./podinfo
          - -port=9898
          - -logtostderr=true
          - -v=2
        volumeMounts:
          - name: metadata
            mountPath: /etc/podinfod/metadata
            readOnly: true
        ports:
        - containerPort: 9898
          protocol: TCP
        readinessProbe:
          httpGet:
            path: /readyz
            port: 9898
          initialDelaySeconds: 1
          periodSeconds: 2
          failureThreshold: 1
        livenessProbe:
          httpGet:
            path: /healthz
            port: 9898
          initialDelaySeconds: 1
          periodSeconds: 3
          failureThreshold: 2
        resources:
          requests:
            memory: "32Mi"
            cpu: "1m"
          limits:
            memory: "256Mi"
            cpu: "100m"
      volumes:
        - name: metadata
          downwardAPI:
            items:
              - path: "labels"
                fieldRef:
                  fieldPath: metadata.labels
              - path: "annotations"
                fieldRef:
                  fieldPath: metadata.annotations
---
apiVersion: v1
kind: Service
metadata:
  name: podinfo
  labels:
    app: podinfo
spec:
  type: NodePort
  ports:
    - port: 9898
      targetPort: 9898
      nodePort: 31198
      protocol: TCP
  selector:
    app: podinfo
</code></pre>

<p>Pay attention to the field <code>annotations: prometheus.io/scrape: 'true'</code>. It is required to request Prometheus to read metrics from the resource. Also note, there are two more annotations, which have default values; but if you change them in your application, you need to add them with the correct values: </p>

<ul>
<li><code>prometheus.io/path</code>: If the metrics path is not /metrics, define it with this annotation.</li>
<li><code>prometheus.io/port</code>: Scrape the pod on the indicated port instead of the pod’s declared ports (default is a port-free target if none are declared).</li>
</ul></li>
<li><p>Next, Prometheus in Istio uses its own modified for Istio purposes configuration, and by default it skips custom metrics from Pods. Therefore, you need to modify it a little.
In my case, I took configuration for Pod metrics from <a href="https://github.com/stefanprodan/k8s-prom-hpa/blob/master/prometheus/prometheus-cfg.yaml" rel="nofollow noreferrer">this example</a> and modified Istio's Prometheus configuration only for Pods:</p>

<pre><code>kubectl edit configmap -n istio-system prometheus
</code></pre>

<p>I changed the order of labels according to the example mentioned before:</p>

<pre><code># pod's declared ports (default is a port-free target if none are declared).
- job_name: 'kubernetes-pods'
  # if you want to use metrics on jobs, set the below field to
  # true to prevent Prometheus from setting the `job` label
  # automatically.
  honor_labels: false
  kubernetes_sd_configs:
  - role: pod
  # skip verification so you can do HTTPS to pods
  tls_config:
    insecure_skip_verify: true
  # make sure your labels are in order
  relabel_configs:
  # these labels tell Prometheus to automatically attach source
  # pod and namespace information to each collected sample, so
  # that they'll be exposed in the custom metrics API automatically.
  - source_labels: [__meta_kubernetes_namespace]
    action: replace
    target_label: namespace
  - source_labels: [__meta_kubernetes_pod_name]
    action: replace
    target_label: pod
  # these labels tell Prometheus to look for
  # prometheus.io/{scrape,path,port} annotations to configure
  # how to scrape
  - 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__
  - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scheme]
    action: replace
    target_label: __scheme__
</code></pre>

<p>After that, custom metrics appeared in Prometheus. But, <strong>be careful with changing Prometheus configuration</strong>, because some metrics required for Istio may disappear, check everything carefully.</p></li>
<li><p>Now it is time to install <a href="https://github.com/directxman12/k8s-prometheus-adapter" rel="nofollow noreferrer">Prometheus custom metric adapter</a>. </p>

<ul>
<li>Download <a href="https://github.com/directxman12/k8s-prometheus-adapter" rel="nofollow noreferrer">this</a> repository</li>
<li>Change the address for Prometheus server in the file <code>&lt;repository-directory&gt;/deploy/manifests/custom-metrics-apiserver-deployment.yaml</code>. Example, <code>- --prometheus-url=http://prometheus.istio-system:9090/</code> </li>
<li>Run command <code>kubectl apply -f &lt;repository-directory&gt;/deploy/manifests</code>
After some time, <code>custom.metrics.k8s.io/v1beta1</code> should appear in the output of a command 'kubectl api-vesions'.          </li>
</ul>

<p>Also, check the output of the custom API using commands <code>kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq .</code> and <code>kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/http_requests" | jq .</code>
The output of the last one should look like in the following example:</p>

<pre><code>{
  "kind": "MetricValueList",
  "apiVersion": "custom.metrics.k8s.io/v1beta1",
  "metadata": {
    "selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/%2A/http_requests"
  },
  "items": [
    {
      "describedObject": {
        "kind": "Pod",
        "namespace": "default",
        "name": "podinfo-6b86c8ccc9-kv5g9",
        "apiVersion": "/__internal"
          },
          "metricName": "http_requests",
          "timestamp": "2018-01-10T16:49:07Z",
          "value": "901m"    },
        {
      "describedObject": {
        "kind": "Pod",
        "namespace": "default",
        "name": "podinfo-6b86c8ccc9-nm7bl",
        "apiVersion": "/__internal"
      },
      "metricName": "http_requests",
      "timestamp": "2018-01-10T16:49:07Z",
      "value": "898m"
    }
  ]
}
</code></pre>

<p>If it does, you can move to the next step. If it doesn’t, look what APIs available for Pods in CustomMetrics <code>kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq . | grep "pods/"</code> and for http_requests <code>kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq . | grep "http"</code>. MetricNames are generating according to the metrics Prometheus gather from Pods and if they are empty, you need to look in that direction.</p></li>
<li><p>The last step is the configuring HPA and test it. So in my case, I created HPA for the podinfo application, defined before:</p>

<pre><code>apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: podinfo
spec:
  scaleTargetRef:
    apiVersion: extensions/v1beta1
    kind: Deployment
    name: podinfo
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Pods
    pods:
      metricName: http_requests
      targetAverageValue: 10
</code></pre>

<p>and used simple Go application to test load:</p>

<pre><code>#install hey
go get -u github.com/rakyll/hey
#do 10K requests rate limited at 25 QPS
hey -n 10000 -q 5 -c 5 http://&lt;K8S-IP&gt;:31198/healthz
</code></pre>

<p>After some time, I saw changes in scaling by using commands <code>kubectl describe hpa</code> and <code>kubectl get hpa</code></p></li>
</ol>

<p>I used instruction about creating Custom Metrics from the article <a href="https://dzone.com/articles/ensure-high-availability-and-uptime-with-kubernete" rel="nofollow noreferrer">Ensure High Availability and Uptime With Kubernetes Horizontal Pod Autoscaler and Prometheus</a></p>

<p>All useful links in one place:          </p>

<ul>
<li><a href="https://mycodesmells.com/post/watching-metrics-with-prometheus" rel="nofollow noreferrer">Watching Metrics With Prometheus</a> - the example of adding metrics to your application</li>
<li><a href="https://github.com/stefanprodan/k8s-prom-hpa" rel="nofollow noreferrer">k8s-prom-hpa</a> - the example of creating Custom Metrics for Prometheus (the same as in the article above)</li>
<li><a href="https://github.com/directxman12/k8s-prometheus-adapter" rel="nofollow noreferrer">Kubernetes Custom Metrics Adapter for Prometheus</a></li>
<li><a href="https://github.com/luxas/kubeadm-workshop#deploying-a-custom-metrics-api-server-and-a-sample-app" rel="nofollow noreferrer">Setting up the custom metrics adapter and sample app</a></li>
</ul>
