抢占式实例可能会因为价格因素或者市场供需变化而被强制回收。如果您的抢占式实例上保存了重要数据,为避免数据丢失,您可以为该实例的系统盘创建快照并使用快照创建自定义镜像,然后使用该镜像新建抢占式实例以完成实例内的数据恢复。
工作原理
您在使用抢占式实例时,实例可能会因为价格因素或者市场供需变化而被强制回收,在被完全回收之前,实例会进入锁定状态,并触发抢占式实例的中断事件。
您可以基于该事件设置监控机制,并在实例正常运行过程中设置系统盘不随实例一起释放,当接收到抢占式实例的中断事件后,系统通过Java SDK 2.0代码自动为系统盘创建快照,再根据系统盘快照自动创建自定义镜像,您可以使用创建好的自定义镜像新建抢占式实例,实现实例内的数据恢复。
设置系统盘不随实例一起释放后,即使抢占式实例被释放,创建系统盘快照、创建自定义镜像等工作不受影响。
本文提供的示例场景中,运维工作流程图如下所示:
前提条件
已准备阿里云账号以及对应的访问密钥(AccessKey)。
使用Alibaba Cloud SDK for Java时需要设置阿里云账号的AccessKey信息。AccessKey的获取方式,请参见创建AccessKey。
已配置环境变量ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET。具体操作,请参见配置环境变量。
已在开发环境中安装Java SDK 2.0。
您需要在Maven项目中添加以下依赖。具体操作,请参见安装Java 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分钟发送实例中断消息,但数据恢复的具体耗时取决于您实例的镜像类型与系统盘文件大小等因素。例如,系统盘文件越大,恢复时间越久。请您使用示例代码前务必自行进行评估与验证。
使用限制
抢占式实例被回收时的数据恢复过程中,存在如下限制:
为了保证磁盘快照不丢失,建议关闭快照随磁盘一起释放的功能。
如果抢占式实例中含有数据盘,且数据盘中有重要数据,建议数据盘也设置为不随实例释放。
当您调用ModifyDiskAttribute接口时设置了不随实例释放(DeleteWithInstance=false)属性,一旦磁盘挂载的ECS实例被安全锁定且OperationLocks中标记了
"LockReason" : "security"
的锁定状态,释放实例时会忽略磁盘的DeleteWithInstance属性而被同时释放。说明您可以设置
DiskIds.N
参数批量修改多个块存储的名称、描述、是否随实例释放等属性。
步骤一:创建抢占式实例
本步骤提供名为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;
}
}
步骤二:设置系统盘不随实例一起释放
本步骤提供名为DiskRelated
的示例类,代码中主要通过ECS的ModifyDiskAttribute接口设置系统盘不随实例一起释放。
import com.aliyun.ecs20140526.Client;
import com.aliyun.ecs20140526.models.DescribeDisksRequest;
import com.aliyun.ecs20140526.models.DescribeDisksResponse;
import com.aliyun.ecs20140526.models.DescribeDisksResponseBody.DescribeDisksResponseBodyDisksDisk;
import com.aliyun.ecs20140526.models.ModifyDiskAttributeRequest;
import com.aliyun.ecs20140526.models.ModifyDiskAttributeResponse;
import org.apache.commons.collections4.CollectionUtils;
public class DiskRelated {
static Client client;
/**
* 请将regionId修改为您的抢占式实例所属的地域ID。
*/
static String regionId = "cn-hangzhou";
/**
* 抢占式实例的实例ID。
*/
//static String instanceId = "<your-instance-id>";
static String instanceId = "i-bp18zj904wqnw5mc****";
public static void main(String[] args) throws Exception {
client = createClient();
DescribeDisksResponseBodyDisksDisk disk = getDisks();
if(null == disk){
System.out.println("disk not exist");
return;
}
String diskId = disk.getDiskId();
modifyDiskAttribute(diskId);
Boolean b = diskNotDeleteWithInstance();
if(b){
//如果第一次设置系统盘不随实例一起释放失败,则重新设置一次。
modifyDiskAttribute(diskId);
}
}
/**
* 查询系统盘详情。
*/
public static DescribeDisksResponseBodyDisksDisk getDisks(){
DescribeDisksRequest request = new DescribeDisksRequest();
request.setRegionId(regionId);
request.setInstanceId(instanceId);
request.setDiskType("system");
try {
DescribeDisksResponse response = client.describeDisks(request);
if(CollectionUtils.isEmpty(response.getBody().getDisks().getDisk())){
System.out.println(("disk not exist. instanceId: " + instanceId));
return null;
}
DescribeDisksResponseBodyDisksDisk disk = response.getBody().getDisks()
.getDisk().get(0);
return disk;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 设置系统盘不随实例一起释放。
*/
public static void modifyDiskAttribute(String diskId){
ModifyDiskAttributeRequest request = new ModifyDiskAttributeRequest();
request.setDeleteWithInstance(false);
request.setRegionId(regionId);
request.setDiskId(diskId);
try {
ModifyDiskAttributeResponse response = client.modifyDiskAttribute(request);
System.out.println(response.getBody().getRequestId());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 查询系统盘是否随实例一起释放。
*/
public static Boolean diskNotDeleteWithInstance(){
DescribeDisksResponseBodyDisksDisk disks = getDisks();
if (disks.getDeleteWithInstance()){
System.out.println(("disk is delete with instance"));
}else {
System.out.println(("disk not delete with instance"));
}
return disks.getDeleteWithInstance();
}
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);
}
}
步骤三:监控到中断事件后自动创建自定义镜像
当监控到抢占式实例产生中断事件后,系统会通过Java代码自动为系统盘创建快照,再根据系统盘快照自动创建自定义镜像。
本步骤提供名为CreateSpotImage
的示例类,代码中依次调用了以下接口分别实现功能:
调用DescribeInstances监控抢占式实例的状态。
当监控到抢占式实例产生中断事件后,先调用CreateSnapshot创建系统盘快照,再调用DescribeSnapshots查询快照状态。
创建系统盘快照后,调用CreateImage,根据已创建的系统盘快照创建自定义镜像。
创建自定义镜像后,调用DescribeImages监控自定义镜像的状态,当镜像变为可用状态时,返回提示信息。
import com.aliyun.ecs20140526.Client;
import com.aliyun.ecs20140526.models.CreateImageRequest;
import com.aliyun.ecs20140526.models.CreateImageResponse;
import com.aliyun.ecs20140526.models.CreateSnapshotRequest;
import com.aliyun.ecs20140526.models.CreateSnapshotResponse;
import com.aliyun.ecs20140526.models.DescribeDisksRequest;
import com.aliyun.ecs20140526.models.DescribeDisksResponse;
import com.aliyun.ecs20140526.models.DescribeDisksResponseBody.DescribeDisksResponseBodyDisksDisk;
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.aliyun.ecs20140526.models.DescribeSnapshotsRequest;
import com.aliyun.ecs20140526.models.DescribeSnapshotsResponse;
import com.aliyun.ecs20140526.models.DescribeSnapshotsResponseBody.DescribeSnapshotsResponseBodySnapshotsSnapshot;
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();
String diskId = getDiskId();
// 步骤二:当抢占式实例产生中断事件时,自动创建系统盘快照。
String snapshotId = createSnapshot(diskId);
// 步骤三:等待系统盘快照创建成功。
waitCreateSnapshotSuccess(snapshotId);
// 步骤四:根据系统盘快照创建自定义镜像。
String imageId = createImage(snapshotId);
// 步骤五:等待自定义镜像创建成功。
waitCreateImageSuccess(imageId);
}
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 getDiskId(){
String diskId = null;
DescribeDisksRequest request = new DescribeDisksRequest();
request.setRegionId(regionId);
request.setInstanceId(instanceId);
request.setDiskType("system");
try {
DescribeDisksResponse response = client.describeDisks(request);
List<DescribeDisksResponseBodyDisksDisk> disks = response.getBody().getDisks().getDisk();
if (CollectionUtils.isEmpty(disks) ){
System.out.println("disk not exist. instance: " + instanceId);
return null;
}
diskId = disks.get(0).getDiskId();
} catch (Exception e) {
e.printStackTrace();
}
return diskId;
}
/**
* 创建系统盘快照。
*/
public static String createSnapshot(String diskId){
CreateSnapshotRequest request = new CreateSnapshotRequest();
request.setDiskId(diskId);
request.setSnapshotName("disk_test");
try {
CreateSnapshotResponse response= client.createSnapshot(request);
System.out.println(JSON.toJSONString(response.getBody()));
System.out.println(response.getBody().getSnapshotId());
return response.getBody().getSnapshotId();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 查询系统盘快照创建是否成功。
*/
public static void waitCreateSnapshotSuccess(String snapshotId){
boolean isSuccess = false;
while (!isSuccess) {
DescribeSnapshotsResponseBodySnapshotsSnapshot snapshot = describeSnapshots(snapshotId);
if (null == snapshot) {
System.err.println("image not exist. imageId: " + snapshotId);
break;
}
if("accomplished".equals(snapshot.getStatus())){
System.out.println("snapshot created successfully.");
isSuccess = true;
}
}
}
/**
* 调用DescribeSnapshots查询系统盘快照状态。
*/
public static DescribeSnapshotsResponseBodySnapshotsSnapshot describeSnapshots(String snapshotId){
DescribeSnapshotsRequest request = new DescribeSnapshotsRequest();
request.setRegionId(regionId);
List<String> snapshotIds = Lists.newArrayList(snapshotId);
String s = JSON.toJSONString(snapshotIds);
request.setSnapshotIds(s);
try {
DescribeSnapshotsResponse response = client.describeSnapshots(request);
if (CollectionUtils.isEmpty(response.getBody().getSnapshots().getSnapshot())) {
return null;
}
return response.getBody().getSnapshots().getSnapshot().get(0);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 创建自定义镜像。
public static String createImage(String snapshotId) {
try {
// 设置CreateImage参数,发送请求。
CreateImageRequest request = new CreateImageRequest();
request.setRegionId(regionId);
request.setSnapshotId(snapshotId);
request.setImageName("image_test");
// 接收调用的返回结果,并输出已创建的自定义镜像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;
}
}
相关文档
如果您在抢占式实例上的数据盘中存储了重要数据,则您可以通过如下方式恢复数据盘上的数据:
设置数据盘不随实例释放,然后在抢占式实例被强制释放后,将该数据盘挂载到新的ECS实例上。具体操作,请参见方法一:将数据盘或系统盘挂载到新实例上以恢复数据。
为抢占式实例创建自定义镜像,然后使用该镜像创建新的ECS实例。具体操作,请参见方法二:使用原实例的自定义镜像创建实例以恢复数据。