이제 직접 chart를 만들어 배포해보도록 하자.
helm chart
를 만드는 가장 쉬운 방법 중 하나는 helm create
명령어를 사용하는 것이다. helm create
명령어를 사용하면 필요한 file과 directory 구조를 만들어주며, template에 대한 예제를 보여준다. 이를 통해 helm chart
개발의 뼈대를 만들 수 있는 것이다.
helm create anvil
Creating anvil
helm create
명령어로 현재 directory에 새로운 anvil chart directory를 만들게 된다.
anvil
chart directory 내부를 보도록 하자.
tree
.
├── Chart.yaml
├── charts
├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── hpa.yaml
│ ├── ingress.yaml
│ ├── service.yaml
│ ├── serviceaccount.yaml
│ └── tests
│ └── test-connection.yaml
└── values.yaml
Chart.yaml
: chart에 대한 metadata와 일부 기능 제어가 들어있다.charts
: 해당 chart에 대한 dependent chart로, optional하게 넣을 수 있다. 가령, db에 대한 기능이 필요할 때 postgres에 관한 chart를 여기에 넣을 수 있다.templates
: kubernetes manifests을 만들기위한 template들이 있다.NOTES.txt
: special template로 kubernetes cluster에 설치되는 것은 아니고, helm install, upgrade 시에 화면에 비춰질 내용들을 담는다.test-connection.yaml
: template들은 test들을 포함할 수 있는데, 이 test들은 install
이나 upgrade
명령어에 의해 cluster에 설치되지 않는다. 이 test는 helm test
명령어로 실행된다. 추후에 살펴보도록 하자.values.yaml
: template에 전달되는 default value값들을 담은 yaml파일이다. 이 values.yaml
을 override하여 다른 값들을 넣을 수 있다.실행하는 방법도 아주 간단하다.
helm install myapp anvil
다음의 output이 나올텐데, 이 output은 NOTES.txt
template에 의해 만들어진 것이다.
NAME: myapp
LAST DEPLOYED: Wed Jul 3 16:11:13 2024
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=anvil,app.kubernetes.io/instance=myapp" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT
문제없이 helm chart의 installation이 kubernetes cluster에 설치된 것을 볼 수 있을 것이다. 삭제하는 방법은 installation으로 하면 된다.
helm delete myapp
Chart.yaml
을 보면 metadata들이 있는 것을 볼 수 있다.
apiVersion: v2
name: anvil
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"
apiVersion
: 이 chart가 사용하는 helm의 version이 적혀있다. 이전에도 말했지만 helm chart v2
는 `helm v3를 말한다.version
: 자체적인 해당 chart의 버전이다. 수정 시에 version을 바꾸면 된다.위의 4가지 field들은 필수 요소이다. 이 정보들을 바탕으로 chart의 특징을 부여할 수 있기 때문이다.
몇 가지 optional기능으로 추가적인 정보들을 담을 수 있는데, 다음의 예제를 보면 알 수 있을 것이다.
apiVersion: v2
name: anvil
description: A surprise to catch something speedy.
version: 0.1.0
appVersion: 9.17.49
icon: https://wile.example.com/anvil.svg
keywords:
- road runner
- anvil
home: https://wile.example.com/
sources:
- https://github.com/Masterminds/learning-helm/tree/main/chapter4/anvil
maintainers:
- name: ACME Corp
email: maintainers@example.com
- name: Wile E. Coyote
email: wile@example.com
예시만 봐도 어떻게써야할 지 느껴지는 것들이다.
helm의 template문법은 golang의 template 문법과 같다. 이는 helm이 golang으로 만들어졌기 때문에 가능한 것이다.
다음은 template문법의 예제이다.
product: {{ .Values.product | default "rocket" | quote }}
product
는 key값인 것이다. value는 template
문법을 통해 만들어지는데, template
문법은 {{
, }}
으로 시작하고 끝난다. 위의 예시는 template
문법이 |
를 통해서 왼쪽부터 오른쪽으로 pipeline을 거치는 것을 볼 수 있다. linux
의 pipeline과 같은 원리인 것이다.
맨 왼쪽인 .Values.product
는 template에 전달된 value를 말한다. 이 값은 직접 전달될 수도 있고 values.yaml
에 의해 전달될 수도 있는 값이다. 이 값이 다음 오른쪽으로 넘어가서 default
함수에 들어가게되는 것이다. 이 함수는 helm
의 기본 함수로 만약 .Values.product
의 결과가 아무 값도 없다면 "rocket"
이라는 default value를 넣으라는 것이다. 이 다음 결과는 quote
함수로 들어가는데 이 함수에 들어가면 ''
를 자동으로 씌워준다. 이 결과값이 product
의 value가 되는 것이다.
anvil
에 있는 deployment.yaml
파일을 보도록 하자.
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "anvil.fullname" . }}
labels:
{{- include "anvil.labels" . | nindent 4 }}
...
include
함수는 해당 template에 다른 template를 가져와 쓰는 문법이라고 생각하면 된다. 따라서 include anvil.fullname
은 anvil.fullname
template 정의를 불러오고 argument로 .
을 넣어준 것으로 생각하면 된다. 참고로 anvil.fullname
template 정의는 _helpers.tpl
에 있다.
_helpers.tpl
...
{{- define "anvil.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
...
해당 template
정의가 include
를 통해 호출되는 것이다. 참고로 _helpers.tpl
은 렌더링되어 kubernetes cluster에 전달되지 않는다.
나머지 spec
부분은 다음과 같다.
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "anvil.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "anvil.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "anvil.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
image
부분을 보면 .Values
를 통해서 값을 전달받는 것을 볼 수 있다. 일부 뻔한 부분만 하드코딩되어 있고, 나머지는 전부 template문법으로 .Values
값을 받는 것을 볼 수 있다. 이 .Values
값은 default로 values.yaml
값이 사용된다. values.yaml
도 yaml이기 때문에 key-value
형식인데, 가령 .Values.affinity
이면 affinity
key에 해당하는 value가 들어가는 것이다.
단, 이 .Values
에 타겟이 되는 values.yaml
은 언제든 다른 것들로 override될 수 있다. 즉, 대체될 수 있다는 것이다. 가령 --set
, --set-file
, --set-string
과 같은 명령어로도 가능하고 file을 처음부터 제공할 때 -f
, --values
로 제공하여 바꿀 수도 있다. 이렇게 전달된 value들이 template에 렌더링되어 kuberntes manifests가 만들어지는 것이다.
누군가가 만든 helm chart를 사용하여 배포할 때, 사용자는 자신이 바꾸고 싶은 것만 입력하고 나머지는 default value로 채워졌으면 할 것이다. 이러한 수요를 만족시키기 위해서 helm은 values.yaml
파일을 도입한 것이다. 따라서, values.yaml
은 default value에 대한 값들이 있으며, 동시에 사용자에게 어떤 value들이 제공되는 지 알려주는 document와 같다.
우리 chart의 value를 알아보도록 하자.
# Default values for anvil.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: nginx
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: "temp"
imagePullSecrets: []
일반적인 yaml
파일이다. 이 value.yaml의 구조는 사용자가 결정하는 것이다. 단, 이 value를 사용하는 부분만 잘 넣어주면 된다. 다시 deployment
의 image부분을 보면 다음과 같다.
...
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
...
여기서의 .Values.image.repository
가 바로 values.yaml의 image.repository
이다. 따라서 nginx
가 된다. tag는 temp
이므로(temp
는 필자가 넣었다 원래 ""
일 것이다.) nginx:temp
가 되는 것이다. 따라서 deployment
의 image
value는 nginx:temp
이다.
values.yaml 파일을 보다보면 비어있는 부분들이 많이 있을 것이다.
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name:
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
{}
이렇게 비어있는 값들은 사실 사용자에게 어떤 configuration을 셋업할 수 있는 지 보여주는 하나의 document와 같은 것이다.
우리가 만든 chart directory를 패키징하여 archive file
을 만들 수 있다. 딱히 별 기능은 아닌게 tar, gzip으로 묶고 압축한 것이 전부이기 때문에 .tgz
확장자를 가지며, tar
를 통해서 archive 파일의 압축을 풀어낼 수 있다.
helm을 이용하여 package를 만들어내면 chartname-version.tgz
형식으로 archive파일이 만들어진다. 이를 통해서 helm
은 해당 chart의 version을 추론할 수 있는 것이다.
helm package anvil
anvil-0.1.0.tgz
archive파일이 만들어진 것을 볼 수 있다.
helm package
에는 몇 가지 재미난 옵션들이 많은데 다음과 같다.
--dependency-update
, -u
: 우리가 만든 chart에 부가적으로 사용되는 dependent chart를 최신으로 update하라는 것이다. Chart.lock
file이 업데이트되고 dependent chart를 chart
directory에 넣어준다.--destination
, -d
: chart archive를 놓을 위치를 지정하도록 한다.--app-version
: Chart.yaml
file의 appVersion
property를 설정해주기 위해서 사용된다. --version
: chart의 version을 업데이트해준다.chart 개발에는 필요했지만 chart archive에는 필요하지 않거나, 빼고싶은 file, driectory가 있을 것이다. 마치 .gitignore
를 사용하듯이 helm도 .helmignore
를 사용하면 된다. 기본적으로 chart를 create
할 때 모두 만들어지므로 별도의 추가적인 설정은 크게 할 필요가 없다.
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
마지막으로 helm chart archive 파일만으로도 helm install이 가능하다.
helm install ./anvil-0.1.0.tgz --generate-name
NAME: anvil-0-1719995963
LAST DEPLOYED: Wed Jul 3 17:39:23 2024
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=anvil,app.kubernetes.io/instance=anvil-0-1719995963" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT
helm chart를 개발하면서 yaml template를 많이 만지게된다. 문제는 너무 사용하기 까다롭기 때문에 bug가 많다는 것이다. 이러한 문제를 해결하기 위해서 helm에서는 lint
기능을 제공해준다.
helm lint anvil
==> Linting anvil
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failed
archive 파일 역시도 lint를 할 수 있다는 특징이 있다.