Packer 플러그인 로딩

Packer 플러그인 로딩

Packer가 로컬 파일시스템에서 플러그인을 어떻게 발견하는지 문서화하는 자료예요. Packer 초보자를 위한 것이 아니라, 호기심과 문제 해결(troubleshooting) 목적의 기술 레퍼런스입니다.

출처: Packer 공식 문서

본문

Packer가 로컬 파일시스템에서 플러그인을 어떻게 발견하는지 문서화하는 자료예요. Packer 초보자용이 아니라 호기심과 문제 해결을 위한 기술 레퍼런스입니다.

Packer를 처음 사용한다면 플러그인 설치 세부사항에 대해 Installing Plugins 페이지를 먼저 보는 것이 좋아요.

플러그인 소스 (Plugin sources)

소스(source)는 개념적으로 플러그인의 배포 지점이에요. 해당 위치의 URL입니다. 플러그인 바이너리를 어디서 얻는지 문서화하고, 호환된다면 Packer가 이 소스에서 플러그인을 원격으로 설치하도록 하기 위한 것이에요. Packer가 그 소스에서 원격으로 설치할 수 없다면 packer plugins install --path <path-to-binary> <source>로도 플러그인을 설치할 수 있습니다.

Packer 1.11.0부터 소스는 플러그인 설치에 필수예요. 소스는 또한 일련의 디렉터리로서 플러그인이 로컬 파일시스템에 설치된 위치를 반영합니다.

예시: github.com/hashicorp/hashicups는 다음과 같은 디렉터리 트리 $HOME/.config/packer/plugins/github.com/hashicorp/hashicups가 됩니다.

소스를 선언할 때 채택할 규칙들이 있어요.

  • 스키마 없음 (예: https://)
  • 프래그먼트 없음 (예: #section)
  • 쿼리 없음 (예: ?page=1)
  • URL은 호스트와 URL의 두 부분 이상을 반드시 포함해야 해요.
  • URL은 호스트 외에 15부분을 초과할 수 없어요.
  • /가 소스에 사용할 수 있는 유일한 구분자예요.
  • 전체 소스 URL은 플러그인을 설치할 파일시스템과 호환되어야 해요. Packer는 URL 내의 지원되지 않는 문자를 검사하지 않지만, 유효한 디렉터리 트리를 만들 수 없으면 오류를 냅니다.

소스 URL의 마지막 부분이 플러그인의 이름이에요. 반드시 packer 또는 packer-plugin 접두어 없이 플러그인의 원시 이름(raw name)을 가져야 합니다.

Note: 플러그인이 GitHub에 있다면 Packer가 찾을 수 있도록 저장소 이름이 packer-plugin-<name>처럼 생겨야 해요. 하지만 템플릿이나 CLI에 지정하는 소스 주소는 packer-plugin-을 제외해야 합니다.

예시: https://github.com/hashicorp/packer-plugin-hashicups는 소스로 선언할 때 github.com/hashicorp/hashicups가 됩니다.

루트 플러그인 디렉터리 (Root plugin directory)

플러그인은 루트 플러그인 디렉터리 아래에 설치되어야 해요. 기본값은 다음 중 하나입니다.

UNIX:

  • $HOME/.packer.d/plugins: 옛 스타일의 플러그인 설치 디렉터리. ~/.packer.d가 존재하면 아래의 것보다 우선합니다.
  • $HOME/.config/packer/plugins: ~/.packer.d를 대체하는 곳. 기존 구성 디렉터리가 없으면 Packer가 첫 사용 시 자동으로 만듭니다.

WINDOWS:

  • %APPDATA%/packer.d/plugins: Windows 시스템의 유일한 기본값.

이 동작을 바꾸고 싶다면 두 가지 대안이 있어요.

  • PACKER_CONFIG_DIR: 이 환경 변수로 구성 디렉터리 위치를 커스터마이즈할 수 있어요. 플러그인은 이 구성 디렉터리의 plugin 하위 디렉터리에 설치됩니다.
  • PACKER_PLUGIN_PATH: 이 환경 변수로 플러그인이 설치되는 위치를 커스터마이즈할 수 있어요. 루트 플러그인 디렉터리를 가리키며, 그 아래에 일반적인 디렉터리 계층구조가 적용됩니다.

Note: PACKER_PLUGIN_PATH는 PACKER_CONFIG_DIR보다 우선합니다. PACKER_PLUGIN_PATH가 정의되면 PACKER_CONFIG_DIR은 플러그인 설치와 로딩에 무시됩니다.

환경 변수의 전체 목록은 Configuring Packer를 참고하세요.

플러그인 설치 디렉터리 (Plugin installation directories)

모든 플러그인은 루트 플러그인 디렉터리 아래에 설치되어야 해요. 이 디렉터리 아래에서 플러그인은 소스 URL과 일치하는 일련의 디렉터리에 설치됩니다.

예시: github.com/hashicorp/hashicups는 다음과 같은 계층구조로 변환됩니다.

<plugin-root-dir>
└── github.com
    └── hashicorp
        └── hashicups

플러그인은 그 예시의 리프(leaf) 디렉터리에 설치돼요. 각 플러그인 버전은 버전당 바이너리가 하나만 있어야 하며, 해당 버전과 일치하는 SHA256SUM 파일을 함께 두어야 합니다. SHA256SUM 파일의 내용은 플러그인 바이너리의 내용에 대한 sha256 합계의 원시 16진 다이제스트예요.

플러그인 바이너리 명명 규칙

Packer가 플러그인 바이너리를 발견하고 로드하려면 다음 명명 규칙을 따라야 해요.

packer-plugin-<name>_<version>_<api_version>_<os>_<arch>[.exe]

sha256sum 파일도 같은 규칙을 따라야 하며, 이름에 SHA256SUM 접미어가 붙습니다.

packer-plugin-<name>_<version>_<api_version>_<os>_<arch>[.exe]_SHA256SUM

이름의 구성 요소에 대한 규칙은 다음과 같아요.

  • name: 플러그인의 원시 이름. 상위 디렉터리 이름과 일치해야 해요. 예: hashicups.
  • version: 플러그인의 semver 버전. v<major>.<minor>.<patch>[-<prerelease>] 규칙을 따라야 해요. 메타데이터 정보는 버전 문자열에 포함되면 안 됩니다.
  • api_version: 플러그인이 컴파일된 플러그인 API 버전. 보통 x<api_major>.<api_minor>처럼 생겼어요.
  • os: 플러그인이 빌드된 OS. 예: darwin(macOS), windows, linux 등.
  • arch: 플러그인이 빌드된 마이크로아키텍처. 예: arm64, amd64, 386 등.

Note: .exe 접미어는 Windows 플러그인에만 사용돼요. 다른 OS는 플러그인 이름에 그 접미어를 추가하면 안 되며, 추가하면 Packer가 무시합니다.

로딩 과정 (Loading process)

packer build 또는 packer validate를 실행하면 Packer는 템플릿에서 커맨드를 실행하기 위해 플러그인을 발견하고 로드하려 시도해요. 두 단계가 있습니다.

  • 명시적으로 요구된 플러그인 로드.
  • 나머지 설치된 플러그인 발견.

명시적으로 요구된 플러그인은 HCL2 고유의 기능이에요. required_plugins 블록을 통해 선언됩니다. 이를 통해 해당 플러그인 요구사항에 정확한 소스와 버전 제약을 지정할 수 있어요.

이렇게 선언된 각 플러그인은 두 번째 단계가 수집하는 것보다 우선합니다.

두 번째 단계는 나머지 설치된 플러그인을 낙관적으로 발견하려 시도해요. 플러그인의 이름은 packer-plugin- 부분을 뺀 바이너리 이름에서 유추됩니다.

예: github.com/hashicorp/hashicups 플러그인이 설치되어 이 단계에서 발견되면, 이 플러그인의 컴포넌트를 사용하는 각각의 이름은 hashicups로 시작합니다.

플러그인을 발견할 때 Packer는 그 describe 커맨드를 실행해요. describe 커맨드는 플러그인의 기능을 보여주고 각각의 버전과 API 버전에 대한 정보를 제공합니다.

보통 플러그인에 describe를 호출하면 볼 수 있는 것은 이것이에요.

> $HOME/.packer.d/plugins/github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.2_x5.0_linux_amd64 describe
{"version":"1.0.2","sdk_version":"0.5.1","api_version":"x5.0","builders":["order"],"post_processors":["receipt"],"provisioners":["toppings"],"datasources":["coffees","ingredients"]}

Note: 플러그인의 describe 출력 정보는 플러그인 이름 안에 지정된 버전과 일치해야 해요.

요약하면, Packer가 플러그인을 후보로 나열할지 결정하기 전에 수행하는 검사 목록은 다음과 같아요.

  • describe가 보고한 버전은 플러그인 이름의 버전과 일치해야 해요. 즉 describe가 v1.0.2를 보고하는데 바이너리가 v1.0.1로 이름 지어져 있으면 Packer는 거부합니다.
  • 버전은 정규(canonical) 형태여야 해요 (버전은 가장 단순한 표현이어야 함). 즉 v1.00.01은 비정규이고 v1.0.1은 정규입니다. 이 버전 불일치가 있는 플러그인은 거부돼요.
  • API 버전은 플러그인이 보고하는 것과 이름이 일치해야 해요. 즉 describe가 x5.1을 보고하는데 바이너리가 x5.0을 포함하면 Packer는 거부합니다.
  • 버전이 최종(final)이 아니라면 프리릴리스 프래그먼트를 포함할 수 있어요. 단 반드시 -dev여야 하며, 다른 것은 거부됩니다.

여러 플러그인이 설치되어 있으면 Packer는 항상 잠재적 제약과 일치하는 가장 높은 버전을 선택해요. 버전 근본(radical)이 동일하면 최종 릴리스가 프리릴리스보다 우선합니다: v1.0.0 < v1.0.1-dev < v1.0.1.

알려진 한계 (Known limits)

명시적 발견이 항상 의도한 것을 얻도록 보장하지만, 자동 발견은 응집(cosion) 문제를 일으킬 수 있어요.

예를 들어 플러그인이 다른 소스로 두 번 설치되면 Packer는 둘 다 발견하지만, 이 플러그인에서 컴포넌트를 요청할 때 최종적으로 실행될 플러그인이 무엇인지는 정의되지 않은 동작(undefined behavior)이에요.

예시:

<plugin-root-dir>
├── github.com
│    └── hashicorp
│       └── hashicups
│          └── packer-plugin-hashicups_v1.0.2_x5.0_linux_amd64
└── gitlab.com
     └── hashicorp
        └── hashicups
           └── packer-plugin-hashicups_v1.0.2_x5.0_linux_amd64

이 경우 두 플러그인 모두 hashicups이고 서로 겹칠 수 있는 일련의 컴포넌트를 정의하므로 모호성 문제가 생겨요. 따라서 이 모호성을 해결할 required_plugins 없이 hashicups-coffees 데이터소스를 사용하면 두 플러그인 중 하나가 실행되지만, 어느 것이 실행될지는 보장되지 않습니다.