こんにちは。@jedipunkz です。

今日は Go 言語でサーバのメトリクスデータを InfluxDB に入れてリソース監視を行う方法について書きます。

Ansible, Terraform, Chef などのソフトウェアを使ってインフラを定義するのが当たり前になった現在ですが、本当の意味でのソフトウェアによるインフラの定義ってなんだろと最近考えています。aws-sdk や fog などを使ったネイティブな言語でインフラを定義することの意味もあるように感じているからです。某サービスプロバイダのエンジニアはこうした言語によるインフラの定義の一番大きなメリットとして “再利用性” をあげていました。こうしたソフトウェアによるインフラの定義や構成を行う上で監視についてもコード化できるのでは?と考えて今回の記事に至りました。

使うモノ

公式の InfluxDB Go Client です。InfluxDB 自体が Go 言語で書かれていますがクライアントも Go 言語で記述することができます。ここにあるサンプルコードをすこしいじって、今回の記事を書こうと思います。

@shirou さんが作られた psutil の Go 言語版です。CPU, Mem などリソースをモニタするのに便利なので利用します。

環境構築

環境を作っていきます。InfluxDB と Chronograf を構築するのですが Docker で構築するのが簡単なのでこれを利用します。Chronograf は InfluxDB 内のデータを可視化するためのソフトウェアです。

  • InfluxDB の起動

InfluxDB のコンテナを起動します。

docker run -p 8083:8083 -p 8086:8086 \
      -v $PWD:/var/lib/influxdb \
      influxdb
  • Chronograf の起動

Chronograf のコンテナを起動します。

docker run -p 10000:10000 chronograf

この時点で http://${DOCKER_HOST}:10000/ にアクセスすると Chronograf の UI を確認できます。

InfluxDB にユーザ・データベースを作成する

InfluxDB 上にユーザとデータベースを作成します。言語の中でも作ることが出来ますが、今回は手動で。 Mac OSX を使っている場合 homebrew で influxdb をインストールすることが簡単にできます。

brew install influxdb

ユーザを作ります。

influx -host 192.168.99.100 -port 8086
> create user foo with password 'foo'
> grant all privileges to foo

データベースを作ります。

influx -host 192.168.99.100 -port 8086
> CREATE DATABASE IF NOT EXISTS square_holes;

Go言語で CPU 時間を取得し InfluxDB にメトリクスデータを挿入

Go 言語でメモリー使用率を取得し得られたメトリクスデータを InfluxDB に挿入するコードを書きます。

package main

import (
    "log"
    "time"

    "github.com/influxdata/influxdb/client/v2"
    "github.com/shirou/gopsutil/cpu"
)

const (
    MyDB = "square_holes"
    username = "foo"
    password = "foo"
)

func main() {
    for {
        // Make client
        c, err := client.NewHTTPClient(client.HTTPConfig{
            Addr: "http://192.168.99.100:8086",
            Username: username,
            Password: password,
        })
    
        if err != nil {
            log.Fatalln("Error: ", err)
        }
    
        // Create a new point batch
        bp, err := client.NewBatchPoints(client.BatchPointsConfig{
            Database:  MyDB,
            Precision: "s",
        })
    
        if err != nil {
            log.Fatalln("Error: ", err)
        }
    
        // get CPU info
        cp, _ := cpu.Times(true)

        // get CPU status info for each core
        var user, system, idle float64 = 0, 0, 0
        for _, sub_cpu := range cp {
            user = user + sub_cpu.User
            system = system + sub_cpu.System
            idle = idle + sub_cpu.Idle
        }
    
        // Create a point and add to batch
        tags := map[string]string{"cpu": "cpu"}
        fields := map[string]interface{}{
            "User":     user / float64(len(cp)),
            "System":   system / float64(len(cp)),
            "Idle":     idle / float64(len(cp)),
        }
        pt, err := client.NewPoint("cpu", tags, fields, time.Now())
    
        if err != nil {
            log.Fatalln("Error: ", err)
        }
    
        bp.AddPoint(pt)
    
        // Write the batch
        c.Write(bp)
        time.Sleep(1 * time.Second)
    }
}

ビルドして実行すると下記のように influxdb 上のデータベースにメトリクスデータが挿入されていることを確認できます。

influx -host 192.168.99.100 -port 8086 -execute 'SELECT * FROM cpu' -database=square_holes -precision=s | head -8
name: cpu
---------
time            Idle            System          User            cpu
1469342272      20831.04296875  3700.185546875  3544.90234375   cpu
1469342273      20831.666015625 3700.302734375  3544.966796875  cpu
1469342274      20832.2109375   3700.447265625  3545.068359375  cpu
1469342275      20832.828125    3700.546875     3545.13671875   cpu
1469342291      20841.728515625 3702.482421875  3546.806640625  cpu

Chronograf の UI で確認してみましょう。

得られた CPU に関するデータが可視化されていることが確認できます。変化に乏しいグラフですが…。 この辺りは CPU 時間から CPU 使用率を得るコードに書き換えるといいかもしれません。

まとめと考察

InfluxDB の提供元が出している Telegraf というメトリクスデータの送信エージェントがありますが、同じような動きを Go 言語で簡単に開発できることが分かりました。ネイティブな言語で開発するとより柔軟にデータの送信ができることも期待できます。また冒頭に述べた通り再利用も用意になるのではと思います。インフラの状態をメトリクスデータとして時系列 DB に挿入して可視化するということは監視のコード化とも言えると思います。ただし、フレームワークが出てきてもっと簡単に書ける仕組みが出てこないと厳しい気もしますが。果たしてこれらインフラを言語で記述していくことがどれだけ有用なのかまだわかりませんが、いつか現場で実践してみたいと思います。