このトピックでは、CloudMonitor が Simple Message Queue (旧称 MNS) (SMQ) キューを使用して Elastic Compute Service (ECS) インスタンスのステータス変更イベントを自動的に処理する方法について説明します。
前提条件
SMQ コンソールでキューが作成されます (例: ecs-cms-event)。
キューの作成方法の詳細については、「キューを作成する」をご参照ください。
CloudMonitor コンソールで、システムイベントによってトリガーされるアラートルールが作成されます。システムイベントによってトリガーされるアラートルールの作成方法の詳細については、「システムイベントによってトリガーされるアラートルールを管理する (旧)」をご参照ください。
Python の依存関係がインストールされています。
このトピックのすべてのコードは Python 3.6 で記述されています。 Java や PHP などの他のプログラミング言語も使用できます。
Python 用 CloudMonitor SDK のインストール方法の詳細については、「SDK リファレンス」をご参照ください。
背景情報
既存のシステムイベントに加えて、CloudMonitor は ECS のステータス変更イベントをサポートしています。 ステータス変更イベントには、プリエンプティブルインスタンスに適用される中断通知イベントが含まれます。 ECS インスタンスのステータスが変更されると、ステータス変更イベントがトリガーされます。 インスタンスのステータス変更は、ECS コンソールまたは SDK を使用して実行する操作、または API 操作の呼び出しによって発生する可能性があります。 インスタンスのステータス変更は、自動スケーリング、支払い遅延、またはシステム例外によっても発生する可能性があります。
CloudMonitor は、システムイベントの通知方法として、SMQ、Function Compute、コールバック URL、Simple Log Service を提供しています。 このトピックでは、SMQ を例として使用して、CloudMonitor が ECS インスタンスのステータス変更イベントを自動的に処理する方法に関する 3 つのベストプラクティスについて説明します。
手順
CloudMonitor は、ECS インスタンスのすべてのステータス変更イベントを SMQ に送信します。 SMQ はメッセージを受信し、処理します。
実践 1: ECS インスタンスのすべての作成イベントとリリースイベントを記録する
ECS コンソールでリリースされた ECS インスタンスをクエリすることはできません。 リリースされた ECS インスタンスをクエリする必要がある場合は、すべての ECS インスタンスのステータス変更イベントをデータベースまたは Simple Log Service に保存できます。 ECS インスタンスが作成されると、CloudMonitor は Created イベントを送信します。 ECS インスタンスがリリースされると、CloudMonitor は Deleted イベントを送信します。
Conf ファイルを作成します。
Conf ファイルには、SMQ の
endpoint
、Alibaba Cloud のaccess_key
とaccess_key_secret
、region_id
(例: cn-beijing)、queue_name
の各パラメーターを含める必要があります。説明endpoint
を取得するには、SMQ コンソールにログインし、[キュー] ページに移動して、[エンドポイントの取得] をクリックします。import os # ALIBABA_CLOUD_ACCESS_KEY_ID および ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数が設定されていることを確認します。 # プロジェクトコードが漏洩した場合、AccessKey ペアが漏洩し、アカウント内のすべてのリソースのセキュリティが侵害される可能性があります。 次のサンプルコードは、環境変数を使用して AccessKey ペアを取得し、AccessKey ペアを使用して API 操作を呼び出す方法を示しています。 サンプルコードは参照用です。 セキュリティトークンサービス (STS) を使用することをお勧めします。これはより高いセキュリティを提供します。 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>'
SMQ SDK を使用して、SMQ からメッセージを受信するための 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
上記のコードは、SMQ からメッセージを受信し、リスナーがメッセージを消費するために呼び出された後にメッセージを削除するために使用されます。
イベントを消費するリスナーを登録します。 リスナーは、Created イベントまたは 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 == 'Created': 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 # ECS インスタンスを起動します。 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 インスタンスのバックエンドサーバーとして追加して、サービスのパフォーマンスを確保できます。