1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?