本".NET をIBM Powerで動かしてみた話"シリーズは、.NET 7がLinux on Power環境で利用可能となった(IBM社のブログ : .NET 7 now includes support for Linux on Power!)ということで、以下のテーマで3つのブログを掲載予定としています。いずれのテーマも、Linux on Powerをご存知の方、もしくは触ったことがある方は今回のニュースを聞いて、抱かれた疑問ではないでしょうか。
1. 「本当に.NET 7は問題なく動くのか」
2. 「x86環境で作成された.NET アプリケーションはppc64le環境でもそのまま動くのか」
3. 「コンテナ環境でも利用できるのか」
今回は、2つ目のテーマである"「x86環境で作成された.NET アプリケーションはppc64le環境でもそのまま動くのか」" について記載していこうと思います。
なお、本記事で使用している環境は、上記の"1. 「本当に.NET 7は問題なく動くのか」"の記事で作成したものをそのまま使用しています。
まだ読まれていない方は、よろしければ本記事に入る前に以下の内容もご覧ください。
それでは、本題に入っていきたいと思います。
2. 「x86環境で作成された.NET アプリケーションはppc64le環境でもそのまま動くのか」
このテーマでは実際に.NETのサンプルコードを利用して、.NETアプリケーションの利用においてCPUアーキテクチャ(x86環境とPower環境)を意識することがあるのかを確認していきたいと思います。.NETの仕組み的に、サンプルコードレベルでの互換性はあると理解していますが、やはり実際に動かしてみたいと思ってしまいますよね。
ちなみにこれは参考情報ですが、.NET環境においても.NETアプリケーション内部で使用している.NETコンポーネントなどは、x86環境とPower環境とでの違いはあります。こちらはPower環境に対応した、対象の.NETコンポーネントが提供されているかどうかという部分になってきます。
このような形で、既存の.NETアプリケーションのコードが"全部・そのまま・必ず"動くかというとそうではありませんのでご注意ください。一度、評価環境などで動作をご確認いただくことをお勧めいたします。
(1) 作業環境の確認
今回使用しているLinux on Power環境は以下となります。
RHEL8.7 相当となるAlmaLinux 8.7 をインストールしています。
[root@dotnet-sample-alma87 ~]# uname -a
Linux dotnet-sample-alma87 4.18.0-425.3.1.el8.ppc64le #1 SMP Tue Nov 8 14:07:43 EST 2022 ppc64le ppc64le ppc64le GNU/Linux
[root@dotnet-sample-alma87 ~]# cat /etc/redhat-release
AlmaLinux release 8.7 (Stone Smilodon)
(2) 使用するサンプルコード
以下のGitHubの内容をもとに動作を確認しています。
このGitHubの内容は、Blazorフレームワークをベースとしたサンプルコードとなっています。
この中から、以下のコンテンツの動作を確認していきます。
問題なく動作すれば、チャット機能やカウンター機能、天気情報の確認が可能なWebUIが起動してきます。
(3) サンプルコードの実行
まず初めに任意のディレクトリで上記のGitHubレポジトリをcloneします。
今回は、/rootディレクトリ配下にworkディレクトリを作成し、そちらにgit clone したいと思います。
[root@dotnet-sample-alma87 ~]# mkdir /root/work
[root@dotnet-sample-alma87 ~]# cd /root/work/
[root@dotnet-sample-alma87 work]# pwd
/root/work
[root@dotnet-sample-alma87 work]# git clone https://github.com/dotnet/blazor-samples.git
Cloning into 'blazor-samples'...
remote: Enumerating objects: 1939, done.
remote: Counting objects: 100% (139/139), done.
remote: Compressing objects: 100% (55/55), done.
remote: Total 1939 (delta 101), reused 95 (delta 84), pack-reused 1800
Receiving objects: 100% (1939/1939), 627.37 KiB | 169.00 KiB/s, done.
Resolving deltas: 100% (1270/1270), done.
[root@dotnet-sample-alma87 work]# ls
blazor-samples
続いて、今回利用する"BlazorServerSignalRApp"のディレクトリの中身を確認してみます。
[root@dotnet-sample-alma87 work]# cd blazor-samples/7.0/BlazorServerSignalRApp/
[root@dotnet-sample-alma87 BlazorServerSignalRApp]# ls
App.razor BlazorServerSignalRApp.csproj Data Hubs Pages Program.cs Properties Shared _Imports.razor appsettings.Development.json appsettings.json wwwroot
次に、.NETアプリケーション起動時のアクセスIPアドレスの設定を変更します。デフォルトではlocalhostとなっていますが、今回は"Any IP"(対象のAlmaLinuxがもつ全てのIPアドレスでアクセス可能)の設定に変更したいと思います。
[root@dotnet-sample-alma87 BlazorServerSignalRApp]# cat Properties/launchSettings.json
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:55347",
"sslPort": 44312
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5098",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7033;http://localhost:5098",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
[root@dotnet-sample-alma87 BlazorServerSignalRApp]# cat Properties/launchSettings.json
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://*:55347",
"sslPort": 44312
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://*:5098",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://*:7033;http://*:5098",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
それでは早速、起動してみましょう!
[root@dotnet-sample-alma87 BlazorServerSignalRApp]# pwd
/root/work/blazor-samples/7.0/BlazorServerSignalRApp
[root@dotnet-sample-alma87 BlazorServerSignalRApp]# dotnet run
ビルドしています...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://[::]:5098
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: /root/work/blazor-samples/7.0/BlazorServerSignalRApp
エラーなく、起動してきました。
また先ほどの設定変更によって、.NETアプリケーションがListenしているURLが"http://[::]:5098
" となっています。もし設定を実施しない場合は、"http://localhost:5098
" となり、localhostからしかアクセスできなくなります。
今回は、"http://[::]:5098"としていますので、.NETアプリケーションが起動している環境がもつIPアドレスであれば、どのIPでもアクセスが可能となっています。
それでは実際にWebUIにアクセスしてみます。正常にアプリケーションが起動していれば、以下のような画面が表示されるはずです。
そしてここまで動作を確認ができれば、.NETのアプリケーションコードがCPUアーキテクチャに依存せず、そのまま利用できていることがわかります。
WebUI画面左側のタブをそれぞれクリックすると、各ページごとに作成された機能を確認できます。
- Counter
"Click me"ボタンを押すと、Current countの値が増えていきます。
- Fetch Data
dotnet run を実行した翌日以降の日付で天気予報が表示されます。今回は、12月20日に実行していますので、21日からの表示になっていますね。
(4) サンプルコードを変更してみよう!
ここからは、.NETアプリケーションのソースコード変更して、どこをどう変更するとWebUIに反映されるのかを確認してみました。
実施してみたことは以下の4点です。
(a) WebUIアクセス時の"Home"画面として、起動している環境のシステム情報を表示したい
(b) 左側のタブの"BlazorServerSignalRApp"を変更したい
(c) 左側のタブの表示順を変更したい
(d) 左側のタブのアイコンを変更したい
各項目を設定するための変更対象ファイルと内容は以下です。
変更後の"BlazorServerSignalRApp"アプリケーションコードは、以下のGitHubに公開しました。
それでは早速変更箇所を見ていきます。
まずは、以下のように"/root/work/blazor-samples/7.0/BlazorServerSignalRApp/Pages/"ディレクトリ配下に"Info.razor"というファイルを作成し、(a)のやりたいことであるHome画面として表示するWebページ用のコードを作ります。
@page "/"
@using System.Runtime.InteropServices
@using System.IO
@using System.Diagnostics
@{
var hostName = System.Net.Dns.GetHostName();
System.Diagnostics.Debug.WriteLine(hostName);
const long Mebi = 1024 * 1024;
const long Gibi = Mebi * 1024;
GCMemoryInfo gcInfo = GC.GetGCMemoryInfo();
string totalAvailableMemory = GetInBestUnit(gcInfo.TotalAvailableMemoryBytes);
bool cgroup = RuntimeInformation.OSDescription.StartsWith("Linux") && Directory.Exists("/sys/fs/cgroup/memory");
string memoryUsage = string.Empty;
string memoryLimit = string.Empty;
if (cgroup)
{
string usage = System.IO.File.ReadAllLines("/sys/fs/cgroup/memory/memory.usage_in_bytes")[0];
string limit = System.IO.File.ReadAllLines("/sys/fs/cgroup/memory/memory.limit_in_bytes")[0];
memoryUsage = GetInBestUnit(long.Parse(usage));
memoryLimit = GetInBestUnit(long.Parse(limit));
}
}
<div class="text-center">
<h1>Hello! .NET on Power!</h1>
</div>
<br>
<font color="#0000cd"><b>今回の検証環境は、以下となります。</b></font><br>
<font color="#0000cd"><b>x86で作成されたサンプルコードをそのまま使用して、本日のセッション用にカスタマイズしています。</b></font><br>
<br>
<div align="center">
<style>
table td {
background: #eee;
}
table tr:nth-child(odd) td {
background: #fff;
}
table {
border:3px solid #333;
}
</style>
<table class="table table-striped table-hover">
<tr>
<td>.NET version</td>
<td>@RuntimeInformation.FrameworkDescription</td>
</tr>
<tr>
<td>Operating system</td>
<td>@RuntimeInformation.OSDescription</td>
</tr>
<tr>
<td>Processor architecture</td>
<td>@RuntimeInformation.OSArchitecture</td>
</tr>
<tr>
<td>CPU cores</td>
<td>@Environment.ProcessorCount</td>
</tr>
<tr>
<td>Containerized</td>
<td>@(Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") is null ? "false" : "true")</td>
</tr>
<tr>
<td>Memory, total available GC memory</td>
<td>@totalAvailableMemory</td>
</tr>
@if (cgroup)
{
<tr>
<td>cgroup memory usage</td>
<td>@memoryUsage</td>
</tr>
<tr>
<td>cgroup memory limit</td>
<td>@memoryLimit</td>
</tr>
}
<tr>
<td>Host name</td>
<td>@hostName</td>
</tr>
</table>
</div>
@{
string GetInBestUnit(long size)
{
if (size < Mebi)
{
return $"{size} bytes";
}
else if (size < Gibi)
{
decimal mebibytes = Decimal.Divide(size, Mebi);
return $"{mebibytes:F} MiB";
}
else
{
decimal gibibytes = Decimal.Divide(size, Gibi);
return $"{gibibytes:F} GiB";
}
}
}
続いて、WebUIにアクセスした際に"Home"として、上記のWebページが表示されるように "BlazorServerSignalRApp/Shared/NavMenu.razor"ファイルを編集します。
またこのファイルでは、WebUI左側タブに表示されている文字やコンテンツの表示順、アイコンの変更(やりたいことの(b)-(d))も可能なため、併せて変更してしまいます。
(b) 左側のタブの"BlazorServerSignalRApp"を変更したい
(c) 左側のタブの表示順を変更したい
(d) 左側のタブのアイコンを変更したい
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">.NET on IBM Power!!</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="chat" Match="NavLinkMatch.All">
<span class="oi oi-chat" aria-hidden="true"></span> Communication
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-cloudy" aria-hidden="true"></span> WeatherForecast
</NavLink>
</div>
</nav>
</div>
@code {
private bool collapseNavMenu = true;
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
最後に、デフォルトのWebUIで"Home"画面として使用されていた"チャット画面"を"Communication"タブ(/chat)に変更するため、BlazorServerSignalRApp/Pages/Index.razor ファイルを変更します。
@page "/chat"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable
<PageTitle>Index</PageTitle>
<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>
<hr>
<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>
@code {
private HubConnection? hubConnection;
private List<string> messages = new List<string>();
private string? userInput;
private string? messageInput;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
InvokeAsync(StateHasChanged);
});
await hubConnection.StartAsync();
}
private async Task Send()
{
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendMessage", userInput, messageInput);
}
}
public bool IsConnected =>
hubConnection?.State == HubConnectionState.Connected;
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
以上で、サンプルコードの変更は終了です。
今一度アプリケーションを起動し、想定した内容となっているか確認してみましょう。また"dotnet run"実行のタイミングでエラーなどが表示されないかも併せて確認します。(もし表示された場合には、そのエラー箇所を修正しましょう。)
[root@dotnet-sample-alma87 BlazorServerSignalRApp]# dotnet run
ビルドしています...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://[::]:5098
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: /root/work/blazor-samples/7.0/BlazorServerSignalRApp
正常に上記の変更がされている場合、以下のようなWebページが表示されるはずです。
ちゃんと"Home"のページコンテンツとして、システム情報が表示されていますし、左側のタブ欄も変更が反映されていますね。
まとめ
今回は、前回の内容につづき、IBM Power上での.NET 7アプリケーションの稼働確認を行いました。この結果から、アプリケーションのソースコードは基本的にはCPUアーキテクチャに依存しないことが分かりました。
しかしながら冒頭に記載しましたように、.NETアプリケーション内部で使用している.NETコンポーネントなどは、x86環境とPower環境とでの違いはあります。これはPower環境に対応した、対象の.NETコンポーネントが提供されているかどうかという部分になってきます。
その為、既存の.NETアプリケーションのコードが"全部・そのまま・必ず"動くかというとそうではありませんのでご注意ください。一度、評価環境などで動作をご確認いただくことをお勧めいたします。
次回は最後のコンテンツとなる"3. 「コンテナ環境でも利用できるのか」" について記載しています。宜しければご覧ください。
今回の内容は以上となります。本記事が何かの参考となれば幸いです。