이것만 공부하세요 - helm chart 만드는 법
helm chart 를 만들기 위해서는 여러 기능들을 알아야 하지만 그 중에서 가장 많이 쓰고 헷갈리는 기능에 대해서 살펴 본다.
기본적으로 실습할 수 있는 환경을 먼저 만들고 하나씩 공부해 본다.
$ helm create flow-control
$ cd flow-control
yaml 형태의 출력을 확인해 본다.
$ helm template .
---
# Source: flow-control/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: release-name-flow-control
labels:
helm.sh/chart: flow-control-0.1.0
app.kubernetes.io/name: flow-control
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
automountServiceAccountToken: true
---
# Source: flow-control/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: release-name-flow-control
labels:
helm.sh/chart: flow-control-0.1.0
app.kubernetes.io/name: flow-control
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: flow-control
app.kubernetes.io/instance: release-name
---
# Source: flow-control/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: release-name-flow-control
labels:
helm.sh/chart: flow-control-0.1.0
app.kubernetes.io/name: flow-control
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: flow-control
app.kubernetes.io/instance: release-name
template:
metadata:
labels:
helm.sh/chart: flow-control-0.1.0
app.kubernetes.io/name: flow-control
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
serviceAccountName: release-name-flow-control
securityContext:
{}
containers:
- name: flow-control
securityContext:
{}
image: "nginx:1.16.0"
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{}
---
# Source: flow-control/templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "release-name-flow-control-test-connection"
labels:
helm.sh/chart: flow-control-0.1.0
app.kubernetes.io/name: flow-control
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['release-name-flow-control:80']
restartPolicy: Never
제대로 출력된다면 필요없는 파일은 삭제하고 초기화 하자.
$ rm -rf template/*
$ cat /dev/null > values.yaml
0. yaml 은 들여 쓰기가 중요하다.
가장 간단한 configmap 을 만들어서 value 값을 출력한다.
$ values.yaml
---
favorite:
drink: coffee
food: pizza
$ vi template/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ .Values.favorite.drink | default "tea" | quote }}
food: {{ .Values.favorite.food | upper | quote }}
{{ if eq .Values.favorite.drink "coffee" }}
mug: "true"
{{ end }}
$ helm template .
--- output ---
Error: YAML parse error on flow-control/templates/configmap.yaml: error converting YAML to JSON: yaml: line 8: did not find expected key
Use --debug flag to render out invalid YAML
template 을 제너레이션하면 에러가 발생한다. 왜 그럴까?
configmap.yaml 에 mug: "true"
가 2칸 들여써 있어서 발생하는 에러이다. 이런 에러를 조심하면서 아래 실습을 해보자.
1. 조건문과 빈라인 없애기
configmap.yaml 을 수정해서 제대로 yaml 을 생성해 보자.
$ vi template/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ .Values.favorite.drink | default "tea" | quote }}
food: {{ .Values.favorite.food | upper | quote }}
{{ if eq .Values.favorite.drink "coffee" }}
mug: "true"
{{ end }}
$ helm template .
---
# Source: flow-control/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
--------------> 빈라인 발생한다.
mug: "true"
--------------> 빈라인 발생한다.
.Release.Name
은 helm 으로 설치할 때 파라미터로 넘기는 값인데 여기서는 설치가 아니므로 기본 값인 release-name
으로 치환 되었고, .Values.favorite.drink
는 values.yaml 에 지정한 키값으로 그에 해당하는 밸류 값이 제너레이트 될 때 출력된다.
함수의 연속적 사용은 |
라인으로 호출 가능하며 default
는 키에 대한 값이 없을 때, quote
는 값을 "
으로 묶을 때 upper
는 값을 대문자로 변환할 때 사용하는 내장 함수이다.
비교 구문은 if eq 값1 값2
와 같이 사용할 수 있다.
출력하지 않는 곳에서는 빈라인이 발생하는데 이 부분을 다음과 같이 없애 줄 수 있다.
{{ if eq .Values.favorite.drink "coffee" }}mug: "true"{{ end }}
하지만 가독성이 떨어지므로 {{-
와 같이 표현하면 빈라인이 없어지면서 윗라인에 나란히 붙는 것과 같은 효과를 낼 수 있다.
{{- if eq .Values.favorite.drink "coffee" }}
mug: "true"
{{- end }}
다시 yaml 을 생성해 보면 아래와 같이 빈라인이 없어졌음을 알 수 있다.
$ helm template .
---
# Source: flow-control/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: "true"
조건문 대신 다음과 같이 with
를 사용하여 조건문과 키밸류 스쿱을 지정할 수 있다.
### configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
{{- with .Values.hobby }}
sports: {{ .sports }}
{{- end }}
### values.yaml
hobby:
sports: golf
with 와 함께 사용한 키는 해당 키에 대한 값이 있을 때만 with ~ end
로 감싼 구문이 출력된다. 또한 감싼 구문 안에서는 스쿱이 재정의되어 hobby 아래의 키인 sports 를 .sports
로 바로 사용할 수 있다.
yaml 을 생성하면 다음과 같은 결과가 나온다.
$ helm template .
# Source: flow-control/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name-configmap
data:
myvalue: "Hello World"
sports: golf
만약 values 에 sports 를 없애면 아래와 같이 출력되지 않는다.
### values.yaml
hobby: {}
# Source: flow-control/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name-configmap
data:
myvalue: "Hello World"
hobby 아래의 키는 키밸류인 디셔너리 타입을 넣기 때문에 아래의 값을 모두 없애기 위해서{}
빈 딕셔너리 값으로 지정했다. 만약 아래의 값이 리스트라면 []
와 같이 빈 리스트 값을 지정할 수 있다.
with ~ end
로 감싼 구문에서 root
영역의 value 를 활용하고 싶을 수 있다. 이 때는 $
를 붙혀서 영역을 최상위 root 로 접근할 수 있다. 아래 예제에서 $.Release.Name
을 참고한다.
### configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
{{- with .Values.hobby }}
sports: {{ .sports }}
release: {{ $.Release.Name }}
{{- end }}
YAML
은 JSON
의 수퍼셋이기 때문에 JSON 으로 표현하여 가독성을 높혀줄 수 도 있다. pod 를 만들 때 yaml 에 args
를 추가할 수 있는데. 이 때 JSON 을 쓰면 읽기에 편하다.
args:
- "--dirname"
- "/foo"
위의 내용은 JSON 으로 아래와 같이 바꿀 수 있다.
args: ["--dirname", "/foo"]
2. range 함수를 이용한 반복문
range 를 이용하여 반복문을 사용할 수 있다.
### values.yaml
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions
### configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
toppings: |-
{{- range .Values.pizzaToppings }}
- {{ . | title | quote }}
{{- end }}
pizzaToppings 의 값은 리스트이다(-
기호가 값으로 붙었기 때문에 리스트임을 알 수 있다). 리스트로 값을 가져와서 출력하기 때문에 아래와 같은 결과가 나온다.
# Source: flow-control/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name-configmap
data:
toppings: |-
- "Mushrooms"
- "Cheese"
- "Peppers"
- "Onions"
한가지 yaml
에서 toppings: 의 뒤에 따라온 |-
기호의 의미는 멀티 라인 스트링을 받겠다는 의미이다.
tuple
을 사용하여 튜플로 만들어 쓸 수 도 있다.
### configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
sizes: |-
{{- range tuple "small" "medium" "large" }}
- {{ . }}
{{- end }}
### 출력 결과
# Source: flow-control/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name-configmap
data:
sizes: |-
- small
- medium
- large
$
와 :=
를 이용하여 변수를 지정할 수 있다. 아래는 리스트에서 받은 값을 index 변수와 value 변수로 받아서 활용하는 부분이다.
### values.yaml
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions
### configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
toppings: |-
{{- range $index, $value := .Values.pizzaToppings }}
- {{ $index }}: {{ $value }}
{{- end }}
# Source: flow-control/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name-configmap
data:
toppings: |-
- 0: mushrooms
- 1: cheese
- 2: peppers
- 3: onions
map 값을 변수로 받아 처리할 수 도 있다.
### values.yaml
favorite:
drink: coffee
food: pizza
### configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
favorite: |-
{{- range $key, $value := .Values.favorite }}
{{ $key }}: {{ $value }}
{{- end }}
# Source: flow-control/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name-configmap
data:
favorite: |-
drink: coffee
food: pizza
3. template 선언 및 활용
부분적으로 사용자 정의 template 을 만들어서 활용 할 수 있다.
define - tempate 사용
template 은 define
으로 선언하고 tempate
으로 활용할 수 있다.
### configmap.yaml
{{- 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"
# Source: flow-control/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name-configmap
labels:
generator: helm
date: 2023-10-31
data:
myvalue: "Hello World"
위의 예제는 labels 에 date: 날짜
를 추가로 넣는 부분을 named template
을 만들어서 사용한 예제이다.
chat 를 만들 때 configmap.yaml 과 같이 쿠버네티스 리소스들은 template 디렉토리 아래에 위치 시킨다고 했다. 이 디렉토리에 위치한 yaml 파일들은 자동으로 렌더링에 포함되는데 _
로 시작하는 파일은 렌더링에서 제외한다. 그래서 보통 define 으로 정의한 함수들은 _helper.tpl
파일을 만들어서 이곳에 위치 시킨다.
define
으로 정의된 named template (함수) 은 template
으로 호출되기 전까지는 렌더링 되지 않는다. 이제 이 함수를 _helper.tpl 파일로 옮겨서 렌더링 결과를 살펴보자.
# _helper.tpl
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
{{- end }}
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
{{- template "mychart.labels" }}
data:
myvalue: "Hello World"
# Source: flow-control/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name-configmap
labels:
generator: helm
date: 2023-10-31
data:
myvalue: "Hello World"
위의 예제에서 define 함수 내에서 .Values
와 같이 value 를 가져오는 것은 하지 않았다. 아래와 같이 {{ .Chart.Name }}
을 사용한다면 위의 방식으로는 값을 표현할 수 없다.
# _helper.tpl
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
chart: {{ .Chart.Name }}
version: {{ .Chart.Version }}
{{- end }}
# Source: flow-control/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name-configmap
labels:
generator: helm
date: 2023-10-31
chart:
version:
data:
myvalue: "Hello World"
이는 template 으로 호출할 때 뒤에 value 를 보내지 않아서 발생한 부분이다. 즉 {{- template "mychart.labels" . }}
과 같이 마지막에 현재의 scope value 인 .
을 넘겨 주어야 제대로 된 값이 출력된다.
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
{{- template "mychart.labels" . }}
data:
myvalue: "Hello World"
# Source: flow-control/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name-configmap
labels:
generator: helm
date: 2023-10-31
chart: flow-control
version: 0.1.0
define - include 사용
template 은 있는 그대로 output 을 보여주기 때문에 들여쓰기의 문제가 있을 수 있다.
# _helper.tlp
{{- define "mychart.app" -}}
app_name: {{ .Chart.Name }}
app_version: "{{ .Chart.Version }}"
{{- end -}}
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
labels:
{{ template "mychart.app" . }}
data:
myvalue: "Hello World"
{{ template "mychart.app" . }}
# Source: flow-control/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name-configmap
labels:
app_name: flow-control
app_version: "0.1.0"
data:
myvalue: "Hello World"
app_name: flow-control
app_version: "0.1.0"
app_name 과 app_version 이 출력된 것을 보면 define 에 정의된 들여쓰기 대로 그대로 출력되어 원하는 대로 출력되지 않는다.
include
와 nindent
를 사용하면 원하는 들여쓰기가 가능하다.
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
labels:
{{ template "mychart.app" . }}
data:
myvalue: "Hello World"
{{ template "mychart.app" . }}
# Source: flow-control/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name-configmap
labels:
app_name: flow-control
app_version: "0.1.0"
data:
myvalue: "Hello World"
app_name: flow-control
app_version: "0.1.0"
끝으로 helm install
할 때 들여쓰기가 잘못되면 렌더링 오류가 나서 최종 결과를 볼 수 가 없다. 이를 해결할 수 있는 옵션이 -disable-openapi-validation
이다.
$ helm install --dry-run --disable-openapi-validation mychart ./