LoginSignup
1
1

More than 5 years have passed since last update.

client-go, gcloud-sdkでk8sクラスタのmetricsをstackdriver monitoringへ送信する

Posted at

はじめに

GKEのpod数などの情報をStackDriver Monitoringに送信するコードをgoで書きました。
Datadogだとエージェントをk8sへ仕込めばもっと簡単に実現できるんですが、有料コースになってしまうのと、golangを久しぶりに書きたかったのでこの方法を試しました。

使用するライブラリ

  • client-go

k8sクラスタの情報は通常kubectlで取得すると思います。
そのクライアントライブラリです。

  • cloud-sdk

StackDriver Monitoringのgoライブラリです。

つまり、クラスタ情報をclient-goで取得し、cloud-sdkでStackDriverに送ります。

alt

前準備

今回は定期的にバッチで起動するため、service accountの認証情報を使います。

  • service accountの取得

IAM & Admin -> CREATE SERVICE ACCOUNT

roleはkuberntes engine viewer, monitoring editorを指定します。

そしてSAVEしたらjsonをDLできるので大事に取っておきます。

コード

こういう階層です。
sa.jsonは先ほど控えておいたservice accountのjsonです。

ディレクトリ階層
gopath/src/github.com/hogehoge
  |- main.go
  `- sa.json
main.go
package main

import (
    "cloud.google.com/go/monitoring/apiv3"
    "context"
    "fmt"
    "github.com/golang/protobuf/ptypes/timestamp"
    "google.golang.org/genproto/googleapis/api/label"
    "google.golang.org/genproto/googleapis/api/metric"
    "google.golang.org/genproto/googleapis/api/monitoredres"
    monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
    "k8s.io/client-go/tools/clientcmd"
    "log"
    "os"
    "os/exec"
    "time"
)

const (
    GAUGE                          = 1
    INT64                          = 2
    STRING                         = 0
)

type Config struct {
    ProjectId   string
    MetricType  string
    MetricName  string
    Description string
    DisplayName string
    Labels      string
    Cluster     string
        K8sConfig   string
}

var config *Config

var client *monitoring.MetricClient

func init() {
    // config
    config = &Config{}

    config.ProjectId = os.Getenv("PROJECT_ID")
        config.K8sConfig = os.Getenv("K8S_CONFIG")
    config.Cluster = os.Getenv("CLUSTER")
    config.MetricType = "custom.googleapis.com/pod_num"
    config.MetricName = fmt.Sprintf(
        "projects/%s/metricDescriptors/%s", config.ProjectId, config.MetricType)
    config.Description = "number of pods"
    config.DisplayName = "number of pods"

    // client-goをservice accountで使用するためにシェルを起動
    exec.Command(
        "gcloud", "config", "set", "container/use_v1_api", "false")
    exec.Command(
        "gcloud", "auth", "activate-service-account", "--key-file="+os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))

    // get metrics client
    var err error
    ctx := context.Background()
    client, err = monitoring.NewMetricClient(ctx)
    if err != nil {
        log.Fatal(err)
    }
}

func main() {
    // metrics作成
    var des *metric.MetricDescriptor
    des = getMetric()
    if des == nil {
        des = createMetric()
    }

    // k8sの情報取得
    podNum := getPodNum()

    // metrics書き込み
    addLogs(podNum)
}

// メトリクスの存在確認
func getMetric() *metric.MetricDescriptor {
    req := &monitoringpb.GetMetricDescriptorRequest{
        Name: config.MetricName,
    }

    ctx := context.Background()
    res, err := client.GetMetricDescriptor(ctx, req)
    if err != nil {
        return nil
    }

    return res
}

// メトリクス作成
func createMetric() *metric.MetricDescriptor {
    ld := label.LabelDescriptor{Key: "test_metrics", ValueType: STRING, Description: "test"}
    req := &monitoringpb.CreateMetricDescriptorRequest{
        Name: config.MetricName,
        MetricDescriptor: &metric.MetricDescriptor{
            Name:        config.MetricName,
            Type:        config.MetricType,
            Labels:      []*label.LabelDescriptor{&ld},
            MetricKind:  GAUGE,
            ValueType:   INT64,
            Description: config.Description,
            DisplayName: config.DisplayName,
        },
    }

    ctx := context.Background()
    res, err := client.CreateMetricDescriptor(ctx, req)
    if err != nil {
        log.Fatal(err)
    }

    return res
}

// pod数取得
func getPodNum() int64 {
    config, err :=
        clientcmd.BuildConfigFromFlags("", config.K8sConfig)
    if err != nil {
        log.Fatal(err)
    }

    clientSet, err := kubernetes.NewForConfig(config)
    if err != nil {
        log.Fatal(err)
    }

    pods, err := clientSet.CoreV1().Pods("default").List(metav1.ListOptions{})
    if err != nil {
        log.Fatal(err)
    }

    return int64(len(pods.Items))
}

// メトリクスへの書き込み
func addLogs(podNum int64) {
    pt := monitoringpb.Point{
        Interval: &monitoringpb.TimeInterval{
            EndTime: &timestamp.Timestamp{
                Seconds: time.Now().Unix(),
            },
        },
        Value: &monitoringpb.TypedValue{
            Value: &monitoringpb.TypedValue_Int64Value{Int64Value: podNum},
        },
    }
    ts := monitoringpb.TimeSeries{
        Metric: &metric.Metric{
            Type: config.MetricType,
            Labels: map[string]string{
                "cluster": config.Cluster,
            },
        },
        Resource: &monitoredres.MonitoredResource{
            Type: "global",
            Labels: map[string]string{
                "project_id": config.ProjectId,
            },
        },
        MetricKind: GAUGE,
        ValueType:  INT64,
        Points:     []*monitoringpb.Point{&pt},
    }
    req := &monitoringpb.CreateTimeSeriesRequest{
        Name:       config.MetricName,
        TimeSeries: []*monitoringpb.TimeSeries{&ts},
    }

    ctx := context.Background()
    err := client.CreateTimeSeries(ctx, req)
    if err != nil {
        log.Fatal(err)
    }
}

以下、コード解説します。

いきなりシェルを呼んでいるんですが、gcloudの認証情報をアクティベートしてk8sapiを叩けるようにしています。

func init() {
           :
    // client-goをservice accountで使用するためにシェルを起動
    exec.Command(
        "gcloud", "config", "set", "container/use_v1_api", "false")
    exec.Command(
        "gcloud", "auth", "activate-service-account", "--key-file="+os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))

この処理を経ると
最終的に欲しい~/.kube/configというkubectlの認証ファイルができます。

cloud-sdkのapiクライアントを取得します。

    // get metrics client
    var err error
    ctx := context.Background()
    client, err = monitoring.NewMetricClient(ctx)

StackDriver monitoringはcustom metricsという箱にtime seriesという名のレコードを突っ込めばmetricsの情報が差し込まれます。
https://cloud.google.com/monitoring/api/v3/metrics

なので、下記のような順序で処理をしています。

  • custom metricsが無かったら作る
  • k8s情報(ここではpod数)の取得
  • metricsへ書き込み
func main() {
    // metrics作成
    var des *metric.MetricDescriptor
    des = getMetric()
    if des == nil {
        des = createMetric()
    }

    // k8sの情報取得
    podNum := getPodNum()

    // metrics書き込み
    addLogs(podNum)
}

実行

go run main.go

結果

うまくいmetricsを作成できていれば

StackDriver Monitoring -> Add Chart で下記のように作ったmetricsが候補として挙がります。

timeseriesも書き込みできていたらグラフも表示されます。

以上です。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1