ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Helm] 가이드 - 템플릿
    공부/쿠버네티스&헬름 2025. 3. 15. 21:31

    named template(때로는 partial 또는 subtemplate이라고도 함)은 단순히 파일 내부에 정의되고 이름이 부여된 템플릿입니다. 이를 생성하는 두 가지 방법과 사용하는 몇 가지 다른 방법을 살펴볼 것입니다.

    템플릿 이름을 지정할 때 중요한 세부 정보는 템플릿 이름은 전역적이라는 것입니다. 동일한 이름으로 두 개의 템플릿을 선언하면 마지막에 로드된 템플릿이 사용됩니다. 서브차트의 템플릿은 최상위 레벨 템플릿과 함께 컴파일되므로, 템플릿 이름을 차트별 고유한 이름으로 지정하는 데 주의해야 합니다.

    일반적인 명명 규칙 중 하나는 정의된 각 템플릿에 차트 이름을 접두사로 붙이는 것입니다. 예를 들어 {{ define "mychart.labels" }}와 같이 사용합니다. 특정 차트 이름을 접두사로 사용하면 서로 다른 두 차트가 동일한 이름의 템플릿을 구현하여 발생할 수 있는 충돌을 피할 수 있습니다.

    이 동작은 차트의 다른 버전에도 적용됩니다. mychart 버전 1.0.0이 특정 방식으로 템플릿을 정의하고, mychart 버전 2.0.0이 기존 명명된 템플릿을 수정하는 경우, 마지막에 로드된 템플릿이 사용됩니다. 차트 이름에 버전을 추가하여 이 문제를 해결할 수 있습니다. 예를 들어 {{ define "mychart.v1.labels" }}{{ define "mychart.v2.labels" }}와 같이 사용할 수 있습니다.

    define과 template

    define 액션을 사용하면 템플릿 파일 내부에 명명된 템플릿을 만들 수 있습니다. 문법은 다음과 같습니다.

    {{- define "MY.NAME" }}
      # body of template here
    {{- end }}

    예를 들어, Kubernetes label 블록을 캡슐화하는 템플릿을 정의할 수 있습니다.

    {{- define "mychart.labels" }}
      labels:
        generator: helm
        date: {{ now | htmlDate }}
    {{- end }}

    이제 이 템플릿을 기존 ConfigMap 내부에 포함하고 template 액션을 사용하여 삽입할 수 있습니다.

    {{- define "mychart.labels" }}
      labels:
        generator: helm
        date: {{ now | htmlDate }}
    {{- end }}
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}-configmap
      {{- template "mychart.labels" }}
    data:
      myvalue: "Hello World"
      {{- range $key, $val := .Values.favorite }}
      {{ $key }}: {{ $val | quote }}
      {{- end }}

    템플릿 엔진이 이 파일을 읽으면 template "mychart.labels"가 호출될 때까지 mychart.labels에 대한 참조를 저장해 둡니다. 그런 다음 해당 템플릿을 현재 위치에 렌더링합니다. 따라서 결과는 다음과 같습니다.

    # Source: mychart/templates/configmap.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: running-panda-configmap
      labels:
        generator: helm
        date: 2016-11-02
    data:
      myvalue: "Hello World"
      drink: "coffee"
      food: "pizza"

    참고: define는 이 예시에서처럼 template으로 호출되지 않는 한 출력을 생성하지 않습니다.

    관례적으로 Helm 차트는 이러한 템플릿들을 파셜(partials) 파일인 _helpers.tpl 안에 넣습니다. 이 함수를 그곳으로 옮겨 보겠습니다. _helpers.tpl에 관련된 내용은 https://brownbears.tistory.com/699 에서 자세히 확인할 수 있습니다.

    템플릿 범위 설정

    위에서 정의한 템플릿에서는 어떤 객체도 사용하지 않고 함수만 사용했습니다. 차트 이름과 차트 버전을 포함하도록 정의된 템플릿을 수정해 보겠습니다.

    {{/* Generate basic labels */}}
    {{- define "mychart.labels" }}
      labels:
        generator: helm
        date: {{ now | htmlDate }}
        chart: {{ .Chart.Name }}
        version: {{ .Chart.Version }}
    {{- end }}
    
    ------
    # Source: mychart/templates/configmap.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: moldy-jaguar-configmap
      labels:
        generator: helm
        date: 2021-03-06
        chart:
        version:

    이름과 버전은 정의된 템플릿의 스코프에 없었습니다. define으로 생성된 명명된 템플릿이 렌더링될 때, template 호출에 의해 전달된 스코프를 받게 됩니다. 우리 예시에서는 다음과 같이 템플릿을 포함했습니다.

    {{- template "mychart.labels" }}

    스코프가 전달되지 않았으므로 템플릿 내에서 .을 통해 아무것도 접근할 수 없습니다. 하지만 이 문제는 쉽게 해결할 수 있습니다. 간단히 템플릿에 스코프를 전달하면 됩니다.

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}-configmap
      {{- template "mychart.labels" . }}

    template 호출의 끝에 .을 전달한 것을 주목하세요. .Values.Values.favorite 또는 원하는 어떤 스코프든 마찬가지로 쉽게 전달할 수 있습니다. 하지만 우리가 원하는 것은 최상위 스코프입니다. 명명된 템플릿의 컨텍스트에서 $는 어떤 전역 스코프가 아니라 여러분이 전달한 스코프를 참조하게 됩니다.

    include

    {{- define "mychart.app" -}}
    app_name: {{ .Chart.Name }}
    app_version: "{{ .Chart.Version }}"
    {{- end -}}

    위 템플릿에 labels: 섹션과 data: 섹션 모두에 삽입하고 싶다고 가정해 봅시다.

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}-configmap
      labels:
        {{ template "mychart.app" . }}
    data:
      myvalue: "Hello World"
      {{- range $key, $val := .Values.favorite }}
      {{ $key }}: {{ $val | quote }}
      {{- end }}
    {{ template "mychart.app" . }}
    
    ---------
    # Source: mychart/templates/configmap.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: measly-whippet-configmap
      labels:
        app_name: mychart
    app_version: "0.1.0"
    data:
      myvalue: "Hello World"
      drink: "coffee"
      food: "pizza"
    app_name: mychart
    app_version: "0.1.0"

    app_version의 들여쓰기가 두 곳 모두 잘못된 것을 확인하세요. 삽입된 템플릿의 텍스트가 왼쪽으로 정렬되어 있기 때문입니다. template은 액션이지 함수가 아니므로 template 호출의 출력을 다른 함수로 전달할 방법이 없습니다. 데이터는 단순히 현재 위치에 삽입됩니다.

    이러한 경우를 해결하기 위해 Helm은 template의 대안으로 템플릿의 내용을 현재 파이프라인으로 가져와 파이프라인의 다른 함수로 전달할 수 있는 기능을 제공합니다.

    위의 예시를 indent 함수를 사용하여 mychart.app 템플릿을 올바르게 들여쓰도록 수정했습니다.

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}-configmap
      labels:
    {{ include "mychart.app" . | indent 4 }}
    data:
      myvalue: "Hello World"
      {{- range $key, $val := .Values.favorite }}
      {{ $key }}: {{ $val | quote }}
      {{- end }}
    {{ include "mychart.app" . | indent 2 }}
    
    -----
    # Source: mychart/templates/configmap.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: edgy-mole-configmap
      labels:
        app_name: mychart
        app_version: "0.1.0"
    data:
      myvalue: "Hello World"
      drink: "coffee"
      food: "pizza"
      app_name: mychart
      app_version: "0.1.0"

    Helm 템플릿에서는 YAML 문서의 출력 형식을 더 잘 처리할 수 있도록 단순히 template 대신 include를 사용하는 것이 더 바람직하게 여겨집니다.

    템플릿 안에서 파일 접근하기

    템플릿이 아닌 파일을 가져와서 해당 내용을 템플릿 렌더러를 거치지 않고 직접 삽입을 해야 될 때가 있습니다. Helm은 .Files 객체를 통해 파일에 접근하는 기능을 제공합니다. 하지만 템플릿 예제를 시작하기 전에 이 기능이 어떻게 작동하는지에 대한 몇 가지 주의 사항이 있습니다.

    • Helm 차트에 추가 파일을 포함하는 것은 괜찮습니다. 이러한 파일은 번들로 묶입니다. 하지만 Kubernetes 객체의 저장 용량 제한으로 인해 차트 크기는 1MB 미만이어야 합니다.
    • 일반적으로 보안상의 이유로 .Files 객체를 통해 접근할 수 없는 파일들이 있습니다.
      • templates/ 디렉토리의 파일은 접근할 수 없습니다.
      • .helmignore를 사용하여 제외된 파일은 접근할 수 없습니다.
      • 부모 차트를 포함하여 Helm 애플리케이션 서브차트 외부의 파일은 접근할 수 없습니다.
    • 차트는 UNIX 모드 정보를 보존하지 않으므로 .Files 객체를 사용할 때 파일 수준의 권한은 파일 접근 가능성에 영향을 미치지 않습니다.

    기본 예시

    이러한 주의 사항을 염두에 두고, 세 개의 파일을 읽어 ConfigMap에 넣는 템플릿을 작성해 보겠습니다. 시작하려면 차트에 세 개의 파일을 추가하고 이 세 파일을 모두 mychart/ 디렉토리 바로 아래에 넣을 것입니다.

    -- config1.toml
    message = Hello from config 1
    
    -- config2.toml
    message = This is config 2
    
    -- config3.toml
    message = Goodbye from config 3

    이 파일들은 모두 간단한 TOML 파일이고 파일 이름을 알고 있으므로 range 함수를 사용하여 이 파일들을 반복 처리하고 그 내용을 ConfigMap에 삽입할 수 있습니다.

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: {{ .Release.Name }}-configmap
    data:
      {{- $files := .Files }}
      {{- range tuple "config1.toml" "config2.toml" "config3.toml" }}
      {{ . }}: |-
        {{ $files.Get . }}    
      {{- end }}

    path helper

    파일 작업을 할 때 파일 경로 자체에 대한 몇 가지 표준 작업을 수행하는 것이 매우 유용할 수 있습니다. 이를 돕기 위해 Helm은 Go의 path 패키지에 있는 많은 함수를 가져와서 사용할 수 있도록 제공합니다. 이러한 함수들은 Go 패키지에서와 동일한 이름으로 접근할 수 있지만, 첫 글자는 소문자로 변경됩니다. 예를 들어, Basebase가 됩니다.

    가져온 함수는 다음과 같습니다.

    • base
    • dir
    • ext
    • isAbs
    • clean

    glob 패턴

    차트가 커짐에 따라 파일을 더 많이 정리해야 할 필요성이 커질 수 있으며, glob 패턴의 모든 유연성을 활용하여 특정 파일을 추출하는 데 도움이 되는 .Files.Glob(pattern string) 메서드를 제공합니다.

    .GlobFiles 타입을 반환하므로, 반환된 객체에서 Files 타입의 모든 메서드를 호출할 수 있습니다.

    아래와 같은 디렉토리 구조가 있다고 가정합니다.

    foo/
      foo.txt foo.yaml
    
    bar/
      bar.go bar.conf baz.yaml

    다양한 glob 패턴을 사용할 수 있습니다.

    {{ $currentScope := .}}
    {{ range $path, $_ :=  .Files.Glob  "**.yaml" }}
        {{- with $currentScope}}
            {{ .Files.Get $path }}
        {{- end }}
    {{ end }}
    
    --- 또는
    {{ range $path, $_ :=  .Files.Glob  "**.yaml" }}
          {{ $.Files.Get $path }}
    {{ end }}

    configmap과 secret 유틸리티 함수

    (Helm 2.0.2 이상에서 사용 가능)

    파일 내용을 ConfigMap과 Secret 모두에 넣어 런타임 시 파드에 마운트하는 것은 매우 일반적인 요구 사항입니다. 이를 돕기 위해 Files 타입에 몇 가지 유틸리티 메서드를 제공합니다.

    더욱 체계적인 관리를 위해 이러한 메서드를 Glob 메서드와 함께 사용하는 것이 특히 유용합니다.

    위의 Glob 예제의 디렉토리 구조가 주어졌을 때 다음과 같이 표현이 가능합니다.

    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: conf
    data:
    {{ (.Files.Glob "foo/*").AsConfig | indent 2 }}
    ---
    apiVersion: v1
    kind: Secret
    metadata:
      name: very-secret
    type: Opaque
    data:
    {{ (.Files.Glob "bar/*").AsSecrets | indent 2 }}

    인코딩

    파일을 가져와서 템플릿이 base64로 인코딩하여 성공적인 전송을 보장하도록 할 수 있습니다.

    apiVersion: v1
    kind: Secret
    metadata:
      name: {{ .Release.Name }}-secret
    type: Opaque
    data:
      token: |-
        {{ .Files.Get "config1.toml" | b64enc }}  

    lines

    템플릿에서 파일의 각 줄에 접근하는 것이 바람직할 때가 있습니다. 이를 위해 편리한 Lines 메서드를 제공합니다.

    range 함수를 사용하여 Lines를 반복 처리할 수 있습니다.

    data:
      some-file.txt: {{ range .Files.Lines "foo/bar.txt" }}
        {{ . }}{{ end }}

    규칙

    templates/ 구조

    templates/ 디렉토리는 다음과 같이 구성되어야 합니다.

    • 템플릿 파일이 YAML 출력을 생성하는 경우 확장자는 .yaml이어야 합니다. 형식화된 콘텐츠를 생성하지 않는 템플릿 파일에는 .tpl 확장자를 사용할 수 있습니다.
    • 템플릿 파일 이름은 카멜 케이스가 아닌 대시 표기법(my-example-configmap.yaml)을 사용해야 합니다.
    • 각 리소스 정의는 자체 템플릿 파일에 있어야 합니다.
    • 템플릿 파일 이름은 이름에 리소스 종류를 반영해야 합니다. 예: foo-pod.yaml, bar-svc.yaml

    defined 템플릿 이름

    {{ define }} 지시문 내에서 생성된 정의된 템플릿은 전역적으로 접근 가능합니다. 이는 하나의 차트와 그 모든 서브차트가 {{ define }}으로 생성된 모든 템플릿에 접근할 수 있다는 의미입니다.

    이러한 이유로, 모든 정의된 템플릿 이름은 네임스페이스되어야 합니다.

    # 좋은 방식
    {{- define "nginx.fullname" }}
    {{/* ... */}}
    {{ end -}}
    
    # 잘못된 방식
    {{- define "fullname" -}}
    {{/* ... */}}
    {{ end -}}

    템플릿 포맷팅

    템플릿은 두 칸 공백을 사용하여 들여쓰기해야 합니다 (탭은 사용 X). 템플릿 지시문은 여는 중괄호 뒤와 닫는 중괄호 앞에 공백이 있어야 합니다.

    # 옳은 방식
    {{ .foo }}
    {{ print "foo" }}
    {{- print "bar" -}}
    
    # 잘못된 방식
    {{.foo}}
    {{print "foo"}}
    {{-print "bar"-}}

    가능한 경우 템플릿은 공백을 제거해야 합니다.

    foo:
      {{- range .Values.items }}
      {{ . }}
      {{ end -}}

    블록 (제어 구조 등)은 템플릿 코드의 흐름을 나타내기 위해 들여쓰기될 수 있습니다.

    {{ if $foo -}}
      {{- with .Bar }}Hello{{ end -}}
    {{- end -}}

    하지만 YAML은 공백 기반 언어이므로, 코드 들여쓰기가 항상 그 규칙을 따르는 것은 종종 불가능합니다.

    템플릿 공백

    생성된 템플릿의 공백 양을 최소한으로 유지하는 것이 바람직합니다. 특히, 여러 개의 빈 줄이 서로 인접하여 나타나서는 안 됩니다. 하지만 가끔씩 빈 줄(특히 논리적인 섹션 사이)이 있는 것은 괜찮습니다.

    # 최고
    apiVersion: batch/v1
    kind: Job
    metadata:
      name: example
      labels:
        first: first
        second: second
    
    # 이정도는 ok
    apiVersion: batch/v1
    kind: Job
    
    metadata:
      name: example
    
      labels:
        first: first
        second: second
    
    # 안좋음
    apiVersion: batch/v1
    kind: Job
    
    metadata:
      name: example
    
      labels:
        first: first
    
        second: second

    주석 (yaml 주석 VS 템플릿 주석)

    -- yaml 주석
    # This is a comment
    type: sprocket
    
    -- 템플릿 주석
    {{- /*
    This is a comment.
    */}}
    type: frobnitz

    템플릿 주석은 정의된 템플릿을 설명하는 것과 같이 템플릿의 기능을 문서화할 때 사용해야 합니다.

    {{- /*
    mychart.shortname provides a 6 char truncated version of the release name.
    */}}
    {{ define "mychart.shortname" -}}
    {{ .Release.Name | trunc 6 }}
    {{- end -}}

    템플릿 내부에서 YAML 주석은 Helm 사용자가 디버깅 중에 주석을 보는 것이 유용할 때 사용할 수 있습니다.

    # This may cause problems if the value is more than 100Gi
    memory: {{ .Values.maxMem | quote }}

    위의 주석은 사용자가 helm install --debug를 실행할 때 표시되지만, {{- /* */}} 섹션에 지정된 주석은 표시되지 않습니다. 특정 템플릿 함수에서 필요로 할 수 있는 Helm 값을 포함하는 템플릿 섹션에 # YAML 주석을 추가하는 것을 주의해야 합니다. 예를 들어, 위의 예시에 required 함수가 도입되고 maxMem이 설정되지 않은 경우, # YAML 주석은 렌더링 오류를 발생시킬 것입니다.

    -- 옳은 방법 (helm template은 아래 블록을 렌더링 하지 않음)
    {{- /*
    # This may cause problems if the value is more than 100Gi
    memory: {{ required "maxMem must be set" .Values.maxMem | quote }}
    */ -}}
    
    -- 잘못된 방법 (helm template은 오류를 발생시킴)
    # This may cause problems if the value is more than 100Gi
    # memory: {{ required .Values.maxMem "maxMem must be set" | quote }}

    템플릿, 템플릿 출력에서 json 사용

    YAML은 JSON의 상위 집합입니다. 어떤 경우에는 JSON 구문을 사용하는 것이 다른 YAML 표현보다 더 읽기 쉬울 수 있습니다.

    예를 들어, 다음 YAML은 일반적인 YAML의 리스트 표현 방식과 더 유사합니다.

    arguments:
      - "--dirname"
      - "/foo"
    
    -- 아래 json으로도 표현 가능
    arguments: ["--dirname", "/foo"]

    가독성 향상을 위해 JSON을 사용하는 것은 좋습니다. 하지만 더 복잡한 구조를 표현하는 데 JSON 구문을 사용해서는 안 됩니다. YAML 내부에 순수 JSON이 포함된 경우 (예: init 컨테이너 구성), JSON 형식을 사용하는 것이 물론 적절합니다.

    레퍼런스

    https://helm.sh/docs/chart_best_practices/templates/

    https://helm.sh/docs/chart_template_guide/named_templates/

    https://helm.sh/docs/chart_template_guide/accessing_files/

    댓글