/ azure

Packer로 Azure VM 이미지 만들기 #1 - Packer 템플릿

VM 이미지 활용

Azure의 VM은 이미지로 만들 수 있다. VM을 만들고 OS 설정과 내 애플리케이션을 배포한 다음 이미지로 만들 수 있다. 이 이미지로 부터 VM을 생성하면 똑같이 설정된 VM들을 계속 만들어 낼 수 있다. 또한 VMSS(Virtual Machine Scalce Set)을 사용할 때 VM 이미지가 필요하다. 하지만 VM 이미지를 생성하고 관리하는 작업은 어려운 점이 있다.

  1. 이미지를 만드는 과정은 시간이 걸리고 반복적인 작업이다. 윈도우 서버의 경우 Sysprep 하고 Generalize 하고 Deallocation 하고 Capture 해야 한다. Azure에서 일반화된 VM의 관리 이미지 만들기 문서에 자세한 내용이 나와있다.
  2. 윈도우의 경우 Sysprep, 리눅스의 경우 sudo waagent -deprovision+user 명령 때문에 전체를 자동화하기가 어렵다.
  3. 애플리케이션이 업데이트되거나 기본 이미지의 설정이 변경되면 이미지를 새로 만들어야 한다.
  4. 계속되는 이미지의 변경사항을 관리하기 어렵다.

Packer로 이미지 만들기

그런 어려운점을 해소하기 위한 방법으로 Packer가 있다. Packer는 Json으로 작성하는 템플릿의 내용에 따라서 VM 이미지를 만든다. Azure 뿐만 아니라 AWS EC2, VMware, VirtualBox 등의 이미지를 만들 수 있고 자동화 할 수 있다.

Azure에서 Packer를 사용하여 Windows 가상 머신 이미지를 만드는 방법 문서에 자세한 내용이 나와있다. 이 문서를 그대로 따라하면 윈도우 서버 2016에 IIS 가 설치된 VM 이미지가 생성된다. 여기에 이미지의 설정이 변경되거나, 추가 툴을 설치하거나, 애플리케이션 배포가 필요하면 Provisioners 에 내용을 추가하면 된다. 이 Json 파일을 소스 관리하면서 변경을 관리하면 된다.

이 문서에서 고생하기 쉬운 부분이 하나 있는데 object_id 를 가져오는 부분이다. 여기서 말하는 object_id 는 Service Principle의 Object ID를 의미하지 않는다. 구독에 추가된 Service Priciple 권한의 Object ID를 의미한다. 아래 스크린샷을 보면 Azure 포탈에서 Object ID를 가져올 수 있다.

최신버전 Packer에서는 실행시 이런 설명을 출력한다. "You have provided Object_ID which is no longer needed, azure packer builder determines this dynamically from the authentication token" 따라서 Object ID는 없어도 될 것 같다.

object-id

Packer로 기존에 만든 이미지에서 새로운 이미지 만들기

Azure에서 Packer를 사용하여 Windows 가상 머신 이미지를 만드는 방법 문서의 템플릿은 Azure 마켓플레이스에 있는 Windows 2016 이미지로 부터 시작 한다. 이미 수동으로 만들어 놓은 이미지로 부터 새로운 이미지를 만들기 위해서는 조금만 수정하면 된다. "image_publisher", "image_offer", "2016-Datacenter" 대신에 "custom_managed_image_resource_group_name", "custom_managed_image_name"를 쓰면 된다.
custom_managed_image_resource_group_name는 기존 이미지가 들어 있는 리소스 그룹 이름, custom_managed_image_name은 만들어 놓은 이미지 이름을 넣어주면 된다.

{
    "variables": {
        "client_id": "{{env `ARM_CLIENT_ID`}}",
        "client_secret": "{{env `ARM_CLIENT_SECRET`}}",
        "tenant_id": "{{env `ARM_TENANT_ID`}}",
        "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}",
        "object_id": "{{env `ARM_OJBECT_ID`}}",
        "custom_managed_image_resource_group_name": "{{env `ARM_CUSTOM_IMAGE_RG_NAME`}}",
        "custom_managed_image_name": "{{env `ARM_CUSTOM_IMAGE_NAME`}}",
        "image_name": "base-image-{{isotime \"2006-01-02\"}}_{{isotime \"03-04-05\"}}"
    },
    "builders": [
        {
            "type": "azure-arm",

            "client_id": "{{user `client_id`}}",
            "client_secret": "{{user `client_secret`}}",
            "tenant_id": "{{user `tenant_id`}}",
            "subscription_id": "{{user `subscription_id`}}",
            "object_id": "{{user `object_id`}}",

            "managed_image_resource_group_name": "{{user `custom_managed_image_resource_group_name`}}",
            "managed_image_name": "{{user `image_name`}}", 

            "azure_tags": {
                "latest-image": "true"
            },

            "location": "Korea Central",
            "vm_size": "Standard_D2s_v3",

            "os_type": "Windows",
            "custom_managed_image_resource_group_name": "{{user `custom_managed_image_resource_group_name`}}",
            "custom_managed_image_name": "{{user `custom_managed_image_name`}}",

            "communicator": "winrm",
            "winrm_use_ssl": "true",
            "winrm_insecure": "true",
            "winrm_timeout": "3m",
            "winrm_username": "ansibleuser"
        }
    ],
    "provisioners": [ 
        {
            "type": "powershell", 
            "script": "./powershell/install-chocolatey.ps1"
        },
        {
            "type": "powershell",
            "inline": [
                "if( Test-Path $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml ){ rm $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml -Force}",
                "& $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 } }"
            ]
        }
        ]
}

윈도우 서버에서 Chocolatey 사용

Windows 서버에 툴을 설치해야 하는 경우 Chocolatey를 사용하면 쉽게 스크립트로 설치 가능하다. Ubuntu에서 apt-get 처럼 choco install jre8 명령으로 JDK를 설치할 수 있다.

설정변수 파일

설정변수는 커멘트의 파라미터로 넣어도 되고 variable.json 파일에 넣어도 된다.

{
    "client_id": "<client_id>",
    "client_secret": "<client_secret>",
    "tenant_id": "<tenant_id>",
    "subscription_id": "<subscription_id>",
    "object_id": "<object_id>",
    "custom_managed_image_resource_group_name": "<resource group name>"
}

실행결과

실행결과는 아래와 같다. 실행되는 중간에 Azure Portal을 보면 Packer가 만든 VM이 보이다가 완료되면 깨끗이 지운다. 새로만든 VM 이미지만 남는다. 실행전에 BaseImageVM-image-20180604165401 VM이미지가 이미 준비되어 있었다. Azure 마켓플레이스에 있는 기본 이미지를 사용하는 예제는 Azure에서 Packer를 사용하여 Windows 가상 머신 이미지를 만드는 방법 에 있다.

packer.exe build -var-file=.\variable.json -var custom_managed_image_name=BaseImageVM-image-20180604165401 .\bdm-baseimage.json
azure-arm output will be in this color.

==> azure-arm: Running builder ...
    azure-arm: Creating Azure Resource Manager (ARM) client ...
    azure-arm: You have provided Object_ID which is no longer needed, azure packer builder determines this dynamically from the authentication token
==> azure-arm: Creating resource group ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-73vfh7b6s2'
==> azure-arm:  -> Location          : 'Korea Central'
==> azure-arm:  -> Tags              :
==> azure-arm:  ->> latest-image : true
==> azure-arm: Validating deployment template ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-73vfh7b6s2'
==> azure-arm:  -> DeploymentName    : 'pkrdp73vfh7b6s2'
==> azure-arm: Deploying deployment template ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-73vfh7b6s2'
==> azure-arm:  -> DeploymentName    : 'kvpkrdp73vfh7b6s2'
==> azure-arm: Getting the certificate's URL ...
==> azure-arm:  -> Key Vault Name        : 'pkrkv73vfh7b6s2'
==> azure-arm:  -> Key Vault Secret Name : 'packerKeyVaultSecret'
==> azure-arm:  -> Certificate URL       : 'https://pkrkv73vfh7b6s2.vault.azure.net/secrets/packerKeyVaultSecret/31e1069f5e7849a2b444cdb49aca782c'
==> azure-arm: Setting the certificate's URL ...
==> azure-arm: Validating deployment template ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-73vfh7b6s2'
==> azure-arm:  -> DeploymentName    : 'pkrdp73vfh7b6s2'
==> azure-arm: Deploying deployment template ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-73vfh7b6s2'
==> azure-arm:  -> DeploymentName    : 'pkrdp73vfh7b6s2'
==> azure-arm: Getting the VM's IP address ...
==> azure-arm:  -> ResourceGroupName   : 'packer-Resource-Group-73vfh7b6s2'
==> azure-arm:  -> PublicIPAddressName : 'pkrip73vfh7b6s2'
==> azure-arm:  -> NicName             : 'pkrni73vfh7b6s2'
==> azure-arm:  -> Network Connection  : 'PublicEndpoint'
==> azure-arm:  -> IP Address          : '52.231.65.219'
==> azure-arm: Waiting for WinRM to become available...
    azure-arm: #< CLIXML
    azure-arm: WinRM connected.
    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><Obj S="progress" RefId="1"><TNRef RefId="0" /><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: Connected to WinRM!
==> azure-arm: Provisioning with Powershell...
==> azure-arm: Provisioning with powershell script: ./powershell/install-chocolatey.ps1
    azure-arm: Getting latest version of the Chocolatey package for download.
    azure-arm: Getting Chocolatey from https://chocolatey.org/api/v2/package/chocolatey/0.10.11.
    azure-arm: Downloading 7-Zip commandline tool prior to extraction.
    azure-arm: Extracting C:\Users\packer\AppData\Local\Temp\chocolatey\chocInstall\chocolatey.zip to C:\Users\packer\AppData\Local\Temp\chocolatey\chocInstall...
    azure-arm: Installing chocolatey on this machine
    azure-arm: Creating ChocolateyInstall as an environment variable (targeting 'Machine')
    azure-arm:   Setting ChocolateyInstall to 'C:\ProgramData\chocolatey'
    azure-arm: WARNING: It's very likely you will need to close and reopen your shell
    azure-arm:   before you can use choco.
    azure-arm: Restricting write permissions to Administrators
    azure-arm: We are setting up the Chocolatey package repository.
    azure-arm: The packages themselves go to 'C:\ProgramData\chocolatey\lib'
    azure-arm:   (i.e. C:\ProgramData\chocolatey\lib\yourPackageName).
    azure-arm: A shim file for the command line goes to 'C:\ProgramData\chocolatey\bin'
    azure-arm:   and points to an executable in 'C:\ProgramData\chocolatey\lib\yourPackageName'.
    azure-arm:
    azure-arm: Creating Chocolatey folders if they do not already exist.
    azure-arm:
    azure-arm: WARNING: You can safely ignore errors related to missing log files when
    azure-arm:   upgrading from a version of Chocolatey less than 0.9.9.
    azure-arm:   'Batch file could not be found' is also safe to ignore.
    azure-arm:   'The system cannot find the file specified' - also safe.
    azure-arm: chocolatey.nupkg file not installed in lib.
    azure-arm:  Attempting to locate it from bootstrapper.
    azure-arm: PATH environment variable does not have C:\ProgramData\chocolatey\bin in it. Adding...
    azure-arm: WARNING: Not setting tab completion: Profile file does not exist at
    azure-arm: 'C:\Users\packer\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1'.
    azure-arm: Chocolatey (choco.exe) is now ready.
    azure-arm: You can call choco from anywhere, command line or powershell by typing choco.
    azure-arm: Run choco /? for a list of functions.
    azure-arm: You may need to shut down and restart powershell and/or consoles
    azure-arm:  first prior to using choco.
    azure-arm: Ensuring chocolatey commands are on the path
    azure-arm: Ensuring chocolatey.nupkg is in the lib folder
==> azure-arm: Provisioning with Powershell...
==> azure-arm: Provisioning with powershell script: C:\Users\iloh\AppData\Local\Temp\packer-powershell-provisioner601613115
    azure-arm: IMAGE_STATE_COMPLETE
    azure-arm: IMAGE_STATE_UNDEPLOYABLE
    azure-arm: IMAGE_STATE_UNDEPLOYABLE
    azure-arm: IMAGE_STATE_UNDEPLOYABLE
    azure-arm: IMAGE_STATE_UNDEPLOYABLE
    azure-arm: IMAGE_STATE_UNDEPLOYABLE
    azure-arm: IMAGE_STATE_UNDEPLOYABLE
    azure-arm: IMAGE_STATE_UNDEPLOYABLE
    azure-arm: IMAGE_STATE_UNDEPLOYABLE
==> azure-arm: Querying the machine's properties ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-73vfh7b6s2'
==> azure-arm:  -> ComputeName       : 'pkrvm73vfh7b6s2'
==> azure-arm:  -> Managed OS Disk   : '/subscriptions/e47f0bbb-cd59-41dc-86b7-2e239d536c04/resourceGroups/packer-Resource-Group-73vfh7b6s2/providers/Microsoft.Compute/disks/pkros73vfh7b6s2'
==> azure-arm: Querying the machine's additional disks properties ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-73vfh7b6s2'
==> azure-arm:  -> ComputeName       : 'pkrvm73vfh7b6s2'
==> azure-arm: Powering off machine ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-73vfh7b6s2'
==> azure-arm:  -> ComputeName       : 'pkrvm73vfh7b6s2'
==> azure-arm: Capturing image ...
==> azure-arm:  -> Compute ResourceGroupName : 'packer-Resource-Group-73vfh7b6s2'
==> azure-arm:  -> Compute Name              : 'pkrvm73vfh7b6s2'
==> azure-arm:  -> Compute Location          : 'Korea Central'
==> azure-arm:  -> Image ResourceGroupName   : 'BaseImageGroup'
==> azure-arm:  -> Image Name                : 'base-image-2018-06-05_07-25-05'
==> azure-arm:  -> Image Location            : 'koreacentral'
==> azure-arm: Deleting resource group ...
==> azure-arm:  -> ResourceGroupName : 'packer-Resource-Group-73vfh7b6s2'
==> azure-arm:
==> azure-arm: The resource group was created by Packer, deleting ...
==> azure-arm: Deleting the temporary OS disk ...
==> azure-arm:  -> OS Disk : skipping, managed disk was used...
==> azure-arm: Deleting the temporary Additional disk ...
==> azure-arm:  -> Additional Disk : skipping, managed disk was used...

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

ManagedImageResourceGroupName: BaseImageGroup
ManagedImageName: base-image-2018-06-05_07-25-05
ManagedImageLocation: koreacentral

다른 관리툴과 연결

Packer는 단일 바이너리 파일로 배포된다. Packer.exe 를 커멘트라인에서 실행한다. 위의 실행결과처럼 커멘드를 직접 실행해서 결과를 볼 수 있지만 앞뒤에 추가 작업이 필요한 경우 다른 배포툴, DevOps 툴과 함께 쓰면 좋다. Packer로 Azure 이미지 만들기 #2 - VSTS와 Packer 편에서 Visual Studio Team Service 에서 Packer를 사용한 사례를 알아보자.