PowerShell:如何使用 Packer 在 Azure 中创建虚拟机映像PowerShell: How to use Packer to create virtual machine images in Azure

Azure 中的每个虚拟机 (VM) 都是基于定义 Windows 分发和操作系统版本的映像创建的。Each virtual machine (VM) in Azure is created from an image that defines the Windows distribution and OS version. 映像可以包括预安装的应用程序和配置。Images can include pre-installed applications and configurations. Azure 市场为最常见的操作系统和应用程序环境提供许多第一和第三方映像,或者也可创建满足自身需求的自定义映像。The Azure Marketplace provides many first and third-party images for most common OS' and application environments, or you can create your own custom images tailored to your needs. 本文详细介绍了如何使用开源工具 Packer 在 Azure 中定义和生成自定义映像。This article details how to use the open-source tool Packer to define and build custom images in Azure.

本文最后一次使用 Packer 版本 1.6.1 于 2020 年 8 月 5 日进行了测试。This article was last tested on 8/5/2020 using Packer version 1.6.1.

创建 Azure 资源组Create Azure resource group

生成过程中,Packer 将在生成源 VM 时创建临时 Azure 资源。During the build process, Packer creates temporary Azure resources as it builds the source VM. 要捕获该源 VM 用作映像,必须定义资源组。To capture that source VM for use as an image, you must define a resource group. Packer 生成过程的输出存储在此资源组中。The output from the Packer build process is stored in this resource group.

使用 New-AzResourceGroup 创建资源组。Create a resource group with New-AzResourceGroup. 以下示例在“chinaeast”位置创建名为“myPackerGroup”的资源组:The following example creates a resource group named myPackerGroup in the chinaeast location:

$rgName = "myPackerGroup"
$location = "China East 2"
New-AzResourceGroup -Name $rgName -Location $location

创建 Azure 凭据Create Azure credentials

使用服务主体通过 Azure 对 Packer 进行身份验证。Packer authenticates with Azure using a service principal. Azure 服务主体是可与应用、服务和自动化工具(如 Packer)结合使用的安全性标识。An Azure service principal is a security identity that you can use with apps, services, and automation tools like Packer. 用户控制和定义服务主体可在 Azure 中执行的操作的权限。You control and define the permissions as to what operations the service principal can perform in Azure.

使用 New-AzADServicePrincipal 创建服务主体。Create a service principal with New-AzADServicePrincipal. -DisplayName 的值必须唯一;请根据需要将其替换为你自己的值。The value for -DisplayName needs to be unique; replace with your own value as needed.

$sp = New-AzADServicePrincipal -DisplayName "PackerSP$(Get-Random)"
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($sp.Secret)
$plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

然后输出密码和应用程序 ID。Then output the password and application ID.

$plainPassword
$sp.ApplicationId

若要向 Azure 进行身份验证,还需使用 Get-AzSubscription 获取 Azure 租户和订阅 ID:To authenticate to Azure, you also need to obtain your Azure tenant and subscription IDs with Get-AzSubscription:

Get-AzSubscription

定义 Packer 模板Define Packer template

若要生成映像,请创建一个模板作为 JSON 文件。To build images, you create a template as a JSON file. 在模板中,定义执行实际生成过程的生成器和设置程序。In the template, you define builders and provisioners that carry out the actual build process. Packer 具有用于 Azure 的生成器,可用于定义 Azure 资源,如在前面创建的服务主体凭据。Packer has a builder for Azure that allows you to define Azure resources, such as the service principal credentials created in the preceding step.

创建名为 windows.json 的文件并粘贴以下内容。Create a file named windows.json and paste the following content. 为以下内容输入自己的值:Enter your own values for the following:

参数Parameter 获取位置Where to obtain
client_idclient_id 通过 $sp.applicationId 查看服务主体 IDView service principal ID with $sp.applicationId
client_secretclient_secret 使用 $plainPassword 查看自动生成的密码View the auto-generated password with $plainPassword
tenant_idtenant_id $sub.TenantId 命令的输出Output from $sub.TenantId command
subscription_idsubscription_id $sub.SubscriptionId 命令的输出Output from $sub.SubscriptionId command
managed_image_resource_group_namemanaged_image_resource_group_name 在第一步中创建的资源组的名称Name of resource group you created in the first step
managed_image_namemanaged_image_name 创建的托管磁盘映像的名称Name for the managed disk image that is created
cloud_environment_namecloud_environment_name 部署目标 Azure 云的名称。Name for the deployment target Azure Cloud. 有效选项在 Public, China, Germany, or USGovernment 列表中the valid choice is in list of Public, China, Germany, or USGovernment
{
  "builders": [{
    "type": "azure-arm",

    "client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
    "client_secret": "ppppppp-pppp-pppp-pppp-ppppppppppp",
    "tenant_id": "zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz",
    "subscription_id": "yyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyy",

    "managed_image_resource_group_name": "myPackerGroup",
    "managed_image_name": "myPackerImage",

    "os_type": "Windows",
    "image_publisher": "MicrosoftWindowsServer",
    "image_offer": "WindowsServer",
    "image_sku": "2016-Datacenter",

    "communicator": "winrm",
    "winrm_use_ssl": true,
    "winrm_insecure": true,
    "winrm_timeout": "5m",
    "winrm_username": "packer",

    "azure_tags": {
        "dept": "Engineering",
        "task": "Image deployment"
    },

    "cloud_environment_name": "China",
    "location": "China East 2",
    "vm_size": "Standard_D2_v2"
  }],
  "provisioners": [{
    "type": "powershell",
    "inline": [
      "Add-WindowsFeature Web-Server",
      "while ((Get-Service RdAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
      "while ((Get-Service WindowsAzureGuestAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
      "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit",
      "while($true) { $imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; if($imageState.ImageState -ne 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { Write-Output $imageState.ImageState; Start-Sleep -s 10  } else { break } }"
    ]
  }]
}

此模板生成 Windows Server 2016 Datacenter 并安装 IIS,然后使用 Sysprep 来通用化该 VM。This template builds a Windows Server 2016 Datacenter, installs IIS, then generalizes the VM with Sysprep. IIS 安装展示了如何使用 PowerShell 预配程序来运行其他命令。The IIS install shows how you can use the PowerShell provisioner to run additional commands. 最终的 Packer 映像包括必需的软件安装和配置。The final Packer image then includes the required software install and configuration.

Windows 来宾代理参与 Sysprep 过程。The Windows Guest Agent participates in the Sysprep process. 必须先完全安装代理,然后才能对 VM 执行 sysprep。The agent must be fully installed before the VM can be sysprep'ed. 若要确保这一点,则在执行 sysprep.exe 之前,所有代理服务必须都在运行。To ensure that this is true, all agent services must be running before you execute sysprep.exe. 上述 JSON 代码片段显示了在 PowerShell 配置程序中执行此操作的一种方法。The preceding JSON snippet shows one way to do this in the PowerShell provisioner. 仅当将 VM 配置为安装代理(默认设置)时,才需要此代码片段。This snippet is required only if the VM is configured to install the agent, which is the default.

生成 Packer 映像Build Packer image

如果本地计算机上尚未安装 Packer,请按照 Packer 安装说明进行安装。If you don't already have Packer installed on your local machine, follow the Packer installation instructions.

按如下所述打开 cmd 提示并指定 Packer 模板文件,以便生成映像:Build the image by opening a cmd prompt and specifying your Packer template file as follows:

./packer build windows.json

前面命令的输出示例如下所示:An example of the output from the preceding commands is as follows:

azure-arm output will be in this color.

==> azure-arm: Running builder ...
    azure-arm: Creating Azure Resource Manager (ARM) client ...
==> azure-arm: Creating resource group ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-pq0mthtbtt'
==> azure-arm:  -> Location          : 'China East'
==> azure-arm:  -> Tags              :
==> azure-arm:  ->> task : Image deployment
==> azure-arm:  ->> dept : Engineering
==> azure-arm: Validating deployment template ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-pq0mthtbtt'
==> azure-arm:  -> DeploymentName    : 'pkrdppq0mthtbtt'
==> azure-arm: Deploying deployment template ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-pq0mthtbtt'
==> azure-arm:  -> DeploymentName    : 'pkrdppq0mthtbtt'
==> azure-arm: Getting the certificate's URL ...
==> azure-arm:  -> Key Vault Name        : 'pkrkvpq0mthtbtt'
==> azure-arm:  -> Key Vault Secret Name : 'packerKeyVaultSecret'
==> azure-arm:  -> Certificate URL       : 'https://pkrkvpq0mthtbtt.vault.azure.cn/secrets/packerKeyVaultSecret/8c7bd823e4fa44e1abb747636128adbb'
==> azure-arm: Setting the certificate's URL ...
==> azure-arm: Validating deployment template ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-pq0mthtbtt'
==> azure-arm:  -> DeploymentName    : 'pkrdppq0mthtbtt'
==> azure-arm: Deploying deployment template ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-pq0mthtbtt'
==> azure-arm:  -> DeploymentName    : 'pkrdppq0mthtbtt'
==> azure-arm: Getting the VM's IP address ...
==> azure-arm:  -> ResourceGroupName   : 'packer-Resource-Group-pq0mthtbtt'
==> azure-arm:  -> PublicIPAddressName : 'packerPublicIP'
==> azure-arm:  -> NicName             : 'packerNic'
==> azure-arm:  -> Network Connection  : 'PublicEndpoint'
==> azure-arm:  -> IP Address          : '40.76.55.35'
==> azure-arm: Waiting for WinRM to become available...
==> azure-arm: Connected to WinRM!
==> azure-arm: Provisioning with Powershell...
==> azure-arm: Provisioning with shell script: /var/folders/h1/ymh5bdx15wgdn5hvgj1wc0zh0000gn/T/packer-powershell-provisioner902510110
    azure-arm: #< CLIXML
    azure-arm:
    azure-arm: Success Restart Needed Exit Code      Feature Result
    azure-arm: ------- -------------- ---------      --------------
    azure-arm: True    No             Success        {Common HTTP Features, Default Document, D...
    azure-arm: <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>
==> azure-arm: Querying the machine's properties ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-pq0mthtbtt'
==> azure-arm:  -> ComputeName       : 'pkrvmpq0mthtbtt'
==> azure-arm:  -> Managed OS Disk   : '/subscriptions/guid/resourceGroups/packer-Resource-Group-pq0mthtbtt/providers/Microsoft.Compute/disks/osdisk'
==> azure-arm: Powering off machine ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-pq0mthtbtt'
==> azure-arm:  -> ComputeName       : 'pkrvmpq0mthtbtt'
==> azure-arm: Capturing image ...
==> azure-arm:  -> Compute ResourceGroupName : 'packer-Resource-Group-pq0mthtbtt'
==> azure-arm:  -> Compute Name              : 'pkrvmpq0mthtbtt'
==> azure-arm:  -> Compute Location          : 'China East'
==> azure-arm:  -> Image ResourceGroupName   : 'myResourceGroup'
==> azure-arm:  -> Image Name                : 'myPackerImage'
==> azure-arm:  -> Image Location            : 'chinaeast'
==> azure-arm: Deleting resource group ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-pq0mthtbtt'
==> azure-arm: Deleting the temporary OS disk ...
==> azure-arm:  -> OS Disk : skipping, managed disk was used...
Build 'azure-arm' finished.

==> Builds finished. The artifacts of successful builds are:
--> azure-arm: Azure.ResourceManagement.VMImage:

ManagedImageResourceGroupName: myResourceGroup
ManagedImageName: myPackerImage
ManagedImageLocation: chinaeast

Packer 需要几分钟时间来生成 VM、运行设置程序并清理部署。It takes a few minutes for Packer to build the VM, run the provisioners, and clean up the deployment.

基于 Packer 映像创建 VMCreate a VM from the Packer image

现在可以使用 New-AzVM 从映像创建 VM。You can now create a VM from your Image with New-AzVM. 如果提供支持的网络资源尚不存在,则会创建这些资源。The supporting network resources are created if they do not already exist. 出现提示时,输入要在 VM 上创建的管理用户名和密码。When prompted, enter an administrative username and password to be created on the VM. 以下示例基于 myPackerImage 创建一个名为 myVM 的 VM。The following example creates a VM named myVM from myPackerImage:

New-AzVm `
    -ResourceGroupName $rgName `
    -Name "myVM" `
    -Location $location `
    -VirtualNetworkName "myVnet" `
    -SubnetName "mySubnet" `
    -SecurityGroupName "myNetworkSecurityGroup" `
    -PublicIpAddressName "myPublicIpAddress" `
    -OpenPorts 80 `
    -Image "myPackerImage"

如果你希望在与 Packer 映像不同的资源组或区域中创建虚拟机,请指定映像 ID 而不是映像名称。If you wish to create VMs in a different resource group or region than your Packer image, specify the image ID rather than image name. 可以使用 Get-AzImage 获取映像 ID。You can obtain the image ID with Get-AzImage.

基于 Packer 映像创建 VM 需要几分钟时间。It takes a few minutes to create the VM from your Packer image.

测试 VM 和 Web 服务器Test VM and webserver

使用 Get-AzPublicIPAddress 获取 VM 的公共 IP 地址。Obtain the public IP address of your VM with Get-AzPublicIPAddress. 以下示例获取前面创建的“myPublicIP” 的 IP 地址:The following example obtains the IP address for myPublicIP created earlier:

Get-AzPublicIPAddress `
    -ResourceGroupName $rgName `
    -Name "myPublicIPAddress" | select "IpAddress"

若要在运行中查看你的 VM,包括 Packer 预配程序中的 IIS 安装,请在 Web 浏览器中输入公共 IP 地址。To see your VM, that includes the IIS install from the Packer provisioner, in action, enter the public IP address in to a web browser.

IIS 默认站点