Prometheus Metricsを使ってArgo WorkflowsのWorkflowの成否をDatadogで監視する

背景

  • 前職に引き続き、現職でもArgo Workflowsを使ってデータエンジニアリング系のバッチ処理を行なっている
  • 前職ではCloud Monitoringで監視していたが、現職ではDatadogで監視しているので、全社の体制に合わせてDatadogで監視していきたい
  • Argo WorkflowsはPrometheus Metricsに対応しており、Datadogはagent経由でPrometheus Metricsの収集を容易に行なえることが分かった
    • 同僚のSREであるtapihさんから教えていただいてました、ありがとうございます
    • メトリックの収集や投稿を自前でやるより手軽にいけそうなので、この方法を試してみたというのがこのエントリの内容
    • GKEクラスタにdatadog agentを仕込んでもらうところはtapihさんにやってもらっていたので、今回の内容からは省きます

具体的な設定

コントローラーに設定を生やす

datadog agentはArgo Workflowsのコントローラー側に対して、定期的にメトリックを収集しにやってくる。そのためのエンドポイントの設定やどのメトリックの収集を許すかの設定を書く。

具体的にはこういった設定になった。Default Controller Metricsもあるが、それだとworkflow単位の監視には足りないことが多いため、カスタムメトリクスがメインの対象となる。今回はworkflowの実行時間をduration、workflowの成否をexit_codeとして定義した。

controller:
   podAnnotations:
    ad.datadoghq.com/controller.checks: |
      {
        "openmetrics": {
          "init_config": {},
          "instances": [
            {
              "openmetrics_endpoint": "http://%%host%%:9090/metrics",
              "namespace": "argo_workflows",
              "metrics": [
                {"argo_workflows_duration":"duration"},
                {"argo_workflows_exit_code":"exit_code"}
              ]
            }
          ]
        }
      }

workflowを監視するためのカスタムメトリクスを定義する

各workflowに対してカスタムメトリクスを定義していく。基本的には公式ページを参考にするのがよいが、細かいところで罠が色々あった。

  • workflowの成否を判定するためのカスタムメトリクスをどう定義するか
    • 成否自体はstatusという変数に入っているが、datadogに送るのはメトリクスである必要がある
    • 定数 + statusをラベルで送る作戦はうまくいかなかった
      • ラベルが異なると異なるメトリクスの系列として扱われてしまうため、思ったような監視ができなさそうであった
  • stepsを使うworkflow*1ではexitCodeが取れない
    • exitCodeをカスタムメトリクスとして送って、0より大きければworkflowが失敗していると判定できるかと思っていたが、それだとダメなケースがある
    • 単一stepで終わるworkflowであれば問題ないが、複数のstepsからなるworkflowだとexitCodeが取れない
      • エラーメッセージ: MetricsError: unable to substitute parameters for metric 'exit_code': failed to resolve {{ exitCode }}
      • 実際のstatus.nodesのyamlを見てみると確かに複数stepsの場合はexitCodeが含まれていなかった

以上のことを回避しつつ、監視用のメトリクスを定義しようと試行錯誤した結果、以下のように落ち着いた。exit_codewhen込みで2回定義しているのが気持ち悪いが、メトリクスの系列が別々の扱いにならないようにしようと思うとこうなってしまった(Failedのみだと、Failed => Succeededに切り変わったことがメトリクスとして表現できない)。

apiVersion: argoproj.io/v1alpha1
kind: CronWorkflow
metadata:
  ...

spec:
  workflowSpec:
    metrics:
      prometheus:
        - name: exit_code
          labels:
            - key: namespace 
              value: "{{ workflow.namespace }}"
          help: "Exit code of workflow"
          when: "{{ status }} == Succeeded"
          gauge:
            value: "0"
        - name: exit_code
          labels:
            - key: namespace 
              value: "{{ workflow.namespace }}"
          help: "Exit code of workflow"
          when: "{{ status }} != Succeeded"
          gauge:
            value: "1"
        - name: duration
          labels:
            - key: namespace 
              value: "{{ workflow.namespace }}"
          help: "Duration seconds"
          gauge:
            value: "{{ workflow.duration }}"

これらのカスタムメトリクスをDatadogに投稿すると、以下のようなグラフが得られる。

Workflowの成否とその実行時間のグラフ

これらのグラフにより、以下のような御利益が得られる。

  • いつからいつまでWorkflowが落ちていたのかが分かる
    • バッチの信頼性を高めるためにSLOを定義したいのであれば、そもそもこういった計測をしていく必要がある
  • バッチの実行時間の推移が分かる
    • どの時期のdeployから変化があったのかが分かると、パフォーマンス改善もしやすい

各workflowに同様のカスタムメトリクスを定義する

カスタムメトリクスの定義ができたが、これを各workfow毎にコピペして回るのはしたくない。いくつかの方法を検討したので、ログを残しておく。

  • 作戦1: Default Workflow Specを使う
    • 試行錯誤してみたところ、CronWorkflowではうまく反映されないということが分かった
    • Workflowでしかうまく動かず、Workflowの中でもうまく動かないフィールドがあることが分かった
      • 例: ttlStrategyは動くが、merticsは動かなかった
  • 作戦2: Cluster Workflow Templates + Exit handlersを使う
    • Cluster Workflow Templatesで共通定義をして、Exit handlersでworkflowsの成否に関わらずmetricsを投稿する作戦
    • うまくいきそうではあったが、この方法だとdurationがworkflows全体ではなくexit handlersの実行時間になってしまうため、workflowsの実行時間が取れなくなってしまう
  • 作戦3: helmのtoYaml関数を使い、共通定義を読み取る
    • 結局、これに落ち着いた
    • 共通定義のyamlファイルを置いておき、helmのtoYaml関数でそれを参照する(参考)する
    • helm依存なのが微妙な感はあるが、作戦1や作戦2だと動かない or 要件に足りない

最終的には作戦3で、以下のように定義した。

spec:
  workflowSpec:
    {{- nindent 4 (toYaml .Values.prometheusMetricsForDataDog) }}

デバッグ方法

「カスタムメトリクスを定義したけど、Datadog上で全然メトリクス流れてこないな...」というときにデバッグ方法で困ったので、それもメモしておく。

そもそもprometheusのエンドポイントに対象のカスタムメトリクスが含まれているかは実際にエンドポイントを叩いてみるとよい。まず、k8sクラスタへのcredentialsがある状態で、port-forwardする。

% kubectl port-forward -n workflows deploy/argo-workflows-workflow-controller 9090:9090

あとはcurlを使って実際にエンドポイントを叩いてみるとよい。正しく動いていれば以下のような出力になる。ここにカスタムメトリクスが含まれていないようであれば定義がおかしいし、ここに含まれているのにDatadog上でメトリクス流れてこないという場合はdatadogのagentのlogを見るとよい。

% curl -s localhost:9090/metrics | grep -e argo_workflows_duration -e argo_workflows_exit_code
# HELP argo_workflows_duration Duration seconds
# TYPE argo_workflows_duration gauge
argo_workflows_duration{namespace="my-workflow"} 208.508275
# HELP argo_workflows_exit_code Exit code of workflow
# TYPE argo_workflows_exit_code gauge
argo_workflows_exit_code{namespace="my-workflow"} 0

所感

  • prometheusは便利
    • 名前は聞いたことあるという感じだったが、エコシステムができあがっているので、自前で色々頑張らなくてもさっとできるのは便利
  • とはいえ、Workflow単位で成否を見ようとすると色々罠があった
    • 必要な変数が参照できないケースなどがあり、バッチの監視に利用しようと思うと一工夫が必要
    • まあまあ時間を溶かした...
  • やっぱり監視の仕組みがあると、バッチを安心して運用できる感は高い

*1:試してないけど、dagも多分同じ