helm을 배워보자 5일차 - chart 만들기

0

helm

목록 보기
5/8

Chart 만들기

이제 직접 chart를 만들어 배포해보도록 하자.

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
  1. Chart.yaml: chart에 대한 metadata와 일부 기능 제어가 들어있다.
  2. charts: 해당 chart에 대한 dependent chart로, optional하게 넣을 수 있다. 가령, db에 대한 기능이 필요할 때 postgres에 관한 chart를 여기에 넣을 수 있다.
  3. templates: kubernetes manifests을 만들기위한 template들이 있다.
  4. NOTES.txt: special template로 kubernetes cluster에 설치되는 것은 아니고, helm install, upgrade 시에 화면에 비춰질 내용들을 담는다.
  5. test-connection.yaml: template들은 test들을 포함할 수 있는데, 이 test들은 install이나 upgrade 명령어에 의해 cluster에 설치되지 않는다. 이 test는 helm test 명령어로 실행된다. 추후에 살펴보도록 하자.
  6. 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 파일

Chart.yaml을 보면 metadata들이 있는 것을 볼 수 있다.

  • Chart.yaml
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"
  1. apiVersion: 이 chart가 사용하는 helm의 version이 적혀있다. 이전에도 말했지만 helm chart v2는 `helm v3를 말한다.
  2. 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

예시만 봐도 어떻게써야할 지 느껴지는 것들이다.

Template 수정

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.fullnameanvil.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가 만들어지는 것이다.

values file 사용하기

누군가가 만든 helm chart를 사용하여 배포할 때, 사용자는 자신이 바꾸고 싶은 것만 입력하고 나머지는 default value로 채워졌으면 할 것이다. 이러한 수요를 만족시키기 위해서 helm은 values.yaml파일을 도입한 것이다. 따라서, values.yaml은 default value에 대한 값들이 있으며, 동시에 사용자에게 어떤 value들이 제공되는 지 알려주는 document와 같다.

우리 chart의 value를 알아보도록 하자.

  • values.yaml
# 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가 되는 것이다. 따라서 deploymentimage value는 nginx:temp이다.

values.yaml 파일을 보다보면 비어있는 부분들이 많이 있을 것이다.

  • 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 패키징

우리가 만든 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에는 몇 가지 재미난 옵션들이 많은데 다음과 같다.

  1. --dependency-update, -u: 우리가 만든 chart에 부가적으로 사용되는 dependent chart를 최신으로 update하라는 것이다. Chart.lock file이 업데이트되고 dependent chart를 chart directory에 넣어준다.
  2. --destination, -d: chart archive를 놓을 위치를 지정하도록 한다.
  3. --app-version: Chart.yaml file의 appVersion property를 설정해주기 위해서 사용된다.
  4. --version: chart의 version을 업데이트해준다.

chart 개발에는 필요했지만 chart archive에는 필요하지 않거나, 빼고싶은 file, driectory가 있을 것이다. 마치 .gitignore를 사용하듯이 helm도 .helmignore를 사용하면 된다. 기본적으로 chart를 create할 때 모두 만들어지므로 별도의 추가적인 설정은 크게 할 필요가 없다.

  • .helmignore
# 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

Linting charts

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를 할 수 있다는 특징이 있다.

0개의 댓글