くらしTEPCOのページから電力使用量情報を取得するツールを作った

東京電力の電力プラン契約を、スマートメーターを使うタイプに変更すると、くらしTEPCOのサイトで使用電力量のデータを見ることが出来るようになります。
最小30分刻みで見ることができ、CSVエクスポート機能もあったりするのですが、どうせならAPIとかで取得して監視ツールに突っ込んでじまえでグラフ化したい! となるわけです。

が、残念なことにデータ取得用のAPIなどは用意されていないので、じゃあ自前で取得する方法を考えないとですねー、ということで、ツールを作りました。 github.com

使い方はREADMEに書いているとおりです。他のツールと併用することを考えているので、基本的には指定された日付のデータしか取得しません(期間指定でどうこう、とかは今の所出来ない)。
ただし他のツールに流し込みやすいように、CSVのデータをそのまま出力するだけでなく、JSONに変換して出力するオプションも着けました。フォーマットはこれでいいのか今一自信がありません…が、まぁとりあえず問題はないかなと。

今回このツールを作るにあたり初めてPythonに触れたわけですが、案外使いやすいですね…。今後しばらくツール類はPythonで書いてみようかしら。

systemd の Service 起動時・停止時にメール通知を送る

前回のエントリーの続き。

Service の自動再起動設定がされている状態で、サービスの再起動が発生した場合にメール通知が飛ぶとうれしいなぁ、という事で調べてみると、同じ事を考えている人が既にいて、こんな感じで実現しています。 qiita.com

が、Apache 2.4 の場合、SIGKILL を送って強制終了した場合、Service のステータスとしては正常終了になってしまうので、OnFailure に書かれたユニットが起動してくれない、という問題にぶち当たりました。

それじゃあいっそ「Service 起動時・停止時に必ずメール通知を送る」ようにしてやろうじゃないか、というのが今回のエントリーの内容です。

通知用のテンプレートユニットを作る

先に、起動時通知用・停止時通知用のテンプレートユニットを作ります。

/etc/systemd/system/service-start-notify@.service

[Unit]
Description = service start notification for %I

[Service]
Type = oneshot
ExecStart = /bin/sh -c 'systemctl status --full "$1" | mail -s "[%H] $1 start" notice' -- %i
User = nobody
Group = systemd-journal

/etc/systemd/system/service-stop-notify@.service

[Unit]
Description = service stop notification for %I

[Service]
Type = oneshot
ExecStart = /bin/sh -c 'systemctl status --full "$1" | mail -s "[%H] $1 stop" notice' -- %i
User = nobody
Group = systemd-journal

mailコマンドで指定している宛先は、通知したいメールアドレスをべた書きしても良いですし、alias を指定しても大丈夫。今回は notice という名前でエイリアスを作って、そこに送るようにしています。

ファイルを作ったら、リロードして有効化します。

$ sudo systemctl daemon-reload

Service の設定を修正する

続いて、Apache2 の Service 設定を修正します。

sudo systemctl edit apache2

でエディタを起動して、以下の内容を追記します。

[Service]
ExecStartPost=/bin/sh -c "systemctl start service-start-notify@$1.service" -- %n
ExecStopPost=/bin/sh -c "systemctl start service-stop-notify@$1.service" -- %n

名前でなんとなく想像出来ると思いますが、ExecStartPost がサービス起動後に実行されるコマンド、ExecStopPost がサービス終了後に起動されるコマンドです。ExecStopPost は正常停止・異常停止のいずれの場合でも実行されるので、Service が止まった場合は必ずメール通知が飛ぶ仕掛けになっています。

この状態で Apache のプロセスを殺すと、notice のエイリアスで指定したメールアドレス宛に、サービス終了通知とサービス開始通知のメールが届きます。

Ubuntu 16.04 LTS で、Apache 2.4 の自動再起動

そもそも最初から自動で再起動するように設定して置いて欲しいんですけどぉ。

Ubuntu 16.04 LTS で入る Apache 2.4 は、ちゃんと systemd 管理になってはいるのですが、自動再起動の設定がされていません。

$ sudo systemctl show apache2
Type=forking
Restart=no <- Restart がデフォルト値のまま!
NotifyAccess=none
RestartUSec=100ms
TimeoutStartUSec=5min
TimeoutStopUSec=5min
RuntimeMaxUSec=infinity
WatchdogUSec=0
WatchdogTimestamp=Thu 2017-03-02 18:10:17 JST
WatchdogTimestampMonotonic=169883017858
FailureAction=none
(snip)

じゃあどうすれば良いかというと簡単で、自分で自動再起動設定を override すれば大丈夫。

$ sudo systemctl edit apache2

とするとエディタが開くので、以下の内容を書き込んでやれば、service の設定が更新されて、自動再起動が有効になります。

[Service]
Restart=always

on-failure にするか always にするかはお好み次第(on-failure だと正常終了した場合は再起動しない)ですが、今回は「どうせ Web サーバなんだし起動するのが当たり前なので always にしとけ」ということで always に。要らなければ service を無効化すれば良いだけですしね。

WWW::Mechanizeで携帯向けサイトを弄れない時はバージョン1.71以上に上げよう(もしくは自分でどうにかしよう)

T/O としてもいい記事ですが、まぁ一応説明を。

PerlのWWW::Mechanizeでサイトを弄る場合で、特に携帯電話(いわゆるガラケー)向けのサイトを弄る場合に、

  • フォームはきちんとHTML内に存在するのにsibmit_form()できない
  • title()でタイトルが取れない

とかいう不思議現象に遭遇することがあります。

これは、WWW::Mechanizeのis_html()メソッドが、Content-Typeとしてtext/html以外が返ってきた場合にはドキュメントをHTMLと見なさず、HTMLとしての解析を行わない事が原因です。携帯電話向けのサイトではContent-Typeとしてapplication/xhtml+xmlを返す事が有るため、この仕様にバッチリとハマり、前述の問題が起こるわけです。 本来xhtmlなドキュメントの場合はtext/htmlではなくapplication/xhtml+xmlを返すのが正しいので、普通のPC向けサイトでもこの事は起こりえるのですが、携帯向けサイトはContent-Typeを厳密に設定する傾向があり(でないと携帯電話が解釈してくれない)、この問題に遭遇しやすいようです。ってか、一般のPC向けサイトで、今日日xhtmlを正しく運用してる所をほとんど見た事がないですね…。

で、解決方法としては、

  • WWW::Mechanizeの1.71でContent-Typeがapplication/xhtml+xmlでもhtmlと見なすように修正が入ったので、これ以降のバージョンにWWW::Mechanizeをバージョンアップする
  • あるいは、自分でWWW::Mechanizeのis_htmlメソッドを上書きする

のどちらかとなります。私は最初新しいバージョンで直っている事に気付かず後者でやりましたが、まあ出来るだけ前者で対応した方が良いと思います。「モジュール内のメソッドをモジュールの外から上書きする」というのはとても黒魔術チックなので…。

配列の差分を取る

たとえば、

my @array_a = (1,2,3,4,5);
my @array_b = (3,4,5,6,7);

とあって、array_aには有るけどarray_bには無い要素だけを取り出したい、というケース。正式には、集合array_aから集合array_bを引いた差集合、とかいう言い方になりますかね。 これを求めるには、以下のようにするとできます。

my @array_c = grep { my $t=$_; ! grep /^$t$/,@array_b; } @array_a;

こうすると、

$ cat tmp.pl
#!/usr/bin/perl
use strict;
use warnings;
my @array_a = (1,2,3,4,5);
my @array_b = (3,4,5,6,7);
my @array_c = grep { my $t=$_; ! grep /^$t$/,@array_b; } @array_a;
print "@array_c\n";
$ perl tmp.pl
1 2

とこんな感じで差分を取ることができます。

同じ理屈で、

my @array_c = grep { my $t=$_; ! grep /^$t$/,@array_b; } @array_a;

こうすれば、両方の配列で重複している要素を取り出すことができます。

Provisioning Frameworks Casual Talks vol.1に参加してきました! #pfcasual

レポートを書くまでが勉強会です! といいつつ最近書けていなかったので、久々にレポート記事を。
当日のメモはGoogle Drive上に生の物を保存しておきましたのでそちらをどうぞ。現場でメモったものをそのままアップロードしているので、typoとか聞き間違いとかあるかもしれないので無保証でお願いします!

で、感想。
ChefもPuppetもそうですけど、「状態を定義するためのツールである」というところの認識を新たに出来たのはまぁよかったかな、と。ちょうど2つのどちらを導入すべか、という検討をやっているところなのですが、導入目的というか判断基準として「どちらの方がより良く状態を定義できるか?」のウェイトを増した方がいいかなぁ、などと思いました。

それより何よりServerSpecすげぇ! と言うのを知ったのが一番の収穫でした。恥ずかしながら「聞いたことはあるけどどんなツールかよく知らない」という状態だったのですが、「サーバーの状態があるべき姿になっているかを客観的に判断したい」という、前職でも今の職場でも悩みとして抱えていた課題がこれで解決できるじゃないの! と。むしろPuppet/Chefよりこちらの導入を先に検討した方がいいのかもしれない…。

あと、事前登録なし先着順入場方式の是非ですが、このくらいの予想来場人数であれば手間が掛からないし、キャンセル待ち繰り上がりに気付かず結局欠席というお互いにとっての悲劇(何度かやらかした事が…)を防げるのでよいのかな、と。多分QPStudyくらいの規模になると…どうなんだろうな、あれでも結構直前キャンセルが出てるので、定員オーバーで泣く泣く帰宅、って人はそう出ない気も。後は平日開催の場合は時間を遅めにする(と言っても19時半〜20時くらい)ようにすると、まぁ不満は出ないかな、と思います。

トラブル☆しゅーたーず#05 〜過去からの贈り物〜」に参加してきました #トラしゅ

ということで、昨日行われたトラブル☆しゅーたーず#05 〜過去からの贈り物〜へ参加してきました。トラしゅ参加は2回目、今回はチーム3で参加したのですが、なんと優勝という評価を頂くことができました。
とはいえ私は色々わーわー言うだけという感じで、一緒に参加したチームの皆様には色々お世話になりまして、どうもありがとうございました、と感謝の限りです。特に@Kill_In_Sunさんには報告書作成の大半にプレゼンまでやっていただいて、どうもありがとうございました。あれだけできれば前途は明るいと思います、ええ、本当に。

また、イベントを開催してくださった運営の皆様、そして会場だけでなくニフティクラウドのサービスも提供してくださったニフティさん、本当にありがとうございました。

で、参加記録という事で、とりあえずログと記憶をたよりに、当日の流れをつらつらと書いてみようと思います。

今回のシチュエーション

前置きはイベントページの通りなので省略。システム構成は以下のような感じです。

このような構成で、WEBサーバがクラウド側のトラブルにより再起動した、というのが今回のシチュエーションです。
お客様からの連絡というのは

•サイトがおかしいみたいです
SSHアクセスもできなくなってます
•今日の16:00〜16:15のTVで取り上げられるのに
•今回はCMの効果をちゃんと確認したいので、どのくらいアクセスがあったとかそういうの教えてもらえますか

というもの。ちなみにこの時点で時刻は14時です。

まずは状況確認

一通りの説明とチーム分け発表が終わり、会議室に入ってまずやることはサーバアクセスの確認。すると、

  • BATCHサーバは普通にコンソールからでもsshでもアクセスできる
  • WEBサーバは確かにsshでアクセスできない、ではコンソールはというと、こちらも表示はできるが、用意されているrootユーザもhonbanユーザもパスワードが通らない

という状態。WEBサーバに入れないとどうしようもないので、一旦再起動しシングルユーザーモードで起動。するとなんとrootもhonbanユーザもshadowが埋められている、という事が判明。これじゃログインできるわけねぇわ、ということで、rootユーザのパスワードを再設定してコンソールからはログインができるようにしました。
さらには

  • 再度の再起動後もなぜかsshdがあがっておらず(chkconfigでは有効になっているのに、って/etc/rc3.d以下までは見てなかったな…!)、sshアクセスできない
  • sshdをあげてhonbanユーザでログインしようとすると鍵の認証ではじかれる。調べてみると~honban/.ssh/authorized_keysのownerがrootになっていた!

と早速罠全開。
なんとかログインしてサーバの様子を探ってみると、動作しているのはApacheMySQL、しかし/usr/local以下にはnginxも置いてあり、さらにphpのバージョンも5.3.2と5.3.4の2つがある、というカオス状態。nginxについては事前説明の中の山○君の証言に

エンジンエックスってどう考えても読めないっすよね

という一文があったことから、恐らくこいつが仕込んだものだろう、という想像はついたのですが、現状Apacheが動いているからとりあえず置いておこう、と言う事でスルー。

一方Webサイトのほうはというと、アクセスは普通にできるものの

  • 商品説明の横の小さい商品画像が見られない
  • 購入処理を進めていくと、最後の決済の所でエラーになる

と、正常には稼動していない状態。

エラーログを眺めて原因を探った所、前者はEC-CUBEの画像縮小表示用のスクリプトが正常に動かない事が原因と判明。ただし「imagecreatefromjpeg()関数が定義されていない」というエラーが出ているもののGDは有効になっており、じゃあ何でエラーになるんだ…、という原因までは、この時点では気づく事ができませんでした。
後者については「mysqliクラスが無い、ってエラーが出てる」という程度は確認できたのですが、DBに接続できなければそもそも商品情報やら他のページも表示できないはず…? と言う事で原因は最後までわからずじまい。

そうこうするうちにトラブルが

そんな感じで状況確認をしていると、なんとsshで繋いでいたWEBサーバの反応がなくなりました。コンソールを見てもらうと、OOM-Killerが発動している事を示すログが。
メモリ搭載量は1GBあり、本来であればこれで足りないと言う事は無いはずなのですが、ともかくサーバの応答がなくなった以上サービスも止まっており、この状態を長引かせるわけにはいかん、ということでサーバの再起動を実施。
並行してお客様にsorryサーバ作成の可否を問い合わせ、許可が得られたのでsorryサーバを作成する事になりました。ただ残念ながら、IPアドレスを付け替えたりと言う事ができなかったので、このsorryサーバは最後まで使われる事がありませんでした。

サーバ復帰後、メモリが足りなくなった原因を調べるもはっきりとした原因は発見できず。my.cnfでのMySQLキャッシュ割り当てが大きめであった事から、キャッシュとして確保されるメモリが多すぎるのでは? という推測を立てて、とりあえずはサーバをメモリ搭載量が多いプランに切り替えることで凌ごう、と言う事になりました。
そうこうしている間にもサーバの応答がなぜかなくなってしまって再度コンソールからリブートを掛けたり、再びOOM-Killerが発動してやっぱりコンソールからリブートを掛けたり、と対応に追われつつも、なんとかサーバのスペックアップ(メモリ搭載量を1GBから4GBへ)が間に合い、16時のテレビ放映を迎える事ができました。

ただし、この時点でもWebサイトの問題2点は未解決。「じつはApacheじゃなくnginxを動かさないと駄目なんじゃね?」という話があったのでnginxを起動させてみるも、デフォルトのWebページが表示されてしまい、こりゃ駄目だ、と言う事で即座にApacheへ切り戻し。
実はこの時点で「なんでデフォルトのWebページが出るんだ」という事をもっと追求していれば、正解にたどりつけていたんですが、まぁ詳しくは後程。

16時!

16時になった瞬間、大量のアクセスが押し寄せてきたため、サーバの負荷が上昇していきました。同時にメモリ使用量も増加し、早くも4GBのメモリを使い果たしそうな勢いになりました。
なんでだ、と思って調べてみると、なんとApacheのMaxClientsが15000という数字に設定されている事が判明。preforkで15000とか何考えてるんだ! とかうめきながらあわててまず1000に、続いて500にMaxClientsを変更しました。このときの再起動が、結果的には功を奏したわけですが、まぁラッキーパンチではありましたね…。
依然としてWebサイトの問題は解決しないものの、「まぁ、アクセスできてるしまぁ……」と言う事で半分くらいはもうあきらめムード。とりあえず発表資料作りましょうか、ということで、アクセスログを集計したり、資料をやいのやいのといいながらまとめたりしつつ、タイムアップの18時を迎えたのでした。

ちなみにこの後も1度OOM-Killerが走って、サーバの応答がなくなったためにリブートを実施しています。このタイミングで何故メモリが足りなくなったんだろう、と言う事を考えていれば、種明かしを待つでもなくOOM-Killerが走った原因が分かった…かもしれません。

種明かし

18時のタイムアップを迎えて、最初に集まったミーティングスペースへ戻ってみると、ホワイトボードに各チームのタイムラインが記録されていました。
これはまぁ前回も同じだったので、さてどんな感じだったのかな、と眺めてみると

攻撃!
そうか、あのOOM-Killerは攻撃を食らった事によるものだったのか、でもあれ、アクセス数は大して増えてなかったはず…、と思って色々思い返してみると、そういえばApacheのバージョンが2.2.14と割と古めだった事を思い出しまして、

となったわけです。

さらには15:30にatコマンドで再起動が仕込まれている事もここで判明。

atコマンドとか前職勤務時代に1度くらいしか使ったこと無いわー、ってかそりゃcronのログを見ても何もでてないわけだわ、と、仕込みのすごさ(あるいはエグさ)に改めて舌を巻いた次第です。

で、正解は

結局未解決となったWebサーバの2つの問題、商品画像が出ない問題と購入処理が最後まで行かない問題は、phpコンパイルオプションで必要な機能が指定されていなかった、というのが原因でした。
実はApacheがロードしていたphpはバージョン5.3.2のもので、正しくコンパイルされたphpは5.3.4、/usr/local/php-5.3.4以下に配置されているものでした。で、これをロードしているのがあのnginxだった、というオチだったのです。
Apacheではなくnginxを起動させる必要がある、というところまでは正解だったのですが、さらにphp-fpmも動かす必要があったのです。これをやらないとphpが動作せず、結果的にnginxのデフォルトのページが見えてしまう、という次第。というかあれがデフォルトのページだったのかというと、多分違った気がしますが、ここは未検証。

我々のチームはWebサイトを落とさない、という1つ目の目標はクリアできた(おかげで優勝する事ができたのですが)のですが、ここに気付く事ができなかったというのは悔いが残るポイントです。というか最低限起動スクリプトくらいは置いといてくれよぉ…。

各チームのプレゼン、解答編の発表が終わった後は毎度恒例の懇親会へ。ここでも色々と面白い話をうかがうことができました。「WEBサーバのrootのパスワードが潰してあったのは、あれは意図的ではなくうっかり」というのを聞いた時には思わず膝から崩れましたけど…w
あとここまで触れていなかったBATCHサーバ。山○君が

tweetしていたものの、「あいつの言う事だから絶対そんなはずは無い、何かあるはずだ」と思ってみんなで色々調べていたのですが、本当に何もしていなかった、というのが判明したのも懇親会の席でした。
でももっと慄然としたのは「どのチームも触れてない罠がまだいっぱいあったんですけどねー」という一言。この時にと決意したのでした。

思ったこと

前職では運用管理を請け負うサービスの中の人でしたし、今も某Webサービスの会社のインフラ管理をしているわけですが、いずれも

  • システムのあらまし(どういうサーバがいてどういう働きをしているか、どんなデーモンが動いているか、アプリケーションはどこに何があるか)を理解しており
  • どんな事が起きたらどうする、というシミュレーションを事前にしており
  • 何か起こった時も密に担当者と連絡を取り合える(というか、今は私自身が担当者ですが)

という環境を整えた上であったので、今回のようにすべて即興かつタイムリミットが迫った状態で、というのは中々いい刺激になりました。
対応をしながら、あるいは懇親会の席で思ったのは

  • やはり事前のシステム把握は大事、正常な状態で何がどう動いているのか、を知っておく事が正常復帰への第一歩。
  • ある障害が発生した時に正しい原因を見つけられるように、日頃から「どういう障害が/どういう事象が起きうるか?」という事をシミュレートし、思い出せるようにしておくべき。
    • 特に今回は、「Apache脆弱性を突いた攻撃がくるかもしれない、来た場合にはApacheがメモリを大量に消費してハングアップするかもしれない」という事に思い当たれば、Apacheのアップデートはできなくても、iptablesで攻撃元のアクセスを遮断するとか、兆候が出た時点でApacheを再起動するとかいう対応策が取れた
    • またこれは構築時の話として、「サーバが再起動するかもしれない」ということを考えていれば、再起動後もnginxが正しく起動するように設定できていたはず。

といったあたり。「備えあれば憂いなし」は金言だと思います。
ただまぁ経験上、備えの斜め上の事態が発生する事もよくあるので、そんな場合には即応力というかとっさの判断力が必要になる、とは思います。こっちの訓練は…どうしたらいいんでしょうね。

おまけ

帰りの副都心線の中でふと「サーバもう1個用意して自分でLAMP環境作って、アプリケーションのtar玉とmysqldumpでのダンプデータをコピーして、そっちをサービスインさせるという手があったな」という凶悪な手を思いついたのですが、IPアドレスを差し替えられない時点で駄目ですね。動作確認とか色々しないといけないですし。