前言:
童鞋们估计已经抱怨过n次这个问题,为啥托管磁盘不能被用来制作镜像或者制作快照后在其他订阅或其它区域(Region)来创建虚拟机,说好的 Infrastructure as a code 呢,刚刚激动的做好了镜像,把应用架构描述成 infra code,一键点亮。跨了订阅,跨了区域玩不转啦,XX**。不要慌,这个事儿并非没有解,就是稍微麻烦,你可以把托管磁盘做导出到目标订阅或区域的存储账户,然后在目标订阅内或区域内创建镜像。一两个订阅还好,一大把订阅那不是很坑,本文后面就带大家以玩的心态,边了解 Azure 的新服务 EventGrid,LogicApp,ACI,然后一起来配合使用来实现跨订阅或跨地域的托管磁盘的自动导出拷贝。
架构逻辑:
通过 EventGrid 来监听 VM 创建事件,当事件发生后,LogicApp 作为该事件的 Subscriber 消费者,触发自动化流水线,将该虚拟机的磁盘拷贝到目的订阅或目的区域的存储账户中,拷贝过程通过azcopy来完成,选择azcopy的原因速度块。整个架构中采用 Severless 的思路,所以将 azcopy 封装到一个做好的容器镜像中,在调用时通过在 Serverless 的 Azure Container Instance 中进行执行,执行完毕资源回收,可以享受容器启动的敏捷性的同时实现按市场付费计算资源的要求。
逻辑实现细节:
整个逻辑实现的重点在LogicApp,也就是自动化流水线的实现,流水线的规程可以参阅如下 LogicApp 的 Code,大家可以导入自己的环境在里面进行学习和修改。犯懒的童鞋也别难过,借此安利一把,Image 共享这个服务 Azure 平台马上会当作功能给到大家 Preview,点我了解更多
1 { 2 "$connections": { 3 "value": { 4 "aci": { 5 "connectionId": "/subscriptions/c04b3c63-8dfe-4f98-be18-e71ff67a1f4e/resourceGroups/eventgrid/providers/Microsoft.Web/connections/aci", 6 "connectionName": "aci", 7 "id": "/subscriptions/c04b3c63-8dfe-4f98-be18-e71ff67a1f4e/providers/Microsoft.Web/locations/southeastasia/managedApis/aci" 8 }, 9 "arm": { 10 "connectionId": "/subscriptions/c04b3c63-8dfe-4f98-be18-e71ff67a1f4e/resourceGroups/eventgrid/providers/Microsoft.Web/connections/arm", 11 "connectionName": "arm", 12 "id": "/subscriptions/c04b3c63-8dfe-4f98-be18-e71ff67a1f4e/providers/Microsoft.Web/locations/southeastasia/managedApis/arm" 13 }, 14 "azureeventgrid": { 15 "connectionId": "/subscriptions/c04b3c63-8dfe-4f98-be18-e71ff67a1f4e/resourceGroups/eventgrid/providers/Microsoft.Web/connections/azureeventgrid", 16 "connectionName": "azureeventgrid", 17 "id": "/subscriptions/c04b3c63-8dfe-4f98-be18-e71ff67a1f4e/providers/Microsoft.Web/locations/southeastasia/managedApis/azureeventgrid" 18 } 19 } 20 }, 21 "definition": { 22 "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 23 "actions": { 24 "Compose": { 25 "inputs": { 26 "vmname": "@substring(body('Parse_JSON')?['subject'],add(16,lastIndexOf(body('Parse_JSON')?['subject'],'virtualMachines/')),sub(length(body('Parse_JSON')?['subject']),add(16,lastIndexOf(body('Parse_JSON')?['subject'],'virtualMachines/'))))" 27 }, 28 "runAfter": { 29 "Parse_JSON": [ 30 "Succeeded" 31 ] 32 }, 33 "type": "Compose" 34 }, 35 "Compose_2": { 36 "inputs": "@triggerBody()", 37 "runAfter": {}, 38 "type": "Compose" 39 }, 40 "Compose_3": { 41 "inputs": "@body('Parse_JSON_3')?['storageProfile']['osDisk']", 42 "runAfter": { 43 "Parse_JSON_3": [ 44 "Succeeded" 45 ] 46 }, 47 "type": "Compose" 48 }, 49 "Create_container_group": { 50 "inputs": { 51 "body": { 52 "location": "southeastasia", 53 "properties": { 54 "containers": [ 55 { 56 "name": "@{concat('azcp',rand(1,100))}", 57 "properties": { 58 "command": [ 59 "/bin/bash", 60 "-c", 61 "azcopy --destination $destination --source $source --dest-key $destkey" 62 ], 63 "environmentVariables": [ 64 { 65 "name": "destination", 66 "value": "@{concat('https://diskclone.blob.core.windows.net/diskclone/',outputs('Compose_3')?['name'])}" 67 }, 68 { 69 "name": "source", 70 "value": "@body('mdsasurl')?['accessSAS']" 71 }, 72 { 73 "name": "destkey", 74 "value": "yrbf9VRMOFN70+tAmoianfx1TGwJPAp+26aQ2g7EdB+4UqNBSoa3bJP9uEwJy2eGUz5y1NPnZx8XuKJP0W3p8w==" 75 } 76 ], 77 "image": "acrbuildpilot.azurecr.io/azcopy:v1", 78 "resources": { 79 "requests": { 80 "cpu": 1, 81 "memoryInGB": 1 82 } 83 } 84 } 85 } 86 ], 87 "imageRegistryCredentials": [ 88 { 89 "password": "iKccXllxyrn8aoSfPtQdUZTzDLIh/VRv", 90 "server": "acrbuildpilot.azurecr.io", 91 "username": "acrbuildpilot" 92 } 93 ], 94 "osType": "Linux", 95 "restartPolicy": "Never" 96 } 97 }, 98 "host": { 99 "connection": { 100 "name": "@parameters('$connections')['aci']['connectionId']" 101 } 102 }, 103 "method": "put", 104 "path": "/subscriptions/@{encodeURIComponent('4507938f-a0ac-4571-978e-7cc741a60af8')}/resourceGroups/@{encodeURIComponent('aci')}/providers/Microsoft.ContainerInstance/containerGroups/@{encodeURIComponent(concat('azcp', rand(1, 100)))}", 105 "queries": { 106 "x-ms-api-version": "2017-10-01-preview" 107 } 108 }, 109 "runAfter": { 110 "mdsasurl": [ 111 "Succeeded" 112 ] 113 }, 114 "type": "ApiConnection" 115 }, 116 "Parse_JSON": { 117 "inputs": { 118 "content": "@outputs('Compose_2')", 119 "schema": { 120 "properties": { 121 "data": { 122 "properties": { 123 "authorization": { 124 "properties": { 125 "action": { 126 "type": "string" 127 }, 128 "evidence": { 129 "properties": { 130 "role": { 131 "type": "string" 132 } 133 }, 134 "type": "object" 135 }, 136 "scope": { 137 "type": "string" 138 } 139 }, 140 "type": "object" 141 }, 142 "claims": { 143 "properties": { 144 "aio": { 145 "type": "string" 146 }, 147 "appid": { 148 "type": "string" 149 }, 150 "appidacr": { 151 "type": "string" 152 }, 153 "aud": { 154 "type": "string" 155 }, 156 "e_exp": { 157 "type": "string" 158 }, 159 "exp": { 160 "type": "string" 161 }, 162 "groups": { 163 "type": "string" 164 }, 165 "http://schemas.microsoft.com/2012/01/devicecontext/claims/identifier": { 166 "type": "string" 167 }, 168 "http://schemas.microsoft.com/claims/authnclassreference": { 169 "type": "string" 170 }, 171 "http://schemas.microsoft.com/claims/authnmethodsreferences": { 172 "type": "string" 173 }, 174 "http://schemas.microsoft.com/identity/claims/objectidentifier": { 175 "type": "string" 176 }, 177 "http://schemas.microsoft.com/identity/claims/scope": { 178 "type": "string" 179 }, 180 "http://schemas.microsoft.com/identity/claims/tenantid": { 181 "type": "string" 182 }, 183 "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname": { 184 "type": "string" 185 }, 186 "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": { 187 "type": "string" 188 }, 189 "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": { 190 "type": "string" 191 }, 192 "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname": { 193 "type": "string" 194 }, 195 "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn": { 196 "type": "string" 197 }, 198 "iat": { 199 "type": "string" 200 }, 201 "in_corp": { 202 "type": "string" 203 }, 204 "ipaddr": { 205 "type": "string" 206 }, 207 "iss": { 208 "type": "string" 209 }, 210 "name": { 211 "type": "string" 212 }, 213 "nbf": { 214 "type": "string" 215 }, 216 "onprem_sid": { 217 "type": "string" 218 }, 219 "puid": { 220 "type": "string" 221 }, 222 "uti": { 223 "type": "string" 224 }, 225 "ver": { 226 "type": "string" 227 } 228 }, 229 "type": "object" 230 }, 231 "correlationId": { 232 "type": "string" 233 }, 234 "operationName": { 235 "type": "string" 236 }, 237 "resourceProvider": { 238 "type": "string" 239 }, 240 "resourceUri": { 241 "type": "string" 242 }, 243 "status": { 244 "type": "string" 245 }, 246 "subscriptionId": { 247 "type": "string" 248 }, 249 "tenantId": { 250 "type": "string" 251 } 252 }, 253 "type": "object" 254 }, 255 "dataVersion": { 256 "type": "string" 257 }, 258 "eventTime": { 259 "type": "string" 260 }, 261 "eventType": { 262 "type": "string" 263 }, 264 "id": { 265 "type": "string" 266 }, 267 "metadataVersion": { 268 "type": "string" 269 }, 270 "subject": { 271 "type": "string" 272 }, 273 "topic": { 274 "type": "string" 275 } 276 }, 277 "type": "object" 278 } 279 }, 280 "runAfter": { 281 "Compose_2": [ 282 "Succeeded" 283 ] 284 }, 285 "type": "ParseJson" 286 }, 287 "Parse_JSON_2": { 288 "inputs": { 289 "content": "@outputs('Compose')", 290 "schema": { 291 "properties": { 292 "vmname": { 293 "type": "string" 294 } 295 }, 296 "type": "object" 297 } 298 }, 299 "runAfter": { 300 "Compose": [ 301 "Succeeded" 302 ] 303 }, 304 "type": "ParseJson" 305 }, 306 "Parse_JSON_3": { 307 "inputs": { 308 "content": "@body('Read_a_resource')?['properties']", 309 "schema": { 310 "properties": { 311 "hardwareProfile": { 312 "properties": { 313 "vmSize": { 314 "type": "string" 315 } 316 }, 317 "type": "object" 318 }, 319 "networkProfile": { 320 "properties": { 321 "networkInterfaces": { 322 "items": { 323 "properties": { 324 "id": { 325 "type": "string" 326 } 327 }, 328 "required": [ 329 "id" 330 ], 331 "type": "object" 332 }, 333 "type": "array" 334 } 335 }, 336 "type": "object" 337 }, 338 "osProfile": { 339 "properties": { 340 "adminUsername": { 341 "type": "string" 342 }, 343 "computerName": { 344 "type": "string" 345 }, 346 "linuxConfiguration": { 347 "properties": { 348 "disablePasswordAuthentication": { 349 "type": "boolean" 350 } 351 }, 352 "type": "object" 353 }, 354 "secrets": { 355 "type": "array" 356 } 357 }, 358 "type": "object" 359 }, 360 "provisioningState": { 361 "type": "string" 362 }, 363 "storageProfile": { 364 "properties": { 365 "dataDisks": { 366 "type": "array" 367 }, 368 "imageReference": { 369 "properties": { 370 "offer": { 371 "type": "string" 372 }, 373 "publisher": { 374 "type": "string" 375 }, 376 "sku": { 377 "type": "string" 378 }, 379 "version": { 380 "type": "string" 381 } 382 }, 383 "type": "object" 384 }, 385 "osDisk": { 386 "properties": { 387 "caching": { 388 "type": "string" 389 }, 390 "createOption": { 391 "type": "string" 392 }, 393 "diskSizeGB": { 394 "type": "number" 395 }, 396 "managedDisk": { 397 "properties": { 398 "id": { 399 "type": "string" 400 }, 401 "storageAccountType": { 402 "type": "string" 403 } 404 }, 405 "type": "object" 406 }, 407 "name": { 408 "type": "string" 409 }, 410 "osType": { 411 "type": "string" 412 } 413 }, 414 "type": "object" 415 } 416 }, 417 "type": "object" 418 }, 419 "vmId": { 420 "type": "string" 421 } 422 }, 423 "type": "object" 424 } 425 }, 426 "runAfter": { 427 "Read_a_resource": [ 428 "Succeeded" 429 ] 430 }, 431 "type": "ParseJson" 432 }, 433 "Read_a_resource": { 434 "inputs": { 435 "host": { 436 "connection": { 437 "name": "@parameters('$connections')['arm']['connectionId']" 438 } 439 }, 440 "method": "get", 441 "path": "/subscriptions/@{encodeURIComponent('c04b3c63-8dfe-4f98-be18-e71ff67a1f4e')}/resourcegroups/@{encodeURIComponent('eventgrid')}/providers/@{encodeURIComponent('Microsoft.Compute')}/@{encodeURIComponent(concat('virtualMachines/', body('Parse_JSON_2')?['vmname']))}", 442 "queries": { 443 "x-ms-api-version": "2017-12-01" 444 } 445 }, 446 "runAfter": { 447 "Parse_JSON_2": [ 448 "Succeeded" 449 ] 450 }, 451 "type": "ApiConnection" 452 }, 453 "deallocate_vm": { 454 "inputs": { 455 "host": { 456 "connection": { 457 "name": "@parameters('$connections')['arm']['connectionId']" 458 } 459 }, 460 "method": "post", 461 "path": "/subscriptions/@{encodeURIComponent('c04b3c63-8dfe-4f98-be18-e71ff67a1f4e')}/resourcegroups/@{encodeURIComponent('eventgrid')}/providers/@{encodeURIComponent('Microsoft.Compute')}/@{encodeURIComponent(concat('virtualMachines/', body('Parse_JSON_2')?['vmname']))}/@{encodeURIComponent('deallocate')}", 462 "queries": { 463 "x-ms-api-version": "2017-12-01" 464 } 465 }, 466 "runAfter": { 467 "Compose_3": [ 468 "Succeeded" 469 ] 470 }, 471 "type": "ApiConnection" 472 }, 473 "mdsasurl": { 474 "inputs": { 475 "content": "@body('storage_sasurl')", 476 "schema": { 477 "properties": { 478 "accessSAS": { 479 "type": "string" 480 } 481 }, 482 "type": "object" 483 } 484 }, 485 "runAfter": { 486 "storage_sasurl": [ 487 "Succeeded" 488 ] 489 }, 490 "type": "ParseJson" 491 }, 492 "storage_sasurl": { 493 "inputs": { 494 "body": { 495 "access": "Read", 496 "durationInSeconds": 3600 497 }, 498 "host": { 499 "connection": { 500 "name": "@parameters('$connections')['arm']['connectionId']" 501 } 502 }, 503 "method": "post", 504 "path": "/subscriptions/@{encodeURIComponent('c04b3c63-8dfe-4f98-be18-e71ff67a1f4e')}/resourcegroups/@{encodeURIComponent('eventgrid')}/providers/@{encodeURIComponent('Microsoft.Compute')}/@{encodeURIComponent(concat('disks/', outputs('Compose_3')?['name']))}/@{encodeURIComponent('beginGetAccess')}", 505 "queries": { 506 "x-ms-api-version": "2017-03-30" 507 } 508 }, 509 "runAfter": { 510 "deallocate_vm": [ 511 "Succeeded" 512 ] 513 }, 514 "type": "ApiConnection" 515 } 516 }, 517 "contentVersion": "1.0.0.0", 518 "outputs": {}, 519 "parameters": { 520 "$connections": { 521 "defaultValue": {}, 522 "type": "Object" 523 } 524 }, 525 "triggers": { 526 "When_a_resource_event_occurs": { 527 "inputs": { 528 "body": { 529 "properties": { 530 "destination": { 531 "endpointType": "webhook", 532 "properties": { 533 "endpointUrl": "@{listCallbackUrl()}" 534 } 535 }, 536 "filter": { 537 "includedEventTypes": [ 538 "Microsoft.Resources.ResourceWriteSuccess" 539 ], 540 "subjectBeginsWith": "/subscriptions/c04b3c63-8dfe-4f98-be18-e71ff67a1f4e/resourcegroups/eventgrid/providers/Microsoft.Compute/virtualMachines" 541 }, 542 "topic": "/subscriptions/c04b3c63-8dfe-4f98-be18-e71ff67a1f4e/resourceGroups/eventgrid" 543 } 544 }, 545 "host": { 546 "connection": { 547 "name": "@parameters('$connections')['azureeventgrid']['connectionId']" 548 } 549 }, 550 "path": "/subscriptions/@{encodeURIComponent('c04b3c63-8dfe-4f98-be18-e71ff67a1f4e')}/providers/@{encodeURIComponent('Microsoft.Resources.ResourceGroups')}/resource/eventSubscriptions", 551 "queries": { 552 "x-ms-api-version": "2017-09-15-preview" 553 } 554 }, 555 "splitOn": "@triggerBody()", 556 "type": "ApiConnectionWebhook" 557 } 558 } 559 } 560 }
参考资料:
因为 EventGrid 和 LogicApp 学习成本都非常低,所以没有 Step by Step 给大家说明操作步骤,这里为了方便大家学习,给大家列举几个文档,帮助大家快速上手。
EventGrid 入门:https://docs.microsoft.com/en-us/azure/event-grid/overview
EventGrid 消息格式:https://docs.microsoft.com/en-us/azure/event-grid/event-schema
LogicApp 入门:https://docs.microsoft.com/en-us/azure/logic-apps/logic-apps-overview
LogicApp Connector 手册:https://docs.microsoft.com/en-us/connectors/
LogicApp 内置函数手册:https://docs.microsoft.com/en-us/azure/logic-apps/workflow-definition-language-functions-reference