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

ASP.NET Core 1.0 がリリースされたのは、2016年の6月28日ですが、1年と少しで .NET Core 2.0 がリリースされました。.NET Standard 2 に対応し、やっと .NET Framework や Xamarin との間でスムーズにコードが共有できるようになりました。.NET Core を使うべきか .NET Framework を使い続けるべきかですが、新しくするところから .NET Core を使った方がいいと思います。

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

Linux サーバーを使って ASP.NET Core アプリをもう1年以上運用していますが、問題は発生していないので、ASP.NET Core を Linux サーバーで運用することに問題はないし、Nginx は優秀です。

なお、昔の project.json 版のメモは、「ASP.NET Core アプリを Linux サーバーで公開(project.json 版)」にあります。

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

参考 公式ドキュメント 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 run

規模の大きなアプリケーションでは、デバッグやメンテナンスのことを考えると、Visual Studio を使うのが便利です。個人開発者であれば、Visual studio community 2017 を無償で利用できます。また、Mac の愛好者には、Visual Studio for Mac が公開されています。Visual Studio for Mac では、Xamarin と ASP.NET Core アプリを同時に開発することができます。JetBrains 社からは Rider という IDE がリリースされています。Microsoft社以外からも IDE が発売されるのは、.NET Core が OSS になった効果だと思います。

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-sdk-2.0.0

なお、.NET Core SDK 及び .NET Core Runtime は、複数バージョンをストールしても問題ありません。global.json や .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種類があります。自己完結型アプリのことについては、最後の補足に書きました。既定では Portable Apps になっていて、その場合は、総てのプラットフォームで共通のバイナリーです。公開環境で使う場合は以下のコマンドで発行します。

dotnet publish --configuration Release

発行に加えたいフォルダーやファイルは、基本的には File のプロパティの Build Action と Copy to Output Direvtory を使ってコントロールします。

Step 3 - Kestrel サーバーで公開

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

Linux サーバーで使う場合に少し注意をしておかないといけないのは、コンソールアプリケーションと違ってWebアプリケーションでは、起動するときにルートディレクトリをカレントディレクトリにしておく必要があることです。

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)
{
  BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(string[] args) =>
  WebHost.CreateDefaultBuilder(args)
  .UseUrls("http://127.0.0.1:5001")
  .UseStartup<Startup>()
  .Build();

そこで、アドレスを 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種類があり、自己完結型アプリの方は .NET Core ランタイムをアプリ内に含むため独立した環境で動作させることができます。

自己完結型アプリを発行するためには、csproj ファイルで以下のように発行したい OS を指定します。.NET Core 1.x では、ランタイム識別子 (RID) を指定する必要があったのですが、.NET Core 2.0 では、Linux で一つの OS になったので、指定がかなり楽になりました。(参考 RID のカタログ

<PropertyGroup>
  <RuntimeIdentifiers>win-x64;win-x86;osx-x64;linux-x64;linux-arm</RuntimeIdentifiers>
</PropertyGroup>

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

dotnet publish -c Release -r linux-x64

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