このトピックでは、CloudMonitor が MNS メッセージキューを使用して、ECS ステータス変更イベントを自動的に処理する方法について説明します。

概要

インスタンスステータスが変更されると、ECS インスタンスステータス変更イベントがトリガーされます。 具体的には、ステータス変更イベントは、コンソール上の操作、API または SDK の使用、自動スケーリング、料金滞納の検出、システム例外などに起因する変更です。

ECS ステータス変更イベントの処理を自動化するために、CloudMonitor では、関数計算式とMNS メッセージキューの 2 つの方法を提供しています。 このトピックでは、MNS メッセージキューを使用する 3 つのベストプラクティスについて説明します。

準備

  • メッセージキューを作成します。
    1. MNS コンソールにログインします。
    2. [キューリスト] ページで、対象のリージョンを選択し、右上隅の [キューの作成] をクリックします。

    3. [新しいキュー] ダイアログボックスで、[キュー名] (例:ecs-cms-event) およびその他の必要な情報を入力し、[OK] をクリックします。
  • ステータス変更イベント用のアラームルールを作成します。
    1. Cloud Monitor コンソール にログインします。
    2. 左側のナビゲーションウィンドウで、[イベントモニタリング] をクリックします。
    3. [アラームルール] タブページに移動し、 [イベントアラーム作成] をクリックします。

    4. [基本情報] エリアで、アラームルール名称 (たとえば、ecs-test-rule) を入力します。
    5. [イベントアラーム] エリアで、以下のとおりパラメーターを設定します。
      • [イベントタイプ][システムイベント] に設定します。
      • [製品タイプ] [ECS ] に、[イベントタイプ][StatusNotiifcation] に設定し、その他のパラメーターを必要に応じて設定します。
      • [リソース範囲][全リソース] に設定されている場合、任意のリソースの変更イベントが、通知をトリガーします。 [リソース範囲][アプリケーショングループ] に設定されている場合、指定されたグループ内のリソースの変更イベントのみが通知をトリガーします。
    6. [アラームタイプ] エリアで [MNS queue] を選択し、[リージョン][queue] (たとえば、ecs-cms-event) を指定します。
    7. [OK] をクリックします。
  • Python の依存関係をインストールします。

    次のコードは Python 3.6 でテストされています。 必要に応じて、Java などの他のプログラミング言語を使用できます。

    PyPi を使用して、以下の Python 依存関係をインストールします。

    • aliyun-python-sdk-core-v3 of 2.12.1 以降
    • aliyun-python-sdk-ecs of 4.16.0 以降
    • aliyun-mns of 1.1.5 以降

手順

CloudMonitor は、ECS インスタンスのすべてのステータス変更イベントを MNS に送信します。 その後、MNS から通知を取得し、コードを実行してそれらを処理できます。 次の演習セクションでは、上に述べた方法のチュートリアルを説明します。

演習1:すべての ECS 作成およびリリースイベントの記録

現在、リリースされたインスタンスを ECS コンソールで照会することはできません。 これらの照会を実行する必要がある場合は、すべての ECS インスタンスのライフサイクルを独自のデータベースに記録するか、ECS ステータス変更イベントを使用して、ログに記録する必要があります。 具体的には、ECS インスタンスが作成されるたびに Pending イベントが送信され、ECS インスタンスがリリースされるたびに Deleted イベントが送信されます。 次の手順を実行して、これら 2 つのイベントを記録できます。

  1. conf ファイルを作成します。ファイルには、MNS エンドポイント、Alibaba Cloud アカウントの AccessKeyId と AccessKeySecret、リージョン ID (たとえば、cn-beijing)、および MNS キュー名が含まれている必要があります。
    MNS エンドポイントを表示するには、MNS コンソールにログインし、[キューリスト] ページで、[エンドポイントの取得] をクリックします。
    class Conf:
        endpoint = 'http://<id>.mns.<region>.aliyuncs.com/'
        access_key = '<access_key>'
        access_key_secret = '<access_key_secrect>'
        region_id = 'cn-beijing'
        queue_name = 'test'
        vsever_group_id = '<your_vserver_group_id>'
    
  2. 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 メッセージを取得し、リスナー消費メッセージが呼び出された後にメッセージを削除するためにのみ使用されます。

  3. 指定されたイベントを使用するようにリスナーを登録します。 このリスナーは、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()

    実際のシナリオでは、イベントをデータベースに格納するか SLS を使用して、後ほど行われる検索および監査タスクを容易にすることができます。

演習 2:ECS サーバーの自動再起動

シナリオによっては、ECS サーバーが突然シャットダウンすることがあります。 この場合、サーバーの自動再起動を設定する必要があります。

演習 1 で MNS クライアントを使用して、新しいリスナーを作成します。 リスナーが 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 イベント通知を受け取ります。 この場合、タイマーとカウンターを使用した、より詳細な O&M のためのコマンド実行時に、継続して手順を実行できます。

演習 3:リリースされる前の SLB からのプリエンプティブルインスタンスの自動削除

プリエンプティブルインスタンスがリリースされる 5 分前に、リリースアラームイベントが送信されます。 この 5 分間に、サービスを中断することなくいくつかのプロセスを実行できます。 たとえば、バックエンド SLB サーバーからターゲットのプリエンプティブルインスタンスを手動で削除できます。

演習 1 で MNS クライアントを使用して、新しいリスナーを作成します。 リスナーはプリエンプティブルインスタンスのリリースアラームを受信すると、SLB SDK を呼び出します。

# -*- 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)
重要

プリエンプティブルインスタンスのリリースアラームのイベント名は、 "Instance:PreemptibleInstanceInterruption"、mns_client.regist_listener(ListenerSLB(Conf.vsever_group_id)、'Instance:PreemptibleInstanceInterruption') です。

実際のシナリオでは、サービスを正常に実行できるようにするために、新しいプリエンプティブルインスタンスを申請して、SLB にアタッチする必要があります。