Elastic Compute Service (ECS) は、インスタンスが状態間で遷移するたびに、Cloud Monitor を通じて状態変更イベントを発行します。遷移のトリガーには、コンソール操作、API コール、SDK 操作、Auto Scaling ポリシー、料金滞納、システム例外などがあります。これらのイベントを Simple Message Queue (旧称:MNS) にルーティングすることで、インスタンスライフサイクルのログ記録、停止したインスタンスの再起動、またはスポットインスタンスをリリース前にロードバランサーから切り離すなどの自動レスポンスを構築できます。
このトピックでは、Python と MNS メッセージキューをイベント配信メカニズムとして使用する 3 つの自動化シナリオを紹介します。
仕組み
Cloud Monitor はすべての ECS インスタンスの状態変更イベントをキャプチャし、MNS のキューに配信します。Python コンシューマーがキューをポーリングし、各イベントを解析して、イベント名 (例:Instance:StateChange、Instance:PreemptibleInstanceInterruption) に基づいて登録済みのリスナーにディスパッチします。各リスナーは、特定の自動化アクションを実装します。
Cloud Monitor は、イベントアラートを処理するために 4 つの方法をサポートしています:
方法 | ユースケース |
MNS | カスタムのコンシューマーロジックによる非同期処理 (このトピックで使用) |
Function Compute (FC) | インフラストラクチャを管理せずにサーバーレスでイベントを処理 |
URL コールバック | イベントを外部の HTTP エンドポイントに転送 |
Simple Log Service (SLS) | イベントログの一元管理と分析 |
前提条件
開始する前に、次を用意してください:
シンプルメッセージキュー (旧 MNS) コンソールで作成されたキュー (例:
ecs-cms-event)Cloud Monitor コンソール (旧バージョン) で設定されたシステムイベントトリガーのアラートルール
Python 3.7 以降がインストールされていること
MNS SDK for Python と ECS SDK for Python がインストール済み
ALIBABA_CLOUD_ACCESS_KEY_IDとALIBABA_CLOUD_ACCESS_KEY_SECRETの環境変数が設定されていること
SDK のインストールの詳細については、「Python SDK のインストール」をご参照ください。その他のプログラミング言語については、「MNS SDK リファレンス」および「ECS SDK の概要」をご参照ください。
手順 1:設定ファイルの作成
MNS エンドポイント、認証情報、リージョン、キュー名を共通の設定ファイルに定義します。3 つのシナリオすべてでこの設定を再利用します。
import os
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 = 'ecs-cms-event'
vserver_group_id = '<your-vserver-group-id>'次のプレースホルダーを実際の値に置き換えてください:
プレースホルダー | 説明 | 例 |
| Alibaba Cloud アカウント ID |
|
| MNS キューのリージョン |
|
| Server Load Balancer (SLB) の vServer グループ ID。シナリオ 3 でのみ必要です。 |
|
MNS エンドポイントは、[キューの詳細] ページの [エンドポイント] セクションにあります。[インターネットアクセス] と [内部アクセス] の両方のエンドポイントが利用可能です。
AccessKey 認証情報をソースコードにハードコードしないでください。上記の例では、環境変数から認証情報を読み取ります。本番ワークロードでは、代わりに Security Token Service (STS) の一時的な認証情報を使用してください。
手順 2:MNS コンシューマーの作成
キューをポーリングし、登録済みのリスナーにイベントをディスパッチし、処理済みのメッセージを削除する再利用可能な MNS クライアントを作成します:
# -*- coding: utf-8 -*-
import json
import logging
from mns.mns_exception import MNSExceptionBase
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このコンシューマーは、ロングポーリング (wait_seconds=5) を使用してメッセージを取得します。メッセージが到着すると JSON ボディを解析し、イベント名で登録済みリスナーを参照して、各リスナーの process メソッドを呼び出します。処理後、キューからメッセージを削除します。
イベントペイロードの構造
状態変更イベントは次の構造を使用します:
フィールド | 説明 | 例 |
| イベントタイプ識別子 |
|
| 現在のインスタンス状態 |
|
| ECS インスタンス ID |
|
シナリオ 1:インスタンスの作成およびリリースイベントのログ記録
Created と Deleted の状態変更をログに記録して、ECS インスタンスのライフサイクル全体を追跡します。リリースされたインスタンスは ECS コンソールに表示されなくなります。これらのイベントを記録しておくことで、容量計画とインシデント後の分析のための監査証跡として保持できます。
リスナーの作成
# -*- 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}')コンシューマーの実行
mns_client = MNSClient()
mns_client.regist_listener(ListenerLog())
mns_client.run()結果の確認
コンソールで ECS インスタンスを作成またはリリースします。
コンシューマーのログで、
The instance i-bp1abc... state is Createdのようなメッセージを確認します。
本番環境では、stdout へのログ出力ではなく、データベースまたは Simple Log Service (SLS) にイベントを保存してください。これにより、過去のインスタンスライフサイクルデータを検索および監査できます。
シナリオ 2:停止したインスタンスの自動再起動
ECS インスタンスは、システム例外または料金滞納により予期せず停止する場合があります。このリスナーは Stopped イベントを監視し、ECS の StartInstance API を呼び出して影響を受けたインスタンスを再起動します。
リスナーの作成
手順 2 の MNS コンシューマーを再利用し、新しいリスナーを登録します:
# -*- coding: utf-8 -*-
import logging
from alibabacloud_ecs20140526.client import Client as Ecs20140526Client
from alibabacloud_ecs20140526.models import StartInstanceRequest
from alibabacloud_tea_openapi.models import Config
from .config import Conf
from .mns_client import BasicListener
class ECSClient(object):
def __init__(self, client):
self.client = client
def start_instance(self, instance_id):
logging.info(f'Start instance {instance_id} ...')
request = StartInstanceRequest(
instance_id=instance_id
)
self.client.start_instance(request)
class ListenerStart(BasicListener):
def __init__(self):
ecs_config = Config(
access_key_id=Conf.access_key,
access_key_secret=Conf.access_key_secret,
endpoint=f'ecs.{Conf.region_id}.aliyuncs.com'
)
client = Ecs20140526Client(ecs_config)
self.ecs_client = ECSClient(client)
def process(self, event):
detail = event['content']
instance_id = detail['resourceId']
if detail['state'] == 'Stopped':
self.ecs_client.start_instance(instance_id)コンシューマーの実行
mns_client = MNSClient()
mns_client.regist_listener(ListenerStart())
mns_client.run()結果の確認
実行中の ECS インスタンスを手動で停止します。
コンシューマーのログで、
Start instance i-bp1abc... ...を確認します。ECS コンソールでインスタンスの状態が
Runningに戻ることを確認します。
本番環境では、起動コマンドを発行した後の後続の状態遷移 (Starting、Running、Stopped) を監視してください。再起動に失敗するケースに備えて、タイマーとリトライカウンターを使用します。
シナリオ 3:解放前にスポットインスタンスを SLB から切り離す
スポットインスタンスが解放される約 5 分前に、Cloud Monitor は Instance:PreemptibleInstanceInterruption イベントを送信します。この猶予時間を利用して、SLB がリリースを受動的に検知するのを待つのではなく、Server Load Balancer (SLB) インスタンスのバックエンドサーバーグループからインスタンスを事前に削除します。
リスナーの作成
手順 2 の MNS コンシューマーを再利用し、スポットインスタンスの割り込みイベント用のリスナーを登録します:
# -*- coding: utf-8 -*-
from alibabacloud_slb20140515.client import Client as Slb20140515Client
from alibabacloud_slb20140515.models import RemoveVServerGroupBackendServersRequest
from alibabacloud_tea_openapi.models import Config
from .config import Conf
from .mns_client import BasicListener
class SLBClient(object):
def __init__(self):
self.client = self.create_client()
def create_client(self):
config = Config()
config.access_key_id = Conf.access_key
config.access_key_secret = Conf.access_key_secret
config.endpoint = 'slb.aliyuncs.com'
return Slb20140515Client(config)
def remove_vserver_group_backend_servers(self, vserver_group_id, instance_id):
request = RemoveVServerGroupBackendServersRequest(
region_id=Conf.region_id,
vserver_group_id=vserver_group_id,
backend_servers="[{'ServerId':'" + instance_id + "','Port':'80','Weight':'100'}]"
)
response = self.client.remove_vserver_group_backend_servers(request)
return response
class ListenerSLB(BasicListener):
def __init__(self, vserver_group_id):
self.slb_caller = SLBClient()
self.vserver_group_id = Conf.vserver_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.vserver_group_id, instance_id)スポットインスタンスの割り込みイベント名は、状態変更イベントとは異なります。このリスナーは、イベント名 Instance:PreemptibleInstanceInterruption で登録してください:
mns_client = MNSClient()
mns_client.regist_listener(ListenerSLB(Conf.vserver_group_id), 'Instance:PreemptibleInstanceInterruption')
mns_client.run()スポットインスタンス割り込みイベントの構造
スポットインスタンスの割り込みイベントは、状態変更イベントとは異なるフィールド名を使用します:
フィールド | 説明 | 例 |
| イベントタイプ識別子 |
|
| ECS インスタンス ID |
|
| 割り込みアクション |
|
結果の確認
スポットインスタンスの割り込みイベントを待機します (またはテスト環境でシミュレートします)。
解放前にインスタンスが SLB の vServer グループから削除されていることを確認します。
本番環境では、サービス可用性を維持するために、代替のスポットインスタンスをリクエストし、直ちに SLB インスタンスにアタッチしてください。
すべてのリスナーの同時実行
単一の MNS コンシューマーに 3 つのリスナーをすべて登録し、複数のイベントタイプを同時に処理します:
mns_client = MNSClient()
# シナリオ1:ライフサイクルイベントのログ記録
mns_client.regist_listener(ListenerLog())
# シナリオ2:停止したインスタンスの自動再起動
mns_client.regist_listener(ListenerStart())
# シナリオ3:解放前にスポットインスタンスをSLBから削除
mns_client.regist_listener(ListenerSLB(Conf.vserver_group_id), 'Instance:PreemptibleInstanceInterruption')
mns_client.run()イベントタイプのリファレンス
イベント名 | トリガー | 主要フィールド |
| インスタンスの状態遷移 (Created、Starting、Running、Stopped、Deleted) |
|
| スポットインスタンスの解放予定 (解放の約 5 分前) |
|