LoginSignup
18
18

More than 5 years have passed since last update.

TwilioAPIを使用してZabbix + AWS CloudWatchのシステム監視で異常検知した時に電話で通知する

Last updated at Posted at 2016-04-19

はじめに

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上に構築しました。

構成図.png

Twilioの利用を開始するまでの流れ

まず、Twilio公式サイトにサインアップして、Twilioアカウントを作成します。
http://twilio.kddi-web.com/

スクリーンショット 2016-04-19 21.46.40.png

スクリーンショット 2016-04-19 21.47.50.png

Twilioアカウントを作成したら「プラグラマブル Voice」->「はじめよう」をクリックすると、「Twilio音声通話 電話番号」が発行されます。

スクリーンショット 2016-04-19 21.49.33.png

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)を利用する事で、指定した電話番号に電話をかける事が出来ます。

twilio_api_server.rb
[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ファイルを作成してみました。

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のアクションで以下のようなアクションを設定し、監視トリガーでアクションを設定すると、監視で異常を検知した時に電話をかける事が出来ます。

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"

zabbix1.png

zabbix2.png

zabbix3.png

AWS CloudWatchからTwilioAPIサーバ経由で電話をかける

AWS CloudWatchからは直接APIをコール出来ないので、AWS Lambdaに以下のようなファンクションを作成します。

AWS CloudWatchの監視で異常を検知した時、AWS CloudWatch -> AWS SNS -> AWS Lambdaファンクションを実行するように設定します。これによりAWS CloudWatchによる監視で、EC2インスタンスの停止やディスク使用率超過等を検知した時、電話をかける事が出来ます。

TwilioAPIで電話をかけるLambdaファンクションのサンプルコード
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');
        }
    });
};

スクリーンショット 2016-04-19 21.02.31.png

スクリーンショット 2016-04-19 21.03.22.png

スクリーンショット 2016-04-19 21.05.50.png

スクリーンショット 2016-04-19 21.08.28.png


ざっとまとめてみましたが、障害の緊急度や影響度を踏まえて、軽微な障害では、電話による通知でなく、SMSによる通知でも良いかもしれませんね。

メール・電話といった障害通知手段を上手く組み合わせて、システム障害を検知しやすくしたいですね。

細かい設定や情報はあらためて記載していきたいと思います。

18
18
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
18
18