<< 前へ | Home

PR: 今だから始める!ECショップ開業    こんなこともできる!Webサービス    結婚式は神前式で    エコカーがこれからの主流    本気の婚活!!    みんなのリサイクル意識    引越しのタイミング    語学を学ぶための基礎   

monitでcheck programを使用する時の注意

monitでcheck programを使ってハマったのでメモ。

とあるサービスを提供するWebアプリを監視するため、以下のようなconfを作成した。

check program test path /home/shanai/test/ping.sh
    if status > 0 then exec /home/shanai/test/restart.sh

ping.shはhttpie使ってWebアプリに簡単なリクエストを送ってみて、正しい応答が無ければ非0を返すスクリプト。restart.shは、Webアプリを再起動するスクリプト。ところが、これが全くうまく動作しない。わざとWebアプリを落とすと、そこから復帰できない。restart.shは呼ばれているのだけど、ping.shが正常にならない。ログを見るとこんな感じで、monitは正しく動作しているように見えるが、なぜかping.shが0を返さない。しかもターミナルから、ping.shを実行してみると0が返っている。

[JST Aug 30 20:32:08] error    : 'test' '/home/shanai/test/ping.sh' failed with exit status (1) -- no output from program
[JST Aug 30 20:32:08] info     : 'test' exec: /home/shanai/test/restart.sh
[JST Aug 30 20:32:38] error    : 'test' '/home/shanai/test/ping.sh' failed with exit status (1) -- no output from program
[JST Aug 30 20:32:38] info     : 'test' exec: /home/shanai/test/restart.sh
[JST Aug 30 20:33:08] error    : 'test' '/home/shanai/test/ping.sh' failed with exit status (1) -- no output from program
[JST Aug 30 20:33:08] info     : 'test' exec: /home/shanai/test/restart.sh

最初はping.shに何か問題があるのかと、httpieのかわりにcurlを使ったりしたけど全く変わらず。そこでping.shとrestart.shを以下のようなダミーに置き替えて様子を見てみた。

--- ping.sh ---

#!/bin/sh
echo "Ping started on " `date` >>/tmp/log
sleep 3
echo "Ping ended on " `date` >>/tmp/log
exit 1
--- restart.sh ---

#!/bin/sh
echo "Restart started on " `date` >>/tmp/log
sleep 10
echo "Restart ended on " `date` >>/tmp/log

ping.shの方は3秒、restart.shの方は少し長めに10秒かかる処理と想定。前後でログを入れてみた。そしてログを見ると、そこには予想の斜め上を行く結果が残されていた。

Ping started on  Sat Aug 30 20:31:38 JST 2014
Ping ended on  Sat Aug 30 20:31:41 JST 2014
Restart started on  Sat Aug 30 20:32:08 JST 2014
Ping started on  Sat Aug 30 20:32:08 JST 2014
Ping ended on  Sat Aug 30 20:32:11 JST 2014
Restart ended on  Sat Aug 30 20:32:18 JST 2014
Restart started on  Sat Aug 30 20:32:38 JST 2014
Ping started on  Sat Aug 30 20:32:38 JST 2014
Ping ended on  Sat Aug 30 20:32:41 JST 2014
Restart ended on  Sat Aug 30 20:32:48 JST 2014
Restart started on  Sat Aug 30 20:33:08 JST 2014
Ping started on  Sat Aug 30 20:33:08 JST 2014
Ping ended on  Sat Aug 30 20:33:11 JST 2014
Restart ended on  Sat Aug 30 20:33:18 JST 2014

最初のPingが終了した後、Restartはすぐには開始せず、monitの監視間隔(今回は30秒に設定)後になってRestartが開始している。しかも、Restart開始と同時に(!)、次のPingが開始している。これではPingが成功するわけがない。何せWebアプリを再起動したタイミングでPingが走ってしまうのだ。つまり、当初は以下のような動作だと思っていたのだが(monit.logだけ見れば、そのように誤解してしまう...)

  • ping.shを実行(失敗)
  • restart.shを実行
  • monitの監視間隔分の待ち
  • 最初に戻る

実際は以下のように動作していた。

  • ping.shを実行(失敗)
  • monitの監視間隔分の待ち
  • restart.sh、ping.shを同時実行
  • 上から2番目に戻る

つまり、check programを使う時は、2行目以降に復帰処理を書いてはいけない。以下のように書くべきなのだろう。

check program test path /home/shanai/test/pingAndRestart.sh
    if status > 0 then alert

check programの行に指定するプログラムで状態を調べて、必要ならリカバリ処理までやってから状態を返す。2行目以降にはリカバリ処理は書かずに、せいぜいalertなど、状態監視とバッティングしても問題ない処理を記載する。

フルスクラッチから1日でCMSを作る シェルスクリプト高速開発手法入門 を読んだ

「フルスクラッチから1日でCMSを作る シェルスクリプト高速開発手法入門」を送っていただきました。

シェルスクリプトでWebアプリケーションを書いてしまう本で、それだけ聞くと単なるキワモノ系な感じがするかもしれないけれど、1冊で3度おいしい本。

まずWebアプリケーションの基本が学べる。というか理解していないと途中で付いていけなくなる恐れあり。しかしこのあたりは理解していないといけないところだと思うので、この本の内容が理解できなかったら、なんとかフレームワークに頼る前にきちんと調べて理解しておくべきだと思う。脆弱性まわりも丁寧に解説されていて素晴しい。

2つ目として、この本は良くあるトリッキーなコードはほとんど使われておらず、非常に純朴なシェルスクリプトで構築されている。それでもシェルスクリプトとそれを支えるツールの思わぬ使い方が学べる。自分もsort -Rでシャフルできるとか <<< を使うことで変数の内容を標準入力として使えるとかは知らなかった。

3つ目にエンジニアとして考え方の基礎が学べる。特にチューニングのところが良いし、なぜwhileを避けるべきかとか、ちゃんと理由が書かれている。あと割り切りがうまい。日本人ってとにかくこの割り切りというやつが苦手で「この程度で十分じゃない」というのが出来ずに無用に造り込んでしまうことが多い。特に面白かったのがサイトのアクセスカウンタのところ。普通ならカウンタ値が書かれたファイルを使うところだけど、そうすると同時アクセスされた時にどうやってカウンタを更新するのか、ロックするのかなどやっかいだ。この本では echo 1 >> counterでカウンタファイルを1バイトごとに大きくしていく。ファイルサイズ=アクセス数だ。アペンドだから同時更新に悩まなくて済む。そのかわりアクセス数が増えればカウンタファイルのサイズが無制限に膨らむことになる。しかしその程度のこといいではないか。仮に100万回その記事が読まれたとしても、たかだか1MBだ。今では大騒ぎするほどのサイズじゃない。

最後に、こういう本を出版してくれたKADOKAWAさんに拍手を送りたい。

鎌倉のあじさい

去年は行けなかった鎌倉。いつもながら凄い混雑だった。

左可井で、いつもの穴子丼。トイレが増設されて2つになってた。


浄妙寺へ。



いつもの猫さんとご対面


長谷寺へ。毎年新しい品種が増えている気がする




汗だくになりながら登っていく




複雑な色合い


これから色付くのかな







ほたるぶくろ




るいもが帰ってきた。

お花を幾つもいただいて、るいもは幸せに天に登って行けたと思います。


TracLightningのJenkinsのJob設定をまとめてやるためのメモ。

今のプロジェクトで、Jenkinsのビルドでたまにソースコードの中身が2重になる(1つのファイルに同じん内容が2回書かれている)状態になることがある。原因は不明で、どうもSubversionとWindowsとの食い合わせっぽい。Jenkinsのジョブ設定で、ワークスペースの更新のデフォルトがsvn updateになっているのを、新規チェックアウトにすると緩和される気がするので、ひとまず全部変更しておこうかと思ったものの、既に数100のジョブがあってとても手でやるのは無理。

JenkinsのAPIを使おうと思ったものの、隔絶環境なのでwgetもcurlも無ければ、Rubyも無くてスクリプト言語系はPerlのみ。Javaでゴリゴリ書くのは辛いなと途方に暮れていたところ、TracLightningなので中にPythonが入っていることを思い出し、ちょっと試してみる。

第一の難関はログインのところ。Firebugで見てみると、TracLightningのデフォルトはDigest認証のよう。これはPythonのurllib2に入っているHttpDigestAuthHandlerを使ってみたら、あっさり突破できた。あとは、ElementTreeをXMLパーサに使用すれば問題無し。以下はjobの一覧を読み出す例:

import urllib2
import xml.etree.ElementTree as ET
URL = 'http://server/jenkins/api/xml'
Realm = 'trac'
Username = 'username'
Password = 'password'

authhandler = urllib2.HTTPDigestAuthHandler()
authhandler.add_password(Realm, URL, Username, Password)
opener = urllib2.build_opener(authhandler)
urllib2.install_opener(opener)
page_content = urllib2.urlopen(URL).read()

elem = ET.fromstring(page_content)
for job in elem.findall(".//job"):
    print job.find(".//name").text
    print job.find("./url").text

以下はjob設定の読み出し例。GETリクエストで読み出し。POSTすれば更新できる。

import urllib2
import xml.etree.ElementTree as ET
URL = 'http://server/jenkins/job/xxx_job/config.xml'
Realm = 'trac'
Username = 'username'
Password = 'password'

authhandler = urllib2.HTTPDigestAuthHandler()
authhandler.add_password(Realm, URL, Username, Password)
opener = urllib2.build_opener(authhandler)
urllib2.install_opener(opener)
page_content = urllib2.urlopen(URL).read()

elem = ET.fromstring(page_content)
elem.find(".//scm/workspaceUpdater").set("class", "hudson.scm.subversion.CheckoutUpdater")
urllib2.urlopen(URL, ET.tostring(elem))

これらをまとめて、全てのジョブのワークスペース更新の方法を、svn updateから新規チェックアウトに変更する例:

import urllib2
import xml.etree.ElementTree as ET

class Jenkins:
    def __init__(self, url, userName, password):
        self.userName = userName
        self.password = password
        self.url = url if url.endswith('/') else url + '/'

    def _setCredential(self, url):
        authHandler = urllib2.HTTPDigestAuthHandler()
        authHandler.add_password('trac', url, self.userName, self.password)
        urllib2.install_opener(urllib2.build_opener(authHandler))

    def _createUrl(self, urlSuffix):
        return self.url + urllib2.quote(urlSuffix.encode('utf-8'))

    def _httpGet(self, urlSuffix):
        accessUrl = createUrl(urlSuffix)
        self.setCredential(accessUrl)
        content = urllib2.urlopen(accessUrl).read()
        return ET.fromstring(content)

    def _httpPost(self, urlSuffix, postXml):
        accessUrl = createUrl(urlSuffix)
        self._setCredential(accessUrl)
        urllib2.urlopen(accessUrl, ET.tostring(postXml))

    def getJobList(self):
        return self._httpGet('api/xml')

    def getJobSettings(self, jobName):
        return self._httpGet('job/%s/config.xml' % jobName)
        
    def setJobSettings(self, jobName, xml):
        self._httpPost('job/%s/config.xml' % jobName, xml)

if __name__ == '__main__':
    jenkins = Jenkins('http://your-server/jenkins', 'username', 'password')
    for job in jenkins.getJobList().findall(".//job"):
        jobName = job.find(".//name").text
        jobSettings = jenkins.getJobSettings(jobName)
        workUpdater = jobSettings.find(".//scm/workspaceUpdater")
        if not workUpdater is None:
            workUpdater.set("class", "hudson.scm.subversion.CheckoutUpdater")
            jenkins.setJobSettings(jobName, jobSettings)

隔絶環境でメモを取りながら作業したのでtypoとかあったらごめんなさい。

るいもとお別れ

るいもが今朝亡くなった。残念ながら18歳は迎えられなかったけど、るいもの兄弟の中では一番の長生きらしい。
ここ最近急速に弱っていたけど、食欲はうちの猫達の中で1番だったし、まだ1-2週間はもつんじゃないかと思っていた。しかし高齢には勝てなかったようで、あっという間に逝ってしまった。




ショパン プレリュード 第16番

ショパン プレリュード 第16番

昔、電子ピアノで録音したものもあるけど、その時と比べて荒々しい感じは表現できただろうか...

鸞鳳玉の花

鸞鳳玉の花が咲いた。

このサイトの掲載内容は私自身の見解であり、必ずしもIBMの立場、戦略、意見を代表するものではありません。
日本アイ・ビー・エム 花井 志生 Since 1997.6.8