テンプレート関数でvaluesを安全に差し込む
한국어 원문으로 표시합니다.
목표
템플릿 함수로 values 를 안전하게 매니페스트에 꽂고, 값이 없을 때·필수일 때·블록일 때 각각 어떤 도구를 써야 하는지 손으로 익힙니다.
왜 중요한가
Helm 템플릿은 YAML 편집기가 아니라 문자열 생성기입니다. 최종 결과가 문자열로 완성된 뒤에야 YAML 파서가 그것을 읽습니다. 그래서 들여쓰기가 두 칸 어긋나면 오류가 나는 대신 블록이 통째로 사라지거나 엉뚱한 부모 밑으로 들어갑니다. toYaml 로 편 블록에 nindent 를 반드시 붙이는 이유가 여기 있습니다 — indent 는 앞의 줄바꿈을 만들어 주지 않아 첫 줄이 앞 키에 그대로 붙습니다. 값 설계도 마찬가지입니다. default 는 "없어도 되는 값"에, required 는 "없으면 배포하면 안 되는 값"에 씁니다. 이 둘을 뒤집으면 잘못된 기본값으로 조용히 배포되거나, 반대로 아무나 못 쓰는 차트가 됩니다. 마지막으로 값의 우선순위는 낮은 것부터 차트의 values.yaml, -f 로 준 파일들(왼쪽에서 오른쪽), --set 순서이며, 맵은 깊게 병합되지만 리스트는 통째로 교체된다는 점을 꼭 기억하세요.
단계
/root/helm/tpl/labhub-api에 차트를 만드세요(helm create labhub-api로 시작해도 좋습니다).values.yaml의image.repository는nginx,image.tag는"1.27"로 두고Chart.yaml의appVersion은"1.26"으로 둡니다. 그다음helm template labhub-api /root/helm/tpl/labhub-api > /root/helm/tpl/out/base.yaml로 렌더링을 저장하세요. 결과에 Deployment 가 있고 컨테이너 이미지가저장소:태그형태여야 하며, 중괄호나<no value>가 남아 있으면 안 됩니다.- 컨테이너 이미지 태그를
{{ .Values.image.tag | default .Chart.AppVersion }}처럼 쓰고,helm template labhub-api /root/helm/tpl/labhub-api --set image.tag="" > /root/helm/tpl/out/default.yaml을 저장하세요. 태그를 비웠는데도 이미지에는nginx:1.26처럼 태그가 붙어 있어야 하며, 기본값으로latest를 쓰면 안 됩니다. values.yaml에ingress.enabled: true와ingress.host: api.labhub.local을 두고,/root/helm/tpl/labhub-api/templates/ingress.yaml에서 호스트를required로 감싸세요(예:{{ required "ingress.host 를 반드시 지정하세요" .Values.ingress.host }}). 그다음helm template labhub-api /root/helm/tpl/labhub-api --set ingress.host="" > /root/helm/tpl/out/required-error.txt 2>&1로 일부러 실패시키고 그 출력을 저장하세요. 파일에는 렌더링 실패 메시지와 함께 어떤 키가 빠졌는지가 보여야 합니다.values.yaml의resources를limits.cpu: 500m,limits.memory: 512Mi,requests.cpu: 100m,requests.memory: 128Mi로 채우고, Deployment 컨테이너에서{{- toYaml .Values.resources | nindent 12 }}형태로 통째로 넘기세요./root/helm/tpl/out/base.yaml을 다시 렌더링하면 컨테이너의resources.limits.cpu와resources.requests.memory가 보여야 합니다.values.yaml에env를 맵으로 두고APP_MODE: server,LOG_LEVEL: info,TZ: Asia/Seoul세 개를 정의하세요. Deployment 에서range $k, $v := .Values.env로 컨테이너env목록을 만듭니다./root/helm/tpl/out/base.yaml의 컨테이너env는 정확히 3개여야 하고 그중 하나의 이름이LOG_LEVEL이어야 합니다./root/helm/tpl/labhub-api/templates/_helpers.tpl에define으로 차트 식별 문자열(이름-버전)을 만들고, Deployment 의metadata.annotations에labhub.io/chart: {{ include "labhub-api.chart" . }}를 붙이세요. 그리고/root/helm/tpl/out/include-note.txt에template과include의 차이를 한 줄로 적으세요 — include 는 결과를 문자열로 반환하므로 파이프로 이어 후처리(들여쓰기)를 할 수 있다는 내용이 들어가야 합니다./root/helm/tpl/values-base.yaml에replicaCount: 2,env.LOG_LEVEL: info,image.tag: base를,/root/helm/tpl/values-stage.yaml에replicaCount: 4,env.LOG_LEVEL: debug,image.tag: stage를 두세요. 그다음helm template labhub-api /root/helm/tpl/labhub-api -f /root/helm/tpl/values-base.yaml -f /root/helm/tpl/values-stage.yaml --set image.tag=cli > /root/helm/tpl/out/merged.yaml을 실행하세요. 결과의replicas는 4,LOG_LEVEL은debug, 이미지 태그는cli여야 합니다. 마지막으로/root/helm/tpl/out/precedence.txt에 우선순위를 낮은 것부터 한 줄씩 적으세요: 차트의values.yaml,-f values-base.yaml,-f values-stage.yaml,--set.- ConfigMap 템플릿을 추가해
values.yaml의config맵을data로 내보내고, 파드 템플릿의metadata.annotations에checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}를 붙이세요. 파드 수준spec.template.spec.securityContext.runAsNonRoot는true가 되어야 합니다. 완성된 렌더링을/root/helm/tpl/out/final.yaml로 저장하세요(오브젝트 3개 이상, 컨테이너 env 3개 이상,<no value>없음). 그리고helm lint /root/helm/tpl/labhub-api > /root/helm/tpl/out/lint.txt도 저장하며[ERROR]가 없어야 합니다.
참고
- 실습 파드는 실습마다 새로 뜨므로 다른 실습에서 만든 차트는 남아 있지 않습니다. 이 실습의 차트도
/root/helm/tpl아래에 처음부터 만듭니다 — 차트가 곧 재현 가능한 패키지라는 사실이 여기서 드러납니다. helm template ... -s templates/deployment.yaml로 한 파일만 볼 수 있고,--debug를 붙이면 실패한 렌더링의 중간 결과까지 보여 줍니다.- 4·5·6번에서 템플릿을 고친 뒤에는
/root/helm/tpl/out/base.yaml을 반드시 다시 렌더링해 덮어쓰세요. 1·4·5·6번 채점이 모두 이 파일 하나를 봅니다. - 흔한 실수 1:
indent를 써서 블록의 첫 줄이 앞 키에 붙는 것. 앞에 줄바꿈이 필요하면nindent입니다. - 흔한 실수 2: 값 파일 두 개를 주면 리스트도 합쳐질 거라 기대하는 것. 맵은 깊게 병합되지만 리스트는 통째로 교체됩니다. 이 실습에서
env를 리스트가 아닌 맵으로 설계한 이유입니다.
values 를 참조해 렌더링하기
/root/helm/tpl/labhub-api 에 차트를 만드세요(helm create labhub-api 로 시작해도 좋습니다). values.yaml 의 image.repository 는 nginx, image.tag 는 "1.27" 로 두고 Chart.yaml 의 appVersion 은 "1.26" 으로 둡니다. 그다음 helm template labhub-api /root/helm/tpl/labhub-api > /root/helm/tpl/out/base.yaml 로 렌더링을 저장하세요. 결과에 Deployment 가 있고 컨테이너 이미지가 저장소:태그 형태여야 하며, 중괄호나 <no value> 가 남아 있으면 안 됩니다.
이미지는 저장소와 태그를 각각 values 에서 가져와 조합합니다. 렌더링 결과에 중괄호나 '' 가 남았다면 참조한 키가 values 에 없다는 뜻입니다.
default 로 빈 값 메우기
컨테이너 이미지 태그를 {{ .Values.image.tag | default .Chart.AppVersion }} 처럼 쓰고, helm template labhub-api /root/helm/tpl/labhub-api --set image.tag="" > /root/helm/tpl/out/default.yaml 을 저장하세요. 태그를 비웠는데도 이미지에는 nginx:1.26 처럼 태그가 붙어 있어야 하며, 기본값으로 latest 를 쓰면 안 됩니다.
태그를 비운 채 렌더링해도 이미지에 태그가 붙어야 합니다. 대신 쓸 값을 Chart.yaml 에서 가져오면 차트와 앱 버전이 자연히 맞습니다. latest 는 답이 아닙니다.
required 로 필수 값 강제하기
values.yaml 에 ingress.enabled: true 와 ingress.host: api.labhub.local 을 두고, /root/helm/tpl/labhub-api/templates/ingress.yaml 에서 호스트를 required 로 감싸세요(예: {{ required "ingress.host 를 반드시 지정하세요" .Values.ingress.host }}). 그다음 helm template labhub-api /root/helm/tpl/labhub-api --set ingress.host="" > /root/helm/tpl/out/required-error.txt 2>&1 로 일부러 실패시키고 그 출력을 저장하세요. 파일에는 렌더링 실패 메시지와 함께 어떤 키가 빠졌는지가 보여야 합니다.
필수 값이 비면 렌더링 자체가 실패해야 합니다. 오류 메시지에는 어떤 키가 빠졌는지 적으세요. 실패 출력은 표준 오류로 나오므로 저장할 때 함께 받아야 합니다.
toYaml 과 nindent 로 블록 넘기기
values.yaml 의 resources 를 limits.cpu: 500m, limits.memory: 512Mi, requests.cpu: 100m, requests.memory: 128Mi 로 채우고, Deployment 컨테이너에서 {{- toYaml .Values.resources | nindent 12 }} 형태로 통째로 넘기세요. /root/helm/tpl/out/base.yaml 을 다시 렌더링하면 컨테이너의 resources.limits.cpu 와 resources.requests.memory 가 보여야 합니다.
리소스 제한처럼 통째로 넘길 블록은 한 줄씩 쓰지 않습니다. 앞에 줄바꿈이 필요한지 아닌지가 두 들여쓰기 함수의 차이입니다.
range 로 환경변수 펼치기
values.yaml 에 env 를 맵으로 두고 APP_MODE: server, LOG_LEVEL: info, TZ: Asia/Seoul 세 개를 정의하세요. Deployment 에서 range $k, $v := .Values.env 로 컨테이너 env 목록을 만듭니다. /root/helm/tpl/out/base.yaml 의 컨테이너 env 는 정확히 3개여야 하고 그중 하나의 이름이 LOG_LEVEL 이어야 합니다.
values 의 맵을 키와 값 두 변수로 받아 반복합니다. 값은 따옴표로 감싸는 편이 안전합니다. 정확히 세 개가 나와야 합니다.
네임드 템플릿 정의하고 include 하기
/root/helm/tpl/labhub-api/templates/_helpers.tpl 에 define 으로 차트 식별 문자열(이름-버전)을 만들고, Deployment 의 metadata.annotations 에 labhub.io/chart: {{ include "labhub-api.chart" . }} 를 붙이세요. 그리고 /root/helm/tpl/out/include-note.txt 에 template 과 include 의 차이를 한 줄로 적으세요 — include 는 결과를 문자열로 반환하므로 파이프로 이어 후처리(들여쓰기)를 할 수 있다는 내용이 들어가야 합니다.
define 으로 만든 조각을 어노테이션 자리에 끼워 넣습니다. 두 호출 방식 중 파이프로 이어 쓸 수 있는 쪽이 무엇인지, 그 이유가 무엇인지 메모로 남기세요.
값 우선순위 확인하기
/root/helm/tpl/values-base.yaml 에 replicaCount: 2, env.LOG_LEVEL: info, image.tag: base 를, /root/helm/tpl/values-stage.yaml 에 replicaCount: 4, env.LOG_LEVEL: debug, image.tag: stage 를 두세요. 그다음 helm template labhub-api /root/helm/tpl/labhub-api -f /root/helm/tpl/values-base.yaml -f /root/helm/tpl/values-stage.yaml --set image.tag=cli > /root/helm/tpl/out/merged.yaml 을 실행하세요. 결과의 replicas 는 4, LOG_LEVEL 은 debug, 이미지 태그는 cli 여야 합니다. 마지막으로 /root/helm/tpl/out/precedence.txt 에 우선순위를 낮은 것부터 한 줄씩 적으세요: 차트의 values.yaml, -f values-base.yaml, -f values-stage.yaml, --set.
값 파일 두 개와 명령줄 옵션을 한 번에 걸어 보세요. 값 파일은 준 순서가 의미를 가집니다. 결과를 보고 낮은 것부터 순서를 적으세요.
설정 해시와 보안 컨텍스트까지 붙이기
ConfigMap 템플릿을 추가해 values.yaml 의 config 맵을 data 로 내보내고, 파드 템플릿의 metadata.annotations 에 checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} 를 붙이세요. 파드 수준 spec.template.spec.securityContext.runAsNonRoot 는 true 가 되어야 합니다. 완성된 렌더링을 /root/helm/tpl/out/final.yaml 로 저장하세요(오브젝트 3개 이상, 컨테이너 env 3개 이상, <no value> 없음). 그리고 helm lint /root/helm/tpl/labhub-api > /root/helm/tpl/out/lint.txt 도 저장하며 [ERROR] 가 없어야 합니다.
ConfigMap 내용이 바뀌어도 파드가 안 바뀌는 문제를 막는 관례가 있습니다. 렌더링된 설정 파일의 해시를 파드 어노테이션에 넣으세요. 파드 수준 보안 설정도 함께 채웁니다.