부록: YAML 기법
부록: YAML 기법 (Appendix: YAML Techniques)
이 안내서는 대부분 템플릿 언어 작성에 초점을 맞췄습니다. 여기서는 YAML 형식 자체를 살펴봅니다. YAML에는 템플릿 작성자가 사용할 수 있는 유용한 기능이 있어, 템플릿의 오류를 줄이고 읽기 쉽게 만들어 줍니다.
출처: 문서
참고: 매니페스트에 차트 값을 삽입할 때는 Helm 값 삽입 시 YAML 주입 방지를 함께 읽어 사용자가 제공한 문자열이 문서 구조를 바꾸지 못하게 하세요.
본문
스칼라와 컬렉션 (Scalars and Collections)
YAML 스펙에 따르면 컬렉션에는 두 종류가 있고, 스칼라 타입은 여러 가지가 있습니다.
컬렉션의 두 종류는 맵(map)과 시퀀스(sequence)입니다.
map:
one: 1
two: 2
three: 3
sequence:
- one
- two
- three
스칼라 값은 (컬렉션과 달리) 개별 값입니다.
YAML의 스칼라 타입 (Scalar Types in YAML)
Helm의 YAML 방언에서 값의 스칼라 데이터 타입은 리소스 정의를 위한 Kubernetes 스키마를 포함한 복잡한 규칙 집합에 의해 결정됩니다. 하지만 타입을 추론할 때는 다음 규칙이 대체로 성립합니다.
정수나 부동 소수점이 따옴표 없이 붙은 단어(bare word)라면 일반적으로 숫자 타입으로 취급됩니다.
count: 1
size: 2.34
하지만 따옴표로 감싸면 문자열로 취급됩니다.
count: "1" # <-- string, not int
size: '2.34' # <-- string, not float
불리언도 마찬가지입니다.
isGood: true # bool
answer: "true" # string
빈 값을 뜻하는 단어는 null 입니다(nil 가 아닙니다).
참고로 port: "80" 은 유효한 YAML이며 템플릿 엔진과 YAML 파서를 모두 통과하지만, Kubernetes가 port 를 정수로 기대하면 실패할 수 있습니다.
어떤 경우에는 YAML 노드 태그(node tag)를 사용해 특정 타입 추론을 강제할 수 있습니다.
coffee: "yes, please"
age: !!str 21
port: !!int "80"
위에서 !!str 은 age 가 정수처럼 보여도 문자열이라는 것을 파서에 알려줍니다. 그리고 port 는 따옴표로 감쌌지만 정수로 취급됩니다.
YAML의 문자열 (Strings in YAML)
YAML 문서에 넣는 데이터의 상당수는 문자열입니다. YAML은 문자열을 표현하는 방법이 하나 이상 있습니다. 이 섹션에서는 그 방법들을 설명하고 일부를 어떻게 사용하는지 보여줍니다.
문자열을 선언하는 "인라인(inline)" 방법은 세 가지입니다.
way1: bare words
way2: "double-quoted strings"
way3: 'single-quoted strings'
모든 인라인 스타일은 한 줄에 있어야 합니다.
- Bare words는 따옴표가 없고 이스케이프되지 않습니다. 그래서 어떤 문자를 쓰는지 주의해야 합니다.
- Double-quoted strings는
\로 특정 문자를 이스케이프할 수 있습니다. 예:"\"Hello\", she said". 줄 바꿈은\n으로 이스케이프할 수 있습니다. - Single-quoted strings는 "리터럴(literal)" 문자열이며
\로 문자를 이스케이프하지 않습니다. 유일한 이스케이프 시퀀스는''로, 단일'로 디코딩됩니다.
한 줄 문자열 외에도 여러 줄 문자열을 선언할 수 있습니다.
coffee: |
Latte
Cappuccino
Espresso
위는 coffee 값을 Latte\nCappuccino\nEspresso\n 과 같은 단일 문자열로 취급합니다.
| 다음의 첫 번째 줄은 올바르게 들여쓰기되어야 합니다. 다음과 같이 하면 위 예제가 깨질 수 있습니다.
coffee: |
Latte
Cappuccino
Espresso
Latte 가 잘못 들여쓰기되었기 때문에 다음과 같은 오류가 발생합니다.
Error parsing file: error converting YAML to JSON: yaml: line 7: did not find expected key
템플릿에서는 위와 같은 오류로부터 보호하기 위해 여러 줄 문서에 가짜 "첫 줄"을 넣는 것이 때로는 더 안전합니다.
coffee: |
# Commented first line
Latte
Cappuccino
Espresso
어떤 첫 줄이든 문자열 출력에 그대로 보존된다는 점에 유의하세요. 예를 들어 이 기법으로 파일 내용을 ConfigMap에 주입한다면, 주석은 해당 항목을 읽는 쪽이 기대하는 타입이어야 합니다.
여러 줄 문자열에서 공백 제어 (Controlling Spaces in Multi-line Strings)
위 예제에서는 | 를 사용해 여러 줄 문자열을 나타냈습니다. 하지만 문자열 내용 뒤에 후행 \n 이 붙는다는 점에 주목하세요. YAML 프로세서가 후행 줄 바꿈을 제거하게 하려면 | 뒤에 - 를 추가합니다.
coffee: |-
Latte
Cappuccino
Espresso
이제 coffee 값은 Latte\nCappuccino\nEspresso 가 됩니다(후행 \n 없음).
때로는 모든 후행 공백을 보존하고 싶을 수도 있습니다. |+ 표기로 이를 할 수 있습니다.
coffee: |+
Latte
Cappuccino
Espresso
another: value
이제 coffee 값은 Latte\nCappuccino\nEspresso\n\n\n 이 됩니다.
텍스트 블록 안의 들여쓰기는 보존되며, 이로 인해 줄 바꿈도 보존됩니다.
coffee: |-
Latte
12 oz
16 oz
Cappuccino
Espresso
위 경우 coffee 는 Latte\n 12 oz\n 16 oz\nCappuccino\nEspresso 가 됩니다.
들여쓰기와 템플릿 (Indenting and Templates)
템플릿을 작성할 때 파일의 내용을 템플릿에 주입하고 싶을 때가 있습니다. 이전 장에서 보았듯이, 이를 위해 두 가지 방법이 있습니다.
{{ .Files.Get "FILENAME" }}를 사용해 차트에 있는 파일의 내용을 가져오기{{ include "TEMPLATE" . }}를 사용해 템플릿을 렌더링한 후 그 내용을 차트에 넣기
파일을 YAML에 삽입할 때는 위의 여러 줄 규칙을 이해하는 것이 좋습니다. 정적 파일을 삽입하는 가장 쉬운 방법은 대체로 다음과 같습니다.
myfile: |
{{- .Files.Get "myfile.txt" | nindent 2 }}
위에서 들여쓰기를 어떻게 하는지 주목하세요. nindent 2 는 템플릿 엔진에 "myfile.txt" 의 모든 줄에 새 줄을 추가하고 두 칸씩 들여쓰라고 알려줍니다. {{- 는 왼쪽 공백을 잘라내고, nindent 가 올바른 들여쓰기로 새 줄을 다시 추가합니다.
접힌 여러 줄 문자열 (Folded Multi-line Strings)
때로는 YAML에서 문자열을 여러 줄로 표현하되, 해석될 때는 하나의 긴 줄로 취급하고 싶을 수 있습니다. 이를 "접기(folding)"라고 합니다. 접힌 블록을 선언하려면 | 대신 > 를 사용합니다.
coffee: >
Latte
Cappuccino
Espresso
위 coffee 값은 Latte Cappuccino Espresso\n 입니다. 마지막 줄 바꿈을 제외한 모든 줄 바꿈이 공백으로 변환됩니다. 공백 제어를 접힌 텍스트 표시와 결합할 수 있으므로, >- 는 모든 줄 바꿈을 대체/정리합니다.
접힌 구문에서는 텍스트를 들여쓰면 줄이 보존된다는 점에 유의하세요.
coffee: >-
Latte
12 oz
16 oz
Cappuccino
Espresso
위는 Latte\n 12 oz\n 16 oz\nCappuccino Espresso 를 만듭니다. 공백과 줄 바꿈이 모두 그대로 남아 있습니다.
한 파일에 여러 문서 포함 (Embedding Multiple Documents in One File)
하나의 파일에 YAML 문서를 여러 개 넣을 수 있습니다. 새 문서 앞에 --- 를 붙이고 문서 끝에 ... 를 붙이면 됩니다.
---
document: 1
...
---
document: 2
...
많은 경우 --- 나 ... 중 하나는 생략될 수 있습니다.
Helm의 일부 파일은 하나 이상의 문서를 담을 수 없습니다. 예를 들어 values.yaml 파일 안에 문서가 두 개 이상 제공되면 첫 번째만 사용됩니다.
반면 템플릿 파일은 여러 문서를 가질 수 있습니다. 이 경우 템플릿 렌더링 중에는 파일(및 모든 문서)이 하나의 객체로 취급됩니다. 하지만 결과 YAML은 Kubernetes에 전달되기 전에 여러 문서로 분할됩니다.
파일당 여러 문서는 정말 필요할 때만 사용할 것을 권장합니다. 파일에 여러 문서가 있으면 디버깅이 어려울 수 있습니다.
YAML은 JSON의 상위 집합 (YAML is a Superset of JSON)
YAML은 JSON의 상위 집합이므로, 유효한 JSON 문서는 유효한 YAML 이어야 합니다.
{
"coffee": "yes, please",
"coffees": [
"Latte", "Cappuccino", "Espresso"
]
}
위는 다음을 표현하는 또 다른 방법입니다.
coffee: yes, please
coffees:
- Latte
- Cappuccino
- Espresso
그리고 둘을 (주의해서) 섞을 수도 있습니다.
coffee: "yes, please"
coffees: [ "Latte", "Cappuccino", "Espresso"]
세 가지 모두 동일한 내부 표현으로 파싱되어야 합니다.
이로 인해 values.yaml 같은 파일에 JSON 데이터가 들어갈 수 있지만, Helm은 .json 확장자를 유효한 접미사로 취급하지 않습니다.
YAML 앵커 (YAML Anchors)
YAML 스펙은 값에 대한 참조를 저장하고, 나중에 그 값을 참조로 가리킬 수 있는 방법을 제공합니다. YAML은 이를 "앵커링(anchoring)"이라고 부릅니다.
coffee: "yes, please"
favorite: &favoriteCoffee "Cappuccino"
coffees:
- Latte
- *favoriteCoffee
- Espresso
위에서 &favoriteCoffee 는 Cappuccino 에 대한 참조를 설정합니다. 나중에 그 참조는 *favoriteCoffee 로 사용됩니다. 그래서 coffees 는 Latte, Cappuccino, Espresso 가 됩니다.
앵커가 유용한 경우도 몇 가지 있지만, 미묘한 버그를 일으킬 수 있는 측면이 하나 있습니다. YAML이 처음 소비될 때 참조가 확장된 뒤 버려진다는 점입니다.
그래서 위 예제를 디코딩한 뒤 다시 인코딩하면 결과 YAML은 다음과 같습니다.
coffee: yes, please
favorite: Cappuccino
coffees:
- Latte
- Cappuccino
- Espresso
Helm과 Kubernetes는 YAML 파일을 읽고, 수정하고, 다시 쓰는 경우가 많기 때문에 앵커는 사라집니다.
참고: 차트 간에 스니펫을 공유하려면 YAML 앵커 대신 라이브러리 차트(library chart)를 사용하세요. 라이브러리 차트는 재사용을 위해 설계되었으며, 위에서 설명한 왕복(round-trip) 함정의 영향을 받지 않습니다.
Helm 값 삽입 시 YAML 주입 방지 (Prevent YAML injection when inserting Helm values)
차트 값은 종종 신뢰할 수 없거나 멀티 테넌트(multi-tenant) 소스에서 옵니다. 원시 {{ .Values.foo }} 로 값을 템플릿에 이어 붙이면, 새 줄·: 매핑 표시·목록 표시를 포함한 값이 렌더링된 YAML의 구조를 바꿀 수 있습니다. 이를 YAML 주입(YAML injection) 이라고 합니다. YAML 주입은 설치를 깨뜨릴 뿐만 아니라 Kubernetes 매니페스트에 추가 키를 주입할 수도 있습니다.
모범 사례 (Best practices)
Helm 값을 삽입할 때 YAML 주입을 피하려면, 다음 표와 같이 데이터를 인코딩하거나 구조화해 주는 헬퍼를 선호하세요.
| 목표 (Goal) | 권장 (Prefer) | 피해야 할 것 (Avoid) |
|---|---|---|
| 문자열 스칼라를 따옴표로 감싸기 | {{ .Values.name | quote }} |
흩어진(bare) 필드에 {{ .Values.name }} |
| 문자열을 하나의 YAML 스칼라로 삽입 | quote, 또는 블록 스칼라 헤더(| / >)와 함께 부모 들여쓰기보다 큰 N 을 쓰는 {{- .Values.config | nindent N }} (보통 부모 + 2) |
헤더 없이 흩어진 {{ .Values.config }} 또는 nindent — 값에 새 줄이 있으면 새 키가 시작될 수 있음 |
| 맵/리스트(컬렉션) 삽입 | 부모 들여쓰기보다 큰 N 을 쓰는 {{ toYaml .Values.extraEnv | nindent N }} (보통 부모 + 2). toYaml 은 컬렉션을 생성하고 nindent 는 그 위치만 옮김 |
toYaml 없이 {{ .Values.extraEnv | nindent N }}, 부모 들여쓰기보다 크지 않은 N, 또는 수작업으로 만든 key: {{ . }} 반복문 |
다음은 값을 삽입할 때 YAML 주입을 방지하기 위한 일반적인 모범 사례입니다.
- YAML 안에서 렌더링되는 모든
{{ ... }}표현식을 신뢰할 수 없는 입력으로 취급하세요. quote,toYaml,nindent/indent를 함께 사용해 YAML 구조를 보존하세요. 문자열 연결로 YAML을 수동으로 만들지 마세요.- CI에서
helm template과 스키마 검증을 실행해 잘못된 값이 클러스터에 도달하기 전에 실패하게 하세요.
예제 (Examples)
안전한 중첩 객체 (Safe nested object: map to YAML, then indent)
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "mychart.fullname" . | quote }}
data:
# values.config is a map; toYaml emits nested YAML, nindent only indents it
config.yaml: |
{{- toYaml .Values.config | nindent 4 }}
블록 스칼라(block scalar) 아래의 문자열도 같은 아이디어입니다. {{- 를 유지해 불필요한 빈 줄을 없애고, 부모 들여쓰기보다 큰 N 을 선택하세요.
data:
app.conf: |
{{- .Values.appConf | nindent 4 }}
안전하지 않은 패턴 (Unsafe patterns)
# BAD: a value of "x\n evil: true" becomes an extra key
data:
app.conf: {{ .Values.appConf }}
# BAD: nindent without a block scalar header only adds spaces
data:
app.conf: {{ .Values.appConf | nindent 4 }}
더 알아보기 (Learn more)
- Template Functions and Pipelines — 템플릿 함수와 파이프라인
- Template Function List — 템플릿 함수 목록 (
toYaml,quote,nindent등)