Cloud FunctionsをGoで実装しGoogle Compute Engineの起動停止を行う

過去にCloud Functions/Cloud Scheduler/Cloud Pub Subを使って、Google Compute Engineインスタンスの起動停止の自動化を行う という記事を書きましたが、背景としては実装した方が楽しいよねとか、Goを使ってみたいという欲がありました。業務でも個人的にも書いたことがなかったので、勉強がてらにCloud Functions×Goで作ってみました。GCPで使うサービスも前回はGUIで作成をしていきましたが、GCP上のサービスはCLIで作成していき、最後の起動部分は画面で確認します。
完成品はGitHubに公開しました。辛辣な意見などもらえると気持ち良いです。

github.com

前提条件

  • OS:macOS Majave
  • IDE:GoLand
  • Go:インストール済み(version1.12.7)
  • Cloud SDK :インストール済み

GoでGoogle Cloud Functionsの実装していく

自前で書いたコードの全貌としてはこちらです。

package src

import (
    "context"
    "encoding/json"
    "google.golang.org/api/compute/v1"
    "log"
    "os"
)

var projectId = os.Getenv("GCP_PROJECT")

type Message struct {
    Data []byte `json:"data"`
}

type PayLoad struct {
    Switch string `json:"switch"`
    Target string `json:"target"`
    Zone   string `json:"zone"`
}

func InstanceSwitcher(ctx context.Context, msg Message) error {

    var payLoad PayLoad

    err := json.Unmarshal(msg.Data, &payLoad)

    if err != nil {
        log.Println("[ERROR][%T][MSG]: %v", err, err)
        return nil
    }

    service, err := compute.NewService(ctx)
    is := compute.NewInstancesService(service)
    insList, err := is.List(projectId, payLoad.Zone).Do()
    log.Printf("instance:list: %v", insList)

    log.Printf("[ProjectId:%s][Switch:%s][Target:%s][Zone:%s]", projectId, payLoad.Switch, payLoad.Target, payLoad.Zone)

    switch payLoad.Switch {
    case "start":
        log.Println("instance start")
        _, err = is.Start(projectId, payLoad.Zone, payLoad.Target).Do()
    case "stop":
        log.Printf("instance stop")
        _, err = is.Stop(projectId, payLoad.Zone, payLoad.Target).Do()
    }

    if err != nil {
        log.Printf("[ERROR][%T][MSG]: %v", err
, err)
        return nil
    }

    return nil
}

Pub/Subのトピックを作成する

メッセージを投げる際の役割を担ってくれるPub/Subトピックを作成しておきます。

❯❯❯ gcloud pubsub topics create insSwitch

Google Cloud Functionsをデプロイする

❯❯❯ gcloud functions deploy InstanceSwitcher --runtime go111 --trigger-topic insSwitch

これだけでサクッと作成してくれるかと思っていましたが、エラーに苛まれました。 以下が実際に出力されたエラーです。

Deploying function (may take a while - up to 2 minutes)...failed.
ERROR: (gcloud.functions.deploy) OperationError: code=3, message=Build failed: /tmp/sgb/gopath/src/serverlessapp/vendor/src/main.go:6:2: cannot find package "google.golang.org/api/compute/v1" in any of:
    /tmp/sgb/gopath/src/serverlessapp/vendor/google.golang.org/api/compute/v1 (vendor tree)
    /go/src/google.golang.org/api/compute/v1 (from $GOROOT)
    /tmp/sgb/gopath/src/google.golang.org/api/compute/v1 (from $GOPATH)

調査したところ公式にしっかり書いてあることに気付きました。

Go の Cloud Functions では、go.mod ファイルを含む Go モジュール、または vendor ディレクトリのいずれかによって、すべての依存関係を指定する必要があります。詳しくは、Go での依存関係の指定をご覧ください。

正直かなりの時間を費やしましたが、以下のドキュメントにある通りgo env GOPATHで作業ディレクトリを見てgo.modを作成しました。私は内側にいたので以下を実行しました。

❯❯❯ go mod init
❯❯❯ go mod tidy

cloud.google.com

Google Cloud Functionsを再度デプロイする

❯❯❯ gcloud functions deploy InstanceSwitcher --runtime go111 --trigger-topic insSwitch
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
entryPoint: InstanceSwitcher
eventTrigger:
  eventType: google.pubsub.topic.publish
  failurePolicy: {}
・・・以降略

これでGoogle Cloud Functionsのデプロイが成功しました。

Google Cloud Functionsの動作確認

Pub/Subから来るメッセージを想定して、実際に動いているか確認します。

❯❯❯ gcloud pubsub topics publish insSwitch --message '{"switch":"switchTest", "target":"targetTest", "zone":"zoneTest"}'
#成功していると以下が返ってきます
messageIds:
- 'XXXXXXXXX'

Google Cloud Functionsのログを確認します。ログに出力されているので問題なく動いていますね!

❯❯❯ gcloud functions logs read --limit 2
LEVEL  NAME              EXECUTION_ID     TIME_UTC                 LOG
       InstanceSwitcher  XXXXXXXXXXXXXXX  2019-08-09 09:58:56.680  2019/08/09 09:58:56 [ProjectId:xxxxxxxxxxx][Switch:switchTest][Target:targetTest][Zone:zoneTest]
D      InstanceSwitcher  XXXXXXXXXXXXXXX  2019-08-09 09:58:56.681  Function execution took 1197 ms, finished with status: 'ok'

Cloud Schedulerを作成する

Pub/Subトピックを中間してGoogle Cloud Functionsをキックし、停止と起動をおこなうCloud Schedulerを作成します。
※一部自身の環境に合わせることに注意してください

GCE停止用

❯❯❯ gcloud --project [PROJECT_ID] scheduler jobs create pubsub [PUBSUB_NAME] \
    --time-zone "[TIME_ZONE]" \
    --schedule "[CRON_TIMER]" \
    --topic [TOPIC_NAME \
    --message-body '{"switch":"stop", "target":"[TARGET_INSTANCE_NAME], "zone":"[ZONE]"}'

GCE起動用

❯❯❯ gcloud --project [PROJECT_ID] scheduler jobs create pubsub [PUBSUB_NAME] \
    --time-zone "[TIME_ZONE]" \
    --schedule "[CRON_TIMER]" \
    --topic [TOPIC_NAME \
    --message-body '{"switch":"start", "target":"[TARGET_INSTANCE_NAME], "zone":"[ZONE]"}'

GCEの起動と停止を確認する

GCEが起動している前提はありますが、作成した起動用のCloud Schedulerと停止用のCloud Schedulerを画面から実行します。

Cloud Schedulerを実行し停止の確認 f:id:tanabebe:20190809195907p:plain 少し時間をおいてから以下の通り、インスタンスが停止してくれました!期待通り動作していますね! f:id:tanabebe:20190809195621p:plain

Cloud Schedulerを実行し起動の確認 f:id:tanabebe:20190809200110p:plain こちらも少し時間をおいてからインスタンスが起動するのが確認出来ました! f:id:tanabebe:20190809200355p:plain

まとめ

Google FunctionsからのGCEへ対しての操作をGo初心者でも実装することが出来た。Goインストール後の作業場はどうするべきかGoからGCEを操作するためにはどうするべきなのかなど環境面も含めて結構時間がかかってしまいました。またまだGoの深淵には程遠いですが、今まで触れてこなかった言語に触れる事で改めてモノ作りの楽しさを実感。GCP上のサービスはまだまだたくさんあるのでGoとのセットでこれに留まらず更に研鑽していきます。余談ですがGopherがとっても可愛かったのでTシャツを購入しました。

参考URL

Google Cloud Functionsをコマンドラインからデプロイする - Qiita
Cloud FunctionsでGo言語を使ってCloud Pub/Subから値を取得する - Qiita
godoc.org/google.golang.org/api/compute/v1 https://cloud.google.com/compute/docs/reference/rest/v1/
The Go Blog - Using Go Modules / Go Modulesを使う(和訳) - Qiita