LabHub
배우기 러닝패스 코스

Helm 차트 제작과 배포 · 차트 꾸리기와 사설 저장소 · 이론

저장소에 올라간 차트 — tgz 와 index.yaml 둘뿐이다

LabHub 에서 이어서 보기

한 줄 요약

차트 저장소는 서버 소프트웨어가 아니라 index.yaml 하나와 tgz 여러 개를 내려 주는 정적 HTTP 디렉터리다.

왜 이게 필요했나

차트를 잘 만들어 놓고도 팀에 퍼뜨리는 단계에서 다들 한 번씩 미끄러진다. 가장 흔한 방식은 깃 저장소 경로를 그대로 알려 주는 것이다. helm install myapp ./charts/myapp 는 잘 돈다. 그런데 이 방식에는 판이 없다. 어제 배포한 것과 오늘 배포한 것이 같은 차트인지 확인할 방법이 git log 밖에 없고, 되돌리려면 커밋 해시를 외워야 한다. 운영 사고 한가운데서 "그때 쓴 차트가 정확히 어느 상태였냐" 는 질문에 답을 못 하는 순간이 온다.

꾸려진 차트는 그 질문에 파일 하나로 답한다. catalog-1.1.0.tgz 는 이름과 판이 파일 이름에 박혀 있고, 내용이 한 바이트라도 다르면 색인의 digest 가 달라진다. 배포 기록에 "catalog 1.1.0" 이라고만 적혀 있어도 그게 정확히 무엇이었는지 다시 꺼내 볼 수 있다.

version 과 appVersion — 따로 움직이는 두 숫자

Chart.yaml 에는 숫자가 두 개 있고, 이 둘을 같은 것으로 착각하면 배포 대화가 계속 어긋난다.

| 필드 | 무엇의 판인가 | 언제 오르나 |
| --- | --- | --- |
| version | 차트 자신 | 템플릿·기본값·의존성이 바뀔 때 |
| appVersion | 차트가 담아 나르는 소프트웨어 | 애플리케이션 이미지 태그가 바뀔 때 |

리소스 제한 기본값만 고쳐도 version 은 올라가야 한다. 배포될 이미지는 그대로이므로 appVersion 은 그대로다. 반대로 애플리케이션만 새로 빌드해 태그를 바꾸면 appVersion 이 오르고, 그 값을 반영하려고 템플릿을 건드렸다면 version 도 함께 오른다. 그래서 두 값은 대부분 서로 다른 숫자이고, 같아지는 쪽이 오히려 우연이다.

파일 이름에는 version 만 들어간다. helm package --app-version 으로 appVersion 을 덮어써도 tgz 이름은 그대로다. helm search repo --versionshelm list 가 두 값을 나란히 보여 주는 이유가 여기 있다.

색인이 자동으로 갱신되지 않는다는 사실

index.yaml 은 저장소의 목차다. 그런데 이 목차는 tgz 를 올린다고 저절로 늘어나지 않는다. helm repo index <디렉터리> 를 사람이 다시 돌려야 하고, 이때 이 명령은 그 디렉터리에 지금 있는 tgz 만 보고 목차를 처음부터 새로 쓴다.

helm package catalog --version 1.2.0 -d stagehelm repo index stage --merge repo/index.yaml   # 옛 목차를 합친다

빌드 산출물만 들어 있는 stage 에서 --merge 없이 색인을 만들어 올리면, 목차에는 방금 만든 한 판만 남는다. tgz 파일은 서버에 그대로 있는데도 helm searchhelm pull 에서는 사라진다 — 파일이 지워진 게 아니라 목차에서 빠진 것이라, 디스크를 봐도 원인이 보이지 않는다.

받는 쪽도 목차를 캐시로 들고 있다. helm repo add 를 하면 ~/.cache/helm/repository/<이름>-index.yaml 로 사본이 내려오고, helm search 는 서버가 아니라 이 사본을 읽는다. 저장소에 새 판이 올라갔는데 검색에 안 나오면 대부분 helm repo update 를 안 돌린 것이다.

HTTP 저장소 말고 다른 길

차트를 나르는 방법이 index.yaml + tgz 하나만 있는 것은 아니다. 헬름 3 은 OCI 레지스트리를 차트 저장소로 쓸 수 있고, helm push <차트.tgz> oci://<레지스트리>/<경로> 로 올린다. 이 방식에는 목차가 없다 — 레지스트리가 태그 목록을 이미 가지고 있기 때문이다. 그래서 helm repo index --merge 를 빠뜨려 옛 판을 날리는 사고가 구조적으로 생기지 않고, 컨테이너 이미지와 같은 인증·권한 체계를 그대로 쓴다. 반대로 helm search repo 로 한 번에 훑어보기가 어렵고, 레지스트리 종류마다 지원 정도가 다르다.

무결성 쪽 장치도 있다. helm package --sign --key <이름> --keyring <경로> 로 꾸리면 tgz 옆에 .prov 파일이 함께 만들어지고, 받는 쪽은 helm verifyhelm install --verify 로 서명을 확인한다. 색인의 digest 는 파일이 전송 중에 바뀌지 않았음을 확인해 주지만 누가 만들었는지는 말해 주지 않는다는 점에서 둘의 역할이 다르다. 사내에서만 쓰는 저장소라면 대개 digest 로 충분하고, 외부에 배포하는 차트라면 서명을 붙이는 쪽으로 간다.

현장에서 만나는 모습

사내 저장소를 처음 세울 때 사람들은 전용 서버를 찾는다. 실제로는 오브젝트 스토리지 버킷 하나를 정적 웹으로 열고 CI 가 helm packagehelm repo index --merge 를 돌려 결과를 올리는 것으로 끝난다. 인증이 필요하면 그 앞에 리버스 프록시를 세운다. 이 단순함이 장점이자 함정이다 — 목차를 갱신하는 책임이 전적으로 파이프라인에 있기 때문에, 한 번 잘못 쓴 색인이 저장소 전체의 역사를 지운다.

의존성 쪽에서는 helm dependency buildupdate 를 구분하지 않아 생기는 문제가 잦다. updateChart.yaml 의 판 범위를 다시 풀어 Chart.lock 을 새로 쓴다. 범위를 ^1.0.0 처럼 열어 두었다면 어제와 오늘의 빌드가 다른 차트를 물어 올 수 있다. build 는 lock 에 적힌 판을 그대로 받아 오고, lock 이 Chart.yaml 과 어긋나 있으면 받아 오지 않고 멈춘다. CI 에서 update 를 쓰고 있다면 그 파이프라인은 재현 가능한 빌드를 하고 있지 않은 것이다.

다음 실습에서 할 것

차트를 두 판 꾸려 색인을 만들고, 그 디렉터리를 python3 -m http.server 로 띄워 진짜 저장소로 등록한다. 판을 골라 받아 보고, 세 번째 판을 --merge 로 합쳐 올린 뒤, 다른 차트의 의존성으로 잠근다. 마지막에는 선언과 잠금이 어긋난 상태를 일부러 만들어 helm dependency build 가 어떤 말로 멈추는지 직접 본다.