はじめに
モダンな監視ツールを導入できない環境向けに簡単な監視デーモンを golang で簡単に実装してみます。
クロスコンパイルでほとんどの環境で動作するデーモンを作れるのでこういう時に golang は本当に頼りになります。
やってみよう
必用なもの
- golang
仕様
監視対象URLにポーリング → エラー回数が上限を超え → 任意のコマンドを発行
上記のデーモンを golang で実装する
監視対象URLやエラー時に発行するコマンドなどを環境変数にから読み込む
実装
まずは必要な変数を定義します。
main.go
var (
CheckUrl string // 監視対象URL
CheckTimeout int64 // 監視のタイムアウト
CheckInterval int64 // 監視間隔(秒)
RetryCount int64 // リトライ最大回数
StopCmd string // エラー検知時のApache/Nginx停止コマンド
StartCmd string // Apache/Nginx起動コマンド
WaitAfterStop int64 // StopCmd実行後のWait(秒)
WaitAfterRestart int64 // リスタート後のWait(秒)
ErrorCounter int64 // エラーカウンター
)
次に http.DefaultClient を使用して指定のURLにリクエストする func を作成します。
main.go
type Check struct {
client *http.Client
}
func (c *Check) request(ctx context.Context, req *http.Request) (*http.Response, error) {
req = req.WithContext(ctx)
resCh := make(chan *http.Response)
errCh := make(chan error)
go func() {
res, err := c.client.Do(req)
if err != nil {
errCh <- err
return
}
resCh <- res
}()
select {
case res := <-resCh:
return res, nil
case err := <-errCh:
return nil, err
case <-ctx.Done():
return nil, errors.New("HTTP request cancelled")
}
}
指定URLにリクエストして、エラーの場合はApache/Nginxを再起動する func を作成します。
main.go
func check() {
c := Check{
client: http.DefaultClient, // DefaultClient を使い回し
}
req, err := http.NewRequest(http.MethodGet, CheckUrl, nil)
if err != nil {
log.Fatal(err)
return
}
defer func() {
if ErrorCounter < RetryCount {
return
}
// Apache/Nginxリスタート
err := c.restart()
if err != nil {
log.Fatal(err)
}
ErrorCounter = 0
time.Sleep(time.Second * time.Duration(WaitAfterRestart))
}()
res, err := c.request(context.Background(), req)
if err != nil {
log.Println(err)
ErrorCounter++
return
}
// ステータスコードが400番台以降ならエラーと判定してエラーカウンタをインクリメント
if res.StatusCode >= 400 {
log.Println(fmt.Sprintf("bad response status code %d", res.StatusCode))
ErrorCounter++
return
}
log.Println(fmt.Sprintf("%s %d", CheckUrl, res.StatusCode))
ErrorCounter = 0
}
最後にStopCmdとStartCmdの実行 func を作成します。
main.go
func (c *Check) restart() error {
var cmd []string
var err error
log.Println(`restart...`)
cmd = strings.Fields(StopCmd)
stop := exec.Command(cmd[0], cmd[1:]...)
log.Println(fmt.Sprintf(`exec stop command. [%#v]`, cmd))
err = stop.Run()
if err != nil {
return err
}
time.Sleep(time.Second * time.Duration(WaitAfterStop))
cmd = strings.Fields(StartCmd)
start := exec.Command(cmd[0], cmd[1:]...)
log.Println(fmt.Sprintf(`exec start command. [%#v]`, cmd))
err = start.Run()
if err != nil {
return err
}
log.Println(`done...`)
return nil
}
func main() {
t := time.NewTicker(time.Second * time.Duration(CheckInterval))
for {
select {
case <-t.C:
check()
}
}
}
今回のコード一式は以下にあります。
やっぱツール作るなら golang がお気に入りです。
https://github.com/noridas80/watch-localhost