tech.guitarrapc.cóm

Technical updates

TopShelf によるWindowsサービスの配置をDSCで自動化してみよう

しばらく C# のみ触っており その中で LightNode + TopShelf を使ったセルフホストな Windows サービスを構築していました。

運用を自動化するために、CIでビルドした生成物を、どうやって本番にデプロイするのかが懸念となります。これを行うために 久々にDSC リソースを書いたので記事にしてみましょう。

開発 => コミット => ビルド => デプロイまでの自動化がなされるので、DSCの使いどころの参考になれば幸いです。

目次

TopShelfってなに

TopShelf は、Windowsサービスを簡単お手軽に作成、デバッグするのためのライブラリです。チュートリアルなどが豊富なので、見るといい感じでわかるでしょう。

Topshelf Key Concepts — Topshelf 3.0 documentation

github.com

qiita.com

TopShelf を使うと、通常のコンソールアプリとして動作可能なのでとにかくデバッグが捗ります。Windowsサービスを普通に作るより圧倒的にいいです。

今回ごく単純なプロジェクトを書いてあるので、これを自動配置できることを目標にします。

中身はいたって簡単です。

ソリューションを作成したら、NuGet で TopShelfを追加します。

Install-Package TopShelf

Program.cs の中身は、Service クラス を呼びだしておきます。無駄に C# 6.0 を使っています。*1

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Topshelf;

namespace SampleTopShelfService
{
    class Program
    {
        private static readonly string _serviceName = "SampleTopShelfService";
        private static readonly string _displayName = "SampleTopShelfService";
        private static readonly string _description = "SampleTopShelfService Description";

        static void Main(string[] args) => HostFactory.Run(x =>
        {
            x.EnableShutdown();

            // Reference to Logic Class
            x.Service<Service>(s =>
            {
                s.ConstructUsing(name => new Service(_serviceName));
                s.WhenStarted(sc => sc.Start());
                s.WhenStopped(sc => sc.Stop());
            });

            // Service Start mode
            x.StartAutomaticallyDelayed();

            // Service RunAs
            x.RunAsLocalSystem();

            // Service information
            x.SetServiceName(_serviceName);
            x.SetDisplayName(_displayName);
            x.SetDescription(_description);
        });
    }
}

Service.cs の中身です。動作確認のためだけなので、何の意味もありません。

サービス開始時に呼ぶ Start() メソッドと、停止時に呼ぶ Stop() メソッドのみ実装してあります。

using System;
using System.Collections.Generic;
using System.Diagnostics.Eventing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleTopShelfService
{
    internal class Service
    {
        public string ServiceName { get; private set; }

        public Service(string serviceName)
        {
            this.ServiceName = serviceName;
        }

        public void Start()
        {
            Console.WriteLine("Running Service");
        }

        public void Stop()
        {
            Console.WriteLine("Stopping Service");
        }
    }
}

ビルドすると SampleTopShelfService.exe が生成されるので、これを実行すればコンソールアプリケーションとして起動します。

管理者権限の cmd や PowerShell から SampleTopShelfService.exe install とすれば、サービスとしてインストールできます。アンインストールは、 SampleTopShelfService.exe uninstall です。

たったこれだけで Windows サービスが作れるのは TopShelf ラクチン便利!

DSC リソースの作成と配置

さて、TopShelf のサンプルアプリができたのですが TopShelfサービスを自動インストール、アンインストールするDSCリソースは「当然ありません」。はい、知ってた。なければさくっと作ろう、で作成したリソースが Grani_TopShelfです。

DSCリソースの細かい作り方は続いているか謎の アドベントカレンダーやBuildInsiderをみていただくとして。

www.buildinsider.net

リソースの中核ロジックになる *-TargetResource 関数のみ抜き出すとこんな感じです。まぁ、シンプル...かなぁ。。?PowerShellでこれ以上こまごまとはしたくないですね、正直。Test-TargetResource では Get-TargetResource を呼ぶだけ。鉄則です。

gist.github.com

作成したリソースを含む、GraniResource モジュールを、$env:ProgramFiles\WindowsPowerShell\Modules を配置します。

DSC Configuration の記述

Grani_TopShelf で TopShelf アプリケーションをインストールするサンプルです。

gist.github.com

簡単に説明をします。

  • Path に、ビルドした TopShelf アプリの .exeまでのフルパスを指定します
  • ServiceName に、ビルドした TopShelf アプリのサービス名を指定します
  • Ensure は、TopShelf アプリケーションのインストール/アンインストールを行います。 Present で インストール。Absentでアンインストールです。この時、TopShelfアプリケーションかどうかの検証に、サービスで実行されたプロセスのパスがPathと一致するかみています

アンインストールするならこうですね。EnsureプロパティにAbsentを指定するだけ!簡単。

gist.github.com

ビルドした成果物をデプロイする流れ

今回はJenkins で CIして、ビルド成果物をS3に上げます。あとは、DSCでS3上のコンテンツ変更を検知して、展開すればokです。

Jenkins から S3 へのアップロードは、Jenkins が EC2 などにあれば、Zipファイルに固める -> S3アップロードを簡単なスクリプトで用意できますね。

DSC が担うのは、S3 のパッケージがローカルと異なるものになったかの変更検知、ダウンロード、展開です。これはGraniResourceを使うことで容易に可能です。こんな感じですね。

gist.github.com

cS3Content

ポイントは、S3コンテンツに変更があったのか検知するこのコンフィグレーションです。PreActionで、該当TopShelf アプリケーションを停止して削除する処理をいれています。PreAction は、変更を検知した時にだけ実行されるので、これでサービスが.exeや.dll をハンドルして更新できない問題を回避できます。

Archive

いろいろ問題なのが、標準リソースの MSFT_Archive リソースです。が詳細は末尾で。今回はcS3Content リソースで「変更検知したときにだけ解凍先フォルダを消す」ことで確実に解凍できるようにしています。CheckSumValidateを指定しなければ、名前の一致のみをみるため、基本的にファイルが消えない限り Set-TargetResource は実行されません。

cTopShelf

TopShelf アプリケーションをサービスインストール担保しています。

Service

TopShelf サービスの起動を担保しています。

まとめ

TopShelf サービスの自動デプロイは DSC で容易に可能です。マイナーな需要かもですが、参考になれば幸いです。

cArchive リソース

標準リソースである、MSFT_Archive リソースは、ValidateCheckSumを指定すると、Zipファイルに含まれるファイルエントリーそれぞれの Stream と、展開された実ファイルの File Stream それぞれについてハッシュ値を比較して変更検知できます。ただし、File Stream を用いるため、ファイルを使っているとこけます。見事に。この問題を回避するため「実ファイルの場合はFile Stream ではなくファイルパスからハッシュ値を計算する」ようにMSFT_Archive リソースに修正をいれたのが cArhiveリソースです。使いませんでしたが、参考程度に。

gist.github.com

*1:Expression Body を ReSharper せんせーから使えと調教されるアレです