3
3

More than 3 years have passed since last update.

Apache/Nginx 自動再起動付き監視デーモンを golang で作ろう

Posted at

はじめに

モダンな監視ツールを導入できない環境向けに簡単な監視デーモンを 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

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3