Azure

利用 Azure Resource Graph 即時監測 Azure 資源變動

為何需要知道 Azure 上面的資源變動?

官方文件提出的情境包含:

  • 有 incident 發生的時候(如系統失效、high latency 等),需要查詢特定時間區段內所有可能受影響的資源
  • 如果組織內有針對特定類型資源的 change management database (CMDB),只在變動發生時更新 CMDB 會比定期掃描所有資源有效率得多。

我們公司沒有針對 Azure 資源的 CMDB, 但是有「每週新增了多少資源及其類型」的週報表,可以讓管理層稍微掌握大家是不是毫無節制地在開新機器或服務。

Azure Resource Graph 的資源變動紀錄

每當一項 resource 被新增、修改、或刪除時(resource 的定義參考此頁),Resource Graph 就會新增一筆變動紀錄,紀錄欄位包含了 ResourceId、變動類別(ChangeType)、及變動屬性的詳情等,如以下例子:

{
  "targetResourceId": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myResourceGroup/providers/microsoft.compute/virtualmachines/myVM",
  "targetResourceType": "microsoft.compute/virtualmachines",
  "changeType": "Update",
  "changeAttributes": {
    "changesCount": 2,
    "correlationId": "88420d5d-8d0e-471f-9115-10d34750c617",
    "timestamp": "2021-12-07T09:25:41.756Z",
    "previousResourceSnapshotId": "ed90e35a-1661-42cc-a44c-e27f508005be",
    "newResourceSnapshotId": "6eac9d0f-63b4-4e7f-97a5-740c73757efb"
  },
  "changes": {
    "properties.provisioningState": {
      "newValue": "Succeeded",
      "previousValue": "Updating",
      "changeCategory": "System",
      "propertyChangeType": "Update"
    },
    "tags.key1": {
      "newValue": "NewTagValue",
      "previousValue": "null",
      "changeCategory": "User",
      "propertyChangeType": "Insert"
    }
  }
}

變動紀錄會在資源變動之後的 5 分鐘內產生。接著我們就能透過 Azure Portal 的 Resource Graph Explorer、或是 Azure CLI 及 PowerShell 查詢到該筆紀錄。Azure Resource Graph 會保留 14 天以內的變動紀錄。以下是一些官方提供的查詢範例(以 Resource Graph Explorer 的 Kusto Query 為例):

  • 過去一天的所有資源變動
resourcechanges
| extend changeTime = todatetime(properties.changeAttributes.timestamp), targetResourceId = tostring(properties.targetResourceId),
changeType = tostring(properties.changeType), correlationId = properties.changeAttributes.correlationId, 
changedProperties = properties.changes, changeCount = properties.changeAttributes.changesCount
| where changeTime > ago(1d)
| order by changeTime desc
| project changeTime, targetResourceId, changeType, correlationId, changeCount, changedProperties
  • 在特定 resource group 內被刪除的所有資源
resourcechanges
| where resourceGroup == "myResourceGroup"
| extend changeTime = todatetime(properties.changeAttributes.timestamp), targetResourceId = tostring(properties.targetResourceId),
changeType = tostring(properties.changeType), correlationId = properties.changeAttributes.correlationId
| where changeType == "Delete"
| order by changeTime desc
| project changeTime, resourceGroup, targetResourceId, changeType, correlationId
  • 查詢特定屬性(VM Size)的變動
resourcechanges
|extend vmSize = properties.changes["properties.hardwareProfile.vmSize"], changeTime = todatetime(properties.changeAttributes.timestamp), targetResourceId = tostring(properties.targetResourceId), changeType = tostring(properties.changeType) 
| where isnotempty(vmSize) 
| order by changeTime desc 
| project changeTime, targetResourceId, changeType, properties.changes, previousSize = vmSize.previousValue, newSize = vmSize.newValue

詳細說明及更多查詢範例可以參考官方文件:Get resource changes

案例: Sandbox Cleanup

之前公司內部的幾個 Sandbox Subscriptions 內,有許多閒置已久的資源(Activity Log 是空的,代表超過三個月以上沒有動過),多半是因為員工來來去去,或測試環境創建、驗證完畢之後忘記刪除,但公司每個月還是得持續花錢運作這些不必要的閒置資源。為了節省開支,最簡單粗暴的方法當然是就直接把所有資源刪了,有需要的使用者再自己重建就好,畢竟這就是沙盒環境的意義:資源會定期自行清除。但是為了避免任何意外,且盡量把使用者的不便降到最低,我們後來採取了以下的方式:

  • 把所有資源都標上 ExpiredBy Tag 及當前日期
  • 發公告信:我們要清理 Sandbox 了,如果你的資源不想被刪除,請更新 ExpiredBy Tag value
  • 兩週後再發一次公告信
  • 刪除前,再發一次最後通牒!

正式刪除當天的運作方式如下:

  1. 如果該資源的 ExpiredBy Tag 是舊值,則直接刪除
  2. 否則就保留該資源,但把 ExpiredBy Tag 更新成新值(下次預定的沙盒清理日期)

這個更新 ExpiredBy Tag 的動作是讓使用者知道:沙盒環境是會定期清除的,請不要把任何開發甚至 正式環境的資源放在這裡!之後再搭配「資源創建時會自動標註 ExpiredBy Tag」,就可以把往後的沙盒環境清理全自動化。

怎麼(即時)知道哪些資源被刪除了

怎麼知道哪些資源被刪除了?只要在 script 內把被刪除的 ResourceId 輸出成檔案就好。但是因為這是我們第一次執行沙盒清理,我的 manager 希望能夠實時監控刪除過程,以確認沒有誤刪到沙盒環境以外的資源。即時監控的方式就是在 Resource Graph Explorer 查詢 changeType == "Delete"的變動

resourcechanges
| extend changeTime = todatetime(properties.changeAttributes.timestamp), targetResourceId = tostring(properties.targetResourceId), changeType = tostring(properties.changeType), correlationId = properties.changeAttributes.correlationId, changedProperties = properties.changes, changeCount = properties.changeAttributes.changesCount
| where changeType == "Delete"
| where changeTime > ago(1d)
| order by changeTime desc
| project changeTime, targetResourceId, changeType, correlationId, changeCount, changedProperties