Helm 차트 제작과 배포 · 라이브러리 차트 · 이론
라이브러리 차트 — 설치할 수 없어서 쓸모 있는 차트
한 줄 요약
라이브러리 차트는 define 만 담고 스스로는 아무것도 렌더하지 않는 차트이며, 표준 라벨과 공통 워크로드 모양을 한 곳에 못 박는 자리다.
왜 이게 필요했나
차트를 처음 만들 때 _helpers.tpl 은 늘 같은 모양으로 태어난다. helm create 가 넣어 주는 fullname, labels, selectorLabels 세 개다. 문제는 차트가 열 개가 됐을 때 시작된다. 그 열 개의 _helpers.tpl 은 처음에는 복사본이었지만, 한 팀이 app.kubernetes.io/component 를 추가하고 다른 팀이 team 라벨을 넣으면서 서로 다른 생물이 된다.
이게 왜 문제가 되는지는 라벨을 쓰는 쪽을 보면 분명해진다. 모니터링 규칙은 app.kubernetes.io/instance 로 대상을 고르고, 네트워크 정책은 app.kubernetes.io/name 으로 고르고, 비용 대시보드는 team 으로 묶는다. 차트 하나에서 라벨 한 줄이 빠지면 그 워크로드만 조용히 규칙 밖으로 나간다. 장애 상황에서 "이 파드만 지표가 없다" 는 보고가 올라오고, 원인은 반년 전 복사본에서 빠진 한 줄이다.
표준 라벨을 고치는 일을 한 번에 끝낼 수 있어야 한다는 요구가 여기서 나온다. 그러려면 그 규칙이 파일 하나에 있어야 하고, 그 파일은 여러 차트가 의존성으로 가져갈 수 있어야 한다.
어떻게 동작하나
Chart.yaml 의 type 을 library 로 두면 헬름은 이 차트를 설치 대상에서 뺀다. 실제로 설치를 시도하면 이렇게 거절한다.
Error: INSTALLATION FAILED: library charts are not installablehelm template 도 같은 이유로 멈춘다. 대신 이 차트는 다른 차트의 dependencies 에 들어갈 수 있고, 들어가는 순간 그 안의 모든 define 이 부모 차트의 템플릿 이름 공간에 합류한다. 부모는 include "platform-lib.deployment" . 한 줄로 Deployment 한 벌을 통째로 받아 쓴다.
템플릿 이름이 차트 전체에서 하나의 이름 공간이라는 점이 핵심이자 함정이다. 서브차트가 정의한 이름과 부모가 정의한 이름이 같으면 나중에 읽힌 쪽이 이기고, 부모가 나중에 읽힌다. 실수로 겹치면 오류도 경고도 없이 조용히 다른 템플릿이 쓰인다. define 이름 앞에 차트 이름을 붙이는 관례는 예쁘게 보이려는 게 아니라 이 충돌을 막으려는 것이다.
values 안의 문자열을 다시 렌더하는 tpl
라이브러리와 짝을 이루어 자주 쓰이는 함수가 tpl 이다. values 에 적힌 문자열은 기본적으로 그냥 글자라서 중괄호가 들어 있어도 그대로 출력된다.
note: "{{ .Release.Name }} in {{ .Release.Namespace }}"이 값을 {{ .Values.note }} 로 꽂으면 중괄호까지 그대로 나간다. {{ tpl .Values.note . }} 로 꽂으면 그 자리에서 한 번 더 템플릿으로 해석되어 릴리스 이름이 들어간다. 사용자가 values 로 자유 형식 설정을 넘기는 차트가 이 방식을 쓴다 — 어노테이션 묶음, 사이드카 정의, 설정 파일 본문 같은 것들이다. 대신 values 가 템플릿을 실행할 수 있다는 뜻이므로, 신뢰할 수 없는 값을 tpl 에 넘기면 안 된다.
언제 쓰고 언제 쓰지 않나
라이브러리 차트가 답이 되는 조건은 의외로 좁다. 여러 차트가 같은 규칙을 지켜야 하고, 그 규칙이 앞으로도 바뀔 것일 때다. 표준 라벨이 정확히 그렇다 — 모니터링과 정책이 라벨로 대상을 고르므로 규칙이 하나여야 하고, 회사가 커지면서 팀 라벨이나 비용 센터 라벨이 하나씩 늘어난다.
반대로 차트가 두세 개뿐이고 규칙도 굳어 있다면, 라이브러리를 만드는 비용이 이득보다 크다. 의존성 선언이 늘고, 판올림 흐름이 하나 더 생기고, 새로 온 사람은 _helpers.tpl 이 아니라 charts/ 안의 tgz 를 열어 봐야 템플릿을 읽을 수 있다. 이 마지막 것이 특히 과소평가된다 — 렌더 결과가 이상할 때 그 템플릿이 어느 파일에 있는지 찾는 일이 한 단계 멀어진다.
그래서 도입 여부보다 먼저 정할 것은 판올림 흐름이다. 라이브러리 판을 올리면 소비 차트들이 언제 따라오는가. Chart.yaml 의 판 범위를 0.1.0 처럼 고정해 두면 소비 차트를 하나씩 손으로 올려야 하고, ^0.1.0 처럼 열어 두면 helm dependency update 를 돌리는 시점에 따라 결과가 달라진다. CI 가 helm dependency build 로 잠금대로만 받아 가게 해 두면 후자의 위험은 사라진다 — 잠금 파일이 저장소에 있으니 어느 판으로 빌드했는지가 기록에 남는다.
현장에서 만나는 모습
라이브러리 차트를 도입할 때 가장 자주 부딪히는 것은 갱신 시점이다. helm dependency update 는 그 순간의 라이브러리를 tgz 로 떠서 부모의 charts/ 에 넣는다. 그래서 라이브러리에서 라벨 한 줄을 고쳐도, 소비하는 차트가 의존성을 다시 받기 전까지는 옛 사본이 계속 쓰인다. 사내 저장소를 쓴다면 라이브러리 판을 올리고, 소비 차트들의 Chart.yaml 판 범위를 조정하고, CI 가 helm dependency build 로 잠금대로 받아 가게 하는 흐름이 필요하다.
그리고 라이브러리에 너무 많이 넣지 않는 편이 낫다. Deployment 한 벌을 통째로 공통화하면 처음에는 깔끔하지만, 차트마다 다른 요구가 하나씩 생기면서 if 가 쌓인다. 실무에서 잘 버티는 경계는 대개 라벨·이름 규칙·공통 어노테이션까지이고, 워크로드 본체는 각 차트가 갖는 쪽이다. 공통화의 이득과 분기의 비용이 어디서 뒤집히는지는 팀마다 다르므로, 라이브러리가 커지기 시작하면 한 번 멈춰 서서 재야 한다.
다음 실습에서 할 것
라이브러리 차트를 직접 만들어 설치가 거절되는 것을 확인하고, 두 애플리케이션 차트가 같은 Deployment 템플릿과 라벨 규칙을 값만 바꿔 쓰게 만든다. tpl 로 values 안의 템플릿 문자열을 되살리고, 마지막에는 부모가 같은 이름을 다시 정의했을 때 어느 쪽이 렌더되는지 두 차트를 나란히 놓고 확인한다.