このトピックでは、CloudMonitorがMessage Service (MNS) キューを使用してElastic Compute Service (ECS) インスタンスのステータス変更イベントを自動的に処理する方法について説明します。
前提条件
MNSコンソールでキューが作成されます。 例: ecs-cms-event.
詳細については、「コンソールでのキューの管理」をご参照ください。
CloudMonitorコンソールで、システムイベントでトリガーされるアラートルールが作成されます。
詳細については、「システムイベントでトリガーされるアラートルールの管理」をご参照ください。
Pythonの依存関係がインストールされます。
このトピックのコードはすべてPython 3.6で記述されています。 JavaやPHPなどの他のプログラミング言語を使用することもできます。
CloudMonitor SDK For Pythonのインストール方法については、「CloudMonitor SDK for Python」トピックの「Cloud Monitor SDK for Pythonのインストール」セクションをご参照ください。
背景情報
CloudMonitorは、既存のシステムイベントに加えて、ECSのステータス変更イベントもサポートしています。 ステータス変更イベントには、プリエンプティブルインスタンスに適用される割り込み通知イベントが含まれます。 ステータス変更イベントは、ECSインスタンスのステータスが変更されるとトリガーされます。 インスタンスのステータスの変更は、ECSコンソールまたはSDKを使用して実行する操作、またはAPI操作を呼び出すことによって発生する可能性があります。 インスタンスステータスの変更は、自動スケーリング、料金滞納、またはシステム例外によっても発生する可能性があります。
CloudMonitorは、システムイベントに対して、MNSキュー、Function Compute、コールバックURL、およびSimple Log Serviceの通知方法を提供します。 この例では、MNSキューを使用して、CloudMonitorがECSインスタンスのステータス変更イベントを自動的に処理する方法に関する3つのベストプラクティスを説明します。
手順
CloudMonitor は、ECS インスタンスのすべてのステータス変更イベントを MNS に送信します。 MNSはメッセージを受信し、メッセージを処理する。
実践1: ECSインスタンスのすべての作成およびリリースイベントを記録する
ECSコンソールでリリースされたECSインスタンスを照会することはできません。 リリースされたECSインスタンスを照会する必要がある場合は、すべてのECSインスタンスのステータス変更イベントをデータベースまたはSimple Log Serviceに保存できます。 ECSインスタンスが作成されると、CloudMonitorはPendingイベントを送信します。 ECSインスタンスがリリースされると、CloudMonitorはDeletedイベントを送信します。
Confファイルを作成します。
Confファイルには、
endpoint
、access_key
、access_key_secret
、region_id
(例: cn-beijing) 、およびqueue_name
のパラメーターを含める必要があります。説明エンドポイント
を取得するには、MNSコンソールにログインし、[キュー] ページに移動し、[エンドポイントの取得] をクリックします。import os # Make sure that the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables are configured in the code runtime environment. # If the project code is leaked, the AccessKey pair may be leaked and the security of resources within your account may be compromised. The following sample code shows how to use environment variables to obtain an AccessKey pair and use the AccessKey pair to call API operations. The sample code is for reference only. We recommend that you use Security Token Service (STS) tokens, which provide higher security. class Conf: endpoint = 'http://<id>.mns.<region>.aliyuncs.com/' access_key = os.environ['ALIBABA_CLOUD_ACCESS_KEY_ID'] access_key_secret = os.environ['ALIBABA_CLOUD_ACCESS_KEY_SECRET'] region_id = 'cn-beijing' queue_name = 'test' vsever_group_id = '<your_vserver_group_id>'
MNS SDKを使用して、MNSからメッセージを受信するためのMNSクライアントを開発します。
# -*- coding: utf-8 -*- import json from mns.mns_exception import MNSExceptionBase import logging from mns.account import Account from . import Conf class MNSClient(object): def __init__(self): self.account = Account(Conf.endpoint, Conf.access_key, Conf.access_key_secret) self.queue_name = Conf.queue_name self.listeners = dict() def regist_listener(self, listener, eventname='Instance:StateChange'): if eventname in self.listeners.keys(): self.listeners.get(eventname).append(listener) else: self.listeners[eventname] = [listener] def run(self): queue = self.account.get_queue(self.queue_name) while True: try: message = queue.receive_message(wait_seconds=5) event = json.loads(message.message_body) if event['name'] in self.listeners: for listener in self.listeners.get(event['name']): listener.process(event) queue.delete_message(receipt_handle=message.receipt_handle) except MNSExceptionBase as e: if e.type == 'QueueNotExist': logging.error('Queue %s not exist, please create queue before receive message.', self.queue_name) else: logging.error('No Message, continue waiting') class BasicListener(object): def process(self, event): pass
上記のコードは、MNSからメッセージを受信し、リスナーが呼び出されてメッセージを消費した後にメッセージを削除するために使用されます。
リスナーを登録してイベントを消費します。 リスナーは、リスナーがPendingまたはDeletedイベントを受信するたびにログエントリを生成します。
# -*- coding: utf-8 -*- import logging from .mns_client import BasicListener class ListenerLog(BasicListener): def process(self, event): state = event['content']['state'] resource_id = event['content']['resourceId'] if state == 'Panding': logging.info(f'The instance {resource_id} state is {state}') elif state == 'Deleted': logging.info(f'The instance {resource_id} state is {state}')
Main関数に次のコードを追加します。
mns_client = MNSClient() mns_client.regist_listener(ListenerLog()) mns_client.run()
本番環境では、その後のクエリと監査のために、イベントをデータベースまたはSimple Log Serviceに保存できます。
プラクティス2: シャットダウンしたECSインスタンスを自動的に再起動する
ECSインスタンスが予期せずシャットダウンされる可能性があるシナリオでは、ECSインスタンスを自動的に再起動する必要があります。
プラクティス1で開発したMNSクライアントを再利用して、別のリスナーを作成できます。 ECSインスタンスのStoppedイベントを受信すると、ECSインスタンスでstartコマンドを実行してインスタンスを起動できます。
# -*- coding: utf-8 -*- import logging from aliyunsdkecs.request.v20140526 import StartInstanceRequest from aliyunsdkcore.client import AcsClient from .mns_client import BasicListener from .config import Conf class ECSClient(object): def __init__(self, acs_client): self.client = acs_client # Start the ECS instance. def start_instance(self, instance_id): logging.info(f'Start instance {instance_id} ...') request = StartInstanceRequest.StartInstanceRequest() request.set_accept_format('json') request.set_InstanceId(instance_id) self.client.do_action_with_exception(request) class ListenerStart(BasicListener): def __init__(self): acs_client = AcsClient(Conf.access_key, Conf.access_key_secret, Conf.region_id) self.ecs_client = ECSClient(acs_client) def process(self, event): detail = event['content'] instance_id = detail['resourceId'] if detail['state'] == 'Stopped': self.ecs_client.start_instance(instance_id)
本番環境では、startコマンドの実行後にStarting、Running、またはStoppedイベントをリッスンできます。 その後、ECSインスタンスが起動されたかどうかに基づいてタイマーとカウンターを使用して、さらにO&Mを実行できます。
実践3: プリエンプティブルインスタンスがリリースされる前に、SLBインスタンスからプリエンプティブルインスタンスを自動的に削除する
割り込みイベント通知は、プリエンプティブルインスタンスがリリースされる5分前にトリガーされます。 5分間は、サービスが中断されないように特定の操作を実行できます。 たとえば、Server Load Balancer (SLB) インスタンスからプリエンプティブルインスタンスを削除できます。
プラクティス1で開発したMNSクライアントを再利用して、別のリスナーを作成できます。 リスナーがプリエンプティブルインスタンスの割り込みイベント通知を受信すると、SLB SDKを呼び出して、SLBインスタンスからプリエンプティブルインスタンスを削除できます。
# -*- coding: utf-8 -*- from aliyunsdkcore.client import AcsClient from aliyunsdkcore.request import CommonRequest from .mns_client import BasicListener from .config import Conf class SLBClient(object): def __init__(self): self.client = AcsClient(Conf.access_key, Conf.access_key_secret, Conf.region_id) self.request = CommonRequest() self.request.set_method('POST') self.request.set_accept_format('json') self.request.set_version('2014-05-15') self.request.set_domain('slb.aliyuncs.com') self.request.add_query_param('RegionId', Conf.region_id) def remove_vserver_group_backend_servers(self, vserver_group_id, instance_id): self.request.set_action_name('RemoveVServerGroupBackendServers') self.request.add_query_param('VServerGroupId', vserver_group_id) self.request.add_query_param('BackendServers', "[{'ServerId':'" + instance_id + "','Port':'80','Weight':'100'}]") response = self.client.do_action_with_exception(self.request) return str(response, encoding='utf-8') class ListenerSLB(BasicListener): def __init__(self, vsever_group_id): self.slb_caller = SLBClient() self.vsever_group_id = Conf.vsever_group_id def process(self, event): detail = event['content'] instance_id = detail['instanceId'] if detail['action'] == 'delete': self.slb_caller.remove_vserver_group_backend_servers(self.vsever_group_id, instance_id)
重要割り込みイベント通知の場合は、イベント名を
mns_client.regist_listener(ListenerSLB(Conf.vsever_group_id), 'Instance:PreemptibleInstanceInterruption')
形式で設定します。本番環境では、別のプリエンプティブルインスタンスを申請し、それをSLBインスタンスのバックエンドサーバーとして追加して、サービスのパフォーマンスを確保できます。