흐름 제어
흐름 제어 (Flow Control)
이 문서는 템플릿 생성 흐름을 제어하는 if/else, with, range 같은 제어 구조를 다뤄요. 조건부 블록, 공백 제어, 범위 지정, 반복을 설명해요.
출처: 문서
본문
제어 구조(템플릿 용어로 "액션(action)")는 템플릿 작성자인 여러분에게 템플릿 생성의 흐름을 제어할 수 있는 능력을 제공해요. Helm의 템플릿 언어는 다음 제어 구조를 제공해요:
-
조건부 블록을 만드는
if/else -
범위를 지정하는
with -
"for each" 스타일 루프를 제공하는
range
이 외에도 이름이 있는 템플릿 세그먼트를 선언하고 사용하기 위한 몇 가지 액션을 제공해요:
-
define은 템플릿 안에서 새 이름이 있는 템플릿을 선언해요. -
template은 이름이 있는 템플릿을 가져와요. -
block은 채울 수 있는 특별한 종류의 템플릿 영역을 선언해요.
이 섹션에서는 if, with, range에 대해 이야기할 거예요. 나머지는 이 가이드의 뒷부분 "일반 템플릿 (Named Templates)" 섹션에서 다뤄요.
If/Else
살펴볼 첫 번째 제어 구조는 조건부로 텍스트 블록을 템플릿에 포함하는 것이에요. 이것이 if/else 블록이에요.
조건문의 기본 구조는 다음과 같아요:
{{ if PIPELINE }}
# 무언가 하기
{{ else if OTHER PIPELINE }}
# 다른 무언가 하기
{{ else }}
# 기본 케이스
{{ end }}
이제 값 대신 파이프라인에 대해 이야기하고 있다는 점에 주목하세요. 그 이유는 제어 구조가 값 하나만 평가하는 것이 아니라 전체 파이프라인을 실행할 수 있다는 것을 명확히 하기 위해서예요.
파이프라인은 값이 다음 중 하나일 때 false로 평가돼요:
-
불리언 false
-
숫자 0
-
빈 문자열
-
nil(비어 있거나 null) -
빈 컬렉션 (
map,slice,tuple,dict,array)
다른 모든 조건에서 조건은 true예요.
ConfigMap에 간단한 조건문을 추가해 봐요. drink가 coffee로 설정되면 추가 설정을 넣을게요:
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 }}
마지막 예시에서 drink: coffee를 주석 처리했으므로 출력에는 mug: "true" 플래그가 포함되지 않아야 해요. 하지만 그 줄을 values.yaml 파일에 다시 추가하면 출력은 다음과 같아야 해요:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: eyewitness-elk-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: "true"
공백 제어 (Controlling Whitespace)
조건문을 살펴보는 동안 템플릿에서 공백이 제어되는 방식을 잠깐 살펴봐야 해요. 이전 예시를 가져와 읽기 조금 더 쉽게 포맷해 봐요:
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 install --dry-run --debug ./mychart
SERVER: "localhost:44134"
CHART PATH: /Users/mattbutcher/Code/Go/src/helm.sh/helm/_scratch/mychart
Error: YAML parse error on mychart/templates/configmap.yaml: error converting YAML to JSON: yaml: line 9: did not find expected key
무슨 일이 일어났나요? 위의 공백 때문에 잘못된 YAML이 생성됐어요.
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: eyewitness-elk-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: "true"
mug가 잘못 들여쓰기됐어요. 그 한 줄의 들여쓰기를 빼고 다시 실행해 봐요:
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 }}
그것을 보내면 유효하지만 여전히 조금 이상해 보이는 YAML을 얻을 거예요:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: telling-chimp-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: "true"
YAML에 몇 개의 빈 줄이 생긴 것에 주목하세요. 왜 그럴까요? 템플릿 엔진이 실행될 때 {{와 }} 안의 내용을 제거하지만 나머지 공백은 그대로 둬요.
YAML은 공백에 의미를 부여하므로 공백을 관리하는 것이 꽤 중요해져요. 다행히도 Helm 템플릿에는 도움이 되는 몇 가지 도구가 있어요.
먼저 템플릿 선언의 중괄호 구문을 특수 문자로 수정해 템플릿 엔진에 공백을 chomp(잘라내기)하라고 지시할 수 있어요. {{- (대시와 공백 추가)은 왼쪽 공백이 잘려야 함을, -}}는 오른쪽 공백이 소비되어야 함을 나타내요. 주의하세요! 새 줄은 공백이에요!
-와 지시문의 나머지 사이에 공백이 있는지 확인하세요. {{- 3 }}은 "왼쪽 공백을 자르고 3을 출력"을 의미하는 반면 {{-3 }}은 "-3 출력"을 의미해요.
이 구문을 사용해 템플릿을 수정해 그 빈 줄들을 없앨 수 있어요:
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 }}
이 점을 분명히 하기 위해 위를 조정하고, 이 규칙을 따라 삭제될 각 공백에 *를 대입해 봐요. 줄 끝의 *는 제거될 새 줄 문자를 나타내요:
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으로 실행해 결과를 볼 수 있어요:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: clunky-cat-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: "true"
chomping 수정자에 주의하세요. 실수로 이런 실수를 하기 쉽다는 점에 주의하세요:
food: {{ .Values.favorite.food | upper | quote }}
{{- if eq .Values.favorite.drink "coffee" -}}
mug: "true"
{{- end -}}
그것은 양쪽의 새 줄을 소비했기 때문에 food: "PIZZA"mug: "true"를 생성할 거예요.
템플릿의 공백 제어에 대한 자세한 내용은 공식 Go 템플릿 문서를 참조하세요.
마지막으로 때로는 템플릿 지시문의 간격을 마스터하려고 노력하는 대신 템플릿 시스템에 들여쓰기 방법을 지시하는 것이 더 쉬울 수 있어요. 그런 이유로 indent 함수({{ indent 2 "mug:true" }})를 사용하면 유용할 때가 있어요.
with를 사용한 범위 수정 (Modifying scope using with)
살펴볼 다음 제어 구조는 with 액션이에요. 이것은 변수 범위를 제어해요. .은 현재 범위에 대한 참조라는 것을 기억하세요. 그래서 .Values는 템플릿에 현재 범위에서 Values 객체를 찾으라고 지시해요.
with의 구문은 간단한 if 문과 비슷해요:
{{ with PIPELINE }}
# 제한된 범위
{{ end }}
범위는 변경될 수 있어요. with는 현재 범위(.)를 특정 객체로 설정할 수 있게 해줘요. 예를 들어 우리는 .Values.favorite로 작업해왔어요. . 범위를 .Values.favorite를 가리키도록 ConfigMap을 다시 써 봐요:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
{{- end }}
이 예시는 with 범위 지정에 초점을 맞추기 위해 이전 연습의 mug 출력을 생략한 것에 주목하세요. with 블록은 .Values.favorite가 비어 있지 않을 때만 실행되므로 별도의 바깥쪽 if 가드는 필요 없어요.
이제 .drink와 .food를 수식 없이 참조할 수 있다는 점에 주목하세요. with 문이 .을 .Values.favorite를 가리키도록 설정하기 때문이에요. {{ end }} 이후에는 .이 이전 범위로 재설정돼요.
하지만 주의할 점이 있어요! 제한된 범위 안에서는 .을 사용해 부모 범위의 다른 객체에 접근할 수 없어요. 예를 들어 이것은 실패해요:
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
release: {{ .Release.Name }}
{{- end }}
Release.Name이 .의 제한된 범위 안에 없기 때문에 오류가 발생할 거예요. 하지만 마지막 두 줄을 바꾸면 {{ end }} 이후에 범위가 재설정되므로 모든 것이 예상대로 작동해요.
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
{{- end }}
release: {{ .Release.Name }}
또는 $를 사용해 부모 범위에서 Release.Name 객체에 접근할 수 있어요. $는 템플릿 실행이 시작될 때 루트 범위에 매핑되며 템플릿 실행 중에는 변하지 않아요. 다음도 작동해요:
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
release: {{ $.Release.Name }}
{{- end }}
range를 살펴본 후 위의 범위 지정 문제에 대한 한 가지 해결책을 제공하는 템플릿 변수를 살펴볼 거예요.
range 액션으로 반복하기 (Looping with the range action)
많은 프로그래밍 언어가 for 루프, foreach 루프 또는 유사한 함수형 메커니즘으로 반복을 지원해요. Helm의 템플릿 언어에서 컬렉션을 반복하는 방법은 range 연산자를 사용하는 거예요.
시작하려면 values.yaml 파일에 피자 토핑 목록을 추가해 봐요:
favorite:
drink: coffee
food: pizza
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions
- pineapple
이제 pizzaToppings 목록(템플릿에서 slice라고 함)이 있어요. 템플릿을 수정해 이 목록을 ConfigMap에 출력할 수 있어요:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
{{- end }}
toppings: |-
{{- range .Values.pizzaToppings }}
- {{ . | title | quote }}
{{- end }}
부모 범위에서 Values.pizzaToppings 목록에 접근하기 위해 $를 사용할 수 있어요. $는 템플릿 실행이 시작될 때 루트 범위에 매핑되며 템플릿 실행 중에는 변하지 않아요. 다음도 작동해요:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
toppings: |-
{{- range $.Values.pizzaToppings }}
- {{ . | title | quote }}
{{- end }}
{{- end }}
toppings: 목록을 자세히 살펴봐요. range 함수는 pizzaToppings 목록을 "range over"(반복)해요. 하지만 이제 흥미로운 일이 발생해요. with가 .의 범위를 설정하듯이 range 연산자도 마찬가지예요. 루프를 돌 때마다 .은 현재 피자 토핑으로 설정돼요. 즉, 첫 번째에는 .이 mushrooms으로, 두 번째 반복에서는 cheese로 설정되는 식이에요.
{{ . | title | quote }}를 하면 .의 값을 파이프라인으로 직접 보내 title(타이틀 케이스 함수)로 보낸 다음 quote로 보내요. 이 템플릿을 실행하면 출력은 다음과 같아요:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: edgy-dragonfly-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
toppings: |-
- "Mushrooms"
- "Cheese"
- "Peppers"
- "Onions"
- "Pineapple"
이제 이 예시에서 까다로운 일을 했어요. toppings: |- 줄은 여러 줄 문자열을 선언해요. 그래서 우리의 토핑 목록은 실제로 YAML 목록이 아니라 큰 문자열이에요. 왜 이렇게 했을까요? ConfigMap의 data가 키/값 쌍으로 구성되고 키와 값이 모두 단순 문자열이기 때문이에요. 왜 그런지 이해하려면 Kubernetes ConfigMap 문서를 보세요. 우리에게는 이 세부 사항이 별로 중요하지 않아요.
YAML의 |- 표시는 여러 줄 문자열을 취해요. 이것은 여기서 예시로 보여주듯이 매니페스트 안에 큰 데이터 블록을 임베딩하는 데 유용한 기법이에요.
때로는 템플릿 안에서 빠르게 목록을 만든 다음 그 목록을 반복하는 것이 유용할 수 있어요. Helm 템플릿에는 이것을 쉽게 만드는 함수가 있어요: tuple이에요. 컴퓨터 과학에서 튜플은 고정 크기의 목록형 컬렉션이지만 임의의 데이터 타입을 가져요. 이것이 tuple이 사용되는 방식을 대략 전달해요.
sizes: |-
{{- range tuple "small" "medium" "large" }}
- {{ . }}
{{- end }}
위 코드는 다음을 생성해요:
sizes: |-
- small
- medium
- large
목록과 튜플 외에도 range는 키와 값을 가지는 컬렉션(예: map 또는 dict)을 반복하는 데 사용할 수 있어요. 다음 섹션에서 템플릿 변수를 소개할 때 그 방법을 볼 거예요.