モニタリング

カスタムコントローラーの運用にはモニタリングが重要です。 システムを安定運用させるためには、カスタムコントローラーが管理するリソースやカスタムコントローラー自身に何か問題が生じた場合、 それを検出して適切な対応をおこなう必要があります。

ここでは、controller-runtimeが提供するメトリクス公開の仕組みについて紹介します。

基本メトリクス

Kubebuilderが生成したコードでは、自動的に基本的なメトリクスが公開されるようになっています。 CPUやメモリの使用量や、Reconcileにかかった時間やKubernetesクライアントのレイテンシーなど、controller-runtime関連のメトリクスが公開されています。

どのようなメトリクスが公開されているのか見てみましょう。 NewManagerのオプションのMetricsBindAddressで指定されたアドレスにアクセスすると、公開されているメトリクスを確認できます。

main.go

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
    Scheme:                 scheme,
    MetricsBindAddress:     metricsAddr,
    Port:                   9443,
    HealthProbeBindAddress: probeAddr,
    LeaderElection:         enableLeaderElection,
    LeaderElectionID:       "3ca5b296.zoetrope.github.io",
    // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
    // when the Manager ends. This requires the binary to immediately end when the
    // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
    // speeds up voluntary leader transitions as the new leader don't have to wait
    // LeaseDuration time first.
    //
    // In the default scaffold provided, the program ends immediately after
    // the manager stops, so would be fine to enable this option. However,
    // if you are doing or is intended to do any operation such as perform cleanups
    // after the manager stops then its usage might be unsafe.
    // LeaderElectionReleaseOnCancel: true,
})

まずはメトリクス用のポートをPort Forwardします。

kubectl -n markdown-view-system port-forward deploy/markdown-view-controller-manager 8080:8080

curlを実行すると、下記のようなメトリクスが出力されます。

$ curl localhost:8080/metrics
# HELP controller_runtime_active_workers Number of currently used workers per controller
# TYPE controller_runtime_active_workers gauge
controller_runtime_active_workers{controller="markdownview"} 0
# HELP controller_runtime_max_concurrent_reconciles Maximum number of concurrent reconciles per controller
# TYPE controller_runtime_max_concurrent_reconciles gauge
controller_runtime_max_concurrent_reconciles{controller="markdownview"} 1
# HELP controller_runtime_reconcile_errors_total Total number of reconciliation errors per controller
# TYPE controller_runtime_reconcile_errors_total counter
controller_runtime_reconcile_errors_total{controller="markdownview"} 0
# HELP controller_runtime_reconcile_total Total number of reconciliations per controller
# TYPE controller_runtime_reconcile_total counter
controller_runtime_reconcile_total{controller="markdownview",result="error"} 0
controller_runtime_reconcile_total{controller="markdownview",result="requeue"} 0
controller_runtime_reconcile_total{controller="markdownview",result="requeue_after"} 0
controller_runtime_reconcile_total{controller="markdownview",result="success"} 0
# HELP controller_runtime_webhook_requests_in_flight Current number of admission requests being served.
# TYPE controller_runtime_webhook_requests_in_flight gauge
controller_runtime_webhook_requests_in_flight{webhook="/mutate-view-zoetrope-github-io-v1-markdownview"} 0
controller_runtime_webhook_requests_in_flight{webhook="/validate-view-zoetrope-github-io-v1-markdownview"} 0
# HELP controller_runtime_webhook_requests_total Total number of admission requests by HTTP status code.
# TYPE controller_runtime_webhook_requests_total counter
controller_runtime_webhook_requests_total{code="200",webhook="/mutate-view-zoetrope-github-io-v1-markdownview"} 0
controller_runtime_webhook_requests_total{code="200",webhook="/validate-view-zoetrope-github-io-v1-markdownview"} 0
controller_runtime_webhook_requests_total{code="500",webhook="/mutate-view-zoetrope-github-io-v1-markdownview"} 0
controller_runtime_webhook_requests_total{code="500",webhook="/validate-view-zoetrope-github-io-v1-markdownview"} 0

・・・ 以下省略

カスタムメトリクス

controller-runtimeが提供するメトリクスだけでなく、カスタムコントローラー固有のメトリクスの公開もできます。 詳しくはPrometheusのドキュメントを参照してください。

ここではMarkdownViewリソースのステータスをメトリクスとして公開してみましょう。 MarkdownViewには3種類のステータスがあるので、Gauge Vectorも3つ用意します。

metrics.go

package controllers

import (
    "github.com/prometheus/client_golang/prometheus"
    "sigs.k8s.io/controller-runtime/pkg/metrics"
)

const (
    metricsNamespace = "markdownview"
)

var (
    NotReadyVec = prometheus.NewGaugeVec(prometheus.GaugeOpts{
        Namespace: metricsNamespace,
        Name:      "notready",
        Help:      "The cluster status about not ready condition",
    }, []string{"name", "namespace"})

    AvailableVec = prometheus.NewGaugeVec(prometheus.GaugeOpts{
        Namespace: metricsNamespace,
        Name:      "available",
        Help:      "The cluster status about available condition",
    }, []string{"name", "namespace"})

    HealthyVec = prometheus.NewGaugeVec(prometheus.GaugeOpts{
        Namespace: metricsNamespace,
        Name:      "healthy",
        Help:      "The cluster status about healthy condition",
    }, []string{"name", "namespace"})
)

func init() {
    metrics.Registry.MustRegister(NotReadyVec, AvailableVec, HealthyVec)
}

メトリクスを更新するための関数を用意します。

markdownview_controller.go

func (r *MarkdownViewReconciler) setMetrics(mdView viewv1.MarkdownView) {
    switch mdView.Status {
    case viewv1.MarkdownViewNotReady:
        NotReadyVec.WithLabelValues(mdView.Name, mdView.Namespace).Set(1)
        AvailableVec.WithLabelValues(mdView.Name, mdView.Namespace).Set(0)
        HealthyVec.WithLabelValues(mdView.Name, mdView.Namespace).Set(0)
    case viewv1.MarkdownViewAvailable:
        NotReadyVec.WithLabelValues(mdView.Name, mdView.Namespace).Set(0)
        AvailableVec.WithLabelValues(mdView.Name, mdView.Namespace).Set(1)
        HealthyVec.WithLabelValues(mdView.Name, mdView.Namespace).Set(0)
    case viewv1.MarkdownViewHealthy:
        NotReadyVec.WithLabelValues(mdView.Name, mdView.Namespace).Set(0)
        AvailableVec.WithLabelValues(mdView.Name, mdView.Namespace).Set(0)
        HealthyVec.WithLabelValues(mdView.Name, mdView.Namespace).Set(1)
    }
}

Statusを更新する際にこの関数を呼び出します。

markdownview_controller.go

r.setMetrics(mdView)

また、メトリクスを削除するための関数も用意します。

markdownview_controller.go

func (r *MarkdownViewReconciler) removeMetrics(mdView viewv1.MarkdownView) {
    NotReadyVec.DeleteLabelValues(mdView.Name, mdView.Namespace)
    AvailableVec.DeleteLabelValues(mdView.Name, mdView.Namespace)
    HealthyVec.DeleteLabelValues(mdView.Name, mdView.Namespace)
}

以下のように、リソースが削除された際にメトリクスを削除するようにしましょう。

markdownview_controller.go

var mdView viewv1.MarkdownView
err := r.Get(ctx, req.NamespacedName, &mdView)
if errors.IsNotFound(err) {
    r.removeMetrics(mdView)
    return ctrl.Result{}, nil
}

先ほどと同様にメトリクスを確認してみましょう。 下記の項目が出力されていれば成功です。

$ curl localhost:8080/metrics

# HELP markdownview_available The cluster status about available condition
# TYPE markdownview_available gauge
markdownview_available{name="markdownview-sample",namespace="markdownview-sample"} 0
# HELP markdownview_healthy The cluster status about healthy condition
# TYPE markdownview_healthy gauge
markdownview_healthy{name="markdownview-sample",namespace="markdownview-sample"} 1
# HELP markdownview_notready The cluster status about not ready condition
# TYPE markdownview_notready gauge
markdownview_notready{name="markdownview-sample",namespace="markdownview-sample"} 0

kube-rbac-proxy

Kubebuilderで生成したプロジェクトには、kube-rbac-proxyを利用できるようにマニフェストが記述されています。 kube-rbac-proxyを利用すると、メトリクスのエンドポイントにアクセスするための権限をRBACで設定できるようになります。 PrometheusにのみメトリクスのAPIをアクセス可能にすることで、任意のユーザーにメトリクスを取得されることを防ぐことができます。

kube-rbac-proxyを利用するためには下記のmanager_auth_proxy_patch.yamlのコメントアウトを解除します。

kustomization.yaml

patchesStrategicMerge:
# Protect the /metrics endpoint by putting it behind auth.
# If you want your controller-manager to expose the /metrics
# endpoint w/o any authn/z, please comment the following line.
#- manager_auth_proxy_patch.yaml

さらに、auto_proxy_から始まる4つのマニフェストも有効にします。

kustomization.yaml

resources:
# All RBAC will be applied under this service account in
# the deployment namespace. You may comment out this resource
# if your manager will use a service account that exists at
# runtime. Be sure to update RoleBinding and ClusterRoleBinding
# subjects if changing service account names.
- service_account.yaml
- role.yaml
- role_binding.yaml
- leader_election_role.yaml
- leader_election_role_binding.yaml
# Comment the following 4 lines if you want to disable
# the auth proxy (https://github.com/brancz/kube-rbac-proxy)
# which protects your /metrics endpoint.
- auth_proxy_service.yaml
- auth_proxy_role.yaml
- auth_proxy_role_binding.yaml
- auth_proxy_client_clusterrole.yaml

Grafanaでの可視化

それでは実際にPrometheusとGrafanaを使って、コントローラーのメトリクスを可視化してみましょう。

まずはマニフェストの準備をします。 config/default/kustomization.yamlの下記のコメントを解除してください。

kustomization.yaml

bases:
# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
- ../prometheus

make manifestsを実行してマニフェストを生成し、Kubernetesクラスターに適用しておきます。

Prometheus Operatorをセットアップするために、下記の手順に従ってHelmをインストールします。

つぎにHelmのリポジトリの登録をおこないます。

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

Prometheus Operatorをセットアップします。完了まで少し時間がかかるので待ちましょう。

kubectl create ns prometheus
helm install prometheus prometheus-community/kube-prometheus-stack --namespace=prometheus --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false
kubectl wait pod --all -n prometheus --for condition=Ready --timeout 180s

起動したPrometheusにメトリクスを読み取る権限を付与する必要があるので、下記のマニフェストを適用します。

prometheus_role_binding.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: metrics
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: markdown-view-metrics-reader
subjects:
  - kind: ServiceAccount
    name: prometheus-prometheus-oper-prometheus
    namespace: prometheus
kubectl apply -f ./config/rbac/prometheus_role_binding.yaml

ローカル環境からGrafanaの画面を確認するためにポートフォワードの設定をおこないます。

kubectl port-forward service/prometheus-grafana 3000:80 --address 0.0.0.0 --namespace prometheus

ブラウザからhttp://localhost:3000でGrafanaの画面が開くので、ユーザー名とパスワードを入力してログインします。

  • Username: admin
  • Password: prom-operator

Explore画面を開いて以下のようなPromQLを入力してみましょう。これによりReconcileにかかっている処理時間をモニタリングできます。

histogram_quantile(0.99, sum(rate(controller_runtime_reconcile_time_seconds_bucket[5m])) by (le))

grafana

results matching ""

    No results matching ""