7
3

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 3 years have passed since last update.

All About Group(株式会社オールアバウト)Advent Calendar 2019

Day 24

サーバレス環境でbotを動かす(Go & Cloud Run)

Last updated at Posted at 2019-12-23

こんにちは!私(@f_yamagami)です!
この記事は、All About Group(株式会社オールアバウト) Advent Calendar 2019 クリスマスイブの記事です。

前談(The・前談)

クリスマスイブですね。マジあれですね
みなさんはクリスマスプレゼントに何が欲しいですか?
ゲーム? お金? 休み?
やっぱりエンジニアならサーバですか?
あれ、そこのエンジニアさん
どうしてサーバと聞いて苦い顔をされていらっしゃるんですか?
え? サーバ障害の対応で、せっかくのクリスマスディナーが冷えてしまい、
恋人との関係も冷えてしまった過去が?ざまあみ(ry
※私はサーバにはなんの思い出もないです。

ということで、今回は会社の開発合宿で先輩エンジニアに手取り足取りしていただきながら、サーバレスの環境でbatch処理を行った話を書いていきます。
[先輩のQiitaはこちら↓]
https://qiita.com/y_hideshi/items/085a92746578b5b7f01d

今回作ったのは、先輩のQiitaにもある通り、
定期的にGCPの確約利用割引の期日の自動チェック & アラートあげるbotです。

確約利用割引とは

確約利用割引はGCEの契約を1年間または3年間の支払いを確約する代わりに、特定の量の vCPU、メモリ、GPU、ローカル SSD を割引価格で購入できるものです。

マシンタイプやコア数、適用期間などを指定したコミットメントというものを購入すると、その分の利用分は確約利用割引が適用されるというものです。
雑な説明にはなりますが、年間パスポートのようなものです。
最初に一括で代金をを支払うわけではないので、通常の年間パスポートとは少し違いますが、下記の例を見るとだいたい年間パスポートみたいなものだという計算になると思います。

例:8コア用のコミットメントを購入

  • 4コアを使用した月 → 8コア用の確約された利用料金が請求される
  • 8コアを使用した月 → 8コア用の確約された利用料金が請求される
  • 12コアを使用した月 → 8コア用の確約された利用料金 + 4コア分は通常料金で請求される

詳しくは公式のドキュメントでご確認ください。
https://cloud.google.com/compute/docs/instances/signing-up-committed-use-discounts?hl=ja

#前談2 (準備的な)
まず、定期的にGCPの確約利用割引の期日の自動チェック & アラートあげるbotを作るにあたって、実現したいことの認識合わせを先輩としました。

  • 今ある課題
    • 契約している確定利用割引の更新日が近づくと、メールでのアラートは来るけど見落としそう
    • 見落として更新を忘れると通常価格での支払いとなり、損してないけど損した気分になるので嫌だ
  • 実現したいこと
    • 契約している確定利用割引の更新日が近づくと、Slackで通知させたい!

某総理大臣と某大統領のごとく、直接会談の結果、完全に認識が一致しました。
次に、実現方法について考えました。
確定利用割引のコミットメントはAPIで内容が確認できることがわかったので、
下記のように、私は考えました。

1.下記のような簡単なアプリを作る
 ・コミットメントの終了日を取得するAPIを叩いて値を取得
 ・取得したコミットメントの終了日が1週間以内か確認
 ・1週間以内ならSlackのAPIを叩いて通知

2.上記アプリをどっかのサーバに置いてcronで1日1回実行

私「先輩! こんな感じで考えてるんですけど、どこのサーバに置きますか?
てか、新しいサーバを立てますか?」

1先輩「なるほど、君はどこのサーバにアプリを置くか迷っているようだけど、全てが違う。
サーバをどこに置くか以前に、サーバを立てる必要があるのかを考えたか?
貴様の悩むべきところは、サーバ代の金銭的負担だけでなく、
サーバ管理が必要となり、技術的負債まで残してしまうことを、1mmたりとも
考慮できていない己の無知に気が付いていないことではないのか?

あまりの衝撃的なお言葉に、なんとおっしゃっていたのかは正確には忘れてしまいましたが、
実際のところは「え?サーバは使わないでCloud Runでしてみない?」だった気もします。

ということで、今回はサーバレス環境で実装することになりました。
また、私は

  • コミットメントの終了日を取得するAPIを叩いて値を取得
  • 取得したコミットメントの終了日が近いかの確認
    の部分を実装したので、その辺り中心に書こうと思います。

システム構成

(略)
※先輩のQiitaにて詳細に紹介済み
https://qiita.com/y_hideshi/items/085a92746578b5b7f01d#%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E6%A7%8B%E6%88%90

実装

今回はgo言語で実装しました。

main.go

/* 
 * importは省略しています。
 * 愚直に公式のExamplesに沿って認証を通していきます。
 * 認証にApplication Default Credentialsを利用したサンプルです。
 */
ctx := context.Background()

c, err := google.DefaultClient(ctx, compute.CloudPlatformScope)
if err != nil {
  log.Fatal(err)
}

computeService, err := compute.New(c)
if err != nil {
  log.Fatal(err)
}

// プロジェクトとリージョンを指定
project := "my-project"
region := "my-region"

if err := req.Pages(ctx, func(page *compute.CommitmentList) error {

    // 今日の日付
    today_str, _ := time.Parse("20060102", time.Now())

    for _, commitment := range page.Items {
        // 文字列 → 日付型
        endDate, _ := time.Parse("2006-01-02T15:04:05Z07:00", commitment.EndTimestamp)

        // 何日前か比較したいリスト
        dayArray := [3]int{7, 14, 30}
        for _, x := range dayArray {
            // 今日の日付からx日先の日付をcompareDateに設定する
            compareDate := today_str.AddDate(0, 0, x)

            // 日付型を整えた上で、アラートを出すべき日かを確認する。
            if (endDate.Format("2006-01-02") == compareDate.Format("2006-01-02")){
                message_str := "確約利用割引 `" + commitment.Name + "`の期限は `" + endDate.Format("2006-01-02") + "`までです:alert:"
                // pub/subにmessageを公開する
                PubsubFunc(message_str)
            }
        }
    }
    return nil
}); err != nil {
        log.Fatal(err)
}
PubsubFunc関数.go
func PubsubFunc(message_str string) {
    ctx := context.Background()

    client, err := pubsub.NewClient(ctx, "my-client")
    if err != nil {
        log.Fatal(err)
    }

    topicName := "slack-topics"
    topic = client.Topic(topicName)

    exists, err := topic.Exists(ctx)
    if err != nil {
        log.Fatal(err)
    }
    if !exists {
        log.Printf("Topic %v doesn't exist - creating it", topicName)
    }
    // slackに投稿するために必要な要素を設定する
    atr := map[string]string{
        "Color":"warning",
        "Fallback":"メッセージ通知タイトル",
        "AuthorName":"Cloud Run",
        "AuthorSubname":"クラウドラン",
        "AuthorLink":"https://console.cloud.google.com/~~~",
        "AuthorIcon":"https://repository-images.githubusercontent.com/~~~",
        "Text":message_str,
        "Footer":"slackapi",
        "FooterIcon":"https://platform.slack-edge.com/img/~~~.png",
        "WebhookUrl":"https://hooks.slack.com/services/~~~",
    }
    topic.Publish(ctx, &pubsub.Message{Data: []byte("payload"), Attributes: atr})
}

※特にmain.goの方で、ネストが深くなっていたり、無理やりな部分があり、見苦しく申し訳ないです。

最後に

今まではPHPでコードを書いて、すでに構築されているk8sの環境にリリースしたことぐらいしかなく、
go言語もサーバレス環境も全てが新鮮でとても勉強になりました。

また、PubSubにメッセージを公開する部分は、ほとんど先輩に教えていただきました。
実装をGCRにあげるところや環境周りでも大変お世話になり、とても感謝しております。
ありがとうございました。

  1. 私の妄想上での発言です。

7
3
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
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?