kashinoki38 blog

something like tech blog

KubernetesNoviceTokyo で「Kubernetes 環境に対する性能試験」を発表した

今やっていることをとりあえずアウトプットしなければ、外部発表しなければ、と思いちょうど KubernetesNovice が良いタイミングだったので応募してみた。
運営の方々ありがとうございました。皆さんのおかげで私のような小心者な初心者でも外部発表を経験する良い機会を得ることができました。

k8s-novice-jp.connpass.com

speakerdeck.com

一応さらっと中身の説明を書きます。

モチベーション

そもそも私の身の回りの Kubernetes なプロジェクトでは、あまりちゃんと性能評価できている節がないように見える。
それが私の気のせいなのか、一部例しか見れてないのかわからないが、少なくとも弊部署にはナレッジがないので、性能に携わる者として Kubernetes× 性能のベストラプラクティスをまずは整理しておきたいと思った。
本当は性能試験を自動化して開発パイプラインに組み込む DevPerfOps という世界観がこうあるべき、という話までたどり着けると良いなあと思っていたのだが、現時点ではまず性能評価だけでも当たり前のように奥が深く、その深淵の先なのかなと思っている。。。

デモアプリ Sock Shop について

Weaveworks が公開している MSA デモアプリの Sock Shop を K8s v1.16 へ対応させた人の Fork してきて使っている。

github.com

多岐に渡るデプロイ方法が提供されているが、今の所はただ yaml の Manifest を kubectl apply -f しているだけ。
したがって更新しているディレクトリも/deploy/kubernetes/のみ。
ゆくゆくは Helm での管理に倒したいと思っているけど、知識と時間がまだない。

この靴下の EC サイトである Sock Shop を GKE にデプロイして、性能試験っぽいこと(後述)をして、実施 → 評価 → 解析といういわゆる性能改善の営みを回しながらベストラプラクティスを整理している。同時に性能問題事例も収集したい。

アーキテクチャ

f:id:kashionki38:20200706004457p:plain

  • Prometheus & Loki & Jager & Kiali でオブザーバビリティ実装

Prometheus でのメトリクス監視

f:id:kashionki38:20200706004512p:plain

RED + USE を Prometheus で実現
具体的な Prometheus の yaml はこちら(Scrape 設定)
https://github.com/kashinoki38/microservices-demo/blob/master/deploy/kubernetes/manifests-monitoring/prometheus-configmap.yaml

以下 Scraping の設定を列挙するけど、書きながら全然理解できてないことに気づく、、、
要勉強。

今、実装できている部分は最低限の OS リソース系の USE のみ。
本来は MW のリソースについても確認したいし、管理リソースについても監視していきたいので今後の課題である。

prometheus.yaml の relabel_config について

まだまとまってないし翻訳ほぼ貼っただけくらいのやつ。 https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config
relabel_configs で取得すべき項目のラベルを作っている

  • source_labels:元ネタ
    • regex正規表現抽出した項目について replace、keep、drop、labelmap、labeldrop,labelkeep でアクションする
  • replace:regex 連結され source_labels たと照合します。次に、設定 target_label に replacement 一致グループの参照(と、${1}、${2}、...)で replacement、その値によって置換されました。regex 一致しない場合、置換は行われません。
    • → 一致した部分を target_label に挿入
  • keep:regex 連結に一致しないターゲットをドロップします source_labels。
  • dropregex 連結に一致するターゲットを削除し source_labels ます。
  • hashmod:連結されたハッシュのに設定さ target_label れ modulus ます source_labels。
  • labelmap:regex すべてのラベル名と照合します。その後で与えられたラベル名に一致するラベルの値をコピー replacement 一致グループの参照を(${1}、${2}中、...)replacement その値によって置換されています。
  • labeldrop:regex すべてのラベル名と照合します。一致するラベルは、ラベルのセットから削除されます。
  • labelkeep:regex すべてのラベル名と照合します。一致しないラベルは、ラベルのセットから削除されます。

元ネタには以下のような meta タグを使用する

__meta_kubernetes_namespace: The namespace of the service object.
__meta_kubernetes_service_annotation_<annotationname>: Each annotation from the service object.
__meta_kubernetes_service_annotationpresent_<annotationname>: "true" for each annotation of the service object.
__meta_kubernetes_service_cluster_ip: The cluster IP address of the service. (Does not apply to services of type ExternalName)
__meta_kubernetes_service_external_name: The DNS name of the service. (Applies to services of type ExternalName)
__meta_kubernetes_service_label_<labelname>: Each label from the service object.
__meta_kubernetes_service_labelpresent_<labelname>: true for each label of the service object.
__meta_kubernetes_service_name: The name of the service object.
__meta_kubernetes_service_port_name: Name of the service port for the target.
__meta_kubernetes_service_port_protocol: Protocol of the service port for the target.
__meta_kubernetes_service_type: The type of the service.

cAdvisor

Pod/Conatiner のリソース使用量は Kubelet バイナリに統合されている cAdvisor に対して Scrape する。
したがって特に Exporter とかは不要。

prometheus.yaml

https://github.com/kashinoki38/microservices-demo/blob/master/deploy/kubernetes/manifests-monitoring/prometheus-configmap.yaml

# Scrape config for Kubelet cAdvisor.
- 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
Grafana ダッシュボード

Namespace 内の全 Pod を俯瞰するpod_allと各 Pod とその中の Container を詳細に見るpod_detailを用意した。

node exporter

Node のリソース使用量は NodeExporter を利用。

NodeExporter

https://github.com/kashinoki38/microservices-demo/blob/master/deploy/kubernetes/manifests-monitoring/node-exporter-ds.yml

node exporter を DaemonSet としてデプロイ

prometheus.yaml

https://github.com/kashinoki38/microservices-demo/blob/master/deploy/kubernetes/manifests-monitoring/prometheus-configmap.yaml

- 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_namespace
    #     - __meta_kubernetes_pod_label_name
    #   separator: /
    #   target_label: job
    - source_labels:
        - __meta_kubernetes_pod_node_name
      target_label: node
    - source_labels: [__meta_kubernetes_namespace]
      action: replace
      target_label: namespace
    - source_labels: [__meta_kubernetes_pod_name]
      action: replace
      target_label: pod_name
Grafana ダッシュボード

https://github.com/kashinoki38/prometheus-sample-yaml/blob/master/grafana/node-exporter.json

istio-mesh

各サービスごとの RED を見ようと思うと Istio のテレメトリ収集が有用。
APM 使ったり AP で独自にログを出すことでもサービスごとの RED を収集できるとは思うが、APM 使用量や AP 実装についてコストがかかるのでメリデメある印象。
もちろん Istio を入れることでもデメリットはあるはずで、そこを許容してでもマイクロサービスにオブザーバビリティやトラフィックコントロール柔軟性、セキュリティをもたせたいと思うかどうかがポイントになる。
(例えば、Istio のコントロールプレーンや Envoy のデプロイによるリソース使用量増加、すべてのトラフィックを Envoy 経由にすることによるレスポンスへのオーバヘッド増加など)
マイクロサービスを入れておきながら、各依存関係間の RED がわからないと性能評価できるわけないので、実装できないなら Istio 入れるしかないという所感 Istio を入れてしまえば、後は Prometheus の Scrape を設定すればテレメトリ情報は収集可能。

prometheus.yaml

https://github.com/kashinoki38/microservices-demo/blob/master/deploy/kubernetes/manifests-monitoring/prometheus-configmap.yaml

# Mixer scrapping. Defaults to Prometheus and mixer on same namespace.
#
- job_name: "istio-mesh"
  kubernetes_sd_configs:
    - role: endpoints
      namespaces:
        names:
          - istio-system
  relabel_configs:
    - source_labels:
        [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
      action: keep
      regex: istio-telemetry;prometheus
Grafana ダッシュボード

以下、Istio-Mesh-DashboardIstio-Workload-Dashboardが今の所わかりやすい。
Istio-Mesh-Dashboardはリアルタイムな RED 把握によく、Istio-Workload-Dashboardは試験後の時系列での RED 把握によい。
とくにIstio-Workload-Dashboardでは、IncomingDuration と OutgoingDuration が見れるので、それにより後続のマイクロサービスからの影響を確認することが可能。
Istio-Mesh-Dashboardは、もし各マイクロサービスについて SLO を設けることができているのであれば、それと比較することができるので Jmeter の結果以外の評価もできるのではないだろうか。

トラシュー

kube-state-metrics

Pod/Container の Status や数を取得するためにkube-state-metricsも導入。
これを入れることで READY な数や、RESTART しているタイミングなどの状況把握が可能。
クラスター内に 1Deployment あれば良い。 https://github.com/kashinoki38/microservices-demo/blob/master/deploy/kubernetes/manifests-monitoring/prometheus-exporter-kube-state-dep.yaml

ものすごい量の情報が取れるのでまずは Doc を読んでみるのもよい。
https://github.com/kubernetes/kube-state-metrics/tree/master/docs

StatefulSet 化+ PersistentVolume

Prometheus に死なれるとリソース情報が全く残らなくてポストモーテムもクソもないので、さすがに Prometheus はせめて PV ででも永続化したほうが良い。
よりメモリ使用量削減や冗長構成を意識して、Victoria Metrics とか Thanos を使った永続化方法もあるようで、今後の課題ではある。
無邪気に Scrape していると Prometheus は結構メモリを食うので、急に OOME なったりするのでそうなってくると Victoria Metrics 付加を検討したい。
https://kashionki38.hatenablog.com/entry/2020/03/06/153039#Prometheus%E3%81%AF%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E3%81%A0%E3%81%A8%E3%82%81%E3%81%A3%E3%81%A1%E3%82%83%E3%83%A1%E3%83%A2%E3%83%AA%E9%A3%9F%E3%81%86%E3%82%88

※Victoria Metrics についてはこちら
www.youtube.com

prometheus statefulset

securityContext を付与しないと pv へのアクセス権限がなくエラーが出る

$ k logs prometheus-deployment-0
level=info ts=2020-06-21T22:35:42.534Z caller=main.go:337 msg="Starting Prometheus" version="(version=2.18.0, branch=HEAD, revision=a12e96299dcd159ea09b260f1a21e7e4b86e011d)"
level=info ts=2020-06-21T22:35:42.534Z caller=main.go:338 build_context="(go=go1.14.2, user=root@7fbcff55abdb, date=20200505-14:26:04)"
level=info ts=2020-06-21T22:35:42.534Z caller=main.go:339 host_details="(Linux 4.19.109+ #1 SMP Mon Mar 16 06:27:01 PDT 2020 x86_64 prometheus-deployment-0 (none))"
level=info ts=2020-06-21T22:35:42.534Z caller=main.go:340 fd_limits="(soft=1048576, hard=1048576)"
level=info ts=2020-06-21T22:35:42.535Z caller=main.go:341 vm_limits="(soft=unlimited, hard=unlimited)"
level=error ts=2020-06-21T22:35:42.535Z caller=query_logger.go:87 component=activeQueryTracker msg="Error opening query log file" file=data/queries.active err="open data/queries.active: permission denied"
panic: Unable to create mmap-ed active query log

goroutine 1 [running]:
github.com/prometheus/prometheus/promql.NewActiveQueryTracker(0x26b4395, 0x5, 0x14, 0x2e940a0, 0xc0003d8e40, 0x2e940a0)
        /app/promql/query_logger.go:117 +0x4cd
main.main()
        /app/cmd/prometheus/main.go:368 +0x4ed8

https://github.com/kashinoki38/microservices-demo/blob/master/deploy/kubernetes/manifests-monitoring/prometheus-dep.yaml

prometheus-sts.yaml

template:
  spec:
    securityContext:
      runAsUser: 1000
      runAsGroup: 3000
      fsGroup: 2000
persistent volume

https://github.com/kashinoki38/microservices-demo/blob/master/deploy/kubernetes/manifests-monitoring/prometheus-pv.yml

作成した PV を prometheus-sts 側でマウント

prometheus-sts.yaml

(略)
      containers:
      - name: prometheus
        image: prom/prometheus:v2.18.0
        args:
        - --web.enable-lifecycle
        - --storage.tsdb.no-lockfile
        - --storage.tsdb.retention.time=6h
        - --config.file=/etc/prometheus/prometheus.yml #重要
        volumeMounts:
        - name: config-volume
          mountPath: /etc/prometheus
        - name: alertrules-volume
          mountPath: /etc/prometheus-rules
        - name: prometheus-pv #重要
          mountPath: /prometheus/data
      volumes:
      - name: prometheus-pv #重要
        persistentVolumeClaim:
          claimName: cn-horiuchiysh-prometheus-pvc-prometheus-deployment-0
      nodeSelector:
        beta.kubernetes.io/os: linux
  volumeClaimTemplates: #重要
    - metadata:
        name: cn-horiuchiysh-prometheus-pvc
      spec:
        storageClassName: manual
        selector:
          matchLabels:
            objective: prometheus
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 40Gi

Loki

Loki は Promtail を DaemonSet として各 Node で走らせて標準出力を収集
Grafana の Explore でログを見れるので、Prometheus + Grafana と併用するのに向いている
まだあまり使用検証できてないので今後詳しい話を書いていければ。

取り急ぎ Helm で導入。
https://github.com/kashinoki38/prometheus-sample-yaml/tree/master/loki-helm/loki-stack

$ helm repo add loki https://grafana.github.io/loki/charts #リポジトリの追加
$ helm repo list
$ helm inspect chart loki/loki-stack
$ helm inspect values loki/loki-stack
$ helm install -n monitoring loki/loki-stack --generate-name #リポジトリで公開されているChartを使ってアプリケーションをインストール(デプロイ)する
$ helm list -n monitoring #デプロイされたReleaseの一覧
NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
loki-stack-1591529810   monitoring      1               2020-06-07 20:36:59.2916867 +0900 JST   deployed        loki-stack-0.37.3       v1.5.0

Jaeger & Kiali

Istio を入れることで Jaeger と Kiali も簡単に入れることが可能。

Sock Shop には Kiali が入っていなかったので、以下シェルで Kiali を有効化。
https://github.com/kashinoki38/prometheus-sample-yaml/blob/master/jaeger-kiali/istioctl-jaeger-kiali.sh

Jaeger は以下

$ istioctl manifest apply --set values.tracing.enabled=true

Jaeger でトレーシングさせるためには環境変数に Jaeger の向き先を ZIPKIN として登録する必要があるっぽい。 以下 catalogue,user,payment の deploy に追加

      containers:
      - name: catalogue
        image: weaveworksdemos/catalogue:0.3.5
        env:
         - name: ZIPKIN
           value: http://zipkin.istio-system.svc.cluster.local:9411/api/v1/spans
        command: ["/app"]
        args:
        - -port=80

これもあんまり Sock Shop 検証では使いこなせてないので、今後詳しい話を述べていければ。 Bookinfo サンプルに対しての使いごごちはこちら。 kashionki38.hatenablog.com

今の所言えるのは、マイクロサービスサービス全体の把握をしたい場合は Kiali を眺めるのが一番可視性が良さそうということ

Jmeter

Deployment

以下 Jmeter と InnfluxDB を Deployment としてデプロイ

もとは JmeterOperator をデプロイしてみようとしてたけど、オペレータのリソース使用量が高くやめた。
https://github.com/kubernauts/jmeter-operator

Grafana

InfluxDB に格納された Jmeter 試験結果データ参照用のダッシュボード

https://github.com/kashinoki38/prometheus-sample-yaml/blob/master/grafana/jmeter-metrics.json

シナリオ

以下のように Jmeter のシナリオを作成。
事前に会員登録しておいたユーザを使って、ログイン → 商品閲覧 → カート投入 → 決済 → オーダ確認のシナリオ。

JmeterDeployment への実行方法はstart_test.shを実行して、シナリオ名を指定すれば良い。
https://github.com/kashinoki38/microservices-demo/blob/master/deploy/kubernetes/manifests-loadtest/start_test.sh

kashinoki38@DESKTOP-NTAMOVN:/mnt/c/Users/motim/sockshop/manifests-loadtest$ sh start_test.sh
Enter path to the jmx file scenario.jmx
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/jmeter/apache-jmeter-5.0/lib/log4j-slf4j-impl-2.11.0.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/jmeter/apache-jmeter-5.0/lib/ext/pepper-box-1.0.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.apache.logging.slf4j.Log4jLoggerFactory]
Creating summariser <summary>
Created the tree successfully using scenario.jmx
Starting the test @ Sun Jun 28 20:33:48 UTC 2020 (1593376428758)
Waiting for possible Shutdown/StopTestNow/Heapdump message on port 4445
summary +     95 in 00:00:09 =   10.2/s Avg:    81 Min:     5 Max:  2773 Err:     0 (0.00%) Active: 1 Started: 1 Finished: 0
summary +    788 in 00:00:30 =   26.3/s Avg:    66 Min:     2 Max:  2589 Err:     0 (0.00%) Active: 3 Started: 3 Finished: 0
summary =    883 in 00:00:39 =   22.5/s Avg:    68 Min:     2 Max:  2773 Err:     0 (0.00%)
summary +   1400 in 00:00:30 =   46.7/s Avg:    73 Min:     2 Max:  3147 Err:     0 (0.00%) Active: 4 Started: 4 Finished: 0
summary =   2283 in 00:01:09 =   32.9/s Avg:    71 Min:     2 Max:  3147 Err:     0 (0.00%)
summary +   1839 in 00:00:30 =   61.3/s Avg:    81 Min:     3 Max:  3719 Err:     0 (0.00%) Active: 6 Started: 6 Finished: 0
summary =   4122 in 00:01:39 =   41.5/s Avg:    75 Min:     2 Max:  3719 Err:     0 (0.00%)
summary +   2065 in 00:00:30 =   68.8/s Avg:    85 Min:     2 Max:  3654 Err:     0 (0.00%) Active: 8 Started: 8 Finished: 0
summary =   6187 in 00:02:09 =   47.9/s Avg:    79 Min:     2 Max:  3719 Err:     0 (0.00%)
summary +   2144 in 00:00:30 =   71.5/s Avg:    87 Min:     2 Max:  4162 Err:     0 (0.00%) Active: 9 Started: 9 Finished: 0
summary =   8331 in 00:02:39 =   52.3/s Avg:    81 Min:     2 Max:  4162 Err:     0 (0.00%)
summary +   2092 in 00:00:30 =   69.7/s Avg:    97 Min:     3 Max:  4235 Err:     0 (0.00%) Active: 10 Started: 10 Finished: 0
summary =  10423 in 00:03:09 =   55.1/s Avg:    84 Min:     2 Max:  4235 Err:     0 (0.00%)
summary +   2218 in 00:00:30 =   73.9/s Avg:    91 Min:     2 Max:  3895 Err:     0 (0.00%) Active: 10 Started: 10 Finished: 0
summary =  12641 in 00:03:39 =   57.6/s Avg:    85 Min:     2 Max:  4235 Err:     0 (0.00%)
summary +   2035 in 00:00:30 =   67.8/s Avg:   104 Min:     3 Max:  4724 Err:     0 (0.00%) Active: 10 Started: 10 Finished: 0
summary =  14676 in 00:04:09 =   58.9/s Avg:    88 Min:     2 Max:  4724 Err:     0 (0.00%)
summary +   2148 in 00:00:30 =   71.6/s Avg:    98 Min:     2 Max:  4252 Err:     0 (0.00%) Active: 10 Started: 10 Finished: 0
summary =  16824 in 00:04:39 =   60.2/s Avg:    89 Min:     2 Max:  4724 Err:     0 (0.00%)
summary +   2156 in 00:00:30 =   71.8/s Avg:    96 Min:     2 Max:  4133 Err:     0 (0.00%) Active: 10 Started: 10 Finished: 0
summary =  18980 in 00:05:09 =   61.4/s Avg:    90 Min:     2 Max:  4724 Err:     0 (0.00%)
command terminated with exit code 137

試験実施について

このへんで力尽きたし、試験実施の流れはスライドのほうが見やすいのでそちらを見てください mm(_ _)mm