.NET Core 3.0 で gRPC サーバーをコンテナー化して Azure にデプロイしてみた

こんにちは、アーキテクトの小林です。
さて、前回は .NET Core 3.0 で gRPC の統合が素晴らしい件をご紹介しました。
今回は gRPC をコンテナー化して Azure にデプロイしようと思います。

f:id:ecb_mkobayashi:20190924113406p:plain

※本記事は下記の記事の続編となっています。記事後半の手順は、前回の記事で作成した成果物をつかっていますので、実際に手を動かしてみたい方は、前回の記事からはじめてください。
blog.ecbeing.tech

Azure App Serivce の gRPC 対応は現在進行中

ここで少しばかり残念なお知らせがあります。 gRPC はまだ IIS や Azure App Serivce に乗せることができません。あまりに質問が多かったのか、Microsoft Docs にドドーンと書いてあります。
“gRPC not supported on Azure App Service”
docs.microsoft.com
どうやら gRPC が依存している「HTTP response trailing headers」に Http.sys が対応していないためだそうです。 Http.sys は Windows のカーネルモードドライバーですので、これに依存している IIS では gRPC が動作しない、Azure App Serivce は IIS をつかっているので動作しない、という話の流れなっています。

GitHub の Issue でこの件に関してやりとりが継続して行われています。

Host grpc service in iis or as an app service?
github.com
Windows のカーネルを変更するのは Windows Update を伴うので Microsoft のスローなプロセスで展開されるため、リリース時期は未定とのこと。このやりとりの中で、Microsoft の Sourabh Shirhatti さんが以下のようなコメントを投稿しています。

This is currently not supported on App Service for Linux. We are currently working with App Service to enable this, but I don't have any ETA to share

Azure App Service on Linux は IIS に依存していないはずですので、こちらは Http.sys というカーネルモードドライバーの話ではなく App Service の作りの問題かと思います。リリース時期については言及を避けていますが、2019年11月には .NET Core 3.1 がLTS 版としてリリースされますので、その頃までにはきっとやり遂げてくれるのではないかな...、と期待しています。 この Issue を定期的にウォッチしていきたいと思います。

gRPC を Azure にホストする場合の推奨は Kubernetes

ということで、2019年10月現在で Azure にホストする方法は、AKS(Azure Kubernetes Service)が推奨されています。
でも Kubernetes なんです。敷居が高いんですよね。僕の脳みそはまだ Kubernetes を受け入れる準備ができていないため、苦難の道のりになりそうだな…と思っていました。

実は Azure にはコンテナーのホストするのに対応したサービスが他にもいろいろ提供されており、コンテナーのニーズによって選択できるようになっています。

azure.microsoft.com

  • Azure Kubernetes Service Kubernetes のデプロイ、管理、運用を簡略化する
  • Azure App Service Web およびモバイル向けのパワフルなクラウド アプリを短期間で作成
  • Azure Container Instances サーバーを管理することなく Azure でコンテナーを簡単に実行
  • Azure Batch クラウド規模のジョブ スケジュール設定とコンピューティング管理
  • Azure Service Fabric Windows または Linux でのマイクロサービスの開発とコンテナーのオーケストレーション

ちなみに Azure Container Instances は AKS と組み合わせて利用することが可能で、AKS からは Virtual Kubelet という概念でラッピングされています。コンテナーオーケストレーターとして Kubernetes が、コンテナーの実行インスタンスとして Azure Container Instances という構成ができるそうです。

AKS を使いこなせるようになれば完璧なのですが、今回は App Service が gRPC に対応するまでのつなぎという位置づけなので、もっとも簡単に Docker コンテナーをホストできる Azure Container Instances でやってみることにしました!

なお、AWS には Fargate という同じくお手軽にコンテナーを実行できるサービスがありまして、こっちは Azure Container Instances よりも高機能で扱いやすいです。今すぐ .NET Core の gRPC をコンテナー化してクラウドにホストしたいということでしたら、AWS Fargate が楽そうに思います。

Docker Desktop community のインストール

前回の記事で作成した gRPC サーバーアプリケーションを Linux コンテナーのイメージ化を行います。 この記事ではイメージ作成作業を Windows 環境で行いますので Docker Desktop のインストールが必要です。 Docker Desktop for Windows のインストールには前提条件があります。以下は必要な前提条件です。

  • Windows 10 Pro (64bit)
  • Hyper-V(Windows の機能の有効化)
    • CPUの仮想化機能が有効になっていること

Docker Desktop for Windows
https://hub.docker.com/

インストールできたら docker -v コマンドで確認します。

D:\Work\dotnetcore\MyFirstGrpc\src\MyFirstGrpc.Grpc>docker -v
Docker version 19.03.2, build 6a30dfc

※これ以降のコマンドはすべて前回の記事で作成した gRPC サーバーアプリケーションをカレントディレクトリにして実行してください。

動作確認したバージョンは以下の通りです。 f:id:ecb_mkobayashi:20191015100121p:plain

サーバー証明書の準備

クラウド上で gRPC を実行するためにはサーバー証明書が必要です。 Visual Studioでデバッグ実行する際は勝手にローカルPCに自己署名証明書をインストールしてくれましたが、Azure にデプロイする場合は、コンテナーのイメージ内に証明書が必要になりますし、DNS 名も localhost では都合が悪いため、自己署名証明書を実際にホストする名前で作成してみたいと思います。

自己署名証明書は PowerShell で簡単に作成することができます。 Windows のスタートボタンを右クリックして Windows PowerShell を起動して以下のコマンドを実行してください。

New-SelfSignedCertificate -CertStoreLocation Cert:\CurrentUser\My `
-DnsName "ここに自分が命名したい名前を入れてください.japaneast.azurecontainer.io" `
-KeyExportPolicy Exportable -KeyLength 2048 `
-TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1") `
-NotAfter (Get-Date).AddYears(5)

このコマンドで、有効期限が5年間の自己署名サーバー証明書を作成しています。
DnsName には Azure Container Instances で使用可能な名前をつかっています。すでに存在する名前を使えませんのでかぶりにくい名前を命名してください。
なお、引数の詳細については New-SelfSignedCertificate コマンドのヘルプをご覧いただければと思います。

コマンドを実行すると個人の証明書の中に証明書が作成されます。
証明書の管理画面はタスクバーの検索から「証明書」と検索すれば出てきます。 f:id:ecb_mkobayashi:20191015101018p:plain

以下のように証明書が作成されています。
この証明書を「信頼されたルート証明機関」...「証明書」にドラッグアンドドロップしてください。
f:id:ecb_mkobayashi:20191015101100p:plain

警告が出ますが問題ありません。「はい」と答えましょう。
f:id:ecb_mkobayashi:20191015101213p:plain

「信頼されたルート証明機関」内に証明書が移動できたら、その証明書をダブルクリックして「詳細」を選び「ファイルにコピー」をクリックします。
f:id:ecb_mkobayashi:20191015101250p:plain

証明書のエクスポートウィザードがはじまります。
f:id:ecb_mkobayashi:20191015101328p:plain

ここでは秘密キーのエクスポートを選択してください。
f:id:ecb_mkobayashi:20191015101352p:plain

この画面はデフォルトのままでOKです。
f:id:ecb_mkobayashi:20191015101430p:plain

秘密キーのパスワードを入力する必要があります。
通常は十分に長い文字列を指定しますが、一旦 password でOKです。
f:id:ecb_mkobayashi:20191015101514p:plain

ファイルを置く場所は前の記事で作成した gRPC サーバーアプリケーションのフォルダ直下にします。
f:id:ecb_mkobayashi:20191015101545p:plain
ファイル cert.pfx が生成されていることを確認してください。

gRPCサーバーに証明書を設定

gRPC サーバーアプリケーションのプログラムを調整して、先ほど発行した自己署名証明書を Kestrel が使用し、Dockerのイメージに証明書が入るようにする必要があります。
最初に、Visual Studioのソリューションエクスプローラーで cert.pfx のプロパティから「出力ディレクトリにコピー」を「新しい場合はコピーする」にします。
f:id:ecb_mkobayashi:20191015101649p:plain

つづいて、Program.csを開いて、以下の設定を追加します。

webBuilder.ConfigureKestrel(options =>
{
    options.Listen(IPAddress.Any, 443, listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http2;
        listenOptions.UseHttps("cert.pfx", "password"); // ここに証明書のエクスポートで入力したパスワードを指定します
    });
});

※ここでは便宜上パスワードをハードコーディングしていますが、パスワードのような重要な情報をソースコード内にハードコーディングしてはいけません。コンテナー実行環境では環境変数を通じて引き渡すようにします。

Program.csのできあがりは以下のようになります。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Hosting;
namespace MyFirstGrpc.Grpc
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }
        // 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
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.ConfigureKestrel(options =>
                    {
                        options.Listen(IPAddress.Any, 443, listenOptions =>
                        {
                            listenOptions.Protocols = HttpProtocols.Http2;
                            listenOptions.UseHttps("cert.pfx", "password");
                        });
                    });
                    webBuilder.UseStartup<Startup>();
                });
    }
}

Dockerfile を作成

引き続き、gRPC サーバーアプリケーションの直下に Dockerfile をというファイル名のテキストファイルを作成します。
Dockerfile に記述する内容は以下のようになります。

FROM mcr.microsoft.com/dotnet/core/aspnet:3.0.0-alpine AS base
WORKDIR /app
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:3.0.100 AS publish
WORKDIR /src
COPY . ./
RUN dotnet publish -c Release -o publish

FROM base AS final
WORKDIR /app
COPY --from=publish /src/publish .
ENTRYPOINT ["dotnet", "MyFirstGrpc.Grpc.dll"]

それぞれの命令について解説していきます。

FROM mcr.microsoft.com/dotnet/core/aspnet:3.0.0-alpine AS base
WORKDIR /app
EXPOSE 443

この命令は Alpine Linux(非常に軽量な Linux ディストリビューション)に ASP.NET Core の実行に必要な Runtime のみを入れた公式イメージをベースにするように指定しています。 作業ディレクトリは app で 443 ポートをリッスンすることを Docker に伝えています。

FROM mcr.microsoft.com/dotnet/core/sdk:3.0.100 AS publish
WORKDIR /src
COPY . ./
RUN dotnet publish -c Release -o publish

この命令は .NET Core SDK の入った Debian の公式イメージ上でプログラムを発行(パブリッシュ)しています。 .NET Core SDK の入った公式イメージはとてもサイズが大きいので、可搬性に優れていません。SDK の入った公式イメージは発行の命令に使うだけで、最終的な出力イメージには含まれません。

FROM base AS final
WORKDIR /app
COPY --from=publish /src/publish .
ENTRYPOINT ["dotnet", "MyFirstGrpc.Grpc.dll"]

ここで最初の命令で宣言した Alpine Linux の公式イメージに発行したプログラムをコピーしています。 ENTRYPOINTではコンテナー起動時に実行されるプロセスを指定します。ここで gRPC のプロジェクトのDLLを指定します。

なお、Visual Studio で新しくプロジェクトを作成する際に「Docker サポートを有効にする」を選んだ場合でもこの Dockerfile を生成してくれます。ただし、Visual Studio が作成する Dockerfile には、プロジェクト名に由来する情報がたっぷり含まれてしまうため、これらを含まないように削除して、なるべくシンプルになるように構成しています。

docker build でコンテナーのイメージを作成する

イメージを作成する準備が整いましたので、コマンドプロンプトから以下のように docker build コマンドを実行してイメージをビルドしてください。

docker build -t myfirstgrpc.grpc .

しばらくたつとビルドが完了しますので、docker images コマンドを実行して確認してください。

docker images
REPOSITORY                             TAG                 IMAGE ID            CREATED             SIZE
myfirstgrpc.grpc                       latest              ee163a01bcf4        11 minutes ago      106MB
<none>                                 <none>              d9ee996792e4        11 minutes ago      770MB
mcr.microsoft.com/dotnet/core/sdk      3.0.100             4422e7fb740c        2 weeks ago         689MB
mcr.microsoft.com/dotnet/core/aspnet   3.0.0-alpine        1cb704cc94a0        2 weeks ago         104MB

イメージが出来たらこれを Azure コンテナー レジストリにアップロードする必要があります。
Azure コンテナー レジストリは事前に作成する必要がありますので、Azure ポータルにアクセスして、コンテナーレジストリを追加してください。

ここではmyfirstgrpcというレジストリ名を付けました。このあとの操作でレジストリ名から付与される myfirstgrpc.azurecr.io という名前が頻繁に使われますので、ご自身で付けたレジストリ名に置き換えて実行してください。
また、Docker からイメージを簡単にアップロードできるようになりますので管理者ユーザーを「有効」を選択してください。
f:id:ecb_mkobayashi:20191015102415p:plain

コンテナーレジストリーが作成できたら、ユーザー名とパスワードをメモ帳にコピーしておきましょう。
f:id:ecb_mkobayashi:20191015102450p:plain

コマンドプロンプトで以下のように docker login コマンドを実行して、メモ帳にコピーしたユーザー名とパスワードでログインしてください。

docker login myfirstgrpc.azurecr.io
Username: myfirstgrpc
Password:
Login Succeeded

つづいて Docker イメージにコンテナーレジストリーの名前に沿ったタグをつけます。

docker tag myfirstgrpc.grpc myfirstgrpc.azurecr.io/myfirstgrpc.grpc

そして、タグをつけたイメージをコンテナーレジストリーにdocker pushしてください。

docker push myfirstgrpc.azurecr.io/myfirstgrpc.grpc
The push refers to repository [myfirstgrpc.azurecr.io/myfirstgrpc.grpc]
0e9991190867: Pushed
d289e195f534: Pushed
fb059cbc2153: Pushed
f5db7d650ebc: Pushed
54fc4b3d1f37: Pushed
f1b5933fe4b5: Pushed
latest: digest: sha256:4bc04357c365a37504d89f167369c5676af3ed460624c1d9d5befffd8d0e0b91 size: 1578

Azureポータル上でアップロードできていることが確認できました。
f:id:ecb_mkobayashi:20191015102614p:plain

リポジトリをクリックして、latestのタグを選ぶと「実行インスタンス」というメニューを選ぶことができます。
ここからでも Azure Container Instances を作成して実行することができるのですが、実はここから作ると微妙に詳細な設定ができないため注意が必要です。
f:id:ecb_mkobayashi:20191015102702p:plain

この「実行インスタンス」を選ばず、Azure ポータルの「すべてのサービス」から「コンテナーインスタンス」を選択してください。
f:id:ecb_mkobayashi:20191015102742p:plain

追加ボタンをクリックしてください。
f:id:ecb_mkobayashi:20191015102813p:plain

コンテナー名は任意の名前で結構です。
その他は以下のような感じで入力・選択してください。地域は東日本を選んでください。
コンテナーレジストリでメモ帳にとっておいた情報も必要です。
f:id:ecb_mkobayashi:20191015102848p:plain

ネットワークタブの「DNS名ラベル名」には自己署名証明書の作成時に付けた名前を設定してください。
ポートは TCP 443 のみを指定してあるようにしてください。
f:id:ecb_mkobayashi:20191015102919p:plain

このような確認画面になりますので作成します。
f:id:ecb_mkobayashi:20191015103011p:plain

しばらくたってコンテナーが出来上がったら、該当コンテナーのリソースに移動して、コンテナーのログを見てください。
443ポートでリスニングを開始していたら成功です。
f:id:ecb_mkobayashi:20191015103122p:plain

前回の記事で作成した gRPC クライアントアプリケーションの接続先をコンテナーインスタンスの作成時設定した URL に打ち換えて実行してみましょう。無事に接続できることを確認できましたか?
f:id:ecb_mkobayashi:20191015103151p:plain

まとめ

今回は .NET Core の gRPC サーバーアプリケーションを Azure Container Instances で実行するまでを手順をご紹介しました。
自己署名証明書の作成から、Docker イメージの作成、Docker イメージの Azure コンテナーレジストリへのアップロード、コンテナーインスタンスの起動と、結構な手順が必要になってしまいましたが、これを AKS で実行する場合、最後のコンテナーインスタンスの起動の手順が AKS に代わるだけ(だろうと…)思います。

.NET Core のアプリケーションをコンテナー化して、クラウドで実行するために必要な手順はほぼ共通していますので、一回やってみて覚えてしまえば、この知識は Azure でも AWS でも活用できる知識となります。

皆さんもぜひ .NET Core で gRPC サーバーアプリケーションの Docker イメージを作成し、クラウドで実行するまでの手順を体験してみていただければと思います。
それではまた。


~ecbeingでは .NET Core アプリケーションのコンテナー化に興味のある、イケてるエンジニアを大募集中です!~ careers.ecbeing.tech