golang で windows サービス 開発 (kardianos/service の 実装説明少々)
前置き
golange で開発し、ビルドして生成した exe ファイルは、そのままでは
sc や NSSM (the Non-Sucking Service Manager) では、Windows サービス化はできても、起動ができない。
以下によると、kardianos/service を使えばよいとのこと。
Cannot start a Go application exe as a windows services
Windows サービスとして golang アプリケーション exe を起動できません
詳細な理由までは分かっていないが、windows サービス化するためには、それに必要な処理が実装されている必要があるらしい。
ref: エラー 1053:カスタムサービスが開始されません
golang には、windows サービス化 に必要となる処理も含む準標準ライブラリ(golang.org/x/sys)があり、ご丁寧にサンプルコードもある(https://pkg.go.dev/golang.org/x/sys@v0.0.0-20220702020025-31831981b65f/windows/svc/example)
kardianos/service とは
kardianos/service は windows の処理に関しては上記ライブラリを使用しつつ、win, linux, 等でサービス化を可能とするための共通のインターフェースを提供している。
- OS 毎にサービス化するために必要な制御は異なる(特に Windows は大きく異なる)が、その制御処理は、kardianos/service に実装されており 共通のインターフェース (Service interface) を提供している。
https://github.com/percona/kardianos-service/blob/master/service.go#L292
- Service interface を実装する windows 向けの service struct があるため、コンストラクタ(New 関数) に Start(), Stop() を実装したものを渡せばよい
https://github.com/percona/kardianos-service/blob/master/service_windows.go
https://github.com/percona/kardianos-service/blob/master/service.go#L85
https://github.com/percona/kardianos-service/blob/master/service.go#L335
service_windows.go の詳細少々
service.Run() では、自身が用意した struct(以下では、exampleService)の Start と Stop が呼ばれる
https://github.com/percona/kardianos-service/blob/master/service_windows.go#L247
コマンドライン引数に start を指定すれば 自身が用意した Start() が呼ばれ、stop を指定すれば、自身が用意した Stop() が呼ばれる訳だが、その仕掛けが以下の通り。
https://github.com/percona/kardianos-service/blob/master/service_windows.go#L190
https://github.com/percona/kardianos-service/blob/master/service_go1.8.go#L10
- start した際、exe ファイルが 引数なしで実行されることになる。つまり、Run()が実行され、自身が用意した Start()が呼ばれる。そしてプロセスが終了するまで待ちに入る
https://github.com/percona/kardianos-service/blob/master/service_windows.go#L271
- stop した際は、windows サービス停止が行われ、(恐らく)それに伴い終了シグナルが発行され、上記で待ちに入っていた箇所でその通知を受けることで、待ちが終わり、後続に控える Stop() の処理が行われると思われる
https://github.com/percona/kardianos-service/blob/master/service_windows.go#L338
少々分かりにくいが、よくできた仕掛けに思う
type exampleService struct {} func (e *exampleService) Start(s service.Service) error { // implement service start return nil } func (e *exampleService) Stop(s service.Service) error { // implement service start return nil } func main() { svcConfig := &service.Config{ Name: "ExampleService", DisplayName: "ExampleService (Go Service Example)", Description: "This is an example Go service.", } // Create Exarvice service program := &exarvice{} s, err := service.New(program, svcConfig) if err != nil { log.Fatal(err) } // Setup the logger errs := make(chan error, 5) logger, err = s.Logger(errs) if err != nil { log.Fatal() } if len(os.Args) > 1 { err = service.Control(s, os.Args[1]) if err != nil { fmt.Printf("Failed (%s) : %s\n", os.Args[1], err) return } fmt.Printf("Succeeded (%s)\n", os.Args[1]) return } // run in terminal s.Run() }
ソース
ログファイルへの出力も可能か試したが、可能であった
https://github.com/Symthy/golang-practices/tree/main/go-win-service
refs
- kardianos/service 使用サンプル
Go 言語で Windows の Service を作成する 2018/06
Go 言語で Windows,Linux の常駐システムを開発する
go で Windows service を作成する 2014/12
- その他