WCF サービスを Azure App Service の Web App for Containers でホストする

はじめに

Azure App Service の Web App for Containers は、Windows コンテナをサポートしているみたい。.NET Framework + WCF のサービスを動かせるかも。

試してみた。

WCF サービスとクライアント

サービスコントラクトとデータコントラクト

using System;
using System.Runtime.Serialization;
using System.ServiceModel;

namespace NetHttpSample.Shared
{
    [ServiceContract]
    public interface ISampleService
    {
        [OperationContract]
        ServerInfo GetServerInfo();
    }

    [DataContract]
    public class ServerInfo
    {
        [DataMember]
        public string MachineName { get; set; }

        [DataMember]
        public string HostName { get; set; }

        [DataMember]
        public string[] IPAddresses { get; set; }
    }
}

WCF サービス

サービス側は NetHttpBinding を使う。Web App for Containers だとフロントで TLS が終端されるみたいなので、コンテナ側では HTTP で待ち受ける。

using System;
using System.Linq;
using System.Net;
using System.ServiceModel;
using System.Threading;
using NetHttpSample.Shared;

namespace NetHttpSample.Server
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var host = new ServiceHost(
                typeof(SampleService),
                new Uri("http://localhost:80"));

            try
            {
                var binding = new NetHttpBinding();
                host.AddServiceEndpoint(
                    typeof(ISampleService),
                    binding,
                    "/SampleService");

                host.Open();

                foreach (var endpoint in host.Description.Endpoints)
                {
                    Console.WriteLine(endpoint.ListenUri);
                }

                Thread.Sleep(-1);
            }
            catch
            {
                host.Close();
            }
        }
    }

    [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
    public class SampleService : ISampleService
    {
        public ServerInfo GetServerInfo()
        {
            var hostName = Dns.GetHostName();
            var ipAddresses = Dns.GetHostAddresses(hostName);
            return new ServerInfo
            {
                MachineName = Environment.MachineName,
                HostName = hostName,
                IPAddresses = ipAddresses.Select(x => x.ToString()).ToArray(),
            };
        }
    }
}

Dockerfile

# ベースイメージとして公式の .NET Framework ランタイムイメージを使用
FROM mcr.microsoft.com/dotnet/framework/runtime:4.8-windowsservercore-ltsc2019

# アプリケーションのディレクトリを作成
WORKDIR /app

# WCF サービスのバイナリをコンテナにコピー
COPY ./bin/Debug/ /app

# 必要なポートを公開
EXPOSE 80

# サービスを起動するためのエントリーポイント
ENTRYPOINT ["/app/NetHttpSample.Server.exe"]

WCF クライアント

クライアント側は NetHttpsBinding を使う。アドレス違うし、バインディングも違うから通信できるか心配だったけど、特に問題なかった。

using System;
using System.Net;
using System.Net.Security;
using System.ServiceModel;
using System.ServiceModel.Channels;
using NetHttpSample.Shared;

namespace NetHttpSample.Client
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string host;
            if (args.Length > 0)
            {
                host = args[0];
            }
            else
            {
                Console.Write("ホスト[localhost]:");
                host = Console.ReadLine();
                if (string.IsNullOrEmpty(host))
                {
                    host = "localhost";
                }
            }

            ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback((a, b, c, d) => true);

            for (var z = 0; z < 3; z++)
            {
                var binding = new NetHttpsBinding
                {
                    AllowCookies = true,
                    UseDefaultWebProxy = false,
                };
                var factory = new ChannelFactory<ISampleService>(
                    binding,
                    new EndpointAddress(
                        new Uri($"https://{host}:443/SampleService"),
                        EndpointIdentity.CreateDnsIdentity("azurewebsites.net")));
                factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;

                for (var y = 0; y < 3; y++)
                {
                    var channel = factory.CreateChannel();
                    for (var x = 0; x < 10; x++)
                    {
                        var result = channel.GetServerInfo();
                        Console.WriteLine($"{nameof(result.MachineName)}: {result.MachineName}");
                    }
                    ((IChannel)channel).Close();
                    Console.WriteLine("----------");
                }
                factory.Close();
                Console.WriteLine("====================");
            }

            Console.WriteLine("Enter で終了");
            Console.ReadLine();
        }
    }
}

デプロイ

  1. Azure ポータルでリソースグループ作成
  2. Azure ポータルでコンテナレジストリ作成
    • アクセスキーの管理者ユーザーを ON にする
  3. コンテナイメージ作成 sh docker build -t nethttpsample:1.0.0 .
  4. コンテナイメージをコンテナレジストリにプッシュ sh az acr login -n <コンテナレジストリ名> docker tag nethttpsample:1.0.0 <コンテナレジストリ名>.azurecr.io/nethttpsample:1.0.0 docker push <コンテナレジストリ名>.azurecr.io/nethttpsample:1.0.0
  5. App Service の Web App for Containers を作成

実行

Web App for Containers のインスタンスを 3 つに増やしてクライアントを実行したら、channel はオープンしている間、同じサーバーにアクセスし続けた。 channel を作り直した場合でも、作成元の ChannelFactory が同じなら、同じサーバーにアクセスし続けた。 ChannelFactory を作り直したら、違うサーバーにアクセスした。

NetTcpBinding と同じで、ChannelFactory が同じなら、channel は同じサーバーに接続し続けることを確認。期待通りの結果だ。

おわりに

レガシーな WCF サービスをどうにかしてモダンなインフラで動かしたいので、Web App for Containers というのはかなりアリだと思った。.NET Framework から .NET に移行するのがベストなのは理解している。ただ、あまりにも工数がかかり過ぎるのでね。