ASP.NET Core アプリを Ubuntu サーバーで公開

ASP.NET Advent Calendar 2015 で書いた「[ASP.NET 5、.NET Core への移行作業をしてみた感想](http://qiita.com/creativewebjp/items/958af200fed9c7bd60ee)」の続きです。[統計メモ帳](https://ecitizen.jp)と [CreativeWeb](https://creativeweb.jp) の .NET Core 及び ASP.NET Core 1.0 への移行が完了したので、今年(2016年)の4月から Ubuntu サーバーで公開しています。実際に運用してみて Ubuntu サーバーでも特に問題はない状況なので、今後は Linux サーバーを使って ASP.NET アプリを運用していきたいと思っています。（5月16日にRC2（ツールについてはプレビュー1）が公開され、アプリもRC2に移行したので、記述を大幅に修正 2016年5月25日、6月27日にRTM（ツールについてはプレビュー2）が公開されたので修正 2016年7月1日）

## Ubuntu サーバーを使おうと思った理由

個人的な理由で、BizSpark が4月に切れるので、新しいサーバーを探す必要があったのですが、調べてみると Linux サーバー と Windows サーバーでは価格差が約2倍ありました、例えば、AWSの東京リージョンの場合で m4.large の リザーブドインスタンスで1年間の全前払いだと、Linux \$799 に対して、Windows Serverは \$1563 で年に約10万円も違ってきます。個人やスタートアップの場合だとこれだけの価格差になると採用は厳しいと思います。ASP.NET を人に勧めるのに、「さくらの VPS」 の月額 635円のでとりあえずは動作しますと言えた方がいいですよね。

4月の時点では、まだ RC1 で、時期的には少し早かったのですが、実際に試してみたら、Ubuntu サーバーでもかなりの部分が動作したので、 Ubuntu サーバーで運用することにしました。

ASP.NET Core RC2 が6月16日に公開されたので、アプリは RC2 に移行しました。RC1 から RC2 への移行は、実行環境が DNX から .NET CLI に変更されたことで破壊的変更が多かったのですが、修正は特定の部分に集中しているので移行は比較的スムーズでした。7月27日には、RTM が公開されたので、アプリは RTM に移行しています。この作業では、変更はそれほど多くなかったので1日もかからずに移行できました。

## Ubuntu サーバーで公開するための作業
Linux サーバーで公開する方法については、既に公式ドキュメントができているので。それを参考にして作業を進めました。

[Publish to a Linux Production Environment](https://docs.asp.net/en/latest/publishing/linuxproduction.html)

### 準備作業

Linux の操作をするために SSH クライアントを用意します。自分の場合は、Putty を使用しています。昔からあるソフトで、UIはかなり古く、操作にも癖があります。別のソフトを探そうと思ったですが、使えないわけではないし、Bash on Ubuntu on Windows がこの夏の Anniversary Update（8月2日開始）で提供されるので、それまでは Putty を使おうと思っています。

Putty を使って便利なのは、Pegeant を立ち上げておくと、FileZilla の SFTP が設定をしなくても使えることです。

SSH クライアントがインストールできたら Key ペアを作っておきます。Putty の場合は、puttygen.exe を使って作成することができます。ただ、今回は、AWS の EC2 を使用したので、AWS の方で Key ペアを作ってくれるので、自分で作成する必要はありませんでした。

### .NET Core のインストール
 
以下の公式ドキュメントを参考にしました。
https://www.microsoft.com/net/core#ubuntu

Ubuntu 14.04 の場合は、パッケージが既に完成していてリポジトリーを登録するだけなので、インストールは簡単で、次の4行でできてしまいます。

```
sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ trusty main" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893
sudo apt-get update
sudo apt-get install dotnet-dev-1.0.0-preview2-003131
```

Ubuntu 16.04 の場合は、2行目で trusty を xenial に変更するだけです。

アプリケーションの実行は次のとおりです。

```
dotnet restore
dotnet run
```

公開環境で使う場合には、以下のように発行をします。

```
dotnet publish --configuration Release
```

アプリケーションのdll ファイルを`dotnet app.dll` のように直接指定して実行させることもできるので、発行後はこちらのコマンドを使って実行させます。

発行する場合に注意をしておかないといけないのは、RC1 では、project.json の Exclude に記述しなければファイルは発行されていたのですが、RTM では、逆に発行したいファイルを以下のように project.json に記述する必要があります。

```js
  "publishOptions": {
    "include": [
      "wwwroot",
      "Views",
      "appsettings.json",
      "web.config"
    ]
  }
  ``` 

また、Linux サーバーで使う場合に少し注意をしておかないといけないのは、コンソールアプリケーションと違ってWebアプリケーションでは、既定では、カレントディレクトリがルートディレクトリになるということです。RC2 で新しく追加された Program.cs ファイルを見ると分かるのですが、`UseContentRoot(Directory.GetCurrentDirectory())`と指定されています。

```csharp
public static void Main(string[] args)
{
  var host = new WebHostBuilder()
    .UseKestrel()
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseIISIntegration()
    .UseStartup<Startup>()
    .Build();

  host.Run();
}
```


### Nginx でリバースプロキシサーバーをたてる
ASP.NET Core は、Kestrel サーバーが付属しますが、単機能のWebサーバーなので、公開時にはWindows サーバーであれば IIS を使いますが、Linux では Nginx を使うのが通常です。

Nginx をインストールする一番簡単な方法は、`sudo apt-get install nginx` で、Ubuntu が提供するパッケージをインストールできます。しかし、かなり古いバージョンになります。

新しいバージョンを使いたい場合には、Nginx が提供しているパッケージを使用します。自分の場合は HTTP/2 を使ってみたかったので、最新のMainline バージョンをインストールすることにしました。

Mainline バージョンを使用する場合は、以下のようにリポジトリを追加することで、Ubuntu のパッケージを利用するのと同じ `sudo apt-get install nginx` でインストールできるようになります。trusty は、14.04 LS のコード名で、ubuntu 16.04 では、trusty を xenial に変更します。詳しい説明は、[Nginx の公式ドキュメント](http://nginx.org/en/linux_packages.html)を見てください。

```
curl http://nginx.org/keys/nginx_signing.key | sudo apt-key add -
sudo sh -c "echo 'deb http://nginx.org/packages/mainline/ubuntu/ trusty nginx' >> /etc/apt/sources.list"
sudo sh -c "echo 'deb-src http://nginx.org/packages/mainline/ubuntu/ trusty nginx' >> /etc/apt/sources.list"
```
ASP.NET Core アプリケーションへのリバースプロキシの設定は基本的には、ASP.NET Core の公式ドキュメントのとおりしました。

```nginx:ecitizen.conf
server {
  listen 80;
  server_name ecitizen.jp;
  location / {
    proxy_pass http://localhost:5001;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection keep-alive;
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  }
}
```
ASP.NET Core のポート番号とを変更したい場合は、RC1の時は project.json の command セクションで変更していたのですが、RTM では、以下のように Main 関数の WebHostBuilder で、UseUrls を使って変更します。

```Program.cs
public static void Main(string[] args)
{
  var host = new WebHostBuilder()
    .UseKestrel()
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseIISIntegration()
    .UseStartup<Startup>()
    .UseUrls("http://localhost:5001")
    .Build();

  host.Run();
}
```
実際に設定してみると、公式ドキュメントでは `/etc/nginx/sites-available/default` を修正するようにと書いてありますが、`/etc/nginx/conf.d/` ディレクトリに設定ファイルをおくようになっていました。

nginx の停止と起動は以下のように行います。

```
sudo service nginx stop
sudo service nginx start
```

nginx の設定を変更したときには、以下のコマンドで設定の検証をして

```
sudo nginx -t
```

結果がOKであれば、以下のコマンドで設定を読み込ませます。そうすれば、nginx を再起動する必要はありません。

```
sudo nginx -s reload
```

Nginx に関しては、サーバ証明書をインストールして、HTTPSを設定しました。また、HTTP/2 も有効にしています。これらのことを書いていると長くなるので別の機会に書きたいと思います。


### Kestrel サーバーをデーモン化してモニタリングする

IIS と違って Nginx には、Kestrel を管理する機能がありません。そのため、安定した運用していくためには、エラーでサーバーが停止したときに自動的に起動するような仕組みが必要になります。そのためのツールはいろいろあるようですが、今回は、マニュアルのとおり supervisor を利用しました。

supervisor のインストールは、普通に `sudo apt-get install supervisor` でおこないました。また、設定ファイルは、以下のようにマニュアルを参考にして設定しています。 

```ini:/etc/supervisor/conf.d/ecitzen.conf
[program:ecitizen]
directory=/var/aspnet/ecitizen
command=dotnet /var/aspnet/ecitizen/ecitizen.dll
autostart=true
autorestart=true
stderr_logfile=/var/log/ecitizen.err.log
stdout_logfile=/var/log/ecitizen.out.log
environment=Hosting__Environment=Production
user=www-data
stopsignal=INT
```

supervisor の停止と起動は以下のように行います。

```
sudo service supervisor stop
sudo service supervisor start
```

特定のサービスのみを停止、起動したい場合も多いと思いますが、その場合は、上の例だと以下のコマンドでできます。

```
sudo supervisorctl stop ecitizen
sudo supervisorctl start ecitizen
```

## ASP.NET Core + Linux のメリットとデメリット

RC2 になって、コンソールアプリケーションは大きく改善されています。まず、ファイル容量が小さくなったことです。そして、事前コンパイルされているので、起動時間が短くなりました。コンソールアプリケーションに関しては、.NET CLI への変更は大きな効果があったと思います。

.NET Core アプリには、Portable Apps と Self-contained Apps の２種類があり、Self-contained Apps の方は .NET Core ランタイムをアプリ内に含むため独立した環境で動作させることができます。VS 付属のテンプレートだと Portable Apps になります。Self-contained Apps を作成するには、コンソールアプリケーションであれば、付属のテンプレートだと次のようになっていますが、

``` project.json
{
  "version": "1.0.0-*",
  "buildOptions": {
    "emitEntryPoint": true
  },

  "dependencies": {
    "Microsoft.NETCore.App": {
      "type": "platform",
      "version": "1.0.0"
    }
  },

  "frameworks": {
    "netcoreapp1.0": {
      "imports": "dnxcore50"
    }
  }
}
```
次のように、"type": "platform" を消して、その代わりに runtimes を定義します。
 
``` project.json
{
  "version": "1.0.0-*",
  "buildOptions": {
    "emitEntryPoint": true
  },

  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.0"
    }
  },

  "frameworks": {
    "netcoreapp1.0": {
      "imports": "dnxcore50"
    }
  },

  "runtimes": {
    "win10-x64": {},
    "ubuntu.14.04-x64": {}
  }
}
```
Self-contained Apps だと exe ファイルが生成されるので、そのまま実行することができます。native コンパイルではないので、ファイル数が多く容量も最低でも50MBを超えますが、単体で動作するので、レンタルサーバーや AWS Lambda でも動かすこともできるのではないかと思っています。

ASP.NET Core の問題点としては、ライブラリーの対応があまり進んでいないことです。しかし、RTM が公開されたことで、今後は、.NET Core への対応が進んでいくと思われます。ASP.NET Core のメリットは、マルチプラットフォームに対応し従来の ASP.NET より軽快に動作することなので、今後に期待したいと思います。

Windows ではなく Linux で ASP.NET Core を使うことについては、それほど多くの Linux の機能を使うわけではなく Nginx の設定ができればいいだけなので、ハードルはそれほど高くありません。また、今は Linux を使っているけど、過去には C# を使っていたことがあるという人にもいい機会だと思うので、ASP.NET Core を一度試してみてください。