ASP.NET Coreの組み込みWebサーバーのKestrelはUNIXドメインソケット(UDS)で待ち受けができる、と知ったのでやってみた。
UNIXドメインソケットは同一ホストでプロセス間通信をするもの。リバースプロキシ(Webサーバー)とASP.NET Coreアプリケーションを同じホストで実行する。両者をDockerコンテナとして実行し、UNIXドメインソケットをDockerのボリューム機能で共有する。Webサーバーは触ってみたかったのでH2Oにした。
やってみた環境
> chcp 437
> systeminfo | findstr /B /C:"OS Name" /C:"OS Version"
OS Name: Microsoft Windows 10 Pro
OS Version: 10.0.16299 N/A Build 16299
> dotnet --version
2.1.2
> docker --version
Docker version 17.09.1-ce build 19e2cf6
Docker ImageはDocker Hubにある公式イメージを使った。(2017/12/13でのlatest)
素のASP.NET Coreアプリケーションを用意する
dotnet new
コマンドの「web」テンプレートでHello Worldと返すだけのアプリケーションが作れる。アプリケーションの名前は「myapp」とする。
> mkdir myapp
> cd myapp
> dotnet new web
Program.csのBuildWebHostメソッドでASP.NET CoreのWebサーバー(Kestrel)がセットアップされる。WebHost.CreateDefaultBuilder
の中でUseKestrel、UseIISIntegrationが呼ばれ、Kestrelの待ち受けが構成される。今回はCreateDefaultBuilderの後でさらに追加でUseKestrel
を呼んでUNIXドメインソケットでのListenを追加することにした。UNIXドメインソケットのパスは「/tmp/myapp/kestrel.sock」にした。
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel(options =>
{
options.ListenUnixSocket("/tmp/myapp/kestrel.sock");
})
.UseStartup<Startup>()
.Build();
}
公式ドキュメント: ASP.NET Core でのホスティング
WebHost.CreateDefaultBuilderのソースコード: MetaPackages/WebHost.cs at rel/2.0.0 · aspnet/MetaPackages · GitHub
ASP.NET Coreアプリケーションを実行する
dotnet publish
コマンドでビルドする。docker run
でアプリケーションを実行する。このときコンテナに2種類のボリュームをマウントする。一つはアプリケーションのdllを含んだホストディレクトリと対応したボリューム。もう一つはUNIXドメインソケットをコンテナ間で共有するためのホストディレクトリと対応しないボリューム。
> dotnet publish
> cd bin\Debug\netcoreapp2.0\publish
> docker run -d --rm -v /tmp/myapp -v %CD%:/app -w /app --name app microsoft/aspnetcore dotnet myapp.dll
docker run |
|
--name app |
コンテナの名前をappとする |
-v /tmp/myapp |
UNIXドメインソケットを作るディレクトリをボリュームとしてマウントする |
-v %CD%:/app |
ビルド結果をフォルダごとDockerコンテナの/app ディレクトリとしてマウントする |
-w /app |
コンテナの作業ディレクトリを/app にする |
dotnet myapp.dll |
コンテナ実行時のコマンドと引数 |
Docker run リファレンス docs.docker.jp
コンテナでデータを管理する
待ち受けを確認する
アプリケーションがUNIXドメインソケットで待ち受けしているか確認する。コンテナの中でcurl
を使う。Hello Worldが返ってくればOK。
> docker exec -it app bash
# curl --unix-socket /tmp/myapp/kestrel.sock http://localhost/
curlでunix domain socket経由アクセスする
Can cURL send requests to sockets? - Super User
curl 7.50以降ではURLにホスト名を含めてよくなったらしい
リバースプロキシ
H2Oの設定ファイルを作る
yamlの設定ファイルを作る。ファイル名はh2o.confとする。接続を試したいだけなのでuser:root
とする。
hosts:
default:
listen:
port: 8080
paths:
/:
proxy.reverse.url: "http://[unix:/tmp/myapp/kestrel.sock]/"
access-log: /dev/stdout
error-log: /dev/stderr
user: root
公式ドキュメント: Reverse Proxy · h2o/h2o Wiki 、Proxy Directives - Configure - H2O
今回のやりかたでは、ASP.NET Coreはroot
ユーザーでUNIXドメインソケットを作る。H2Oもroot
で実行しないとUNIXドメインソケットが読み書きできない。ちゃんと使おうと思ったらきちんとユーザーを作って設定しなければならないだろう。
H2Oを実行する
--volumes-from
で「app」コンテナのボリュームを共有する。 これで「/tmp/myapp」ディレクトリが2つのコンテナ間で共有される。
h2o.confファイルをコンテナの「/etc/h2o/」ディレクトリに置く。ファイル1つでもボリュームマウントできるが、今回はdocker cp
を使ってファイルをコンテナにコピーした。
>docker create --rm --volumes-from app -p 8080:8080 --name h2o lkwg82/h2o-http2-server
>docker cp h2o.conf app:/etc/h2o/h2o.conf
>docker start h2o
できあがり
Dockerホスト1のブラウザでhttp://localhost:8080にアクセスするとアプリケーションからの応答が返ってくる。
手軽にできるようにDocker HubのイメージのDockerfileを見てdocker runのオプションを決めた。docker build のコンテキストと、DockerfileでのCOPYがこのイメージの肝。DockerfileはDockerデーモンが実行するので、docker buildしたクライアントとは見えるファイルが違う。そのためのコンテキスト。
参考にした: Dockerfileを書く時の注意とかコツとかハックとか | kim hirokuni
おまけ1
H2Oのコンテナの中でUNIXドメインソケットへアクセスできているか確認する。このイメージはalpine linuxがベースになっている。bash
がないので、コマンドash
で対話する。パッケージマネージャーはapk
、update
してadd
でインストール。
> docker exec -it h2o ash
# apk update
# apk add curl
# curl --unix-socket /tmp/myapp/kestrel.sock http://localhost/
おまけ2
Kestrelの既定の待ち受けポートはTCP:5000。UseIISIntegration(IIS統合)が有効になるとKestrelの待ち受けポートが動的に決定され、IIS経由のリクエスト以外は拒否される。しかし、後で追加するUnixドメインソケットでの待ち受けには影響がないっぽい。
実行中のASP.NET CoreアプリケーションでWebサーバーの待ち受けがどうなっているのかIServerAddressesFeature
でわかる。
Kestrel web server implementation in ASP.NET Core | Microsoft Docs
var serverAddressesFeature = app.ServerFeatures.Get<IServerAddressesFeature>();
片付け
> docker rm -v -f h2o app