カスタムインテグレーションの例

ここでは、ホスト/サーバーのデータを New Relic Infrastructure に送信する Infrastructure インテグレーション SDK で作成されたカスタムインテグレーションの例を示します。このインテグレーションの例には、設定ファイル、定義ファイル、データ生成用の2つの実行ファイル(Go と Python)があります。

ファイルの仕様に関しては、インテグレーションの作成をご覧ください。

この機能を利用できるかは契約しているサブスクリプションレベルによります。

カスタムインテグレーションの例

以下の Infrastructure のカスタムインテグレーションの例では以下の内容について言及しています。

以下の例は、 Linux OS を対象にしたものです。

定義ファイルの例

インテグレーションの定義ファイルは、定義ファイルの仕様に従っている必要があります。ここでの定義ファイルには、Python 実行ファイルとコンパイル済みの Go を実行可能なバイナリファイルを参照しています。

定義ファイル名 : example_integration-definition.yml

パス: /var/db/newrelic-infra/custom-integrations/example_integration-definition.yml

name: com.myorg.example_integration
description: Reports random numbers as metrics and inventory
protocol_version: 1
os: linux

commands:
  go-example:
    command: 
      - ./bin/example-integration
    prefix: services/go-example
    interval: 15

  py-example:
    command: 
      - ./bin/example-integration.py
    prefix: services/py-example
    interval: 15

構成ファイルの例

インテグレーションの構成ファイルは、構成ファイルの仕様に従っている必要があります。ここでの定義ファイルには、Python 実行ファイルとコンパイル済みの Go を実行フィアルを参照しています。

構成ファイル名: example_integration-config.yml

パス: /etc/newrelic-infra/integrations.d/example_integration-config.yml

integration_name: com.myorg.example_integration

instances:
  - name: example-one
    command: go-example
    arguments:
      environment: "golang"
    labels:
      role: "production"

  - name: example-two
    command: py-example
    arguments:
      environment: "python"
    labels:
      role: "development"

Go のインテグレーション用実行ファイル

Infrastructure インテグレーションの実行ファイルは、出力される JSON が仕様を満たしている限り、どのような方法で作成されていても構いません。

Go で書かれた Infrastructure のカスタムインテグレーションの実行ファイルの例を以下に示します。このスクリプトは、上の構成ファイルの例定義ファイルの例で参照されているものです。

Go 言語は、インテグレーションを作成する言語として推奨されている言語です。理由として、Go はコンパイル済みのバイナリファイル内にすべての外部依存関係をバンドルしているからです。つまり、インテグレーションを実行するために他のものは必要ありません。

ファイル名: example-integration.go

バイナルのパス (定義ファイルによって参照される): /var/db/newrelic-infra/custom-integrations/bin/example-integration

ソースコードのパス: /var/db/newrelic-infra/custom-integrations/bin/example-integration.go

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "math/rand"
    "os"

    log "github.com/Sirupsen/logrus"
)

// InventoryData は、インテグレーション用のデータソースによって生成され、
// エージェントのインベントリデータストアに送信されるインベントリデータ用のデータ・タイプです。
type InventoryData map[string]interface{}

// MetricData は、インテグレーション用のデータソースによって生成され、
// エージェントのメトリクスデータストアに送信されるイベント用のデータ・タイプです。
type MetricData map[string]interface{}

// EventData は、1回切りのイベント用のデータ・タイプです。
type EventData map[string]interface{}

// IntegrationData は、はインテグレーションが返す JSON 形式の出力フォーマットを定義します。
type IntegrationData struct {
    Name            string                   `json:"name"`
    ProtocolVersion string                   `json:"protocol_version"`
    IntegrationVersion   string              `json:"integration_version"`
    Metrics         []MetricData             `json:"metrics"`
    Inventory       map[string]InventoryData `json:"inventory"`
    Events          []EventData              `json:"events"`
}

// OutputJSON はオブジェクトを受け取り、それを JSON 文字列として stdout に出力します。
// pretty 属性が true の場合、JSON はインデントされ、読みやすくなります。
func OutputJSON(data interface{}, pretty bool) error {
    var output []byte
    var err error

    if pretty {
        output, err = json.MarshalIndent(data, "", "\t")
    } else {
        output, err = json.Marshal(data)
    }

    if err != nil {
        return fmt.Errorf("Error outputting JSON: %s", err)
    }

    if string(output) == "null" {
        fmt.Println("[]")
    } else {
        fmt.Println(string(output))
    }

    return nil
}

func main() {
    // インテグレーション用のコマンドラインパラメータを設定します。
    verbose := flag.Bool("v", false, "Print more information to logs")
    pretty := flag.Bool("p", false, "Print pretty formatted JSON")
    flag.Parse()

    // ログ出力、ログの stderr へのリダイレクト、ログレベルの設定を行います。
    log.SetOutput(os.Stderr)
    if *verbose {
        log.SetLevel(log.DebugLevel)
    } else {
        log.SetLevel(log.InfoLevel)
    }

    // 出力構造を初期化します
    var data = IntegrationData{
        Name:            "example",
        ProtocolVersion: "1",
        IntegrationVersion:   "1.0.0",
        Inventory:       make(map[string]InventoryData),
        Metrics:         make([]MetricData, 0),
        Events:          make([]EventData, 0),
    }

    // 有効な event_type であるメトリクス用のディクショナリを作成します。
    //  * LoadBalancerSample
    //  * BlockDeviceSample
    //  * DatastoreSample
    //  * QueueSample
    //  * ComputeSample
    //  * IamAccountSummarySample
    //  * PrivateNetworkSample
    //  * ServerlessSample
    // Provider は、データプロバイダーを識別するものが、設定されていない可能性があります
    var metric = map[string]interface{}{
        "event_type": "DatastoreSample",
        "provider":   "ExampleServer",
    }

    // エージェントが設定した ENVIRONMENT 変数を取得します。
    env := os.Getenv("ENVIRONMENT")
    if env != "" {
        metric["environment"] = env
    }

        // プロバイダー固有の各メトリクスには、プロバイダー名前空間の接頭辞を付ける必要があります。
    keyList := []string{"provider.valueOne", "provider.valueTwo", "provider.valueThree"}
    for _, key := range keyList {
        metric[key] = rand.Int()
        log.Debugf("Adding metric %s with value %d", key, metric[key])
    }

    data.Metrics = append(data.Metrics, metric)

    keyList = []string{"valueOne", "valueTwo", "valueThree"}
    itemKeys := []string{"item1", "item2", "item3"}
    for _, item := range itemKeys {
        data.Inventory[item] = InventoryData{}
        for _, key := range keyList {
            data.Inventory[item][key] = rand.Int()
            log.Debugf("Set inventory key %s=%d for %s", key, data.Inventory[item][key], item)
        }
    }

    fatalIfErr(OutputJSON(data, *pretty))
}

func fatalIfErr(err error) {
    if err != nil {
        log.WithError(err).Fatal("can't continue")
    }
}

Python インテグレーションの実行ファイル

Infrastructure インテグレーションの実行ファイルは、出力される JSON が仕様を満たしている限り、どのような方法で作成されていても構いません。

Python で書かれた Infrastructure のカスタムインテグレーションの実行ファイルの例を以下に示します(コメント付き)。このスクリプトは、上の構成ファイルの例定義ファイルの例で参照されているものです。

ファイル名: example-integration.py

パス: /var/db/newrelic-infra/custom-integrations/bin/example-integration.py

#!/usr/bin/env python
import argparse
import random
import json
import logging
import os


class IntegrationData:
    def __init__(self, name, protocol_version, integration_version):
        self.name = name
        self.protocol_version = protocol_version
        self.integration_version = integration_version
        self.inventory = {}
        self.metrics = []
        self.events = []

    def addInventory(self, item, key, value):
        self.inventory.setdefault(item, {})[key] = value

    def addMetric(self, metric_dict):
        self.metrics.append(metric_dict)

def parse_arguments():
    parser = argparse.ArgumentParser()
    parser.add_argument('-v', default=False, dest='verbose', action='store_true',
                        help='Print more information to logs')
    parser.add_argument('-p', default=False, dest='pretty', action='store_true',
                        help='Print pretty formatted JSON')

    args, unknown = parser.parse_known_args()
    return args


if __name__ == "__main__":
    # インテグレーション用のコマンドラインパラメータを設定します。
    args = parse_arguments()

    # ログ出力、ログの stderr へのリダイレクト、ログレベルの設定を行います。
    logger = logging.getLogger("infra")
    logger.addHandler(logging.StreamHandler())
    if args.verbose:
        logger.setLevel(logging.DEBUG)
    else:
        logger.setLevel(logging.INFO)

    # 出力オブジェクトを初期化します
    data = IntegrationData("example", "1", "1.0.0")

    # 有効な event_type であるメトリクス用のディクショナリを作成します。
    #  * LoadBalancerSample
    #  * BlockDeviceSample
    #  * DatastoreSample
    #  * QueueSample
    #  * ComputeSample
    #  * IamAccountSummarySample
    #  * PrivateNetworkSample
    #  * ServerlessSample
    # Provider は、データプロバイダーを識別するものが、設定されていない可能性があります
    metric = {
        "event_type": "DatastoreSample",
        "provider": "ExampleServer",
    }

    # エージェントが設定した ENVIRONMENT 変数を取得します。
    env = os.getenv("ENVIRONMENT")
    if env:
        metric["environment"] = env

    # プロバイダー固有の各メトリクスには、プロバイダー名前空間の接頭辞を付ける必要があります。
    for key in ["provider.valueOne", "provider.valueTwo", "provider.valueThree"]:
        metric[key] = random.randint(0, 100)
        logger.debug("Adding metric %s with value %d", key, metric[key])

    data.addMetric(metric)

    key_list = ["valueOne", "valueTwo", "valueThree"]
    item_keys = ["item1", "item2", "item3"]
    for item in item_keys:
        for key in key_list:
            data.addInventory(item, key, random.randint(0, 100))
            logger.debug("Set inventory key %s=%d for %s", key, data.inventory[item][key], item)

    if args.pretty:
        print json.dumps(data.__dict__, indent=4)
    else:
        print json.dumps(data.__dict__)