搶佔式執行個體可能會因為價格因素或者市場供需變化而被強制回收,如果您的業務對執行個體中斷敏感,則需要注意及時感知搶佔式執行個體的中斷事件並做出響應處理。為了協助您更好地開發中斷事件處理常式,本文將指導您如何通過不同的方式感知中斷事件。
感知中斷事件
通過ECS的SDK查詢
通過Elastic Compute Service的DescribeInstances介面查詢執行個體資訊,並根據返回的OperationLocks判斷執行個體是否進入待回收狀態。
若返回空值:執行個體可持續使用。
若返回
LockReason值為Recycling:搶佔式執行個體被中斷,處於待回收狀態。
SDK調用樣本
準備工作
建立AccessKey
由於阿里雲帳號(主帳號)擁有資源的所有許可權,其AccessKey一旦泄露風險巨大,所以建議您使用滿足最小化許可權需求的RAM使用者的AccessKey。擷取方法請參見建立AccessKey。
為RAM使用者授予ECS相關許可權
給RAM使用者授予操作Elastic Compute Service相關資源的許可權。本文提供的範例程式碼需要建立查詢執行個體資訊,建議授予以下許可權:
雲產品
授予許可權
Elastic Compute Service
本樣本選擇系統策略: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-timeWindows執行個體
# 擷取中繼資料服務器的訪問憑證用於鑒權
$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通過CloudMonitorSDK查詢
ECS執行個體相關的事件都會同步至CloudMonitor,您也可直接通過CloudMonitor的DescribeSystemEventAttribute介面查詢搶佔式執行個體中斷事件Instance:PreemptibleInstanceInterruption,根據返回結果中的content欄位傳回值是否為delete判斷搶佔式執行個體是否觸發中斷回收。
SDK調用樣本
準備工作
建立AccessKey
由於阿里雲帳號(主帳號)擁有資源的所有許可權,其AccessKey一旦泄露風險巨大,所以建議您使用滿足最小化許可權需求的RAM使用者的AccessKey。擷取方法請參見建立AccessKey。
為RAM使用者授予CMS相關許可權
給RAM使用者授予操作CloudMonitorCMS的許可權。本文提供的範例程式碼需要查詢系統事件,建議授予以下許可權:
雲產品
授予許可權
CloudMonitorCMS
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 |
訂閱CloudMonitor系統事件
通過訂閱CloudMonitor系統事件即時監控搶佔式執行個體的中斷事件,再通過簡訊、郵件、Function Compute、訊息佇列、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"); } }建立訂閱策略
在左側導覽列,選擇。
在訂閱策略頁簽,單擊建立訂閱策略。
在建立訂閱策略頁面,設定訂閱策略的相關參數。
本樣本僅說明訂閱搶佔式執行個體中斷事件所涉及的參數,更多參數說明,請參見管理事件訂閱(推薦)。
基本資料:輸入訂閱策略名稱和描述。
警示訂閱:
訂閱類型:選擇系統事件
訂閱範圍:
產品:選擇Elastic Compute Service
事件類型:選擇狀態通知
事件名稱:選擇搶佔式執行個體中斷通知
事件等級:選擇警示(Warning)
應用分組、事件內容和事件資源:均不設定,表示訂閱本帳號內所有應用分組中的所有ECS執行個體的系統事件搶佔式執行個體中斷通知。
合并降噪:使用預設值。
通知:自訂通知方式使用預設通知方式。
推送與整合:點擊添加渠道,快顯視窗中點擊增加渠道,目標類型選擇第1步準備的渠道,其他參數根據提示完成填寫即可。更多推送渠道說明,請參見管理推送渠道。
類比中斷事件
搶佔式執行個體的中斷事件為被動觸發事件,當您在開發搶佔式執行個體中斷事件處理常式過程中,無法有效地進行代碼調試。您可以藉助調試事件訂閱類比搶佔式執行個體中斷事件。
在訂閱策略頁簽,單擊調試事件訂閱。
在建立事件調試面板,產品選擇Elastic Compute Service,名稱選擇搶佔式執行個體中斷通知。
系統自動產生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" }
單擊確定。
系統提示操作成功,CloudMonitor自動根據訂閱策略中的通知方式發送一條警示測試通知。
接收和查看
輕量訊息佇列
登入輕量訊息佇列(原 MNS)控制台,在左側導覽列,單擊队列列表。
在頂部功能表列,選擇地區,然後在隊列列表頁面,找到目標隊列,在其右側操作列選擇收发消息。
可選:在收發訊息快速體驗頁面,單擊编辑接收消息参数,在编辑接收消息参数面板配置单次获取最大条数和轮询时间,然後單擊確定。
在隊列收發訊息快速體驗頁面,單擊接收消息按鈕,接收訊息列表地區顯示隊列的訊息列表。
可選:在訊息列表中找到目標訊息,在其右側操作列單擊详情,在訊息詳情對話方塊中查看訊息內容等資訊。
Webhook
在部署了Webhook的程式中查看調用情況和通知內容。
響應中斷事件
如何響應中斷,取決於您的實際業務情境和需求,強烈建議您對應用程式進行測試,確保它可以很好地應對搶佔式執行個體的中斷回收,下面給出一些思路和建議供您參考:
優雅處理中斷
及時響應中斷訊號,儲存任務處理進度並進行資源清理,終止任務執行。
資料持久化和檢查點
定期將任務處理進度和中間結果儲存到持久化儲存(如本地檔案或資料庫),確保業務重要資料和配置得到了儲存。關於資料保留和恢複的配置,請參見搶佔式執行個體資料保留和恢複。
測試整合是否成功
在CloudMonitor中觸發測試事件,檢查程式是否正確響應。更多資訊,請參見通過輕量訊息佇列感知並響應搶佔式執行個體中斷事件。