DaemonSetで立ち上げたDatadogにカスタムメトリクスを送りつける

helmでDatadogを入れることが多いんだけど,これで立ち上がるDogStatsDにアプリケーションからカスタムメトリクスを送りたいことがある.

https://github.com/helm/charts/tree/73e2adc5d2250d02cb02ef4602b7eeda05e6aaf4/stable/datadog

現状のdatadogのchartでは,datadog/dogstatsd のdocker imageを立ち上げている場所はないが,datadog/agent は各所で起動してくれる.agentはdogstatsdを内包しているので,agentであっても,dogstatsd用のUDPポートがあいていればdogstatsdとして利用可能だ. このchart内の

https://github.com/helm/charts/blob/73e2adc5d2250d02cb02ef4602b7eeda05e6aaf4/stable/datadog/templates/container-agent.yaml

このtemplateでは, DD_DOGSTATSD_PORT が設定されており,dogstatsdが利用できる.このcluster-agent.yaml

https://github.com/helm/charts/blob/73e2adc5d2/stable/datadog/templates/daemonset.yaml#L63

ここで参照されているため,daemonsetで起動するDatadogのプロセスはdogstatsdとして利用可能だ.

つまり,DaemonSetで起動したDatadogのPodに,アプリケーションからUDPでメトリクスを送りつけることができれば良い.

任意のアプリケーションPodからDaemonSetにアクセスする

kubernetes.io

ここに書かれているような方法がある.HeadlessServiceを作ったり,Serviceを作ってアクセスするという手もある.ただ,よく考えると基本的にDaemonSetはすべてのNodeに配置されているはずである. となると,Serviceで無駄にLBを経由して,その結果どのPodにアクセスがいくかはわからないという構成は,そんなにスマートとは思えない(楽だけど).

すべてのNodeにDaemonSetのPodが配置されるのであれば,当然アプリケーションを実行しているPodが存在するNodeにもDaemonSetのPodは存在する.そこにアクセスできれば一番スマートな気がする.

ホストのIPでDaemonSetへのアクセスを受け付ける

これをやるには, spec.template.spec.hostNetwork: true にすれば良い. 他には,portsの設定時にhostPort: 8125 (これはdogstatsdのポート)とかしても良い.ただし他のコンテナのポートと被らないようにする必要はあるが.

前述のhelm chartを使うのであれば,

releases:
  - name: datadog
    namespace: kube-system
    chart: stable/datadog
    values:
      - agents:
          useHostNetwork: true
      - datadog:
          apiKey: hogehoge
          appKey: fugafuga
          dogstatsd:
            nonLocalTraffic: true

みたいな helmfile.yaml にしとけばいい.

一つ重要なこととして,nonLocalTraffic: true にしておかないと,外から投げられたカスタムメトリクスをdatadogに送ってくれない.

自身のホストIPに向かってリクエストを投げる

次はアプリケーションのPod側の話.DogStatsDはホストIP+8125ポートで待ち受けてくれているので,ここにUDPリクエストを投げれば良い.

ホストのIPを知るためにはDownward APIを使う.使えるものはこのへんに書いてあるが,とりあえずホストのIPを知りたければ status.hostIP で取れる.

つまり,アプリケーションのPod定義内で

containers:
  - name: application
    image: ruby:2.7.1
    env:
      - name: DD_AGENT_HOST
        valueFrom:
          fieldRef:
            fieldPath: status.hostIP

としておけば, DD_AGENT_HOST でホストのIPが取れる.内部ではdogstatsdのライブラリを使って,

require 'datadog/statsd'

statsd = Datadog::Statsd.new(ENV['DD_AGENT_HOST'], 8125)

とかしておけば使える.

参考

qiita.com

ほぼこれと同じことを説明している. ただ,最近のdatadog/agent:7 あたりだと,hostNetwork: true であっても hostPort: 8125 であっても,どちらでも正しくインスタンスのホスト名が取得されており,hostname=Pod名とはならなかった.なので hostNetworkでもhostPortでもどちらでも良いと思う.