しばらくC# のみ触っておりその中でLightNode + TopShelfを使ったセルフホストなWindowsサービスを構築していました。
運用を自動化するために、CIでビルドした生成物を、どうやって本番にデプロイするのかが懸念となります。これを行うために久々にDSCリソースを書いたので記事にしてみましょう。
開発 => コミット => ビルド => デプロイまでの自動化がなされるので、DSCの使いどころの参考になれば幸いです。
TopShelfってなに
TopShelfは、Windowsサービスを簡単お手軽に作成、デバッグするのためのライブラリです。チュートリアルなどが豊富なので、見るといい感じでわかるでしょう。
https://topshelf.readthedocs.org/en/latest/overview/faq.html
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をみていただくとして。
リソースの中核ロジックになる *-TargetResource関数のみ抜き出すとこんな感じです。まぁ、シンプル...かなぁ。。? PowerShellでこれ以上こまごまとはしたくないですね、正直。Test-TargetResourceではGet-TargetResourceを呼ぶだけ。鉄則です。
作成したリソースを含む、GraniResourceモジュールを、$env:ProgramFiles\WindowsPowerShell\Modulesを配置します。

DSC Configuration の記述
Grani_TopShelfでTopShelfアプリケーションをインストールするサンプルです。
簡単に説明をします。
- Pathに、ビルドしたTopShelfアプリの .exeまでのフルパスを指定します
- ServiceNameに、ビルドしたTopShelfアプリのサービス名を指定します
- Ensureは、TopShelfアプリケーションのインストール/アンインストールを行います。
Presentでインストール。Presentでアンインストールです。この時、TopShelfアプリケーションかどうかの検証に、サービスで実行されたプロセスのパスがPathと一致するかみています
アンインストールするならこうですね。EnsureプロパティにEnsureを指定するだけ! 簡単。
ビルドした成果物をデプロイする流れ
今回はJenkinsでCIして、ビルド成果物をS3に上げます。あとは、DSCでS3上のコンテンツ変更を検知して、展開すればokです。
JenkinsからS3へのアップロードは、JenkinsがEC2などにあれば、Zipファイルに固める -> S3アップロードを簡単なスクリプトで用意できますね。
DSCが担うのは、S3のパッケージがローカルと異なるものになったかの変更検知、ダウンロード、展開です。これはGraniResourceを使うことで容易に可能です。こんな感じですね。
cS3Content
ポイントは、S3コンテンツに変更があったのか検知するこのコンフィグレーションです。PreActionで、該当TopShelfアプリケーションを停止して削除する処理をいれています。PreActionは、変更を検知した時にだけ実行されるので、これでサービスが.exeや.dllをハンドルして更新できない問題を回避できます。
Archive
いろいろ問題なのが、標準リソースのMSFT_Archiveリソースです。が詳細は末尾で。今回はcS3Contentリソースで「変更検知したときにだけ解凍先フォルダを消す」ことで確実に解凍できるようにしています。CheckSumとCheckSumを指定しなければ、名前の一致のみをみるため、基本的にファイルが消えない限りSet-TargetResourceは実行されません。
cTopShelf
TopShelfアプリケーションをサービスインストール担保しています。
Service
TopShelfサービスの起動を担保しています。
まとめ
TopShelfサービスの自動デプロイはDSCで容易に可能です。マイナーな需要かもですが、参考になれば幸いです。
cArchive リソース
標準リソースである、MSFT_Archiveリソースは、ValidateとValidateを指定すると、Zipファイルに含まれるファイルエントリーそれぞれのStreamと、展開された実ファイルのFile Streamそれぞれについてハッシュ値を比較して変更検知できます。ただし、File Streamを用いるため、ファイルを使っているとこけます。見事に。この問題を回避するため「実ファイルの場合はFile Streamではなくファイルパスからハッシュ値を計算する」ようにMSFT_Archiveリソースに修正をいれたのがcArhiveリソースです。使いませんでしたが、参考程度に。
*1:Expression BodyをReSharperせんせーから使えと調教されるアレです