こんにちは。kobayashiです。
今回はサーバの障害通知&対応システムを作ってみました。
Zabbixで検知した障害内容をAmazon ConnectのOutboundCallを用いて音声通知をし、 それを受けた管理者が電話ボタンをプッシュすることで、 検知された障害と予め紐づけてあるコマンドが実行されるというシステムです。
概要
構築するシステムのイメージは下図の通りです。
①Zabbixが障害を検知すると障害内容が記載されたファイルがS3にPutされます。
②S3のイベント通知でLambdaが走り、Amazon Connectで登録された電話番号宛に電話が掛かります。
③S3のファイル(障害内容)をPollyが読み上げ、障害に対応したコマンドを実行するか質問します。コマンドを実行するかどうかは電話のボタンをプッシュして選択します。
④「実行する」が選択された場合は、RunCommandで障害が検知されているサーバにコマンドが送信されます。
手順
では早速設定を行っていきたいと思います。
前提として下記の作業は既に完了しているものとします。
●EC2インスタンス(CentOS7)を作成
●Zabbixのインストール、初期設定
●下記のポリシーがアタッチされたロールがEC2に割り当てられている
・EC2(ZabbixServer) AWSLambdaFullAccess AmazonS3FullAccess
・EC2(ZabbixClient) AmazonEC2RoleforSSM
●EC2(ZabbixClient)にSSMエージェントをインストールする。 ※今回はCentOS7を利用しているので手動でインストールする必要があります。 詳しくはこちらをご参照下さい。
●S3バケットの作成
S3に障害内容が記載されたファイルを送る
EC2(ZabbixServer)に下記のスクリプトを配置しておきます。 Amazon Connectで電話を掛けた際にPollyに読ませるファイルをバケットにPutするスクリプトになります。
#!/bin/bash host="$1" trigger="$2" today=$(date "+%Y%m%d_%H%M%S") cd /tmp/sendfile echo こちらはザビックスの障害アラートです,案内に従って対応をして下さい,障害が発生しているサーバーのホスト名は,${host},トリガー名は,${trigger},です。 >> "alert_${host}_${today}.txt" newest=$(ls -tr | tail -1) sudo aws s3 cp ${newest} s3://<バケット名>/
Zabbixコンソールにアクセスし下図の通りアクションの設定をします。 電話で通知するトリガーを設定しましょう。
実行内容はリモートコマンドを選択し、先程配置したスクリプトを実行するように設定します。 引数はZabbixのマクロを利用して、ホスト名とトリガー名を指定しています。
テストしてみると以下のようにS3へファイルがPutされていました。
DynamoDBテーブル作成
Amazon Connectで利用する情報をDynamoDBのテーブルに入力しておきます。
・trigger-name : Zabbixトリガーの名前 ・command : RunCommandで実行するコマンド ・polly : Amazon ConnectのフローでPollyに読ませる障害時対応の説明
こちらで設定したコマンドが最終的に電話のプッシュボタンを利用して実行する処理になります。
Amazon Connect 問い合わせフローの作成
Amazon Connectの問い合わせフローの作成をします。 フロー作成前までの利用方法に関しましては次の記事をご参照下さい。
Amazon Connectのフローイメージ図は下記の通りです。
次にAmazon Connectで利用するlambda functionを作成します。
Lambda function (発信) S3のアクションをトリガーにAmazon ConnectでOutboundCallを発信するための関数です。
import boto3 def lambda_handler(event, context): client = boto3.client('connect', region_name='<Amazon Connectのリージョン>') alert = client.start_outbound_voice_contact( DestinationPhoneNumber="<発信先番号>", ContactFlowId="<フローID>", InstanceId="<インスタンスID>", SourcePhoneNumber="<発信元番号>" )
Lambda function1
S3にPutされたファイルの中身を取得しPollyに渡すための関数です。
import boto3 def lambda_handler(event, context): response = boto3.resource('s3') bucket = response.Bucket('<バケット名>') objectname = [] for object in bucket.objects.all(): name = object.key objectname.append(name) select_object = objectname[-1] obj = bucket.Object(select_object) inside = obj.get() body = inside['Body'].read() bo = body.decode('utf-8') return {'Res': bo}
Lambda function2
DynamoDBのテーブルから障害内容に紐づいた障害対応の説明文を取得し、Pollyに渡すための関数です。
import boto3 def lambda_handler(event, context): response = boto3.resource('s3') bucket = response.Bucket('<バケット名>') objectname = [] for object in bucket.objects.all(): name = object.key objectname.append(name) select_object = objectname[-1] obj = bucket.Object(select_object) inside = obj.get() body = inside['Body'].read() bite = body.decode('utf-8') select = bite.split(",") trigger_name = (select[5]) dynamodb = boto3.client('dynamodb','<リージョン名>') get_command = dynamodb.get_item( TableName='<テーブル名>', Key={ 'trigger-name':{ 'S':trigger_name } } ) get_polly = get_command['Item']['polly']['S'] return {'Res': get_polly}
Lambda function3
RunCommandを実行する関数です。
import boto3 from time import sleep def lambda_handler(event, context): response = boto3.resource('s3') bucket = response.Bucket('<バケット名>') objectname = [] for object in bucket.objects.all(): name = object.key objectname.append(name) select_object = objectname[-1] obj = bucket.Object(select_object) inside = obj.get() body = inside['Body'].read() bite = body.decode('utf-8') select = bite.split(",") instance_name = (select[3]) trigger_name = (select[5]) ec2=boto3.client('ec2','<リージョン名>') instance_list = ec2.describe_instances( Filters=[ { 'Name':'tag:Name', 'Values':[instance_name] } ] ) instanceID = instance_list['Reservations'][0]['Instances'][0]['InstanceId'] dynamodb = boto3.client('dynamodb','<リージョン名>') get_command = dynamodb.get_item( TableName='<テーブル名>', Key={ 'trigger-name':{ 'S':trigger_name } } ) select_command = get_command['Item']['command']['S'] ssm=boto3.client('ssm','<リージョン名>') run_command = ssm.send_command( InstanceIds = [instanceID], DocumentName = "AWS-RunShellScript", Parameters= { "commands":[ select_command ] } ) command_id = run_command['Command']['CommandId'] sleep(2) res = ssm.list_command_invocations( CommandId = command_id, Details = True ) status = res['CommandInvocations'][0]['Status'] return {'Res': status}
Lambda functionを作成したら、Amazon Connectから呼び出せるように設定をします。
aws lambda add-permission --function-name function:<Lambdaの関数名> \ --statement-id 1 --principal connect.amazonaws.com \ --action lambda:InvokeFunction --source-account <アカウントID> \ --source-arn <Amazon connectインスタンスのarn>
詳細に関しましてはこちらをご参照下さい。
以上のLambda functionを利用して作成したAmazon Connectのフローが下図になります。
実際に試してみると電話のみで、障害の把握から対応まで行うことができました。 Pollyはただテキストを渡すだけでも流暢に読み上げてくれるため非常に便利ですね。 Amazon Connectは簡単に様々なサービスと連携したフローを作成できるので、皆様も是非お試しください。