차트 개발 팁과 요령

차트 개발 팁과 요령

품질 좋은 프로덕션 차트를 만드는 과정에서 Helm 차트 개발자들이 배운 몇 가지 팁과 요령을 다루는 가이드입니다.

출처: 문서

본문

이 가이드는 Helm 차트 개발자들이 품질 좋은 프로덕션 차트를 만들며 배운 몇 가지 팁과 요령을 다룹니다.

템플릿 함수 알기 (Know Your Template Functions)

Helm은 리소스 파일을 템플릿화하기 위해 Go 템플릿을 사용합니다. Go가 여러 내장 함수를 제공하지만 우리는 다른 함수도 많이 추가했습니다.

먼저, 보안 이유로 envexpandenv 를 제외하고 Sprig 라이브러리의 모든 함수를 추가했습니다.

또한 includerequired 라는 두 개의 특별한 템플릿 함수를 추가했습니다. include 함수는 다른 템플릿을 가져온 다음 그 결과를 다른 템플릿 함수에 전달할 수 있게 합니다. required 함수는 특정 values 항목이 템플릿 렌더링에 필수임을 선언할 수 있게 합니다. 값이 비어 있으면 사용자가 제출한 에러 메시지와 함께 템플릿 렌더링이 실패합니다.

'include' 함수 사용하기

Go는 내장 template 지시문을 사용해 한 템플릿을 다른 템플릿에 포함하는 방법을 제공합니다. 그러나 내장 함수는 Go 템플릿 파이프라인에서 사용할 수 없습니다.

템플릿을 포함한 다음 그 템플릿 출력에 연산을 수행할 수 있도록 Helm은 특별한 include 함수를 갖습니다.

{{- include "toYaml" $value | nindent 2 }}

위는 toYaml 이라는 템플릿을 포함하고 $value 를 전달한 다음 그 템플릿의 출력을 nindent 함수에 전달합니다. {{- ... | nindent _n_ }} 패턴을 사용하면 왼쪽 공백(이전 개행 포함)을 잘라내고(chomp) nindent 가 새 개행을 다시 추가하고 포함된 내용을 요청한 만큼 들여쓰기하므로 문맥에서 include 를 읽기 쉽게 만듭니다.

YAML은 들여쓰기 수준과 공백에 의미를 부여하므로, 이것은 코드 조각을 포함하면서 관련 문맥에서 들여쓰기를 처리하는 훌륭한 방법 중 하나입니다.

이 템플릿 조각은 mytpl 이라는 템플릿을 포함한 다음 결과를 소문자로 바꾸고 쌍따옴표로 감쌉니다.

value: {{ include "mytpl" . | lower | quote }}

'required' 함수 사용하기

Go는 맵에 없는 키로 맵을 인덱싱할 때 동작을 제어하는 missingkey 템플릿 옵션을 제공합니다. 차트 개발자가 values.yaml 파일의 특정 값에 대해 이 동작을 강제하고 싶은 상황이 있을 수 있습니다.

required 함수는 개발자가 값을 템플릿 렌더링에 필수로 선언할 수 있게 합니다. values.yaml 에서 항목이 비어 있으면 템플릿이 렌더링되지 않고 개발자가 제공한 에러 메시지를 반환합니다.

예를 들어:

{{ required "A valid foo is required!" .Values.foo }}

위는 .Values.foo 가 정의되면 템플릿을 렌더링하지만, .Values.foo 가 정의되지 않으면 렌더링에 실패하고 종료합니다.

다음 required 함수 예제는 .Values.who 항목이 필수임을 선언하고, 해당 항목이 없으면 에러 메시지를 출력합니다.

value: {{ required "A valid .Values.who entry required!" .Values.who }}

문자열은 인용하고 정수는 인용하지 마세요

문자열 데이터를 다룰 때는 문자열을 그대로 두는 것보다 인용하는 것이 항상 더 안전합니다.

name: {{ .Values.MyName | quote }}

하지만 정수를 다룰 때는 값을 인용하지 마세요. 많은 경우 Kubernetes 내부에서 파싱 에러를 일으킬 수 있습니다.

port: {{ .Values.Port }}

이 주의 사항은 정수를 나타내더라도 문자열이어야 하는 env 변수 값에는 적용되지 않습니다.

env:
  - name: HOST
    value: "http://host"
  - name: PORT
    value: "1234"

'tpl' 함수 사용하기

tpl 함수는 개발자가 템플릿 안에서 문자열을 템플릿으로 평가할 수 있게 합니다. 템플릿 문자열을 차트의 값으로 전달하거나 외부 구성 파일을 렌더링할 때 유용합니다. 구문: {{ tpl TEMPLATE_STRING VALUES }}

예제:

# values
template: "{{ .Values.name }}"
name: "Tom"

# template
{{ tpl .Values.template . }}

# output
Tom

외부 구성 파일 렌더링:

# external configuration file conf/app.conf
firstName={{ .Values.firstName }}
lastName={{ .Values.lastName }}

# values
firstName: Peter
lastName: Parker

# template
{{ tpl (.Files.Get "conf/app.conf") . }}

# output
firstName=Peter
lastName=Parker

이미지 풀 시크릿 만들기 (Creating Image Pull Secrets)

이미지 풀 시크릿은 기본적으로 registry, username, password 의 조합입니다. 배포 중인 애플리케이션에서 필요할 수 있지만, 만들려면 base64 를 몇 번 실행해야 합니다. Secret 의 페이로드로 사용할 Docker 구성 파일을 조합하는 헬퍼 템플릿을 작성할 수 있습니다. 다음은 그 예입니다.

먼저 자격 증명이 values.yaml 파일에 다음과 같이 정의되어 있다고 가정합니다.

imageCredentials:
  registry: quay.io
  username: someone
  password: sillyness
  email: [email protected]

그런 다음 헬퍼 템플릿을 다음과 같이 정의합니다.

{{- define "imagePullSecret" }}
{{- with .Values.imageCredentials }}
{{- printf "{\"auths\":{\"%s\":{\"username\":\"%s\",\"password\":%s,\"email\":\"%s\",\"auth\":\"%s\"}}}" .registry .username (.password | quote) .email (printf "%s:%s" .username .password | b64enc) | b64enc }}
{{- end }}
{{- end }}

마지막으로 더 큰 템플릿에서 헬퍼 템플릿을 사용해 Secret 매니페스트를 만듭니다.

apiVersion: v1
kind: Secret
metadata:
  name: myregistrykey
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: {{ template "imagePullSecret" . }}

Deployment 자동 롤링 (Automatically Roll Deployments)

종종 ConfigMap 이나 Secret이 컨테이너에 구성 파일로 주입되거나, Pod를 롤링해야 하는 다른 외부 의존성 변경이 있습니다. 애플리케이션에 따라 이후 helm upgrade 로 업데이트되면 재시작이 필요할 수 있지만, deployment spec 자체가 변경되지 않으면 애플리케이션은 일관성이 없는 구성으로 계속 실행됩니다.

sha256sum 함수를 사용해 다른 파일이 변경되면 deployment 의 어노테이션 섹션이 업데이트되도록 보장할 수 있습니다.

kind: Deployment
spec:
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
[...]

참고: 라이브러리 차트에 이것을 추가하는 경우 $.Template.BasePath 에서 파일에 접근할 수 없습니다. 대신 {{ include ("mylibchart.configmap") . | sha256sum }} 으로 정의를 참조할 수 있습니다.

항상 deployment 를 롤링하고 싶다면 위와 유사한 어노테이션 단계를 사용하되 랜덤 문자열로 대체해 항상 변경되어 deployment 가 롤링되게 할 수 있습니다.

kind: Deployment
spec:
  template:
    metadata:
      annotations:
        rollme: {{ randAlphaNum 5 | quote }}
[...]

템플릿 함수의 각 호출은 고유한 랜덤 문자열을 생성합니다. 즉 여러 리소스에서 사용하는 랜덤 문자열을 동기화해야 한다면 관련 리소스 모두가 같은 템플릿 파일에 있어야 합니다.

이 두 방법 모두 Deployment 가 다운타임을 피하기 위해 내장된 업데이트 전략 로직을 활용하게 합니다.

참고: 과거에는 다른 옵션으로 --recreate-pods 플래그를 권장했습니다. 이 플래그는 더 선언적인 위 방법을 위해 Helm 3에서 deprecated 로 표시되었습니다.

Helm이 리소스를 제거하지 못하게 하기 (Tell Helm Not To Uninstall a Resource)

때로는 helm uninstall 실행 시 제거되지 않아야 하는 리소스가 있습니다. 차트 개발자는 리소스에 어노테이션을 추가해 제거되지 않도록 막을 수 있습니다.

kind: Secret
metadata:
  annotations:
    helm.sh/resource-policy: keep
[...]

helm.sh/resource-policy: keep 어노테이션은 helm uninstall, helm upgrade, helm rollback 같은 helm 작업이 이 리소스를 삭제하게 되는 경우에도 Helm이 이 리소스 삭제를 건너뛰도록 지시합니다. 하지만 이 리소스는 고아가 됩니다. Helm은 더 이상 어떤 방식으로도 관리하지 않습니다. 이미 제거되었지만 리소스를 유지한 릴리스에 helm install --replace 를 사용하면 문제가 생길 수 있습니다.

"부분(Partials)"과 템플릿 포함 사용하기 (Using "Partials" and Template Includes)

때로는 차트에서 블록이든 템플릿 부분이든 재사용 가능한 부분을 만들고 싶을 때가 있습니다. 그리고 그것들을 자체 파일에 두는 것이 더 깔끔한 경우가 많습니다.

templates/ 디렉터리에서 밑줄(_)로 시작하는 파일은 Kubernetes 매니페스트 파일을 출력하지 않을 것으로 예상됩니다. 그래서 관례적으로 헬퍼 템플릿과 부분을 _helpers.tpl 파일에 둡니다.

의존성이 많은 복잡한 차트 (Complex Charts with Many Dependencies)

CNCF Artifact Hub의 차트 중 상당수는 더 고급 애플리케이션을 만들기 위한 "빌딩 블록"입니다. 그러나 차트는 대규모 애플리케이션의 인스턴스를 만드는 데 사용될 수도 있습니다. 그런 경우 단일 umbrella 차트가 여러 서브차트를 가질 수 있고, 각각이 전체의 한 부분으로 기능합니다.

이산적인 부분에서 복잡한 애플리케이션을 조합하는 현재의 모범 사례는 전역 구성을 노출하는 최상위 umbrella 차트를 만들고, charts/ 하위 디렉터리를 사용해 각 구성 요소를 내장하는 것입니다.

YAML은 JSON의 상위 집합 (YAML is a Superset of JSON)

YAML 명세에 따르면 YAML은 JSON의 상위 집합입니다. 즉 유효한 모든 JSON 구조는 YAML에서도 유효해야 합니다.

이것은 이점이 있습니다: 때로는 템플릿 개발자가 YAML의 공백 민감성을 다루는 대신 JSON 같은 구문으로 데이터 구조를 표현하는 것이 더 쉬울 수 있습니다.

모범 사례로, JSON 구문이 형식 문제의 위험을 실질적으로 줄이지 않는 한 템플릿은 YAML 같은 구문을 따라야 합니다.

랜덤 값 생성에 주의하기 (Be Careful with Generating Random Values)

Helm에는 랜덤 데이터, 암호화 키 등을 생성할 수 있는 함수가 있습니다. 이것들은 사용해도 괜찮습니다. 하지만 업그레이드 중에는 템플릿이 다시 실행된다는 점을 알아야 합니다. 템플릿 실행이 마지막 실행과 다른 데이터를 생성하면 해당 리소스의 업데이트가 트리거됩니다.

하나의 명령으로 릴리스 설치 또는 업그레이드

Helm은 설치-또는-업그레이드를 단일 명령으로 수행하는 방법을 제공합니다. helm upgrade--install 명령과 함께 사용하세요. 그러면 Helm이 릴리스가 이미 설치되었는지 확인합니다. 아니라면 설치를 실행합니다. 이미 있다면 기존 릴리스를 업그레이드합니다.

$ helm upgrade --install <release name> --values <values file> <chart directory>

재현 가능한 차트 아카이브 빌드

기본적으로 패키징된 차트 아카이브(.tgz) 안의 파일은 디스크의 소스 파일 수정 시각을 가집니다. 결과적으로 같은 차트를 다시 패키징하면 빌드마다 다른 아카이브가 만들어집니다. 차트 아카이브를 재현 가능하게 만들기 위해 Helm은 Reproducible Builds 프로젝트가 정의한 SOURCE_DATE_EPOCH 환경 변수를 존중합니다. 설정하면 Helm은 빌드 시각 대신 제공한 수정 시각으로 아카이브의 모든 파일에 스탬프를 찍습니다.

SOURCE_DATE_EPOCH 를 Unix 타임스탬프(1970-01-01 UTC 이후의 정수 초)로 설정하고 helm package 를 실행하세요.

$ SOURCE_DATE_EPOCH=1609459200 helm package ./mychart

Helm은 결과 아카이브의 모든 파일에 2021-01-01T00:00:00Z 로 스탬프를 찍으므로, 같은 소스를 다시 패키징하면 같은 타임스탬프가 생산됩니다. 소스 기록을 추적하려면 마지막 커밋에서 타임스탬프를 파생하세요.

$ SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) helm package ./mychart

SOURCE_DATE_EPOCH 를 사용할 때 다음을 기억하세요:

  • 값은 음수가 아닌 정수여야 합니다. Helm은 이를 UTC의 정수 초로 해석하며 소수점 이하 초는 지원하지 않습니다. 숫자가 아니거나 음수인 값은 invalid SOURCE_DATE_EPOCH 에러로 명령이 실패합니다.
  • 변수가 설정되지 않았거나 비어 있으면 Helm은 아카이브 항목이 실제 파일 수정 시각을 반영하는 기본 동작을 유지합니다.
  • Chart.lock 파일을 포함하는 차트를 패키징하면 Helm은 그 파일의 generated: 타임스탬프도 UTC와 정수 초로 정규화한 SOURCE_DATE_EPOCH 값으로 스탬프를 찍습니다. 결과적으로 의존성을 선언하는 차트는 같은 SOURCE_DATE_EPOCH 를 사용하면 빌드와 머신에 걸쳐 바이트 단위로 동일한 아카이브를 생산합니다. 파일 수정 시각뿐 아니라 Chart.lock 내용이 일치하기 때문입니다.
  • helm install, helm upgrade, helm dependency build, helm dependency updateSOURCE_DATE_EPOCH 를 존중하지만, 로컬 file:// 리포지토리에서 가져온 의존성을 다시 아카이빙할 때만 적용됩니다. Helm은 원격으로 다운로드한 의존성은 가져온 그대로 저장합니다.

더 알아보기 (Learn more)