PowerShell: Como utilizar o Packer para criar imagens de máquinas virtuais no Azure

Aplica-se a: ✔️ VMs do Windows

Cada máquina virtual (VM) no Azure é criada a partir de uma imagem que define a distribuição do Windows e a versão do SO. As imagens podem incluir aplicações e configurações pré-instaladas. O Azure Marketplace fornece muitas imagens de primeiro e terceiros para os ambientes mais comuns do SO e da aplicação ou pode criar as suas próprias imagens personalizadas adaptadas às suas necessidades. Este artigo detalha como utilizar a ferramenta open source Packer para definir e criar imagens personalizadas no Azure.

Este artigo foi testado pela última vez em 05/08/2020 com a versão 1.8.1 do Packer .

Nota

O Azure tem agora um serviço, o Azure Image Builder, para definir e criar as suas próprias imagens personalizadas. O Azure Image Builder baseia-se no Packer, pelo que pode até utilizar os scripts do aprovisionador de shell do Packer existentes com o mesmo. Para começar a utilizar o Azure Image Builder, veja Criar uma VM do Windows com o Azure Image Builder.

Criar um grupo de recursos do Azure

Durante o processo de compilação, o Packer cria recursos temporários do Azure à medida que cria a VM de origem. Para capturar essa VM de origem para utilização como imagem, tem de definir um grupo de recursos. O resultado do processo de compilação do Packer é armazenado neste grupo de recursos.

Crie um grupo de recursos com New-AzResourceGroup. O exemplo seguinte cria um grupo de recursos com o nome myPackerGroup na localização eastus :

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

Criar credenciais do Azure

O Packer autentica-se com o Azure através de um principal de serviço. Um principal de serviço do Azure é uma identidade de segurança que pode utilizar com aplicações, serviços e ferramentas de automatização, como o Packer. Pode controlar e definir as permissões sobre as operações que o principal de serviço pode realizar no Azure.

Crie um principal de serviço com New-AzADServicePrincipal. O valor para -DisplayName tem de ser exclusivo; substitua pelo seu próprio valor, conforme necessário.

$sp = New-AzADServicePrincipal -DisplayName "PackerPrincipal" -role Contributor -scope /subscriptions/yyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyy
$plainPassword = (New-AzADSpCredential -ObjectId $sp.Id).SecretText

Em seguida, produza a palavra-passe e o ID da aplicação.

$plainPassword
$sp.AppId

Para autenticar no Azure, também tem de obter os IDs de subscrição e inquilino do Azure com Get-AzSubscription:

$subName = "mySubscriptionName"
$sub = Get-AzSubscription -SubscriptionName $subName

Definir modelo do Packer

Para criar imagens, crie um modelo como um ficheiro JSON. No modelo, define construtores e aprovisionadores que executam o processo de compilação real. O Packer tem um construtor para o Azure que lhe permite definir recursos do Azure, como as credenciais do principal de serviço criadas no passo anterior.

Crie um ficheiro com o nome windows.json e cole o seguinte conteúdo. Introduza os seus próprios valores para o seguinte:

Parâmetro Onde obter
client_id Ver o ID do principal de serviço com $sp.AppId
client_secret Ver a palavra-passe gerada automaticamente com $plainPassword
tenant_id Saída do $sub.TenantId comando
subscription_id Saída do $sub.SubscriptionId comando
managed_image_resource_group_name Nome do grupo de recursos que criou no primeiro passo
managed_image_name Nome da imagem de disco gerido que é criada
{
  "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"
    },

    "build_resource_group_name": "myPackerGroup",
    "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 } }"
    ]
  }]
}

Também pode criar um ficheiro com o nome windows.pkr.hcl e colar o seguinte conteúdo com os seus próprios valores, conforme utilizado para a tabela de parâmetros acima.

source "azure-arm" "autogenerated_1" {
  azure_tags = {
    dept = "Engineering"
    task = "Image deployment"
  }
  build_resource_group_name         = "myPackerGroup"
  client_id                         = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx"
  client_secret                     = "ppppppp-pppp-pppp-pppp-ppppppppppp"
  communicator                      = "winrm"
  image_offer                       = "WindowsServer"
  image_publisher                   = "MicrosoftWindowsServer"
  image_sku                         = "2016-Datacenter"
  managed_image_name                = "myPackerImage"
  managed_image_resource_group_name = "myPackerGroup"
  os_type                           = "Windows"
  subscription_id                   = "yyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyy"
  tenant_id                         = "zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz"
  vm_size                           = "Standard_D2_v2"
  winrm_insecure                    = true
  winrm_timeout                     = "5m"
  winrm_use_ssl                     = true
  winrm_username                    = "packer"
}

build {
  sources = ["source.azure-arm.autogenerated_1"]

  provisioner "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 } }"]
  }

}

Este modelo cria uma VM Windows Server 2016, instala o IIS e, em seguida, generaliza a VM com sysprep. A instalação do IIS mostra como pode utilizar o aprovisionador do PowerShell para executar comandos adicionais. A imagem final do Packer inclui, em seguida, a instalação e configuração de software necessárias.

O Agente Convidado do Windows participa no processo Sysprep. O agente tem de estar totalmente instalado antes de a VM poder ser sysprep'ed. Para garantir que isto é verdade, todos os serviços de agente têm de estar em execução antes de executar sysprep.exe. O fragmento JSON anterior mostra uma forma de o fazer no aprovisionador do PowerShell. Este fragmento só é necessário se a VM estiver configurada para instalar o agente, que é a predefinição.

Imagem de Compilação do Packer

Se ainda não tiver o Packer instalado no seu computador local, siga as instruções de instalação do Packer.

Crie a imagem ao abrir uma linha de comandos e especificar o ficheiro de modelo do Packer da seguinte forma:

packer build windows.json

Também pode criar a imagem ao especificar o ficheiro windows.pkr.hcl da seguinte forma:

packer build windows.pkr.hcl

Um exemplo da saída dos comandos anteriores é o seguinte:

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          : ‘East US’
==> 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.net/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          : ‘East US’
==> azure-arm:  -> Image ResourceGroupName   : ‘myResourceGroup’
==> azure-arm:  -> Image Name                : ‘myPackerImage’
==> azure-arm:  -> Image Location            : ‘eastus’
==> 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: eastus

O Packer demora alguns minutos a criar a VM, a executar os aprovisionadores e a limpar a implementação.

Criar uma VM a partir da imagem do Packer

Agora pode criar uma VM a partir da sua Imagem com New-AzVM. Os recursos de rede de suporte são criados se ainda não existirem. Quando lhe for pedido, introduza um nome de utilizador administrativo e uma palavra-passe a criar na VM. O exemplo seguinte cria uma VM com o nome myVM a partir de myPackerImage:

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

Se quiser criar VMs num grupo de recursos ou região diferente da imagem do Packer, especifique o ID da imagem em vez do nome da imagem. Pode obter o ID da imagem com Get-AzImage.

Demora alguns minutos a criar a VM a partir da imagem do Packer.

Testar VM e webserver

Obtenha o endereço IP público da sua VM com Get-AzPublicIPAddress. O exemplo seguinte obtém o endereço IP para myPublicIP criado anteriormente:

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

Para ver a sua VM, que inclui a instalação do IIS a partir do aprovisionador Packer, em ação, introduza o endereço IP público num browser.

Site predefinido do IIS

Passos seguintes

Também pode utilizar scripts do aprovisionador Packer existentes com o Azure Image Builder.