Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
C#による.NET Core入門(4)

C#による.NET Core入門(4)

.NET Coreでコンソールアプリを配置する

2017年9月29日

クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。4回目は作成したコンソールアプリのプロジェクトをビルドして配置する手順を説明する。

レッドハット株式会社 田中 孝佳
  • このエントリーをはてなブックマークに追加

.NET Core 2.0 SDKインストール方法の変更[対象: Linux]

 CentOSをはじめ、以前はtar.gz形式の圧縮ファイルを解凍してインストールしていたLinuxディストリビューションについても、rpmなどのパッケージとして配布されたものをインストールする方法が紹介されるように公式ページでのインストール方法の記載が変更されている。

 必ずしも再インストールする必要はないが、今回の後半でLinuxサーバーに配置する際に、ログインシェルを持たないdotnetuserでdotnetプロセスを実行している部分が、従来のインストール方法だとそのままでは実行できなくなる。rpmパッケージによるインストールでは実行可能である。

 そのため、インストールし直したい場合は、PATH環境変数から~/dotnetを削除し、rm -rf ~/dotnetコマンドで解凍したフォルダーを削除した後、公式ページの手順でインストールし直してほしい。

 従来のユーザーディレクトリに展開している状態のまま、Linuxサーバーへの展開の節を読み進める場合は、実行ユーザーを.NET Core SDKをインストールしたユーザーにし、dotnetコマンドのフルパスを解凍先のディレクトリに読み替えて試してほしい。

.NET Core アプリケーションの配置

2種類の配置方法

 前回の記事で説明した通り、作成したプロジェクトはdotnet runコマンドでアプリケーションを実行できる。しかし、この方法では実行環境にソースコードを含めたプロジェクトが存在している必要があり、かつコマンド実行のたびにビルドが実行される。

 そこで.NET Coreでは、.NET Framework同様にアプリを事前にビルドしておき、ビルド成果物であるバイナリファイルを配置してアプリを実行する方法が用意されている。具体的には、フレームワークに依存する展開Framework-dependent deploymentFDD)と自己完結型の展開Self-contained deploymentSFD)という2種類の方法が利用可能である。

 フレームワークに依存する展開は、アプリケーションが動作するOSに.NET Coreのランタイムがインストールされていることを前提としている。.NET Frameworkでの配置と同様の仕組みだ。

 この方式の利点としては、ビルド成果物のバイナリがOSによらず同一であるためビルドの実行や配置が容易になること、ビルド成果物が軽量になること、複数のアプリを同じOSで実行する場合に.NET Coreランタイムは共通であるためディスク使用量が削減されること、といった3点が挙げられる。

 一方、欠点としては、事前に.NET Coreランタイムのインストールが必要であること、.NET Coreランタイム側のバージョンアップでまれではあるがアプリの動作に変更が生じる可能性があること、の2点が挙げられる。

 自己完結型の展開は、.NET CoreのランタイムがサポートしているOSかつ必要なネイティブパッケージが利用可能であれば事前にランタイムのインストールをすることなく、配置するバイナリのみで実行可能な形式である。OSにランタイムがインストールされていても利用することはなく、あくまで配置するバイナリに含まれるランタイムを利用する。

 事前のランタイムインストールが不要ということと、利用するランタイムを完全に固定できるのが利点だ。

 欠点としては、OSごとにビルドを行ってバイナリを生成しないといけないこと、ランタイムが含まれるので配置するバイナリのサイズが大きくなること、配置先のマシンで占めるディスク容量が増えることが挙げられる。

 それでは前回作成したアプリを使って2種類の配置方法を試してみよう。作成していない人は前回の記事を読んで作成するか、任意のコンソールアプリを使って試してほしい。その場合、プロジェクト名やファイル名などは適宜読み替える必要がある。

フレームワークに依存する展開

 フレームワークに依存する展開は、dotnet newコマンドで作成したデフォルトのプロジェクトファイルを利用してdotnet publishコマンドでバイナリを生成できる。

shell
$ dotnet publish -f netcoreapp2.0 -c Release
リスト1 プロジェクトファイル(.csprojファイル)と同じディレクトリで、「netcoreapp2.0」をフレームワークに指定して、「Release」構成で展開用のバイナリを生成するコマンド

 引数は指定しなくてもデフォルト値が使われるが、リスト1では2つ指定している。

-fオプション: フレームワーク

 最初の-fオプションは実行時に利用するフレームワークを指定している。フレームワークはアプリケーションが利用できるAPIの一覧を定義しており、フレームワークに依存する展開の場合は、実行時に指定したフレームワークをサポートするランタイムがインストールされている必要がある。netcoreapp2.0というのは.NET Core 2.0に対応している。他にはnetstandard2.0(.NET Standard 2.0)、netcoreapp1.1(.NET Core 1.1)、net47(.NET Framework 4.7)などがある。このページにターゲットフレームワークの詳細な説明がある。このうち、netstandard2.0はクラスライブラリを開発する次回で詳細に説明する予定である。

 フレームワークはプロジェクトファイル側でも指定する必要があり、デフォルトで作成されたファイルではリスト2のように1つだけ指定されている。

Example03.csproj
<PropertyGroup>
  <OutputType>Exe</OutputType>
  <TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
リスト2 デフォルトで作成されたままのターゲットフレームワークの指定

 また同一のソースコードファイルで複数のターゲットフレームワークを指定することもできる。その場合、リスト2の<TargetFramework>要素を消して<TargetFrameworks>要素(複数形になっていることに注意)を追加して、;区切りで複数のフレームワークを指定する(リスト3)。

Example03.csproj
<PropertyGroup>
  <OutputType>Exe</OutputType>
  <TargetFrameworks>netcoreapp2.0;netcoreapp1.1</TargetFrameworks>
</PropertyGroup>
リスト3 複数のターゲットフレームワークを指定する方法

 publishコマンド実行時にはここで指定したフレームワークのうち1つを選んで-fオプションに指定することになる。

-cオプション: ビルド構成

 次に-cオプションで指定したReleaseだが、これはビルド構成を指定している。ビルド構成は多くの場合DebugReleaseの2種類があらかじめ想定されているが、任意の文字列で好きな構成を指定できる。構成ごとに参照するライブラリを切り替えたり、#ifディレクティブを利用してビルド対象とするソースコードを切り替えたりできる。Debugビルド時はより詳細にログを出力するようにコーディングするといったことに利用できる。

 なおDebugビルドでバイナリを生成することもできるし、Releaseビルドでdotnet runコマンドを実行することもできる。ともに-cオプションで指定すればよい。

アプリケーションの配置方法と実行方法

 さて、dotnet publishコマンドで生成したバイナリはデフォルトでは、bin/<ビルド構成>/<ターゲットフレームワーク>/publishディレクトリ以下に出力される。つまりリスト1のコマンドであればbin/Release/netcoreapp2.0/publishだ。

 .NET Coreアプリを実行するためには、このディレクトリ内の全ファイルを単純にコピーして、実行するOS上の任意のディレクトリに配置すればよい。なお、配置先のディレクトリは、dotnetコマンドを実行するユーザーがアクセスできる必要がある。

 ちなみに、このディレクトリの中には、拡張子が.pdbのファイルがある。この.pdbファイルはデバッグ時に役に立つ情報が含まれているが、配置時に含める必要はない。

 配置したアプリを実行する場合は、アプリ名.dlldotnetコマンドの引数に指定すればよい(リスト4)。

shell
$ dotnet Example.dll
リスト4 フレームワークに依存する展開で配置したアプリの実行方法

 また、前回のサンプルアプリを利用している場合、NuGetからライブラリを追加した状態でpublishを実行している。この場合、ライブラリのうち実行に必要なバイナリもpublishディレクトリに配置されている。

自己完結型の展開

 フレームワークに依存する展開と異なり、自己完結型の展開はまず、プロジェクトファイルに実行先のOSを指定する必要がある。実行先のOS、より正確にはプラットフォームごとにランタイム識別子(RID)が用意されており、一覧はこのページで確認できる。

 例えばLinuxであればポータブル(共通化)としてlinux-x64が用意されている。.NET Core 1.1までは各ディストリビューションのRIDを指定する必要があったが、.NET Core 2.0からは追加したライブラリが特定のRIDのみをサポートしてない限りは、このポータブルなRIDが利用できる。

 今回はリスト5のようにWindows、Linux、macOS(全て64bit OS)を指定した。

Example03.csproj
<PropertyGroup>
  <OutputType>Exe</OutputType>
  <TargetFrameworks>netcoreapp2.0;netcoreapp1.1</TargetFrameworks>
  <RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
リスト5 Windows/Linux /macOSの3つのプラットフォーム向けにRIDを指定した設定

最近のMac用のOSは「macOS」と呼ばれるようになっているが、従来は「OS X」と呼ばれていたため、osx-x64はその従来の呼ばれ方に準拠している。

 プロジェクトファイルでRIDを指定したら、RIDごとにバイナリを生成する。-rオプションでRIDを指定する。

shell
$ dotnet publish -c Release -f netcoreapp2.0 -r win-x64
$ dotnet publish -c Release -f netcoreapp2.0 -r linux-x64
$ dotnet publish -c Release -f netcoreapp2.0 -r osx-x64
リスト6 リスト5のそれぞれの構成ごとに自己完結型の展開のためのバイナリを生成するコマンド

 すると、bin/<ビルド構成>/<ターゲットフレームワーク>/<RID>/publishディレクトリに、配置用のファイル群が出力されているはずだ。さて、それぞれのプラットフォームごとの出力結果を見てみよう。

図1 自己完結型の展開で生成した実行可能バイナリのファイル形式

 Windowsなら.exe、LinuxやmacOSなら拡張子なしと、プラットフォームごとに実行可能なファイル形式が異なっている。そして、注目してもらいたいのが、このビルドを行ったのはLinux(筆者の環境ではCentOS 7.2)であるということだ。LinuxでLinux向けはもちろん、WindowsやmacOS向けの実行可能形式なバイナリファイルを出力可能である。

 あとは、publishディレクトリ以下のファイルをサブディレクトリも含めて実行先のOSにコピーし、実行すればよい。最初に説明したように、そのときOSに.NET Coreがインストールされている必要はない。ただ、WindowsでLinux向けのバイナリをビルドしてLinuxにコピーした場合、実行可能権限がつかない点に注意してほしい。

Linuxサーバーへ配置する

 配置したバイナリはコンソールやターミナルからコマンドで実行可能ではあるが、実際のシステムでは異なる実行方法が必要になることが多いだろう。WindowsであればWindowsサービスにしたり、タスクスケジューラーでスケジュール実行したりできる。

 この節では、Linuxについて、かつ紹介するソフトウェアが利用できるディストリビューション限定ではあるが、バックグラウンドプロセスとして実行する方法や、スケジュール実行する方法を紹介する。

systemdを使いバックグランドプロセスとして実行する

 systemdはLinuxの起動処理に使われているが、プロセス管理にも使うことができ、カスタムユニットを定義することで.NET Coreアプリを管理できる。今回は単純なカスタムユニットファイルを作成して、systemctlコマンドで実行させてみよう。なお、systemdはその性質上、多くのLinuxではすでにインストールされているはずである。

 まずサーバー側の事前準備として、配置したアプリを実行する専用のユーザーを作成する。

 リスト7ではdotnetuserという名前を指定している。そしてアプリを配置するディレクトリを用意し、cpコマンドでコピーしている。これは同一マシンで配置しているが、リモートのマシンで実行する場合は、適当な方法で転送を行えばよい。配置したディレクトリの所有者を実行するユーザーに変えて事前準備は完了である。

shell
$ sudo useradd dotnetuser
$ sudo mkdir /var/Example03
$ sudo cp bin/Release/netcoreapp2.0/publish/* /var/Example03/
$ sudo chown -R dotnetuser /var/Example03/
リスト7 アプリを実行するための準備

 次にsystemdのカスタムユニットファイルを指定する。

 カスタムユニットファイルは/etc/systemd/system/<ユニット名>.<ユニットタイプ>に作成する。今回作成するユニットタイプはserviceであり、ユニット名は任意の名前でよい。netcore-example03.serviceという名前でリスト8のようなユニットファイルを作成した。

shell
[Unit]
Description=.NET Core Example03
DefaultDependencies=no

[Service]
Type=oneshot
RemainAfterExit=no
ExecStart=/usr/bin/dotnet Example03.dll
WorkingDirectory=/var/Example03
User=dotnetuser
Group=dotnetuser
リスト8 .NET Coreコンソールアプリを実行するカスタムユニットファイル

 Type=oneshotRemainAfterExit=noを指定することで、サービスが起動されたらコンソールアプリが実行され、実行完了したらサービスも停止するようにした。ExecStartで実行するコマンドを指定しているが、ここではdotnetコマンドはフルパスで指定する必要がある。引数のExample03.dllが相対パスとなっているが、基準はWorkingDirectoryで指定したディレクトリである。UserGroupでプロセスを実行するユーザーとグループを指定する。このファイルを保存したら、daemon-reloadした後、起動してみよう。リスト9に実行結果と合わせてコマンドを記載した。

shell
$ sudo systemctl daemon-reload 
$ sudo systemctl start netcore-example03.service 

$ sudo journalctl -u netcore-example03.service 
-- Logs begin at Fri 2017-09-22 08:08:23 JST, end at Fri 2017-09-22 18:00:58 JST. --
Sep 22 18:00:47 localhost.localdomain systemd[1]: Starting .NET Core Example03...
Sep 22 18:00:47 localhost.localdomain dotnet[24687]: a
Sep 22 18:00:47 localhost.localdomain dotnet[24687]: {"key1":"a"}
Sep 22 18:00:47 localhost.localdomain dotnet[24687]: Hello World!
Sep 22 18:00:47 localhost.localdomain systemd[1]: Started .NET Core Example03.
リスト9 リスト8で作成したカスタムユニットの起動とログファイルの確認

 journalctlコマンドで-uオプションにユニット名を指定することで、systemdのユニットのログを確認できる。これでバックグラウンドプロセスとして実行できた。

cronを使いスケジュール実行する

 次にスケジュール実行するためにcronを利用してみよう。

 cronも最初からインストールされていることが多いと思われるが、入っていない場合、例えばCentOS 7であればsudo yum install cronieでインストールできる。

 cronは、cron書式と呼ばれる形式で指定したコマンドを、指定したタイミングで定期的に実行できる。今回はcronの詳細の説明は割愛するが、必要な場合は適宜検索して調べてほしい。

 systemctlコマンド経由でcronから実行したいため、rootユーザーのcronを編集する必要がある。そのためリスト10のようにsudo crontabで編集画面を呼び出し、適宜入力する。リスト10では毎分実行するように指定している。

shell
$ sudo crontab  -e
no crontab for root - using an empty one
//開いた編集画面で
//* * * * * systemctl start netcore-example03.service
//と入力し(「i」をタイプして編集モードに入り)、保存する([Esc]キーを押してコマンドモードに復帰後、「:wq」の順にタイプし、[Enter]キーで確定する)
crontab: installing new crontab

//-lオプションで現在登録されているジョブを確認できる。
$ sudo crontab  -l
* * * * * systemctl start netcore-example03.service
リスト10 毎分実行するcronジョブの作成

 登録後、1分以上待つと、実行結果をjournalctlで確認できるはずだ(リスト11)。

shell
$ sudo journalctl -u netcore-example03.service  -l
-- Logs begin at Fri 2017-09-22 08:08:23 JST, end at Fri 2017-09-22 18:10:29 JST. --
[省略]
Sep 22 18:10:02 localhost.localdomain systemd[1]: Starting .NET Core Example03...
Sep 22 18:10:02 localhost.localdomain dotnet[25204]: a
Sep 22 18:10:02 localhost.localdomain dotnet[25204]: {"key1":"a"}
Sep 22 18:10:02 localhost.localdomain dotnet[25204]: Hello World!
Sep 22 18:10:02 localhost.localdomain systemd[1]: Started .NET Core Example03.
リスト11 cronによるジョブの実行結果

Dockerコンテナーを作成する

 最近ではコンテナーの利用が進んでおり、.NET Coreで作成したアプリも、直接、Linuxのホストで動かすよりDockerや他のコンテナーで動かすケースの方が多いかもしれない。

 Dockerを使った.NET Coreアプリの配置やデプロイは、よりユースケースが多いであろうASP.NET Coreのところでも詳しく説明する予定だが、今回はDockerfileを書いて、シンプルなコンソールアプリを含んだDockerコンテナーを作成する方法を紹介する。

 なお、コンテナーを中心に置いたContainer as a ServicePlatform as a Serviceのソフトウェアやサービスでは、Dockerfileをユーザーに記述させることなく、ソースコードのリポジトリやいくつかのビルド設定を指定するだけでアプリケーションのコンテナーを作成する機能が備わってきている。今後、Dockerfileそのものを記述することは少なくなる可能性もあるため、Dockerfileそのものの詳細な記述方法については省略する。

 まずはDockerをインストールして、有効化、起動しておく(リスト12)。

shell
$ sudo yum install docker
$ sudo systemctl enable docker
$ sudo systemctl start docker
リスト12 dockerのインストールと有効化、起動

 Dockerコンテナーのイメージは、ベースとなるコンテナーを基に必要な操作をDockerfileに記述し、ビルドして生成する。.NET Core向けにはDocker Hub上でマイクロソフトが目的に合わせて何種類かのコンテナーイメージを公開している。また、マイクロソフト以外で.NET Coreをサポートしているレッドハットもレッドハットがサポートする.NET Coreのコンテナーイメージを公開している。

 今回はDocker Hubで公開されているLinuxコンテナーを利用する。用意されているタグ(TAG)とその利用目的は公式ドキュメントにも記載されているが、改訂中で情報が古くなっているので、以下でも簡単に説明しておこう。

 コンテナー内にソースコードを配置してビルドできるコンテナーもあるが、そのためには.NET Core SDKがインストールされている必要があり、コンテナーの容量が大きくなる。そこで、実行するためだけのコンテナーとして、.NET Coreランタイムのみをインストールしたものをベースに使い、バイナリファイルをコピーしてイメージを作成する。.NET Core 2.0でこの目的のために公開されているコンテナーイメージが、microsoft/dotnet:2.0-runtime(<リポジトリ>:<タグ>)である。

 また自己完結型の展開の場合は、.NET Coreのランタイムは不要だが、必要なネイティブパッケージが利用可能になっていなければならない。その準備が整ったコンテナーイメージがmicrosoft/dotnet:2.0-runtime-depsである。

 次にDockerfileを記述する。

 まずは.NET Coreランタイムがインストールされているコンテナーを使って、フレームワークに依存する展開をしたバイナリを配置してみよう。今回はバイナリと同じディレクトリにリスト13のようなDockerfileを記述し、リスト14のようにビルド、実行を行った。特に指定しない場合、docker buildコマンドの最後にイメージ名が表示されるので、そのイメージ名を指定して実行している。

Dockerfile
FROM microsoft/dotnet:2.0-runtime

COPY . /var/Example03/

WORKDIR /var/Example03

ENTRYPOINT ["dotnet", "Example03.dll"]
リスト13 フレームワークに依存する展開をしたバイナリを配置して実行するコンテナーを作成するDockerfile
shell
$ sudo docker build .
Sending build context to Docker daemon 678.4 kB
Step 1 : FROM microsoft/dotnet:2.0-runtime
Trying to pull repository docker.io/microsoft/dotnet ...
2.0-runtime: Pulling from docker.io/microsoft/dotnet
219d2e45b4af: Pull complete
c7ce0e589ef9: Pull complete
2e3b417372db: Pull complete
f6c4ad998cfa: Pull complete
Digest: sha256:2bb1ceec506c4fef9ace749f17d758ab564a44fc7d94450b15393972bf7eb3dc
---> 2f9be1296dcf
Step 2 : COPY . /var/Example03/
---> 29d2e37cd369
Removing intermediate container 5bf6b3ad3054
Step 3 : WORKDIR /var/Example03
---> Running in 1cf9756729a8
---> 41077f09ebea
Removing intermediate container 1cf9756729a8
Step 4 : ENTRYPOINT dotnet Example03.dll
---> Running in 4fc8a45b3ce5
---> 6b8b05e03c41
Removing intermediate container 4fc8a45b3ce5
Successfully built 6b8b05e03c41
$ sudo docker run 6b8b05e03c41
a
{"key1":"a"}
Hello World!
リスト14 dockerイメージの作成と起動

 今度は、.NET Coreランタイムが含まれていないコンテナーに自己完結型の展開をしたバイナリを配置してみよう。Dockerfileがリスト15、ビルドと実行した例をリスト16に載せた。

Dockerfile
FROM microsoft/dotnet:2.0-runtime-deps

COPY . /var/Example03/

WORKDIR /var/Example03

ENTRYPOINT ["./Example03"]
リスト15 自己完結型の展開をしたバイナリを配置して実行するコンテナーを作成するDockerfile
shell
$ sudo docker build .
Sending build context to Docker daemon 66.06 MB
Step 1 : FROM microsoft/dotnet:2.0-runtime-deps
---> 9561655c08ea
Step 2 : COPY . /var/Example03/
---> cc7e520d89f2
Removing intermediate container ce1ee5f02b1e
Step 3 : WORKDIR /var/Example03
---> Running in 55bdba62fdf1
---> 2bf3cafece1c
Removing intermediate container 55bdba62fdf1
Step 4 : ENTRYPOINT ./Example03
---> Running in 7e617594c5ce
---> 0056283e768a
Removing intermediate container 7e617594c5ce
Successfully built 0056283e768a
$ sudo docker run 0056283e768a
a
{"key1":"a"}
Hello World!
リスト16 dockerイメージの作成と起動

 ベースとなるイメージと起動コマンドが異なるだけでリスト13とほとんど同じである。さて、それぞれのdockerイメージのサイズを確認したのが図2である。

図2 リスト14やリスト16で利用・作成したイメージの容量

[IMAGE ID]=6b8b05e03c41がフレームワークに依存する展開、0056283e768aが自己完結型の展開をそれぞれ配置したイメージである。

 .NET Coreランタイムがインストールされていない分、2.0-runtime-depsの方が当然2.0-runtimeより軽量であるが、どちらも.NET Coreランタイムとアプリのバイナリが配置されるため、作成したコンテナーの容量は同程度になっていることが分かる。

 今回は2種類の配置方法とそれぞれのバイナリの生成方法を説明し、実際の配置例として、Linuxホスト上でのsystemdやcronによる管理、さらに.NET Coreアプリを配置したDockerコンテナーの作成までを行った。

 次回はクラスライブラリプロジェクトの作成を依存関係のある複数のプロジェクトを利用しつつ説明する予定だ。

※以下では、本稿の前後を合わせて5回分(第1回~第5回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

C#による.NET Core入門(4)
1. .NET Coreとは? 開発環境(SDKとVisual Studio Code)のインストール

クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載スタート。初回は.NET Coreの歴史/立ち位置/特徴を紹介し、開発環境の準備方法を説明する。

C#による.NET Core入門(4)
2. レッドハット版.NET Coreとマイクロソフト版.NET Coreの違い

.NET CoreのMicrosoft提供版とRed Hat提供版には違いがある。Red Hat Enterprise Linux上でのNET Core環境の構築方法を紹介したうえで、.NET Core 1.xと2.x以降での違いを示す。さらにOpenShiftでの.NET Coreの利用についても言及する。

C#による.NET Core入門(4)
3. .NET Coreでプロジェクトを作成して開発してみよう

クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。3回目は実際にプロジェクトを新規に作成して、Visual Studio Codeを使って開発するフローを説明する。

C#による.NET Core入門(4)
4. 【現在、表示中】≫ .NET Coreでコンソールアプリを配置する

クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。4回目は作成したコンソールアプリのプロジェクトをビルドして配置する手順を説明する。

C#による.NET Core入門(4)
5. .NET Standardなライブラリプロジェクトを作成して参照する

クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。5回目は.NET Standardなクラスライブラリなプロジェクトを作成し、別のコンソールプロジェクトから参照する方法を説明する。

サイトからのお知らせ

Twitterでつぶやこう!