Azure Functions+TypeScriptでMackerelにメトリック投稿

このエントリーは、ふぃーるどのーつおよび錬金術MeetUpとのタイアップ記事です。

技術書典のWebサイトにはサークルチェックという機能があり、サークル参加者は マイページで自分のサークルへのチェック数を知ることができます。

f:id:setoazusa:20180416015555p:plain

本エントリーでは、技術書典のサークル被チェック数を取得してMackerelにメトリック投稿する バッチ処理をAzure Functions上でTypeScriptを用いて構築します。

なぜTypeScriptかというと、Javaプログラマーは型がないとろくなプログラムがかけないからです。...という話を第二十一回 Azureもくもく会 @ 品川 - connpassのLTでしたら主宰の@kingkinoさんに「C#でいいじゃない」とつっこまれました。いやそうでもあるし、Azure Functions上でJava っていう手もありますが。

azure-moku2.connpass.com

サンプルコード

ソースコードは、以下で公開しています。

https://github.com/azusa/circlecheck-mackerel

なお、このコードはえるきちさん(@erukiti)がerukiti/techbookfest-circle-botで公開している、「技術書典4向けの被サークルチェック数をSlackに垂れ流すbot」が元になっています。

用意するもの

関数を作成する

「Azure CLI で初めての関数を作成する」を参考にして関数を作成します。

docs.microsoft.com

# コンソール出力のメッセージに従って https://microsoft.com/devicelogin にアクセスしてコードを入力する
az login

# リソースグループの作成
az group create --name myResourceGroup --location japaneast

az storage account create --name startazurefunction --location japaneast --resource-group myResourceGroup --sku Standard_LRS

# Fuction App を作成する
az functionapp create --resource-group myResourceGroup --consumption-plan-location japaneast --name fieldnotesjpfunction --storage-account  startazurefunction

なおaz functionapp createの実行時に以下のようなエラーメッセージが表示されたときは--nameで指定するApp名が既に取得されているので、ぶつからない名前にしてください。

Operation failed with status: 'Not Found'. Details: 404 Client Error: Not Found for url: 
https://management.azure.com/subscriptions/***/resourceGroups/myResourceGroup/providers/Microsoft.Web/sites/startazurefunction?api-version=2016-08-01
# Azure Functions Core Tools をインストールする
npm install -g azure-functions-core-tools@core
# ひな形を作成する
func init MyFunctionProj
cd MyFunctionProj
# JavaScriptで関数のひな形を作成する
func new --language JavaScript --template TimerTrigger --name TimerTriggerJS
cd TimerTriggerJS

TimeTriggerJSディレクトリー配下にpackge.jsonを作成します。postinstallで行っていることについては後述します。

{
  "name": "azurefunctions-typescript-startup",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "tslint": "^5.9.1",
    "tslint-config-prettier": "^1.10.0",
    "tslint-plugin-prettier": "^1.3.0",
    "typescript": "^2.8.1"
  },
  "scripts": {
    "postinstall": "npm install -g typescript && tsc"
  }
}
# ライブラリーをインストールする
npm install
# tsconfig.jsonを作成する
tsc --init

tsconfig.jsonの設定を記述します。

{
  "compilerOptions": {
    "target": "ES2015", 
    "module": "commonjs",
     "lib": [ "es2015" ], 
    "sourceMap": true,
    "strict": true,
  },
  "exclude": [
    "node_modules"
  ]
}

index.jsをindex.tsにリネームし、中の記述を以下の通りなおします。

export async function run (context: any, myTimer: any) {
    const timeStamp = new Date().toISOString();
    
    if(myTimer.isPastDue)
    {
        context.log('JavaScript is running late!');
    }
    context.log('JavaScript timer trigger function ran!', timeStamp);   
    
    context.done();    
  };
# コンパイルが通るか確認
tsc

デプロイの設定をする

ここまでの過程をpushしたものが次にあります。

github.com

Azure Functions の継続的なデプロイ | Microsoft Docsに従い、GitHubとの連携設定を行います。設定は、関数の「プラットフォーム機能」の「展開オプション」から行います。

docs.microsoft.com

f:id:setoazusa:20180416021813p:plain

なお、メニューを展開していくと画面が横に果てしなく長くなりますが、これがAzure流なのでしょう。多分。

f:id:setoazusa:20180416021836p:plain

デプロイ時にtscコマンドが実行されるようにする

ここまでの設定で、Gitのリモートレポジトリーにコードをpushした時にAzure Functionsにコードが連携され、npm installで依存ライブラリーのインストールが行われるようになっています。

TypeScriptの場合は、このタイミングで、TypeScriptからJavaScriptへのコンパイルを行う必要があります。

Azure Fucntionsの場合はnpm installのタイミングで行うことになるので、postinstallのタイミングでTypeScriptのインストールとtscコマンドの実行を行うよう、package.jsonを記述することになります。

postinstallのタイミングで、グローバルへのnpmパッケージのインストールを行うのはnpmのお作法からははずれますが、やむを得ないという感じです。

timerTriggerの注意点

timeTriggerの設定はfunction.jsonで行いますが、この際の注意点は、CRON書式の一番左が秒だということ です。

docs.microsoft.com

UNIXのCRONのつもりで毎時実行しようとして0 * * * * *と書くと、毎分実行されることになります。(なりました)

バッチ処理の記述

以上の仕組みをもとにした、バッチ処理の実装は、以下の通りです。

github.com

MackerelのAPIを使用して、POSTする部分の処理は、以下の通りです。

const sendToMackerel = async checkedCount => {
    const opt = {
        method: 'POST',
        uri: 'https://api.mackerelio.com/api/v0/services/techbookfest/tsdb',
        body: JSON.stringify([{ name: "circleCheck", time: Math.floor(new Date().getTime() / 1000), value: checkedCount }]),
        headers: {
          'content-type': 'application/json',
          'X-Api-Key': apiKey
        },
        resolveWithFullResponse: true
      }
    await rp(opt)
}

スクリプトが参照する環境変数の設定は、「プラットフォーム機能」の「アプリケーション設定」から行います。

f:id:setoazusa:20180416023853p:plain

余談ですが、他のXaaSサービスの設定画面にあるような、「パラメーターをマスクする機能」ってないんですかね...

取得するメトリック

以上の設定を行ってFunction Appを実行すると、以下の様なメトリックが取得できます。*1

f:id:setoazusa:20180423123824p:plain

おわりに

上記のバッチ処理は、4/22に開催される技術書典4で、サークル「ふぃーるどのーつ」の新刊「Mackerelではじめるお手軽サーバー監視」のサービスメトリックの事例として実装したものです。同書ではプラグイン開発による対象メトリックの追加やCRONによるバッチ処理のロギングと監視など、Mackerelの活用事例を豊富に取り上げています。

pub.fieldnotes.jp

*1:Mackerelのグラフ共有機能を使用しています