抢占式实例可能会因为价格因素或者市场供需变化而被强制回收,如果您的业务对实例中断敏感,则需要注意及时感知抢占式实例的中断事件并做出响应处理。为了帮助您更好地开发中断事件处理程序,本文将指导您如何通过不同的方式感知中断事件。
感知中断事件
通过ECS的SDK查询
通过云服务器ECS的DescribeInstances接口查询实例信息,并根据返回的OperationLocks判断实例是否进入待回收状态。
若返回空值:实例可持续使用。
若返回
LockReason
值为Recycling
:抢占式实例被中断,处于待回收状态。
SDK调用示例
准备工作
创建AccessKey
由于阿里云账号(主账号)拥有资源的所有权限,其AccessKey一旦泄露风险巨大,所以建议您使用满足最小化权限需求的RAM用户的AccessKey。获取方法请参见创建AccessKey。
为RAM用户授予ECS相关权限
给RAM用户授予操作云服务器ECS相关资源的权限。本文提供的示例代码需要创建查询实例信息,建议授予以下权限:
云产品
授予权限
云服务器ECS
本示例选择系统策略:AliyunECSFullAccess
配置访问凭证
本文示例代码会在系统环境变量中读取AccessKey作为访问云服务的凭证,具体操作步骤请参见在Linux、macOS和Windows系统配置环境变量。
安装ECS SDK
获取ECS SDK,本文通过添加Maven依赖的方式来安装。更多安装方式,请参见安装ECS Java SDK。
初始化客户端
阿里云SDK支持多种访问凭据用于初始化客户端,例如AccessKey和STS Token等,更多方式请参见管理访问凭据。本示例以通过AccessKey初始化客户端为例。
import com.aliyun.ecs20140526.Client;
import com.aliyun.teaopenapi.models.Config;
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"))
// Endpoint 请参考 https://api.aliyun.com/product/Ecs
.setEndpoint("ecs.cn-hangzhou.aliyuncs.com");
return new Client(config);
}
}
构建接口的请求对象
在构建请求对象之前,请查看该接口的API文档获取参数信息。
// 构造请求对象
DescribeInstancesRequest request = new DescribeInstancesRequest().setRegionId("cn-hangzhou");
发起调用
通过客户端调用OpenAPI时,支持设置运行时参数,例如超时配置、代理配置等,更多信息请查看进阶配置。
// 设置运行时参数
RuntimeOptions runtime = new RuntimeOptions();
// 调用 DescribeInstances 接口
DescribeInstancesResponse response = client.describeInstancesWithOptions(request, runtime);
System.out.println(response.body.toMap());
异常处理
Java SDK将异常进行了细致的分类,主要划分为TeaUnretryableException和TeaException。
TeaUnretryableException:主要是因为网络问题造成,一般是网络问题达到最大重试次数后抛出。
TeaException:主要以业务报错为主的异常。
建议采取合理的措施来处理异常,比如合理地传播异常、记录日志、尝试恢复等,以确保系统的健壮性和稳定性。
完整示例
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"))
// Endpoint 请参考 https://api.aliyun.com/product/Ecs
.setEndpoint("ecs.cn-hangzhou.aliyuncs.com");
return new Client(config);
}
public static void main(String[] args) {
try {
Client client = Sample.createClient();
// 构造请求对象
// 设置待查询的一个或多个ECS实例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();
//获取实例信息,并根据lockReason返回值进行判断
if (instanceList != null && instanceList.getInstance()!= null && !instanceList.getInstance().isEmpty()) {
for (DescribeInstancesResponseBody.DescribeInstancesResponseBodyInstancesInstance instance : instanceList.getInstance()) {
// 输出被查询的实例ID与可用区信息。
System.out.println("result:instance:" + instance.getInstanceId() + ",az:" + instance.getZoneId());
if (instance.getOperationLocks() != null ) {
DescribeInstancesResponseBody.DescribeInstancesResponseBodyInstancesInstanceOperationLocks operationLocks = instance.getOperationLocks();
if(operationLocks.getLockReason()!=null && !operationLocks.getLockReason().isEmpty()){
for (DescribeInstancesResponseBody.DescribeInstancesResponseBodyInstancesInstanceOperationLocksLockReason lockReason : operationLocks.getLockReason()) {
// 如果实例被锁定,输出指定实例ID以及对应的锁定类型。
System.out.println("instance:" + instance.getInstanceId() + "-->lockReason:" + lockReason.getLockReason() + ",vmStatus:" + instance.getStatus());
if ("Recycling".equals(lockReason.getLockReason())) {
// 输出即将被回收的实例ID信息。
System.out.println("spot instance will be recycled immediately, instance id:" + instance.getInstanceId());
instanceIds.remove(instance.getInstanceId());
}
}
}
}
}
// 如果抢占式实例还未被锁定,将每隔两分钟查询一次。
System.out.print("try describeInstances again later ...");
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());
// 打印错误信息,错误信息中包含 RequestId
System.out.println(e.getMessage());
// 打印服务端返回的具体错误内容
System.out.println(e.getData());
} catch (Exception e) {
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
e.printStackTrace();
}
}
}
返回结果
触发回收时输出结果如下:
result:instance:i-bp1i9c3qiv1qs6nc****,az:cn-hangzhou-i
instance:i-bp1i9c3qiv1qs6nc****-->lockReason:Recycling,vmStatus:Stopped
spot instance will be recycled immediately, instance id:i-bp1i9c3qiv1qs6nc****
通过元数据在实例内部查询
您可以在ECS实例内部访问元数据服务(Metadata Service)获知抢占式实例的终止时间。更多关于实例元数据的信息,请参见实例元数据。
获取抢占式实例停机释放时间元数据:instance/spot/termination-time
返回结果:
如果返回404:实例可持续使用。
如果返回类似
2015-01-05T18:02:00Z
格式的信息(UTC时间):实例将于这个时间被回收。
调用示例:
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-time
Windows实例
# 获取元数据服务器的访问凭证用于鉴权
$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-time
通过云监控SDK查询
ECS实例相关的事件都会同步至云监控,您也可直接通过云监控的DescribeSystemEventAttribute接口查询抢占式实例中断事件Instance:PreemptibleInstanceInterruption
,根据返回结果中的content
字段返回值是否为delete
判断抢占式实例是否触发中断回收。
SDK调用示例
准备工作
创建AccessKey
由于阿里云账号(主账号)拥有资源的所有权限,其AccessKey一旦泄露风险巨大,所以建议您使用满足最小化权限需求的RAM用户的AccessKey。获取方法请参见创建AccessKey。
为RAM用户授予CMS相关权限
给RAM用户授予操作云监控CMS的权限。本文提供的示例代码需要查询系统事件,建议授予以下权限:
云产品
授予权限
云监控CMS
AliyunCloudMonitorFullAccess
配置访问凭证
本文示例代码会在系统环境变量中读取AccessKey作为访问云服务的凭证,具体操作步骤请参见在Linux、macOS和Windows系统配置环境变量。
安装CMS SDK
获取CMS SDK,本文通过添加Maven依赖的方式来安装。更多安装方式,请参见安装CMS Java SDK。
初始化客户端
阿里云SDK支持多种访问凭据用于初始化客户端,例如AccessKey和STS Token等,更多方式请参见管理访问凭据。本示例以通过AccessKey初始化客户端为例。
import com.aliyun.cms20190101.Client;
import com.aliyun.teaopenapi.models.Config;
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"))
// Endpoint 请参考 https://api.aliyun.com/product/Ecs
.setEndpoint("metrics.cn-hangzhou.aliyuncs.com");
return new Client(config);
}
}
构建接口的请求对象
配置抢占式实例中断事件的查询参数,更多参数请参见DescribeSystemEventAttribute。
API | 参数 | 示例取值 |
Product | 产品名称缩写:ECS | |
EventType | 事件类型:StatusNotification | |
Name | 事件名称:Instance:PreemptibleInstanceInterruption |
// 构造请求对象
DescribeSystemEventAttributeRequest request = new DescribeSystemEventAttributeRequest()
.setProduct("ECS");
.setEventType("StatusNotification");
.setName("Instance:PreemptibleInstanceInterruption");
发起调用
通过客户端调用OpenAPI时,支持设置运行时参数,例如超时配置、代理配置等,更多信息请查看进阶配置。
// 设置运行时参数
RuntimeOptions runtime = new RuntimeOptions();
// 调用 DescribeInstances 接口
DescribeSystemEventAttributeResponse response = client.describeSystemEventAttributeWithOptions(request, runtime);
System.out.println(response.body.toMap());
异常处理
Java SDK将异常进行了细致的分类,主要划分为TeaUnretryableException和TeaException。
TeaUnretryableException:主要是因为网络问题造成,一般是网络问题达到最大重试次数后抛出。
TeaException:主要以业务报错为主的异常。
建议采取合理的措施来处理异常,比如合理地传播异常、记录日志、尝试恢复等,以确保系统的健壮性和稳定性。
完整示例
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.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;
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"))
// Endpoint 请参考 https://api.aliyun.com/product/Ecs
.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();
// 调用 DescribeSystemEventAttribute 接口
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());
// 打印错误信息,错误信息中包含 RequestId
System.out.println(e.getMessage());
// 打印服务端返回的具体错误内容
System.out.println(e.getData());
} catch (Exception e) {
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
e.printStackTrace();
}
}
}
返回结果
根据返回结果判断抢占式实例中断事件。
事件通知的JSON格式如下所示:
{
"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
字段解释如下表所示。更多参数说明,请参见DescribeSystemEventAttribute。
字段 | 说明 | 示例值 |
instanceId | 抢占式实例的ID。 | i-bp1ecr********5go2go |
action | 抢占式实例的操作事件。取值delete时表示抢占式实例中断,将被强制回收。 | delete |
订阅云监控系统事件
通过订阅云监控系统事件实时监控抢占式实例的中断事件,再通过短信、邮件、函数计算、消息队列、Webhook等不同的渠道推送。
工作流程
操作步骤
接收渠道准备
轻量消息队列
在左侧导航栏,选择 。
在顶部菜单栏,选择地域。
在队列列表页面,单击创建队列。
在创建队列面板配置以下参数,然后单击确定。
名称:队列名称。
消息最大长度:发送到队列的消息体的最大长度。
长轮询时间:当队列中没有消息时,该队列的ReceiveMessage请求的最大等待时长。
消息可见性超时时间:消息从队列中取出后从Active状态变成Inactive状态后的持续时间。
消息保存时长:消息在队列中的最长存活时间。从发送到队列开始经过此参数指定的时间后,不论消息是否被取出都将被删除。
消息延时时间:发送到队列的所有消息将延后此参数指定的时间后被消费。
启用日志功能:是否开启日志管理功能。
队列列表页面目标队列已创建。
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) { // 处理payload,例如记录日志或进行业务逻辑处理 System.out.println("Received webhook payload: " + payload); // 返回成功响应 return ResponseEntity.ok("Webhook received"); } }
创建订阅策略
登录云监控控制台。
在左侧导航栏,选择
。在订阅策略页签,单击创建订阅策略。
在创建订阅策略页面,设置订阅策略的相关参数。
本示例仅说明订阅抢占式实例中断事件所涉及的参数,更多参数说明,请参见管理事件订阅(推荐)。
基本信息:输入订阅策略名称和描述。
报警订阅:
订阅类型:选择系统事件
订阅范围:
产品:选择云服务器ECS
事件类型:选择状态通知
事件名称:选择抢占式实例中断通知
事件等级:选择告警(Warning)
应用分组、事件内容和事件资源:均不设置,表示订阅本账号内所有应用分组中的所有ECS实例的系统事件抢占式实例中断通知。
合并降噪:使用默认值。
通知:自定义通知方式使用默认通知方式。
推送与集成:点击添加渠道,弹出窗口中点击增加渠道,目标类型选择第1步准备的渠道,其他参数根据提示完成填写即可。更多推送渠道说明,请参见管理推送渠道。
模拟中断事件
抢占式实例的中断事件为被动触发事件,当您在开发抢占式实例中断事件处理程序过程中,无法有效地进行代码调试。您可以借助调试事件订阅模拟抢占式实例中断事件。
在订阅策略页签,单击调试事件订阅。
在创建事件调试面板,产品选择云服务器ECS,名称选择抢占式实例中断通知。
系统自动生成JSON格式的调试内容,您需要将JSON文件中资源相关的信息替换为待模拟中断事件的抢占式实例的信息。
阿里云账号UID
变量需要替换为当前登录的阿里云账号UID。<resource-id>
以及<instanceId>
需要替换为抢占式实例的实例ID。<地域ID>
需要替换为抢占式实例所属的地域ID。{ "product": "ECS", "resourceId": "acs:ecs:cn-shanghai:阿里云账号UID:instance/<resource-id>", "level": "WARN", "instanceName": "instanceName", "regionId": "<地域ID>", "groupId": "0", "name": "Instance:PreemptibleInstanceInterruption", "content": { "instanceId": "i-2zeg014dfo4y892z***", "instanceName": "wor***b73", "action": "delete" }, "status": "Normal" }
单击确定。
系统提示操作成功,云监控自动根据订阅策略中的通知方式发送一条报警测试通知。
接收和查看
轻量消息队列
登录轻量消息队列(原 MNS)控制台,在左侧导航栏,单击队列列表。
在顶部菜单栏,选择地域,然后在队列列表页面,找到目标队列,在其右侧操作列选择收发消息。
可选:在收发消息快速体验页面,单击编辑接收消息参数,在编辑接收消息参数面板配置单次获取最大条数和轮询时间,然后单击确定。
在队列收发消息快速体验页面,单击接收消息按钮,接收消息列表区域显示队列的消息列表。
可选:在消息列表中找到目标消息,在其右侧操作列单击详情,在消息详情对话框中查看消息内容等信息。
Webhook
在部署了Webhook的程序中查看调用情况和通知内容。
响应中断事件
如何响应中断,取决于您的实际业务场景和需求,强烈建议您对应用程序进行测试,确保它可以很好地应对抢占式实例的中断回收,下面给出一些思路和建议供您参考:
优雅处理中断
及时响应中断信号,保存任务处理进度并进行资源清理,终止任务执行。
数据持久化和检查点
定期将任务处理进度和中间结果保存到持久化存储(如本地文件或数据库),确保业务重要数据和配置得到了保存。关于数据保留和恢复的配置,请参见抢占式实例数据保留和恢复。
测试集成是否成功
在云监控中触发测试事件,检查程序是否正确响应。更多信息,请参见通过轻量消息队列感知并响应抢占式实例中断事件。