커스텀 프로비저너 만들기
커스텀 프로비저너 만들기 (Create Custom Provisioners)
Packer 프로비저너는 머신을 이미지로 바꾸기 전에, 실행 중인 머신에 소프트웨어를 설치하고 구성해요. 예를 들어 shell 프로비저너는 머신 안에서 shell 스크립트를 실행하죠.
프로비저너 플러그인은 packer.Provisioner 인터페이스를 구현하고 plugin.ServeProvisioner 함수를 사용해 서비스로 제공돼요.
:::warning 이 내용은 Packer와 Packer 플러그인에 대한 깊은 지식을 요구하는 고급 주제예요. :::
출처: Packer 공식 문서
본문
시작하기 전에 (Before You Begin)
개발을 시작하기 전에 다음 리소스를 검토하는 걸 권장해요.
- 플러그인 개발 - 개요
- Go 언어. 커스텀 플러그인은 Go로 작성해야 하므로, 이 가이드는 Go 언어에 익숙하다고 가정해요.
인터페이스 (The Interface)
프로비저너를 위해 구현해야 하는 인터페이스는 packer.Provisioner 인터페이스예요. 참고를 위해 아래에 다시 옮겨뒀어요. 소스 코드의 실제 인터페이스에는 각 메서드가 무엇을 해야 하는지 설명하는 기본 문서도 함께 담겨 있어요.
type Provisioner interface {
ConfigSpec() hcldec.ObjectSpec
Prepare(...interface{}) error
Provision(context.Context, Ui, Communicator, map[string]interface{}) error
}
Prepare 메서드
각 프로비저너의 Prepare 메서드는 템플릿에 주어진 설정으로 어떤 실행보다 먼저 호출돼요. 이 설정은 interface{} 타입의 배열로 전달되지만, 일반적으로 map[string]interface{}예요. Prepare 메서드는 이 설정을 내부 구조로 변환하고 검증하며, 오류가 있으면 반환하는 책임을 져요.
여러 매개변수가 있다면 최종 설정으로 병합되며, 나중 매개변수가 이전 설정을 덮어써요. 병합의 정확한 의미는 빌더 작성자에게 맡겨져 있어요.
interface{}를 의미 있는 구조로 디코딩하려면 mapstructure 라이브러리를 권장해요. Mapstructure는 interface{}를 받아 임의로 복잡한 struct로 디코딩해요. 오류가 있으면 매우 사람 친화적인 오류를 만들어내는데, Prepare 메서드에서 직접 반환할 수 있어요.
적극적으로 강제되지는 않지만, Prepare 메서드 실행에서 부작용(side effect)이 발생해서는 안 돼요. 구체적으로 파일을 만들거나, 가상 머신을 실행하는 등의 일을 하지 말라는 거예요. Prepare의 목적은 오로지 빌더를 구성하고 설정을 검증하는 것뿐이에요.
Prepare 메서드는 무엇이 실제로 일어나기 전에 사용자에게 오류를 보여줄 수 있도록 빌드 과정의 매우 초기에 호출돼요.
ConfigSpec 메서드
이 메서드는 hcldec.ObjectSpec을 반환해요. 이는 Packer와 HCL2 템플릿을 사용하는 데 필요한 스펙이에요. 이 함수를 사용하고 구현하는 방법은 object spec 문서를 확인하세요.
Provision 메서드
Provision 메서드는 머신이 실행 중이고 프로비저닝할 준비가 되었을 때 호출돼요. 프로비저너는 여기서 진짜 작업을 해야 해요.
이 메서드는 두 매개변수를 받아요: packer.Ui와 packer.Communicator. UI는 어떤 일이 진행 중인지 사용자에게 알리는 데 쓰이고, communicator는 실행 중인 머신과 통신하는 데 쓰여요. 이 시점에서 communicator는 반드시 연결되어 있다고 보장돼요.
Provision 메서드는 프로비저닝이 완료될 때까지 반환하지 않아야 해요.
map[string]interface{}는 build 템플릿 엔진이 제공하는 호스트와 IP 같은 빌드 관련 정보를 사용자에게 제공해요. 프로비저너는 이 정보를 원하는 대로 사용하거나 사용하지 않을 수 있어요.
Communicator 사용하기 (Using the Communicator)
packer.Communicator 매개변수와 인터페이스는 실행 중인 머신과 통신하는 데 사용돼요. 머신은 로컬(어떤 형태의 가상 머신이나 컨테이너 안)일 수도 있고 원격(클라우드 안)일 수도 있어요. communicator 인터페이스는 이를 추상화해서 전반적으로 통신이 동일하게 동작하게 해 줘요.
코드 자체에 대한 문서는 인터페이스를 사용하는 방법의 개요로 정말 훌륭해요. 이 문서부터 읽기 시작하면 좋아요. 다 읽고 나면 아래에서 몇 가지 사용 예시를 볼 수 있어요.
// Build the remote command.
var cmd packer.RemoteCmd
cmd.Command = "echo foo"
// We care about stdout, so lets collect that into a buffer. Since
// we don't set stderr, that will just be discarded.
var stdout bytes.Buffer
cmd.Stdout = &stdout
// Start the command
if err := comm.Start(&cmd); err != nil {
panic(err)
}
// Wait for it to complete
cmd.Wait()
// Read the stdout!
fmt.Printf("Command output: %s", stdout.String())