こんにちは、たびとです。
前回、Windows 版を作ったので、今回は Linux 版を作ります。 いつものように、WSL2/Ubuntu で作ってみたいと思います。
前回の Windows 版はこちらから。
この記事の対象者
事前準備
前回も IP アドレスを確認しましたが、ここでの IP アドレスは Log2Console の出力用に使います。 よって、nlog.config の localhost を 172.18.149.227 に指定することになります。 当然ですが、各自の IP アドレスに置き換えてください。
注意:「イーサネット アダプター vEthernet (WSL)」を指定しても出力されません。
PS C:\> ipconfig Windows IP 構成 イーサネット アダプター イーサネット: 接続固有の DNS サフィックス . . . . .: mshome.net リンクローカル IPv6 アドレス. . . . .: fe80::c441:9249:4a11:3714%7 IPv4 アドレス . . . . . . . . . . . .: 172.18.149.227 サブネット マスク . . . . . . . . . .: 255.255.240.0 デフォルト ゲートウェイ . . . . . . .: 172.18.144.1
gRPC サーバ
WSL2/Ubuntu のコンソール上で、dotnet コマンドで GrpcGreeter を作成します。 dotnet new コマンドを実行するときにパスを指定しないと、直下のディレクトリ名がプロジェクト名になります。
$ mkdir GrpcGreeter $ cd GrpcGreeter $ dotnet new grpc The template "ASP.NET Core gRPC Service" was created successfully. Processing post-creation actions... Running 'dotnet restore' on /home/tabito/GrpcGreeter/GrpcGreeter.csproj... Determining projects to restore... Restored /home/tabito/GrpcGreeter/GrpcGreeter.csproj (in 8.68 sec). Restore succeeded.
NLog.Web.AspNetCore パッケージを追加します。
$ dotnet add package NLog.Web.AspNetCore Determining projects to restore... Writing /tmp/tmpKNtG2H.tmp 以下省略
nlog.config ファイルを作成します。
$ touch nlog.config
ソースコード
Visual Studio Code をインストールしている場合、code コマンドで Windows 上で編集することができて便利です。
$ code . Updating VS Code Server to version 6d9b74a70ca9c7733b29f0456fd8195364076dda Removing previous installation... Installing VS Code Server for x64 (6d9b74a70ca9c7733b29f0456fd8195364076dda) Unpacking: 100% Unpacked 2416 files and folders to /home/tabito/.vscode-server/bin/6d9b74a70ca9c7733b29f0456fd8195364076dda.
launchSettings.json を編集します。 IP アドレスを指定してアクセスできるように、applicationUrl の localhost を 0.0.0.0 に変更します。
{ "profiles": { "GrpcGreeter": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": false, "applicationUrl": "http://0.0.0.0:5045;https://0.0.0.0:7045", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } }
GrpcGreeter.csproj を編集します。 ItemGroup を新たに追加し、nlog.config をプロジェクトに追加します。
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <None Include="nlog.config"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup> <ItemGroup> <Protobuf Include="Protos\greet.proto" GrpcServices="Server" /> </ItemGroup> <ItemGroup> <PackageReference Include="Grpc.AspNetCore" Version="2.40.0" /> <PackageReference Include="NLog.Web.AspNetCore" Version="5.1.0" /> </ItemGroup> </Project>
nlog.config を編集します。NLogViewer の localhost は各自の IP アドレスを指定します。
tcpOutlet の address="tcp4://...
の tcp4 を tcp に変更すると IPv6 の出力に切替わります。
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="Info" internalLogFile="./logs/internal-nlog-gRPC.log"> <extensions> <add assembly="NLog.Web.AspNetCore"/> </extensions> <targets> <!-- File --> <target name="logFile" xsi:type="File" encoding="UTF-8" writeBom="true" lineEnding="LF" layout="${longdate} ${level:uppercase=true:padding=-5} [${threadid}] ${logger} - ${message} ${exception:format=tostring}" fileName="${basedir}/logs/grpcgreeter.log" archiveFileName="${basedir}/logs/backup/grpcgreeter_{###}.log" archiveEvery="Day" archiveNumbering="Sequence" maxArchiveFiles="10" /> <!-- Console --> <target name="console" xsi:type="ColoredConsole" layout="${level:uppercase=true:padding=-5}: ${message}" /> <!-- Log2Console: WSLのアドレスではなく、通常のIPアドレスを指定すること! --> <target name="tcpOutlet" xsi:type="NLogViewer" address="tcp4://172.18.149.227:4505"/> </targets> <!-- rules to map from logger name to target --> <rules> <logger name="*" minlevel="Trace" writeTo="logFile" /> <logger name="*" minlevel="Trace" writeTo="console" /> <logger name="*" minlevel="Trace" writeTo="tcpOutlet" /> </rules> </nlog>
Program.cs を編集します。NLog が有効になるように追加します。
using GrpcGreeter.Services; using NLog; using NLog.Web; var _logger = NLog.LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); _logger.Debug("init main"); try { var builder = WebApplication.CreateBuilder(args); // Additional configuration is required to successfully run gRPC on macOS. // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682 // NLog: Setup NLog for Dependency injection builder.Logging.ClearProviders(); builder.Host.UseNLog(); // Add services to the container. builder.Services.AddGrpc(); var app = builder.Build(); // Configure the HTTP request pipeline. app.MapGrpcService<GreeterService>(); app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); app.Run(); } catch (Exception ex) { // NLog: catch setup errors _logger.Error(ex, "Stopped program because of exception"); throw; } finally { // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux) NLog.LogManager.Shutdown(); }
ビルド&実行
donet build コマンドでビルドします。
$ dotnet build MSBuild version 17.3.0+92e077650 for .NET Determining projects to restore... All projects are up-to-date for restore. GrpcGreeter -> /home/tabito/GrpcGreeter/bin/Debug/net6.0/GrpcGreeter.dll Build succeeded. 0 Warning(s) 0 Error(s) Time Elapsed 00:00:04.50
dotnet run コマンドで実行します。
$ dotnet run Building... DEBUG: init main TRACE: Discovering gRPC methods for GrpcGreeter.Services.GreeterService. TRACE: Added gRPC method 'SayHello' to service 'greet.Greeter'. Method type: 'Unary', route pattern: '/greet.Greeter/SayHello'. DEBUG: Hosting starting DEBUG: Using development certificate: CN=localhost (Thumbprint: 45FD30CC9749529649024AD1EAD628123590A96E) INFO : Now listening on: http://0.0.0.0:5045 INFO : Now listening on: https://0.0.0.0:7045 DEBUG: Loaded hosting startup assembly GrpcGreeter INFO : Application started. Press Ctrl+C to shut down. INFO : Hosting environment: Development INFO : Content root path: /home/tabito/GrpcGreeter/ DEBUG: Hosting started
Log2Console を起動しておくと、上記のログを確認することができます。 Log2Console の詳細は、前回 Windows 版の記事を参考にしてください。
gRPC クライアント
WSL2/Ubuntu のコンソールを新たに開き、dotnet コマンドで GrpcGreeterClient を作成します。
$ mkdir GrpcGreeterClient $ cd GrpcGreeterClient $ dotnet new console The template "Console App" was created successfully. Processing post-creation actions... Running 'dotnet restore' on /home/tabito/GrpcGreeterClient/GrpcGreeterClient.csproj... Determining projects to restore... Restored /home/tabito/GrpcGreeterClient/GrpcGreeterClient.csproj (in 86 ms). Restore succeeded.
gRPC と NLog パッケージを追加します。
$ dotnet add package Google.Protobuf $ dotnet add package Grpc.Net.Client $ dotnet add package Grpc.Tools $ dotnet add package NLog
サーバ用の greet.proo をコピーします。
$ mkdir Protos $ cp ../GrpcGreeter/Protos/greet.proto ./Protos
nlog.config ファイルを作成します。
$ touch nlog.config
ソースコード
greeto.proto を編集します。 csharp_namespace はサーバ用なので、 GrpcGreeterClient に変更します。
syntax = "proto3"; option csharp_namespace = "GrpcGreeterClient"; package greet; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply); } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings. message HelloReply { string message = 1; }
GrpcGreeterClient.csproj を編集します。 gRPC と NLog を有効にするため、それぞれの ItemGroup を追加します。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <None Update="nlog.config"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup> <ItemGroup> <Protobuf Include="Protos\greet.proto" GrpcServices="Client" /> </ItemGroup> <ItemGroup> <PackageReference Include="Google.Protobuf" Version="3.21.5" /> <PackageReference Include="Grpc.Net.Client" Version="2.47.0" /> <PackageReference Include="Grpc.Tools" Version="2.48.0"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> <PackageReference Include="NLog" Version="5.0.2" /> </ItemGroup> </Project>
nlog.config を編集します。 NLogViewer の localhost は各自の IP アドレスを指定します。
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" throwExceptions="false" internalLogLevel="Info" internalLogFile="../logs/internal-nlog-Console.log"> <targets> <!-- File --> <target name="logFile" xsi:type="File" encoding="UTF-8" writeBom="true" lineEnding="CRLF" layout="${longdate} ${level:uppercase=true:padding=-5} [${threadid}] ${logger} - ${message} ${exception:format=tostring}" fileName="../logs/${processname}.log" archiveFileName="../logs/backup/${processname}_{###}.log" archiveEvery="Day" archiveNumbering="Sequence" maxArchiveFiles="10" /> <!-- Console --> <target name="console" xsi:type="ColoredConsole" layout="${level:uppercase=true:padding=-5}: ${message}" /> <!-- Log2Console: WSLのアドレスではなく、通常のIPアドレスを指定すること! --> <target name="tcpOutlet" xsi:type="NLogViewer" address="tcp4://172.18.149.227:4505"/> </targets> <rules> <logger name="*" minlevel="Debug" writeTo="logFile" /> <logger name="*" minlevel="Trace" writeTo="console" /> <logger name="*" minlevel="Trace" writeTo="tcpOutlet" /> </rules> </nlog>
WSL2/Ubuntu の IP アドレスを取得します。
$ ip a show dev eth0 6: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:15:5d:88:c1:b8 brd ff:ff:ff:ff:ff:ff inet 172.18.126.178/20 brd 172.18.127.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::215:5dff:fe88:c1b8/64 scope link valid_lft forever preferred_lft forever
Program.cs を編集します。サーバの IP アドレスは ip コマンドで取得した IP アドレスを指定します。
using System.Net.Http; using System.Threading.Tasks; using Grpc.Net.Client; using GrpcGreeterClient; using NLog; var _logger = LogManager.GetCurrentClassLogger(); var server = "https://172.18.126.178:7045"; _logger.Info(server); try { var option = new GrpcChannelOptions() { HttpClient = new HttpClient(new HttpClientHandler { // SSL証明書の検証で常に True を返す ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, }) }; var channel = GrpcChannel.ForAddress(server, option); var client = new Greeter.GreeterClient(channel); var response = await client.SayHelloAsync(new HelloRequest { Name = "World" }); _logger.Info(response.Message); } catch (Exception ex) { _logger.Error(ex); }
ビルド&実行
donet build コマンドでビルドします。
$ dotnet build MSBuild version 17.3.0+92e077650 for .NET Determining projects to restore... All projects are up-to-date for restore. GrpcGreeterClient -> /home/tabito/GrpcGreeterClient/bin/Debug/net6.0/GrpcGreeterClient.dll Build succeeded. 0 Warning(s) 0 Error(s) Time Elapsed 00:00:00.88
dotnet run コマンドで実行します。
$ dotnet run INFO : https://172.18.126.178:7045 INFO : Hello World
サーバ側のコンソールには、以下のようにメッセージが出力されています。
DEBUG: Connection id "0HMK0ATIV0DCR" accepted. DEBUG: Connection id "0HMK0ATIV0DCR" started. DEBUG: Connection 0HMK0ATIV0DCR established using the following protocol: Tls13 TRACE: Connection id "0HMK0ATIV0DCR" sending SETTINGS frame for stream ID 0 with length 18 and flags NONE. TRACE: Connection id "0HMK0ATIV0DCR" sending WINDOW_UPDATE frame for stream ID 0 with length 4 and flags 0x0. TRACE: Connection id "0HMK0ATIV0DCR" received SETTINGS frame for stream ID 0 with length 12 and flags NONE. TRACE: Connection id "0HMK0ATIV0DCR" sending SETTINGS frame for stream ID 0 with length 0 and flags ACK. TRACE: Connection id "0HMK0ATIV0DCR" received WINDOW_UPDATE frame for stream ID 0 with length 4 and flags 0x0. TRACE: Connection id "0HMK0ATIV0DCR" received SETTINGS frame for stream ID 0 with length 0 and flags ACK. TRACE: Connection id "0HMK0ATIV0DCR" received HEADERS frame for stream ID 1 with length 189 and flags END_HEADERS. TRACE: Connection id "0HMK0ATIV0DCR" received DATA frame for stream ID 1 with length 12 and flags NONE. TRACE: Connection id "0HMK0ATIV0DCR" received DATA frame for stream ID 1 with length 0 and flags END_STREAM. INFO : Request starting HTTP/2 POST https://172.18.126.178:7045/greet.Greeter/SayHello application/grpc - DEBUG: Wildcard detected, all requests with hosts will be allowed. TRACE: All hosts are allowed. DEBUG: 3 candidate(s) found for the request path '/greet.Greeter/SayHello' DEBUG: Endpoint 'gRPC - /greet.Greeter/SayHello' with route pattern '/greet.Greeter/SayHello' is valid for the request path '/greet.Greeter/SayHello' DEBUG: Endpoint 'gRPC - Unimplemented method for greet.Greeter' with route pattern 'greet.Greeter/{unimplementedMethod}' is valid for the request path '/greet.Greeter/SayHello' DEBUG: Endpoint 'gRPC - Unimplemented service' with route pattern '{unimplementedService}/{unimplementedMethod}' is valid for the request path '/greet.Greeter/SayHello' DEBUG: Request matched endpoint 'gRPC - /greet.Greeter/SayHello' INFO : Executing endpoint 'gRPC - /greet.Greeter/SayHello' DEBUG: Reading message. DEBUG: Connection id "0HMK0ATIV0DCR", Request id "0HMK0ATIV0DCR:00000001": started reading request body. DEBUG: Connection id "0HMK0ATIV0DCR", Request id "0HMK0ATIV0DCR:00000001": done reading request body. TRACE: Deserializing 7 byte message to 'GrpcGreeter.HelloRequest'. TRACE: Received message. TRACE: Connection id "0HMK0ATIV0DCR" sending HEADERS frame for stream ID 1 with length 59 and flags END_HEADERS. DEBUG: Sending message. TRACE: Serialized 'GrpcGreeter.HelloReply' to 13 byte message. TRACE: Message sent. INFO : Executed endpoint 'gRPC - /greet.Greeter/SayHello' TRACE: Connection id "0HMK0ATIV0DCR" sending DATA frame for stream ID 1 with length 18 and flags NONE. TRACE: Connection id "0HMK0ATIV0DCR" sending HEADERS frame for stream ID 1 with length 15 and flags END_STREAM, END_HEADERS. INFO : Request finished HTTP/2 POST https://172.18.126.178:7045/greet.Greeter/SayHello application/grpc - - 200 - application/grpc 213.0363ms TRACE: Connection id "0HMK0ATIV0DCR" received PING frame for stream ID 0 with length 8 and flags NONE. TRACE: Connection id "0HMK0ATIV0DCR" sending PING frame for stream ID 0 with length 8 and flags ACK. DEBUG: Connection id "0HMK0ATIV0DCR" received FIN. DEBUG: Connection id "0HMK0ATIV0DCR" is closed. The last processed stream ID was 1. DEBUG: Connection id "0HMK0ATIV0DCR" sending FIN because: "The client closed the connection." DEBUG: Connection id "0HMK0ATIV0DCR" stopped.
Log2Console は、以下のようにサーバとクライアントの両方のログが出力されます。
SSL 証明書はどうなるの?
何事もなく動作したので忘れていましたが、Linux の場合は SSL 証明書はどうなるのでしょう。 Windows と同様に裏側でデフォルトの証明書が作成されるのでしょうか。
最初から WSL2/Ubuntu を作って確認すると、初回作成時に開発用の証明書がインストールされてました。 この表示は初回のみに表示されるため、2回目以降は表示されないようです。
$ dotnet new grpc .NET 6.0 へようこそ! --------------------- SDK バージョン: 6.0.108 ---------------- ASP.NET Core の HTTPS 開発証明書をインストールしました。 証明書を信頼するには、'dotnet dev-certs https --trust' (Windows および macOS のみ) を実行します。 HTTPS の詳細については、https://aka.ms/dotnet-https を参照してください ---------------- 最初のアプリを作成するには、https://aka.ms/dotnet-hello-world を参照してください 最新情報については、https://aka.ms/dotnet-whats-new を参照してください ドキュメントを探索するには、https://aka.ms/dotnet-docs を参照してください GitHub で問題の報告とソースの検索を行うには、https://github.com/dotnet/core を参照してください 'dotnet --help' を使用して使用可能なコマンドを確認するか、https://aka.ms/dotnet-cli にアクセスしてください -------------------------------------------------------------------------------------- テンプレート "ASP.NET Core gRPC Service" が正常に作成されました。 作成後の操作を処理しています... /home/tabito/grpc_greeter/src/GrpcGreeter/GrpcGreeter.csproj で ' dotnet restore ' を実行しています... Determining projects to restore... Restored /home/tabito/grpc_greeter/src/GrpcGreeter/GrpcGreeter.csproj (in 10.52 sec). 正常に復元されました。
数年前、.NET 5 で作ったときは、SSL 証明書を openssl コマンドで作成して利用したのを思い出しました。 とりあえず、次回への課題としましょう。
まとめ
今回は、WSL2/Ubuntu 上の .NET 6 で gRPC アプリを作成してみました。 サーバ用途は Linux (docker)で、GUI を作るなら Windows が現時点でのベストだと思うので、 どちらでも作れる .NET Core は便利だと思います。 ただ、まだまだ情報が少ないため、やりたいことを実現するための調査に苦労することが多いです。
以前、.NET Core の Windows 版と Linux 版を説明したときは、Windows 版を Linux へコピーしましたが、 今回はコマンドで作成してみました。
ちなみに、dotnet new sln コマンドを使うと、ソリューションファイル(sln) を作成することができます。 このソリューションファイルにプロジェクトファイルを追加することで Windows と同様の構成にすることができます。 そんな面倒なことをするより、Windows でプロジェクトを作成して、Linux にコピーした方が早いと思います。
今回の続きで、Docker 版を掲載しました。ここでは、今回使わなかった証明書も作成しています。
最後に参考サイトを掲載しておきます。 では、皆さん、よい旅を。