銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

ASP.NET Core で複数Webサーバーでセッションを共有するときは、IDistributedCacheとIDataProtectionに注意しないといけない話

ASP.NET Core Advent Calendar 13日目です。

qiita.com

初日にSignalRでエントリ書きましたが、もともと予定していたセッションについて簡単に追いかけてみましょう。ASP.NET Core でSessionを使う場合、まずはこのドキュメントを読むのがよいでしょう。

Managing Application State | Microsoft Docs

で、Sessionを使うにはまずMicrosoft.AspNetCore.Sessionパッケージを追加しろとあります。その後のNoteに書いてあることを無視して、とりあえずこのパッケージだけ追加して、Startup.csを次のように書いてみましょう。

public void ConfigureServices(IServiceCollection services)
{
    //services.AddDistributedMemoryCache();
    services.AddSession();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    app.UseSession();
}

これで実行するとこんなエラーが出ます。

Unhandled Exception: System.InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.Caching.Distributed.IDistributedCache' while attempting to activate 'Microsoft.AspNetCore.Session.DistributedSessionStore'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.Service.PopulateCallSites(ServiceProvider provider, ISet`1 callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.Service.CreateCallSite(ServiceProvider provider, ISet`1 callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetResolveCallSite(IService service, ISet`1 callSiteChain)

DIでIDistributedCacheを実装したサービスが見つからないということでエラーが出ます。無視したNoteにある通り、Sessionの機能はIDistributedCacheに依存して実装しているので少なくとも1つはその実装を追加しないといけません。実際に、Microsoft.AspNetCore.SessionMicrosoft.Extensions.Caching.Abstractionsに依存していることもパッケージからわかります。

www.nuget.org

開発用途限定ですが、IDistributedCacheを実装するパッケージがMicrosoft.Extensions.Caching.Memoryです。これは特に設定の必要もなく、次のようにコード一行追加するだけで動きます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddDistributedMemoryCache();
    services.AddSession();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    app.UseSession();
}

この開発用途限定と言っているパッケージ、名前の通りインメモリーにキャッシュを保存します。注意しないといけないのは複数サーバーでスケールアウトする場合で、当然各サーバーで別々にセッションをメモリーに保存するので、別のサーバーに行くとその前にアクセスしたセッションにはアクセスできません。というわけで本番環境では、IDistributedCacheを実装しているサービスを用意する必要があります。ASP.NET Coreチームから提供されているのはSQLServerとRedisに格納するものになります。

github.com

www.nuget.org

www.nuget.org

よし、これで複数サーバーでSticky Sessionの設定をせずともセッションを共有できる... という訳には実はいきません。そのことは実はだいぶ前のエントリで書いています。

tech.tanaka733.net

この記事に書いている通り、ASP.NET Coreではセッションデータを格納する際に暗号化しています。これはIDataProtectionというインターフェースを実装するサービスがその役割をになっており、デフォルトはマシンごとに固有の鍵をローカルファイルシステムにファイルとして保存します。さらにこのインターフェースはIDistributedCacheとは独立しているので、Redis Cachingを使ったとしてもIDataProtectionはデフォルト実装のままです。デフォルト実装の際に保存されるファイルパスはこのコードにあります。

DataProtection/FileSystemXmlRepository.cs at rel/1.1.0 · aspnet/DataProtection · GitHub

実際、このようなファイルが保存されています。

.aspnet/DataProtection-Keys/key-c28289ea-0e92-4e6b-94ff-6985ba6700ac.xml
$ cat .aspnet/DataProtection-Keys/key-c28289ea-0e92-4e6b-94ff-6985ba6700ac.xml 
<?xml version="1.0" encoding="utf-8"?>
<key id="c28289ea-0e92-4e6b-94ff-6985ba6700ac" version="1">
  <creationDate>2016-11-30T12:24:55.245682Z</creationDate>
  <activationDate>2016-11-30T12:24:55.220079Z</activationDate>
  <expirationDate>2017-02-28T12:24:55.220079Z</expirationDate>
  <descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
    <descriptor>
      <encryption algorithm="AES_256_CBC" />
      <validation algorithm="HMACSHA256" />
      <masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
        <!-- Warning: the key below is in an unencrypted form. -->
        <value>uT3l6pCiERM0Mwd6F8T4nOnnv9DmwSU6ii8AaZj6XG6qR0MorUN1Akxa5Lqivt8hKfu2L9TBmryC16I6BuOXrQ==</value>
      </masterKey>
    </descriptor>
  </descriptor>
</key>

というわけですので、IDataProtectionも必要に応じて共有できる実装に切り替えましょう。Data Protectionについては別のドキュメントで説明されており、デフォルトでは同じ物理パスを指定してもマシン固有になることも書かれています。

Configuring Data Protection | Microsoft Docs

上に引用した記事を書いた時点では、.NET Core on Liniuxで(比較的)容易に用意できる共有先はNFSくらいだったのですが、現在はRedis実装がPreviewですが用意されています。またAzureStorage向けのものも用意されています。

www.nuget.org

www.nuget.org

といったところで長くなってきたので、実際に動くコードで検証するのは続編でかきたいと思います。

Visual Studio から AzureやWindows以外のdocker containerで走るASP.NET Core をリモートデバッグする

空いていたので予定を早めて Visual Studio Advent Calendar 12日目のエントリを書きます。

qiita.com

Windows上のdockerやAzure Container ServicesであればVisual Studio Tools for Dockerを使えばVisual StudioからASP.NET Coreのアプリにリモートデバッグできます。Microsoft謹製のサービスしか接続できないので、なにか闇の技術でも使われているかと思われるかもしれないですが、そんなことはなくてGitHubで公開されているMIEngineというデバッグエンジンが利用されています。

github.com

この当たりの話は、Visual StudioからSSHを使えば、Linux上の.NET Coreプロセスにリモートデバッグできるというネタで何度かセッションでデモをしてきました。

というわけで、Visual StudioをインストールしているWindowsマシンからdockerにリモートでコマンドを実行できれば、他のdocker containerであってもリモートデバッグができます。今回は、OpenShift Container Platform上で動くASP.NET Coreアプリにリモート接続してみます。

続きを読む

Linux でも C# 7 を使いたい

C# Advent Calendar 10日目の記事です。

qiita.com

C# 7のリリースが近づいてきている(はず)のですが、専らC# 7の記事はVisual Studioで試しましたというのが多く、.NET Core on Linuxでは試せないのか?という疑問がありました。現時点では、.NET Core 1.0 previewとVisual Studio Code の最新版を使えばC# 7の機能を試すことができます。今回は、.NET Core SDK のDaily Build版とVS Code 1.8.0-insider+C#拡張の最新版1.5.3で試しました。

github.com

marketplace.visualstudio.com

$ dotnet --info
.NET Command Line Tools (1.0.0-preview5-004232)

Product Information:
 Version:            1.0.0-preview5-004232
 Commit SHA-1 hash:  355b6233cb

Runtime Environment:
 OS Name:     rhel
 OS Version:  7.3
 OS Platform: Linux
 RID:         rhel.7.2-x64
 Base Path:   /opt/dotnet/sdk/1.0.0-preview5-004232
続きを読む