本文通過實踐案例為您介紹CloudMonitor如何通過輕量訊息佇列(原 MNS)的隊列實現自動化處理ECS主機狀態變化事件。
前提條件
請您確保已在輕量訊息佇列(原 MNS)控制台,建立隊列,例如:ecs-cms-event。
關於如何建立隊列,請參見建立隊列。
請您確保已在CloudMonitor控制台,建立系統事件警示規則,有關具體操作,請參見管理系統事件警示規則(舊版)。
請您確保已安裝Python依賴。
本文所有代碼均以Python 3.6為例,您也可以使用其他程式設計語言,例如:Java和PHP。
關於如何安裝Python SDK,請參見Python SDK安裝。
背景資訊
ECS在已有的系統事件基礎上,通過CloudMonitor新發布了狀態變化類事件和搶佔型執行個體的中斷通知事件。每當ECS主機的狀態發生變化時,都會觸發一條ECS狀態變化事件。這種變化包括您通過控制台、OpenAPI或SDK操作導致的變化,也包括Auto Scaling或欠費等原因而自動觸發的變化,還包括因為系統異常而觸發的變化。
CloudMonitor提供四種事件警示處理方式,包括:輕量訊息佇列(原 MNS)、Function Compute、URL回調和Log Service。本文以輕量訊息佇列(原 MNS)為例,為您介紹CloudMonitor自動化處理ECS主機狀態變更事件的三種最佳實務。
操作步驟
CloudMonitor將ECS主機所有的狀態變化事件投遞到輕量訊息佇列(原 MNS),輕量訊息佇列(原 MNS)擷取訊息並進行訊息處理。
實踐一:對所有ECS主機的建立和釋放事件進行記錄。
目前ECS控制台無法查詢已經釋放的執行個體。如果您有查詢需求,可以通過ECS主機狀態變化事件將所有ECS主機的生命週期記錄在資料庫或Log Service中。每當您建立ECS主機時,會發送一個Pending事件,每當您釋放ECS主機時,會發送一個Deleted事件。
編輯一個Conf檔案。
Conf檔案中需要包含輕量訊息佇列(原 MNS)的
endpoint
、阿里雲的access_key
和access_key_secret
、region_id
(例如:cn-beijing)和queue_name
。說明endpoint
可以在輕量訊息佇列(原 MNS)控制台的隊列頁面,單擊擷取Endpoint。import os # 請確保代碼運行環境設定了環境變數 ALIBABA_CLOUD_ACCESS_KEY_ID 和 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 # 工程代碼泄露可能會導致 AccessKey 泄露,並威脅帳號下所有資源的安全性。以下程式碼範例使用環境變數擷取 AccessKey 的方式進行調用,僅供參考,建議使用更安全的 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>'
使用輕量訊息佇列(原 MNS)的SDK編寫一個MNS Client用來擷取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)擷取的資料,調用Listener消費訊息之後刪除訊息。
註冊一個指定Listener消費事件。這個簡單的Listener判斷收到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()
實際生產環境下,可能需要將事件儲存在資料庫或Log ServiceSLS中,方便後期的搜尋和審計。
實踐二:ECS主機關機自動重啟。
在某些情境下,ECS主機會非預期地關機,您可能需要自動重啟已經關機的ECS主機。
為了實現ECS主機關機後自動重啟,您可以複用實踐一中的MNS Client,添加一個新的Listener。當您收到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等事件,再配合計時器和計數器,進行成功或失敗之後的處理。
實踐三:搶佔型執行個體釋放前,自動從Server Load Balancer移除。
搶佔型執行個體在釋放之前五分鐘左右,會發出釋放警示事件,您可以在這短暫的時間運行業務不中斷邏輯,例如:主動從Server Load Balancer的後端伺服器中去掉這台即將被釋放的搶佔型執行個體,而非被動等待執行個體釋放後Server Load Balancer的自動處理。
您複用實踐一的MNS Client,添加一個新的Listener,當收到搶佔型執行個體的釋放警示時,調用Server Load Balancer的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)
重要搶佔型執行個體釋放警示的event name與前面不同,應該是
mns_client.regist_listener(ListenerSLB(Conf.vsever_group_id), 'Instance:PreemptibleInstanceInterruption')
。在實際生產環境下,您需要再申請一台新的搶佔型執行個體,掛載到Server Load Balancer,來保證服務能力。