LabHub

Helm 차트 제작과 배포 · 템플릿과 values · 이론

템플릿 엔진 — 문자열을 만들어 YAML 인 척하는 일

LabHub 에서 이어서 보기

한 줄 요약

Helm 은 YAML 을 편집하지 않는다. Go 템플릿으로 문자열을 만들고, 다 만든 뒤에야 그것을 YAML 로 파싱한다.

왜 이게 필요했나

이 한 문장을 모르면 하루를 잃는다. resources 블록을 넣었는데 렌더링 결과에서 통째로 사라지거나, 값 하나를 넣었을 뿐인데 파서 오류가 난다. 원인은 거의 언제나 들여쓰기다. 템플릿 엔진에게 YAML 은 그냥 글자이고, 두 칸 어긋난 블록은 오류가 아니라 다른 뜻의 문서가 된다.

그래서 Helm 을 잘 쓴다는 것은 함수를 많이 외우는 일이 아니라, "이 자리에 문자열이 어떤 모양으로 끼어드는가"를 늘 의식하는 일이다. toYaml, nindent, default, required 네 가지가 실무의 8할을 덮는 이유도 전부 이 감각과 관련이 있다.

어떻게 동작하나

템플릿 처리는 파싱과 실행 두 단계다. 파싱에서 템플릿 텍스트가 구문 트리가 되고, 실행에서 데이터 컨텍스트(점 하나로 표시되는 그것)를 적용해 최종 문자열을 만든다. 실행 중에 쓸 수 있는 내장 객체는 .Values(기본값과 사용자 값의 병합 결과), .Release(이름·네임스페이스·리비전), .Chart(Chart.yaml 내용), .Capabilities(클러스터가 지원하는 API), .Files, .Template 이다.

핵심 함수는 이렇게 나뉜다.

| 함수 | 언제 쓰나 | 함정 |
| --- | --- | --- |
| default | 값이 비었을 때 대신 쓸 것을 정한다 | 기본값을 latest 로 두면 재현 불가능한 배포가 된다 |
| required | 없으면 렌더링 자체를 실패시킨다 | 메시지에 "무엇이 빠졌는지"를 적어야 쓸모가 있다 |
| toYaml | 맵이나 리스트를 통째로 문자열로 편다 | 혼자 쓰면 들여쓰기가 안 맞는다 |
| nindent | 앞에 줄바꿈을 넣고 n칸 들여쓴다 | indent 는 줄바꿈이 없어 첫 줄이 앞 키에 붙어 버린다 |
| range | 목록이나 맵을 펼친다 | 맵을 돌 때는 키 순서가 정렬되어 결정적이다 |
| include | 이름 붙은 템플릿을 불러온다 | template 은 결과를 바로 출력해 파이프로 이을 수 없다 |

templateinclude 의 차이는 사소해 보이지만 결정적이다. template 은 렌더링 결과를 그 자리에 그대로 뱉기 때문에 뒤에 파이프를 붙일 수 없다. include 는 결과를 문자열로 반환하므로 | nindent 4 같은 후처리를 이어 쓸 수 있다. 라벨 블록처럼 들여쓰기가 필요한 곳은 전부 include 를 쓰는 이유다.

값이 어디서 오는지도 규칙이 있다. 약한 것부터 차례로 차트의 values.yaml-f 로 준 값 파일(왼쪽에서 오른쪽 순서) → --set 이다. 여기서 한 가지를 더 기억해야 한다. 맵은 깊게 병합되지만 리스트는 통째로 교체된다. 환경변수를 리스트로 설계해 두면 값 파일 하나로 항목 하나만 바꾸는 일이 불가능해진다. 그래서 "덮어쓸 일이 잦은 값"은 맵으로 설계하는 편이 낫다.

마지막으로 관례 하나. ConfigMap 내용이 바뀌어도 파드는 그대로 남는다. 파드 스펙이 안 바뀌었으니 롤아웃이 일어나지 않는 것이다. 그래서 파드 템플릿 어노테이션에 설정 파일의 해시를 checksum/config 로 넣어 둔다. 내용이 바뀌면 해시가 바뀌고, 해시가 바뀌면 파드 스펙이 바뀌어 롤아웃이 자연스럽게 발생한다.

현장에서 만나는 모습

첫째, 사라진 블록. resourcesnodeSelector 가 렌더링 결과에 아예 없다면 값이 비었거나 들여쓰기가 어긋난 것이다. 필드 하나가 틀리면 오류가 나지만, 블록 전체의 들여쓰기가 어긋나면 조용히 다른 곳에 붙거나 사라진다. 이럴 때는 helm template 출력을 눈으로 보는 것이 유일한 진단법이다.

둘째, lookup 의 함정. 클러스터를 조회하는 lookup 함수는 helm template 에서는 언제나 빈 결과를 돌려준다. 로컬에서 잘 되던 조건문이 실제 설치에서 다르게 동작하는 대표적인 이유다.

셋째, 재현성. 템플릿 안에서 now 나 랜덤 함수를 쓰면 렌더링할 때마다 결과가 달라져 매 배포가 변경으로 잡힌다. GitOps 도구를 쓰면 영원히 동기화가 끝나지 않는 상태가 된다.

다음 실습에서 할 것

/root/helm/tpl/labhub-api 차트에서 default 로 빈 값을 메우고, required 로 필수 값을 강제해 일부러 실패시켜 본다. toYamlnindent 로 리소스 블록을 통째로 넘기고, range 로 환경변수를 펼친다. 값 파일 두 개와 --set 을 동시에 걸어 무엇이 이기는지 확인하고, 마지막에는 설정 해시와 보안 컨텍스트까지 갖춘 완성형 렌더링을 만든다.