tech.guitarrapc.cóm

Technical updates

Azure Functions で Asssembly.Location が正しくかえって来ない問題の対処

Azure Functions が6日ほど前に更新されて1.0.10690になってから、以下のエラーが発生する場合があります。

Can't create a metadata reference to an assembly without location.
  at Microsoft.CodeAnalysis.MetadataReference.CreateFromAssemblyInternal(Assembly assembly, MetadataReferenceProperties properties, DocumentationProvider documentation)
  at Microsoft.CodeAnalysis.Scripting.ScriptOptions.CreateReferenceFromAssembly(Assembly assembly)
  at Microsoft.CodeAnalysis.Scripting.ParameterValidationHelpers.<>c__DisplayClass4_0`2.<SelectChecked>b__0(T item)
  at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
  at Microsoft.CodeAnalysis.Scripting.ParameterValidationHelpers.AddRangeChecked[T](ArrayBuilder`1 builder, IEnumerable`1 items, String parameterName)
  at Microsoft.CodeAnalysis.Scripting.ParameterValidationHelpers.ToImmutableArrayChecked[T](IEnumerable`1 items, String parameterName)
  at Microsoft.CodeAnalysis.Scripting.ScriptOptions.WithReferences(IEnumerable`1 references)
  at Microsoft.CodeAnalysis.Scripting.ScriptOptions.WithReferences(IEnumerable`1 references)
  at Submission#0.<EvaluateCSharpAsync>d__3.MoveNext() in D:\home\site\wwwroot\CSharpScripting.csx:line 47

今回はこの発生原因と対処についてです。

目次

発生原因

最新バージョン 1.0.10690 で入った以下のコミットが原因です。

github.com

ということで、残念ながら現在 Azure Functions をコンソールから最新バージョン (1.0~) にするとエラーの起こっているバージョンになります。

現在のバージョンの確認

現在問題が起こるのかは、1.0未満なら Azure Functions の画面から確認できます。

1.0 、あるいはもっと正確に知りたい場合 Kudu を使います。

github.com

batコマンドだとこうですね。

type d:\local\config\applicationhost.config | findstr virtual | findstr Functions

表示された結果にバージョンが埋まっています。この場合なら 1.0.10690 となります。

<virtualDirectory path="/" physicalPath="D:\Program Files (x86)\SiteExtensions\Functions\1.0.10690" />

PowerShell でやりたい場合はこんな感じでどうでしょうか。

gist.github.com

問題が出るコード

Assembly.Load が正しい値を返さないのが問題です。Issue がすでに立っており、解決待ちステータスです。*1

github.com

今回私がひっかかったのが、Roslyn を使ったコード評価で独自dll を参照に追加するときでした。typeof(EnumerableExtensions).Assembly, の部分で独自dll のアセンブリを返していますがここでCan't create a metadata reference....エラーが出ます。

private static readonly Assembly[] DefaultReferences =
{
    typeof(Enumerable).Assembly,
    typeof(List<string>).Assembly,
    typeof(System.Net.Http.HttpClient).Assembly,
    typeof(EnumerableExtensions).Assembly,
};

https://github.com/guitarrapc/AzureFunctionsIntroduction/blob/master/CSharpScripting.csx#L39

対処

2つのワークアラウンドが提示されています。

  1. Azure Functions のバージョンをひとつ前に固定する
  2. 独自dllをFunctionフォルダ\bin ではない場所に配置する

今回私は 2の独自dll のパスを変更しています。修正PRは次のものです。

github.com

ワークアラウンドを見てみましょう。

(非推奨) Azure Functions のバージョンをひとつ前に固定する。

私個人としては非推奨です。

  1. バージョン追従がマニュアルになる
  2. 通常のフローではないバージョン管理が必要になる
  3. バージョン固定時に既存のFunction からFunctionが全部消える

特に、Function が全部消えるのは痛恨の罠です。Function Url というか code が作り直しになるので、外部連携している場合 URL 張り直しです。*2

一見コード修正もいらず最高かと思いましたがちょっと苦しいですね。

(推奨) 独自dllをFunctionフォルダ\bin ではない場所に配置する。

私個人としては推奨です。

過去には独自dll は {Function名}\bin\独自dll.dll 固定だったのですがどうやら制約はなくなったようです。reference には残っているので修正してほしいですね。

If you need to reference a private assembly, you can upload the assembly file into a bin folder relative to your function and reference it by using the file name (e.g. #r "MyAssembly.dll"). For information on how to upload files to your function folder, see the following section on package management.

docs.microsoft.com

修正も参照先アセンブリ #r の変更のみなので非常に容易です。

github.com

修正後

PRマージ前の問題発生中はエラーが返っていました。

一方、ワークアラウンド2の対処で修正後は問題なくAssembly を読んでコード評価しています。よかったです。

まとめ

おそらく近日中に修正されると思うので心待ちにしましょう。

Azure は、AzureFunctions などのAppService チームの中の人がふつーに Twitter で補足、アドバイスをくださり 神がかっています。*3

というのは別にしても、熱意と対応の速さは素晴らしく、真摯になっている姿勢を心から尊敬するとともに見習いたいと思います。

*1:正しく直す前に修正する場合に備えて、Revert コミットも準備してくれています : https://github.com/Azure/azure-webjobs-sdk-script/pull/1074

*2:これは Azure Functions の設計上の難しポイントでもあります。API Gateayなどが必要なのはこういう問題の対処です

*3:神が降臨したとTwitter リプライのたびに拝んでいます

.NET Core on Lambda の CI を組んでみる

パッケージの利用、ローカルテスト、ビルド、Visual Studioからのデプロイまで来たのでローカル開発は問題なくできそうですね。ただCIがないままではチーム開発がしにくいです。

tech.guitarrapc.com

tech.guitarrapc.com

tech.guitarrapc.com

そこで今回は、.NET Core on Lambda をいい感じで CIで回す方法についてみていきます。

目次

先人の知恵

先人の知恵があります。Circle CI での例ですが、今回のベースになっています。

blog.shibayan.jp

今回は、Circle CI と Visual Studio Team Service (VSTS) を試してみましょう。

circleci.com

www.visualstudio.com

デプロイ方法

Cloud Formation や Terraform はちょっと重厚すぎる、Serverless Framework ほどではない。ぐらいの温度感で行きたいので、dotnet lambda を用いましょう。

dotnet lambda に関しては、コードをみるのもいいでしょう。

github.com

default-settings.json について

dotnet lambbda コマンドで渡す内容を事前に default-settings.json に定義しておけばずいぶんとシンプルになります。

今回はデフォルトのものにfunction-namefunction-role の 2パラメータを追加しています。この2つを足すことで、function名、roleが指定可能になりCIが捗ります。*1

  "function-name": "GithubWebhook",
  "function-role": "arn:aws:iam::ACCOUNTID:role/service-role/lambda_exec_role"

gist.github.com

.Net Core 1.0 の構築

CircleCI は Ubuntu 環境です。.NET Core は入っていないので入れましょう。

CI環境 .NET Core有無 (インストールが必要)
CircleCI なし (Ubuntu12 or 14)
VSTS Hosted Windows あり
VSTS Hosted Linux あり
VSTS Agent なし (環境に依存します)

Ubuntu それぞれの環境構築コマンドは以下にまとまっています。

Download .NET (Linux, macOS, and Windows)

ただし、ここのバージョンは .NET Core 1.1 です。現状の AWS Lambda が .NET Core 1.0 なので、対象パッケージを変えます。ちなみにこのままやると以下のようなエラーに会います。

github.com

.NET Core 1.0 の最新パッケージバージョンは以下のURLで確認できます。

Download .NET (Linux, macOS, and Windows)

Circle CI

Circle CI のアカウントを持っていない場合は作ってしまいます。リポジトリ側と Circle CI でそれぞれ設定しておくことがあるので見てみましょう。

(リポジトリ側作業) circle.yml の作成

CircleCI のビルドタスクは、circle.yml で記述できます。これをリポジトリの直下においておけば、CircleCI でビルドが走った時に自動的に利用されてるので記述しましょう。

今回は、circle.yml に以下のタスクを記述します。

  1. .net core 1.0 環境の構築
  2. dotnet restore のビルド前実行
  3. dotnet build によるコンパイルテスト
  4. dotnet test による実行テスト
  5. dotnet lambda deploy-function による AWS Lambda へのデプロイ

ざくっと書きました。

gist.github.com

CircleCI は、Ubuntu 14 で行きます。

(CircleCI作業) 環境変数

AWS Lambda で、環境変数にリポジトリへ入れたくない情報を入れました。CircleCI でテストするにあたり、同様にこれらの情報を環境変数に入れます。

qiita.com

(CircleCI作業) AWS Lambda への デプロイIAM User Access/Secret

AWS に、dotnet lambda deploy-function でデプロイするにあたり、Circle CIのAWS Permissions に 適切な Permission が付与された IAM User の Access Key/Secret Key を取得、設定します。ACCOUNT_ID にはご自分のIDをどうぞ。

今回は、Lambda Function の新規作成、更新を踏まえて以下の Permission にしました。

gist.github.com

ビルド実行

あとは、git push でトリガーされます。

dotnet lambda deploy-function をみてもいい感じですね。

Executing publish command
... invoking 'dotnet publish', working folder '/home/ubuntu/AWSLambdaCSharpIntroduction/AWSLambdaCSharpIntroduction/SlackSlashCommandWebhook/SlackSlashCommandWebhook/bin/Release/netcoreapp1.0/publish'
... publish: Publishing SlackSlashCommandWebhook for .NETCoreApp,Version=v1.0
... publish: Project LambdaShared (.NETStandard,Version=v1.6) was previously compiled. Skipping compilation.
... publish: Project SlackSlashCommandWebhook (.NETCoreApp,Version=v1.0) will be compiled because expected outputs are missing
... publish: Compiling SlackSlashCommandWebhook for .NETCoreApp,Version=v1.0
... publish: /home/ubuntu/AWSLambdaCSharpIntroduction/AWSLambdaCSharpIntroduction/SlackSlashCommandWebhook/SlackSlashCommandWebhook/Function.cs(25,35): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
... publish: Compilation succeeded.
... publish:     1 Warning(s)
... publish:     0 Error(s)
... publish: Time elapsed 00:00:01.6428709
... publish:  
... publish: publish: Published to /home/ubuntu/AWSLambdaCSharpIntroduction/AWSLambdaCSharpIntroduction/SlackSlashCommandWebhook/SlackSlashCommandWebhook/bin/Release/netcoreapp1.0/publish
... publish: Published 1/1 projects successfully
Changed permissions on published dll (chmod +r SlackSlashCommandWebhook.dll).
Changed permissions on published dll (chmod +r Amazon.Lambda.Core.dll).
Changed permissions on published dll (chmod +r Amazon.Lambda.Serialization.Json.dll).
Changed permissions on published dll (chmod +r Amazon.Lambda.TestUtilities.dll).
Changed permissions on published dll (chmod +r Newtonsoft.Json.dll).
Changed permissions on published dll (chmod +r System.Runtime.Serialization.Primitives.dll).
Changed permissions on published dll (chmod +r LambdaShared.dll).
Zipping publish folder /home/ubuntu/AWSLambdaCSharpIntroduction/AWSLambdaCSharpIntroduction/SlackSlashCommandWebhook/SlackSlashCommandWebhook/bin/Release/netcoreapp1.0/publish to /home/ubuntu/AWSLambdaCSharpIntroduction/AWSLambdaCSharpIntroduction/SlackSlashCommandWebhook/SlackSlashCommandWebhook/bin/Release/netcoreapp1.0/SlackSlashCommandWebhook.zip
... publish:   adding: SlackSlashCommandWebhook.dll (deflated 59%)
... publish:   adding: SlackSlashCommandWebhook.pdb (deflated 46%)
... publish:   adding: Amazon.Lambda.Core.dll (deflated 57%)
... publish:   adding: Amazon.Lambda.Serialization.Json.dll (deflated 56%)
... publish:   adding: Amazon.Lambda.TestUtilities.dll (deflated 61%)
... publish:   adding: Newtonsoft.Json.dll (deflated 60%)
... publish:   adding: System.Runtime.Serialization.Primitives.dll (deflated 48%)
... publish:   adding: LambdaShared.dll (deflated 60%)
... publish:   adding: LambdaShared.pdb (deflated 43%)
... publish:   adding: SlackSlashCommandWebhook.deps.json (deflated 72%)
... publish:   adding: SlackSlashCommandWebhook.runtimeconfig.json (deflated 22%)
Created publish archive (/home/ubuntu/AWSLambdaCSharpIntroduction/AWSLambdaCSharpIntroduction/SlackSlashCommandWebhook/SlackSlashCommandWebhook/bin/Release/netcoreapp1.0/SlackSlashCommandWebhook.zip).
Updating code for existing function 

VSTS

Windows / Linux ともに、Hosted Agent では2つが障壁となってデプロイできません。これらは自前環境に Agent を入れることで回避ができます。

問題 Agent の場合の解決方法
~/.aws/config が作成できない 作成してください。こんなことができないようになっているのは仕方ないとはいえ、見るところ間違えてますかねぇ。
VSTS UI 上の Environment Variables がコードに渡らない OS の環境変数に入れてください。こんなことができな(略

ということで、VSTS で AWS Lambda をCI する場合は、hosted agent ではなく、自前エージェントでどうぞ。

~/.aws/config が作成できない

単純にこれをやるだけなんですが、厳しい...です。

docs.aws.amazon.com

結果、default profile が見つからず dotnet lambda が実行できない、と。

******************************************************************************
Starting: Run echo
******************************************************************************
==============================================================================
Task         : Command Line
Description  : Run a command line with arguments
Version      : 1.1.1
Author       : Microsoft Corporation
Help         : [More Information](https://go.microsoft.com/fwlink/?LinkID=613735)
==============================================================================
/bin/echo '[default]' > ~/.aws/config
'[default]' > ~/.aws/config
******************************************************************************
Finishing: Run echo
******************************************************************************
******************************************************************************
Starting: Run ls
******************************************************************************
==============================================================================
Task         : Command Line
Description  : Run a command line with arguments
Version      : 1.1.1
Author       : Microsoft Corporation
Help         : [More Information](https://go.microsoft.com/fwlink/?LinkID=613735)
==============================================================================
/bin/ls ~/.aws/config
/bin/ls: cannot access '~/.aws/config': No such file or directory
/bin/ls failed with return code: 2
/bin/ls failed with error: /bin/ls failed with return code: 2
******************************************************************************
Finishing: Run ls
******************************************************************************```

Error retrieving configuration for function GithubWebhook: Profile default cannot be found
/usr/bin/dotnet failed with return code: 255
/usr/bin/dotnet failed with error: /usr/bin/dotnet failed with return code: 255
VSTS UI 上の Environment Variables がコードに渡らない

単純にC#コードから、Environemnt.GetVariable() で取得できると思ったんですが... CircleCI でできてVSTS Hosted Agent でできないのは何かやり方おかしいんですかねぇ。

www.visualstudio.com

Hosted agent では、環境依存を環境変数に逃がしている場合に、.Tests でコードを通せなくなっています。

==============================================================================
Task         : .NET Core (PREVIEW)
Description  : Build, test and publish using dotnet core command-line.
Version      : 0.1.0
Author       : Microsoft Corporation
Help         : [More Information](https://go.microsoft.com/fwlink/?linkid=832194)
==============================================================================
/usr/bin/dotnet test /opt/vsts/work/1/s/AWSLambdaCSharpIntroduction/GithubWebhook/GithubWebhook.Tests/project.json --configuration release
    GithubWebhook.Tests.FunctionTest.TestValidSnsGithubWebhookMessage [FAIL]
SUMMARY: Total: 1 targets, Passed: 0, Failed: 1.
Project LambdaShared (.NETStandard,Version=v1.6) was previously compiled. Skipping compilation.
Project GithubWebhook (.NETCoreApp,Version=v1.0) was previously compiled. Skipping compilation.
Project GithubWebhook.Tests (.NETCoreApp,Version=v1.0) will be compiled because inputs were modified
Compiling GithubWebhook.Tests for .NETCoreApp,Version=v1.0
Compilation succeeded.
    0 Warning(s)
    0 Error(s)
Time elapsed 00:00:01.1756032
 
xUnit.net .NET CLI test runner (64-bit .NET Core ubuntu.16.04-x64)
  Discovering: GithubWebhook.Tests
  Discovered:  GithubWebhook.Tests
  Starting:    GithubWebhook.Tests
GitHub WebHook triggered
      System.AggregateException : One or more errors occurred. (An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.)
      ---- System.InvalidOperationException : An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.
      Stack Trace:
           at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
           at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
        /opt/vsts/work/1/s/AWSLambdaCSharpIntroduction/GithubWebhook/GithubWebhook.Tests/FunctionTest.cs(21,0): at GithubWebhook.Tests.FunctionTest.TestValidSnsGithubWebhookMessage()
        ----- Inner Stack Trace -----
           at System.Net.Http.HttpClient.PrepareRequestMessage(HttpRequestMessage request)
           at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
           at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
           at System.Net.Http.HttpClient.PostAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken)
        /opt/vsts/work/1/s/AWSLambdaCSharpIntroduction/GithubWebhook/GithubWebhook/Function.cs(81,0): at GithubWebhook.Function.<FunctionHandlerAsync>d__1.MoveNext()
  Finished:    GithubWebhook.Tests
=== TEST EXECUTION SUMMARY ===
   GithubWebhook.Tests  Total: 1, Errors: 0, Failed: 1, Skipped: 0, Time: 0.545s
Dotnet command failed with non-zero exit code: 1.

まとめ

CircleCI は流石、圧倒的な構築の容易さがあります。VSTS は、まぁシカタナイ(? 見るところがおかしいような気がするのですが...)

.NET Core on AWS Lambda は、.NET Core なだけあっていい感じに Linux 上でビルドできるので、CircleCI便利です。オススメ。

今回の内容もリポジトリに置いておきます 。

github.com

*1:省くと初めて作成するときに対話プロンプトで聞かれて困ったります

.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 にエコシステムが出来てよかったですね!

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