康威,资深工程师,SDN 和大规模数据中心网络专家

最近 AZURE 发布了 ARM 模式(Azure Resource Manager),其中a’a包含了对于Resource Manager Template的支持。什么是 Resource Template?我们先来看一下 AZURE 在 ARM 模式下的逻辑架构:

对于首次接触公有云的小伙伴,通常会习惯通过 Azure Portal 门户来完成相关服务及资源的配置交付(如创建虚拟云主机,虚拟网络,MySql 数据库服务等),我们可以把以上创建的服务及资源抽象为一套配置模板,用于描述所创建的服务及资源。由上面的图示可见,ASM 模式下用户与 Azure 云平台交互界面的接口包括 Portal,CLI,Visual Studio 等,通过以上接口用户给出的服务及资源的描述,Azure 平台通过资源描述中间件(Resource Provider Contract)将客户的描述转译为平台资源描述并执行服务及资源创建。Azure Portal 通过交互式的 GUI 方式可以友好方便的帮助用户上手,但是在批量及复杂服务及资源创建时就有些捉襟见肘。

那么在批量及复杂服务及资源创建时有没有好的解决办法呢?Infrastructure As Code,脚本部署是可行的方案之一,类似所有自动化事务的执行,我们可以将复杂的服务及资源逐一创建并通过脚本实现自动化执行。但是本文在这里不推荐大家使用上述方法,原因有二:

  1. 服务及资源的抽象不够,我们在定义服务及资源的时候希望通过更高级的抽象给出简单的接口,以便更便捷的定义复杂的服务及资源;
  2. 传统的脚本语言的执行以串行顺序方式执行,对于复杂的服务及资源定义如果希望实现并行及依赖关系定义是比较复杂的。

这里给大家介绍的 Azure Resource Template 可以很好的解决上述问题,帮助大家简单快捷的实现复杂的服务及资源交付,它通过 JSON 语言高级抽象描述资源定义大大降低了用户的复杂度,同时将并行任务的拆解交给 Azure 平台资源描述中间件来实现高效,实现 Infrastructure as Code。为方便大家理解,本文后面的内容按照如下顺序:

  • 工具方法介绍
  • 举例快速开始
  • 总结及课题延展

工具方法介绍:

工欲善其事必先利其器,得力顺手的工具可以让我们事半功倍。Infrastructure as Code,和其他编程语言一样,一个好的 IDE 可以为我们保驾护航。MSFT 为 Azure Resource Template 提供了 Visual Studio + Azure SDK 的工具组合,使用户可以在 VS 环境下实现开发。主要功能如下:

  1. 格式检查。帮助用户检查 JSON 格式;
  2. 基础模板。微软已经将 AZURE 服务及资源的标准模板定义好,方便客户调运。另外 Github 上还有海量的模板示例作为参考示例(https://github.com/Azure/azure-quickstart-templates),VS已经跟 Github 做了打通,在创建新项目的时候可以直接引用 Github 的模板,并且模板可以直接通过 VS 调用 Powershell 进行执行;
  3. 自动补全及函数库查询。在 VS 中可以帮助用户做索引、函数的补全。

VS 的安装与设置这里不做赘述,大家可以参加如下文章进行配置安装:

https://github.com/Microsoft/HealthClinic.biz/wiki/Deploy-and-manage-Azure-resources

https://www.azure.cn/documentation/articles/developerdifferences/#confdevcomp (中国区 AZURE 的连接需要修改VS的配置文件,可以参考此链接,如果使用 Global Azure 可以忽略)。

举例快速开始

开始之前我们先来设定一个目标场景,在 Azure Portal 上目前我们无法创建多网卡的虚拟机,我们的目标就是通过 Template 部署来实现多网卡虚拟机的交付,同时为了实现模板的复用,希望用户可以自定义操作系统类型,VM 的规格,以及网卡的数量。

1.打开 Visual Studio,创建项目,选择 Cloud -> Azure 资源组

选择 100-blank-template 空白模板(此示例里面使用空白模板进行介绍,小伙伴们也可以在此处直接选用内置模板及 Github 上的模板作为基础模板进行修改)

2.开始编写模板,点击 azuredeploy.json

大家可以看到基础的模板框架已经出现:

{

“$schema”: “https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#”,

“contentVersion”: “1.0.0.0”,

“parameters”: {

},

“variables”: {

},

“resources”: [

],

“outputs”: {

}

}

Azure Resource Template 模板主要分为4部分:

参数(parameters为定义模板的通配性,可以定义可配置变量,用户在部署模板时候可以更改变量参数实现自定义。以我们要完成的模板为例,可以通过参数来实现用户自定义 VM 规格、VM 操作系统、网卡数量,从而模板实现通用化,便于用户交付不同要求的资源。

变量(variables类似编程语言中的环境变量,方便用户对参数值的引用。如定义 VM 的操作系统类型,通过命名友好可读的名称,方便在资源编写时进行调用。

资源(resources– Infrastructure as Code 的主体,所有 Azure 的服务及资源均通过 resource 来定义,通过定义中不同的参数描述实现不同的 Infrastructure 的部署。

输出(outputs模板部署完毕后可以给出输出,如环境访问方式,成功消息等。

在 VS 环境中,客户在编写资源的时候无需从零写起,可以通过快速添加的方式,VS 会直接将添加的资源基础模板引入的项目中,我们可以直接在上面进行参数的编辑修改即可。下面以添加一个虚拟机资源为例

在选择虚拟机模板后,向导问询输入虚拟机名称,存储账户,及虚拟网络。这里是VS环境下编写模板相较其他 TEXT Editor 的优势之一,帮用户自动创建依赖关系。在 Azure 上的资源可能与其他资源存在依赖关系,如当我们想创建一个虚拟机的时候,我们需要依赖与存储账号资源,虚拟网络资源。当我们创建虚拟机资源的时候,VS 会自动将依赖关系的资源模板进行引入,方便客户开发。

在向导中输入依赖资源的相关参数后,模板主体 Resource 部分,除去 VirtualMachine 部分外还出现了StorageAccountVirtualNetworkNetworkNic 资源定义。

“resources”: [

{

“name”: “[variables(‘DemoStorageAccountName’)]”,

“type”: “Microsoft.Storage/storageAccounts”,

“location”: “[resourceGroup().location]”,

“apiVersion”: “2016-01-01”,

“sku”: {

“name”: “[parameters(‘DemoStorageAccountType’)]”

},

“dependsOn”: [ ],

“tags”: {

“displayName”: “DemoStorageAccount”

},

“kind”: “Storage”

},

{

“name”: “demoVnet”,

“type”: “Microsoft.Network/virtualNetworks”,

“location”: “[resourceGroup().location]”,

“apiVersion”: “2016-03-30”,

“dependsOn”: [ ],

“tags”: {

“displayName”: “demoVnet”

},

“properties”: {

“addressSpace”: {

“addressPrefixes”: [

“[variables(‘demoVnetPrefix’)]”

]

},

“subnets”: [

{

“name”: “[variables(‘demoVnetSubnet1Name’)]”,

“properties”: {

“addressPrefix”: “[variables(‘demoVnetSubnet1Prefix’)]”

}

},

{

“name”: “[variables(‘demoVnetSubnet2Name’)]”,

“properties”: {

“addressPrefix”: “[variables(‘demoVnetSubnet2Prefix’)]”

}

}

]

}

},

{

“name”: “[variables(‘DemoVirtualMachineNicName’)]”,

“type”: “Microsoft.Network/networkInterfaces”,

“location”: “[resourceGroup().location]”,

“apiVersion”: “2016-03-30”,

“dependsOn”: [

“[resourceId(‘Microsoft.Network/virtualNetworks’, ‘demoVnet’)]”

],

“tags”: {

“displayName”: “DemoVirtualMachineNic”

},

“properties”: {

“ipConfigurations”: [

{

“name”: “ipconfig1”,

“properties”: {

“privateIPAllocationMethod”: “Dynamic”,

“subnet”: {

“id”: “[variables(‘DemoVirtualMachineSubnetRef’)]”

}

}

}

]

}

},

{

“name”: “[parameters(‘DemoVirtualMachineName’)]”,

“type”: “Microsoft.Compute/virtualMachines”,

“location”: “[resourceGroup().location]”,

“apiVersion”: “2015-06-15”,

“dependsOn”: [

“[resourceId(‘Microsoft.Storage/storageAccounts’, variables(‘DemoStorageAccountName’))]”,

“[resourceId(‘Microsoft.Network/networkInterfaces’, variables(‘DemoVirtualMachineNicName’))]”

],

“tags”: {

“displayName”: “DemoVirtualMachine”

},

“properties”: {

“hardwareProfile”: {

“vmSize”: “[variables(‘DemoVirtualMachineVmSize’)]”

},

“osProfile”: {

“computerName”: “[parameters(‘DemoVirtualMachineName’)]”,

“adminUsername”: “[parameters(‘DemoVirtualMachineAdminUsername’)]”,

“adminPassword”: “[parameters(‘DemoVirtualMachineAdminPassword’)]”

},

“storageProfile”: {

“imageReference”: {

“publisher”: “[variables(‘DemoVirtualMachineImagePublisher’)]”,

“offer”: “[variables(‘DemoVirtualMachineImageOffer’)]”,

“sku”: “[parameters(‘DemoVirtualMachineUbuntuOSVersion’)]”,

“version”: “latest”

},

“osDisk”: {

“name”: “DemoVirtualMachineOSDisk”,

“vhd”: {

“uri”: “[concat(reference(resourceId(‘Microsoft.Storage/storageAccounts’, variables(‘DemoStorageAccountName’)), ‘2016-01-01’).primaryEndpoints.blob, variables(‘DemoVirtualMachineStorageAccountContainerName’), ‘/’, variables(‘DemoVirtualMachineOSDiskName’), ‘.vhd’)]”

},

“caching”: “ReadWrite”,

“createOption”: “FromImage”

}

},

“networkProfile”: {

“networkInterfaces”: [

{

“id”: “[resourceId(‘Microsoft.Network/networkInterfaces’, variables(‘DemoVirtualMachineNicName’))]”

}

]

}

}

}

],

在 AZURE 中创建一个虚拟机需要将系统盘,网卡关联到虚拟机上,网卡需要连接的虚拟网络上。通过以上的过程我们可以看到,相关的依赖关系通过 VS 工具已经自动帮助我们完成。依赖关系在模板中非常重要,因为依赖关系定义的资源创建的先后顺序,从而保证资源的顺利创建。在上述模板中 Virtual Machine 我们可以看到 depends On 部分就是来描述依赖关系的

{

“name”: “[parameters(‘DemoVirtualMachineName’)]”,

“type”: “Microsoft.Compute/virtualMachines”,

“location”: “[resourceGroup().location]”,

“apiVersion”: “2015-06-15”,

“dependsOn”: [

“[resourceId(‘Microsoft.Storage/storageAccounts’, variables(‘DemoStorageAccountName’))]”, #定义虚拟机资源依赖于存储账号资源

“[resourceId(‘Microsoft.Network/networkInterfaces’, variables(‘DemoVirtualMachineNicName’))]” #定义虚拟机依赖于虚拟网络资源

],

到此我们已经将一个虚拟机创建的基本模板框架搭建完毕,后面我们都基于这个框架来进行修改来达成我们之前预设的目标,通过 Template 部署来实现多网卡虚拟机的交付,同时为了实现模板的复用,希望用户可以自定义操作系统类型,VM 的规格,以及网卡的数量。下面我们先给出最终的实现模板,然后展开介绍,后面的介绍主要侧重一些特殊语法的说明,以便大家可以自行编排自己的模板。

{

“$schema”: “https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#”,

“contentVersion”: “1.0.0.0”,

“parameters”: {

“adminUsername”: {

“type”: “string”,

“metadata”: {

“description”: “User name for the Virtual Machine.”

}

},

“adminPassword”: {

“type”: “securestring”,

“metadata”: {

“description”: “Password for the Virtual Machine.”

}

},

“dnsLabelPrefix”: {

“type”: “string”,

“metadata”: {

“description”: “Unique DNS Name for the Public IP used to access the Virtual Machine.”

}

},

“LinuxPublisher”: {

“type”: “string”,

“allowedValues”: [ “Ubuntu”, “CentOS” ]

},

“ubuntuOSVersion”: {

“type”: “string”,

“defaultValue”: “16.04.0-LTS”,

“allowedValues”: [

“12.04.5-LTS”,

“14.04.5-LTS”,

“15.10”,

“16.04.0-LTS”

],

“metadata”: {

“description”: “The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version.”

}

},

“centOSVersion”: {

“type”: “string”,

“defaultValue”: “7.3”,

“allowedValues”: [

“6.9”,

“7.1”,

“7.2”,

“7.3”

],

“metadata”: {

“description”: “The CentOS version for the VM. This will pick a fully patched image of this given CentOS version.”

}

},

“Standard_A2”: {

“type”: “int”,

“defaultValue”: 2,

“minValue”: 1,

“maxValue”: 2,

“allowedValues”: [ 1, 2 ]

},

“Standard_D3_v2”: {

“type”: “int”,

“defaultValue”: 4,

“minValue”: 1,

“maxValue”: 4,

“allowedValues”: [ 1, 2, 3, 4 ]

},

“vmSize”: {

“type”: “string”,

“defaultValue”: “Standard_A2”,

“allowedValues”: [

“Standard_A2”,

“Standard_D3_v2”

]

}

},

“variables”: {

“linuxtype”: “[variables(concat(‘linux’, parameters(‘LinuxPublisher’)))]”,

“linuxUbuntu”: {

“imagePublisher”: “Canonical”,

“imageOffer”: “UbuntuServer”,

“OSVersion”: “[parameters(‘ubuntuOSVersion’)]”,

“VMsize”: “[parameters(‘vmSize’)]”,

“NicNumber”: “[parameters(parameters(‘vmSize’))]”

},

“linuxCentOS”: {

“imagePublisher”: “OpenLogic”,

“imageOffer”: “CentOS”,

“OSVersion”: “[parameters(‘centOSVersion’)]”,

“VMsize”: “[parameters(‘vmSize’)]”,

“NicNumber”: “[parameters(parameters(‘vmSize’))]”

},

“storageAccountName”: “[concat(‘demo’,uniquestring(resourceGroup().id))]”,

“nicName”: “DemoVMNic”,

“addressPrefix”: “10.0.0.0/16”,

“subnetName”: “Subnet”,

“subnetPrefix”: “10.0.0.0/24”,

“storageAccountType”: “Standard_LRS”,

“publicIPAddressName”: “DemoPublicIP”,

“publicIPAddressType”: “Dynamic”,

“vmName”: “DemoLinuxVM”,

“virtualNetworkName”: “DemoVNET”,

“vnetID”: “[resourceId(‘Microsoft.Network/virtualNetworks’,variables(‘virtualNetworkName’))]”

},

“resources”: [

{

“type”: “Microsoft.Storage/storageAccounts”,

“name”: “[variables(‘storageAccountName’)]”,

“apiVersion”: “2017-06-01”,

“location”: “[resourceGroup().location]”,

“sku”: {

“name”: “[variables(‘storageAccountType’)]”

},

“kind”: “Storage”,

“properties”: {}

},

{

“apiVersion”: “2017-04-01”,

“type”: “Microsoft.Network/publicIPAddresses”,

“name”: “[variables(‘publicIPAddressName’)]”,

“location”: “[resourceGroup().location]”,

“properties”: {

“publicIPAllocationMethod”: “[variables(‘publicIPAddressType’)]”,

“dnsSettings”: {

“domainNameLabel”: “[parameters(‘dnsLabelPrefix’)]”

}

}

},

{

“apiVersion”: “2017-04-01”,

“type”: “Microsoft.Network/virtualNetworks”,

“name”: “[variables(‘virtualNetworkName’)]”,

“location”: “[resourceGroup().location]”,

“properties”: {

“addressSpace”: {

“addressPrefixes”: [

“[variables(‘addressPrefix’)]”

]

},

“copy”: [

{

“name”: “subnets”,

“count”: “[variables(‘linuxtype’).NicNumber]”,

“input”: {

“name”: “[concat(variables(‘subnetName’),copyIndex(‘subnets’))]”,

“properties”: {

“addressPrefix”: “[concat(‘10.0.’,copyIndex(‘subnets’),’.0/24′)]”

}

}

}

]

}

},

{

“apiVersion”: “2017-04-01”,

“type”: “Microsoft.Network/networkInterfaces”,

“name”: “[concat(variables(‘nicName’),’0′)]”,

“location”: “[resourceGroup().location]”,

“dependsOn”: [

“[resourceId(‘Microsoft.Network/publicIPAddresses/’, variables(‘publicIPAddressName’))]”,

“[resourceId(‘Microsoft.Network/virtualNetworks/’, variables(‘virtualNetworkName’))]”

],

“properties”: {

“ipConfigurations”: [

{

“name”: “ipconfig1”,

“properties”: {

“privateIPAllocationMethod”: “Dynamic”,

“publicIPAddress”: {

“id”: “[resourceId(‘Microsoft.Network/publicIPAddresses’,variables(‘publicIPAddressName’))]”

},

“subnet”: {

“id”: “[concat(variables(‘vnetID’),’/subnets/’,variables(‘subnetName’),’0′)]”

}

}

}

]

}

},

{

“apiVersion”: “2017-04-01”,

“condition”: “[greater(variables(‘linuxtype’).NicNumber,1)]”,

“type”: “Microsoft.Network/networkInterfaces”,

“name”: “[concat(variables(‘nicName’),copyIndex(1))]”,

“location”: “[resourceGroup().location]”,

“copy”: {

“name”: “nicloop”,

“count”: “[sub(variables(‘linuxtype’).NicNumber,1)]”

},

“dependsOn”: [

“[resourceId(‘Microsoft.Network/publicIPAddresses/’, variables(‘publicIPAddressName’))]”,

“[resourceId(‘Microsoft.Network/virtualNetworks/’, variables(‘virtualNetworkName’))]”

],

“properties”: {

“ipConfigurations”: [

{

“name”: “ipconfig1”,

“properties”: {

“privateIPAllocationMethod”: “Dynamic”,

“subnet”: {

“id”: “[concat(variables(‘vnetID’),’/subnets/’,variables(‘subnetName’),copyIndex(1))]”

}

}

}

]

}

},

{

“apiVersion”: “2017-03-30”,

“type”: “Microsoft.Compute/virtualMachines”,

“name”: “[variables(‘vmName’)]”,

“location”: “[resourceGroup().location]”,

“dependsOn”: [

“[resourceId(‘Microsoft.Storage/storageAccounts/’, variables(‘storageAccountName’))]”

],

“properties”: {

“hardwareProfile”: {

“vmSize”: “[variables(‘linuxtype’).VMsize]”

},

“osProfile”: {

“computerName”: “[variables(‘vmName’)]”,

“adminUsername”: “[parameters(‘adminUsername’)]”,

“adminPassword”: “[parameters(‘adminPassword’)]”

},

“storageProfile”: {

“imageReference”: {

“publisher”: “[variables(‘linuxtype’).imagePublisher]”,

“offer”: “[variables(‘linuxtype’).imageOffer]”,

“sku”: “[variables(‘linuxtype’).OSVersion]”,

“version”: “latest”

},

“osDisk”: {

“name”: “osdisk”,

“vhd”: {

“uri”: “[concat(‘http://’, variables(‘storageAccountName’), ‘.blob.core.windows.net/vhds/’, ‘osdisk’, ‘.vhd’)]”

},

“createOption”: “FromImage”

}

},

“networkProfile”: {

“copy”: [

{

“name”: “networkInterfaces”,

“count”: “[variables(‘linuxtype’).NicNumber]”,

“input”: {

“id”: “[replace(reference(concat(variables(‘nicName’),copyIndex(‘networkInterfaces’))).ipConfigurations[0].id,’/ipConfigurations/ipconfig1′,”)]”,

“properties”: {

“primary”: “[less(copyIndex(‘networkInterfaces’),1)]”

}

}

}

]

}

}

}

],

“outputs”: {

“result”: {

“type”: “string”,

“value”: “success”

}

}

}

对于操作系统类型的定义可通过定义参数完成,通过 allowedValues 的定义可以在最终使用模板时实现在交互页面进行参数选择以避免手工输入带来的错误。同理操作系统版本,VM 规格以及网卡数量均可采用类似方法定义。

“LinuxPublisher”: {

“type”: “string”,

“allowedValues”: [ “Ubuntu”, “CentOS” ]

},

变量套用式引用,在 Resource 模板定义中会通过调用参数或变量的方式来实现模板的通用。在此用例中目标创建的虚拟机的操作系统由于有客户变量来进行选定,所以在虚拟机资源描述部分里面设计的操作系统的相关描述都需要随之而变。因为 Ubuntu 和 CentOS 两个选项对应选项背后,相应的操作系统发行商,操作系统版本等参数都要随之改变,为了实现资源描述部分的通用性,避免反复写两次资源描述来覆盖不同操作系统场景,我们采用变量套用式引用。通过定义 linuxUbuntu 和 linuxCentOS 变量来分别描述不同操作系统下相关变量参数,同时定义 linuxtype 变量,其中 linuxtype 的值等于 linux+ 用户输入的 LinuxPublisher 参数选项,客户选定 LinuPublisher 选项后 linuxtype的最终值将等于 linuxUbuntu 或 linuxCentOS,在后面的虚拟机资源描述的操作系统部分只需要调用 linuxtype 变量对象及可实现对客户选定操作系统的变量值抽取,以“publisher”: “[variables(‘linuxtype’).imagePublisher]” 为例,通过对 linuxtype.imagePublisher 的抽取实现相应操作系统发布商信息的提取,而且整个资源描述只需要编写一次。

“linuxtype”: “[variables(concat(‘linux’, parameters(‘LinuxPublisher’)))]”,

“linuxUbuntu”: {

“imagePublisher”: “Canonical”,

“imageOffer”: “UbuntuServer”,

“OSVersion”: “[parameters(‘ubuntuOSVersion’)]”,

“VMsize”: “[parameters(‘vmSize’)]”,

“NicNumber”: “[parameters(parameters(‘vmSize’))]”

},

“linuxCentOS”: {

“imagePublisher”: “OpenLogic”,

“imageOffer”: “CentOS”,

“OSVersion”: “[parameters(‘centOSVersion’)]”,

“VMsize”: “[parameters(‘vmSize’)]”,

“NicNumber”: “[parameters(parameters(‘vmSize’))]”

},

循环描述 copy 可以实现多资源创建,在此例子中客户可以自定义创建网卡的数量并且每个网卡关联不同的子网,所以无法预先通过静态反复定义的方式将虚拟网络子网资源描述写死,通过 copy 描述配以客户输入的网卡数量NicNumber 变量作为循环次数 count,实现动态创建多个资源的目标。

{

“apiVersion”: “2017-04-01”,

“type”: “Microsoft.Network/virtualNetworks”,

“name”: “[variables(‘virtualNetworkName’)]”,

“location”: “[resourceGroup().location]”,

“properties”: {

“addressSpace”: {

“addressPrefixes”: [

“[variables(‘addressPrefix’)]”

]

},

“copy”: [

{

“name”: “subnets”,

“count”: “[variables(‘linuxtype’).NicNumber]”,

“input”: {

“name”: “[concat(variables(‘subnetName’),copyIndex(‘subnets’))]”,

“properties”: {

“addressPrefix”: “[concat(‘10.0.’,copyIndex(‘subnets’),’.0/24′)]”

}

}

}

]

}

},

使用条件语句实现 if or not 逻辑,在上述例子中虚拟机在多网卡场景下只有主网卡绑定公网 IP,在网卡资源描述中,需要公网地址的描述和不需要公网地址的描述是不同的,所以这里的做法是先静态描述一块带公网地址的网卡资源(因为客户至少创建 块网卡),在将剩余的网卡资源描述引入条件语句从而实现当客户输入大于 块网卡时(“condition”: “[greater(variables(‘linuxtype’).NicNumber,1)]”),剩余网卡都按照无公网地址方式进行创建。

{

“apiVersion”: “2017-04-01”,

“condition”: “[greater(variables(‘linuxtype’).NicNumber,1)]”,

“type”: “Microsoft.Network/networkInterfaces”,

“name”: “[concat(variables(‘nicName’),copyIndex(1))]”,

“location”: “[resourceGroup().location]”,

“copy”: {

“name”: “nicloop”,

“count”: “[sub(variables(‘linuxtype’).NicNumber,1)]”

},

“dependsOn”: [

“[resourceId(‘Microsoft.Network/publicIPAddresses/’, variables(‘publicIPAddressName’))]”,

“[resourceId(‘Microsoft.Network/virtualNetworks/’, variables(‘virtualNetworkName’))]”

],

“properties”: {

“ipConfigurations”: [

{

“name”: “ipconfig1”,

“properties”: {

“privateIPAllocationMethod”: “Dynamic”,

“subnet”: {

“id”: “[concat(variables(‘vnetID’),’/subnets/’,variables(‘subnetName’),copyIndex(1))]”

}

}

}

]

}

},

动态依赖关系的定义,通常在资源描述中通过 dependsOn 来表示依赖关系,如虚拟机资源需要依赖存储账户及网卡资源。但本例中网卡的数量是变量无法预知,所以在描述依赖关系的时候无法静态将所依赖的资源 ID 全部放上。这里采用调用reference函数实现隐性依赖关系的描述,在虚拟机资源的 dependsOn 描述中移除网卡的依赖描述,在虚拟机描述中的 networkProfile 部分中定义 id 时,通过 reference 函数抓取 IDreference 函数会在模板执行过程中抓取 runtime 的 id 值进行返回,只有当相应资源创建完毕时 reference 才会有返回值,从而实现资源依赖关系的隐性描述。

“networkProfile”: {

“copy”: [

{

“name”: “networkInterfaces”,

“count”: “[variables(‘linuxtype’).NicNumber]”,

“input”: {

“id”: “[replace(reference(concat(variables(‘nicName’),copyIndex(‘networkInterfaces’))).ipConfigurations[0].id,’/ipConfigurations/ipconfig1′,”)]”,

“properties”: {

“primary”: “[less(copyIndex(‘networkInterfaces’),1)]”

}

3.部署模板

至此通过本例中几个基本目标给大家介绍了在模板中常用的一些高级功能,这些功能有助于大家描述定义复杂的服务和资源,实现 Infrastructure as Code 的目标。最后模板一切就绪可以来部署了,Azure 支持通过在 PortalPowerShellCLI 中部署模板,其中 Powershell 可以直接在 VS 环境中调用,各种部署方法大家可以参阅如下链接:https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-template-deploy。本文介绍通过Portal的方式进行部署,以便帮助大家理解在模板中定义的参数 Parameter 部分。

打开 Azure Portal,搜索 Template Deployment;

点击 Create 创建,选择 Build your own template in the editor;

将在 VS 模式下编写的模板粘贴至 Portal 并保存;

进入参数 Parameter 输入页面,模板中参数 Parameter 部分定义的参数选项均出现在该页面下,客户可以自定义输入预期值,如操作系统类型,虚拟机大小,网卡数量等;

勾选确认条款点击 Purchase,模板即开始部署;

部署开始后可以在 Portal 中看到部署任务的实时状态,在未完成前 STATUS 为 Deploying 状态,Outputs 为空;

部署成功后,STATUS 变为 SucceededOutputs 部分给出模板中 Outputs 部分给出的定义描述(可查看前面例子中Outputs 部分的描述)。

总结及课题延展:

通过上述例子大家对 Azure Resource Template 应该已经有了一定的了解。有了这些了解大家可以着手开始自己的 Infrastructure as Code 的旅程,下面这些资源应该会帮到大家。

  1. Azure Resource Template 使用指南,基本使用方法帮助快速上手:https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview
  2. Azure Resource Template 最佳实践,介绍 Template 定义的一些常用方法提高效率:https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-template-best-practices
  3. Azure Resource Template 高级函数使用指南,介绍 Template 内置函数帮助实现复杂逻辑:https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-template-functions?toc=/azure/templates/toc.json&bc=/azure/templates/breadcrumb/toc.json
  4. Azure Resource Template 服务及资源原始模板参考,不知道每种服务及资源额如何定义描述时可作为字典参考:https://docs.microsoft.com/en-us/azure/templates/microsoft.analysisservices/servers
  5. Azure Resource Template Github,你想做的内容别人可能已经做了七八成,弯道超车搞起:https://github.com/Azure/azure-quickstart-templates
  6. Azure Resource Portal 查阅现有 Resource 的模板作为参考:https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-export-template

最后我们再来谈一谈延伸的话题,不少用户也许用过其他云平台的类似的模板工具,那么在多云场景下岂不是需要维护两套语言逻辑,这里简单提一下 Terraform,它是一个跨平台的模板语言工具,它可以适配如 AWSAZUREAlicloud 等云厂商提供的 template 接口,不仅如此,它还可以适配操作系统配置,应用配置等终端对象,其通过标准的模板描述语言抽象从而实现云平台无关以及更复杂的 Infrastructure as Code,有兴趣的话可以自行科学上网查询更多相关资料。

现在准备好手头的工具开始自己的 Infrastructure as Code 之旅吧。