type:LoadBalancerで作られたELBとserviceを関連付ける

## 真・タイトル
type:LoadBalancerのserviceを、awsに作られたkubernetes-clusterにapplyして、生成されたELBとserviceをprometheusで関連付けてメトリクスにする

## こんな人に

- AWSでkubernetesを使い
- 外部との通信にserviceを使い
- prometheusを使っている
- （ただEKSのことは知らない）

## ちょっと長いのでなにをやったか大まかに

- service名とELBを関連付けるために仲を取り持つexporterみたいなものを作った
- 既存のexporterと組み合わせてk8sのservice名からELBのメトリクスを取得できるようにした
- 使用例のdeploymentをまとめてみた

## 長い前置き
kubernetesのserviceでtype:LoadBalancerを選択してawsにelbを作ると、名前がランダムな文字列になって、そのメトリクスを他のkubernetes側のメトリクスと関連付けるのって少し面倒です。
https://github.com/kubernetes/kubernetes/issues/29789
ここで色々やりとりされてますけど結局どういう状況なんですかね。

aws + kubernetes + prometheus の場合、メトリクスの可視化にgrafanaを使ってcloudwatchメトリクスとprometheusをそれぞれデータソースにするのが割と定番な使い方になるかと思うのですが、多少の面倒や不都合がちょいちょい顔を出します。

例えば
serviceを作り直した時に、新しく作られたelbの名前を既存のものと置き換えなくてはいけない（elb名はランダムで勝手に作られる）
prometheusのメトリクスとcloudwatchのメトリクスで計算したくなることがある（requestCount / podCountなど）

前者はおとなしく手動でやるかスクリプトかなんかで対応することになるかと思います。
後者はcloudwatchメトリクスを<a href="https://github.com/prometheus/cloudwatch_exporter">cloudwatch_exporter</a>でprometheusのメトリクスにして対応します。

そしてこのcloudwatch_exporterが少し使いにくくて、得られるlabelがconfigのdimensionsで指定した項目だけのようです。
つまりcloudwatchメトリクスの情報をprometheusに入れても結局ランダムで作られたelbの名前を拾ってその他のリソースと関連付けてやらないといけないわけですね。
他にもこのcloudwatch_exporterは多少癖がある部分があるのですがそれはまた別の話…

で、思ったのが、serviceにこのcloudwatchメトリクスをservice-nameかなんかで紐付けてやればelbを作り直した後にいちいち設定し直さなくてもいいし、使い勝手も良くなるはず

## 必要なものを作成

まずは、どうやってelbとserviceを繋げるかですが、
serviceから作られたelbはデフォルトでtagに以下の画像のような情報が入るらしいです。
また後から出てきますが自由にtagを付与することもできます。

![スクリーンショット 2018-11-24 21.07.47.png](https://qiita-image-store.s3.amazonaws.com/0/122084/4afd1d14-4ff7-bd59-2fa9-5a3c7ce24195.png)

上はvalueが `namespace / service-name` になっていて、
下はkeyにcluster名が入っています。（モザイクの部分）

このtagのkey-valueをlabelにしたprometheusのメトリクスがあればserviceと関連付けられるのですが、そんなものを作ってくれるexporterは見つかりませんでしたので簡単なものを作りました。
https://github.com/buildsville/elb-tag-pusher
elbのtagを取得して<a href="https://github.com/prometheus/pushgateway">pushgateway</a>にメトリクスをpushします。
素直にexporterを作ろうと思ったのですが、tagのkey-valueをlabelにするというような動的にラベリングする方法がわからなかったのでこの形になりました。

これをdeployするとこんな感じのメトリクスが取得できます。

```
aws_elb_tags{
  app="elb-tag-pusher",
  exported_job="push_elb_tag",
  instance="0.0.0.0:9091",
  job="kubernetes-pods",
  kubernetes_io_cluster_my_cluster_name="owned",
  kubernetes_io_service_name="default_test-service",
  kubernetes_namespace="default",
  kubernetes_node_hostname="ip-0-0-0-0.ap-northeast-1.compute.internal",
  kubernetes_node_ip="10.0.13.151",
  kubernetes_pod_ip="0.0.0.0",
  kubernetes_pod_name="elb-tag-pusher-xxxxxxxxxx-xxxxx",
  load_balancer_name="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  pod_template_hash="2709900335"
} 1
```

pushgatewayが勝手に付けたlabelも含まれていますが、大事な部分は

```
load_balancer_name="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
kubernetes_io_service_name="default_test-service"
kubernetes_io_cluster_my_cluster_name="owned"
```

この辺りです。
load_balancer_nameはそのままelbの名前で、その他がtagをlabelにしたものです。
上の画像と見比べるとわかるのですが、記号が一部置換されています。
これはprometheusのlabelのkeyに使える記号は`:`と`_`のみで、valueには`/`が使えないという仕様に対応した苦肉の策で、実際に使う場面ではこれを考慮しないといけません。

## 使用例

### kube-state-metricsのdeploy

今回作るserviceのmetricsの土台になります。
Role付与が必要ですがconfigは特に必要ないのでdeployに関しては問題ないかと思います。
https://github.com/kubernetes/kube-state-metrics

### elbの準備
まずは、elbのtagに`service=[service名]`を追加します。
serviceのannotationに下記のようなものを追加すれば自由にtagを追加できます。

```
service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: 'service=test-service'
```

これで、elbに`key:service,value:test-service`のtagが付与され、先出のelb-tag-pusherが出すprometheusのメトリクスに`service="test-service"`のlabelが付与されます。
tagを追加せずにデフォルトで付く `kubernetes_io_service_name="default_test-service"` をlabel_replaceでいじってもいいのですがこちらの方が断然簡単です。

### elb-tag-pusherの準備
こんな感じでdeployしておきます。

```yaml:deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: elb-tag-pusher
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: elb-tag-pusher
  template:
    metadata:
      labels:
        app: elb-tag-pusher
      annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port: '9091'
      containers:
        - image: masahata/elb-tag-pusher:latest
          name: elb-tag-pusher
        - image: prom/pushgateway:v0.6.0
          name: pushgateway
          ports:
            - containerPort: 9091
```

下記の権限を付与してください。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:DescribeLoadBalancers",
                "elasticloadbalancing:DescribeTags"
            ],
            "Resource": "*"
        }
    ]
}
```

nodeにroleを付与するか~/.aws/credentialsを置くかcontainerの環境変数でアカウントを設定してください。

### cloudwatch_exporterの準備

https://github.com/prometheus/cloudwatch_exporter
elbのメトリクスを取得するようにconfigを設定してdeployします。
例としてrequest数とlatencyを取得してみます。

```yaml:config.yaml
region: ap-northeast-1
delay_seconds: 60
range_seconds: 60
metrics:
  - aws_namespace: AWS/ELB
    aws_metric_name: RequestCount
    aws_dimensions: [LoadBalancerName]
    aws_dimension_select_regex:
      LoadBalancerName: ["[0-9a-f]{32}"]
    aws_statistics: [Sum]
  - aws_namespace: AWS/ELB
    aws_metric_name: Latency
    aws_dimensions: [LoadBalancerName]
    aws_dimension_select_regex:
      LoadBalancerName: ["[0-9a-f]{32}"]
    aws_statistics: [Average]
```

`delay_seconds`と`range_seconds`はちょっと面倒な項目なので使用方法に合わせて設定します。

### メトリクス取得

ここまで正常にできていれば、こんなpromQL

```
kube_service_info
  * on(service) group_left(load_balancer_name) aws_elb_tags{service=~".+"}
  * on(load_balancer_name) group_left() aws_elb_request_count_sum
```

で、メトリクスが取得できるかと思います

```
{
  app="kube-state-metrics",
  cluster_ip="0.0.0.0",
  instance="0.0.0.0:8080",
  job="kubernetes-pods",
  kubernetes_namespace="default",
  kubernetes_node_hostname="ip-0-0-0-0.ap-northeast-1.compute.internal",
  kubernetes_node_ip="0.0.0.0",
  kubernetes_pod_ip="0.0.0.0",
  kubernetes_pod_name="kube-state-metrics-dc9bb7b77-xdg69",
  load_balancer_name="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  namespace="default",
  pod_template_hash="999999999"
  service="test-service"
} 15
```

※ elbにアクセスがないとcloudwatch_exporterのメトリクスが出ないのでこのメトリクスも取得できません

長くて面倒なのでprometheusのrecording ruleにするとよいです。

```yaml:rules.yaml
groups:
  - name: kube_service_extra.rules
    rules:
      - record: kube_service_request_count
        expr: kube_service_info * on(service) group_left(load_balancer_name) aws_elb_tags{service=~".+"} * on(load_balancer_name) group_left() aws_elb_request_count_sum
      - record: kube_service_latency_seconds
        expr: kube_service_info * on(service) group_left(load_balancer_name) aws_elb_tags{service=~".+"} * on(load_balancer_name) group_left() aws_elb_latency_average
```

これでservice名からelbのメトリクスを取得できるようになりました。
serviceを作り直してelbが変わってもtagが同じな限りは気にする必要がありません。


## 最後に少し

以上の内容をまとめたサンプルのyamlを https://github.com/buildsville/elb-tag-pusher/tree/master/sample に置いてあります。

https://github.com/DirectXMan12/k8s-prometheus-adapter を使えばautoscaleの指標にも使えるかと思います。
が、cloudwatch_exporterでどうしても多少の遅れが出てしまうので瞬発力が必要な場面では少し難しいかもしれません。

この辺も書きたかったのですが長くなりすぎるので今回は以上です。