여기서는 Chart의 구조에 대해 조금 더 상세히 알아보자.

기본 차트 파일의 구성

다음과 같은 명령으로 간단한 기본 차트의 형태를 만들 수 있다.

$ helm create testchart
Creating testchart

$ tree testchart
testchart
├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── ingress.yaml
│   └── service.yaml
└── values.yaml

2 directories, 7 files

두 개의 디렉토리 charts, templates이 있다. 디렉토리 안에는 친숙한 yaml파일들이 있는데, 이는 아마 쿠버네티스 리소스를 정의하는 기본 내용들이 들어가 있을 것이다.

자세한 내용은 아래에서 살펴보자.

파일 분석

templates

이 디렉토리에는 쿠버네티스 리소스 파일들과 몇 개의 기본 파일들이 들어 있다.

resources

쿠버네티스 리소스를 정의하는 yaml파일은 그 내용을 살펴보면 바로 이해할 수 있다.

apiVersion: v1
kind: Service
metadata:
{{- if .Values.service.servicename }}
  name: {{ .Values.service.servicename }}
{{- else }}
  name: {{ include "testchart.fullname" . }}
{{- end }}
  labels:
{{ include "testchart.labels.standard" . | indent 4 }}
{{- if .Values.service.labels }}
{{ toYaml .Values.service.labels | indent 4 }}
{{- end }}
{{- if .Values.service.annotations }}
  annotations:
{{ toYaml .Values.service.annotations | indent 4 }}
{{- end }}
spec:
  type: {{ .Values.service.type }}
  {{- if eq .Values.service.type "ClusterIP" }}
  {{- if .Values.service.clusterIP }}
  clusterIP: {{ .Values.service.clusterIP }}
  {{- end }}
  {{- end }}
  ports:
  - port: {{ .Values.service.externalPort }}
{{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }}
    nodePort: {{.Values.service.nodePort}}
{{- else }}
    targetPort: http
{{- end }}
    protocol: TCP
    name: http
  selector:
    app: {{ template "testchart.name" . }}
    release: {{ .Release.Name | quote }}

go-templates을 사용하여 리소스 파일을 변경한다는 것을 알 수 있다. 이 값들은 다음 방법으로 변경할 수 있다.

  • values.yaml: 리소스가 참조할 값을 기록한다
  • passing value to helm install: -f, 혹은 –set을 통해 값을 전달할 수 있다.

즉 values.yaml에 값이 있고, 실행시에 별다른 값을 전달하지 않는다면 values.yaml의 값을 default로 적용하게 될 것이다. 그래서 차트의 설치 혹은 업데이트 시점에 이런 값들을 사용하여 변경된 쿠버네티스 리소스를 만들어내고, 그것을 통해 리소스를 생성하거나 변경하게 될 것이라고 생각할 수 있다.

특히 중요한 것 중 하나는 if-else등을 통해 흐름 제어를 만들 수 있다는 것인데, 예를 들어 위 코드를 보면, 유저가 선택한 서비스 타입이 NodePort일 경우에는 nodePort의 값을 values로부터 참조하고, 다른 것일 경우에는 http(80)을 사용하도록 작성되어 있다. values를 사용하여 리소스 파일의 다양한 변주를 만들어낼 수 있다는 것이다.

yaml파일을 살펴보면 go-templates에는 없는 다양한 액션들을 사용하는 것을 확인할 수 있다. 예를 들어 empty가 그런 경우가 되는데, 이는 charts에서는 기본 go-templates을 확장한 sprig를 사용하고 있기 때문이다. sprig 문서를 확인해 보면 알겠지만 여러 유용한 것들이 많이 있으므로 여러 곳에서도 한번 사용해 볼 만하다.

_helpers.tpl

설명은 그냥 자주쓰는 내용을 쉽고 간단하게 재사용할 수 있도록 따로 빼서 저장해둔다고 나와 있다. 파일 내용을 보면,

cat templates/_helpers.tpl
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "testchart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "testchart.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 -}}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "testchart.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}

예를 들어 차트를 설치했을 때 생기는 릴리즈 이름 같은 것들을 여기서 간단히 호출할 수 있도록 정의한 것을 확인할 수 있다. 사실상 자동으로 생성되며 이 정도의 내용 이외에는 크게 사용하지 않으므로, 딱히 수정할 필요는 없어 보이지만 자주 사용하는 것들이 많이 있다면 여기에 뭔가를 더 추가하여 사용하는 것도 가능하다.

NOTES.txt

이 파일은 helm install을 통해 패키지를 설치하여 릴리즈를 만들 경우에 추가 설명을 출력하기 위해 사용한다. 이것도 예를 들어 확인하는 것이 좋겠다. 어떤 차트, 예를 들어 incubator 차트 저장소에 있는 gogs를 설치한다고 가정한다면, 설치 후에는 다음과 같은 메시지를 확인할 수 있다.

$ helm install incubator/gogs --set image.repository="gogs/gogs-rpi" --set postgresql.imageTag="11.1" --set serviceType="LoadBalancer"
NAME:   quieting-mite
LAST DEPLOYED: Sat Feb  9 17:51:42 2019
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Secret
NAME                      TYPE    DATA  AGE
quieting-mite-postgresql  Opaque  1     2s
quieting-mite-gogs        Opaque  1     2s

==> v1/ConfigMap

NAME                        DATA  AGE
tcp-quieting-mite-gogs-ssh  1     2s
quieting-mite-gogs-config   1     2s

==> v1/PersistentVolumeClaim

NAME                      STATUS   VOLUME                                    CAPACITY  ACCESS MODES  STORAGECLASS  AGE
quieting-mite-postgresql  Bound    pvc-5c2fa8b2-2c93-11e9-a6da-96683186b805  8Gi       RWO           nfs-client    2s
quieting-mite-gogs        Pending  nfs-client                                1s

==> v1/Service

NAME                      TYPE          CLUSTER-IP     EXTERNAL-IP  PORT(S)                    AGE
quieting-mite-postgresql  ClusterIP     10.105.97.166  <none>       5432/TCP                   1s
quieting-mite-gogs        LoadBalancer  10.110.83.253  <pending>    80:30283/TCP,22:30661/TCP  1s

==> v1beta1/Deployment

NAME                      DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
quieting-mite-postgresql  1        1        1           0          1s
quieting-mite-gogs        1        1        1           0          1s

==> v1/Pod(related)

NAME                                      READY  STATUS             RESTARTS  AGE
quieting-mite-postgresql-975d75f54-jcs8c  0/1    ContainerCreating  0         1s
quieting-mite-gogs-744bcfdb76-q8p6k       0/1    Pending            0         1s


NOTES:
1. Get the Gogs URL by running:

  NOTE: It may take a few minutes for the LoadBalancer IP to be available.
        Watch the status with: 'kubectl get svc --namespace default -w quieting-mite-gogs'

  export SERVICE_IP=$(kubectl get svc --namespace default quieting-mite-gogs -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
  echo http://$SERVICE_IP/

2. Register a user.  The first user registered will be the administrator.

설치 후에는 설치된 릴리즈의 정보를 출력해주는데, 마지막에 보면 NOTES를 통해 친절하게 접속법이나 사용법을 알려주는 것을 확인할 수 있다. NOTES.txt는 이 NOTES의 내용을 포함한다.

$ cat gogs/templates/NOTES.txt

1. Get the Gogs URL by running:

{{- if contains "NodePort" .Values.serviceType }}

export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "gogs.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT/

{{- else if contains "LoadBalancer" .Values.serviceType }}

NOTE: It may take a few minutes for the LoadBalancer IP to be available.
Watch the status with: 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "gogs.fullname" . }}'

export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "gogs.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo http://$SERVICE_IP/
{{- else if contains "ClusterIP"  .Values.serviceType }}

export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "gogs.fullname" . }}" -o jsonpath="{.items[0].metadata.name}")
echo http://127.0.0.1:8080/
kubectl port-forward $POD_NAME 8080:80
{{- end }}

2. Register a user.  The first user registered will be the administrator.

이 파일 또한 go-templates을 사용하여 작성한다. 다양한 방식으로 설치한 유저에게 정확하고 친절하게 사용법을 알려주려 사용한다고 생각하면 될 것 같다.

Chart.yaml

이 파일은 차트에 반드시 필요하다. Chart.yaml에는 여러 필드와 그에 해당하는 값들이 들어 있는데, 일단 기본값은 아래와 같다.

$ cat testchart/Chart.yaml
apiVersion: v1
appVersion: "1.0"
description: A Helm chart for Kubernetes
name: testchart
version: 0.1.0

이 파일은 다음의 커맨드를 사용할 때의 결과로 출력된다.

$ helm inspect chart testchart
apiVersion: v1
appVersion: "1.0"
description: A Helm chart for Kubernetes
name: testchart
version: 0.1.0

helm inspect 명령의 chart 파라미터는 이 정보를 출력해준다. 이외에도 이 파일에 사용 가능한 값들을 통해 여러가지 일을 할 수 있는데, 가능한 값들이 뭔지 한번 알아보자.

Chart.yaml에 사용 가능한 필드

  • apiVersion(필수): chart API 버전을 명시한다. 항상 “v1"이다.
  • appVersion: 이 필드는 어플리케이션의 버전을 명시한다. 차트 자체의 버전 계산, 그러니까 우위를 계산할 때 사용하지 않는다.
  • description: 한 줄로 적는 이 차트의 설명
  • home: 이 프로젝트 홈페이지의 URL
  • icon: 이 프로젝트의 아이콘 URL
  • keywords: 배열 형태이며, 프로젝트의 키워드를 적는다. 아마도 검색이나 다른 곳에서 분류를 위해 사용하지 않을까?
  • maintainers: 배열 형태이며, 이 패키지의 관리자를 명시한다. 관리자의 정보는 다음의 세 정보를 사용하여 명시한다.
    • name(필수): 관리자의 이름. 각 관리자를 명시할 때 필수적
    • email: 관리자의 이메일 주소
    • url: 관리자의 url, 뭐 홈페이지 같은것을 이야기하는 것 같다.
  • name(필수): 차트의 이름
  • version(필수): 이 차트의 버전을 기록한다. 포맷은 유의적 버전 2을 사용한다.
  • deprecated: boolean 필드로, 이 차트가 deprecated되었음을 알리는 역할을 한다.
  • tillerVersion: 이 차트를 릴리즈하는데 필요한 tiller의 버전을 명시한다.

필드 version

차트의 버전은 SemVer2, Semantic Versioning 2.0.0(유의적 버전 2.0.0)을 따른다. version필드가 차트의 버전을 나타내며, 이 버전을 가지고 버전 우위 계산을 통해 차트가 조금 더 최신인지 아닌지를 판단한다. 그뿐 아니라 이 버전은 Helm에서 조금 더 많은 부분에서 사용하는데, 예를 들어 helm을 통해 버전 범위를 사용한 패키지 검색을 할 수도 있다.

$ helm search stable/nginx --version "< 0.1.1"
NAME             	CHART VERSION	APP VERSION	DESCRIPTION                                    
stable/nginx-lego	0.1.0        	           	Chart for nginx-ingress-controller and kube-lego
$ helm search stable/nginx --version "< 0.2.1"
NAME                       	CHART VERSION	APP VERSION	DESCRIPTION                                    
stable/nginx-ldapauth-proxy	0.1.2        	1.13.5     	nginx proxy with ldapauth                      
stable/nginx-lego          	0.2.0        	           	Chart for nginx-ingress-controller and kube-lego
$ helm search stable/nginx                   
NAME                       	CHART VERSION	APP VERSION	DESCRIPTION                                                
stable/nginx-ingress       	1.3.0        	0.22.0     	An nginx Ingress controller that uses ConfigMap to store ...
stable/nginx-ldapauth-proxy	0.1.2        	1.13.5     	nginx proxy with ldapauth                                  
stable/nginx-lego          	0.3.1        	           	Chart for nginx-ingress-controller and kube-lego

helm은 패키지를 관리하는 역할을 하는데, 위와 같이 패키지 버전을 선택하여 설치하게 되면 tiller에 선택된 패키지 정보를 전달하여 릴리즈를 생성할 것이다. 또한 릴리즈를 업데이트하는 경우에도 이 버전을 통해 우위 계산을 진행하게 될 것이다.

필드 appVersion

이는 이 패키지의 버전을 정하는 version필드와는 아무런 상관이 없다. 예를 들어 내가 어떤 API서버를 작성하였고 이 서버의 바이너리가 차트로 패키지 된 경우, 이 API 서버의 버전을 appVersion에 넣는다. 차트 버전 우위계산에 사용하지 않는 것이다.

필드 deprecated

이 필드는 boolean으로 이 차트가 deprecated되었음을 알린다. 최신 버전의 차트가 deprecated된 경우, 전체 패키지의 버전들이 모두 deprecated판정을 받게 된다. 만약 그 이후에 같은 이름을 가진 패키지가 deprecated false인 경우, 그 이후 버전부터는 deprecated판정을 받지 않게 된다.

다시 말하면 deprecated 이전의 패키지 버전은 모두 deprecated된다.

README.md

우리는 위에서 helm inspect chart 명령을 통해 Chart.yaml파일을 확인하였다. helm inspect는 chart이외에도 다른 파라미터를 받을 수 있는데, 파라미터는 아래와 같다.

  • readme: readme의 내용을 확인한다.
  • values: values의 내용을 확인한다.

readme 파라미터는 이 차트의 README.md 파일을 확인한다. 따라서,

$ echo "# TESTCHART\

hello world...?" > testchart/README.md
$ helm inspect readme testchart
# TESTCHART
hello world...?

README.md 파일은 대략적인 chart의 정보를 포함한다. 실제로 깃허브에서 차트들을 살펴보면 README.md 파일에 이런저런 정보들이 포함된 것을 알 수 있는데, 예를 들어 이 차트의 목적이 무엇이고 이걸로 뭘 할수 있으며 그렇게 하기 위해 필요한 것은 무엇인지에 대해 작성되어 있다.

또 중요한 것 중 하나는 values.yaml에 대한 간단한 설명이다, 이를테면 값을 전달하지 않을 때 어떤 values가 가지는 default가 무엇인지에 대한 설명이 대략적으로 적혀 있으므로, 굳이 values.yaml파일을 열어보지 않고도 간단히 확인할 수 있게 해주는, 일종의 가이드 역할을 하고 있다고 보면 된다. 특히나 values.yaml은 value의 이름과 값만 지정되어 있으므로 리소스 파일을 열어보지 않으면 이게 어떻게 쓰이는지를 알기가 쉽지 않다.

Chart 개발 가이드에서는 다음과 같은 정보를 포함하도록 안내하고 있다.

  • 이 어플리케이션이나 서비스가 제공할 것에 대한 설명과
  • 차트를 실행하기 위해 필요한 것들이나 미리 준비해야 할 것들,
  • values.yaml과 default values에 대한 설명
  • 이 차트를 설치하거나 설정하는데 필요한 정보들

이 파일은 반드시 README.md의 이름을 가져야 한다. 그렇다고 대소문자를 구분하는 것은 아니지만, 거의 관습적으로 이렇게 쓰지 않나 싶다.

values.yaml

차트를 작성하는데 필요한 가장 중요한 파일인 values.yaml에 대해 알아보자.

위에서 우리는 templates디렉토리 안 파일들이 go-templates으로 작성된 것을 확인하였는데, 내용을 보면 .Values로부터 값을 참조하였다. values.yaml은 간단하게 이야기하면 리소스 파일들이 참조 가능한 값을 전달해 주는 역할을 한다. 두말않고 바로 파일 내용을 보자.

cat testchart/values.yaml
# Default values for testchart.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1

image:
  repository: nginx
  tag: stable
  pullPolicy: IfNotPresent

nameOverride: ""
fullnameOverride: ""

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  path: /
  hosts:
    - chart-example.local
  tls: []
    #  - secretName: chart-example-tls
    #    hosts:
    #      - chart-example.local

resources: {}
  # We usually recommend not to specify default resources and to leave this as a conscious
  # choice for the user. This also increases chances charts run on environments with little
  # resources, such as Minikube. If you do want to specify resources, uncomment the following
  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
  # limits:
  #  cpu: 100m
  #  memory: 128Mi
  # requests:
  #  cpu: 100m
  #  memory: 128Mi

nodeSelector: {}

tolerations: []

affinity: {}

values안의 어떤 변수를 참조하기 위해 들여쓰기된 내용대로 진행할 수 있다. 예를 들어 이미지를 정의한 image의 태그를 참조하고 싶은 경우 image.tag와 같이 참조하는 것이다.