LoginSignup
4
5

More than 5 years have passed since last update.

Cloud Functions/GoでGCPの課金情報をSlackに通知する

Last updated at Posted at 2019-01-19

Cloud FunctionsでGo 1.11がβリリースされたので試したときのメモです。
GCPの課金情報をCloud StorageにJSON形式でexportするようにし、
Cloud Functions/GoでSlackに通知するコードを書いて試してみました。

関数の実装方法

関数の実装方法は以下の2種類に分けられるようです。
新しく覚えることはほとんどなくどちらも非常に簡単です。

HTTP functions

HTTPリクエストをトリガーにして実行される関数です。
実装は簡単で一般的なWebアプリケーションと同様にハンドラを定義すればよいです。

// function.go
package function

import "net/http"

func F(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "text/plain; charset=utf-8")
        w.Write([]byte(r.Header.Get("X-Forwarded-For")))
}

background functions

今回はこちらを使いました。
イベントをトリガーにして実行される関数です。
利用可能なイベントは、公式ドキュメントに記載があるので見てみてください。

第二引数で各イベントに対応した構造体を受け取るように実装する必要があります。
現時点では構造体は自前で定義する必要がありますが、上記ドキュメントに記載されているものを参考にすれば良さそうです。

// function.go

// Package function includes an example of processing a GCS event.
package function

import (
        "context"
        "log"
)

// GCSEvent holds event data from a Google Cloud Storage Event.
type GCSEvent struct {
        Bucket      string `json:"bucket"`
        Name        string `json:"name"`
}

func F(ctx context.Context, e GCSEvent) error {
        log.Printf("Processing file: %s", e.Name)
        return nil
}

事前準備

  • GCP
    • IAMでCloud FunctionsのAPIの有効化
    • Cloud SDKを最新にする
      gcloud components update && gcloud components install beta
    • Cloud Storageに適当なバケットを作成
    • 課金情報をJSON形式で作成したバケットに出力するように設定
  • その他
    • SlackのWebhook URLをメモしておく

実装してみよう

作成したコードはこちらです。

概要

あまり難しいコードではないので要点だけ説明します。
GCPのサンプルコードも参考になるので併せて参照してください。

まず、Cloud Storageからのイベント情報を構造体で定義します。
こちらはGCPのサンプルコードを参考にしました。

type GCSEvent struct {
    Bucket         string    `json:"bucket"`
    Name           string    `json:"name"`
    Metageneration string    `json:"metageneration"`
    ResourceState  string    `json:"resourceState"`
    TimeCreated    time.Time `json:"timeCreated"`
    Updated        time.Time `json:"updated"`
}

あとは、第一引数にcontext.Contextと第二引数に上記で定義した構造体を受け取る関数を定義するだけです。
簡単ですね!!

func F(ctx context.Context, e GCSEvent) error {
    // contextからメタ情報を取得することができる
    meta, err := metadata.FromContext(ctx)
    if err != nil {
        return fmt.Errorf("metadata.FromContext: %v", err)
    }
    log.Printf("Event ID: %v\n", meta.EventID)
    log.Printf("Event type: %v\n", meta.EventType)
    // ...省略

    // Slackにwebhookで通知
    return webhook(ctx, webhookURL, buildMessage(e.Name, b))
}

第二引数で受け取ったイベント情報(バケット名とファイル名)を使ってCloud Storageからファイルの内容を読み取ります。
今回は、JSON形式で課金情報が記載されているので、必要な情報を抜き出してSlackに通知しています。

func readFromGCS(ctx context.Context, bucket, name string) (io.ReadCloser, error) {
    return storageClient.
        Bucket(bucket).
        Object(name).
        NewReader(ctx)
}

依存ライブラリ

依存ライブラリの管理には、go modulesvendoringが利用可能です。
両方同時に利用することができないと記載があるので注意してください。

One-time initialization

初期化の方法が紹介されています。
DBのコネクションなど一度初期化しておけばよいものはまとめて実装しておきましょう。
func init()sync.Once.Do()の2パターンが紹介されていますので、それぞれ用途に合わせて利用しましょう。
私は、Cloud Storageのクライアント初期化や正規表現のコンパイルをfunc init()で行いました。

func init() {
    var err error
    storageClient, err = storage.NewClient(context.Background())
    if err != nil {
        log.Fatalf("storage.NewClient: %v", err)
    }

    webhookURL = os.Getenv("WEBHOOK")
    regexB = regexp.MustCompile(`billing-(.*).json`)
}

デプロイしてみよう

ドキュメントを参考にしてgcloud cliでデプロイします。

gcloud functions deploy FUNCTION_NAME --runtime go111 --entry-point F --trigger-resource \
TRIGGER_BUCKET_NAME --trigger-event google.storage.object.finalize \
--region asia-northeast1 --project=PROJECT_ID --set-env-vars WEBHOOK=WEBHOOK_URL

FUNCTION_NAMEは、実装とは関係なくデプロイした関数の名前になるので好きなものを指定してください。
--entry-pointに呼び出して欲しい関数名を指定します。
--trigger-resourceにバケット名を指定します。
--trigger-eventには、イベントのタイプを指定します。
今回は、バケットにファイルが生成されたタイミングで関数を実行して欲しいのでgoogle.storage.object.finalizeを指定しています。
google.storage.object.finalizeは、以下のように説明されています。

This event is sent when a new object is created (or an existing object is overwritten, and a new generation of that object is created) in the bucket.

環境変数の設定

設定には環境変数を利用したいですよね。
Using Environment Variablesに記載があります。
先のコマンド例では、--set-env-vars WEBHOOK=WEBHOOK_URLのようにデプロイ時にwebhook先のURLを環境変数として設定しています。

扱い方の注意事項などいろいろ記載があるので読んでみてください。
例えば、Variable lifecycleには、環境変数はデプロイ時のみ設定が可能とあります。
デプロイ後にUI等で変更することはできないようです。

Environment variables are bound to a deployment of a Cloud Function, and can only be set or changed with a deployment. If a deployment fails for any reason, any changes to environment variables will not be applied. Environment variable changes require a successful deployment.

まとめ

Cloud Functions/Goを試してみました。
今後SDKが拡充されてもっと実装しやすくなることを期待します。

普段はApp Engineでサクッと済ませることが多いのですが、Cloud StorageやFirestoreなどで発生したイベントをもとに処理したいときには便利そうです。
また、Cloud Pub/SubやCloud Schedulerと組み合わせることでいろいろとおもしろい使い方ができそうです。
みなさんも試してみてください!!

4
5
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
4
5