Prometheusで収集したメトリクスを元にHPA, kubectl topコマンドを可能にする

## はじめに
Horizontal Pod Autoscaler(HPA)やkubectl topコマンドを実行する時には、APIServiceである`metrics.k8s.io`からメトリクスを取得します。
Metrics Serverがデフォルトでデプロイされていない環境ではkubectl topコマンドを実行すると次のようなエラーが出ます。

```console
$ kubectl top node
Error from server (NotFound): the server could not find the requested resource (get services http:heapster:)
```

Metrics Serverを使用するのがよくあるパターンですが、今回はPrometheus Adapterを利用する方法を説明します。

## 環境
macOS Mojave 10.14.6
EKS 1.16
Helm 2.14.3
Helmfile 0.122.0

## 導入
EKSは構築済みで、Helm Tillerもデプロイ済みであるものとします。
PrometheusとPrometheus-AdapterをHelmfileを使ってデプロイします。

```yaml:helmfile.yaml
repositories:
  - name: stable
    url: https://kubernetes-charts.storage.googleapis.com

releases:
  # https://github.com/helm/charts/tree/master/stable/prometheus
  - name: prometheus
    namespace: kube-system
    chart: stable/prometheus
    version: 11.7.0
  # https://github.com/helm/charts/tree/master/stable/prometheus-adapter
  - name: prometheus-adapter
    namespace: kube-system
    chart: stable/prometheus-adapter
    version: 2.5.0
    values:
      - prometheus:
          url: http://prometheus-server.kube-system.svc
          port: 80
        rules:
          resource: 
            cpu:
              containerQuery: sum(rate(container_cpu_usage_seconds_total{<<.LabelMatchers>>}[3m])) by (<<.GroupBy>>)
              nodeQuery: sum(rate(container_cpu_usage_seconds_total{<<.LabelMatchers>>, id='/'}[3m])) by (<<.GroupBy>>)
              resources:
                overrides:
                  instance:
                    resource: node
                  namespace:
                    resource: namespace
                  pod:
                    resource: pod
              containerLabel: container
            memory:
              containerQuery: sum(container_memory_working_set_bytes{<<.LabelMatchers>>}) by (<<.GroupBy>>)
              nodeQuery: sum(container_memory_working_set_bytes{<<.LabelMatchers>>,id='/'}) by (<<.GroupBy>>)
              resources:
                overrides:
                  instance:
                    resource: node
                  namespace:
                    resource: namespace
                  pod:
                    resource: pod
              containerLabel: container
            window: 3m
```

```console
$ helmfile -f helmfile.yaml apply
```

PrometheusのHelm Chartのvaluesについてですが今回は何も設定しません。
外部からPrometheusにアクセスする場合はIngressの設定も必要になってくるので注意しましょう。

Prometheus AdapterのHelm Chartのvaluesについて説明します。
まず`.prometheus`です。ここではPrometheus Serviceを指定しています。これによりPrometheus Podにアクセスできるようになります。

次に`.rules.resource`です。ここで定義したものが`/apis/metrics.k8s.io/v1beta1`で取得できます。そしてHPAやkubectl topコマンドが利用できるようになります。

APIServiceが作成されたことは次のコマンドで分かります。

```console
$ kubectl describe apiservice v1beta1.metrics.k8s.io   
Name:         v1beta1.metrics.k8s.io
Namespace:    
Labels:       app=prometheus-adapter
              chart=prometheus-adapter-2.5.0
              heritage=Tiller
              release=prometheus-adapter
Annotations:  <none>
API Version:  apiregistration.k8s.io/v1
Kind:         APIService
Metadata:
  Creation Timestamp:  2020-07-19T09:24:54Z
  Resource Version:    27240
  Self Link:           /apis/apiregistration.k8s.io/v1/apiservices/v1beta1.metrics.k8s.io
  UID:                 e3c2e4f4-a58b-4294-8076-5f2cb51d6886
Spec:
  Group:                     metrics.k8s.io
  Group Priority Minimum:    100
  Insecure Skip TLS Verify:  true
  Service:
    Name:            prometheus-adapter
    Namespace:       kube-system
    Port:            443
  Version:           v1beta1
  Version Priority:  100
Status:
  Conditions:
    Last Transition Time:  2020-07-19T09:25:35Z
    Message:               all checks passed
    Reason:                Passed
    Status:                True
    Type:                  Available
Events:                    <none>
```

## 確認

HPA確認のために次のマニフェストファイルをデプロイします。

```yaml:hpa-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hpa-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hpa-test
  template:
    metadata:
      labels:
        app: hpa-test
    spec:
      containers:
        - image: busybox
          name: hpa-test
          command: ["dd", "if=/dev/zero", "of=/dev/null"]
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: hpa-test
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hpa-test
  minReplicas: 1
  maxReplicas: 5
  metrics:
    - type: Resource
      resource:
        name: cpu
        targetAverageUtilization: 50
```

```console
$ kubectl apply -f hpa-test.yaml
```

しばらくするとPodが最大数である5個立つことが確認できます。

```console
$ kubectl get pod
NAME                       READY   STATUS    RESTARTS   AGE
hpa-test-596d78d9d-2gwr5   1/1     Running   0          3m39s
hpa-test-596d78d9d-6l9jj   1/1     Running   0          21s
hpa-test-596d78d9d-hg7vc   1/1     Running   0          21s
hpa-test-596d78d9d-sxghk   1/1     Running   0          6s
hpa-test-596d78d9d-vntkf   1/1     Running   0          21s
```

kubectl topコマンドも問題なく動作します。

```console
$ kubectl top node
NAME                                              CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%   
ip-10-0-109-39.ap-northeast-1.compute.internal    1523m        78%    516Mi           15%       
ip-10-0-132-197.ap-northeast-1.compute.internal   1654m        85%    529Mi           15%       
ip-10-0-46-89.ap-northeast-1.compute.internal     833m         43%    741Mi           22%

$ kubectl top pod
NAME                       CPU(cores)   MEMORY(bytes)   
hpa-test-596d78d9d-2gwr5   1941m        1Mi             
hpa-test-596d78d9d-6l9jj   1362m        1Mi             
hpa-test-596d78d9d-hg7vc   1300m        1Mi             
hpa-test-596d78d9d-sxghk   1145m        1Mi             
hpa-test-596d78d9d-vntkf   1294m        2Mi
```

## まとめ
Metrics Serverを用いずにPrometheus AdapterとPrometheusでHPAとkubectl topコマンド実行が可能であることを確認しました。
Prometheusをクラスタにデプロイしている環境ではPrometheus Adapterを使用することも良いかもしれません。