Agent Checkの書き方

Agent Checkを使うには、Datadog Agent 3.2.0 以降をインストールしている必要があります。 それ以前のバージョンには、Agent Checkのインターフェースは実装されていません。

概要

このガイドでは、Pythonで記述したDatadog Agent のpluginであるAgent Check を記述することで、新しいデータソースからメトリクスとイベント情報を取得する方法について説明します。 AgentCheckのインターフェースを確認した後、HTTP サービスからタイミングメトリクスやステータスに関するイベント情報を取得する簡単なAgent Checkを記述してみます。

Agent Checkは、メインチェックの実行ループに組み込まれ、デフォルト設定では15秒間隔で実行されます。

セットアップ

まだDatadog Agentをインストールしていない場合は、Datadog Agent 入門又は、ダッシュボード内タブをInstallations -> Agentとクリックしてインストールドキュメントを参照してください。これらのドキュメントでは、特定のOS用のDatadog Agent をインストールする手順を解説しています。

セットアップ中に問題が発生した場合は、freenode にあるDatadogのチャットルームで気兼ねなく質問してください。 (web チャットクライアント)

Agent Check のインターフェース

すべてのカスタムチェックは、checks/__init__.pyに記述されているAgentCheckクラスを継承し、check()関数をオーバーライドして生成します。 check() 関数には、そのインスタンスの構成が記述された辞書型の情報を引数に渡します。 check() 関数は、Check設定で定義されたインスタンスごとに一回実行されることになります。(詳細に関しては、後述します)

メトリクスの送信

Agent Checkでメトリクスを送信することは非常に簡単です。既にDogStatsDが提供している関数に精通しているなら、移行は非常に簡単です。 まだそれらの関数に慣れていない場合でも、それほど大変ではないことがすぐに分かるはずです。

Agent Checkでは、以下の関数を利用することが出来ます:

self.gauge( ... ) # Sample a gauge metric

self.increment( ... ) # Increment a counter metric

self.decrement( ... ) # Decrement a counter metric

self.histogram( ... ) # Sample a histogram metric

self.rate( ... ) # Sample a point, with the rate calculated at the end of the check

self.count( ... ) # Sample a raw count metric

self.monotonic_count( ... ) # Sample an increasing counter metric

全ての関数は、以下の引数を取ります:

  • metric: The name of the metric
  • value: The value for the metric (defaults to 1 on increment, -1 on decrement)
  • tags: (optional) A list of tags to associate with this metric.
  • hostname: (optional) A hostname to associate with this metric. Defaults to the current host.
  • device_name: (optional) A device name to associate with this metric.

これらの関数は、チェックロジックのどこからでも呼び出すことが出来ます。これらの関数を使って取得したメトリクスは、check 機能の実行の最後に他のAgent メトリクスと共にDatadogのサービスに転送されます。

イベントの送信

Check 内でイベントを送信するには、self.event(...)を呼び出し、"argument":に値を設定することで、そのイベントに関する情報(payload)を設定することが出来ます。イベントの中身は次のような構造になっている必要があります:

{
    "timestamp": int, the epoch timestamp for the event,
    "event_type": string, the event name,
    "api_key": string, the api key for your account,
    "msg_title": string, the title of the event,
    "msg_text": string, the text body of the event,
    "aggregation_key": string, a key to use for aggregating events,
    "alert_type": (optional) string, one of ('error', 'warning', 'success', 'info');
        defaults to 'info',
    "source_type_name": (optional) string, the source type name,
    "host": (optional) string, the name of the host,
    "tags": (optional) list, a list of tags to associate with this event
}

全てのイベントは、check 機能の実行の最後に、他のAgent Checkの内容と共にDatadogのサービスに転送されます。

エラーと例外の表示

不適切な設定、プログラミング時のエラー、いずれかのメトリクスが取得できない時など、Checkが正常に実行できない場合には、状況を把握しやすい例外メッセージが必要です。この例外メッセージはログに記録されると同時に、datadog-agent info コマンドで表示出来るので、デバッグに利用することが出来ます:

$ sudo /etc/init.d/datadog-agent info

  Checks
  ======

    my_custom_check
    ---------------
      - instance #0 [ERROR]: ConnectionError('Connection refused.',)
      - Collected 0 metrics & 0 events

ログの保存

AgentCheckクラスを承継しているので、親クラスで実装されているロギング機能を self.log.info('hello')の形で使うことが出来ます。 The log handler will be checks.{name} where {name} is the name of your check (based on the filename of the check module).

設定

Agent Checkには設定ファイルがあり、その設定ファイルはconf.d 以下に置かれています。設定ファイルは、YAML 形式で記述します。 設定ファイルの名前は、Agent Checkのモジュールの名前と同じ名前である必要があります。 (例: モジュール名がhaproxy.py の場合は、設定ファイル名は、haproxy.yaml になります)

設定ファイルは、以下のようになります:

init_config:
    key1: val1
    key2: val2

instances:
    - username: jon_smith
      password: 1234

    - username: jane_smith
      password: 5678
注: YAML ファイルは、タブを使わず、スペースを使って記述してください。

init_config

*init_config*のセクションでは、Agent Check実行時に利用できるグローバルな設定オプションを複数記述することが出来ます。このオプションは、全てのCheckの実行においてself.init_configの方法で、アクセスすることが出来ます。

instances

*instances*のセクションは、Checkが実行される全ての対象(instance)のリストになります。

Checkの中で実行されるcheck() 関数の実体は、各チェック対象(instance)に対して個別に実行されていきます。このことより全てのCheckは、そのままの状態で複数のチェック対象(instance)をサポートしていることを意味しています。

ディレクトリの構造

Checkのプログラミングを始める前に、まず関連するディレクトリの構造を理解しておくのは重要なことです。Checkで使用するファイルは2カ所に分けて配置します。 Check のPython 実行ファイルは、Datadog Agent のルートディレクトリにあるchecks.d のフォルダに配置します。

Linux系のシステムは、以下のディレクトリになります:

/etc/dd-agent/checks.d/

Windows Server > = 2008の場合は、以下のディレクトリになります:

C:\Program Files (x86)\Datadog\Agent\checks.d\

OR

C:\Program Files\Datadog\Agent\checks.d\

Mac OS Xと、ソースからインストールした場合は、以下のディレクトリになります:

~/.datadog-agent/agent/checks.d/

OR

~/.pup/agent/checks.d/

OR

<sandbox_folder>/checks.d/

Check の設定ファイルは、Datadog Agent の設定ファイルのルートディレクトリにあるconf.d になります。

Linux系のシステムは、以下のディレクトリになります:

/etc/dd-agent/conf.d/

Windowsの場合は、以下のディレクトリになります:

C:\ProgramData\Datadog\conf.d\

OR

C:\Documents and Settings\All Users\Application Data\Datadog\conf.d\

Mac OS Xと、ソースからインストールした場合は、以下のディレクトリになります:

~/.datadog-agent/agent/conf.d/

OR

~/.pup/agent/conf.d/

OR

<sandbox_folder>/conf.d/

実行ファイルと設定ファイルは、デフォルトの場所以外に、一つのディレクトリにまとめて設置し、datadog.conf 内で設置ディレクトリを指定することも出来ます:

additional_checksd: /path/to/custom/checks.d/

始めてのCheck

Check 実行ファイルおよび設定ファイルの名前(拡張子を除く)は一致している必要があります。 例えば、実行ファイルがmycheck.py の場合、設定ファイルは、mycheck.yaml というファイル名になります。

まず簡単な例として、メトリクス名hello.worldで、値1を送信するAegent Checkを書いてみます。設定ファイルは、conf.d / hello.yaml に配置し、以下の3行で非常にシンプルな内容になります:

init_config:

instances:
    [{}]

HelloCheckは、AgentCheckクラスを継承し、hello.worldというメトリクス名で毎回1を送信します。このCheckを記述したhello.py ファイルは、checks.d ディレクトリ以下に配置します:

from checks import AgentCheck
class HelloCheck(AgentCheck):
    def check(self, instance):
        self.gauge('hello.world', 1)
ごのようにCheckのインターフェースは、シンプルで、簡単に使い始めることが出来ます。

次のセクションでは、HTTPサービスに対しpingを実行し、レスポンス時間を計測してDatadogに送信するCheckを書いてみることにします。

HTTP のCheck

ここからは、HTTPのエンドポイントの状況を確認するための基本的なCheckの書き方を解説します。 Checkが実行される度に、HTTPのエンドポイントに対してGETリクエストを実行します。 レスポンスの結果に基づいて次の処理をします:

  • レスポンスが成功と判定された場合(レスポンスステータスが200で、タイムアウトをしていない)、レスポンス時間をメトリクスとして送信します。
  • レスポンスがタイムアウトした場合、URLとタイムアウトのイベントを送信します。
  • レスポンスステータスが200以外の場合、URL及びレスポンスコードのイベントを送信します。

設定

インスタンス定義の中身が各Check本体でどのように処理されるのかを理解するために、 まず最初に設定ファイルがどのようになるか考えてみることにします。

設定ファイルは、Checkの実行先のURLの定義以外に、それぞれのURLに対してのタイムアウトを設定すると、より環境に合ったCheckを実行出来ることになります。更に、それぞれのインスタンスでタイムアウトを設定しなかった時のために、デフォルトのタイムアウトを設定しておくことも必要になります。

上記をふまえると、設定ファイルは次のようになります:

init_config:
    default_timeout: 5

instances:
    -   url: https://google.com

    -   url: http://httpbin.org/delay/10
        timeout: 8

    -   url: http://httpbin.org/status/400
<!– #### The Check

Now we can start defining our check method. The main part of the check will make a request to the URL and time the response time, handling error cases as it goes.

In this snippet, we start a timer, make the GET request using the requests library and handle and errors that might arise.

# Load values from the instance config
url = instance['url']
default_timeout = self.init_config.get('default_timeout', 5)
timeout = float(instance.get('timeout', default_time))

# Use a hash of the URL as an aggregation key
aggregation_key = md5(url).hexdigest()

# Check the URL
start_time = time.time()
try:
    r = requests.get(url, timeout=timeout)
    end_time = time.time()
except requests.exceptions.Timeout as e:
    # If there's a timeout
    self.timeout_event(url, timeout, aggregation_key)

if r.status_code != 200:
    self.status_code_event(url, r, aggregation_key)

If the request passes, we want to submit the timing to Datadog as a metric. Let’s call it http.response_time and tag it with the URL.

timing = end_time - start_time
self.gauge('http.reponse_time', timing, tags=['http_check'])

Finally, we’ll want to define what happens in the error cases. We have already seen that we call self.timeout_event in the case of a URL timeout and we call self.status_code_event in the case of a bad status code. Let’s define those methods now.

First, we’ll define timeout_event. Note that we want to aggregate all of these events together based on the URL so we will define the aggregation_key as a hash of the URL.

def timeout_event(self, url, timeout, aggregation_key):
    self.event({
        'timestamp': int(time.time()),
        'event_type': 'http_check',
        'msg_title': 'URL timeout',
        'msg_text': '%s timed out after %s seconds.' % (url, timeout),
        'aggregation_key': aggregation_key
    })
Next, we’ll define status_code_event which looks very similar to the timeout event method.

def status_code_event(self, url, r, aggregation_key):
    self.event({
        'timestamp': int(time.time()),
        'event_type': 'http_check',
        'msg_title': 'Invalid reponse code for %s' % url,
        'msg_text': '%s returned a status of %s' % (url, r.status_code),
        'aggregation_key': aggregation_key
    })
–>

Check 本体

では、Check 本体の関数を定義することにします。関数のメインは、設定ファイルで指定したURLに対しHTTP リクエストを実行し、レスポンス時間を計測します。 エラーが発生していれば、エラー条件によって処理をします。

以下のセクションでは、タイマをスタートし、requests libraryを使いHTTPリクエストを実行し、発生する可能性のあるエラーの処理を行います。

# Load values from the instance config
url = instance['url']
default_timeout = self.init_config.get('default_timeout', 5)
timeout = float(instance.get('timeout', default_time))

# Use a hash of the URL as an aggregation key
aggregation_key = md5(url).hexdigest()

# Check the URL
start_time = time.time()
try:
    r = requests.get(url, timeout=timeout)
    end_time = time.time()
except requests.exceptions.Timeout as e:
    # If there's a timeout
    self.timeout_event(url, timeout, aggregation_key)

if r.status_code != 200:
    self.status_code_event(url, r, aggregation_key)

リクエストが成功した場合、レスポンス時間をDatadogへ送信します。その際、メトリクス名は、http.response_time。URLをタグとして付記します。

timing = end_time - start_time
self.gauge('http.reponse_time', timing, tags=['http_check'])

最後に、エラー発生時の処理内容を定義します。 先のコードで既に、 HTTP リクエストがタイムアウトした場合のtimeout_event 関数と、レスポンスステータスが200 以外の場合のself.status_code_event 関数を定義しています。 従って、これらのイベントの関数の中身を定義します。

まず、timeout_event を定義します。self.event() で注目してほしいのは、'aggregation_key': に、先のコードに出ているURLのハッシュであるaggregation_key = md5(url).hexdigest()を設定している部分です。このaggregation_key を使って、特定のURLに関連したイベントを集約します。

def timeout_event(self, url, timeout, aggregation_key):
    self.event({
        'timestamp': int(time.time()),
        'event_type': 'http_check',
        'msg_title': 'URL timeout',
        'msg_text': '%s timed out after %s seconds.' % (url, timeout),
        'aggregation_key': aggregation_key
    })

次に、status_code_event を定義します。先に定義したtimeout_event とほぼ同じ内容になります。

def status_code_event(self, url, r, aggregation_key):
    self.event({
        'timestamp': int(time.time()),
        'event_type': 'http_check',
        'msg_title': 'Invalid reponse code for %s' % url,
        'msg_text': '%s returned a status of %s' % (url, r.status_code),
        'aggregation_key': aggregation_key
    })

今までの解説をまとめると

このガイドの最後に、Checkの実行コード全体を載せてあります。このコードを、checks.dフォルダ以下へ、http.pyのファイル名で配置します。この実行コードに必要な、設定ファイルは、conf.dフォルダー以下に、http.yamlとして配置します。

Checkに実行ファイルを配置したら、次のpython スクリプトを使ってテストを実行することが出来ます。 尚、__main__の部分の/path/to/conf.d/http.yamlを、テスト用の設定ファイルを配置している場所に書き換えてあることを必ず確認してください。

Agent のroot から、次のコマンドでテストを実行します:

PYTHONPATH=. python checks.d/http.py

インスタンスごとに生成されているメトリクスとイベントが表示されます。

チェックの完全なソースは次の通りです。

import time
import requests

from checks import AgentCheck
from hashlib import md5

class HTTPCheck(AgentCheck):
    def check(self, instance):
        if 'url' not in instance:
            self.log.info("Skipping instance, no url found.")
            return

        # Load values from the instance config
        url = instance['url']
        default_timeout = self.init_config.get('default_timeout', 5)
        timeout = float(instance.get('timeout', default_timeout))

        # Use a hash of the URL as an aggregation key
        aggregation_key = md5(url).hexdigest()

        # Check the URL
        start_time = time.time()
        try:
            r = requests.get(url, timeout=timeout)
            end_time = time.time()
        except requests.exceptions.Timeout as e:
            # If there's a timeout
            self.timeout_event(url, timeout, aggregation_key)
            return

        if r.status_code != 200:
            self.status_code_event(url, r, aggregation_key)

        timing = end_time - start_time
        self.gauge('http.reponse_time', timing, tags=['http_check'])

    def timeout_event(self, url, timeout, aggregation_key):
        self.event({
            'timestamp': int(time.time()),
            'event_type': 'http_check',
            'msg_title': 'URL timeout',
            'msg_text': '%s timed out after %s seconds.' % (url, timeout),
            'aggregation_key': aggregation_key
        })

    def status_code_event(self, url, r, aggregation_key):
        self.event({
            'timestamp': int(time.time()),
            'event_type': 'http_check',
            'msg_title': 'Invalid reponse code for %s' % url,
            'msg_text': '%s returned a status of %s' % (url, r.status_code),
            'aggregation_key': aggregation_key
        })

if __name__ == '__main__':
    check, instances = HTTPCheck.from_yaml('/path/to/conf.d/http.yaml')
    for instance in instances:
        print "\nRunning the check against url: %s" % (instance['url'])
        check.check(instance)
        if check.has_events():
            print 'Events: %s' % (check.get_events())
        print 'Metrics: %s' % (check.get_metrics())

トラブルシュート

カスタム Agent checkは、Pythonコマンドから直接実行出来ません。代わりに、Datadog Agentを使って、テスト実行するようにします。テストするには、次のようなコマンドを実行します:

sudo -u dd-agent dd-agent check my_check

問題が解決しない場合、お問い合わせに記載された方法でサポート要請のご連絡をしていただけると幸いです。

WindowsでのカスタムCheckのテスト

Windows上でカスタムチェックのテストは簡単です。 Datadog Agentのインストールにはshell.exe というDatadog Agent 用のpythonの実行環境が含まれています。 このファイルは、Program Files ディレクトリに保存されています。

Agent Check(例えば「my_check」)を書き終え、.pyファイルと.yamlファイルの配置が終わったら、次のコマンドを、shell.exe で起動したウインドウ内で実行してみて下さい。

>>> from checks import run_check
>>> run_check('my_check')

このコマンドは、Checkが送信するメトリクスかイベントを表示します。