Go 1.16 embed 패키지로 정적 파일 바이너리에 포함하기
배경
사내 CLI 도구를 Go로 만들면서 템플릿 파일들을 함께 배포해야 하는 상황이었다. 기존에는 go-bindata를 사용했는데, 빌드 파이프라인에 별도 단계를 추가해야 해서 번거로웠다.
Go 1.16에서 embed 패키지가 표준으로 추가되면서 이 문제가 깔끔하게 해결됐다.
기본 사용법
단일 파일을 임베드하는 경우:
package main
import (
_ "embed"
"fmt"
)
//go:embed template.txt
var templateContent string
func main() {
fmt.Println(templateContent)
}
주석과 변수 선언 사이에 다른 코드가 들어가면 안 된다는 점을 주의해야 한다.
여러 파일 임베드
디렉토리 전체를 임베드하려면 embed.FS를 사용한다:
package main
import (
"embed"
"io/fs"
"log"
)
//go:embed templates/*
var templatesFS embed.FS
func main() {
data, err := templatesFS.ReadFile("templates/config.yaml")
if err != nil {
log.Fatal(err)
}
// 또는 fs.FS 인터페이스로 사용
fs.WalkDir(templatesFS, "templates", func(path string, d fs.DirEntry, err error) error {
if !d.IsDir() {
log.Println(path)
}
return nil
})
}
실제 적용 사례
CLI 도구에서 프로젝트 초기화 템플릿을 제공하는 코드:
//go:embed scaffolds/*
var scaffolds embed.FS
func InitProject(projectType string) error {
dir := fmt.Sprintf("scaffolds/%s", projectType)
return fs.WalkDir(scaffolds, dir, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() {
return err
}
content, err := scaffolds.ReadFile(path)
if err != nil {
return err
}
targetPath := strings.TrimPrefix(path, dir+"/")
return os.WriteFile(targetPath, content, 0644)
})
}
장점
- 서드파티 도구 불필요
- 빌드 파이프라인 단순화
- 단일 바이너리 배포 가능
- fs.FS 인터페이스 호환으로 기존 코드와 통합 용이
Go 1.16 이상을 사용하는 프로젝트라면 적극 활용할 만하다.