powershell provisioner

powershell provisioner

powershell Packer provisioner는 Windows 머신에서 PowerShell 스크립트를 실행해요. 이 provisioner는 WinRM을 communicator로 사용하는 머신을 위해 설계됐지만, SSH communicator와도 사용할 수 있어요. 자세한 내용은 PowerShell Provisioner와 SSH Communicator 결합하기를 참고해 주세요.

출처: Packer 공식 문서

본문

경로는 특히 구성 안에서 상대 경로를 쓸 때 슬래시(/)로 구분해 주세요. Windows는 경로를 구분할 때 백슬래시(\)를 쓰지만, Packer는 Windows가 아닌 시스템에서 빌드를 만들 때 /만 경로 구분자로 인식해요. Packer는 백슬래시를 일반 텍스트로도 취급하기 때문에 오류가 생길 수 있어요.

기본 예시 (Basic Example)

아래 예시는 완전히 동작해요.

HCL2

provisioner "powershell" {
  inline = ["dir c:/"]
}

JSON

{
  "type": "powershell",
  "inline": ["dir c:/"]
}

구성 레퍼런스 (Configuration Reference)

사용 가능한 구성 옵션의 레퍼런스가 아래에 정리돼 있어요. 필수 요소는 "inline" 또는 "script" 중 하나뿐이고, 다른 모든 옵션은 선택 사항이에요.

정확히 하나 는 필수예요:

  • inline (문자열 배열) - 실행할 명령어들의 배열. 명령어들은 새줄로 연결되어 단일 파일이 되므로 모두 같은 컨텍스트에서 실행돼요. 그래서 한 명령어에서 디렉터리를 바꾸고 다음 명령어에서 그 디렉터리 안의 것을 사용하는 식으로 할 수 있어요. Inline 스크립트는 머신 안에서 간단한 작업을 해내는 가장 쉬운 방법이에요.

  • script (string) - 머신 안에서 업로드하고 실행할 스크립트의 경로. 절대 경로나 상대 경로일 수 있어요. 상대 경로라면 Packer가 실행될 때의 작업 디렉터리를 기준으로 해요.

  • scripts (문자열 배열) - 실행할 스크립트들의 배열. 지정된 순서대로 업로드·실행돼요. 각 스크립트는 격리된 상태로 실행되므로, 한 스크립트의 변수 같은 상태는 다음 스크립트로 이어지지 않아요.

선택 매개변수:

  • binary (boolean) - true이면 스크립트(들)이 바이너리 파일임을 지정해요. 따라서 Packer는 Windows 줄 끝을 Unix 줄 끝으로 변환하지 않아야 해요(존재하는 경우). 기본값은 false예요.

  • valid_exit_codes (int 목록) - 스크립트의 유효한 종료 코드. 기본값은 그저 0이에요.

  • debug_mode - 설정하면 스크립트 디버깅을 쉽게 하기 위해 PowerShell의 PSDebug 모드를 설정해요. 예를 들어 값을 1로 설정하면 실행 명령어에 다음을 추가해요:

Set-PSDebug -Trace 1
  • elevated_execute_command (string) - 승격된 스크립트를 실행하는 데 사용할 명령어. 기본값은 다음과 같아요:
powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};. {{.Vars}}; &'{{.Path}}'; exit $LastExitCode }"

이 값은 템플릿 엔진이에요. 따라서 이 필드에서 사용자 변수와 템플릿 함수를 사용할 수 있어요. 추가로 두 가지 추가 변수를 사용할 수 있어요:

  • Path: 실행할 스크립트의 경로

  • Vars: 설정된 경우 environment_vars 목록을 담은 임시 파일의 위치

  • env (문자열 맵) - execute_command 전에 주입할 키/값 쌍의 맵. Packer는 기본적으로도 일부 환경 변수를 환경에 주입하는데, 이는 아래 절에서 다뤄요. 중복된 env 설정은 environment_vars 설정을 재정의해요. 이것은 JSON 템플릿 엔진 활성 함수가 아니에요. HCL 보간은 평소처럼 동작해요.

  • environment_vars (문자열 배열) - execute_command 전에 주입할 키/값 쌍의 배열. 형식은 key=value여야 해요. Packer는 기본적으로도 일부 환경 변수를 환경에 주입하는데, 이는 아래 절에서 다뤄요.

  • use_pwsh (boolean) - powershell.exe 대신 pwsh.exe를 실행해요. 기본값은 false예요.

이 값은 템플릿 엔진이에요. 따라서 이 필드에서 사용자 변수와 템플릿 함수를 사용할 수 있어요. AWS, Azure, Google Compute, OpenStack에서 실행 중이고 Packer가 WinRM으로 인스턴스에 연결하는 데 사용하는 자동 생성 비밀번호에 접근하고 싶다면 build 템플릿 엔진으로 {{ build Password }}를 사용해 주입할 수 있어요. HCL 템플릿에서는 build 변수에 접근해 같은 일을 할 수 있어요. 예를 들면:

HCL2

provisioner "powershell" {
  environment_vars = ["WINRMPASS=${build.Password}"]
  inline = ["Write-Host \"Automatically generated aws password is: $Env:WINRMPASS\""]
}

JSON

{
  "type": "powershell",
  "environment_vars": ["WINRMPASS={{ build `Password` }}"],
  "inline": ["Write-Host \"Automatically generated aws password is: $Env:WINRMPASS\""]
}
  • execute_command (string) - 스크립트를 실행하는 데 사용할 명령어. 기본값은 다음과 같아요:
powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};. {{.Vars}}; &'{{.Path}}'; exit $LastExitCode }"

이 값은 템플릿 엔진이에요. 따라서 이 필드에서 사용자 변수와 템플릿 함수를 사용할 수 있어요. 추가로 두 가지 추가 변수를 사용할 수 있어요:

  • Path: 실행할 스크립트의 경로
  • Vars: 설정된 경우 environment_vars 목록을 담은 임시 파일의 위치. Path와 Vars 두 값 모두 remote_path와 remote_env_var_path 값을 각각 설정해 수동으로 구성할 수 있어요.

SSH communicator를 사용하고 기본 셸을 바꿨다면 execute_command가 유효하고 제대로 이스케이프되도록 수정해야 할 수 있어요. 기본값은 기본 셸을 cmd에서 바꾸지 않았다고 가정해요.

  • elevated_user와 elevated_password (string) - 지정하면 PowerShell 스크립트가 주어진 Windows 사용자로 승격된 권한으로 실행돼요.

이 값은 템플릿 엔진이에요. 따라서 이 필드에서 사용자 변수와 템플릿 함수를 사용할 수 있어요. AWS, Azure, Google Compute, OpenStack에서 실행 중이고 Packer가 WinRM으로 인스턴스에 연결하는 데 사용하는 자동 생성 비밀번호에 접근하고 싶다면 build 템플릿 엔진으로 {{ build Password }}를 사용해 주입할 수 있어요. HCL 템플릿에서는 build 변수에 접근해 같은 일을 할 수 있어요. 예를 들면:

HCL2

provisioner "powershell" {
    elevated_user = "Administrator"
    elevated_password = build.Password
}

JSON

{
  "type": "powershell",
  "elevated_user": "Administrator",
  "elevated_password": "{{ build `Password` }}",
  ...
}

빈 elevated_password 값을 지정하면 PowerShell 스크립트가 서비스 계정으로 실행돼요. 예를 들면:

HCL2

provisioner "powershell" {
  elevated_user = "SYSTEM"
  elevated_password = ""
}

JSON

{
  "type": "powershell",
  "elevated_user": "SYSTEM",
  "elevated_password": "",
  ...
}
  • execution_policy - Windows에서 ps 스크립트를 실행하기 위해 Packer는 기본값을 "bypass"로 설정하고 명령어를 감싸서 실행해요. 이것을 "none"으로 설정하면 감싸기를 방지해서 Docker for Windows에서 종료 코드를 볼 수 있어요. 가능한 값은 bypass, allsigned, default, remotesigned, restricted, undefined, unrestricted, none이에요.

  • remote_path (string) - 대상 빌드 머신 안에서 PowerShell 스크립트가 업로드될 경로. 기본값은 C:/Windows/Temp/script-UUID.ps1이고, 여기서 UUID는 스크립트를 고유하게 식별하는 동적 생성 문자열로 대체돼요.

이 설정으로 기본 업로드 위치를 재정의할 수 있어요. 값은 쓰기 가능한 위치여야 하고 상위 디렉터리들이 이미 존재해야 해요.

  • remote_env_var_path (string) - 원격 환경에 필요한 환경 변수들은 PowerShell 스크립트 안에 업로드된 뒤, 메인 명령어나 스크립트 실행 직전에 그 스크립트를 'dot sourcing'해서 활성화돼요.

환경 변수 스크립트가 업로드될 경로의 기본값은 C:/Windows/Temp/packer-ps-env-vars-UUID.ps1이고, 여기서 UUID는 스크립트를 고유하게 식별하는 동적 생성 문자열로 대체돼요.

이 설정으로 환경 변수 스크립트가 업로드될 위치를 재정의할 수 있어요. 값은 쓰기 가능한 위치여야 하고 상위 디렉터리들이 이미 존재해야 해요.

  • skip_clean (bool) - provisioner 실행 후 스크립트를 정리할지 여부. 기본값은 false예요. true이면 비승격 Powershell provisioner가 만든 스크립트가 원격 머신에서 제거돼요. 승격된 스크립트와 예약된 작업들은 skip_clean에 설정된 값과 무관하게 항상 제거돼요.

  • start_retry_timeout (string) - 원격 프로세스를 시작 하려고 시도할 시간. 기본값은 5m(5분)이에요. 이 설정은 시스템 재부팅처럼 SSH가 다시 시작될 수 있는 상황을 다루기 위해 존재해요. 재부팅에 더 오랜 시간이 걸린다면 더 높은 값으로 설정해 주세요.

  • pause_after (string) - PowerShell 스크립트 프로비저닝 후 이 시간만큼 기다려요. 이 멈춤은 이전 단계가 모두 성공했을 때만 적용돼요.

모든 provisioner에 공통된 매개변수:

  • pause_before (duration) - 실행 전에 duration만큼 잠자기.
  • max_retries (int) - 실패 시 provisioner가 재시도할 최대 횟수. 기본값은 0이에요. 0은 오류를 재시도하지 않는다는 뜻이에요.
  • only (문자열 배열) - 이름으로 나열된 builder에 대해서만 provisioner를 실행해요.
  • override (object) - 특정 builder에 대해 builder를 다른 설정으로 재정의해요. 예:

HCL2에서:

source "null" "example1" {
  communicator = "none"
}

source "null" "example2" {
  communicator = "none"
}

build {
  sources = ["source.null.example1", "source.null.example2"]
  provisioner "shell-local" {
    inline = ["echo not overridden"]
    override = {
      example1 = {
        inline = ["echo yes overridden"]
      }
    }
  }
}

JSON에서:

{
  "builders": [
    {
      "type": "null",
      "name": "example1",
      "communicator": "none"
    },
    {
      "type": "null",
      "name": "example2",
      "communicator": "none"
    }
  ],
  "provisioners": [
    {
      "type": "shell-local",
      "inline": ["echo not overridden"],
      "override": {
        "example1": {
          "inline": ["echo yes overridden"]
        }
      }
    }
  ]
}
  • timeout (duration) - provisioner가 예를 들어 1h10m1s나 10m보다 오래 걸리면 provisioner는 타임아웃되고 실패해요.

기본 환경 변수 (Default Environmental Variables)

environment_vars 구성으로 커스텀 환경 변수를 지정할 수 있을 뿐 아니라, provisioner는 몇 가지 흔히 유용한 환경 변수도 자동으로 정의해요:

  • PACKER_BUILD_NAME은 Packer가 실행 중인 빌드의 이름으로 설정돼요. Packer가 여러 빌드를 만들 때 공통 프로비저닝 스크립트에서 그것들을 조금 구분하고 싶을 때 가장 유용해요.

  • PACKER_BUILDER_TYPE은 스크립트가 실행 중인 머신을 만드는 데 사용된 builder의 타입이에요. 특정 builder로 만든 시스템에서 스크립트의 일부만 실행하고 싶을 때 유용해요.

  • PACKER_HTTP_ADDR 파일 전송을 위한 HTTP 서버를 제공하는 builder(예: hyperv, parallels, qemu, virtualbox, vmware)를 사용한다면 이 값은 그 주소로 설정돼요. 프로비저너에서 이 주소를 사용해 HTTP로 큰 파일을 다운로드할 수 있어요. 기본 file provisioner로 속도가 느릴 때 유용할 수 있어요. winrm communicator를 사용하는 file provisioner는 이런 어려움을 겪을 수 있어요.

PowerShell Provisioner와 SSH Communicator 결합하기 (Combining the PowerShell Provisioner with the SSH Communicator)

먼저 좋은 소식이에요. Microsoft의 OpenSSH 포트를 사용한다면 provisioner가 예상대로 동작해요. 추가 구성이 필요 없어요.

이제 주의할 점이 있어요. 대안 구성을 사용하고 SSH 연결이 원격 호스트의 *nix 셸로 들어간다면, execute_command를 수동으로 설정해야 할 가능성이 높아요. Packer가 사용하는 기본 execute_command는 동작하지 않을 거예요. 명령어를 구성할 때 달러 기호나 원격 셸이 잘못 해석할 수 있는 다른 문자들은 그에 맞게 이스케이프해야 한다는 점을 확인해 주세요.

다음 예시는 Cygwin/OpenSSH가 설치된 원격 시스템에서 표준 execute_command가 동작하도록 재구성하는 방법을 보여줘요. execute_command의 각 달러 기호는 원격 Bash 셸(Cygwin 환경의 기본 셸)이 해석하지 못하도록 백슬래시로 이스케이프돼요.

HCL2

provisioner "powershell" {
    execute_command = "powershell -executionpolicy bypass \"& { if (Test-Path variable:global:ProgressPreference){\\$ProgressPreference='SilentlyContinue'};. {{.Vars}}; &'{{.Path}}'; exit \\$LastExitCode }\""
    inline          = [ "Write-Host \"Hello from PowerShell\""]
}

JSON

"provisioners": [
  {
    "type": "powershell",
    "execute_command": "powershell -executionpolicy bypass \"& { if (Test-Path variable:global:ProgressPreference){\\$ProgressPreference='SilentlyContinue'};. {{.Vars}}; &'{{.Path}}'; exit \\$LastExitCode }\"",
    "inline": ["Write-Host \"Hello from PowerShell\""]
  }
]

PowerShell에 특수한 문자의 Packer 처리 (Packer's Handling of Characters Special to PowerShell)

PowerShell의 이스케이프 문자는 backtick이에요. 때로는 grave accent라고도 불려요. PowerShell에 특수한 문자를 언제 이스케이프해야 하고 언제 하지 말아야 하는지는 일련의 예시로 가장 잘 보여줄 수 있어요.

언제 이스케이프할까 (When To Escape...)

사용자는 inline PowerShell provisioner에서 사용하는 명령어에 직접 나타나거나, 사용자 자신의 스크립트에 직접 나타날 때 PowerShell에 특수한 문자를 이스케이프해야 해요. 큰따옴표 안에 큰따옴표가 나타나는 곳에서는 JSON 템플릿이 올바르게 파싱되도록 백슬래시 이스케이프 추가가 필요하다는 점에 유의하세요.

HCL2

provisioner "powershell" {
    inline = [
        "Write-Host \"A literal dollar `$ must be escaped\"",
        "Write-Host \"A literal backtick `` must be escaped\"",
        "Write-Host \"Here `\"double quotes`\" must be escaped\"",
        "Write-Host \"Here `'single quotes`' don`'t really need to be\"",
        "Write-Host \"escaped... but it doesn`'t hurt to do so.\"",
    ]
}

JSON

  "provisioners": [
    {
      "type": "powershell",
      "inline": [
          "Write-Host \"A literal dollar `$ must be escaped\"",
          "Write-Host \"A literal backtick `` must be escaped\"",
          "Write-Host \"Here `\"double quotes`\" must be escaped\"",
          "Write-Host \"Here `'single quotes`' don`'t really need to be\"",
          "Write-Host \"escaped... but it doesn`'t hurt to do so.\""
      ]
    }
  ]

위 코드 조각은 Packer 콘솔에 다음 출력이 나와야 해요:

==> amazon-ebs: Provisioning with Powershell...
==> amazon-ebs: Provisioning with PowerShell script: /var/folders/15/d0f7gdg13rnd1cxp7tgmr55c0000gn/T/packer-powershell-provisioner508190439
    amazon-ebs: A literal dollar $ must be escaped
    amazon-ebs: A literal backtick ` must be escaped
    amazon-ebs: Here "double quotes" must be escaped
    amazon-ebs: Here 'single quotes' don't really need to be
    amazon-ebs: escaped... but it doesn't hurt to do so.

언제 이스케이프하지 않을까 (When Not To Escape...)

사용자 환경 변수 값과 elevated_user, elevated_password 필드에 나타나는 특수 문자는 사용자를 위해 자동으로 처리돼요. 이런 경우에는 이스케이프를 쓸 필요가 없어요.

HCL2

variable "psvar" {
  type    = string
  default = "My$tring"
}

build {
  sources = ["source.amazon-ebs.example"]

  provisioner "powershell" {
      elevated_user     = "Administrator"
      elevated_password = "Super$3cr3t!"
      inline            = ["Write-Output \"The dollar in the elevated_password is interpreted correctly\""]
  }
  provisioner "powershell" {
    environment_vars = [
        "VAR1=A$Dollar",
        "VAR2=A`Backtick",
        "VAR3=A'SingleQuote",
        "VAR4=A\"DoubleQuote",
        "VAR5=${var.psvar}",
    ]
    inline = [
      "Write-Output \"In the following examples the special character is interpreted correctly:\"",
      "Write-Output \"The dollar in VAR1:                            $Env:VAR1\"",
      "Write-Output \"The backtick in VAR2:                          $Env:VAR2\"",
      "Write-Output \"The single quote in VAR3:                      $Env:VAR3\"",
      "Write-Output \"The double quote in VAR4:                      $Env:VAR4\"",
      "Write-Output \"The dollar in VAR5 (expanded from a user var): $Env:VAR5\"",
    ]
  }
}

JSON

{
  "variables": {
    "psvar": "My$tring"
  },
  ...
  "provisioners": [
    {
      "type": "powershell",
      "elevated_user": "Administrator",
      "elevated_password": "Super$3cr3t!",
      "inline": "Write-Output \"The dollar in the elevated_password is interpreted correctly\""
    },
    {
      "type": "powershell",
      "environment_vars": [
        "VAR1=A$Dollar",
        "VAR2=A`Backtick",
        "VAR3=A'SingleQuote",
        "VAR4=A\"DoubleQuote",
        "VAR5={{user `psvar`}}"
      ],
      "inline": [
        "Write-Output \"In the following examples the special character is interpreted correctly:\"",
        "Write-Output \"The dollar in VAR1:                            $Env:VAR1\"",
        "Write-Output \"The backtick in VAR2:                          $Env:VAR2\"",
        "Write-Output \"The single quote in VAR3:                      $Env:VAR3\"",
        "Write-Output \"The double quote in VAR4:                      $Env:VAR4\"",
        "Write-Output \"The dollar in VAR5 (expanded from a user var): $Env:VAR5\""
      ]
    }
  ]
  ...
}

위 코드 조각은 Packer 콘솔에 다음 출력이 나와야 해요:

==> amazon-ebs: Provisioning with Powershell...
==> amazon-ebs: Provisioning with PowerShell script: /var/folders/15/d0f7gdg13rnd1cxp7tgmr55c0000gn/T/packer-powershell-provisioner961728919
    amazon-ebs: The dollar in the elevated_password is interpreted correctly
==> amazon-ebs: Provisioning with Powershell...
==> amazon-ebs: Provisioning with PowerShell script: /var/folders/15/d0f7gdg13rnd1cxp7tgmr55c0000gn/T/packer-powershell-provisioner142826554
    amazon-ebs: In the following examples the special character is interpreted correctly:
    amazon-ebs: The dollar in VAR1:                            A$Dollar
    amazon-ebs: The backtick in VAR2:                          A`Backtick
    amazon-ebs: The single quote in VAR3:                      A'SingleQuote
    amazon-ebs: The double quote in VAR4:                      A"DoubleQuote
    amazon-ebs: The dollar in VAR5 (expanded from a user var): My$tring