shell-local 프로비저너

shell-local 프로비저너

shell-local 프로비저너는 Packer가 실행되고 있는 머신에서 셸 스크립트를 실행하는 공식 프로비저너예요. Packer로 프로비저닝하는 원격 또는 게스트 머신이 아니라, 빌드 서버·데스크톱·기타 로컬 머신에서 셸 스크립트를 실행하고 싶을 때 사용해요.

출처: Packer 공식 문서

본문

원격 셸 프로비저너는 원격 머신에서 셸 스크립트를 실행하는 프로비저너예요.

기본 예시 (Basic Example)

아래 예시는 완전히 동작해요. HCL2와 JSON:

source "file" "example" {
    content = "example content"
}

build {
  source "source.file.example" {
    target = "./test_artifact.txt"
  }

  provisioner "shell-local" {
    inline = ["echo foo"]
  }
}
{
  "builders": [
    {
      "type": "file",
      "name": "example",
      "target": "./test_artifact.txt",
      "content": "example content"
    }
  ],
  "provisioners": [
    {
      "type": "shell-local",
      "inline": ["echo foo"]
    }
  ]
}

구성 참조 (Configuration Reference)

사용 가능한 구성 옵션의 참조는 아래에 나열돼 있어요. 유일하게 필수인 요소는 command예요.

다음 중 정확히 하나가 필요해요:

  • command (string) - 실행할 단일 명령이에요. 임시 파일에 쓰여진 뒤 아래의 execute_command 호출로 실행돼요. AWS, Azure, Google Compute, OpenStack에서 Windows VM을 빌드 중이고 WinRM으로 인스턴스에 연결할 때 Packer가 사용하는 생성된 비밀번호에 접근하고 싶다면, 템플릿 변수 {{.WinRMPassword}}를 사용해 환경 변수로 설정할 수 있어요.

  • inline (array of strings) - 실행할 명령들의 배열이에요. 명령들은 개행으로 연결되어 하나의 파일이 되므로 모두 같은 문맥 안에서 실행돼요. 이 덕분에 한 명령에서 디렉터리를 바꾸고 다음 명령에서 그 디렉터리의 무언가를 사용하는 식으로 이어갈 수 있어요. 인라인 스크립트는 Packer가 실행되는 머신 안에서 간단한 작업을 처리하는 가장 쉬운 방법이에요.

  • script (string) - 실행할 스크립트의 경로예요. 경로는 절대 또는 상대일 수 있어요. 상대 경로라면 Packer가 실행될 때의 작업 디렉터리를 기준으로 해요.

  • scripts (array of strings) - 실행할 스크립트들의 배열이에요. 스크립트들은 지정된 순서대로 실행돼요. 각 스크립트는 격리된 상태로 실행되기 때문에, 한 스크립트의 변수 같은 상태가 다음 스크립트로 이어지지 않아요.

선택 파라미터:

  • env (map of strings) - execute_command 이전에 주입할 키/값 쌍의 맵이에요. Packer는 기본적으로 몇몇 환경 변수를 환경에 주입하기도 하는데, 이는 아래 섹션에서 다뤄요. 중복되는 env 설정은 environment_vars 설정을 오버라이드해요.

  • environment_vars (array of strings) - execute_command 이전에 주입할 키/값 쌍의 배열이에요. 형식은 key=value여야 해요. Packer는 기본적으로 몇몇 환경 변수를 환경에 주입하기도 하는데, 이는 아래 섹션에서 다뤄요. AWS, Azure, Google Compute, OpenStack에서 Windows VM을 빌드 중이고 WinRM으로 인스턴스에 연결할 때 Packer가 사용하는 생성된 비밀번호에 접근하고 싶다면, 템플릿 변수 {{.WinRMPassword}}를 사용해 환경 변수로 설정할 수 있어요. 예를 들어: "environment_vars": "WINRMPASS={{.WinRMPassword}}"

  • env_var_format (string) - 제공한 environment_vars를 파싱할 때 환경 변수를 올바르게 설정하기 위해 사용하는 문자열 템플릿이에요. Windows 호스트에서는 기본적으로 이 형식이 %s=%s &&로, Unix에서는 %s='%s'로 설정돼요. 이 형식을 바꿀 필요는 대부분 없겠지만, 필요한 곳의 사용 예시는 아래에서 볼 수 있어요.

  • execute_command (array of strings) - 스크립트를 실행하는 데 사용하는 명령이에요. Unix에서는 기본적으로 ["/bin/sh", "-c", "{{.Vars}}", "{{.Script}}"], Windows에서는 ["cmd", "/c", "{{.Vars}}", "{{.Script}}"]예요. 템플릿 엔진으로 처리돼요. 두 가지 변수를 사용할 수 있어요: 실행할 스크립트의 경로인 Script와, 구성되어 있으면 환경 변수 목록인 Vars예요.

    이 옵션을 설정하기로 했다면, 배열의 첫 요소가 사용하려는 셸 프로그램(예: "sh")이고 나중 요소 중 하나는 {{.Script}}임을 확인하세요.

    이 옵션은 매우 유연하게 쓸 수 있어요. 자신만의 셸 프로그램, 예를 들어 "/usr/local/bin/zsh"나 심지어 "powershell.exe"를 제공할 수도 있어요. 하지만 큰 힘에는 큰 책임이 따르죠 - 이런 명령들은 공식적으로 지원되지 않고, 기본 셸과 다른 셸을 사용하면 환경 변수 같은 것이 동작하지 않을 수 있어요.

    역호환성을 위해 {{.Command}}를 사용할 수도 있지만, {{.Script}}와 같은 방식으로 디코딩돼요. 명확성을 위해 {{.Script}}를 사용하는 것을 권장해요. 단일 명령만 실행하도록 설정해도 Packer는 그것을 임시 파일에 쓰고 스크립트로 실행하거든요.

    AWS, Azure, Google Compute, OpenStack에서 Windows VM을 빌드 중이고 WinRM으로 인스턴스에 연결할 때 Packer가 사용하는 생성된 비밀번호에 접근하고 싶다면, 템플릿 변수 {{.WinRMPassword}}를 사용해 환경 변수로 설정할 수 있어요.

  • inline_shebang (string) - inline으로 지정한 명령을 실행할 때 사용할 shebang 값이에요. 기본적으로 /bin/sh -e예요. inline을 사용하지 않는다면 이 구성은 아무 효과가 없어요. 중요: 이 값을 사용자 지정한다면 반드시 -e 플래그 같은 것을 포함하세요. 그렇지 않으면 개별 단계가 실패해도 프로비저너가 실패하지 않아요.

  • only_on (array of strings) - shell-local이 실행될 런타임 운영 체제들의 배열이에요. only_on이 설정된 특정 운영 체제에서만 shell-local을 실행할 수 있게 해 줘요. 기본적으로 only_on이 설정되지 않으면 shell-local은 항상 실행돼요.

  • use_linux_pathing (bool) - Windows 호스트에만 해당하는 옵션이에요. Windows Subsystem for Linux 기능이 활성화된 Windows 환경에서 Packer를 실행하고, Cmd 스크립트 대신 bash 스크립트를 호출하고 싶다면 이 플래그를 true로 설정해야 해요. 이 플래그는 Packer에게 Windows 경로 대신 Linux 하위 시스템 경로(예: C:/path/to/your/file 대신 /mnt/c/path/to/your/file)로 스크립트를 처리하라고 알려줘요. 이 기능을 사용하는 방법에 대한 자세한 안내는 아래 예시를 참고하세요. Windows 호스트가 아니거나 bash 스크립트를 실행하기 위해 shell-local 프로비저너를 사용할 의도가 없다면 이 옵션은 무시하세요.

  • valid_exit_codes (list of ints) - 스크립트의 유효한 종료 코드예요. 기본적으로 0이에요.

모든 프로비저너에 공통인 파라미터:

  • pause_before (duration) - 실행 전에 해당 시간만큼 대기해요.
  • max_retries (int) - 실패 시 프로비저너가 재시도할 최대 횟수. 기본값은 0. 0이면 오류를 재시도하지 않아요.
  • only (array of string) - 나열된 빌더에 대해서만 이름으로 프로비저너를 실행해요.
  • override (object) - 특정 빌더에 다른 설정으로 빌더를 오버라이드해요. 예:

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) - 프로비저너가 예를 들어 1h10m1s나 10m보다 오래 걸리면 타임아웃되어 실패해요.

실행 명령 (Execute Command)

많은 새 사용자에게 execute_command는 헷갈리기 쉬워요. 하지만 명령이 어떻게 실행되는지를 사용자 지정한다는 중요한 기능을 제공해요. 가장 흔한 사용 사례는 sudo 비밀번호 프롬프트를 다루는 것이에요. FreeBSD의 tcsh 같은 비POSIX 셸을 사용한다면 이것도 사용자 지정해야 할 수 있어요.

Windows Linux 하위 시스템 (The Windows Linux Subsystem)

shell-local 프로비저너는 로컬 운영 체제의 네이티브 셸에서 명령을 실행할 수 있게 하자는 취지로 설계됐어요. Windows의 경우 기본값에서 이것이 Cmd라고 가정했어요. 하지만 프로비저너 구성에서 execute_command와 use_linux_pathing 옵션을 수정하면 Windows Linux 하위 시스템의 일부로 bash 스크립트를 실행하는 것도 가능해요.

아래 예시는 완전히 동작하는 테스트 구성이에요.

이 기능의 한 가지 제한은 "inline"과 "command" 옵션을 사용할 수 없다는 거예요. "script"나 "scripts" 옵션만 사용하세요.

WSL은 베타 기능이므로 이 도구가 기대한 대로 동작한다는 보장은 없다는 점을 알아두세요. HCL2와 JSON:

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

build {
    sources = [
        "source.null.example"
    ]

    provisioner "shell-local"{
        environment_vars = ["PROVISIONERTEST=ProvisionerTest1"]
        execute_command = ["bash", "-c", "{{.Vars}} {{.Script}}"]
        use_linux_pathing = true
        scripts = ["C:/Users/me/scripts/example_bash.sh"]
    }
    provisioner "shell-local"{
        environment_vars = ["PROVISIONERTEST=ProvisionerTest2"]
        execute_command = ["bash", "-c", "{{.Vars}} {{.Script}}"]
        use_linux_pathing = true
        script = "C:/Users/me/scripts/example_bash.sh"
    }
}
{
  "builders": [
    {
      "type": "null",
      "communicator": "none"
    }
  ],
  "provisioners": [
    {
      "type": "shell-local",
      "environment_vars": ["PROVISIONERTEST=ProvisionerTest1"],
      "execute_command": ["bash", "-c", "{{.Vars}} {{.Script}}"],
      "use_linux_pathing": true,
      "scripts": ["C:/Users/me/scripts/example_bash.sh"]
    },
    {
      "type": "shell-local",
      "environment_vars": ["PROVISIONERTEST=ProvisionerTest2"],
      "execute_command": ["bash", "-c", "{{.Vars}} {{.Script}}"],
      "use_linux_pathing": true,
      "script": "C:/Users/me/scripts/example_bash.sh"
    }
  ]
}

기본 환경 변수 (Default Environmental Variables)

environment_vars 구성으로 사용자 지정 환경 변수를 지정할 수 있는 것 외에도, 이 프로비저너는 흔히 유용한 몇몇 환경 변수를 자동으로 정의해요:

  • PACKER_BUILD_NAME - Packer가 실행 중인 빌드의 이름으로 설정돼요. Packer가 여러 빌드를 만들고 공통 프로비저닝 스크립트에서 그것들을 조금 구분하고 싶을 때 가장 유용해요.
  • PACKER_BUILDER_TYPE - 스크립트가 실행되고 있는 머신을 만드는 데 사용된 빌더의 타입이에요. 특정 빌더로 빌드된 시스템에서만 스크립트의 일부 부분을 실행하고 싶을 때 유용해요.
  • PACKER_HTTP_ADDR - 파일 전송을 위한 HTTP 서버를 제공하는 빌더(hyperv, parallels, qemu, virtualbox, vmware 등)를 사용한다면 이것이 주소로 설정돼요. 프로비저너에서 이 주소를 사용해 큰 파일을 HTTP로 다운로드할 수 있어요. 기본 파일 프로비저너를 사용할 때 속도가 느리다면 유용할 수 있어요. winrm 커뮤니케이터를 사용하는 파일 프로비저너가 이런 어려움을 겪을 수 있어요.

스크립트 안전하게 작성하기 (Safely Writing A Script)

inline 옵션을 쓰든 직접 스크립트나 scripts를 넘기든, shell-local 프로비저너가 어떻게 동작하는지 몇 가지 이해하면 안전하고 쉽게 실행할 수 있어요. 이 이해는 과정에서 많은 시간을 절약해 줘요.

빌더당 한 번 (Once Per Builder)

넘겨준 shell-local 스크립트는 빌더당 한 번 실행돼요. amazon-ebs 빌더와 docker 빌더가 있으면 스크립트가 두 번 실행된다는 뜻이에요. 빌더가 3개면 각 빌더에 대해 3번 실행돼요.

항상 의도적으로 종료하기 (Always Exit Intentionally)

어떤 프로비저너가 실패하면 packer 빌드는 멈추고 모든 중간 아티팩트가 정리돼요.

셸 스크립트라면, 스크립트가 코드 0으로 종료해야 한다는 뜻이에요. 필요할 때 반드시 exit 0을 하도록 각별히 주의하세요.

사용 예시 (Usage Examples)

Windows 호스트 (Windows Host)

Windows에서 .cmd 파일을 실행하는 예시. HCL2와 JSON:

provisioner "shell-local" {
  environment_vars = ["SHELLLOCALTEST=ShellTest1"]
  scripts          = ["./scripts/test_cmd.cmd"]
}
{
  "type": "shell-local",
  "environment_vars": ["SHELLLOCALTEST=ShellTest1"],
  "scripts": ["./scripts/test_cmd.cmd"]
}

"test_cmd.cmd"의 내용:

echo %SHELLLOCALTEST%

Windows에서 인라인 명령을 실행하는 예시. 필요한 사용자 지정: tempfile_extension. HCL2와 JSON:

provisioner "shell-local" {
  environment_vars   = ["SHELLLOCALTEST=ShellTest2"]
  tempfile_extension = ".cmd"
  inline             = [echo "%SHELLLOCALTEST%"]
}
{
  "type": "shell-local",
  "environment_vars": ["SHELLLOCALTEST=ShellTest2"],
  "tempfile_extension": ".cmd",
  "inline": ["echo %SHELLLOCALTEST%"]
}

WSL을 사용해 Windows에서 bash 명령을 실행하는 예시. 필요한 사용자 지정: use_linux_pathing과 execute_command. HCL2와 JSON:

provisioner "shell-local" {
  environment_vars  = ["SHELLLOCALTEST=ShellTest3"]
  execute_command   = ["bash", "-c", "{{.Vars}} {{.Script}}"]
  use_linux_pathing = true
  script            = "./scripts/example_bash.sh"
}
{
  "type": "shell-local",
  "environment_vars": ["SHELLLOCALTEST=ShellTest3"],
  "execute_command": ["bash", "-c", "{{.Vars}} {{.Script}}"],
  "use_linux_pathing": true,
  "script": "./scripts/example_bash.sh"
}

example_bash.sh의 내용:

#!/bin/bash
echo $SHELLLOCALTEST

Windows에서 PowerShell 스크립트를 실행하는 예시. 필요한 사용자 지정: env_var_format과 execute_command. HCL2와 JSON:

provisioner "shell-local" {
  environment_vars = ["SHELLLOCALTEST=ShellTest4"]
  execute_command  = ["powershell.exe", "{{.Vars}} {{.Script}}"]
  env_var_format   = "$env:%s=\"%s\"; "
  script           = "./scripts/example_ps.ps1"
}
{
  "type": "shell-local",
  "environment_vars": ["SHELLLOCALTEST=ShellTest4"],
  "execute_command": ["powershell.exe", "{{.Vars}} {{.Script}}"],
  "env_var_format": "$env:%s=\"%s\"; ",
  "script": "./scripts/example_ps.ps1"
}

Windows에서 PowerShell 스크립트를 "inline"으로 실행하는 예시. 필요한 사용자 지정: env_var_format, tempfile_extension, execute_command. HCL2와 JSON:

provisioner "shell-local" {
  tempfile_extension = ".ps1"
  environment_vars   = ["SHELLLOCALTEST=ShellTest5"]
  execute_command    = ["powershell.exe", "{{.Vars}} {{.Script}}"]
  env_var_format     = "$env:%s=\"%s\"; "
  inline             = ["write-output $env:SHELLLOCALTEST"]
}
{
  "type": "shell-local",
  "tempfile_extension": ".ps1",
  "environment_vars": ["SHELLLOCALTEST=ShellTest5"],
  "execute_command": ["powershell.exe", "{{.Vars}} {{.Script}}"],
  "env_var_format": "$env:%s=\"%s\"; ",
  "inline": ["write-output $env:SHELLLOCALTEST"]
}

Unix 호스트 (Unix Host)

Unix에서 셸 스크립트를 실행하는 예시. HCL2와 JSON:

provisioner "shell-local" {
  environment_vars = ["PROVISIONERTEST=ProvisionerTest1"]
  scripts          = ["./scripts/example_bash.sh"]
}
{
  "type": "shell-local",
  "environment_vars": ["PROVISIONERTEST=ProvisionerTest1"],
  "scripts": ["./scripts/example_bash.sh"]
}

Unix에서 셸 스크립트를 "inline"으로 실행하는 예시. HCL2와 JSON:

provisioner "shell-local" {
  environment_vars = ["PROVISIONERTEST=ProvisionerTest2"]
  inline           = ["echo hello", "echo $PROVISIONERTEST"]
}
{
  "type": "shell-local",
  "environment_vars": ["PROVISIONERTEST=ProvisionerTest2"],
  "inline": ["echo hello", "echo $PROVISIONERTEST"]
}

Unix에서 Python 스크립트를 실행하는 예시. HCL2와 JSON:

provisioner "shell-local" {
  script = "hello.py"
  environment_vars = ["HELLO_USER=packeruser"]
  execute_command  = [
    "/bin/sh",
    "-c",
    "{{.Vars}} /usr/local/bin/python {{.Script}}"
  ]
}
{
  "type": "shell-local",
  "script": "hello.py",
  "environment_vars": ["HELLO_USER=packeruser"],
  "execute_command": [
    "/bin/sh",
    "-c",
    "{{.Vars}} /usr/local/bin/python {{.Script}}"
  ]
}
Where "hello.py" contains:

    import os

    print('Hello, %s!' % os.getenv("HELLO_USER"))