www.mfmican.net

Windows Service 対応の実行ファイルを Golang で作る

Windows のログをリモート転送するために Grafana loki の promtail を導入しようとしたが Windows 向けの実行ファイルが Windows Service としての起動に対応していなかったのでやり方を調べた。

Golang では golang.org/x/sys/windows/svc を使うことでWindowsService対応の実行ファイルがわりと簡単につくれることがわかった。

以下のようにすればよい

package main

import (
        "context"
        "log"
        "os"
        "os/signal"

        "golang.org/x/sys/windows/svc"
)

var serviceName string = "hogehoge"
var StopCh = make(chan bool)

type service struct {
        stopCh chan<- bool
}

func (s *service) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
        const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
        changes <- svc.Status{State: svc.StartPending}
        changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
loop:
        for {
                select {
                case c := <-r:
                        switch c.Cmd {
                        case svc.Interrogate:
                                changes <- c.CurrentStatus
                        case svc.Stop, svc.Shutdown:
                                s.stopCh <- true
                                break loop
                        }
                }
        }
        changes <- svc.Status{State: svc.StopPending}
        return
}

func dosomething(ctx context.Context) {
    // Do something
}

func main() {
        isService, err := svc.IsWindowsService()
        if err != nil {
                log.Fatalf("Failed to detect process environment: %v",err.Error())
        }

        if isService {
                go func() {
                        err = svc.Run(serviceName, &service{stopCh: StopCh})
                        if err != nil {
                                log.Fatalf("Failed to start %s service: %v", serviceName, err.Error())
                        }
                }()
        }

        ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
        defer cancel()

        go func() {
                dosomething(ctx)
        }()

        for {
                if <-StopCh {
                        log.Printf("Shutting down %s", serviceName)
                        break
                }
        }
}

同じようにして promtail のコードを修正して Windows Service に対応させることも考えたが、ビルド対象OSによって動作を切り替えてLinux等での動作に影響ない形でこれを実装するのがちょっと面倒だったので、外部コマンドを実行するだけの簡単な Windows Service を作った。

https://github.com/mamiya312/command2windowsservice

Windows Service にこれを登録し、これ経由でPromtailを起動することにしてやりたいことは実現できた。 以下のように使う。

C:\> sc create promtail binPath= "C:\promtail\command2windowsservice.exe -name promtail C:\promtail\v2.7.3\promtail-windows-amd64.exe --config.file C:\promtail\config\config.yaml
C:\> sc qc promtail
[SC] QueryServiceConfig SUCCESS

SERVICE_NAME: promtail
        TYPE               : 10  WIN32_OWN_PROCESS
        START_TYPE         : 2   AUTO_START  (DELAYED)
        ERROR_CONTROL      : 1   NORMAL
        BINARY_PATH_NAME   : C:\promtail\command2windowsservice.exe -name promtail C:\promtail\v2.7.3\promtail-windows-amd64.exe --config.file C:\promtail\config\config.yaml
        LOAD_ORDER_GROUP   :
        TAG                : 0
        DISPLAY_NAME       : promtail
        DEPENDENCIES       :
        SERVICE_START_NAME : LocalSystem

command2windowsservice.exe の引数にサービス名と promtail の実行ファイル、引数を指定している。

promtail の設定は以下のような感じ。どこにどんなログが出るのかわかっていないので、絞ったりはしていない

clients:
#  - url: https://127.0.0.1/loki/api/v1/push
#    basic_auth:
#      username: DUMMY
#      password: DUMMY
scrape_configs:
- job_name: windows.application
  windows_events:
    use_incoming_timestamp: false
    bookmark_path: "C:\\promtail\\config\\Application.xml"
    eventlog_name: "Application"
    xpath_query: '*'
    exclude_event_data: true
    exclude_user_data: true
    labels:
      job: windows
  relabel_configs:
    - source_labels: ['computer']
      target_label: 'host'
- job_name: windows.security
  windows_events:
    use_incoming_timestamp: false
    bookmark_path: "C:\\promtail\\config\\Security.xml"
    eventlog_name: "Security"
    xpath_query: '*'
    exclude_event_data: true
    exclude_user_data: true
    labels:
      job: windows
  relabel_configs:
    - source_labels: ['computer']
      target_label: 'host'
- job_name: windows.setup
  windows_events:
    use_incoming_timestamp: false
    bookmark_path: "C:\\promtail\\config\\Setup.xml"
    eventlog_name: "Setup"
    xpath_query: '*'
    exclude_event_data: true
    exclude_user_data: true
    labels:
      job: windows
  relabel_configs:
    - source_labels: ['computer']
      target_label: 'host'
- job_name: windows.system
  windows_events:
    use_incoming_timestamp: false
    bookmark_path: "C:\\promtail\\config\\System.xml"
    eventlog_name: "System"
    xpath_query: '*'
    exclude_event_data: true
    exclude_user_data: true
    labels:
      job: windows
  relabel_configs:
    - source_labels: ['computer']
      target_label: 'host'

windows_exporter がとても参考になった