スポットインスタンスは、市場価格の変動やリソース不足により強制的に再利用される場合があります。ビジネスへの影響を最小限に抑えるため、中断イベントを早期に検出し、インスタンスが再利用される前に適切に対応してください。
検出方法は以下の 3 種類があります:
| メソッド | 仕組み | 推奨ユースケース |
|---|---|---|
| インスタンスメタデータ | インスタンス内部からメタデータエンドポイントをクエリし、正確な終了時刻を取得します。 | 外部 API 呼び出しを伴わない、軽量かつプロセス内での検出。 |
| ECS SDK | DescribeInstances API をポーリングして、インスタンスが「再利用予定」状態であるかを確認します。 | 既にインスタンスステータスをポーリングしているアプリケーション。 |
| CloudMonitor | メール、SMS、Simple Message Queue (SMQ)、Simple Log Service、Function Compute、または Webhook を通じてリアルタイムでプッシュ通知を受信できるように、Instance:PreemptibleInstanceInterruption システムイベントをサブスクライブします。 | ポーリング オーバーヘッドがゼロであるイベント駆動型アーキテクチャ。 |
スポットインスタンスの中断イベントを検出する
インスタンスメタデータを使用する
スポットインスタンス内部から instance/spot/termination-time メタデータ項目をクエリすることで、予定されている終了時刻を取得できます。この方法では外部 API 呼び出しは不要であり、インスタンス内部のみで完結します。
| 応答 | 意味 |
|---|---|
| HTTP 404 | インスタンスは利用可能です — 回収はスケジュールされていません。 |
UTC タイムスタンプ(例:2015-01-05T18:02:00Z) | インスタンスはこの時刻に回収されます。 |
インスタンスメタデータの詳細については、「インスタンスメタデータ」をご参照ください。
Linux
# 認証用のメタデータトークンを取得します。
TOKEN=`curl -X PUT "http://100.100.100.200/latest/api/token" \
-H "X-aliyun-ecs-metadata-token-ttl-seconds:<メタデータサーバーへのアクセス認証情報の有効期間>"`
# スポットインスタンスの終了時刻をクエリします。
curl -H "X-aliyun-ecs-metadata-token: $TOKEN" \
http://100.100.100.200/latest/meta-data/instance/spot/termination-timeWindows
# 認証用のメタデータトークンを取得します。
$token = Invoke-RestMethod `
-Headers @{"X-aliyun-ecs-metadata-token-ttl-seconds" = "<メタデータサーバーへのアクセス認証情報の有効期間>"} `
-Method PUT `
-Uri http://100.100.100.200/latest/api/token
# スポットインスタンスの終了時刻をクエリします。
Invoke-RestMethod `
-Headers @{"X-aliyun-ecs-metadata-token" = $token} `
-Method GET `
-Uri http://100.100.100.200/latest/meta-data/instance/spot/termination-timeECS SDK を使用する
DescribeInstances API を呼び出してインスタンス情報を取得し、応答内の OperationLocks 配列を確認します:
OperationLocksが空 — インスタンスは利用可能です。OperationLocksにLockReasonが含まれており、その値がRecyclingである — インスタンスは「再利用予定」状態であり、まもなく再利用されます。
前提条件
開始する前に、以下の条件を満たしていることを確認してください。
AccessKey ペアを持つ Resource Access Management (RAM) ユーザー。手順については、「AccessKey ペアの作成」をご参照ください。
RAM ユーザーに
AliyunECSFullAccessポリシーがアタッチされていること。AccessKey ペアが環境変数
ALIBABA_CLOUD_ACCESS_KEY_IDおよびALIBABA_CLOUD_ACCESS_KEY_SECRETに設定されていること。手順については、「Linux、macOS、Windows における環境変数の設定」をご参照ください。Java 向け ECS SDK がインストール済みであること。以下の Maven 依存関係を追加してください。
<dependencies> <dependency> <groupId>com.aliyun</groupId> <artifactId>ecs20140526</artifactId> <version>5.3.0</version> </dependency> </dependencies>その他のインストール方法については、「Java 向け ECS SDK のインストール」をご参照ください。
サンプル
以下のサンプルでは、スポットインスタンスのリストを 2 分ごとにポーリングし、「再利用予定」状態になったインスタンスをログ出力します。すべてのサンプルは環境変数から認証情報を読み取ります。
import com.aliyun.ecs20140526.Client;
import com.aliyun.ecs20140526.models.DescribeInstancesRequest;
import com.aliyun.ecs20140526.models.DescribeInstancesResponse;
import com.aliyun.ecs20140526.models.DescribeInstancesResponseBody;
import com.aliyun.tea.TeaException;
import com.aliyun.tea.TeaUnretryableException;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;
import com.alibaba.fastjson.JSONArray;
import java.util.Arrays;
public class Sample {
// 環境変数から認証情報を使用してクライアントを初期化します。
private static Client createClient() throws Exception {
Config config = new Config()
// 必須:ALIBABA_CLOUD_ACCESS_KEY_ID 環境変数が設定されていることを確認してください。
.setAccessKeyId(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"))
// 必須:ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数が設定されていることを確認してください。
.setAccessKeySecret(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"))
// 対象リージョンのエンドポイントを指定します。
.setEndpoint("ecs.cn-hangzhou.aliyuncs.com");
return new Client(config);
}
public static void main(String[] args) {
try {
Client client = Sample.createClient();
// 監視対象のスポットインスタンス ID を指定します。
JSONArray instanceIds = new JSONArray();
instanceIds.addAll(Arrays.asList("i-bp145cvd0exyqj****", "i-bp1gehfgfrrk4lah****"));
DescribeInstancesRequest request = new DescribeInstancesRequest()
.setRegionId("cn-hangzhou")
.setInstanceIds(instanceIds.toJSONString());
RuntimeOptions runtime = new RuntimeOptions();
while (!instanceIds.isEmpty()) {
// DescribeInstances を呼び出してインスタンスのロック状態を確認します。
DescribeInstancesResponse response = client.describeInstancesWithOptions(request, runtime);
DescribeInstancesResponseBody responseBody = response.getBody();
DescribeInstancesResponseBody.DescribeInstancesResponseBodyInstances instanceList = responseBody.getInstances();
if (instanceList != null && instanceList.getInstance() != null && !instanceList.getInstance().isEmpty()) {
for (DescribeInstancesResponseBody.DescribeInstancesResponseBodyInstancesInstance instance : instanceList.getInstance()) {
System.out.println("インスタンス: " + instance.getInstanceId() + ", ゾーン: " + instance.getZoneId());
if (instance.getOperationLocks() != null) {
DescribeInstancesResponseBody.DescribeInstancesResponseBodyInstancesInstanceOperationLocks operationLocks = instance.getOperationLocks();
if (operationLocks.getLockReason() != null && !operationLocks.getLockReason().isEmpty()) {
for (DescribeInstancesResponseBody.DescribeInstancesResponseBodyInstancesInstanceOperationLocksLockReason lockReason : operationLocks.getLockReason()) {
System.out.println("インスタンス " + instance.getInstanceId() + " のロック理由: " + lockReason.getLockReason() + ", ステータス: " + instance.getStatus());
if ("Recycling".equals(lockReason.getLockReason())) {
// インスタンスは「再利用予定」状態です — グレースフルシャットダウン処理をここに実装します。
System.out.println("スポットインスタンスが再利用されます: " + instance.getInstanceId());
instanceIds.remove(instance.getInstanceId());
}
}
}
}
}
// 2 分後に再度ポーリングします。
System.out.println("2 分後に再度確認します...");
Thread.sleep(2 * 60 * 1000);
} else {
break;
}
}
} catch (TeaUnretryableException ue) {
// ネットワークエラー — 最大リトライ回数に達しました。
ue.printStackTrace();
System.out.println(ue.getMessage());
System.out.println(ue.getLastRequest());
} catch (TeaException e) {
// 業務エラー — エラーコードとメッセージを確認してください。
e.printStackTrace();
System.out.println(e.getCode());
System.out.println(e.getMessage());
System.out.println(e.getData());
} catch (Exception e) {
e.printStackTrace();
}
}
}再利用がトリガーされた際の出力例は以下の通りです。
インスタンス: i-bp1i9c3qiv1qs6nc****, ゾーン: cn-hangzhou-i
インスタンス i-bp1i9c3qiv1qs6nc**** のロック理由: Recycling, ステータス: Stopped
スポットインスタンスが再利用されます: i-bp1i9c3qiv1qs6nc****SDK の例外タイプ(ネットワークエラーの場合は TeaUnretryableException、業務エラーの場合は TeaException)および高度なランタイムオプションの詳細については、「アクセス認証情報の管理」をご参照ください。
CloudMonitor を使用する
ECS インスタンスのイベントは CloudMonitor に同期されます。DescribeSystemEventAttribute API を呼び出して Instance:PreemptibleInstanceInterruption イベントをクエリします。content パラメーター内の action フィールドの値が delete の場合、インスタンスが強制的に再利用される直前であることを示します。
前提条件
開始する前に、以下の条件を満たしていることを確認してください。
AccessKey ペアを持つ RAM ユーザー。手順については、「AccessKey ペアの作成」をご参照ください。
RAM ユーザーに
AliyunCloudMonitorFullAccessポリシーがアタッチされていること。AccessKey ペアが環境変数
ALIBABA_CLOUD_ACCESS_KEY_IDおよびALIBABA_CLOUD_ACCESS_KEY_SECRETに設定されていること。手順については、「Linux、macOS、Windows における環境変数の設定」をご参照ください。Java 向け CloudMonitor SDK がインストール済みであること。以下の Maven 依存関係を追加してください。
<dependencies> <dependency> <groupId>com.aliyun</groupId> <artifactId>cms20190101</artifactId> <version>8.1.3</version> </dependency> </dependencies>その他のインストール方法については、「Java 向け CloudMonitor SDK のインストール」をご参照ください。
サンプル
以下のサンプルでは、CloudMonitor から Instance:PreemptibleInstanceInterruption イベントをクエリします。
import com.aliyun.cms20190101.Client;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.cms20190101.models.DescribeSystemEventAttributeRequest;
import com.aliyun.cms20190101.models.DescribeSystemEventAttributeResponse;
import com.aliyun.tea.TeaException;
import com.aliyun.tea.TeaUnretryableException;
import com.aliyun.teautil.models.RuntimeOptions;
public class Sample {
// 環境変数から認証情報を使用して CloudMonitor クライアントを初期化します。
private static Client createClient() throws Exception {
Config config = new Config()
// 必須:ALIBABA_CLOUD_ACCESS_KEY_ID 環境変数が設定されていることを確認してください。
.setAccessKeyId(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"))
// 必須:ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数が設定されていることを確認してください。
.setAccessKeySecret(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"))
// 対象リージョンの CloudMonitor エンドポイントを指定します。
.setEndpoint("metrics.cn-hangzhou.aliyuncs.com");
return new Client(config);
}
public static void main(String[] args) {
try {
Client client = Sample.createClient();
// スポットインスタンスの中断イベントをクエリします。
DescribeSystemEventAttributeRequest request = new DescribeSystemEventAttributeRequest()
.setProduct("ECS")
.setEventType("StatusNotification")
.setName("Instance:PreemptibleInstanceInterruption");
RuntimeOptions runtime = new RuntimeOptions();
DescribeSystemEventAttributeResponse response = client.describeSystemEventAttributeWithOptions(request, runtime);
System.out.println(response.body.toMap());
} catch (TeaUnretryableException ue) {
// ネットワークエラー — 最大リトライ回数に達しました。
ue.printStackTrace();
System.out.println(ue.getMessage());
System.out.println(ue.getLastRequest());
} catch (TeaException e) {
// 業務エラー — エラーコードとメッセージを確認してください。
e.printStackTrace();
System.out.println(e.getCode());
System.out.println(e.getMessage());
System.out.println(e.getData());
} catch (Exception e) {
e.printStackTrace();
}
}
}応答フォーマット
スポットインスタンスの中断イベントの例は以下の通りです。
{
"ver": "1.0",
"id": "2256A988-0B26-4E2B-820A-8A********E5",
"product": "ECS",
"resourceId": "acs:ecs:cn-hangzhou:169070********30:instance/i-bp1ecr********5go2go",
"level": "INFO",
"name": "Instance:PreemptibleInstanceInterruption",
"userId": "169070********30",
"eventTime": "20190409T121826.922+0800",
"regionId": "cn-hangzhou",
"content": {
"instanceId": "i-bp1ecr********5go2go",
"action": "delete"
}
}中断処理に関連する content フィールドの説明は以下の通りです。
| フィールド | 説明 | 例 |
|---|---|---|
instanceId | スポットインスタンスの ID | i-bp1ecr********5go2go |
action | インスタンスに対する操作。「delete」は、インスタンスが中断され、強制的に再利用されることを意味します。 | delete |
その他の応答フィールドの説明については、「DescribeSystemEventAttribute」をご参照ください。
CloudMonitor システムイベントをサブスクライブする
ポーリングではなく、CloudMonitor で Instance:PreemptibleInstanceInterruption イベントをサブスクライブすることで、リアルタイムのプッシュ通知を受信できます。サポートされるプッシュチャネルには、Simple Message Queue(旧称:MNS)(SMQ)、Simple Log Service、Function Compute、Webhook があります。また、ショートメッセージまたはメールによる通知も可能です。
ステップ 1:プッシュチャネルを作成する
以下のプッシュチャネルのいずれかを選択してください。
SMQ
SMQ コンソール にログインします。
左側ナビゲーションウィンドウで、キュー モデル > キュー を選択します。
上部ナビゲーションバーで、リージョンを選択します。
キュー ページで、キューの作成 をクリックします。
キューの作成 パネルで、以下のパラメーターを設定し、OK をクリックします。
名前:キューの名前。
最大メッセージ長:キューに送信されるメッセージの最大長。
ロングポーリング期間:
ReceiveMessage呼び出し後のロングポーリング要求の最大待機時間。可視性タイムアウト期間:受信したメッセージが非アクティブ状態で保持される時間。
メッセージ保持期間:メッセージがキュー内で削除されるまでの最大持続時間。
メッセージ遅延期間:キュー内のメッセージが利用可能になるまでの遅延時間。
ログ機能の有効化:ログ機能を有効にするかどうか。
キューが作成されると、キュー ページに表示されます。
Webhook
Webhook サービスをインターネットに接続可能なサーバーにデプロイしてください。サーバーのセキュリティグループおよびファイアウォールで Webhook ポートが開放されていることを確認してください。
Java 実装のサンプル:
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class WebhookController {
@PostMapping("/callback")
public ResponseEntity<String> receiveWebhook(@RequestBody String payload) {
// ペイロードを処理します — ログ出力またはビジネスロジックの実行。
System.out.println("受信した Webhook ペイロード: " + payload);
return ResponseEntity.ok("Webhook 受信完了");
}
}ステップ 2:サブスクリプションポリシーを作成する
CloudMonitor コンソール にログインします。
左側ナビゲーションウィンドウで、イベントセンター > イベントサブスクリプション を選択します。
サブスクリプションポリシー タブで、サブスクリプションポリシーの作成 をクリックします。
サブスクリプションポリシーの作成 ページで、以下のパラメーターを設定します。ここに記載されていないパラメーターについては、「イベントサブスクリプションの管理(推奨)」をご参照ください。
基本情報:サブスクリプションポリシーの名前および説明を入力します。
アラームサブスクリプション:
サブスクリプションタイプ: システムイベント を選択します。
プロダクト: Elastic Compute Service (ECS) を選択します。
イベントタイプ: ステータス通知 を選択します。
イベント名: Instance:PreemptibleInstanceInterruption を選択します。
イベントレベル: 警告(Warning) を選択します。
アプリケーショングループ、イベント内容、および イベントリソース:空白のままにすると、現在のアカウント内のすべてのアプリケーショングループに属するすべての ECS インスタンスに対して
Instance:PreemptibleInstanceInterruptionイベントがサブスクライブされます。
統合ノイズ低減:デフォルト設定を使用します。
通知: カスタム通知方法 のデフォルト設定を使用します。
プッシュおよび統合: チャネルの追加 をクリックし、次に チャネルの増加 をクリックします。プッシュチャネルの作成 パネルで、ターゲットタイプ をステップ 1 で作成したチャネルに設定し、画面上の指示に従って設定を完了します。詳細については、「プッシュチャネルの管理」をご参照ください。
ステップ 3:サブスクリプションをテストする
イベントサブスクリプションのデバッグ 機能を使用して、実際の中断イベントを待たずにシミュレーションを行います。
サブスクリプションポリシー タブで、イベントサブスクリプションのデバッグ をクリックします。
イベントデバッグの作成 パネルで、プロダクト を Elastic Compute Service (ECS) に、名前 を Instance:PreemptibleInstanceInterruption に設定します。
CloudMonitor がサンプル JSON デバッグコンテンツを生成します。プレースホルダーの値を実際のリソース情報に置き換えてください。
<Alibaba Cloud アカウント ID>を Alibaba Cloud アカウント ID に置き換えます。<resource-id>および<instanceId>をスポットインスタンス ID に置き換えます。<Region ID>をスポットインスタンスのリージョン ID に置き換えます。
{
"product": "ECS",
"resourceId": "acs:ecs:cn-shanghai:<Alibaba Cloud アカウント ID>UID:instance/<resource-id>",
"level": "WARN",
"instanceName": "instanceName",
"regionId": "<Region ID>",
"groupId": "0",
"name": "Instance:PreemptibleInstanceInterruption",
"content": {
"instanceId": "i-2zeg014dfo4y892z***",
"instanceName": "wor***b73",
"action": "delete"
},
"status": "Normal"
}OK をクリックします。
CloudMonitor が、サブスクリプションポリシーで設定した方法でテスト通知を送信します。
ステップ 4:通知を確認する
SMQ
SMQ コンソール にログインします。左側ナビゲーションウィンドウで、キュー モデル > キュー を選択します。
上部ナビゲーションバーでリージョンを選択します。キュー ページで、対象のキューを見つけ、操作 列の メッセージの送信 をクリックします。
(任意)メッセージの受信 セクションで、メッセージ受信パラメーターの編集 をクリックして、受信回数 および ポーリング期間 を設定し、OK をクリックします。
メッセージの受信 をクリックします。メッセージが メッセージの受信 セクションに表示されます。
(任意)メッセージ横の 詳細 をクリックして、メッセージの内容を確認します。
Webhook
Webhook サービスの呼び出しログを確認して、プッシュされた通知を確認します。
スポットインスタンスの中断イベントに対応する
中断イベントを検出したら、ワークロードに応じて以下のいずれか、または複数の対応策を実施してください。
新しい処理の受付を停止する
インスタンスが再利用される前に、新しいリクエストやタスクがインスタンスにルーティングされないようにします。オンラインサービスの場合は、ロードバランサーからインスタンスを登録解除します。バッチジョブの場合は、タスクスケジューラが新しい処理をインスタンスに割り当てないように停止します。
進行状況の保存と状態のフラッシュ
インスタンスが再利用される前に、進行中のタスクの状態および中間結果を永続ストレージ(ローカルファイルまたはデータベースなど)に保存します。チェックポイントを活用することで、代替インスタンスが最後に保存されたポイントから再開でき、最初からやり直す必要がなくなります。スポットインスタンスのデータを保持および復元する手順については、「データの保持および復元」をご参照ください。
クリーンなシャットダウンを実行する
保留中の書き込みをフラッシュし、ロックや接続を解放して、プロセスをクリーンに終了します。これにより、データ破損を防止し、依存するサービスが待ち続ける状態になることを防ぎます。
ハンドラのエンドツーエンド検証を行う
CloudMonitor の イベントサブスクリプションのデバッグ 機能を使用して中断イベントをシミュレートし、実際の再利用が発生する前にアプリケーションが期待通りに動作することを確認します。完全な手順については、「SMQ を使用したスポットインスタンスの中断イベントの検出および対応」をご参照ください。
次のステップ
データの保持および復元 — スポットインスタンスの再利用にわたってデータを保持する方法について学習します。
SMQ を使用したスポットインスタンスの中断イベントの検出および対応 — SMQ をプッシュチャネルとして使用したエンドツーエンドのチュートリアルです。