しばらく C# のみ触っており その中で LightNode + TopShelf を使ったセルフホストな Windows サービスを構築していました。
運用を自動化するために、CIでビルドした生成物を、どうやって本番にデプロイするのかが懸念となります。これを行うために 久々にDSC リソースを書いたので記事にしてみましょう。
開発 => コミット => ビルド => デプロイまでの自動化がなされるので、DSCの使いどころの参考になれば幸いです。
目次
TopShelfってなに
TopShelf は、Windowsサービスを簡単お手軽に作成、デバッグするのためのライブラリです。チュートリアルなどが豊富なので、見るといい感じでわかるでしょう。
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をみていただくとして。
リソースの中核ロジックになる *-TargetResource 関数のみ抜き出すとこんな感じです。まぁ、シンプル...かなぁ。。?PowerShellでこれ以上こまごまとはしたくないですね、正直。Test-TargetResource では Get-TargetResource を呼ぶだけ。鉄則です。
作成したリソースを含む、GraniResource モジュールを、$env:ProgramFiles\WindowsPowerShell\Modules
を配置します。
DSC Configuration の記述
Grani_TopShelf で TopShelf アプリケーションをインストールするサンプルです。
簡単に説明をします。
- Path に、ビルドした TopShelf アプリの .exeまでのフルパスを指定します
- ServiceName に、ビルドした TopShelf アプリのサービス名を指定します
- Ensure は、TopShelf アプリケーションのインストール/アンインストールを行います。
Present
で インストール。Absent
でアンインストールです。この時、TopShelfアプリケーションかどうかの検証に、サービスで実行されたプロセスのパスがPathと一致するかみています
アンインストールするならこうですね。Ensure
プロパティにAbsent
を指定するだけ!簡単。
ビルドした成果物をデプロイする流れ
今回は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
とValidate
を指定しなければ、名前の一致のみをみるため、基本的にファイルが消えない限り Set-TargetResource は実行されません。
cTopShelf
TopShelf アプリケーションをサービスインストール担保しています。
Service
TopShelf サービスの起動を担保しています。
まとめ
TopShelf サービスの自動デプロイは DSC で容易に可能です。マイナーな需要かもですが、参考になれば幸いです。
cArchive リソース
標準リソースである、MSFT_Archive リソースは、Validate
とCheckSum
を指定すると、Zipファイルに含まれるファイルエントリーそれぞれの Stream と、展開された実ファイルの File Stream それぞれについてハッシュ値を比較して変更検知できます。ただし、File Stream を用いるため、ファイルを使っているとこけます。見事に。この問題を回避するため「実ファイルの場合はFile Stream ではなくファイルパスからハッシュ値を計算する」ようにMSFT_Archive リソースに修正をいれたのが cArhiveリソースです。使いませんでしたが、参考程度に。
*1:Expression Body を ReSharper せんせーから使えと調教されるアレです