全部產品
Search
文件中心

Elastic Compute Service:建立搶佔式執行個體的自訂鏡像並基於該鏡像建立執行個體以恢複被回收執行個體上的資料

更新時間:Jun 25, 2024

搶佔式執行個體可能會因為價格因素或者市場供需變化而被強制回收。如果您的搶佔式執行個體上儲存了重要資料,為避免資料丟失,您可以為該搶佔式執行個體建立自訂鏡像,並基於搶佔式執行個體中斷事件設定監控機制,接收到搶佔式執行個體的中斷事件後,系統自動使用該鏡像建立搶佔式執行個體,實現執行個體內資料恢複。

工作原理

使用搶佔式執行個體時,執行個體可能會因為價格因素或者市場供需變化而被強制回收,在被完全回收前,執行個體會進入鎖定狀態,並觸發搶佔式執行個體的中斷事件。

您可以基於該事件設定監控機制,當接收到搶佔式執行個體的中斷事件後,通過Java SDK 2.0代碼自動為執行個體建立自訂鏡像,並基於建立好的自訂鏡像建立搶佔式執行個體,以實現執行個體內的資料恢複。

本文提供的樣本情境中,營運工作流程圖如下所示:

前提條件

  • 已準備阿里雲帳號以及對應的存取金鑰(AccessKey)。

    使用Alibaba Cloud SDK for Java時需要設定阿里雲帳號的AccessKey資訊。AccessKey的擷取方式,請參見建立AccessKey

  • 已配置環境變數ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET。具體操作,請參見配置環境變數

  • 已在開發環境中安裝ECS Java SDK 2.0。

    您需要在Maven專案中添加以下依賴。具體操作,請參見整合SDK

    <dependencies>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>ecs20140526</artifactId>
            <version>5.1.8</version>
        </dependency>
    
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>20.0</version>
        </dependency>
    
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.4</version>
        </dependency>
    
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.5</version>
        </dependency>
    
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>
    
    </dependencies>

注意事項

重要

本文提供的範例程式碼僅供參考,並不能保證您的執行個體一定會在5分鐘內完成鏡像的建立與資料的恢複。

搶佔式執行個體會提前至少5分鐘發送執行個體中斷訊息,但資料恢複的具體耗時取決於您執行個體的鏡像類型與系統硬碟檔案大小等因素。例如,系統硬碟檔案越大,恢復越久。請您使用範例程式碼前務必自行進行評估與驗證。

步驟一:建立搶佔式執行個體

本步驟提供名為CreateSpotInstance的樣本類,代碼中主要通過ECS的RunInstances介面建立搶佔式執行個體。

import com.aliyun.ecs20140526.Client;
import com.aliyun.ecs20140526.models.RunInstancesRequest;
import com.aliyun.ecs20140526.models.RunInstancesResponse;

public class CreateSpotInstance {
    static Client client;
    // 指定地區ID。指定後您建立的ECS執行個體屬於該地區內。
    static String regionId = "cn-hangzhou";
    // 指定可用性區域ID。指定後您建立的ECS執行個體屬於該可用性區域內。
    static String zoneId = "cn-hangzhou-i";
    // 指定建立的ECS執行個體所使用的執行個體規格。
    static String instanceType = "ecs.e-c1m1.large";
    // 指定建立的ECS執行個體所使用的鏡像ID。
    static String imagesId = "aliyun_3_9_x64_20G_alibase_20231219.vhd";
    // 指定建立的ECS執行個體所屬的交換器ID。
    static String vSwitchId = "<your-vSwitchId>";
    // 指定建立的ECS執行個體所屬的安全性群組ID。
    static String securityGroupId = "<your-securityGroupId>";
    // 指定搶佔策略。
    static String spotStrategy = "SpotAsPriceGo";
    // 修改您需要保留搶佔式執行個體的時間長度。不能確定保留時間長度時,請設定為0。
    static Integer spotDuration = 0;
    // 指定ECS執行個體的登入密碼。
    static String password = "<your-password>";

    public static void main(String[] args) throws Exception {
        client = createClient();
        createInstance();
    }

    private static Client createClient() throws Exception {
        // 工程代碼泄露可能會導致 AccessKey 泄露,並威脅帳號下所有資源的安全性。以下程式碼範例僅供參考。
        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.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"));
        // Endpoint 請參考 https://api.aliyun.com/product/Ecs
        config.endpoint = "ecs-cn-hangzhou.aliyuncs.com";
        return new Client(config);
    }

    //建立執行個體。
    public static String createInstance() {
        try {
            // 設定RunInstances參數,發送請求。
            RunInstancesRequest request = new RunInstancesRequest();
            request.setRegionId(regionId);
            request.setZoneId(zoneId);
            request.setInstanceType(instanceType);
            request.setSpotDuration(spotDuration);
            request.setSpotStrategy(spotStrategy);
            request.setImageId(imagesId);
            request.setVSwitchId(vSwitchId);
            request.setSecurityGroupId(securityGroupId);
            // InstanceChargeType取值為PostPaid時才會生效搶佔策略。
            request.setInstanceChargeType("PostPaid");
            request.setPassword(password);
            request.setInternetMaxBandwidthOut(1);
            // 接收調用的返回結果,並輸出已建立的ECS執行個體ID。
            RunInstancesResponse response = client.runInstances(request);
            if (null == response.getBody().getInstanceIdSets() || response.getBody().getInstanceIdSets().getInstanceIdSet().isEmpty()) {
                return null;
            }
            String instanceId = response.getBody().getInstanceIdSets().getInstanceIdSet().get(0);
            System.out.println("建立的執行個體ID:" + instanceId);
            return instanceId;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

步驟二:監控到中斷事件後自動建立自訂鏡像

本步驟提供名為CreateSpotImage 的樣本類,代碼中依次調用了以下介面分別實現功能:

  • 調用DescribeInstances監控搶佔式執行個體的狀態。

  • 當監控到搶佔式執行個體產生中斷事件後,調用CreateImage為指定的搶佔式執行個體建立自訂鏡像。

  • 建立自訂鏡像後,調用DescribeImages監控自訂鏡像的狀態,當鏡像變為可用狀態時,返回提示資訊。

import com.aliyun.ecs20140526.Client;
import com.aliyun.ecs20140526.models.CreateImageRequest;
import com.aliyun.ecs20140526.models.CreateImageResponse;
import com.aliyun.ecs20140526.models.DescribeImagesRequest;
import com.aliyun.ecs20140526.models.DescribeImagesResponse;
import com.aliyun.ecs20140526.models.DescribeImagesResponseBody.DescribeImagesResponseBodyImagesImage;
import com.aliyun.ecs20140526.models.DescribeInstancesRequest;
import com.aliyun.ecs20140526.models.DescribeInstancesResponse;
import com.aliyun.ecs20140526.models.DescribeInstancesResponseBody.DescribeInstancesResponseBodyInstances;
import com.aliyun.ecs20140526.models.DescribeInstancesResponseBody.DescribeInstancesResponseBodyInstancesInstance;
import com.aliyun.ecs20140526.models.DescribeInstancesResponseBody.DescribeInstancesResponseBodyInstancesInstanceOperationLocksLockReason;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;

import java.util.List;

import com.alibaba.fastjson.JSON;

public class CreateSpotImage {
    static Client client;
    // 請將regionId修改為您的搶佔式執行個體所屬的地區ID。
    static String regionId = "cn-hangzhou";
    // 搶佔式執行個體的執行個體ID。
    static String instanceId = "<your-instanceId>";

    public static void main(String[] args) throws Exception {
        client = createClient();
        // 步驟一:等待搶佔式執行個體到待回收狀態,併產生中斷事件。
        waitForInstanceMarked();
        System.out.println("spot instance will be recycled immediately, instance id:" + instanceId);
        // 步驟二:當搶佔式執行個體產生中斷事件時,自動為執行個體建立自訂鏡像。
        String image1 = createImage();
        // 步驟三:等待自訂鏡像建立成功。
        waitCreateImageSuccess(image1);
    }

    private static Client createClient() throws Exception {
        // 工程代碼泄露可能會導致 AccessKey 泄露,並威脅帳號下所有資源的安全性。以下程式碼範例僅供參考。
        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.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"));
        // Endpoint 請參考 https://api.aliyun.com/product/Ecs
        config.endpoint = "ecs-cn-hangzhou.aliyuncs.com";
        return new Client(config);
    }

    // 監控搶佔式執行個體的狀態,當產生中斷事件時,輸出執行個體相關資訊。
    public static void waitForInstanceMarked() {
        // 將對象轉化為JSON字串。
        List<String> instanceIds = Lists.newArrayList();
        instanceIds.add(instanceId);
        String instanceIdStr = JSON.toJSONString(instanceIds);
        boolean isMarked = false;
        // 判斷搶佔式執行個體是否產生中斷事件。
        while (!isMarked) {
            try {
                // 設定DescribeInstances參數,發送請求。
                DescribeInstancesRequest request = new DescribeInstancesRequest();
                // 指定搶佔式執行個體所在的地區。
                request.setRegionId(regionId);
                // 指定搶佔式執行個體ID查詢。
                request.setInstanceIds(instanceIdStr);
                // 接收調用的返回結果。
                DescribeInstancesResponse response = client.describeInstances(request);
                // 擷取搶佔式執行個體相關的返回結果。
                DescribeInstancesResponseBodyInstances instances = response.getBody().getInstances();
                // 如果未查詢到執行個體資訊,則跳出迴圈。
                if (CollectionUtils.isEmpty(instances.getInstance())) {
                    break;
                }
                DescribeInstancesResponseBodyInstancesInstance instance = instances.getInstance().get(0);
                // 如果查詢到的執行個體沒有被中斷,則重新開始迴圈。
                if (instance.getOperationLocks() == null || instance.getOperationLocks().getLockReason().size() == 0) {
                    continue;
                }
                for (DescribeInstancesResponseBodyInstancesInstanceOperationLocksLockReason lockReason : instance
                    .getOperationLocks().getLockReason()) {
                    // 如果查詢到的執行個體被中斷,則輸出指定執行個體ID以及造成中斷的原因。
                    System.out.println("instance:" + instance.getInstanceId() + "-->lockReason:" + lockReason.getLockReason() + ",vmStatus:" + instance.getStatus());
                    if ("Recycling".equals(lockReason.getLockReason())) {
                        isMarked = true;
                    }
                }

                Thread.sleep(2 * 1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 建立自訂鏡像。
    public static String createImage() {
        try {
            // 設定CreateImage參數,發送請求。
            CreateImageRequest request = new CreateImageRequest();
            request.setRegionId(regionId);
            request.setInstanceId(instanceId);
            // 接收調用的返回結果,並輸出已建立的自訂鏡像ID。
            CreateImageResponse response = client.createImage(request);
            System.out.println("imageID:" + response.getBody().getImageId());
            return response.getBody().getImageId();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // 查詢鏡像建立是否成功。
    public static void waitCreateImageSuccess(String imageId) {
        boolean isSuccess = false;
        while (!isSuccess) {
            DescribeImagesResponseBodyImagesImage image = describeImage(imageId);
            if (null == image) {
                System.err.println("image not exist. imageId: " + imageId);
                break;
            }
            if ("Available".equals(image.getStatus())) {
                System.out.println("Image created successfully.");
                isSuccess = true;
            }
        }
    }

    // 調用DescribeImages監控鏡像狀態。
    public static DescribeImagesResponseBodyImagesImage describeImage(String imageId) {
        try {
            Thread.sleep(6 * 60 * 1000);
            DescribeImagesRequest imagesRequest = new DescribeImagesRequest();
            imagesRequest.setRegionId(regionId);
            imagesRequest.setImageId(imageId);
            imagesRequest.setPageSize(100);
            DescribeImagesResponse imagesResponse = client.describeImages(imagesRequest);
            if (null == imagesResponse.getBody().getImages() || CollectionUtils.isEmpty(imagesResponse.getBody().getImages().getImage())) {
                return null;
            }
            return imagesResponse.getBody().getImages().getImage().get(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

步驟三:使用自訂鏡像建立搶佔式執行個體實現資料恢複

本步驟提供名為CreateSpotInstanceFromImage 的樣本類,代碼中調用ECS的RunInstances介面,指定已建立的自訂鏡像建立搶佔式執行個體。

import com.aliyun.ecs20140526.Client;
import com.aliyun.ecs20140526.models.RunInstancesRequest;
import com.aliyun.ecs20140526.models.RunInstancesResponse;

public class CreateSpotInstanceFromImage {
    static Client client;
    // 指定執行個體所屬的地區ID。建議與源搶佔式執行個體所屬地區保持一致。
    static String regionId = "cn-hangzhou";
    // 指定執行個體所屬的可用性區域ID。建議與源搶佔式執行個體所屬可用性區域保持一致。
    static String zoneId = "cn-hangzhou-i";
    // 指定建立的ECS執行個體所使用的執行個體規格。
    static String instanceType = "ecs.s6-c1m1.small";
    // 指定已建立的自訂鏡像ID。
    static String imagesId = "<your-imagesId>";
    // 指定建立的ECS執行個體所屬的交換器ID。
    static String vSwitchId = "<your-vSwitchId>";
    // 指定建立的ECS執行個體所屬的安全性群組ID。
    static String securityGroupId = "<your-securityGroupId>";
    // 指定搶佔策略。
    static String spotStrategy = "SpotAsPriceGo";
    // 修改您需要保留搶佔式執行個體的時間長度。不能確定保留時間長度時,請設定為0。
    static Integer spotDuration = 0;
    // 指定ECS執行個體的登入密碼。
    static String password = "<your-password>";


    public static void main(String[] args) throws Exception {
        client = createClient();
        createInstance();
    }

    private static Client createClient() throws Exception {
        // 工程代碼泄露可能會導致 AccessKey 泄露,並威脅帳號下所有資源的安全性。以下程式碼範例僅供參考。
        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.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"));
        // Endpoint 請參考 https://api.aliyun.com/product/Ecs
        config.endpoint = "ecs-cn-hangzhou.aliyuncs.com";
        return new Client(config);
    }

    //調用RunInstances建立執行個體。
    public static String createInstance() {
        try {
            RunInstancesRequest request = new RunInstancesRequest();
            request.setRegionId(regionId);
            request.setZoneId(zoneId);
            request.setInstanceType(instanceType);
            request.setSpotDuration(spotDuration);
            request.setSpotStrategy(spotStrategy);
            request.setImageId(imagesId);
            request.setVSwitchId(vSwitchId);
            request.setSecurityGroupId(securityGroupId);
            request.setInstanceChargeType("PostPaid");
            request.setPassword(password);
            request.setInternetMaxBandwidthOut(1);
            RunInstancesResponse response = client.runInstances(request);
            if (null == response.getBody().getInstanceIdSets() || response.getBody().getInstanceIdSets().getInstanceIdSet().isEmpty()) {
                return null;
            }
            String instanceId = response.getBody().getInstanceIdSets().getInstanceIdSet().get(0);
            System.out.println("建立的執行個體ID:" + instanceId);;
            return instanceId;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}