今やっていることをとりあえずアウトプットしなければ、外部発表しなければ、と思いちょうど KubernetesNovice が良いタイミングだったので応募してみた。
運営の方々ありがとうございました。皆さんのおかげで私のような小心者な初心者でも外部発表を経験する良い機会を得ることができました。
一応さらっと中身の説明を書きます。
モチベーション
そもそも私の身の回りの Kubernetes なプロジェクトでは、あまりちゃんと性能評価できている節がないように見える。
それが私の気のせいなのか、一部例しか見れてないのかわからないが、少なくとも弊部署にはナレッジがないので、性能に携わる者として Kubernetes× 性能のベストラプラクティスをまずは整理しておきたいと思った。
本当は性能試験を自動化して開発パイプラインに組み込む DevPerfOps という世界観がこうあるべき、という話までたどり着けると良いなあと思っていたのだが、現時点ではまず性能評価だけでも当たり前のように奥が深く、その深淵の先なのかなと思っている。。。
デモアプリ Sock Shop について
Weaveworks が公開している MSA デモアプリの Sock Shop を K8s v1.16 へ対応させた人の Fork してきて使っている。
多岐に渡るデプロイ方法が提供されているが、今の所はただ yaml の Manifest を kubectl apply -f しているだけ。
したがって更新しているディレクトリも/deploy/kubernetes/
のみ。
ゆくゆくは Helm での管理に倒したいと思っているけど、知識と時間がまだない。
この靴下の EC サイトである Sock Shop を GKE にデプロイして、性能試験っぽいこと(後述)をして、実施 → 評価 → 解析といういわゆる性能改善の営みを回しながらベストラプラクティスを整理している。同時に性能問題事例も収集したい。
アーキテクチャ
- Prometheus & Loki & Jager & Kiali でオブザーバビリティ実装
Prometheus でのメトリクス監視
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:元ネタ
- replace:regex 連結され source_labels たと照合します。次に、設定 target_label に replacement 一致グループの参照(と、${1}、${2}、...)で replacement、その値によって置換されました。regex 一致しない場合、置換は行われません。
- → 一致した部分を target_label に挿入
- keep:regex 連結に一致しないターゲットをドロップします source_labels。
- drop:regex 連結に一致するターゲットを削除し 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
# 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
を用意した。
- https://github.com/kashinoki38/prometheus-sample-yaml/blob/master/grafana/pod_all.json
- https://github.com/kashinoki38/prometheus-sample-yaml/blob/master/grafana/pod_detail.json
node exporter
Node のリソース使用量は NodeExporter を利用。
NodeExporter
node exporter を DaemonSet としてデプロイ
prometheus.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
# 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-Dashboard
とIstio-Workload-Dashboard
が今の所わかりやすい。
Istio-Mesh-Dashboard
はリアルタイムな RED 把握によく、Istio-Workload-Dashboard
は試験後の時系列での RED 把握によい。
とくにIstio-Workload-Dashboard
では、IncomingDuration と OutgoingDuration が見れるので、それにより後続のマイクロサービスからの影響を確認することが可能。
Istio-Mesh-Dashboard
は、もし各マイクロサービスについて SLO を設けることができているのであれば、それと比較することができるので Jmeter の結果以外の評価もできるのではないだろうか。
- https://github.com/kashinoki38/prometheus-sample-yaml/blob/master/grafana/Istio-Mesh-Dashboard.json
- https://github.com/kashinoki38/prometheus-sample-yaml/blob/master/grafana/Istio-Workload-Dashboard.json
トラシュー
- Service の Port name を protocol-suffix の命名形式にしないとテレメトリが収集されなかった
- https://kiali.io/documentation/validations/#_kia0601_port_name_must_follow_protocol_suffix_form
- https://istio.io/docs/ops/configuration/traffic-management/protocol-selection/#manual-protocol-selection
Istio では、サービスポートが「protocol-suffix」の命名形式に従う必要があります。「-suffix」部分はオプションです。命名がこの形式と一致しない場合(または未定義の場合)、Istio は定義で定義されているプロトコルではなく、すべてのトラフィック TCP を処理します。ダッシュはプロトコルとサフィックスの間に必要な文字です。たとえば、「http2foo」は無効ですが、「http2-foo」は有効です(http2 プロトコルの場合)。
- Istio1.5 から、telemetry のメトリクスが以下のように変更に。Istio on GKE は 2020/06/01 現在で、最新 1.4.6
istio_request_duration_seconds_bucket
→istio_request_duration_milliseconds_bucket
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
template: spec: securityContext: runAsUser: 1000 runAsGroup: 3000 fsGroup: 2000
persistent volume
作成した PV を prometheus-sts 側でマウント
(略) 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 としてデプロイ
- https://github.com/kashinoki38/microservices-demo/blob/master/deploy/kubernetes/manifests-loadtest/jmeter-loadtest-jmeter-master-dep.yaml
- https://github.com/kashinoki38/microservices-demo/blob/master/deploy/kubernetes/manifests-loadtest/jmeter-loadtest-influxdb-dep.yaml
もとは JmeterOperator をデプロイしてみようとしてたけど、オペレータのリソース使用量が高くやめた。
https://github.com/kubernauts/jmeter-operator
Grafana
InfluxDB に格納された Jmeter 試験結果データ参照用のダッシュボード
https://github.com/kashinoki38/prometheus-sample-yaml/blob/master/grafana/jmeter-metrics.json
シナリオ
以下のように Jmeter のシナリオを作成。
事前に会員登録しておいたユーザを使って、ログイン → 商品閲覧 → カート投入 → 決済 → オーダ確認のシナリオ。
- シナリオフロー定義
- 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