搶占式實例可能會因為價格因素或者市場供需變化而被強制回收。如果您的搶占式實例上保存了重要數據,為避免數據丟失,您可以為該搶占式實例創建自定義鏡像,并基于搶占式實例中斷事件設置監控機制,接收到搶占式實例的中斷事件后,系統自動使用該鏡像新建搶占式實例,實現實例內數據恢復。
工作原理
使用搶占式實例時,實例可能會因為價格因素或者市場供需變化而被強制回收,在被完全回收前,實例會進入鎖定狀態,并觸發搶占式實例的中斷事件。
您可以基于該事件設置監控機制,當接收到搶占式實例的中斷事件后,通過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;
}
}