# Helm CLI를 감싸는 대신 Go SDK로 배포 제어하기
서비스에서 Helm 차트를 배포해야 할 때 가장 빠른 방법은 exec.Command("helm", "upgrade", ...)로 CLI를 호출하는 것입니다. 동작은 하지만, 릴리스 상태를 알려면 출력 문자열을 파싱해야 하고, 실행 환경마다 helm 바이너리 버전이 달라지며, 에러는 종료 코드 하나로만 돌아옵니다.
Helm 3은 CLI와 라이브러리를 분리해 Go SDK를 제공합니다. 이 글에서는 SDK의 구성, action.Configuration 초기화 방법, 릴리스 저장 드라이버의 의미, 그리고 CLI 래핑에서 넘어올 때 달라지는 지점을 정리합니다.
# 1. CLI 래핑의 한계
// 흔한 출발점
out, err := exec.Command("helm", "upgrade", "--install", name, chart,
"-n", ns, "-f", valuesPath).CombinedOutput()
if err != nil {
return fmt.Errorf("helm 실패: %s", out) // 무엇이 왜 실패했는지 구조화되지 않음
}
| 문제 | 내용 |
|---|---|
| 결과 파싱 | 릴리스 리비전·상태·렌더 결과를 문자열에서 뽑아야 함 |
| 환경 의존 | 컨테이너 이미지에 helm 바이너리를 넣고 버전을 관리해야 함 |
| 자격 증명 | kubeconfig 파일을 디스크에 써야 함 |
| 에러 처리 | 종료 코드만으로는 "이미 존재함"과 "권한 없음"을 구분하기 어려움 |
| values 조립 | 파일로 직렬화 → CLI가 다시 파싱하는 왕복 |
SDK를 쓰면 이 다섯 가지가 전부 Go 타입으로 바뀝니다.
# 2. SDK 구성
공식 문서가 정리한 주요 패키지는 다음과 같습니다.
| 패키지 | 역할 |
|---|---|
action | Helm 동작의 "클라이언트". CLI 자신도 이 패키지를 사용 |
chart, chartutil | 차트 로딩과 조작 |
cli | Helm 환경변수·출력·values 파일 처리 |
release | Release 객체와 상태 정의 |
action은 CLI 자신이 쓰는 것과 같은 경로입니다. CLI로 되는 일은 SDK로도 됩니다.
호환성도 명시돼 있습니다.
breaking changes will only be made with a major version release or to remediate a security issue.
다만 문서는 SDK가 CLI에서 분리되는 과정의 흔적이 남아 있다는 점("some rough edges remaining")도 함께 밝히므로, 마이너 업그레이드 시 릴리스 노트는 확인하는 편이 좋습니다.
# 3. action.Configuration 초기화
모든 동작의 출발점입니다. Init은 네 가지를 받습니다.
| 인자 | 의미 |
|---|---|
RESTClientGetter | 어느 클러스터에 접속할지 |
namespace | 대상 네임스페이스(빈 문자열이면 전체 조회) |
helmDriver | 릴리스 상태 저장 방식 |
| 로그 함수 | 내부 로그 출력 |
import (
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli"
)
func newConfig(namespace string) (*action.Configuration, error) {
settings := cli.New()
settings.SetNamespace(namespace)
cfg := new(action.Configuration)
if err := cfg.Init(
settings.RESTClientGetter(),
namespace,
os.Getenv("HELM_DRIVER"), // 비어 있으면 secret
func(format string, v ...interface{}) {
slog.Debug(fmt.Sprintf(format, v...))
},
); err != nil {
return nil, err
}
return cfg, nil
}
여기서 Configuration은 네임스페이스에 묶입니다. 여러 네임스페이스를 다루는 서비스라면 요청마다 만들거나 네임스페이스별로 캐시해야 합니다. 하나를 만들어 전역에서 재사용하면 엉뚱한 네임스페이스에 배포됩니다.
# 4. 릴리스 저장 드라이버
Helm은 릴리스 상태를 클러스터에 저장합니다. HELM_DRIVER로 방식을 고릅니다.
| 값 | 저장 위치 | 특징 |
|---|---|---|
secret (기본) | 네임스페이스의 Secret | 릴리스 매니페스트가 압축돼 들어감 |
configmap | ConfigMap | 과거 방식, Secret 대신 쓸 때 |
sql (베타) | 외부 DB | HELM_DRIVER_SQL_CONNECTION_STRING 필요 |
이 선택이 실무에서 걸리는 지점은 크기 제한입니다. Secret과 ConfigMap 모두 오브젝트 크기 상한(약 1MiB)이 있어 CRD가 많은 대형 차트에서 문제가 될 수 있습니다. 또한 서비스 계정에 해당 네임스페이스의 Secret 읽기·쓰기 권한이 필요하므로, RBAC를 짤 때 차트가 만드는 리소스뿐 아니라 릴리스 저장용 Secret 권한도 포함해야 합니다.
# 5. install / upgrade / list
func Upgrade(ctx context.Context, cfg *action.Configuration,
name, ns string, ch *chart.Chart, vals map[string]interface{}) (*release.Release, error) {
up := action.NewUpgrade(cfg)
up.Namespace = ns
up.Install = true // 없으면 설치 (helm upgrade --install)
up.Atomic = true // 실패 시 롤백
up.Wait = true // 리소스가 Ready 될 때까지 대기
up.Timeout = 10 * time.Minute
up.MaxHistory = 10 // 릴리스 히스토리 상한
return up.RunWithContext(ctx, name, ch, vals)
}
values가 map[string]interface{}라는 점이 CLI 대비 가장 큰 차이입니다. 파일 직렬화 왕복 없이 구조체에서 바로 만들 수 있고, 타입 오류를 컴파일 시점이나 검증 단계에서 잡을 수 있습니다.
차트는 로컬 디렉터리, 패키지 파일, 저장소 어디서든 로드할 수 있습니다.
ch, err := loader.Load("./charts/workspace") // 디렉터리 또는 .tgz
목록 조회는 상태 필터를 지원합니다.
list := action.NewList(cfg)
list.All = true
list.SetStateMask() // Filter 설정을 반영. 호출하지 않으면 기본 상태만 조회
releases, err := list.Run()
SetStateMask() 호출을 빠뜨려 "분명히 있는데 목록에 안 나오는" 상황이 자주 생깁니다.
# 6. 운영에서 챙길 것
# 6-1. 동시 실행
같은 릴리스에 대한 동시 작업은 충돌합니다. Helm은 릴리스가 진행 중이면 another operation (install/upgrade/rollback) is in progress로 거부하는데, 서비스가 여러 레플리카로 뜬다면 애플리케이션 레벨에서 릴리스 단위 잠금을 두는 편이 안전합니다.
# 6-2. Wait와 타임아웃
Wait = true는 편리하지만 요청 스레드를 길게 잡습니다. 웹 요청 안에서 동기로 기다리기보다, 작업을 큐에 넣고 상태를 폴링하게 만드는 구조가 낫습니다. RunWithContext를 쓰면 상위 컨텍스트 취소가 전달됩니다.
# 6-3. dry-run으로 렌더 검증
배포 전에 렌더 결과만 확인할 수 있습니다. values 스키마 오류를 클러스터에 손대기 전에 잡는 용도로 유용합니다.
inst := action.NewInstall(cfg)
inst.DryRun = true
inst.ClientOnly = true // 클러스터 접속 없이 렌더만
rel, err := inst.Run(ch, vals)
fmt.Println(rel.Manifest)
# 7. 트러블슈팅
| 증상 | 원인 | 해결 |
|---|---|---|
| 엉뚱한 네임스페이스에 배포됨 | Configuration을 전역 재사용 | 네임스페이스별로 생성 |
| 릴리스가 목록에 안 보임 | SetStateMask() 미호출 | 필터 설정 후 호출 |
another operation in progress | 동시 요청 또는 중단된 이전 작업 | 릴리스 단위 잠금, 필요 시 상태 확인 후 롤백 |
| Secret 권한 오류 | 릴리스 저장용 Secret 권한 누락 | RBAC에 Secret CRUD 추가 |
| 대형 차트 저장 실패 | 오브젝트 크기 상한 | 차트 분리 또는 sql 드라이버 검토 |
| 업그레이드 후 히스토리 폭증 | MaxHistory 미설정 | 상한 지정 |
# 8. 마무리
- Helm CLI 래핑은 시작은 빠르지만 결과 파싱·환경 의존·에러 구분에서 비용이 누적됩니다.
- SDK의
action패키지는 CLI가 사용하는 것과 같은 경로이므로 기능 차이를 걱정할 필요가 없습니다. action.Configuration은 네임스페이스에 묶인 객체입니다. 전역 재사용은 사고로 이어집니다.- 릴리스 저장 드라이버 선택은 RBAC 설계와 오브젝트 크기 상한에 직접 영향을 줍니다.