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

ASP.NET Core が公開されて、ASP.NET も公式に Linux サーバーで動作するようになりました。ASP.NET Core 1.0 がリリースされたのは、昨年の6月28日なので、リリースされてから半年を過ぎましたが、まだまだ Windows でだけ使うのであれば、従来の.NET Framework を使った方が便利です。2倍近いAPIが利用可能になる .NET Standard 2 に対応し、.NET Framework や Xamarin との間でスムーズにコードが共有できるようになる .NET Core 2 に期待したいと思います。

Linux サーバーと Windows サーバーでは利用料には約2倍の差があるので、コストを重視する場合は、Linux サーバーを使うことを検討したらいいと思います。Linux サーバーを使うといっても、大部分は nginx のことになるので、Linuxについてそれほど多くのことを知らなくても動かすことはできます。自分の場合も、Linux のことは殆ど分かりませんでしたが、最近ではかなり慣れてきて、Linux のテキストベースの設定が結構便利だと思うようになりました。

.NET Core SDK 1.0 の RTM では、MSBuild を使うようになりました。それで、以下は MSBuild 版で ASP.NET Core アプリを Linux サーバーに公開する方法についてのメモです。従来の project.json 版のメモは、「ASP.NET Core アプリを Linux サーバーで公開(project.json 版)」に移動しました。project.json 版のアプリを MSBuild 版に更新するのは簡単で、Visual studio 2017 を使えば、自動的に変換されます。また、コマンドラインからは、global.json を削除後、dotnet migrade で更新できます。

以下は、公式ドキュメントを参考にしてメモしています。

参考 公式ドキュメント Publish to a Linux Production Environment

Step 0 - Web アプリの作成

ASP.NET Core のアプリケーションの開発は、テキストエディターのみでも可能です。特に、Visual Studio Code を使えば、C# を容易に編集できます。

.NET Core をインストール後に、以下のコマンドで新たな ASP.NET Core のプロジェクトを作成できます。

$ dotnet new -t web

それをテキストエディターで編集して、それが完了したら、以下のコマンドで発行します。

dotnet restore
dotnet run
dotnet publish

規模の大きなアプリケーションでは、デバッグやメンテナンスのことを考えると、Visual Studio を使うのが便利です。個人開発者であれば、Visual studio community 2017 を無償で利用できます。また、Mac の愛好者には、Visual Studio for Mac が発表され現在プレビュー版が公開されています。Visual Studio for Mac では、Xamarin と ASP.NET Core アプリを同時に開発することができます。

Step 0.1 - Linux サーバー及び SSH クライアントの準備

「さくらの VPS」を使う場合については、低価格 Linux VPS を使うにメモしました。Amazon EC2、Azure VM、Google Compute Engine を利用する場合は、「さくらの VPS」を使うよりも簡単です。SSH接続の公開鍵認証については、事前に公開鍵を登録すればデプロイ時に自動で設定してくれるし、ファイアーウォールについてはコントロールパネルで設定できます。

Step 1 .NET Core のインストール

.NET Core のインストールは、以下の公式ページを参考にします。

.NET Core installation guide

Ubuntu 16.04 の場合は、リポジトリーを登録するだけなので、インストールは簡単で、次の4行でできてしまいます。

sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ xenial 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.1

なお、上は MSBuild 版(VS 2017 に含まれるもの)をインストールする場合のコマンドです。.NET Core SDK 及び .NET Core Runtime は、複数バージョンをストールしても問題ありません。コマンドラインで使用する場合は、まず、global.json ファイルがあるかどうかによって判断し、あれば従来の project.json プロジェクト形式のものになり global.json に記載しているバージョンによって、使用する SDK が決められます。もし、global.json ファイルがなければ、MSBuild 版ということになり、csprojファイルが読み込まれます。(参考: Scott Hanselman 「Working with Multiple .NET Core SDKs - both project.json and msbuild/csproj」

Ubuntu 14.04 の場合は、2行目で xenial を trusty に変更するだけです。Xenial は Ubuntu 16.04 のコードネームで、Trusty は Ubuntu 14.04 のコードネームです。

Step 2 アプリの発行

Ubuntu サーバー用のアプリの発行は、Windows PC でも、Ubuntu サーバーのどちらでも可能です。また、発行の方法として、Portable Apps にする方法と Self-contained Apps にする方法の2種類があります。Self-contained Apps のことについては、最後の補足に書きました。既定では Portable Apps になっていて、その場合は、総てのプラットフォームで共通のバイナリーです。公開環境で使う場合は以下のコマンドで発行します。

dotnet publish --configuration Release

発行に加えたいフォルダーやファイルは、project.json 版では、project.json に記述する必要があったのですが、MSBuild 版では、従来の方式に戻って基本的には File のプロパティの Build Action と Copy to Output Direvtory を使ってコントロールするようになります。

Step 3 - Kestrel サーバーで公開

発行後は、アプリケーションのdll ファイルをdotnet app.dll のように直接指定して実行させます。

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

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

Step 4 - Nginx でリバースプロキシサーバーをたてる

ASP.NET Core は、Kestrel サーバーで動作しますが、Kestrel は単機能のWebサーバーなので、公開時にはWindows サーバーであれば IIS を使いますが、Linux では Nginx をリバースプロキシサーバーにして使用します。

Nginx をインストールする一番簡単な方法は、sudo apt-get install nginx ですが、かなり古いバージョンがインストールされます。

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

Mainline バージョンを使用する場合は、以下のようにリポジトリを追加することで、Ubuntu のパッケージを利用するのと同じ sudo apt-get install nginx でインストールできるようになります。詳しい説明は、Nginx の公式ドキュメントを見てください。

curl http://nginx.org/keys/nginx_signing.key | sudo apt-key add -
sudo sh -c "echo 'deb http://nginx.org/packages/mainline/ubuntu/ xenial nginx' >> /etc/apt/sources.list"
sudo sh -c "echo 'deb-src http://nginx.org/packages/mainline/ubuntu/ xenial nginx' >> /etc/apt/sources.list"

ASP.NET Core アプリケーションへのリバースプロキシの設定は基本的には、ASP.NET Core の公式ドキュメントのとおりにしています。ただし、クライアントの IP アドレスを取得したいので、proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; を追加しています。

creativeweb.conf

server {
  listen 80;
  server_name creativeweb.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 X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  }
}

ASP.NET Core アプリのリモートアドレスは、リバースプロキシのアドレスではなくて、ForwardedHeaders で送られてくるアドレスにしたいので、UseForwardedHeaders というミドルウェアを利用します。Startup.cs の Configure メソッドに以下のコードを追加します。

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
  ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});

ASP.NET Core のポート番号を変更したい場合は、以下のように Main 関数の WebHostBuilder で、UseUrls を使って変更します。

Program.cs

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

そこで、アドレスを localhost ではなくて、127.0.0.1 と指定しているのは、Bash on Ubuntu on Windows で使用するためです(参照)。

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

sudo service nginx stop
sudo service nginx start

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

sudo nginx -t

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

sudo nginx -s reload

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

Step 5 - Systemd でサービス化してモニタリングする

IIS と違って Nginx には、Kestrel を管理する機能がありません。そのため、安定した運用していくためには、エラーでサーバーが停止したときに自動的に起動するような仕組みが必要になります。そのために systemd を使用します。公式マニュアルは、以前は supervisor を使用していましたが、systemd を使うようように変更されたので、このメモでも systemd を使うように修正しました。今では主要なLinuxディストリビューションのほとんどで systemd がデフォルトのinitシステムとして採用されているため、それを使った方がいいだろうという判断だと思います。Ubuntu も 16.04 から systemd がデフォルトのinitシステムになっています。実際に使ってみても supervisor よりも systemd の方が便利です。

設定ファイルは、以下のようにマニュアルを参考にして設定しています。

/etc/systemd/system/ecitzen.service

[Unit]
Description=ecitzen version 1.0.6

[Service] ExecStart=/usr/bin/dotnet /var/aspnet/ecitzen/ecitzen.dll Restart=always RestartSec=10 SyslogIdentifier=ecitzen User=www-data Environment=ASPNETCORE_ENVIRONMENT=Production WorkingDirectory=/var/aspnet/ecitzen

[Install] WantedBy=multi-user.target

User については、公式マニュアルに書いてあるように、ASP.NET Core のユーザーを指定するので、ファイルのパーミションと合わせる必要があります。また、ワーキングディレクトリをルートディレクトリに指定しています。

サービスとして登録するのには以下のコマンドを使います。.service は、省略可能です。

sudo systemctl enable ecitzen

アプリを起動するには以下のコマンドを使います。

sudo systemctl start ecitzen

アプリを状況を確認するには以下のコマンドを使います。

sudo systemctl status ecitzen

ログを確認する場合は以下のコマンドを使います。

sudo journalctl -fu ecitizen

補足 - Self-contained Apps を使う

Step2 で説明したように .NET Core アプリには、Portable Apps と Self-contained Apps の2種類があり、Self-contained Apps の方は .NET Core ランタイムをアプリ内に含むため独立した環境で動作させることができます。

Self-contained Appsを発行するためには、csproj ファイルで以下のように発行したい OS のランタイム識別子 (RID) を指定します。(参考 RID のカタログ

<PropertyGroup>
  <RuntimeIdentifiers>win7-x64;win10-x64;osx.10-11-x64;ubuntu.16.04-x64</RuntimeIdentifiers>
</PropertyGroup>

それから、以下のコマンドで ubuntu 16.04 用の実行ファイルが作成されます

dotnet publish -c Release /p:RuntimeIdentifier=ubuntu.16.04-x64

.NET Core は、マルチラットフォーム対応なので、例えば Ubuntu PC で Windows 用の Self-contained Apps を作成することができます。現状では Self-contained Apps は、native コンパイルではないので、ファイル数が多く容量も最低でも50MBを超えます。しかし、単体で動作するので、AWS Lambda でも動かすことが可能です。