selmertsxの素振り日記

ひたすら日々の素振り内容を書き続けるだけの日記

GCPでCloud Functionsを作る際の個人的なTIPS

この記事は技術同人誌Advent Calendar 2018の19日目の記事です。 こんにちは。 @selmertsxです。主にRailsのサーバーサイドエンジニアをやってます。

技術同人誌 Advent Calendarということで、今年の11月にNextPublishing様から出版させて頂いたAmazon Web Servicesサーバーレスレシピ という本に書ききれなかった、「Serverlessでチャットボットを作る」というお話を、僕が以前作った serverless-prpolice というslack botを題材にお話をさせていただこうと思っていました。

しかしながら、最近の業務ではGCP(Google Cloud Platform) のCloud Functionsを利用していることが多く、そちらのキャッチアップをしたいなぁという気持ちになりました。そこで、最近作った Cloud Functions製のslack botである datadog_slack_report を題材にして、僕がCloud Functionsを作る際によくやるTipsなどを説明させて頂きます。

前提条件

  • Datadog Slack Report は TypeScriptで書かれています
  • Cloud FunctionsはServerless Frameworkなどを利用せず、素のgcloud コマンドを利用してデプロイされています
  • TypeScriptのコードはwebpackを利用してバンドルされています
  • 監視したいEC2インスタンスには productタグにプロダクト名をつけてもらう必要があります (ex: product: sample )
  • cloud schedulerの設定等、細かい部分についてはここでは説明しません
  • 開発には Visual Studio Code を利用しています
  • testにはjestを利用しています

※ ここで説明していない細かい諸々に関して、コメントを頂ければ説明を追加しようと思います!

Datadog Slack Report とは何をするものなのか

f:id:selmertsx:20181218191749j:plain
datadog slack reportのイメージ

毎日決まった時間に、Datadogを利用している監視ホスト数をproduct毎にまとめてslackで通知します。

Datadog Slack Report をなぜ作ったのか

ぼくがいる会社ではDatadogを利用して全てのサービスのホストを監視しています。僕たちの会社ではDatadogのサービスを1つのアカウントで運用しています。Datadogではアカウントに親子関係を作ることが出来るので、1つの会社で複数のサービスを監視する際は、支払い用のアカウントと各サービスごとのアカウントを分けるのが一般的な利用方法でしょう。しかしながらその方法では、アカウント間でサーバーメトリクスや、プロダクト毎のモニタリング方法、アラート設定等々を、簡単に共有したり確認したりすることができません。 ( 今は違うようでしたら教えて頂けるとめっちゃ喜びます!!!! )

なので僕たちの会社では、1つのアカウントで複数のサービスの監視をすることにしました。プロダクト毎の按分の計算については僕が温かみのある手計算をしいます。 これはさすがに自動化したいなぁと思い、Cloud Functionsのキャッチアップがてら作ってみました。

TIPS

jest & ts-nodeとdebuggerを利用して開発する

AWS Lambdaや Cloud Functionsをいきなり書き始めると確認がとても大変です。なので僕は普通にTypeScriptで動くコードを書いて、一通りできあがった後にLambdaやCloud Functionsに合わせてコードをラッピングしていきます。mockするのが容易な処理であれば、テストコードから書きだすこともあります。Visual Studio Code をEditorとして利用している場合、簡単にdebuggerを使って開発することができます。

f:id:selmertsx:20181218202639p:plain

debuggerを使うときの設定は下記のようになります。

// https://github.com/selmertsx/datadog_slack_report/blob/master/.vscode/launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Jest",
      "preLaunchTask": "build",
      "program": "${workspaceRoot}/node_modules/jest/bin/jest.js",
      "args": [
        "${file}",
        // 現在のプロセスで逐次テストを実行していく設定.
        // この設定をしないと複数のchildプロセスでテストを実行するらしい
        // debuggingのために有用とのことだったので、この設定を有効にした
        // https://facebook.github.io/jest/docs/en/cli.html#runinband
        "--runInBand",
        // debugの際はcacheを無効にした。
        // cacheを無効にすると、2倍くらい遅くなるらしい。
        "--no-cache"
      ],
      "cwd": "${workspaceRoot}",
      "console": "integratedTerminal"
    },
    {
      "name": "ts-node[debug]",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/ts-node/dist/bin.js",
      "args": [ "${relativeFile}" ],
      "console": "integratedTerminal"
    }
  ]
}
// 動作確認の際に利用するコード
// https://github.com/selmertsx/datadog_slack_report/commit/f26a441d23d80f4f9b06541240c5fe20bff8231d

async function datadog_handler(): Promise<void> {
  const fromTime = moment({ hour: 0, minute: 0, second: 0 })
    .add("days", -1)
    .format("X");
  const toTime = moment({ hour: 23, minute: 59, second: 59 }).format("X");

  for (const product of products) {
    const productMetrics: ProductMetrics = await datadogClient.countHosts(
      product,
      fromTime,
      toTime
    );
    attachments.push(SlackMessage.attachments(productMetrics));
  }
  await slackClient.post(attachments);
}

datadog_handler(); // ここで実行される。Lambda化するときなどは、この行を取り除き、引数を適切に設定する

sourcemapを埋め込む

f:id:selmertsx:20181218210015p:plain

TypeScript & webpack環境でCloud Functionsを利用する場合、エラーログは上記の図のように丸め込まれてしまい、ほとんど読むことができません。 なのでsource-map-supportというモジュールを利用して、エラーログを詳細に見れるようにします。

設定方法

npm install source-map-support
// src/index.ts
// この一行を追加
import "source-map-support/register";
// webpack.config.js
module.exports = {
  ...
  devtool: 'source-map', //<= これを追加
  ...
}

結果

f:id:selmertsx:20181218205442p:plain

このように設定すると下記の図のように、エラーが発生している箇所の詳細を把握できるようになります。webpackを利用している際は必ず入れた方が良いモジュールですね。

Cloud FunctionsをLocalで実行する

debuggerをいい感じに利用できていると、正直なところCloud FunctionsをLocalで実行するメリットがあまりないのですが、一応載せておきます。

設定方法

npm i -D @google-cloud/functions-emulator
$ npx functions start
Starting Google Cloud Functions Emulator...
Google Cloud Functions Emulator STARTED
┌────────┬───────────┬─────────┬─────────────────────────────────────────────────────────────┐
│ Status │ Name      │ Trigger │ Resource                                                    │
├────────┼───────────┼─────────┼─────────────────────────────────────────────────────────────┤
│ READY  │ subscribe │ HTTP    │ http://localhost:8010/xxx/us-central1/subscribe │
└────────┴───────────┴─────────┴─────────────────────────────────────────────────────────────┘
$ npx functions deploy datadog_handler --trigger-topic=datadog_report
Copying file:///var/folders/64/xxx/T/tmp-3550rM1YUf87egAJ.zip...
Waiting for operation to finish...done.
Deploying function......done.
Function datadog_handler deployed.
┌────────────┬──────────────────────────────────────────────────────────────────────────────────┐
│ Property   │ Value                                                                            │
├────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ Name       │ datadog_handler                                                                  │
├────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ Trigger    │ google.pubsub.topic.publish                                                      │
├────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ Resource   │ datadog_report                                                                   │
├────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ Timeout    │ 60 seconds                                                                       │
├────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ Local path │ /Users/shuhei.morioka/project/xxx/datadog_slack_report                         │
├────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ Archive    │ file:///var/folders/64/xxx/T/tmp-3550rM1YUf87egAJ.zip │
└────────────┴──────────────────────────────────────────────────────────────────────────────────┘

結果

$ npx functions call datadog_handler
ExecutionId: 790ca715-a537-4f3a-8657-0301e71c8a36

所感と今後の予定

以上、一通り Cloud Functionsを利用してみました。 定性的なお話になってしまいますが、AWS Lambda よりも簡単に開発できる反面、API Gatewayなどのようなツールがないので Functionの実行できるユーザーを制限できない (アルファ版として一部ユーザーには提供されているっぽい) など、AWSの方がServerless の権限まわりに関しては一歩先に進んでいるなぁという印象を受けました。

今後 datadog slack reportについては、下記の機能を追加していく予定です。

  • プロダクト毎のホスト料金按分計算機能
  • プロダクト毎の監視台数設定機能
  • プロダクト毎のAPMの料金按分計算機能
  • プロダクト毎のAPMの監視台数設定機能

監視台数設定機能周りについては、firebaseと認証周りをキャッチアップするために、SPAでサービスをつくっていければな〜と思っています。 その他要望などなどありましたら、GitHubのissueかこのブログへのコメントで頂けると幸いです〜。