すべてのプロダクト
Search
ドキュメントセンター

Elastic Compute Service:Simple Message Queue を使用したスポットインスタンスの中断イベントの検出と対応

最終更新日:Apr 02, 2026

スポットインスタンスはいつでも回収される可能性があります。回収が差し迫ると、CloudMonitor は Simple Message Queue (SMQ) に通知を送信します。これにより、アプリケーションは状態を保存し、クリーンアップするための時間を確保できます。このガイドでは、SMQ キューの作成、ECS 中断イベントのサブスクライブ、イベントのシミュレーション、通知をプルして処理するハンドラの作成まで、一連の設定手順を説明します。

仕組み

image

CloudMonitor がスポットインスタンスの中断イベントを検出すると、サブスクリプションポリシーで定義された SMQ キューに通知をプッシュします。アプリケーションはキューをポーリングし、Base64 でエンコードされたメッセージ本文をデコードし、JSON ペイロードを解析して、影響を受けるインスタンスを特定します。イベントを処理した後、再処理を防ぐためにキューからメッセージを削除します。

前提条件

開始する前に、以下が準備できていることを確認してください。

  • プログラムによるアクセス用に設定された RAM ユーザーを持つ Alibaba Cloud アカウント。Alibaba Cloud アカウント (root ユーザー) はリソースに対するすべての権限を持っています。Alibaba Cloud アカウントの AccessKey ペアが漏洩すると、リソースがリスクにさらされます。RAM ユーザーの AccessKey ペアを使用することを推奨します。詳細については、「AccessKey ペアの作成」をご参照ください。

  • RAM ユーザーにアタッチされた AliyunMNSFullAccess ポリシー

  • RAM ユーザーの AccessKey 認証情報が環境変数として保存されていること (「Linux、macOS、Windows で環境変数を設定する」をご参照ください)

  • SMQ エンドポイントが設定されていること (「エンドポイントの設定」をご参照ください)

  • Java と Maven がインストールされていること

SMQ SDK の依存関係の追加

次の内容を pom.xml に追加します。

<dependencies>
    <!-- Alibaba Cloud SMQ SDK -->
    <dependency>
        <groupId>com.aliyun.mns</groupId>
        <artifactId>aliyun-sdk-mns</artifactId>
        <version>1.2.0</version>
    </dependency>
</dependencies>

その他のインストール方法については、「SMQ SDK for Java のインストール」をご参照ください。

SMQ キューの作成

CloudMonitor からスポットインスタンスの中断通知を受信するためのキューを作成します。

  1. SMQ コンソールにログインします。左側のナビゲーションウィンドウで、[キューモデル] > [キュー] を選択します。

  2. 上部ナビゲーションバーで、リージョンを選択します。[キュー] ページで、[キューの作成] をクリックします。

  3. [キューの作成] パネルで、必須パラメーターを設定し、[OK] をクリックします。

image

サブスクリプションポリシーの作成

サブスクリプションポリシーは、イベント通知をプッシュする場所を CloudMonitor に指示します。作成したキューにスポットインスタンスの中断イベントを転送するように設定します。

  1. CloudMonitor コンソールにログインします。左側のナビゲーションウィンドウで、[イベントセンター] > [イベントサブスクリプション] を選択します。

  2. [サブスクリプションポリシー] タブで、[サブスクリプションポリシーの作成] をクリックします。

  3. [サブスクリプションタイプ][システムイベント] に設定します。

    image

  4. 以下に示すように [サブスクリプション範囲] を設定します。

    image

  5. [プッシュと統合] で、[チャンネルの追加] をクリックし、次に [チャンネルの追加] をクリックします。作成した SMQ キューを選択し、画面の指示に従ってプッシュチャンネルの設定を完了します。プッシュチャンネルのオプションの詳細については、「プッシュチャンネルの管理」をご参照ください。サブスクリプションパラメーターの完全なリストについては、「イベントサブスクリプションの管理」をご参照ください。

中断イベントのシミュレーション

スポットインスタンスの中断は予測不可能なため、CloudMonitor は開発中にそれらをシミュレートするための[デバッグイベントサブスクリプション] ツールを提供しています。

  1. [サブスクリプションポリシー] タブで、[イベントサブスクリプションのデバッグ] をクリックします。

  2. [イベントデバッグの作成] パネルで、[プロダクト][Elastic Compute Service (ECS)] に、[名前]Instance:PreemptibleInstanceInterruption に設定します。システムは自動的に JSON ペイロードを生成します。プレースホルダー値を実際のインスタンス情報に更新してください。

    • <Alibaba Cloud アカウント ID> をご利用の Alibaba Cloud アカウント ID に置き換えます。

    • <resource-id><instanceId> をスポットインスタンスの ID に置き換えます。

    • <Region ID> をスポットインスタンスのリージョン ID に置き換えます。

    フィールド説明
    nameイベントタイプ。このフィールドをチェックして、メッセージが中断イベントであることを確認します。
    content.instanceId回収されるスポットインスタンスの ID。
    content.action実行されるアクション。delete は、インスタンスが停止され、回収されることを意味します。
    levelイベントの重要度レベル。たとえば、WARN は中断イベントを示します。
    {
        "product": "ECS",
        "resourceId": "acs:ecs:cn-shanghai:<Alibaba Cloud アカウント ID>UID:instance/<resource-id>",
        "level": "WARN",
        "instanceName": "instanceName",
        "regionId": "<リージョン ID>",
        "groupId": "0",
        "name": "Instance:PreemptibleInstanceInterruption",
        "content": {
            "instanceId": "<instanceId>",
            "instanceName": "wor***b73",
            "action": "delete"
        },
        "status": "Normal"
    }

    イベントペイロードの主要なフィールド:

  3. [OK] をクリックします。CloudMonitor はシミュレーションされたアラートを SMQ キューに送信します。

メッセージのプルと応答

次の Java の例は、SMQ から中断通知をプルして応答する方法を示しています。実際のワークロードの代わりとしてグレースケール画像変換タスクを使用していますが、同じパターンが任意の中断可能なジョブに適用されます。

完全なサンプルコード

import com.aliyun.mns.client.CloudAccount;
import com.aliyun.mns.client.CloudQueue;
import com.aliyun.mns.client.MNSClient;
import com.aliyun.mns.common.utils.ServiceSettings;
import com.aliyun.mns.model.Message;
import org.json.JSONObject;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Base64;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * スポットインスタンスの回収時にグレースフルシャットダウンをサポートする中断可能な画像プロセッサ。
 *
 * 主な動作:
 * - 50 行処理するごとに進捗を保存 (チェックポイントメカニズム)
 * - SMQ から中断イベントを受信すると即座に応答
 * - 中断時に部分的な結果を partial_output.jpg に書き込む
 */
public class InterruptibleImageProcessor implements Runnable {

    // スレッドセーフなステータス制御のための AtomicBoolean
    private final AtomicBoolean running = new AtomicBoolean(true);
    // 処理中の画像を保持
    private BufferedImage processedImage;
    // 処理の進捗 (0-100)
    private int progress;

    /**
     * ITU-R BT.601 の加重平均を使用して画像をグレースケールに変換:
     * グレー = 0.30*R + 0.59*G + 0.11*B
     *
     * 中断時のデータ損失を最小限に抑えるため、50 行ごとに進捗を保存します。
     */
    public void convertToGrayScale(File inputFile, File outputFile) throws Exception {
        BufferedImage original = ImageIO.read(inputFile);
        int width = original.getWidth();
        int height = original.getHeight();
        processedImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);

        for (int y = 0; y < height && running.get(); y++) {
            for (int x = 0; x < width; x++) {
                // 各ピクセルでスレッドの中断フラグを確認
                if (Thread.interrupted()) {
                    throw new InterruptedException("Image processing interrupted");
                }
                // ARGB チャンネルを分解
                int rgb = original.getRGB(x, y);
                int r = (rgb >> 16) & 0xFF;  // 赤チャンネル
                int g = (rgb >> 8) & 0xFF;   // 緑チャンネル
                int b = rgb & 0xFF;           // 青チャンネル
                // 加重平均を適用
                int gray = (int)(0.3 * r + 0.59 * g + 0.11 * b);
                // グレースケール値から RGB を再構築
                processedImage.setRGB(x, y, (gray << 16) | (gray << 8) | gray);
                // 進捗率を更新 (注意: 整数除算)
                progress = (y * width + x) * 100 / (width * height);
            }
            // 50 行ごとにチェックポイントを保存
            if (y % 50 == 0) {
                saveProgress(outputFile);
            }
        }
        // 最終結果を保存
        ImageIO.write(processedImage, "jpg", outputFile);
    }

    /**
     * 現在の処理状態を一時ファイルに保存します。
     * 保存プロセス自体が中断されるのを避けるため、サイレントフェイルアプローチを使用します。
     */
    private void saveProgress(File outputFile) {
        try {
            // 最終的な出力を上書きしないように一時ファイルに書き込む
            ImageIO.write(processedImage, "jpg", new File("partial_output.jpg"));
        } catch (Exception e) {
            System.err.println("Auto-save failed: " + e.getMessage());
        }
    }

    /**
     * スレッドのエントリポイント。
     * 中断時: 現在の進捗を保存し、スレッドを再中断して
     * 呼び出し元のための中断セマンティクスを維持します。
     */
    @Override
    public void run() {
        try {
            convertToGrayScale(new File("input.jpg"), new File("output.jpg"));
            Thread.sleep(5000); // 長時間実行されるタスクをシミュレート
            System.out.println("Image processing is complete");
        } catch (InterruptedException e) {
            System.out.println("Processing interrupted at " + progress + "%");
            saveProgress(new File("partial_output.jpg"));
            Thread.currentThread().interrupt(); // 中断ステータスを復元
        } catch (Exception e) {
            System.err.println("Handling error: " + e.getMessage());
        }
    }

    /**
     * 現在の行が完了した後にプロセッサに停止を通知します。
     */
    public void stop() {
        running.set(false);
    }

    /**
     * main メソッド: 画像プロセッサを開始し、SMQ で中断イベントをポーリングし、
     * イベントを受信したときにプロセッサをグレースフルシャットダウンします。
     */
    public static void main(String[] args) throws InterruptedException {
        // 環境変数から認証情報を使用して SMQ クライアントを初期化
        CloudAccount account = new CloudAccount(
                ServiceSettings.getMNSAccessKeyId(),
                ServiceSettings.getMNSAccessKeySecret(),
                ServiceSettings.getMNSAccountEndpoint());
        MNSClient client = account.getMNSClient();

        // バックグラウンドスレッドで画像処理タスクを開始
        InterruptibleImageProcessor processor = new InterruptibleImageProcessor();
        Thread processThread = new Thread(processor);
        processThread.start();

        try {
            // SMQ キューで中断通知をポーリング
            CloudQueue queue = client.getQueueRef("spot-interruption");
            Message popMsg = queue.popMessage();

            if (popMsg != null) {
                // SMQ からのメッセージ本文はデフォルトで Base64 エンコードされています
                System.out.println("Raw message body: " + popMsg.getMessageBodyAsRawString());

                // JSON ペイロードをデコードして解析
                byte[] decodedBytes = Base64.getDecoder().decode(popMsg.getMessageBodyAsRawString());
                String decodedString = new String(decodedBytes);
                System.out.println("Decoded message: " + decodedString);

                JSONObject json = new JSONObject(decodedString);
                String name = json.getString("name");

                // これがスポットインスタンスの中断イベントであるかどうかを確認
                if ("Instance:PreemptibleInstanceInterruption".equals(name)) {
                    System.out.println("Spot instance is about to be reclaimed. Shutting down...");

                    // プロセッサに停止を通知し、終了するのを待機
                    processor.stop();
                    processThread.interrupt();
                    processThread.join();
                    System.out.println("Shutdown complete");

                    // 再処理を防ぐためにメッセージを削除
                    queue.deleteMessage(popMsg.getReceiptHandle());
                }
            }
        } catch (Exception e) {
            System.out.println("Unexpected error: ");
            e.printStackTrace();
        }

        client.close();
    }
}
この例では、中断処理パターンを実証するためにグレースケール画像変換タスクを使用しています。画像処理ロジックを独自のワークロードに置き換えてください。チェックポイントの保存、アトミックな停止フラグ、処理後の SMQ メッセージの削除といったコアパターンは、スポットインスタンス上のあらゆる長時間実行ジョブに適用できます。

次のステップ

  • インスタンスが回収される前にディスクスナップショットを作成するには、「CreateSnapshot」をご参照ください。

  • 回収前にインスタンスからカスタムイメージを作成するには、「CreateImage」をご参照ください。

  • スポットインスタンスに保存されているデータを保持および回復する方法については、「データの保持と復元」をご参照ください。

参考資料