tech.guitarrapc.cóm

Technical updates

.NET Core on Lambda で nuget パッケージを利用してみた

といいつつ、当初から Json.NET を利用しているのですが、そこはおいておきましょう。

今回は、AWS Lambda で nuget パッケージを利用してみます。とはいっても難しいことは何もなく、通常の.NET Core アプリと同様です。

Azure Fucntions とは少し違うのでそのあたりも見てみましょう。

tech.guitarrapc.com

目次

必要な条件

.NET Core 対応です。具体的には以下が現状の条件になっています。

compatible with netcoreapp1.0 (.NETCoreApp,Version=v1.0)

ということはつまり、.NET Core 1.0 か .NET Standard 1.6 がいけるということです。

The arrows indicate that the platform supports a higher version of .NET Standard. For instance, .NET Core 1.0 supports the .NET Standard version 1.6, which is why there are arrows pointing to the right for the lower versions 1.0 – 1.5.

dotnetcore.gaprogman.com

ざっくりいうと、project.json に以下の記述がされている nuget packge が互換性があるパッケージとなります。

gist.github.com

gist.github.com

例えば Json.NET は次のようになっています。さすが Json.NET は難しい...。

https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/project.jsongithub.com

ダメなパッケージの例

一見 .NET Core 行けてそうでダメなものが結構出会います。例えば Octokit がそうです。

www.nuget.org

さて、.NET Core 4.5? .NET Core と書いてあるからといって同じではないのです。

エラーが明確に原因を示しています。

    Package Octokit 0.23.0 is not compatible with netcoreapp1.0 (.NETCoreApp,Version=v1.0). Package Octokit 0.23.0 supports:
      - net45 (.NETFramework,Version=v4.5)
      - netcore451 (.NETCore,Version=v4.5.1)
      - portable-net45+win8+wp8+wpa81 (.NETPortable,Version=v0.0,Profile=Profile259)
    Package Microsoft.Net.Http 2.0.20505 is not compatible with netcoreapp1.0 (.NETCoreApp,Version=v1.0). Package Microsoft.Net.Http 2.0.20505 supports: net40 (.NETFramework,Version=v4.0
)
    One or more packages are incompatible with .NETCoreApp,Version=v1.0.

Github で project.json を見てみると....?

https://github.com/octokit/octokit.net/blob/master/Octokit.Next/project.jsongithub.com

いけそうですが、残念です。

適当にビルドして参照しようとしても、現状の .NET Core では、nuget からの取得になるようです。

github.com

ということはつまり、VSTSやMyGetのようなプライベートNuget feed を利用するか、local folder から Nuget feed を配信すればいけるようですがそれはまたの機会に。

stackoverflow.com

Chatworkに送信する

Azure Functions でやったように Chatwork にメッセージ送信してみましょう。

.NET Core 対応していないライブラリの対応

Chatwork.Api という いつも使っている nuget パッケージがありますが、昨日まで 0.4.0 で .NET Core 対応されていませんでした。

www.nuget.org

このままでは project.json で 0.4.0 を指定しても当然利用できません。

    Package Chatwork.Api 0.4.0 is not compatible with netcoreapp1.0 (.NETCoreApp,Version=v1.0). Package Chatwork.Api 0.4.0 supports: portable-net45+win8+wp8+wpa81 (.NETPortable,Version=v
0.0,Profile=Profile259)

    One or more packages are incompatible with .NETCoreApp,Version=v1.0.

さっそく .NET Core 対応する PR を送ったところ、id:tanaka733 さんが素早く対応してくださいました。心から感謝なのです。

github.com

Json.NET をはじめとしていくつかのライブラリでどうやって対応しているのかとみてみましたが、ソリューション .sln を分割して、.xproj を改めて作成というパターンが多いのですね.... PCL から .NETStandard 1.6 へのコンバートかと思いましたが意外でした。クロス対応パッケージの作成には苦労しそうですが、今回は .NET Standard 1.6 にすることになりました。

これにより、0.5.0 がリリースされ .NET Standard 1.6 ベースになりました。

www.nuget.org

ライブラリの利用

もちろん新しくなった Chatwork.Api は .NET Core でも利用できるようになっています。project.json に指定してみましょう。

ばっちりですね。

早速コードを書いてみましょう。

今回は、API Gateway から AzureFunctions 同様に以下のフォーマットの JSON が飛んで来たら送信するようにします。

gist.github.com

Azure Functions では次のコードでした。

gist.github.com

AWS Lambda on .NET Core では次のようになります。事前にシリアライズされるか程度で、コードはほぼ変わりません。

gist.github.com

テストも通り、

ローカル実行でもブレークポイントが貼れています。

では VSからデプロイしてみましょう。環境変数に ChatworkApiKey をキーにApiキーを埋め込みます。

サンプルJSON を与えて実行してみると、テスト同様に飛びました。無事に成功です。

もちろん、Channel に誤った数値を入れれば権限がないことを教えてくれます。

{
  "errorType": "AggregateException",
  "errorMessage": "One or more errors occurred. (Failed with code Forbidden. Message: {"errors":["You don't have permission to send messages in this room"]})",
  "stackTrace": [
    "at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)",
    "at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)",
    "at lambda_method(Closure , Stream , Stream , ContextInfo )"
  ],
  "cause":   {
    "errorType": "Exception",
    "errorMessage": "Failed with code Forbidden. Message: {"errors":["You don't have permission to send messages in this room"]}",
    "stackTrace": [
      "at Chatwork.Service.ChatworkClient.<SendAsync>d__33`1.MoveNext()",
      "--- End of stack trace from previous location where exception was thrown ---",
      "at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)",
      "at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)",
      "at SendToChatwork.Function.<FunctionHandler>d__1.MoveNext()"
    ]
  }
}

まとめ

無事に .NET Core なパッケージが扱えることが確認できました。まだまだ .NET Core 対応のライブラリは少ないですが、今後盛んになると祈っています。

今回のサンプルも Github に置いておきます。

github.com

.NET Core on Lambda のローカルデバッグでロガーと変数確認する

Serverless といっても大事なのはローカルで実行できるかです。どんなに便利でも高速なイテレーションが回せないものは継続せず触らなくなったり進化の波に乗れません。

さて、.NET Core on Lambda が出た当初にローカルデバッグで ILambdaContext がないため context 触ろうとするとエラーが出ていましたがサクッと解決できるのでしましょう。

これができることで、関数内の変数を見たり、ローカル実行専用の任意ロガー実装を差し込んだりできます。便利。

tech.guitarrapc.com

目次

ILambdaContext の実装クラスを作る

前回のエラーは ILambdaContext の実装がなく、nullを渡していたためでした。ということで context をザクッと1ファイルに書いちゃいます。namespace はご自分の関数でも shared class にしてください。

gist.github.com

これでローカル実行用の Program.cs から Lambda関数呼び出し時に context が渡せ、Loggerの中でコンソールに書き出します。

gist.github.com

ローカル実行して上手く通っていますね?

ちなみに、AWS Lambda Project with Tests でプロジェクト追加したときのテストクラスではTestClientContext が用意されており、その中の TestLambdaLogger は 以下のような実装になっています。大体同じですね。

  public class TestLambdaLogger : ILambdaLogger
  {
    /// <summary>
    /// Buffer for all the log messages written to the logger.
    /// </summary>
    public StringBuilder Buffer { get; } = new StringBuilder();

    /// <summary>Write log messages to the console.</summary>
    /// <param name="message"></param>
    public void Log(string message)
    {
      this.LogLine(message);
    }

    /// <summary>Write log messages to the console.</summary>
    /// <param name="message"></param>
    public void LogLine(string message)
    {
      this.Buffer.AppendLine(message);
      Console.WriteLine(message);
    }
  }

そのため テストクラスではvar context = new TestLambdaContext(); のままでok です。

まとめ

簡単な実装でずいぶんと楽になります。インターフェースなのでお好きな実装でどうぞ。

リポジトリも更新してあります。

github.com

Amazon Athena で S3 Access Log を分析する

re:Invent 2016 は AWS の利用が一段回上に上がる素晴らしい発表が多かったです。さて今回取り上げるのは Athena です。

すでに素晴らしい資料があるのでそちらをご参照ください。ざっくり個人的な理解は、Google BigQuery の AWS版 という雑な印象です。

www.slideshare.net

data.gunosy.io

今回は S3 を Static website hosting している場合の S3 アクセスログを Athena で分析してみましょう。ELBとか CloudTrail はあるのに、S3 Access Log の記事見つからないものですね。

なお、この記事は以下のフォーマットを対象にしているため、フォーマットが将来変更された場合に動かないことがありえます。あらかじめご了承ください。

docs.aws.amazon.com

1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd hogemogeTestTest.contoso.com [07/Dec/2016:05:59:12 +0000] 191.1.1.2 1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd 667C2B96205F407E REST.GET.VERSIONING  - "GET /hogemogeTestTest.contoso.com?tagging HTTP/1.1" 404 NoSuchTagSet 285 - 45 - "-" "S3Console/0.4" -

目次

S3 バケットの状態

Athena は US East (Virginia)US West (Oregon) のみで選択できるサービスですが、対象となるS3 はその制約がありません。当然ですね。

さて、Athena の対象となる S3 バケットを Static website hosting として用意します。

Logging を有効にしておいて事前にログが吐かれた状態にしましょう。今回は、BucketName/logs に吐くようにしました。

このような構成だと思ってください

バケット名 ログパス
hogemogeTestTest.contoso.com logs/

この状態で logs フォルダをみると.... 絶望ですね。これを手やAPI で見ようというのは人間のやることではなくなりました。Athena を使うのです。

Athenaの処理

さて分析対象が定まったので Athena にDBとテーブルを用意して分析しましょう。作成する内容は次の通りですね。

対象バケット名 Athena データベース名 Athena テーブル名
s3://hogemogeTestTest.contoso.com/logs/ s3_AccessLogsDB hogemogeTestTest_contoso_com

対象バケット名ですが、作成ウィザードではリージョンが必要そうなブランク欄の薄い灰色表示ですが、リージョンは不要です。

では早速見てみましょう。今回は パーティションを省いてクエリ (HiveQL) のみで一気に行きます。

データベースの作成

まずは、Athena でS3AccessLogに関するデータベース s3_AccessLogsDB を作成します。すでにデータベースがあるならここはスキップしていただいてok です。

gist.github.com

実行を待ってねと出て上手く作成できました。

ちなみにCREATE DATABASE 文と 後述する CREATE EXTERNAL TABLE 文は同時に投げることはできません。1つずつ投入、実行してください。

テーブル定義の作成

お次はテーブル定義です。

S3 Access Log のログ形式は次の通りです。

docs.aws.amazon.com

さてフォーマットのシリアライザはどれが使えるでしょうか。

一見 org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe がよさそうですがクォートのサポートがないので、今回は org.apache.hadoop.hive.serde2.RegexSerDe を使ってごりごりやります。

gist.github.com

2点だけ説明をした方がよさそうなので触れておきます。

  1. s3_accesslogsdb.hogemogeTestTest_contoso_com は、<データベース名>.<テーブル名> としています。お好きに書き換えてください
  2. Location にある s3://hogemogeTestTest.contoso.com/logs/ が対象の S3バケット名です

これで実行すると、上手くテーブルが作られたはずです。

テーブルプロパティをみてみます。

適当なクエリで中身を見てもうまくいっていますね。

SELECT * FROM hogemogetesttest_contoso_com limit 10;

あとは任意のクエリで分析しましょう。

dev.classmethod.jp

まとめ

Athena がきたことでようやく S3 にエコシステムが出来てよかったですね!

ちょくちょく使ったりデータ量が多いなら パーティションを貼っておいた方が精神安定上いいと思います。

.NET Core on Lambda で Github 連携をSlack に飛ばしてみよう

私の中で Serverless ななんとかネタで鉄板なのが、Github の PRやIssue などの通知連携です。日々使っているものなのでついつい。

もちろん過去にも Lambda + Node.js や Azure Functions で作っています。

tech.guitarrapc.com

では .NET Core でもやってみましょう。

目次

連携の流れ

Azure Functions では、Azure Functions の Secret と URL を直接 Github Webhook に入力して連携しました。ただ入力するだけ、簡単ですね。

AWS Lambda の場合は、Webhook を API Gateway で受けるようなことはせず、Amazon SNS を使って連携することが定番になっているように思います。Amazon SNS にするとAPI Gatewayと違ってレスポンスの受け口を作ったり管理する必要がほぼないのである意味ではとても楽です。*1

ということで今回もこの構成で行ってみましょう。Amazon SNS ではなく API Gateway で作っていたこともあったのですが余り楽しくないので推奨しません。

参考

Node.js で行われていますが、構成は同じです。

qiita.com

下準備

いきなりコードに行きたいところですが、まずは下準備からです。

Slack で Incoming Webhook を作成

Lambda の受け口となる Webhookの口を Slack に作成します。通知先の Channel を選んで適当にどうぞ。

slack.com

これで Lambda が送る先の Incoming Webhook URL が確定しました。URL は後ほどLambda の環境変数に仕込んでおくことになります。*2

SNS の Topic 作成

次にGithub が投げる先となる SNS の受け口 Topic を作成しておきます。作成する AWS Lambda と同一Region で作っておくといいでしょう。今回は ap-northeast-1 で作ります。

SNS から Create Topic を選んで

他の Topic と区別が付くようにつけておきましょう。

これで Topic を一意に示す Topic ARN が定まります。Github で入力するので把握しておきましょう。

今回は仮に arn:aws:sns:ap-northeast-1:123456789012:githublambdawebhook だったとします。

Github から SNS に投げる限定権限の IAM 作成

Github の Application Integrations を使った Amazon SNS では IAM の access key / secret key を直接入れることになります。

ということで指示に従って SNS を投げることができるsns:Publish権限だけの IAM ユーザーを作成しておきます。

ポリシーの Resource には、先ほどの Sns ARN arn:aws:sns:ap-northeast-1:123456789012:githublambdawebhook を指定します。

あとはIAM User作成時、あるいは後ほどでも Access Key / Secret Key を取得します。Secret Key は作成時の画面でしか確認できないのでお気をつけて。

仮に、Access Key を accesskey12345、Secret Key を secretkey12345 と表現します。

Github に Amazon SNS との連携を組む

では、Github の連携させたいリポジトリの Settings 画面に行きましょう。

メニューから Integrations & services を選択します。

サービス一覧から Amazon SNS を選びます。

あとは、AWS で作成した SNS Topic の情報、SNS送信専用 IAM Userの Access Key、Secret Key を入れます。

項目 入力する内容 入力値の例
Aws Key SNS 専用 IAM User の Access Key accesskey12345
Sns topic 作成した Sns の TOPIC Arn arn:aws:sns:ap-northeast-1:123456789012:githublambdawebhook
Sns region 作成した Sns の Region。SNS Topicに書いてありますが入れます ap-northeast-1
Aws secret SNS 専用 IAM User の Secret Key secretkey12345

Integrations の Event を指定する

Github の Webhook を使わない Integrations & services で連携する場合の最大のめんどくさいポイントはこれです。

Github API の hook.json にあるとおり、Amazon SNS の デフォルト Event は Push のみです。

この変更は Web request でしかできないのでしかたいので cURL でやります。*3

対象とするイベントは、Webhooks で適当に決めます。

今回はIssue 処理時Issue コメント時Pull Request 処理時Pull Request レビューサブミット時Pull Request レビューコメント時 とします。

gist.github.com

API経由で変更をかける際に利用する Application Token は、Github ユーザー プロファイルから作成できます。admin:repo_hookadmin:org_hook を有効にしておけばいいでしょう。

qiita.com

Webhook の ID は、URLに表示されています。

events が期待通りに設定されましたか? cURL の設定時のレスポンスで確認できます。

残りは Amazon SNS と Lambda のつなぎこみですが、Visual Studioから Lambda コード反映時にまとめて設定できるので後述します。

お疲れさまでした、長い下準備もおしまいです!

C# で Github Webhook を受ける

Azure Functions で書いていたコードを使って書いてみましょう。

サンプルJSON

今回サンプルで利用した、SNS から送ってくるJSON は次のフォーマットです。これは Issue のサンプルなので、コードもIssue を想定したものとします。

gist.github.com

C# コード例

Issue が書かれたらSlack に飛ばすサンプルです。ほぼ Azure Functions のコードと違いはありません。

違いは2点のみです。

  • フィールドに AWS Lambdaの環境変数から SlackWebhookUrl というキーを探して URL をstringとして取得するようにします。パスワードとかそういうのは Lambda の環境変数で KMS 使って暗号化がいいですね
  • SNS Response のデシリアライズ処理が入っています

当たり前の注意点だけ

  • X-Github-Event がプロパティ名に利用できないので JSONPropertyとして指定しておきます
  • ただし、github のイベントJSONは issue だったり PR だったりでフォーマットが変わるので、そこは AzureFunctiosn 同様に dynamic で受けておきます

gist.github.com

今回は Issue に限定しましたが、適当に他のイベント対応も switch に達せばいいでしょう。*4

xUnit テスト

ごく単純な実行テストです。様々なイベントに応じた JSON パターンをここでテストするといいでしょう。

gist.github.com

ローカル実行

ローカル実行する場合は、buildOptions に emitEntryPointを true と設定してProgram.cs を適当に書きましょう。

  "buildOptions": {
    "emitEntryPoint": true
  },

gist.github.com

これで普通のコンソールアプリケーションとして検証できます。

アップロード と 環境変数 と イベントソース設定

そろそろ CI ほしいですが、まだいったんVisual Studio でやります。適当にアップロード先 Function を作成します。

冒頭で作成した Slack Incoming Webhook のURL を環境変数に入れるのもここでできます。便利。

ついでに、Amazon SNS を起点に Lambda を実行するので、Event Sources も設定してしまいます。

もちろん Web Console 画面からやってもok です。

テスト実行

ローカル実行やテストする場合は、OSの環境変数に Lambda に設定する環境変数と同じものを仕込んでおきます。

Isssue を適当に立ててコメントしてみます。

うまく Slack に通知されましたね。

テスト中の実行グラフも出ています。

失敗や成功のログも Cloud Watch Logs に出力されています。

まとめ

Azure Functions と比べると SNS が増えてるだけなのに手間が大きく違いますね!シカタナイとは言え、Github とのつなぎこみに特化対応している Azure Functions はこの面で圧倒的に便利です。ただ、.csx でやり続けるのとどっちがいいかというと、どうでしょうかねぇ。

さて、今回のサンプルも挙げておきます。参考になれば幸いです。

github.com

*1:最高とは思いません。IAM作るのいやぽよ

*2:Azure Functions の AppSettings と同様です

*3:別に PowerShell でも C# でもいいです

*4:大半のケースはたいした処理しないでしょう。

.NET Core on Lambda で Slack Slash Command を作ってみよう

さて、前回、前々回と .NET Core on Lambda の下回りを見てきました。

tech.guitarrapc.com

tech.guitarrapc.com

大事なパッケージ周りやデプロイについては別の機会にするとして、そろそろ簡単なコマンドをWebhook で投げて返してみましょう。ついでに Lambda の環境変数も触っておきます。

目次

サンプル

今回実装のサンプルとするのは、Azure Functions の時に見かけた Slack の Slash Command を作ってみたという例です。Slack の Slash Command 入力を起点に、Slack (Post) -> Azure Functions (コード実行) -> Slack という流れです。

blog.xin9le.net

API Gateway + AWS Lambda

AWS Lambda はステートを持たないイベントドリブンなアプリケーション実行環境です。そのため、Azure Functions と異なり単体では外部Webリクエストを受け付けたり返却することはできません。

そこで、Slack -> Azure Functions / AzureFunctions -> Slack に該当する接続部分にAPI Gateway を用いるのが定石となっています。今回もそれに従いましょう。

AWS Lambda のコード作成

.NET Core on AWS Lambda の最も使いやすいポイントは、ローカル実行環境の整備 と ふつうのC# (.NET Core)を普通にかけることです。今回は、Slack Slash Command が送られてきたら、Hello from Lambda .NET Core. と返させてみます。

gist.github.com

コードはごく単純です。

Slack Slash Command で返ってくるJSON を SlackSlashCommand クラスでデシリアライズしてLambda関数に受けます。あとは、&でつながれている入力文字列を分割して、=で区切られた Key/Value から 値を取得しています。

続いて、Lambdaの環境変数に埋めたトークンと送信されてきたトークンの一致を検証しています。本来なら、処理内部ではなく前段のAPI Gateway 時点ではじきたいのですが、API Gateway の APIキー指定が Header 利用で、Slack Slash Command の送信ヘッダをいじれないためシカタナイです。

最後に、Slack に対してレスポンスを返しています。

AWS Lambda のコードをアップロードと環境変数操作

Visual Studio からのアップロードにて、Lambda関数の環境変数も併せて調整可能です。

今回は、SlackSlashCommandWebhook としてLambda関数を作成しました。

Azure Functions でもそうでしたが、環境変数を利用することでコードから機微情報を除外できるのでとてもいいです。特に AWS Lambda は KMS による暗号化もかかっていて一定の安心感があります。環境変数については参考にできるサイトがいくつもあります。より詳しく知りたい方はこちらへ。

dev.classmethod.jp

API Gateway に API を作成

続いて、Slack の Slash Command (という名の Webhook) を受けるため API Gateway で APIを作成します。Slack の Slash Command は POST でリクエストできるので、GET ではなく POST にします。

先ほど作成した、Lambda関数 をバックエンドに指定しておいてください。これで次のような API Gateway の設定ができたと思います。

さて、Slack Slash Command は JSON ではなく application/x-www-form-urlencoded としてリクエストしてくるのですが、Lambda関数は JSON で入力されることを期待しています。そこで、Integration Request を調整して、リクエストボディから JSONを抽出して Lambda 関数に渡します。具体的に、次の通り入力します。

Content-Type 処理
application/x-www-form-urlencoded { "body": $input.json("$") }

これでAPI Gateway の設定が出来ました。Deploy API でス任意の環境にデプロイしてみましょう。

デプロイすると、API のURL が定まります。

Slack の Slash Command を作成する

API Gateway の URL が定まったので、Slack に Slash Command を作成します。URL欄には、API Gateway で作成した APIのURL を入れます。

準備できましたか?

早速実行してみます。

うまくいきましたね!

まとめ

従来 Node.js や Python、Java でやっていたことは基本的に .NET Core でも同様にできるでしょう。手始めに Slackを用いましたが、別の様々なサンプルの移行が簡単にできます。当然、Azure Functions で実装していたものは、ほぼそのまま移行できます。次はそのあたりを。

今回のコードサンプルも追加しておきます。

github.com