Ubuntu Weekly Recipe

第729回Ubuntuリポジトリに取り込まれたパッケージ版.NETで、UbuntuでもC#プログラミングを始めよう!

Ubuntu Weekly Topicsの2022年8月19日号でも紹介されているように、Microsoftが提供するクロスプラットフォームな開発フレームワークである.NETのUbuntu向けパッケージが公式リポジトリからも提供されることになりました。今回はこれを用いてC#でWindows向けのプログラムを作ってみましょう。

実は前から存在した.NETパッケージ

「.NET」はMicrosoftが提供する開発プラットフォームではありますが、MITライセンスで公開されたオープンソースプログラムでもあります[1]。.NETを使えば、Windowsだけでなく、macOSやLinuxなどの各プラットフォームに対応し、サーバーアプリケーションを含むCLIツールをC#で作成できます[2]

.NET Frameworkのオープンソース実装としては昔からMonoが存在しました。Monoはモバイルアプリも含めたクロスプラットフォームなフレームワークであるXamarinで採用され、⁠なんやかんや」あってXamarinはMonoごとMicrosoftに買収されることになります。最新版の.NET 6ではこのモバイルアプリ向けであるXamarinも統合し、名実ともに「クロスプラットフォームな開発フレームワーク」となりました。ただしUbuntuのリポジトリにはMono関連パッケージも残っています。よって単純にC#でプログラミングするなら、Monoを使う手もあります。

話を元に戻すと、もともと.NET Coreの時点でオープンソース化されていますし、MicrosoftはUbuntu向けの独自リポジトリを作って、そこから.NET SDKや.NETランタイムをaptコマンドでインストールできるようにしていました[3]。ユーザーや開発者は必要に応じてMicrosoftが提供するリポジトリを追加するだけで、Ubuntuでも.NET環境を構築できたのです[4]

ここまでの話をまとめると、今回の「.NETのUbuntu向けパッケージ」は、単に次のような、どちらかというと政治的な話でしかありません。

  • MicrosoftはWSLに続いて.NETに関してもCanonicalと協力する
  • .NETのSDKやランタイムを、Canonicalがパッケージングし公式リポジトリから提供する
  • Canonicalは.NETのSDKやランタイムのセキュリティアップデートに対応する
  • .NETランタイムが入った小さなDockerイメージを提供する

一般的なユーザーにとっては、⁠.NETアプリの導入がより簡単になる」という利点はあるものの、そもそも.NETアプリの大半を占めるGUIアプリは動きません。せいぜいPowerShellがインストールしやすくなるかもしれない、ぐらいでしょうか。

開発ユーザーにとっては、開発環境の構築が「ちょっとだけ」楽になります。それ以上に、Ubuntuユーザーの環境への.NETランタイムのランタイムのインストールが簡単になったために、Debianパッケージで配布する場合も、依存関係に.NETのパッケージ名だけを書けば良いようになりました。今後.NETベースのCLIアプリやWebアプリが比較的簡単にインストールできるようになるかもしれません。

たとえば第435回のPowerShellをUbuntuにインストールで紹介したPowerShellは、.NET製のアプリケーションです。PowerShell自体はすでにMicrosoftによってUbuntu版のインストール方法がまとめられていますが、このような.NETソフトウェアがどんどんUbuntuでも使えるようになる可能性はあります。

もうひとつは、サーバーアプリケーションフレームワークであるASP.NETとの親和性です。たとえば第533回でも紹介したメディアサーバーであるEmbyや、Embyがプロプライエタリ化する際にフォークしたJellyFinなどは、マルチプラットフォームのサポートも想定して、.NET SDKを採用しています。これらのようなASP.NETベースのアプリケーションも、UbuntuのリポジトリやLinux版のコンテナに取り込みやすくなることでしょう。

.NETパッケージのインストールとサンプルプログラムの作成

今回Ubuntu 22.04 LTSに取り込まれた.NETパッケージは、主に次の3種類のパッケージに分類できます。

  • dotnet6:.NET 6のSDK。.NETアプリケーションの開発時にインストールする。
  • dotnet-runtine-6.0:.NET 6のアプリケーションを実行するために必要なランタイム。任意の.NETバイナリを実行だけしたい場合にインストールする。
  • apsnetcore-runtime-6.0:APS.NET 6のサーバーソフトウェアを実行するために必要なランタイム。サーバー用のライブラリが追加されている。

ビルドする際はdotnet6をインストールし、実行するだけなら用途に応じてdotnet-runtime-6.0かapsnetcore-runtime-6.0のどちらかをインストールするという形です。

では、さっそくdotnet6パッケージをインストールして、.NETアプリケーションをビルドしてみましょう。

$ sudo apt install dotnet6

アプリケーションの作成する際は「テンプレート」を指定します。今回は「console」テンプレートを利用しましょう。

$ dotnet new console -o MyApp -f net6.0

Welcome to .NET 6.0!
---------------------
SDK Version: 6.0.108

----------------
Installed an ASP.NET Core HTTPS development certificate.
To trust the certificate run 'dotnet dev-certs https --trust' (Windows and macOS only).
Learn about HTTPS: https://aka.ms/dotnet-https
----------------
Write your first app: https://aka.ms/dotnet-hello-world
Find out what's new: https://aka.ms/dotnet-whats-new
Explore documentation: https://aka.ms/dotnet-docs
Report issues and find source on GitHub: https://github.com/dotnet/core
Use 'dotnet --help' to see available commands or visit: https://aka.ms/dotnet-cli
--------------------------------------------------------------------------------------
The template "Console App" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on /home/ubuntu/temp/dotnet/MyApp/MyApp.csproj...
  Determining projects to restore...
  Restored /home/ubuntu/temp/dotnet/MyApp/MyApp.csproj (in 48 ms).
Restore succeeded.

テンプレートについては、SDKのドキュメントを参照してください。また、dotnet new --listを実行すれば、Ubuntu上で使用できるテンプレート一覧を確認できます。

$ dotnet new --list
These templates matched your input:

Template Name                                 Short Name      Language    Tags
--------------------------------------------  --------------  ----------  --------------------------
ASP.NET Core Empty                            web             [C#],F#     Web/Empty
ASP.NET Core gRPC Service                     grpc            [C#]        Web/gRPC
ASP.NET Core Web API                          webapi          [C#],F#     Web/WebAPI
ASP.NET Core Web App                          webapp,razor    [C#]        Web/MVC/Razor Pages
ASP.NET Core Web App (Model-View-Controller)  mvc             [C#],F#     Web/MVC
ASP.NET Core with Angular                     angular         [C#]        Web/MVC/SPA
ASP.NET Core with React.js                    react           [C#]        Web/MVC/SPA
Blazor Server App                             blazorserver    [C#]        Web/Blazor
Blazor WebAssembly App                        blazorwasm      [C#]        Web/Blazor/WebAssembly/PWA
Class Library                                 classlib        [C#],F#,VB  Common/Library
Console App                                   console         [C#],F#,VB  Common/Console
dotnet gitignore file                         gitignore                   Config
Dotnet local tool manifest file               tool-manifest               Config
EditorConfig file                             editorconfig                Config
global.json file                              globaljson                  Config
MSTest Test Project                           mstest          [C#],F#,VB  Test/MSTest
MVC ViewImports                               viewimports     [C#]        Web/ASP.NET
MVC ViewStart                                 viewstart       [C#]        Web/ASP.NET
NuGet Config                                  nugetconfig                 Config
NUnit 3 Test Item                             nunit-test      [C#],F#,VB  Test/NUnit
NUnit 3 Test Project                          nunit           [C#],F#,VB  Test/NUnit
Protocol Buffer File                          proto                       Web/gRPC
Razor Class Library                           razorclasslib   [C#]        Web/Razor/Library
Razor Component                               razorcomponent  [C#]        Web/ASP.NET
Razor Page                                    page            [C#]        Web/ASP.NET
Solution File                                 sln                         Solution
Web Config                                    webconfig                   Config
Worker Service                                worker          [C#],F#     Common/Worker/Web
xUnit Test Project                            xunit           [C#],F#,VB  Test/xUnit

今回はconsoleテンプレートを「MyApp」という名前で作成しました。実際に作られたファイルを見てみましょう。

$ cd MyApp/
$ find -type f
./obj/MyApp.csproj.nuget.g.targets
./obj/MyApp.csproj.nuget.g.props
./obj/project.nuget.cache
./obj/MyApp.csproj.nuget.dgspec.json
./obj/project.assets.json
./MyApp.csproj
./Program.cs
$ cat Program.cs
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

単にHello, World!を表示するだけのシンプルなプログラムですね。dotnet runでプログラムをビルドし実行してくれます。

$ dotnet run
Hello, World!

試しに生成されたバイナリを確認してみましょう。

$ file bin/Debug/net6.0/MyApp*
bin/Debug/net6.0/MyApp:                    ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, (略)
bin/Debug/net6.0/MyApp.deps.json:          JSON data
bin/Debug/net6.0/MyApp.dll:                PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows
bin/Debug/net6.0/MyApp.pdb:                Microsoft Roslyn C# debugging symbols version 1.0
bin/Debug/net6.0/MyApp.runtimeconfig.json: JSON data

$ ldd ./bin/Debug/net6.0/MyApp
        linux-vdso.so.1 (0x00007ffc1ddfb000)
        libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f0ef5035000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f0ef4f4e000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f0ef4f2e000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0ef4d06000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f0ef527d000)

bin/Debug/net6.0/MyAppはLinux向けのELFバイナリですが、⁠for MS Windows」と表示されるPE32バイナリも作られています。これも「Linux上でバイナリを実行するのに必要なライブラリ」です。実は.NETのランタイムライブラリはPEフォーマットでビルドされているのです。

次に同じソースコードを「Windows向け」にビルドしてみましょう。

$ dotnet build --self-contained true -r win-x64
Microsoft (R) Build Engine version 17.0.0+c9eb9dd64 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
  MyApp -> /home/ubuntu/temp/dotnet/MyApp/bin/Debug/net6.0/win-x64/MyApp.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.54
$ file bin/Debug/net6.0/win-x64/MyApp.exe
bin/Debug/net6.0/win-x64/MyApp.exe: PE32+ executable (console) x86-64, for MS Windows

クロスプラットフォームなビルドを行うためには、dotnet build-rオプションでランタイム識別子を指定する必要があります。64ビット版のWindowsなら「win-x64」ですし、バージョン指定するなら「win10-x6」になります。Linuxなら「linux-x64⁠⁠、macOSなら「osx-x64」のように指定できますし、バージョンやディストリビューション、CPU固有の識別子も存在します。

ランタイム識別子を指定する場合は、--self-containedも必須のオプションとなります。これは実行用の.NETライブラリ一式をビルドディレクトリに生成するかどうかを指定するオプションです。⁠true」なら生成し、⁠false」なら生成しません。たとえばビルドディレクトリをアーカイブして成果物にしたいなら、trueにする必要があるでしょう。

.NETは複数のプラットフォームのクロスビルドをサポートしています。つまり、.NET SDKをインストールしたUbuntuのコンテナイメージをひとつ用意しておけば、CIを使って簡単にすべてのプラットフォーム向けのバイナリを自動生成できるというわけです。

シングルバイナリの生成方法

もうひとつ、.NET関連のランタイムをシングルバイナリにまとめる方法も紹介しておきましょう。まず、.NETでは最終的な成果物はdotnet publishで整理します。これはバイナリと必要なランタイム類を整理し、publishディレクトリに保存するコマンドです。普通にpublishすると次のようになります。

$ dotnet publish
Microsoft (R) Build Engine version 17.0.0+c9eb9dd64 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
  MyApp -> /home/ubuntu/temp/dotnet/MyApp/bin/Debug/net6.0/MyApp.dll
  MyApp -> /home/ubuntu/temp/dotnet/MyApp/bin/Debug/net6.0/publish/

$ ls -lh bin/Debug/net6.0/publish/MyApp
-rwxr-xr-x 1 ubuntu ubuntu 76K Aug 27 14:47 bin/Debug/net6.0/publish/MyApp

このときの.NETバイナリ(MyApp)を実行するためには、MyApp.dllも、.NETランタイムも必要になります。そこでPublishSingleFileを設定してpublishしてみましょう。

$ dotnet publish -r linux-x64 --self-contained false /p:PublishSingleFile=true
$ ls -lh bin/Debug/net6.0/linux-x64/MyApp
-rwxr-xr-x 1 ubuntu ubuntu 140K Aug 27 14:48 bin/Debug/net6.0/linux-x64/MyApp

PublishSingleFileを指定する際は、-rオプションによるランタイム識別子と--self-containedも指定する必要があります。今回は--self-contained falseなので.NETランタイムは同梱していません。ただしサイズは少し大きくなっています。これはMyApp.dllがMyAppに同梱されたことによるものです。これにより「.NETランタイムがインストールされている環境」であれば、MyAppバイナリだけで実行が可能になります。

次に--self-contained trueを指定してみましょう。

$ dotnet publish -r linux-x64 --self-contained true /p:PublishSingleFile=true
$ ls -lh bin/Debug/net6.0/linux-x64/publish/MyApp
-rwxr-xr-x 1 ubuntu ubuntu 62M Aug 27 14:58 bin/Debug/net6.0/linux-x64/publish/MyApp

ファイルサイズが一気に大きくなりましたね。つまり、必要な.NETランタイムもバイナリの中に同梱されたことになります。このバイナリは.NETランタイムがインストールされていない環境でも実行可能です。もし様々な環境で利用する.NETアプリケーションを作るなら、シングルバイナリにしておくと良いでしょう。ちなみにPublishSingleFilecsprojファイルに記述できます

Webアプリケーションの作成

CLIアプリケーションとして、Webアプリケーションのテンプレートも使ってみましょう。

$ dotnet new webapp -o MyService
The template "ASP.NET Core Web App" was created successfully.
This template contains technologies from parties other than Microsoft, see https://aka.ms/aspnetcore/6.0-third-party-notices for details.

Processing post-creation actions...
Running 'dotnet restore' on /home/ubuntu/temp/dotnet/MyService/MyService.csproj...
  Determining projects to restore...
  Restored /home/ubuntu/temp/dotnet/MyService/MyService.csproj (in 47 ms).
Restore succeeded.

テンプレートに「webapp」を指定するだけで、その他は一緒です。

$ cd MyService/
$ cat Program.cs
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

コードがHello, World!に比べて若干複雑になっていますね。これはシンプルなWebサーバーを実現するためのプログラムです。早速実行してみましょう。

$ dotnet run
Building...
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
      No XML encryptor configured. Key {7e1b3662-a4c1-4fd9-a0b1-8c0f277bb029} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7039
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5183
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /home/ubuntu/temp/dotnet/MyService/

今回はプロセスが残り続けています。HTTPSとしてhttps://localhost:7039が、HTTPとしてhttp://localhost:5183が用意されていますので、curl等でアクセスしてみましょう。

$ curl -kL https://localhost:7039
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Home page - MyService</title>
    <link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="/css/site.css?v=ZJg29BfKMfPQ1nmIkdqg6iWhTGRNJx2HnD4FfdtVCcU" />
    <link rel="stylesheet" href="/MyService.styles.css?v=xMx8ooszZup4_uOEasJFRYBupwgLrjVIrzpyi2dW8Ws" />
</head>
<body>
(後略)

無事にコンテンツが表示されました。

最小コンテナの構築ツールである「Chisel」

「ASP.NET」は.NETでウェブアプリケーションを構築するためのランタイムです。.NETランタイムが公式パッケージで提供されることで、APP.NETでアプリケーションサービスを提供している場合もコンテナでの配布が楽になります。ただし.NETのDebianパッケージはそれなりに大きなサイズなので、容量が小さくなるように対応したいかもしれません。そこで同時に登場したのが、ChiselベースのOCIイメージです。

このChiselは現在開発中のGo言語のコマンドで、⁠本当に最低限のバイナリしか含まないコンテナイメージ」を作るツールとなります。今回の例で言うと、Ubuntu 22.04 LTSのコンテナイメージをベースに、.NETバイナリが動作するために必要なglibcや証明書関連のライブラリ以外を全部削除したrootfsを作成し、それをコンテナイメージに変換するのです。結果的に、次のようなファイルサイズになっています。

  • ubuntu/dotnet-deps(mcr.microsoft.com/dotnet/nightly/runtime-deps:6.0-jammy-chiseled⁠⁠:12.9MB
  • ubuntu/dotnet-runtime(mcr.microsoft.com/dotnet/nightly/runtime:6.0-jammy-chiseled⁠⁠:117MB
  • ubuntu/dotnet-aspnet(mcr.microsoft.com/dotnet/nightly/apsnet:6.0-jammy-chiseled⁠⁠:140MB

丸括弧の中はMicrosoftのレジストリ上のイメージ名です。dotnet-despは、⁠.NETを動かすための環境」のみ用意されており、.NETのランタイムライブラリは含まれていません。このコンテナイメージを使う場合は、Dockerfileの中に、self containedな.NETバイナリ群をインストールする必要があります。

それに対してdotnet-runtimeとdotnet-apsnetは、それぞれのランタイムライブラリがインストール済みのイメージとなります。よってビルドした.NETバイナリのみをコンテナに取り込めば完成です。

これらのイメージは、aptコマンドはもちろんのこと、bashなどのシェルコマンドも同梱されていません。rootアカウントも存在せず、UIDとGIDがそれぞれ101のユーザーで実行することになります。よって実際のDockerイメージを作るには、マルチステージビルドを用いて取り込むことになります。このあたりは、上記に記載した個々のコンテナイメージのDocker HubのURLでも解説されているため、まずはそちらを参照すると良いでしょう[5]

このようにUbuntuでも、CLIアプリケーションやWebアプリケーションであれば、十分に本格的な.NETプログラミングが可能になっています。VisualStudio Codeと組み合わせれば開発手法もWindowsのそれに近くなってきます。まだGUI部分のサポートがないものの、WindowsとUbuntuの両方で動くCLI/Webアプリケーションを開発する必要がある場合は、.NETも選択肢に含めても良いのではないでしょうか。

お知らせUbuCon Asia 2022の発表者を募集しています

2022年11月26日・27日の日程で、UbuCon Asia 2022が開催されます。これは韓国でのオフラインおよびオンラインで開催されるカンファレンスで、現在セミナー発表者を募集中です。

セミナーは会場に訪れての発表だけでなく、リアルタイムのオンライン発表も可能です。また、発表の言語や資料が英語である必要はありません。もちろん各国から参加される以上、英語のほうが伝わりやすいですが、資料は英語で発表は日本語のような組み合わせも可能です。

募集期限は9月14日までです。その後、9月27日まで審査を行い、10月5日までにはその結果を通知し、発表の意志の確認を行うことになるようです。昨年度の内容から、発表の難易度はかなり多種多様なものが許容されるようです。ぜひ日本からもふるってご応募ください。

また、オフライン開催に伴う旅費補助も検討中で、そのためのスポンサーも随時募集中のようです。Ubuntuにお世話になっている企業の皆様は、ぜひスポンサーのご検討もよろしくお願いいたします。

おすすめ記事

記事・ニュース一覧