ようへいの日々精進XP

よかろうもん

【細かすぎて伝わらないかもしれない tips】時代はイミュータブルインフラストラクチャだけど, 敢えて monit について書いてみる

tl;dr

お仕事にて, 指定したプロセスが停止したら (それだけではないですが), 自動的にそのプロセスを起動してくれる monit というツールを使いました.

mmonit.com

monit の詳細については, インターネット上の記事がたくさんありますので, そちらをご一読ください.

monit は 2000 年前半にリリースされたいにしへのツールで, 現在でもメンテナンスが続けれています. monit が出来ることは以下の通りで, その気になれば, monit だけでサーバーの運用が出来てしまうと思います.

  • プロセス, ファイル, サービスの監視
  • 監視対象に異常を検知したら, 対象のプロセスの再起動, 指定したメールアドレスに通知, 指定したコマンド実行

サーバーリソースは使い捨てが望ましいと言われている時代, サーバーはコンテナ化され, そのコンテナのオーケストレーションも自動化されていく中で, 1 台のサーバーの中で個々のサービスプロセス自体の死活を監視し, 再起動を促すようなツールの存在意義が薄れてしまいそうな状況にありながら, monit は現在もバージョンアップを重ねていることは素晴らしいことだと思います.

ということで, 今回, 格闘した環境は以下の通りです.

$ cat /etc/system-release
Amazon Linux AMI release 2018.03

$ monit -V
This is Monit version 5.2.5
Copyright (C) 2000-2011 Tildeslash Ltd. All Rights Reserved.

先述の通り, monit については, インターネット上に多くの記事がありますので, そちらの記事の方が monit の出来ることや良さ, 欠点等も網羅されているかと思います. あくまでも, 本記事は自分が monit でやりたかったことをどのように実現したか, 簡単なデモを交えて書いていきます.

俺はこうした

プロセスが停止したら, 再起動して欲しい

シンプルな例です.

Apache のプロセスが停止したら, Apache のプロセスを再起動して欲しい場合, monit の設定は以下のように書きます. 尚, 設定は /etc/monit.conf に書いても良いですし, /etc/monit.d/ 以下に任意のファイル名で作成しても構いません.

check process httpd with pidfile "/var/run/httpd/httpd.pid"
  start program = "/sbin/service httpd start"
  stop  program = "/sbin/service httpd stop"

/var/log/monit には以下のようなログが記録され, httpd プロセスは自動的に起動されています.

[UTC Mar 20 23:43:20] error    : 'httpd' process is not running
[UTC Mar 20 23:43:20] info     : 'httpd' trying to restart
[UTC Mar 20 23:43:20] info     : 'httpd' start: /sbin/service
[UTC Mar 20 23:44:21] info     : 'httpd' process is running with pid 2686

尚, monit はデフォルトは 60 秒間隔で監視対象をチェックしています.

プロセスが停止したら, 通知してから再起動して欲しい

人間というのは, 色々と欲が出てくるもので, 再起動してくれるんなら, 一報入れてから再起動してよって思ってしまいます. そんな時には以下のように設定を書きました.

check process httpd with pidfile "/var/run/httpd/httpd.pid"
  start program = "/sbin/service httpd start"
  stop  program = "/sbin/service httpd stop"
  if does not exist
    then exec "/bin/bash -c '/path/to/bin/slak -env /path/to/bin/.env && /sbin/service httpd restart'"
    else if succeeded then exec "/path/to/bin/slak -env /path/to/bin/.env"

上記の例は, exec コマンドを使って, Slack に通知する為の別のスクリプトを叩いている例となります. キモというか, 苦肉の策となるのが, 以下の部分です.

  if does not exist
    then exec "/bin/bash -c '/path/to/bin/slak -env /path/to/bin/.env && /sbin/service httpd restart'"

if does not exist は, 監視対象のプロセスが存在しない場合という条件で, その条件が真となる場合に then exec 以降が実行されることになりますが, 複数のコマンドを実行する場合には /bin/bash -c... で書きはじめて && でコマンドを連結するみたいな書き方をする必要がありました.

httpd プロセスが停止すると, Slack の指定したチャンネルに以下のように通知されます.

f:id:inokara:20190321092926p:plain

/var/log/monit には以下のようなログが記録されます.

[UTC Mar 21 00:02:27] error    : 'httpd' process is not running
[UTC Mar 21 00:02:27] info     : 'httpd' exec: /bin/bash
[UTC Mar 21 00:03:27] info     : 'httpd' process is running with pid 3153
[UTC Mar 21 00:03:27] info     : 'httpd' exec: /path/to/bin/slak

プロセスの再起動が完了したら通知して欲しい

せっかくなので, monit によって再起動が完了したら, 通知してもらいましょう. 既に, 設定は掲出していますが, 改めて, 以下に記載します.

check process httpd with pidfile "/var/run/httpd/httpd.pid"
  start program = "/sbin/service httpd start"
  stop  program = "/sbin/service httpd stop"
  if does not exist
    then exec "/bin/bash -c '/path/to/bin/slak -env /path/to/bin/.env && /sbin/service httpd restart'"
    else if succeeded then exec "/path/to/bin/slak -env /path/to/bin/.env"

ポイントは, else if succeeded then exec "/path/to/bin/slak -env /path/to/bin/.env" となります. これは, monit によるチェックが成功したら exec 以降の処理を実行することを意味しています.

全体的な処理の流れとしては, httpd プロセスが停止したら, Slack に通知してから httpd プロセスの再起動を行い, 60 秒後 (厳密には 60 秒よりも短い) の次のチェックでプロセスチェックに成功 (プロセスが存在) していたら Slack に通知という感じです.

PID ファイルではなく, プロセス名で監視したい

with pidfile ではなく, matching を利用します.

check process httpd matching "httpd"
  start program = "/sbin/service httpd start"
  stop  program = "/sbin/service httpd stop"

matching の後のプロセス名がちゃんとチェックされるかどうかは, 以下のコマンドを利用してチェックすることが出来ます.

$ sudo monit procmatch httpd

以下のように出力されます.

List of processes matching pattern "httpd":
------------------------------------------
        /usr/sbin/httpd
        /usr/sbin/httpd
        /usr/sbin/httpd
        /usr/sbin/httpd
        /usr/sbin/httpd
        /usr/sbin/httpd
        /usr/sbin/httpd
        /usr/sbin/httpd
        /usr/sbin/httpd
------------------------------------------
Total matches: 9
WARNING: multiple processes matched the pattern. The check is FIRST-MATCH based, please refine the pattern

どんな風にモニタリングされているのか知りたい

monit -vI を実行します.

$ sudo monit -vI

以下のように出力されます.

Process Name          = httpd
 Pid file             = /var/run/httpd/httpd.pid
 Monitoring mode      = active
 Start program        = '/sbin/service httpd start' timeout 30 second(s)
 Stop program         = '/sbin/service httpd stop' timeout 30 second(s)
 Existence            = if does not exist 1 times within 1 cycle(s) then exec '/bin/bash -c /path/to/bin/slak -env /path/to/bin/.env && /sbin/service httpd restart' timeout 0 cycle(s) else if succeeded 1 times within 1 cycle(s) then exec '/path/to/bin/slak -env /path/to/bin/.env' timeout 0 cycle(s)
 Pid                  = if changed 1 times within 1 cycle(s) then alert
 Ppid                 = if changed 1 times within 1 cycle(s) then alert

System Name           = system_ip-xx-x-x-xxx.ap-northeast-1.compute.internal
 Monitoring mode      = active

以上

今回, monit のほんの一部の機能を使ってみました. 通知の部分については, ちょっと格闘しちゃいましたが, プロセスが停止したら再起動させるというシンプルな使い方であれば, とても簡単に使うことが出来ました.

ということで, 素敵な monit ライフをお過ごしください.

おまけ

monit から Slack に通知するコマンドは初心者レベルの Go 言語で作ってみました. クソコードでお恥ずかしい限りですが, おまけとして掲載させて頂きます.

package main

import (
    "bytes"
    "flag"
    "fmt"
    "github.com/joho/godotenv"
    "net/http"
    "os"
    "strings"
)

const (
    AppVersion = "0.0.1"
)

var (
    argEnv     = flag.String("env", "", ".env ファイルのパスを指定.")
    argVersion = flag.Bool("version", false, "バージョンを出力.")
)

func loadEnv(envPath string) {
    var err error
    if envPath != "" {
        err = godotenv.Load(envPath)
    } else {
        err = godotenv.Load()
    }

    if err != nil {
        fmt.Println("Error loading .env file")
        os.Exit(1)
    }
}

func main() {
    flag.Parse()

    if *argVersion {
        fmt.Println(AppVersion)
        os.Exit(0)
    }

    loadEnv(*argEnv)
    username := os.Getenv("SLACK_USER_NAME")
    channel := os.Getenv("SLACK_CHANNEL")
    icon_emoji := os.Getenv("SLACK_ICON")

    descStr := `{"title":"description","value":"` + os.Getenv("MONIT_DESCRIPTION") + `"}`
    var text string
    var attachmentStr string
    if strings.Contains(os.Getenv("MONIT_DESCRIPTION"), "not running") {
        text = os.Getenv("MONIT_HOST") + " にて " + os.Getenv("MONIT_SERVICE") + " プロセスが停止しました."
        attachmentStr = `{"color":"#ff0000","fields":[` + descStr + `]}`
    } else {
        text = os.Getenv("MONIT_HOST") + " にて " + os.Getenv("MONIT_SERVICE") + " プロセスが再起動しました."
        attachmentStr = `{"color":"#008000","fields":[` + descStr + `]}`
    }
    jsonStr := `{"channel":"` + channel + `","username":"` + username + `","text":"` + text + `","icon_emoji":"` + icon_emoji + `","attachments":[` + attachmentStr + `]}`

    req, err := http.NewRequest(
        "POST",
        os.Getenv("SLACK_URL"),
        bytes.NewBuffer([]byte(jsonStr)),
    )
    if err != nil {
        fmt.Print(err)
    }
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Print(err)
    }

    // fmt.Print(resp)
    defer resp.Body.Close()
}

monit は監視対象に異常を検知した場合, 以下のような環境変数に値をセットします.

  • MONIT_HOST
  • MONIT_SERVICE
  • MONIT_DESCRIPTION

これらを利用して, プロセスが停止した場合, 再起動した場合の処理分岐を行っています. また, Slack の Incomming Webhook の URL 等の情報は .env ファイルに書いて, 別で管理することを想定しています. コマンドは以下のように実行します.

/path/to/bin/command -env /path/to/env

たかが monit されど monit ... M/Monit で垣間見た monit の本当の実力

ども、かっぱです。

M/Monit って...

monit をエージェントとして動作する統合監視ツール

f:id:inokara:20150306093200p:plain

monit のドキュメントとか設定ファイルを見ていて、しょっちゅう M/Monit って文字が踊っているのでなんだろって思っていたら...

monit をエージェントとして利用している統合監視ツールようです。iPhone やアンドロイド端末向けのクライアントアプリも提供されていたり、HTTP API を利用して監視の状態を取得出来たりとなかなか面白そうな統合監視ツールかと思います。残念ながら(という言い方は良くないかもしれませんが...)有償とはなっていますが、一回払いきりのライセンスのようですので意外に安い印象です。

f:id:inokara:20150306093353p:plain

ということで、今回は 30 日間のお試し期間をダウンロードして試してみたいと思います。

で、monit て...

(個人的に)monit は対象となるプロセスを監視して、プロセスが停止したら自動的に叩き起こすツールだと思っていました。そう、M/Monit に出会うまでは...。M/Monit を使うことでタダのプロセス叩き起こしツールから monit が稼働するサーバーの監視ツールとしての片鱗を見ることが出来ました。

M/Monit と monit のアーキテクチャ

ドキュメントには以下のように書かれています。

Monit is a small, powerful monitoring program that runs on each host monitored by M/Monit. With regular intervals, Monit will send a message to M/Monit with the status of the host it is running on. If a service fails or Monit has to perform an action to fix a problem, an event message is sent to M/Monit at once. Both status and event messages are stored in a database. Upon receiving an event message from Monit, M/Monit will consult its rule-set and perform an alert notification if a rule matched. From M/Monit, you can start, stop and restart services on any of your hosts running Monit.

ざっくり意訳すると...

  • monit からホストの状態を M/Monit にメッセージを送る
  • もしホストで問題(監視しているプロセスが止まったりしたら)があれば monit は問題を解決した上で M/Monit にイベントを通知する
  • M/Monit はその通知を保存する(保存先は多分 sqlite
  • monit からの通知を受けると M/Monit はルールに基づいた通知を送る
  • M/Monit からは各種サービスの起動や停止、再起動を実行することが出来る

リンク


試す

環境

以下のような環境を Docker コンテナで用意しました。

  • M/Monit 環境✕ 1
  • monit 入りサーバー✕ 3

OS はおなじみの CentOS 6 系で...。

ちなみに M/Monit 環境のコンテナは以下のように起動して 8080 ポートへアクセス出来るようにしておきましょう。

docker run --name=mmonit -t -i -p 8080 -d your_mmonit_image

monit のインストールと設定

monit の設定は以下のように yum で一発!

yum install --enablerepo=rpmforge monit

rpmforge に登録されている monit がバージョンも新しいので個人的にはオススメでございます。

そして、インストールが終わったら /etc/monit.conf を以下のように設定します。

set daemon  60
set idfile /var/monit/id
set statefile /var/monit/state
set mmonit http://monit:monit@172.17.0.xxx:8080/collector
set httpd port 2812 and
    allow localhost
    allow 172.17.0.xxx

include /etc/monit.d/*

ポイントは set mmonit http://monit:monit@172.17.0.90:8080/collector の部分です。monit から M/Monit の collector にアクセスする為の設定を行っています。上記の例では M/Monit は 172.17.0.xxx 上のコンテナで稼働していてユーザー名 monit 及びパスワード monit でアクセスすることを定義しています。

設定したら以下のように monit を起動しておきましょう。

service monit start

以下のようなメッセージと共に起動します。

Stopping monit:                                            [  OK  ]
Starting monit: monit: Warning: M/Monit registration with credentials enabled, but no suitable credentials found in monit configuration file -- please add 'allow user:password' option to 'set httpd' statement
Starting monit daemon with http interface at [*:2812]
                                                           [  OK  ]

警告メッセージは monit 自身の HTTP インターフェースに認証を設定しましょうという警告となります。今回はお試しなので特に触れずに進めたいと思います。実際の運用の際には HTTP インターフェース自身を有効にするか等は検討する必要があると思います。

M/Monit のインストールと起動

M/Monit のインストールは簡単です。こちらからバイナリを落っことしてきて展開して適当なディレクトリに展開するだけ。

cd /usr/local/src/
wget https://mmonit.com/dist/mmonit-3.4-linux-x64.tar.gz
tar zxvf mmonit-3.4-linux-x64.tar.gz
cp mmonit-3.4-linux-x64 /usr/local/mmonit

起動も簡単。

/usr/local/mmonit/bin/mmonit

起動するとポート 8080 で mmonit が Listen するので、ブラウザで 8080 にアクセスするかコンテナにバインドされているポートにアクセスしましょう。

f:id:inokara:20150307102543p:plain

monit 君(というかは知りませんが)がお出迎えしてくれます。

M/Monit へログイン

デフォルトのユーザー名とパスワードはこちらに記載されている通り、ユーザー名が admin でパスワードが swordfish となるので入力してログインしましょう。

f:id:inokara:20150307102911p:plain

上図はダッシュボードで各ホストの monit から受け取ったメッセージのサマリがグラフによって可視化されています。

Status

Dashboard から Hosts Status をクリックするか、上部メニューの Status をクリックすると M/Monit にメッセージを送ってきているホストの一覧を確認することが出来ます。

f:id:inokara:20150307105333p:plain

一覧から一つをクリックするすると以下のように対象ホストのサマリ(CPU やメモリ使用率、monit で監視しているプロセスの状態)を確認することが出来ます。

f:id:inokara:20150307105621p:plain

上記ではアクティブになっていませんが、監視しているプロセスの起動や停止等を行うことが出来ます。

Reports

個人的には Reports 機能がよく出来ているなーと思っていたりしてます。 上部メニューをクリックすると Analytics / Uptime / Events の三つのサブメニューが現れるのでそれぞれ見ていきます。

f:id:inokara:20150307110111p:plain

まずは Analytics です。これはホストのリソース状態を可視化しています。上図では CPU と Memory 及び Swap と Load average が表示されていますが、ネットワークや対象となるポートのレスポンスタイム等をリアルタイムに分析して表示するようです。グラフの表示も上記の棒グラフに加えて円グラフや折れ線グラフでの表示も可能です。

f:id:inokara:20150307110744p:plain

次に Hosts Uptime です。対象ホストの Uptime のサマリが確認出来ます。

f:id:inokara:20150307110905p:plain

最後に Events です。対象ホストの monit から送られてくるイベントメッセージを確認することが出来ます。

Alarts

全ての機能を紹介するのは疲れるので最後に通知機能を少し。Admin メニューから Alerts を選択すると以下のように表示されます。

f:id:inokara:20150307111718p:plain

通知として利用出来るのは...メールと Jabber と呼ばれる IM サービスのみか... Slack に通知出来たら嬉しいのになあと思っていたら、Slack を通知先にする方法が以下に紹介されていました。

ブログ記事によると...

  • ルールを作成
  • ルールの Execute Program を利用して Slack の API を叩かせる

という若干力技にはなりますが Execute Program を利用することで Slack 以外にも PagerDuty 等に通知を飛ばせることになります。

早速、以下のように仕込んでみました。

f:id:inokara:20150307115047p:plain

スクリプトは以下のようなスクリプトです。

ruby -e "
    require 'net/https'
    require 'json'
    uri = URI.parse('https://hooks.slack.com/services/xxxxx/xxxxx/xxxxxxxxxxxxxxxxxx')
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    request = Net::HTTP::Post.new(uri.request_uri, {'Content-Type' => 'application/json'})
    request.body = { \"channel\"  => \"#monit-notification\", \"username\" => \"mmonit\", \"text\"     => \"[#{ENV['MONIT_HOST']}] #{ENV['MONIT_SERVICE']} - #{ENV['MONIT_DESCRIPTION']}\" }.to_json
    response = http.request(request)
    puts response.body
"

実際に monit で監視する Apache を止めたり、起動させたりすると以下のように Slack に通知が飛んできました。

f:id:inokara:20150307115324p:plain

おおっ。


ということで...

駆け足で M/Monit を触ってみた感想は以下の通りです。

  • monit 一個入れておけばリソースの管理、プロセスの監視をやってくれるのは嬉しい
  • monit だけにプロセスをたたき起こすことも自動で出来る上に M/Monit からコントロール出来るのも嬉しい
  • M/Monit の動作がめっちゃ軽い(台数が少ないってのもあるけど)
  • M/Monit と monit の関係は Sensu と同様に M/Monit 側で情報を持つ必要が無いのでディスポーザブルな環境でも導入し易い
  • 通知機能は一応 Slack 等の外部 API を叩ける
  • 残念ながら有償、でも安いかも

ということで、使用期限が切れるまでは M/Monit で遊んでみたいと思います。

Nagios に API でアクセスすることが出来る nagira を触ってみる(2)

はじめに

  • 前回の続き
  • パッシブ監視を軽く触ってみる

参考


構築

  • こちらで構築した
  • ちなみに dockerfileこちら(※ nagira の起動はコンテナにログインしてから /etc/init.d/nagira restart する)

尚、 dockerfile で構築した場合には関係するプロセスは monit で監視、起動、停止が行われる(以下、monit の画面)。

f:id:inokara:20140217012256p:plain


どんなことが出来る?

リクエスト URI

前回からの引き続きとしてどんなことが出来るのかを整理してみる。以下の全てが API 経由で操作が行える。

  • 監視メトリクスの取得
  • 監視対象へのパッシブ監視
  • Nagios 設定自体の取得

http://${nagios_url}:4567/_api で取得出来る内容を確認出来る。

curl -s http://${nagios_url}:4567/_api | jq .

を実行すると以下のように表示される。

{
  "PUT": [
    "/_status/:host_name/_services",
    "/_status/:host_name/_services/:service_description",
    "/_status/:host_name/_services/:service_description/_return_code/:return_code/_plugin_output/:plugin_output",
    "/_status",
    "/_status/:host_name",
    "/_host_status/:host_name"
  ],
  "HEAD": [
    "/_config",
    "/_objects",
    "/_objects/:type",
    "/_objects/:type/:name",
    "/_status/:hostname/_services/:service_name",
    "/_status/(?<hostname>w([w-.]+)?w)/_(?<service>(services|hostcomments|servicecomments))",
    "/_status(/_hosts)?",
    "/_status/(?<hostname>w([w-.]+)?w)",
    "/_api",
    "/_runtime",
    "/"
  ],
  "GET": [
    "/_config",
    "/_objects",
    "/_objects/:type",
    "/_objects/:type/:name",
    "/_status/:hostname/_services/:service_name",
    "/_status/(?<hostname>w([w-.]+)?w)/_(?<service>(services|hostcomments|servicecomments))",
    "/_status(/_hosts)?",
    "/_status/(?<hostname>w([w-.]+)?w)",
    "/_api",
    "/_runtime",
    "/"
  ]
}

パッシブ監視

パッシブ監視について

今更だが Nagios のパッシブ監視についてメモ。

  • 基本的に Nagios が行っている監視はアクティブ監視(Nagios 側から監視対象に対してポーリングする)
  • 対してパッシブ監視は Nagios サーバー以外のプロセスから監視を行い、結果を Nagios で結果を処理させる

nagira を経由してパッシブ監視を行う

以下のような監視結果ファイルを用意する。(Nagios 以外のアプリケーションで監視した結果)

{
 "status_code":"0",
 "plugin_output" : "ping OK"
}

このファイルを host_check.json というファイル名で保存して以下のようなリクエストを nagira に対して投げると...

curl -s -XPUT -H "Content-type: application/json;" -d @host_check.json "http://172.17.0.2:4567/_status/localhost" | jq .

以下のようにレスポンスが返ってくる。

{
  "object": [
    {
      "messages": [],
      "result": true,
      "data": {
        "action": "PROCESS_HOST_CHECK_RESULT",
        "host_name": "localhost",
        "plugin_output": "ping OK",
        "status_code": "0"
      }
    }
  ],
  "result": true
}

パッシブ監視を使えば...

  • 外部アプリケーションの結果等を Nagios で一元管理することが出来る
  • Nagios に飛ばす監視結果 JSON 形式で扱えることで簡単に他のアプリケーションとの連携が取れそう
  • でも、イマイチ、機能について理解出来ていない(すんません)

引き続き...

  • パッシブ監視の理解を深める
  • 出来れば全ての API コールについて確認していきたい

Dockerfile の書き方「私的」なベストプラクティス(3)〜サービスの起動について〜

はじめに

  • ずるずる引きずってすいません
  • Dockerfile 内でのアプリケーションサービスについて monit を利用する方法を思いついたので試してみたのでまとめてみた

参考


どうするのか?

何はともあれ monit をインストールする。

sudo apt-get install monit

Dockerfile 内では以下のように書く。

RUN apt-get install monit

ちなみに monit の詳細については上記の公式サイトへ。monit をざっくり言うと...

  • ホスト上で稼働しているサービスを監視する
  • 監視しているサービスが停止した場合にはルールに従ってサービスのプロセスを再起動する
  • サービスの監視方法としては PID ファイルの監視やプロセス名を監視する
  • WebUI が利用可能で WebUI から監視しているプロセスの再起動や稼働状況を監視出来る

イメージ

やってることは簡単で以下のようなイメージ。

f:id:inokara:20140104071534p:plain

試しに...

以下のサービスを docker run -d 実行時に起動したい場合...

  • nginx
  • td-agent
  • norikra

コンテナ内で起動したいサービスの数だけ monit 用の設定を下記のように記載して /etc/monit/conf.d 以下に放り込む。

nginx

/etc/monit/conf.d/nginx.conf というファイル名で保存する。(ファイル名は任意)

check process nginx with pidfile /var/run/nginx.pid
    start program = "/etc/init.d/nginx start"
    stop  program = "/etc/init.d/nginx stop"

ちなみに nginx を起動する際に daemon off; をつけることでフォアグランドでの起動が可能になる。但し、その場合にはログのローテーション等について別途検討する必要があるようだ。

td-agent

/etc/monit/conf.d/td-agent.conf というファイル名で保存する。(ファイル名は任意)

check process td-agent with pidfile /var/run/td-agent/td-agent.pid
    start program = "/etc/init.d/td-agent start"
    stop program = "/etc/init.d/td-agent stop"

td-agent ではなく fluentd に応用出来る。但し、pid ファイルのパス等には注意する。

norikra

/etc/monit/conf.d/norikra.conf というファイル名で保存する。(ファイル名は任意)

check process norikra with pidfile /var/run/norikra/norikra.pid
    start program = "/usr/local/bin/norikractl start"
    stop  program = "/usr/local/bin/norikractl stop"

上記の /usr/local/bin/norikractl は以下のようなシェルスクリプトこちらを参考に自作した。(monit だけで PATH の上書き方法が解らなかったので...)

#!/bin/bash

export PATH=$PATH:/usr/local/jruby/bin

if [ ! -d /var/run/norikra ];then
  mkdir /var/run/norikra
else
  echo ""
fi

case $1 in
   start)
      /usr/local/jruby/bin/norikra start &
      sleep 3
      ps ax | grep  \[j\]ava | awk '{print $1}' > /var/run/norikra/norikra.pid;
      ;;
    stop)
      kill `cat /var/run/norikra/norikra.pid` ;;
    *)
      echo "usage: norikractl {start|stop}" ;;
esac
exit 0

Dockefile への組み込み

上記の /etc/monit/conf.d/*.conf ファイルに関して Dockerfile を使ってコンテナに組み込む為には ADD コマンドを利用して docker build 時に組み込むようにする。以下のように記載する。

ADD td-agent.conf /etc/monit/conf.d/td-agent.conf
ADD nginx.conf /etc/monit/conf.d/nginx.conf
ADD norikra.conf /etc/monit/conf.d/norikra.conf

上記で Dockerfile と同じディレクトリに置かれた td-agent.confnginx.confnorikra.conf がコンテナ内の所定のパスにアップロードされる。そして、Dockerfile の最後で以下のように EXPOSE でサービスに必要なポートを開放して monitCMD コマンドで起動している。

# for norikra
EXPOSE 26571
EXPOSE 26578
# for fluentd
EXPOSE 24220
EXPOSE 24224
# for nginx
EXPOSE 80
# for ssh
EXPOSE 22
# for monit
EXPOSE 2812
CMD ["/usr/bin/monit", "-I", "-c", "/etc/monit/monitrc"]

尚、monit を起動する際には -I オプションをつけることでフォアグランドで起動させることで docker run -d しても exit 0 でコンテナがデタッチで起動せずに終了してしまうことを避けることが出来る。また、全てのアプリケーションが monit を介して起動する為、monit が起動すると全てのアプリケーションが起動することになる。(やった!)

monit で稼働プロセスの管理

monit では WebUI で monit で管理しているプロセスを確認、操作等を行うことが出来る。monitrc で以下の設定を行うことで WebUI を利用することが出来る。(※実際に運用環境で利用する場合にはアクセス元の制限やログイン名パスワードの管理はちゃんとすること。)

set httpd port 2812 and
  use address 0.0.0.0 # 全てのホストからの接続を受け付ける
  allow admin:monit  # ログイン名、パスワードを指定

monit を起動してブラウザで http://${container_IP}:2812/ にアクセスすると以下のように表示される。

f:id:inokara:20131229211842p:plain

各プロセス名のリンクをクリックすると詳細情報が表示される。また、適切に権限が設定されている場合には詳細情報のページからプロセスの起動、停止を行うことも出来る。


最後に

  • Dockerfile だけでコンテナ内で稼働させるサービスを制御するのは個人的には限界を感じた(三回程記事を書く為に検証した結果)
  • monit を使うことで個々のサービスの起動や停止、プロセスの監視を抽象化して一元管理することが出来るのでコンテナ内で稼働させるサービスの制御はこのようなツールに任せた方が幸せになれそうな気がする
  • dockerDockerfile を使えば簡単にアプリケーションコンテナを構築、提供することが出来るし、手軽にそのインフラを構築することが出来る
  • コンテナ内で稼働する各種サービスは monit を使って管理すれば小難しいコマンドラインでの管理を極力減らせることが出来る
  • まだまだサービスの実運用で利用するには検証は必要かなーと思うけど、インフラ運用の新しい方向性(コンテナを量産して必要に応じて使い捨てる)を見いだせたような気がする(大げさ)

td-agent を monit で監視する

はじめに

  • どうしても落とせない td-agentmonit で監視することにした
  • td-agent は今までほとんど落ちたことは無いが転ばぬ先の杖
  • ちなみに monit と似たツールとしては supervisor がある(まだあるかも)
  • 両方使ってみたけど個人的な主観だけどマジ似てる(プロセスの監視方法や WebUI があったりする点)

参考


設定

Debian の場合

環境は Debian 7.2 を利用。Docker コンテナだけど。

インストール

apt-get でサクッとインストール。

sudo apt-get install monit

インストールされるバージョンは下記の通り。

This is Monit version 5.4
Copyright (C) 2001-2012 Tildeslash Ltd. All Rights Reserved.

設定

monitrc を以下のように書く。

set daemon 30
set logfile /var/log/monit.log
set idfile /var/lib/monit/id
set statefile /var/lib/monit/state
set eventqueue
   basedir /var/lib/monit/events
   slots 100
include /etc/monit/conf.d/*

/etc/monit/conf.d/td-agent.conf を以下のように設置。

check process tdagent with pidfile "/var/run/td-agent/td-agent.pid"
   start program = "/etc/init.d/td-agent start"
   stop program = "/etc/init.d/td-agent stop"
   if 5 restarts within 5 cycles then timeout

/etc/init.d/monit restart で早速起動。冗長出出力モード(-vv)で起動すると以下のように出力される。

 Control file       = /etc/monit/monitrc
 Log file           = /var/log/monit.log
 Pid file           = /var/run/monit.pid
 Id file            = /var/lib/monit/id
 Debug              = True
 Log                = True
 Use syslog         = False
 Is Daemon          = True
 Use process engine = True
 Poll time          = 30 seconds with start delay 0 seconds
 Expect buffer      = 256 bytes
 Event queue        = base directory /var/lib/monit/events with 100 slots
 Mail from          = (not defined)
 Mail subject       = (not defined)
 Mail message       = (not defined)
 Start monit httpd  = False

The service list contains the following entries:

Process Name          = tdagent
 Pid file             = /var/run/td-agent/td-agent.pid
 Monitoring mode      = active
 Start program        = '/etc/init.d/td-agent start' timeout 30 second(s)
 Stop program         = '/etc/init.d/td-agent stop' timeout 30 second(s)
 Existence            = if does not exist 1 times within 1 cycle(s) then restart else if succeeded 1 times within 1 cycle(s) then alert
 Pid                  = if changed 1 times within 1 cycle(s) then alert
 Ppid                 = if changed 1 times within 1 cycle(s) then alert
 Timeout              = If restarted 5 times within 5 cycle(s) then unmonitor

System Name           = system_8d2124a9a59d
 Monitoring mode      = active

ほうほう。

テスト

td-agent が動いてますね。

root@8d2124a9a59d:/etc/monit# ps ax | grep td-agent
  987 ?        Sl     0:00 /usr/lib/fluent/ruby/bin/ruby /usr/sbin/td-agent --daemon /var/run/td-agent/td-agent.pid --log /var/log/td-agent/td-agent.log
  990 ?        Sl     0:00 /usr/lib/fluent/ruby/bin/ruby /usr/sbin/td-agent --daemon /var/run/td-agent/td-agent.pid --log /var/log/td-agent/td-agent.log

td-agent 止めまーす。

root@8d2124a9a59d:/etc/monit# /etc/init.d/td-agent stop
[ ok ] Stopping td-agent: td-agent.

30 秒程待ちまーす。(この時間は monitrcset daemon パラメータで設定)

root@8d2124a9a59d:/etc/monit# ps ax | grep td-agent
 1066 ?        Sl     0:00 /usr/lib/fluent/ruby/bin/ruby /usr/sbin/td-agent --daemon /var/run/td-agent/td-agent.pid --log /var/log/td-agent/td-agent.log
 1069 ?        Sl     0:00 /usr/lib/fluent/ruby/bin/ruby /usr/sbin/td-agent --daemon /var/run/td-agent/td-agent.pid --log /var/log/td-agent/td-agent.log

はい。立ち上がっております。念の為にログも確認してみる。

[JST Dec 14 08:33:53] info     : Starting monit daemon
[JST Dec 14 08:33:53] info     : 'system_8d2124a9a59d' Monit started
[JST Dec 14 08:34:53] error    : 'tdagent' process is not running
[JST Dec 14 08:34:53] info     : 'tdagent' trying to restart
[JST Dec 14 08:34:53] info     : 'tdagent' start: /etc/init.d/td-agent
[JST Dec 14 08:35:24] info     : 'tdagent' process is running with pid 987
[JST Dec 14 08:38:24] error    : 'tdagent' process is not running
[JST Dec 14 08:38:24] info     : 'tdagent' trying to restart
[JST Dec 14 08:38:24] info     : 'tdagent' start: /etc/init.d/td-agent
[JST Dec 14 08:38:56] info     : 'tdagent' process is running with pid 1066

おお、素晴らしい。


最後に

  • monit の設定とっても簡単
  • ちゃんと設定すればメールとか飛ばせたりするので嬉しい
  • Debian のセットアップ(Debian でも WheezySquueze でバージョン違うので注意)と CentOS でセットアップでちょっと違うので注意
  • あとで CentOS のセットアップについても追記する
  • でも簡単にプロセスの監視と再起動が出来たりするのは嬉しい

monit について調べたのでメモ

はじめに

  • td-agent の監視と万が一の時の自動起動に monit を使いたいと思って調べた

monit とは?

個人的な解釈としては monit とは以下のような機能がある。

  • 指定されたプロセスの監視
  • 指定されたプロセスが停止した場合には自動的にプロセスを再起動する

公式サイトはこちら


monit -h

とりあえず monit -h でどんなオプションがあるかを確認。

Usage: monit [options] {arguments}
Options are as follows:
 -c file       Use this control file
 -d n          Run as a daemon once per n seconds
 -g name       Set group name for start, stop, restart, monitor and unmonitor
 -l logfile    Print log information to this file
 -p pidfile    Use this lock file in daemon mode
 -s statefile  Set the file monit should write state information to
 -I            Do not run in background (needed for run from init)
 -t            Run syntax check for the control file
 -v            Verbose mode, work noisy (diagnostic output)
 -vv           Very verbose mode, same as -v plus log stacktrace on error
 -H [filename] Print SHA1 and MD5 hashes of the file or of stdin if the
               filename is omited; monit will exit afterwards
 -V            Print version number and patchlevel
 -h            Print this text
Optional action arguments for non-daemon mode are as follows:
 start all           - Start all services
 start name          - Only start the named service
 stop all            - Stop all services
 stop name           - Only stop the named service
 restart all         - Stop and start all services
 restart name        - Only restart the named service
 monitor all         - Enable monitoring of all services
 monitor name        - Only enable monitoring of the named service
 unmonitor all       - Disable monitoring of all services
 unmonitor name      - Only disable monitoring of the named service
 reload              - Reinitialize monit
 status              - Print full status information for each service
 summary             - Print short status information for each service
 quit                - Kill monit daemon process
 validate            - Check all services and start if not running
 procmatch <pattern> - Test process matching pattern

(Action arguments operate on services defined in the control file)

-t オプションでテストも出来るというのは怠惰な自分には有り難い。


試す

以下の環境で試した。

簡単な設定

インストール直後は下記のように /etc/minit/ 以下にファイルが作成される。尚、インストールは apt-get install monit で楽ちん。

# tree
.                                 
|-- conf.d                        
|   `-- cron                      
|-- monitrc                       
|-- monitrc.d                     
|   |-- cron                      
|   `-- openssh-server            
`-- templates                     
    |-- rootbin                   
    |-- rootrc                    
    `-- rootstrict                
                                  
3 directories, 7 files            

cron のプロセスを監視するので monitrc.d 以下にある cron ファイルを conf.d 以下にコピーして利用した。(※monitrc.d 以下に置いておくだけではプロセスは監視してくれなかった...)

sudo cp monitrc.d/cron conf.d/

コピーしたら monit のプロセスを再起動する。

/etc/init.d/monit restart

これで monit から crond の監視が開始されることになる。ちなみに、 /etc/monit/conf.d にコピーした cron ファイルは下記の通り。

 check process crond with pidfile /var/run/crond.pid
   group system
   group crond
   start program = "/etc/init.d/cron start"
   stop  program = "/etc/init.d/cron stop"
   if 5 restarts with 5 cycles then timeout
   depend cron_bin
   depend cron_rc
   depend cron_spool

 check file cron_bin with path /usr/sbin/cron
   group crond
   include /etc/monit/templates/rootbin

 check file cron_rc with path "/etc/init.d/cron"
   group crond
   include /etc/monit/templates/rootbin

 check directory cron_spool with path /var/spool/cron/crontabs
   group crond
   if failed permission 1730 then unmonitor
   if failed uid root        then unmonitor
   if failed gid crontab     then unmonitor

cron のプロセスを止めてみる

早速、意図的に cron のプロセスを止めてみる。

/etc/init.d/cron stop

しばらく(今回の環境で計測したら約 120 秒)すると /var/log/monit.log に以下のようなログが出力されて crond が再起動される。

[JST Dec  6 11:03:20] error    : 'crond' process is not running
[JST Dec  6 11:03:20] info     : 'crond' trying to restart
[JST Dec  6 11:03:20] info     : 'crond' start: /etc/init.d/cron
[JST Dec  6 11:05:20] info     : 'crond' process is running with pid 3192

cron のプロセスが起動しているかを確認する。

# ps aux | grep cron                        
root      3192  0.0  0.0  18880   860 ?        Ss   11:03   0:00 /usr/sbin/cron

おお。


も少し突っ込んで挙動を見てみる

デバッグモードで起動

-vv を付けて monit を起動するとデバッグモードで起動するので、それを利用して monit がどんなお仕事をしているのかを見てみる。

--- monit.original      2013-12-06 11:10:36.995894157 +0900
+++ monit       2013-12-06 11:08:40.035898212 +0900
@@ -25,7 +25,7 @@
 DELAY="/etc/monit/monit_delay"
 NAME=monit
 DESC="daemon monitor"
-MONIT_OPTS=
+MONIT_OPTS="-vv"
 PID="/var/run/$NAME.pid"

 # Check if DAEMON binary exist

/etc/init.d/monit を上記のように修正して monit を再起動する。

もう一回 cron を止めてみる

先ほどと同様に cron のプロセスを停止してからしばらくすると以下のようなログが出力された。

[JST Dec  6 11:10:43] debug    : 'cron_bin' file exists check succeeded
[JST Dec  6 11:10:43] debug    : 'cron_bin' is a regular file
[JST Dec  6 11:10:43] debug    : 'cron_bin' has valid checksums
[JST Dec  6 11:10:43] debug    : 'cron_bin' permission check succeeded [current permission=0755]
[JST Dec  6 11:10:43] debug    : 'cron_bin' uid check succeeded [current uid=0]
[JST Dec  6 11:10:43] debug    : 'cron_bin' gid check succeeded [current gid=0]
[JST Dec  6 11:10:43] debug    : 'cron_rc' file exists check succeeded
[JST Dec  6 11:10:43] debug    : 'cron_rc' is a regular file
[JST Dec  6 11:10:43] debug    : 'cron_rc' has valid checksums
[JST Dec  6 11:10:43] debug    : 'cron_rc' permission check succeeded [current permission=0755]
[JST Dec  6 11:10:43] debug    : 'cron_rc' uid check succeeded [current uid=0]
[JST Dec  6 11:10:43] debug    : 'cron_rc' gid check succeeded [current gid=0]
[JST Dec  6 11:10:43] debug    : 'cron_spool' directory exists check succeeded
[JST Dec  6 11:10:43] debug    : 'cron_spool' is directory
[JST Dec  6 11:10:43] debug    : 'cron_spool' permission check succeeded [current permission=1730]
[JST Dec  6 11:10:43] debug    : 'cron_spool' uid check succeeded [current uid=0]
[JST Dec  6 11:10:43] debug    : 'cron_spool' gid check succeeded [current gid=102]
[JST Dec  6 11:10:43] debug    : monit: pidfile '/var/run/crond.pid' does not exist
[JST Dec  6 11:10:43] error    : 'crond' process is not running
[JST Dec  6 11:10:43] debug    : -------------------------------------------------------------------------------
[JST Dec  6 11:10:43] debug    :     /usr/bin/monit() [0x41c07a]
[JST Dec  6 11:10:43] debug    :     /usr/bin/monit(LogError+0xa0) [0x41c7d0]
[JST Dec  6 11:10:43] debug    :     /usr/bin/monit(Event_post+0x20a) [0x418ada]
[JST Dec  6 11:10:43] debug    :     /usr/bin/monit(check_process+0xa2) [0x42b312]
[JST Dec  6 11:10:43] debug    :     /usr/bin/monit(validate+0x2fe) [0x42b23e]
[JST Dec  6 11:10:43] debug    :     /usr/bin/monit(main+0x99f) [0x40d32f]
[JST Dec  6 11:10:43] debug    :     /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xfd) [0x7f024d240ead]
[JST Dec  6 11:10:43] debug    :     /usr/bin/monit() [0x40d6b5]
[JST Dec  6 11:10:43] debug    : -------------------------------------------------------------------------------
[JST Dec  6 11:10:43] info     : 'crond' trying to restart
[JST Dec  6 11:10:43] debug    : monit: pidfile '/var/run/crond.pid' does not exist
[JST Dec  6 11:10:43] debug    : monit: pidfile '/var/run/crond.pid' does not exist
[JST Dec  6 11:10:43] info     : 'crond' start: /etc/init.d/cron
[JST Dec  6 11:10:43] debug    : monit: pidfile '/var/run/crond.pid' does not exist

どうやら下記のようなことが /etc/monit/conf.d/cron に記述された内容に基づいて行われているっぽい。

改めて /etc/monit/conf.d/cron とログを照らしあわせて確認すると...

プロセスの実行ファイルの所在のチェックとパーミッションチェック

 check file cron_bin with path /usr/sbin/cron
   group crond
   include /etc/monit/templates/rootbin

rc スクリプトの所在のチェックとパーミッションチェック

 check file cron_rc with path "/etc/init.d/cron"
   group crond                                  
   include /etc/monit/templates/rootbin

cron のスプールディレクトリの所在のチェックとパーミッションチェック

 check directory cron_spool with path /var/spool/cron/crontabs
   group crond                                                
   if failed permission 1730 then unmonitor                   
   if failed uid root        then unmonitor                   
   if failed gid crontab     then unmonitor                   

という感じであらかじめ各プロセスの監視ファイル(例:/etc/monit/conf.d/cron)に プロセスが正しく稼働している状態を任意で記述 することで、それを基に monit はチェックを行いプロセスの死活を監視していると思われる。


最後に

  • なにげなく monit を使えばイイヂャんって思っていたけどちゃんと調べて良かった
  • 正しく動作している状態を記述してテストするという意味では監視ツールという側面と serverspec のようなツールの側面も持ちあわせていると思った
  • 異常を検知してプロセスを再起動するまでの時間はどこで定義されていて調整が可能なのかな?
  • まだまだ続く(td-agent で利用しなければいけないので)
  • そもそもだけど、td-agent って運用開始してから落ちてしまうってことが無いくらいに安定しているからなー