tech.guitarrapc.cóm

Technical updates

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

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

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

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

TopShelfってなに

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

https://topshelf.readthedocs.org/en/latest/overview/faq.html

https://github.com/Topshelf/Topshelf

https://qiita.com/tokumura/items/507820a181652a447864

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 installです。

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

DSC リソースの作成と配置

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

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

https://www.buildinsider.net/enterprise/powershelldsc/02

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

https://gist.github.com/guitarrapc/1a6c194bbc76ef011db8

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

DSC Configuration の記述

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

https://gist.github.com/guitarrapc/10ba4b73d07adc2617f6

簡単に説明をします。

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

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

https://gist.github.com/guitarrapc/b7db8c86978751e566b3

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

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

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

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

https://gist.github.com/guitarrapc/3ef1cc33dc13ef0f3776

cS3Content

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

Archive

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

cTopShelf

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

Service

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

まとめ

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

cArchive リソース

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

https://gist.github.com/guitarrapc/9efbac1c59c8af06ad1c

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