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かこのブログへのコメントで頂けると幸いです〜。

TypeScript開発における僕のVisual Studio Code設定

僕が Visual Studio Code を利用して開発しているときの設定。 こうすると ts-nodeとjestの双方でdebuggerを利用できる。

※ 他に良い方法があれば :pray:

設定ファイル

{
  "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
        // debugの際はcacheを無効にした。
        // cacheを無効にすると、2倍くらい遅くなるらしい。
        "--runInBand",
        "--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"
    }
  ]
}
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "build",
      "type": "shell",
      "command": "npx tsc"
    }
  ]
}

AWS re:invent 2018 サービスアップデート1行まとめ

ML系

Amazon Personalize eコマースなどでレコメンドができるようになるサービス。今までのサービスと異なり、自分たちの持っているデータを学習データとしてINPUTさせることができる。データはS3 or Amplify から送信可能。

Amazon Forecast 時系列データの予測。過去の売上などから、将来の売上を予測したりする。自分たちのデータを食わせることが可能

Amazon Comprehend Medical Amazon ComprehendというNLサービスの拡張。メディカル向けのチューニングが行われている。医療分野に対応。

Amazon Textract OCR。日本語には未対応

Amazon Translate Custom Terminology Amazon Translateという翻訳サービスで、カスタム語彙が登録できるようになった。

ML Models in AWS Marketplace 機械学習モデルのマーケットプレイス。どこかの企業が作った出来合えのアルゴリズム・モデルを利用して、色々できるようになる

Amazon SageMaker Ground Truth データのアノテーションをサポートしてくれるサービス。アノテートするためのUIをいい感じに作ってくれる。実際にアノテートするのは人間。

SageMaker Neo SageMakerで作成したモデルを、EC2 インスタンスやGreegranss デバイス等のそれぞれのデバイスで最適に実行できるように変換する。それによって、モデルの効率をサイズ・速度の両面で改善する。OSSで公開中。

フレームワーク:Tensorflow, Apache MXNet, PyThorch デバイス:EC2 / ラズベリーパイ

Amazon Elastic Inference GPUリソースを安く利用するためのサービス。 Px 系のインスタンスを使わなくても、必要なときにCx系インスタンスにEIAをアタッチすればGPUリソースを一時的に付与して利用することができるっぽい。

SageMakerの画像認識にセマンティックセグメンテーションが追加 特定の画像に対して、ピクセル単位で画像領域を判断可能になった

Amazon SageMaker RL 強化学習をサポートする SageMakerの拡張。AWS RoboMakerや DeepRacer, Sumerianをシミュレーターとして利用可能。

Database

Amazon QLDB フルマネージドな元帳データベース。すべてのデータ変更を正確に順序づけられたエントリーとして格納

Amazon Managed Blockchain Hyperledger FabricとEthereum* を仕様したフルマネージドなブロックチェーンネットワークサービス。

Amazon Timestream 大量に発生する時系列データに特化したDB。RDBMSの10/1コスト、1000倍の性能で時系列データを扱える。

DynamoDB Transactions

DynamoDBで、複数アイテム・テーブルに対してACIDトランザクションをサポート。 Serializableでロックは取らない。頻繁に同じ行をrockして取り合うようなものには向かない。

DynamoDB On-Demand DynamoDB でPay-per-Requestモデルの請求モードが選択可能になった。

Amazon Aurora Global Database MySQL 5.6 互換。異なるリージョンのリードレプリカを、1秒未満の低レンテンシでのレプリケーション

Serverless

Amazon Aurora Serverless Data API Aurora Serverless へのアクセスについて、HTTPSエンドポイントが追加。VPCにアクセスすることなく Auroraを利用可能。

Lambda Layers Lambda間でロジックを簡単に共有できるもの。

AWS Step Functions API Connectors

ECS, DynamoDB, AWS Batch等々のサービスについては、Lambdaで書かなくても StepFunctionsから直接色々実行できるようになる

  • ALBがLambdaに対応しました
  • LambdaのRuntimeにRuby 2.5が追加されました

Storage

AWS DataSync マイグレーションやアップロード、バックアップ/DRにおけるデータ転送の加速と自動化を実現する

Glacier Deep Archive Deep ArchiveはGlacierよりもさらにコストを抑えることができるもの。 Glacierとの差分がよく分からぬ。

EFS Infrequent Access Storage Class 30日間くらいアクセスされないファイルを、自動でこのクラスに割り当てる。安くなる。

Automatic Cost Optimization for Amazon S3 Intelligent Tiering S3の拡張。AWSの方でパターンを見つけて、コスト最適になるように自動的にストレージクラスを選ぶ。

AWS Transfer for SFTP SFTPでS3のバケットにupload可能。AWSの機能で認証できる

Amazon FSx for Windows Server WindowsのファイルサーバーをAWSで構築可能。セキュリティの要件も日本のPCI/ISOに対応してるらしい

マイクロサービス

AWS Cloud Map マイクロサービス間の依存関係を把握するためのもの

AWS App Mesh マイクロサービス向けのサービスメッシュを提供する。無料

アカウント管理やセキュリティ

AWS Control Tower マルチアカウントに対応したセキュアな環境を簡単に設定・管理可能

AWS Security Hub GuardDutyやInspector、3rd Partyのログを一元管理できる。

AWS Transit Gateway 複数のアカウントで作成された複数のVPCを、中央で集約して管理することができるようになる

Amazon CloudWatch Log Insights CloudWatchでElasticsearch + Kibanaみたいな使い方ができるようになるらしい。

ネットワーク

AWS Global Accelerator 複数リージョン向けのアプリケーションのパフォーマンスの向上を容易にする。 一番近いリージョンのELB, EIPに自動で割り振るようにする。

その他

AWS Outposts AWSインフラストラクチャをオンプレミスで実行できるようになる。 EC2, EBS、RDS, ECS, EKS, SageMaker, ERMが対応している。

AWS Well-Architected Tool 現在のAWSベスト・プラクティスに照らしてワークロードを確認し、アーキテクチャを改善する方法を教えてもらう。

AWS Lake Formation データレイクを簡単に作ることができるようになるサービス

AWS Elemental Media Connect ビデオのライブストリーミングができるサービス 

AWS Re:invent 2日目のKeynoteまとめ

(TypeScriptで書かれてWebpackでビルドされた) CloudFunctionsのエラー通知をわかりやすくしてみた

やりたいこと

stack driver error reportのエラーの内容をもう少しわかりやすくしたい。

やること

GitHub - prisma/serverless-plugin-typescript: Serverless plugin for zero-config Typescript support

ここを参考に node-sourcemap-supportを導入した。

https://firebase.google.com/docs/functions/typescript?hl=ja

ここを見ると、Firebaseでは index.js.map も一緒にdeployできると書いてあるが、cloud functions単体ではそういった記述を確認できなかったので、少し古いが上記のtopicに書かれていた通りの方法で実装した。serverless frameworkじゃなくても上手くいくか分からなかったがとりあえず試してみて、上手くいった。

具体的な変更内容

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

これだけ。

結果

stack driver error traceで見たエラーメッセージを比較する。

before

ReferenceError: atob is not defined
at t.HistoryAppender (index.js:593)
at t.history_appender (index.js:424)
at (/worker/worker.js:756)
at <anonymous>
at process._tickDomainCallback (next_tick.js:228)

どこのコードで問題があったのか全くわからない。

after

ReferenceError: atob is not defined
at parse (/srv/webpack:/node_modules/@google-cloud/bigquery/src/index.js:5)
at t.history_appender (index.ts:49)
at (/worker/worker.js:756)
at <anonymous>
at process._tickDomainCallback (next_tick.js:228)

index.ts: 49行目の処理に問題があることがわかるようになった。

その他メモ

  • どのくらい深い階層まで追えるのかまだ分かっていない
  • build後のファイルサイズが大きくなっている訳ではない
// before
ls -alh index.js
-rw-r--r--  1 shuhei.morioka  staff   5.5M 11 12 21:24 index.js

// after
ls -alh index.js
-rw-r--r--  1 shuhei.morioka  staff   5.5M 11 12 21:15 index.js

他に良い方法をご存知の方いれば教えてください〜