はじめに
Webサイトやサーバの監視において、Webサイト停止やサーバ停止等の異常を検知した場合、メールで異常を通知する事が多いと思います。
「Twilio」というクラウドAPIサービスを使用すると、RubyやPHPといった様々なプログラム言語から、指定した電話番号に対して、簡単に電話をかけたりSMS通知を行なえます。
http://twilio.kddi-web.com/
この間、ZabbixやAWS CloudWatchによるシステム監視で、Webサイト停止やサーバ停止といった異常を検知した時、TwilioAPI(Ruby)を使用して、電話で障害を通知する仕組みを作ってみましたので、まとめていこうと思います。
システム構成
以下のような構成で、ZabbixやAWS CloudWatchからTwilioAPIをコールして、電話で障害を通知する仕組みを作ってみました。
ZabbixサーバやTwilioAPIサーバ(詳細は後述)については、Amazon EC2のAmazon Linux上に構築しました。
Twilioの利用を開始するまでの流れ
まず、Twilio公式サイトにサインアップして、Twilioアカウントを作成します。
http://twilio.kddi-web.com/
Twilioアカウントを作成したら「プラグラマブル Voice」->「はじめよう」をクリックすると、「Twilio音声通話 電話番号」が発行されます。
TwilioAPIを使って電話をかけると、発行された「Twilio音声通話 電話番号」の電話番号から電話がかかってきます。
https://jp.twilio.com/user/account/voice/phone-numbers
次に、APIクレデンシャル画面で、TwilioAPIを利用する為の「ACCOUNT SID」「AUTH TOKEN」と確認します。
https://jp.twilio.com/user/account/voice/getting-started
TwilioAPIを使用したTwilioクラウドサービスへの電話発信について
ZabbixやAWS CloudWatchから簡単に電話をかけられるよう、Ruby + SinatraでTwilioAPIサーバを作成してみました。
ZabbixやAWS CloudWatchからTwilioAPIサーバをコールする事で、Webサイトやサーバ監視で異常を検知した時、電話をかけれるようにしました。
TwilioAPIを使用して電話をかけるサンプルコード、またTwilioAPIを使う時に気をつけた方が良さそうな点について記載致します。
・TwilioAPIで電話をかけるには、TwilioクラウドサービスからTwilioAPIへのHTTPコールバック通信を通す必要があります。AWSのNetworkACLsやセキュリティグループで、TwilioクラウドサービスからTwilioAPIサーバへのHTTP通信を許可しておく必要があります。
・サンプルコードでは、GETで電話をかける電話番号をパラメータ指定出来るようにしていますが、電話番号は秘匿した方が良い情報なので、実際の運用時にはGETでパラメータ渡さない方が良いと思います。また、発信先の電話番号は特定番号だけに絞る、API認証を設ける等の対応も適宜行なうのが良いかと思います。
・Twilioで電話をかける際には、電話番号をE.164形式に変換する必要があります。
Twilioから電話をかける電話番号 090-XXXX-1234 → +8190XXXX1234
Twilio音声通話 電話番号 050-XXXX-5678 → +8150XXXX5678
・サンプルコード内では「phony」を利用させて頂く事で、GETパラメータで「090-XXXX-1234」のような発信先の電話番号を指定する際、内部的に「+8190XXXX1234」のようなE.164形式の電話番号に変換し、Twilioクラウドサービスへ電話発信のリクエストを投げて、電話をかけるようにしています。
https://github.com/floere/phony/blob/ebcfcb42f3c50a8d1c9e23d9ae05b338c8cc6a95/qed/country-specific.md
・電話番号のE.164形式については、以下をご参照下さい。
https://www.nic.ad.jp/ja/basics/terms/E164.html
TwilioAPIで電話をかけるサンプルコード
以下がTwilioAPIのサンプルコードになります。
雑多なコードで済みませんが、以下のようにTwilioクラウドサービスが提供するAPI(client.account.calls.create)を利用する事で、指定した電話番号に電話をかける事が出来ます。
[root@twilio-example-server ~]# cat /root/twilio/twilio_api_server.rb
require 'rubygems'
require 'twilio-ruby'
require 'sinatra'
require 'sinatra/reloader'
require 'json'
require 'phony'
get '/call/test' do
# put your own credentials here
@account_sid = 'Twilioアカウント APIクレデンシャルのACCOUNT SID'
@auth_token = 'Twilioアカウント APIクレデンシャルのAUTH TOKEN'
@to_tel_number = '+8190XXXX1234'
@from_tel_number = '+8150XXXX5678'
# set up a client to talk to the Twilio REST API
@client = Twilio::REST::Client.new @account_sid, @auth_token
call = @client.account.calls.create(
:url => "http://demo.twilio.com/docs/voice.xml",
:to => @to_tel_number,
:from => @from_tel_number
)
puts call.to
"Twilio API Test Target Tel Number = [#@to_tel_number]]"
end
get '/call' do
if params['api_key'] == nil
halt 400, 'API Key Parameter Not Found!'
end
if params['api_key'] != 'twilio-api-sample-key'
halt 400, 'API Key Wrong!'
end
if params['to_tel_number'] == nil
halt 400, 'To E.164 Telephones Number Parameter Not Found!'
end
if params['to_tel_number'].count("-") != 2 then
halt 400, 'To Telephones Number Format Error [XXX-XXXX-XXXX]!'
end
if params['tel_messages_type'] == nil
halt 400, 'Telephones Messages Type Parameter Not Found!'
end
# put your own credentials here
@account_sid = 'Twilioアカウント APIクレデンシャルのACCOUNT SID'
@auth_token = 'Twilioアカウント APIクレデンシャルのAUTH TOKEN'
@to_tel_number = params['to_tel_number']
@tel_messages_type = params['tel_messages_type']
@from_tel_number = '+8150XXXX5678'
## https://github.com/floere/phony/blob/ebcfcb42f3c50a8d1c9e23d9ae05b338c8cc6a95/qed/country-specific.md
@japan = Phony["81"]
@to_tel_number = @japan.normalize( @to_tel_number )
@to_tel_number = '+81' + @to_tel_number
##
if @tel_messages_type == "zabbix_site_alert" then
@twiml_url = 'http://TwiMLサーバのIPアドレスやFQDN/twilio_test_zabbix_site_alert.xml'
elsif @tel_messages_type == "cloudwatch_disk_alert" then
@twiml_url = 'http://TwiMLサーバのIPアドレスやFQDN/twilio_test_cloudwatch_disk_alert.xml'
elsif @tel_messages_type == "cloudwatch_instance_stop_alert" then
@twiml_url = 'http://TwiMLサーバのIPアドレスやFQDN/twilio_test_cloudwatch_instance_alert.xml'
else
@twiml_url = 'http://TwiMLサーバのIPアドレスやFQDN/twilio_test.xml'
end
# set up a client to talk to the Twilio REST API
@client = Twilio::REST::Client.new @account_sid, @auth_token
call = @client.account.calls.create(
:url => @twiml_url,
:to => @to_tel_number,
:from => @from_tel_number
)
puts call.to
"Twilio API Test Target Tel Number = [#@to_tel_number]]"
end
get '/' do
erb :index
end
get '/about' do
erb :about
end
post '/confirm' do
if params[:to_tel_number] == '' || params[:to_tel_number] == nil then
halt 400, 'To E.164 Telephones Number Parameter Not Found!'
end
if params[:to_tel_number].count("-") != 2 then
halt 400, 'To Telephones Number Format Error [-] Not Found! Example Number Format [XXX-XXXX-XXXX]!'
end
@to_tel_number = params[:to_tel_number]
@japan = Phony["81"]
@to_tel_number = @japan.normalize( @to_tel_number )
@to_tel_number = '+81' + @to_tel_number
##
# put your own credentials here
@account_sid = 'Twilioアカウント APIクレデンシャルのACCOUNT SID'
@auth_token = 'Twilioアカウント APIクレデンシャルのAUTH TOKEN'
@from_tel_number = '+8150XXXX5678'
@twiml_url = 'http://TwiMLサーバのIPアドレスやFQDN/twilio_test.xml'
# set up a client to talk to the Twilio REST API
@client = Twilio::REST::Client.new @account_sid, @auth_token
call = @client.account.calls.create(
:url => @twiml_url,
:to => @to_tel_number,
:from => @from_tel_number
)
puts call.to
erb :confirm
end
[root@twilio-example-server ~]#
TwilioAPIから電話をかけれるようにするには、TwilioAPIサーバ上でサンプルコードを実行します。
[root@twilio-example-server ~]# ruby /root/twilio/twilio_api_server.rb -o 0.0.0.0 -p 80 &
[1] 3337
[root@twilio-example-server ~]#
[root@twilio-example-server ~]# ps awux | grep -v grep | grep ruby
root 3337 0.8 6.1 220624 30932 pts/0 Sl 19:42 0:00 ruby /root/twilio/twilio_api_server.rb -o 0.0.0.0 -p 80
[root@twilio-example-server ~]#
・Twilioから電話かけた時に流す音声メッセージは「TwiML」というファイルで定義します。
TwiMLファイルはTwilioAPIサーバ自身に置いても、別のHTTPサーバに置いても良いようです。今回は別のサーバにTwiMLファイル置けるか試したかったので、TwilioAPIサーバ以外にTwiMLファイルを作成してみました。
[root@TwiML-example-server ~]# cat /var/www/html/twilio_test_zabbix_site_alert.xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say language="ja-jp">TwilioとZabbix</Say>
<Say language="ja-jp">の監視テストです。</Say>
<Say language="ja-jp">監視対象サイト</Say>
<Say language="ja-jp">が停止しました。</Say>
</Response>
[root@TwiML-example-server ~]#
TwilioAPIで電話をかける例
Webブラウザを起動し、以下のようにTwilioAPIサーバへアクセスすると、090-XXXX-1234に電話をかける事が出来ます。
ZabbixやAWS CloudWatchのシステム監視で異常を検知した時、上記のようにTwilioAPIをコールすると、電話をかける事が出来ます。
電話を取ると、TwiMLファイルに記述したメッセージが流れます。
ZabbixからTilioAPIサーバ経由で電話をかける
Zabbixのアクションで以下のようなアクションを設定し、監視トリガーでアクションを設定すると、監視で異常を検知した時に電話をかける事が出来ます。
/usr/bin/curl -s "http://TwilioAPIサーバのIPアドレスやFQDN/call?api_key=twilio-api-sample-key&to_tel_number=%22090-XXXX-1234%22&tel_messages_type=zabbix_site_alert"
AWS CloudWatchからTwilioAPIサーバ経由で電話をかける
AWS CloudWatchからは直接APIをコール出来ないので、AWS Lambdaに以下のようなファンクションを作成します。
AWS CloudWatchの監視で異常を検知した時、AWS CloudWatch -> AWS SNS -> AWS Lambdaファンクションを実行するように設定します。これによりAWS CloudWatchによる監視で、EC2インスタンスの停止やディスク使用率超過等を検知した時、電話をかける事が出来ます。
console.log('Loading function');
exports.handler = function(event, context) {
var exec = require('child_process').exec;
var cmd = "curl -s 'http://TwilioAPIサーバのIPアドレスやFQDN/call?api_key=twilio-api-sample-key&to_tel_number=%22090-XXXX-1234%22&tel_messages_type=cloudwatch_disk_alert'"
var child = exec(cmd, function(error, stdout, stderr) {
if (!error) {
console.log('standard out: ' + stdout);
console.log('standard error: ' + stderr);
context.done();
} else {
console.log("error code: " + error.code + ", err: " + error);
context.done(error,'lambda');
}
});
};
ざっとまとめてみましたが、障害の緊急度や影響度を踏まえて、軽微な障害では、電話による通知でなく、SMSによる通知でも良いかもしれませんね。
メール・電話といった障害通知手段を上手く組み合わせて、システム障害を検知しやすくしたいですね。
細かい設定や情報はあらためて記載していきたいと思います。