HashiCorp의 배포 도구인 Waypoint를 HashiCorp의 Waypoint 살펴보기
, HashiCorp Waypoint로 Kubernetes 클러스터에 배포하기에서 설명했는데 사용해보면 Waypoint는 아주 간단해서 금방 익힐 수 있지만, 라이프사이클을 담당하고 있는 build, deploy, release에서 사용하는 플러그인이 핵심이라는 것을 알 수 있다.
다음과 waypoint.hcl
을 보면 build
, deploy
, release
단계를 정의하고 있고 각 단계에서 use
스탠자(Stanza, {}
로 묶이는 코드 블록을 이렇게 부른다.)를 이용해서 플러그인을 사용하고 있다.
여기서 pack
, kubernetes
플러그인을 사용하고 있는데 결국 이 플러그인을 사용해서 어떻게 빌드하고 배포하고 릴리스하는지를 정하는 것이므로 환경에 맞게 필요한 기능이 있다면 플러그인을 어떻게 확장할 수 있는지가 궁금해진다.
내장 플러그인
Waypoint의 플러그인 페이지에 가면 내장된 플러그인을 볼 수 있는데 이 글을 쓰는 시점의 기준으로는 다음과 같은 내장 플러그인이 있다.
- AWS EC2
- AWS ECS
- AWS SSM
- Azure Container Instances
- Docker
- Exec
- Google Cloud Run
- Kubernetes
- Netlify
- Nomad
- CloudNative Buildpacks: Buildpacks
- Vault
이름을 보면 어떤 서비스 혹은 프로그램과 연관되었는지 알 수 있긴 한데 각 플러그인은 build
, deploy
, release
단계에 맞춰져 있고 한 단계만을 위한 플러그인도 여러 단계에서 같이 사용할 수 있는 플러그인도 있다.
Waypoint의 버전이 초기이므로 플러그인이 많지는 않고 다 써보진 않았지만 약간 복잡한 실 서비스에 쓰려면 아쉬운 점이 보일 거로 생각한다. 그래서 원하는 기능을 적용하려면 플러그인을 직접 만들어야 한다.
Waypoint 플러그인
플러그인은 Waypoint 애플리케이션과 통신하는 별도의 바이너리라고 할 수 있고 통신은 gRPC를 사용한다. 그래서 gRPC를 사용할 수 있는 어떤 언어로도 플러그인을 만들 수는 있지만 Go 언어로 Waypoint SDK를 제공하고 있으므로 이를 사용하면 쉽게 플러그인을 만들 수 있다.
플러그인과 관련된 용어를 먼저 살펴보자.
- 플러그인: Waypoint 애플리케이션이 실행하는 컴포넌트가 담긴 컴파일된 바이너리이다.
- 컴포넌트: 애플리케이션의 라이프 사이클(빌드, 디플로이, 릴리스)을 담당하고 여러 인터페이스를 구현해서 만들어진다.
- 인터페이스: 컴포넌트에서
Configurable
,ReleaseManager
같은 동작을 구현한다. - 출력값: Protocol Buffer로 직렬화해서 컴포넌트 간에 주고받을 수 있다.
Go 프로젝트를 빌드하는 플러그인
이 글은 공식 사이트에서 제공하는 Creating Waypoint Plugins를 따라 해 본 것이다. 한번 따라 해보면 플러그인을 통해서 어느 정도 확장할 수 있을지 감을 잡을 수 있다. 앞에서 얘기한 대로 Go 언어용 SDK를 이용해서 만들 것이다.
Go 언어로 플러그인을 작성하려면 아래 의존성이 설치되어 있어야 한다.
- Go 1.14+
- Protocol Buffer 컴파일러
- Go 용 Protocol Buffer 컴파일 플러그인
여기서 만들 플러그인은 Builder
컴포넌트를 만들어서 Waypoint의 build
단계에서 Go 프로젝트를 빌드해서 바이너리로 만들어주는 플러그인을 만들 것이다.
Waypoint가 제공하는 예제 프로젝트를 클론 받는다.
이 예제프로젝트에서 플러그인 제작을 시작할 수 있는 템플릿을 제공한다. 이 템플릿에서 컴포넌트와 인터페이스가 구현되어 있는데 복사해서 사용하기 쉽게 clone.sh
를 제공한다.
clone.sh
를 실행하면서 플러그인 이름, 복사할 경로, Go 패키지 명을 지정하면 코드에서 이름과 패키지도 알아서 바꿔주면서 복사한다. cd ../gobuilder
로 복사된 폴더로 이동한다.
복사된 파일은 위와 같은 파일을 가지고 있다. 여기서는 builder
만 사용할 것이지만 플러그인 템플릿이므로 다른 파일도 포함되어 있다.
의존성이 잘 설치되었는지 일단 make
로 빌드를 해보자. 앞에서 언급한 의존성이 제대로 설치되지 않았다면 빌드 단계에서 오류가 날 것이다.
./bin/linux_amd64/waypoint-plugin-gobuilder
처럼 bin
폴더 아래 OS와 아키텍처별로 컴파일된 Go 바이너리가 생기고 이 파일이 플러그인이다.
플러그인 컴포넌트 등록
소스를 수정해 보자. main.go
파일은 아래와 같이 작성되어 있다.
여기서 sdk.Main
함수로 Waypoint SDK를 설정하고 go-plugin
시스템에 인터페이스를 등록하는데 sdk.WithComponents
함수를 이용해서 플러그인에서 구현한 컴포넌트를 정리한다. 모든 컴포넌트가 등록되어 있지만 Builder
만 필요하므로 나머지는 제거한다.
설정 처리
컴포넌트는 Waypoint 인터페이스를 구현한 Go Struct인데 builder/builder.go
파일을 보면 Builder
Struct를 볼 수 있다. 템플릿에서는 Directory
를 가진 BuildConfig
타입의 config
필드를 가지고 있다.
여기서 만들 gobuilder
라는 플러그인을 생각하면 Go 프로젝트를 빌드하기 위해 빌드한 바이너리의 이름과 소스 코드의 위치를 입력받아야 할 것이다. 상상해보면 다음과 같이 waypoint.hcl
을 통해 설정을 컴포넌트에 전달하게 될 것이다.
Waypoint가 이 설정 파라미터를 Struct로 매핑하는 방법을 알고 있으므로 컴포넌트의 Struct에서는 config
에서 직렬화할 필드와 태그를 추가하면 된다. builder/builder.go
파일의 BuildConfig
를 다음과 같이 변경한다.
각 필드는 생략할 수 있으므로 반드시 optional
로 지정해야 한다.
Waypoint가 설정을 파싱할 때 컴포넌트가 Configurable
인터페이스를 구현했는지 확인하고 구현했다면 config
struct
가 반환한 참조에서 Config
메서드를 호출한다. 여기서 Waypoint는 이 참조를 사용해서 설정을 직렬화한다.
builder/builder.go
을 보면 이미 Configurable
인터페이스를 구현한 코드를 볼 수 있다.
컴포넌트는 ConfigurableNotify
인터페이스를 사용해서 설정의 유효성을 검사한다. ConfigurableNotify
는 Waypoint가 HCL 설정을 읽을 수 이를 Config
가 반환한 struct
로 직렬화한 후에 호출된 ConfigSet
메서드를 정의하고 있다. 이 인터페이스는 다음과 같이 생겼다.
BuildFunc
같은 컴포넌트용 인터페이스 메서드가 호출되기 전에는 항상 ConfigSet
이 호출되는데 여기서 설정을 검사하고 오류를 반환할 수 있다. 템플릿 코드에서는 이미 Builder
에 ConfigSet
이 다음과 같이 구현되어 있다.
이를 전달받은 소스 코드의 폴더인 Source
필드의 폴더가 실제로 존재하는지 검사하도록 수정해 보자. os.Stat
으로 해당 폴더가 있는지 검사하고 없으면 오류를 반환하게 다음과 같이 수정한다.
글이 길어져서 이 글은 HashiCorp의 Waypoint의 커스텀 플러그인 작성하기 #2으로 이어진다.
Comments