안녕하세요. 스포카 프로그래머 한유리입니다.

최근 스포카에서는 ECS에서 EKS로 넘어가며 kubernetes(이하 k8s)를 사용하고 있습니다. 생각보다 k8s의 리소스들을 관리하는 것이 복잡하였고 자연스레 아래 불편한 점들을 발견했습니다.

  • 리소스 변경이 언제 적용되었는지 확인하기 힘들다.
  • 리소스 파일이 많아 설치 과정이 번거롭다.
  • 여러 리소스 파일에 변경 사항이 있으면 롤백하기 복잡하다.
  • 환경별 리소스 파일을 분리하며 코드 중복이 생겼다.
  • production 클러스터에 local 혹은 staging 리소스를 적용하는 경우가 있었다.

조금 더 효율적이고 안전하게 k8s 리소스 관리를 하기 위해 Helm이라는 k8s package manager를 도입하게 되었습니다.

이번 글에서는 Helm을 도입하는 과정과 후기를 공유하고자 합니다.

Helm

Helm-logo

Helm?

Helm은 위에서 간단히 설명한 것처럼 복잡한 k8s 리소스들을 간편하게 관리할 수 있도록 도와주는 툴입니다.

하나의 커맨드로 클러스터 내에 리소스들을 설치하고 변경사항을 반영 할 수 있으며, 이러한 변경사항들은 리비전으로 관리할 수 있습니다. 또한, .tar.gz확장자로 클러스터 리소스 정의를 패키징하여 원격 저장소를 통해 공유 할 수 있도록 도와줍니다.

설치

$ brew install helm
$ helm version
version.BuildInfo{Version:"v3.1.2", GitCommit:"d878d4d45863e42fd5cff6743294a11d28a9abce", GitTreeState:"clean", GoVersion:"go1.14"}

brew를 이용하여 Helm을 쉽게 설치 할 수 있습니다. (Mac OS 기준) 다른 운영체제의 경우 해당 문서에 설치하는 방법이 나와있습니다.

이번 글에서는 3.1.2 버전을 사용하였습니다.

Chart

설치가 완료되었다면 본격적으로 Helm을 이용하여 클러스터 내에 리소스를 설치해봅시다.

Chart는 Helm에서 사용하는 패키지의 포맷입니다. helm create <Name>을 통해서 Chart의 기본 구조를 생성할 수 있습니다.

이번 글에서는 dodo-helm-tutorial이라는 이름의 Chart를 만들어보겠습니다.

$ helm create dodo-helm-tutorial
Creating dodo-helm-tutorial

$ tree ./dodo-helm-tutorial
./dodo-helm-tutorial
├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── ingress.yaml
│   ├── service.yaml
│   ├── serviceaccount.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml

3 directories, 9 files

create 명령 후 dodo-helm-tutorial이라는 이름의 폴더가 만들어졌습니다. 내부를 확인하니 위와 같은 파일들이 자동으로 생성되었습니다. 각각 파일들은 아래와 같은 역할을 합니다.

  • Chart.yaml: Chart의 정보를 정의하는 파일로 Chart의 이름, 버전 등을 정의합니다.
  • charts: dependency chart 파일들이 해당 디렉토리 아래에 생기게 됩니다.
  • template: k8s 리소스 템플릿이 보관되는 디렉토리입니다.
    • NOTES.txt: Chart를 설치 후 출력되는 내용을 정의합니다.
    • *.yaml: 클러스터에 띄울 리소스 템플릿 파일들입니다.
  • values.yaml: 템플릿에 사용될 변수들을 모아놓은 파일입니다.

Helm으로 리소스 띄우기

이제 기존 리소스들을 Helm으로 띄워보려 합니다.

$ helm list
NAME    NAMESPACE       REVISION        UPDATED STATUS  CHART   APP VERSION

helm list 명령어를 통해 현재 어떤 차트가 릴리즈 되어있는지 확인이 가능합니다. 1 아직은 아무것도 조회되지 않습니다.

$ cd dodo-helm-tutorial

$ tree .
.
├── Chart.yaml
├── charts
├── templates
│   ├── configmap.yaml
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml

2 directories, 5 files

$ cat templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: dodo-helm-tutorial-config
data:
  SENTRY__DSN: https://sentry.io/
  WEB__CROSS_ORIGIN__0: http://localhost:8080
  WEB__CROSS_ORIGIN__1: http://127.0.0.1:8080
  WEB__CROSS_ORIGIN__2: http://localhost:8081
  WEB__CROSS_ORIGIN__3: http://127.0.0.1:8081

$ cat templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dodo-helm-tutorial-web
  labels:
    app.kubernetes.io/name: dodo-helm-tutorial-web
    app.kubernetes.io/component: web
    app.kubernetes.io/part-of: web-prod
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: dodo-helm-tutorial-web
      app.kubernetes.io/component: web
  template:
    metadata:
      labels:
        app.kubernetes.io/name: dodo-helm-tutorial-web
        app.kubernetes.io/version: 1.0.0
        app.kubernetes.io/component: web
        app.kubernetes.io/part-of: web-prod
    spec:
      containers:
      - name: dodo-helm-tutorial-web
        image: python-ping:latest
        ports:
        - name: http
          containerPort: 8000
        resources:
          requests:
            memory: "256Mi"
            cpu: "25m"
          limits:
            memory: "384Mi"
            cpu: "250m"
        imagePullPolicy: Never

$ cat templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: dodo-helm-tutorial-web
  labels:
    app.kubernetes.io/name: dodo-helm-tutorial-web
    app.kubernetes.io/component: web
    app.kubernetes.io/part-of: web-prod
spec:
  selector:
    app.kubernetes.io/name: dodo-helm-tutorial-web
    app.kubernetes.io/version: 1.0.0
    app.kubernetes.io/component: web
  ports:
    - name: http
      protocol: TCP
      port: 8000
      targetPort: 8000
  type: LoadBalancer

사용하지 않을 파일들을 삭제하고 기존 리소스 파일들을 옮겨왔습니다.2

$ cat Chart.yaml
apiVersion: v2
name: dodo-helm-tutorial
description: python ping web server
type: application
version: 0.1.0
appVersion: 1.16.0

$ cat values.yaml

Chart.yaml는 프로젝트에 맞춰 바꿔주고, values.yaml 파일은 아직 사용하지 않으니 파일을 비워두었습니다.

$ helm install dodo-helm-tutorial .
NAME: dodo-helm-tutorial
LAST DEPLOYED: Mon Mar 30 17:20:35 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

$ helm list
NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                           APP VERSION
dodo-helm-tutorial      default         1               2020-03-30 17:20:35.213435 +0900 KST    deployed        dodo-helm-tutorial-0.1.0        1.16.0

helm install <Release Name> <Chart Directory>를 이용하여 리소스들을 클러스터 내에 설치 할 수 있습니다.3 설치 후 릴리즈 리스트를 확인해보니 dodo-helm-tutorial이름을 가진 릴리즈가 추가 되었습니다!

$ kubectl get deploy
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
dodo-helm-tutorial-web   1/1     1            1           99s

$ kubectl get services
NAME                     TYPE           CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE
dodo-helm-tutorial-web   LoadBalancer   10.97.59.4   localhost     8000:30160/TCP   110s
kubernetes               ClusterIP      10.96.0.1    <none>        443/TCP          17d

$ kubectl get configmap
NAME                        DATA   AGE
dodo-helm-tutorial-config   3      2m31s

kubectl을 통해 클러스터를 확인해보니 각 리소스들도 잘 생성이 된 것을 확인 할 수 있습니다.

$ helm install dodo-helm-tutorial .
Error: rendered manifests contain a resource that already exists. Unable to continue with install: existing resource conflict: namespace: default, name: dodo-helm-tutorial-web, existing_kind: /v1, Kind=Service, new_kind: /v1, Kind=Service

혹시 기존 리소스가 클러스터 내에 띄워져 있는 상태라면 클러스터 내에 동일한 이름의 리소스가 중복될 수 없어 위와 같은 에러가 발생합니다. 리소스의 이름을 수정하거나 기존 리소스를 삭제 후 실행해주세요.

변경사항 반영하기

이렇게 띄운 리소스들은 이제 Helm을 통해서 관리가 가능합니다. 이제 변경사항을 반영하고, 롤백하는 부분에 대해서 알아봅시다.

$ cat templates/deployment.yaml
# ... (생략)
        resources:
          requests:
            memory: "256Mi"
            cpu: "25m"
          limits:
            memory: "512Mi"
            cpu: "250m"
        imagePullPolicy: Never

위와 같이 deployment.yaml에서 메모리의 limit을 384Mi에서 512Mi로 바꾸었습니다.

$ helm upgrade dodo-helm-tutorial .
Release "dodo-helm-tutorial" has been upgraded. Happy Helming!
NAME: dodo-helm-tutorial
LAST DEPLOYED: Mon Mar 30 17:53:57 2020
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None

$ kubectl describe deploy/dodo-helm-tutorial-web
# ... (생략)
  Containers:
   dodo-helm-tutorial-web:
    Image:      python-ping:latest
    Port:       2028/TCP
    Host Port:  0/TCP
    Limits:
      cpu:     250m
      memory:  512Mi
# ... (생략)

helm upgrade <Release Name> <Chart Directory> 명령을 이용하여 이전 스텝에서 만들었던 릴리즈에 변경사항을 반영 할 수 있습니다. kubectl을 통해 deployment를 확인해보니 메모리 limit이 잘 조정이 된 것을 확인 할 수 있습니다.

$ helm history dodo-helm-tutorial
REVISION        UPDATED                         STATUS       CHART                    APP VERSION     DESCRIPTION     
1               Mon Mar 30 17:20:35 2020        superseded   dodo-helm-tutorial-0.1.0 1.16.0          Install complete
2               Mon Mar 30 17:53:57 2020        superseded   dodo-helm-tutorial-0.1.0 1.16.0          Upgrade complete

$ kubectl get secret -l "owner=helm" --all-namespaces
NAMESPACE   NAME                                       TYPE                 DATA   AGE
default     sh.helm.release.v1.dodo-helm-tutorial.v1   helm.sh/release.v1   1      63m
default     sh.helm.release.v1.dodo-helm-tutorial.v2   helm.sh/release.v1   1      29m

이 변경된 사항들은 helm history <Release Name> 명령을 이용하여 해당 릴리즈의 리비전들을 확인할 수 있습니다. 그리고 Helm은 이 리비전들을 각각 secret으로 만들어 클러스터 내에 관리해줍니다.

$ helm rollback dodo-helm-tutorial 1
Rollback was a success! Happy Helming!

$ helm history dodo-helm-tutorial
REVISION        UPDATED                         STATUS       CHART                    APP VERSION     DESCRIPTION     
1               Mon Mar 30 17:20:35 2020        superseded   dodo-helm-tutorial-0.1.0 1.16.0          Install complete
2               Mon Mar 30 17:53:57 2020        superseded   dodo-helm-tutorial-0.1.0 1.16.0          Upgrade complete
3               Mon Mar 30 18:25:36 2020        deployed     dodo-helm-tutorial-0.1.0 1.16.0          Rollback to 1

이 리비전들은 필요시 helm rollback <Release Name> [Revision] 명령을 통해 롤백 하는 데에 사용 할 수 있습니다. 다시 메모리 limit을 384Mi로 변경하기 위해 1번 리비전으로 롤백을 하고 history를 확인하니 새로운 릴리즈가 롤백 정보와 함께 추가되었습니다.

$ kubectl describe deploy/dodo-helm-tutorial-web
# ... (생략)
  Containers:
   dodo-helm-tutorial-web:
    Image:      python-ping:latest
    Port:       2028/TCP
    Host Port:  0/TCP
    Limits:
      cpu:     250m
      memory:  384Mi
# ... (생략)

kubectl로 deployment를 확인하니 deployment 역시 이전 버전으로 잘 롤백이 되어 다시 메모리 limit이 384Mi로 바뀐 것을 확인 할 수 있습니다.

환경별 설정 분리하기

이제 Helm의 template을 활용하여 환경별로 설정을 달리하는 작업을 하려고 합니다.

스포카에서는 production, staging, local 3가지 환경을 관리하고 있으며 각각에 맞추어 설정을 분리하고 있습니다. 이전까지는 설정을 달리 줘야 하는 리소스의 경우 각 환경별로 리소스 파일을 만들어 관리하고 있어 중복되는 코드가 있었습니다.

Helm은 템플릿 기반으로 동작하기 때문에 중복되는 코드를 줄여줄 수 있습니다.

Built-in Objects

templates/*.yaml 파일에서는 Built-in Objects에 접근하여 원하는 값을 사용할 수 있습니다. 예로 template 파일에 {{ .Values.name }} 과 같은 형태로 정의하면 values.yaml 파일(혹은 커맨드의 인자로 넘어온 온 값)에 있는 name이라는 key를 가진 value로 치환됩니다.

Built-in Objects는 Release, Chart, Values, Files 등이 있으며 각각에 대한 사용법은 해당 문서에서 확인 할 수 있습니다.

$ cat templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dodo-helm-tutorial-web
  labels:
    app.kubernetes.io/name: dodo-helm-tutorial-web
    app.kubernetes.io/component: web
    app.kubernetes.io/part-of: web-prod
spec:
  replicas: {{ .Values.web.replicas }}
  selector:
# ... (생략)

$ cat values.yaml
web:
  replicas: 2

먼저 환경별로 분리해야 할 값들을 템플릿 파일에서 분리했습니다. Values를 통해 values.yaml 파일에서 web.replicas를 가져올 수 있도록 하였습니다.

$ helm template dodo-helm-tutorial .
# ... (생략)
# Source: dodo-helm-tutorial/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dodo-helm-tutorial-web
  labels:
    app.kubernetes.io/name: dodo-helm-tutorial-web
    app.kubernetes.io/component: web
    app.kubernetes.io/part-of: web-prod
spec:
  replicas: 2
# ... (생략)

템플릿이 잘 동작 하는지 확인해봅시다. helm template <RELEASE NAME> <Chart Directory> 커맨드를 이용하여 템플릿을 렌더링해 볼 수 있습니다. 커맨드의 결과와 기존 리소스 정의가 동일한 것을 보아 설정이 잘 된 것 같습니다.

$ helm template  --set web.replicas=3 dodo-helm-tutorial .
# ... (생략)
---
# Source: dodo-helm-tutorial/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dodo-helm-tutorial-web
  labels:
    app.kubernetes.io/name: dodo-helm-tutorial-web
    app.kubernetes.io/component: web
    app.kubernetes.io/part-of: web-prod
spec:
  replicas: 3
# ... (생략)

values는 --set 옵션을 이용하여 값을 Override 해줄 수 있습니다. 렌더링 결과를 확인하니 replicas가 3으로 설정이 된 것을 확인 할 수 있습니다.

$ cat local.values.yaml
web:
  replicas: 1

$ helm template  -f local.values.yaml dodo-helm-tutorial .
# ... (생략)
# Source: dodo-helm-tutorial/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dodo-helm-tutorial-web
  labels:
    app.kubernetes.io/name: dodo-helm-tutorial-web
    app.kubernetes.io/component: web
    app.kubernetes.io/part-of: web-prod
spec:
  replicas: 1
  selector:
# ... (생략)

혹은 -f <file> 옵션을 이용 할 수도 있습니다. 이번에는 replicas가 1로 설정이 된 것을 확인 할 수 있습니다.

스포카에서는 환경별로 각각 values.yaml, staging.values.yaml, local.values.yaml 파일을 만들어 관리하고 있습니다.

조건문과 반복문

특정 환경에서는 필요 없는 설정이 있거나, 비슷한 형태가 반복되어 관리 되는 설정이 있습니다. 이런 상황에서 사용할 수 있도록 template 파일에서 조건문, 반복문을 사용할 수 있습니다.

스포카에서 기존 리소스에 적용하며 있었던 경우로 예를 들어보겠습니다.

$ cat templates/configmap.yaml
# ... (생략)
  SENTRY__DSN: {{ .Values.sentry.dsn }}
# ... (생략)

$ cat local.values.yaml
web:
  replicas: 1

sentry:
  dsn: ""

$ helm upgrade -f local.values.yaml dodo-helm-tutorial .
Error: UPGRADE FAILED: error validating "": error validating data: unknown object type "nil" in ConfigMap.data.SENTRY__DSN

로컬에서는 스포카에서 사용하는 error 모니터링 도구 Sentry 관련 설정을 하지 않으려고 합니다. 위에서 진행한 것과 같이 Values로 값을 가져오도록 해보겠습니다. 그리고 설정 파일에는 빈 문자열을 정의하니, 위와 같은 문제가 발생했습니다.45

$ cat local.values.yaml
env: local
web:
  replicas: 1

$ cat templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: dodo-helm-tutorial-config
data:
{{- if or (eq .Values.env "prod") (eq .Values.env "staging") }}
  SENTRY__DSN: https://sentry.io/
{{- end }}
  WEB__CROSS_ORIGIN__0: http://localhost:8080
  WEB__CROSS_ORIGIN__1: http://127.0.0.1:8080
  WEB__CROSS_ORIGIN__2: http://localhost:8081
  WEB__CROSS_ORIGIN__3: http://127.0.0.1:8081

위 문제를 해결하기 위해 조건문을 이용했습니다.

어떤 환경인지 구분하기 위해 values 파일 각각에 env 변수를 추가6하였습니다. 그리고 templates/configmap.yaml에서 조건문을 통해 env의 값이 prodstaging일 경우에만 설정하도록 조건문을 추가하였습니다.

apiVersion: v1	
kind: ConfigMap	
metadata:	
  name: dodo-helm-tutorial-config
data:	
  SENTRY__DSN: https://sentry.io/
  WEB__CROSS_ORIGIN__0: http://localhost:8080
  WEB__CROSS_ORIGIN__1: http://127.0.0.1:8080
  WEB__CROSS_ORIGIN__2: http://localhost:8081
  WEB__CROSS_ORIGIN__3: http://127.0.0.1:8081

또 다른 예로, 웹서버에서 CORS를 설정을 위해 configmap에 위와 같이 허용할 도메인 리스트의 정의가 필요했습니다. 허용할 도메인의 수가 환경마다 다르므로 개수와 관계없이 유동적으로 설정이 가능해야 하고, 각각 명시해 줄 경우 코드가 반복되고 복잡해질 가능성이 있습니다.

$ cat values.yaml
env: prod
web:
  replicas: 2
  crossOrigins:
  - http://localhost:8080
  - http://127.0.0.1:8080
  - http://localhost:8081
  - http://127.0.0.1:8081

$ cat template/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: dodo-helm-tutorial-config
data:
{{- if or (eq .Values.env "prod") (eq .Values.env "staging") }}
  SENTRY__DSN: https://sentry.io/
{{- end }}
{{- range $index, $origin := .Values.web.crossOrigins }}
  WEB__CROSS_ORIGIN__{{ $index }}: {{ $origin }}
{{- end }}

range를 이용하여 반복문으로 해결 할 수 있었습니다. range는 list와 tuple 외에도 map, dict를 반복하는 데 사용할 수 있습니다.

$ helm upgrade -f local.values.yaml dodo-helm-tutorial . 
Release "dodo-helm-tutorial" has been upgraded. Happy Helming!
NAME: dodo-helm-tutorial
LAST DEPLOYED: Mon Mar 30 18:52:16 2020
NAMESPACE: default
STATUS: deployed
REVISION: 4
TEST SUITE: None

$ kubectl describe configmap/dodo-helm-tutorial-config
Name:         dodo-helm-tutorial-config
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
WEB__CROSS_ORIGIN__0:
----
http://localhost:8080
WEB__CROSS_ORIGIN__1:
----
http://127.0.0.1:8080
WEB__CROSS_ORIGIN__2:
----
http://localhost:8081
WEB__CROSS_ORIGIN__3:
----
http://127.0.0.1:8081
Events:  <none>

조건문과 반복문을 추가한 상태로 업그레이드를 해보았습니다. 적용된 configmap을 확인해보니 sentry 설정이 없는 것과 CORS 설정이 의도대로 동작한 것을 확인 할 수 있습니다.

Chart hooks

여기까지 Helm으로 클러스터 리소스를 옮기는데 마지막 작업 하나가 남아 있습니다.

서비스가 처음 배포될 때 돌아야 하는 데이터베이스 마이그레이션 Job이었습니다. 한 번 배포 된 이후로는 실행되지 않아도 되기 때문에 기존 templates 파일과 섞여 있는 것이 부자연스럽게 느껴집니다.

이런 상황을 위해 Helm에서는 hooks를 이용하여 릴리즈 생명주기에 개입 할 수 있습니다. install, delete, upgrade, rollback 각각 실행 전후로 hook 지정이 가능합니다.

$ cat templates/post-install.job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration-job
  annotations:
    "helm.sh/hook": post-install
    "helm.sh/hook-weight": "5"
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
# ... (생략)

hook 지정을 위해서는 위와 같이 annotation을 추가하면 됩니다. 데이터베이스 마이그레이션 Job은 첫 설치 이후에 한 번만 돌면 되기 때문에 post-install 훅으로 설정했습니다.

각 key 별 용도는 아래와 같습니다.

  • helm.sh/hook: 어떤 시점에 hook을 동작시킬 지 지정할 수 있으며 ,를 통해 여러 시점을 지정 할 수 있습니다.
  • helm.sh/hook-weight: 실행 순서 결정을 위하여 정수를 통해 중요도를 지정 할 수 있으며 문자열로 정의해야 합니다.
  • helm.sh/hook-delete-policy: hook을 언제 삭제할지 설정할 수 있으며 지정하지 않을 경우 다음 hook이 생성될 때 사라집니다.

Hooks에 대한 더 자세한 설명과 동작은 해당 문서에 기술되어 있습니다.

Hooks를 마지막으로 모든 리소스들을 옮기는 데 성공하였습니다. 🥳

Release 삭제하기

$ helm uninstall dodo-helm-tutorial
release "dodo-helm-tutorial" uninstalled

$ helm ls
NAME    NAMESPACE       REVISION        UPDATED STATUS  CHART   APP VERSION

$ kubectl get deployments
No resources found.

helm install을 통해 생성된 릴리즈들은 helm uninstall <Release Name> 명령을 통해 삭제 할 수 있습니다. install하며 생겼던 모든 리소스들이 사라지고, history 역시 사라지게 됩니다.

$ helm history foobar
REVISION        UPDATED                         STATUS       CHART                    APP VERSION     DESCRIPTION
1               Tue Mar 30 16:31:07 2020        superseded   foobar-0.1.0 1.16.0          Install complete
2               Tue Mar 30 16:31:18 2020        superseded   foobar-0.1.0 1.16.0          Upgrade complete
3               Tue Mar 30 16:31:25 2020        deployed     foobar-0.1.0 1.16.0          Upgrade complete

$ helm uninstall --keep-history foobar
release "foobar" uninstalled

$ helm list --uninstalled  # uninstall 한 릴리즈를 확인하기 위해선 해당 옵션이 필요합니다.
NAME                    NAMESPACE       REVISION        UPDATED                               STATUS          CHART                         APP VERSION
foobar	default  	3       	2020-03-30 16:31:25.686462 +0900 KST	uninstalled	foobar-0.1.0	1.16.0

$ helm history foobar
REVISION        UPDATED                         STATUS       CHART                    APP VERSION     DESCRIPTION
1       	Tue Mar 30 16:31:07 2020	superseded 	foobar-0.1.0	1.16.0     	Install complete
2       	Tue Mar 30 16:31:18 2020	superseded 	foobar-0.1.0	1.16.0     	Upgrade complete
3       	Tue Mar 30 16:31:25 2020	uninstalled	foobar-0.1.0	1.16.0     	Uninstallation complete

$ helm upgrade --install foobar .
Error: UPGRADE FAILED: "foobar" has no deployed releases

리비전 정보들을 날리고 싶지 않은 경우 --keep-history 옵션을 사용하면 history 내역을 uninstall 후에도 확인이 가능합니다. 그러나 삭제된 상태이기 때문에 롤백은 불가능합니다.

알아두면 좋은 깨알 팁

추가로 이번에 Helm을 도입하며 알게 된 소소한 내용을 공유합니다.

Helm의 v2와 v3 그 사이

2019년 11월 Helm은 버전 3을 배포하면서 꽤 많은 것들을 바꾸었습니다.

(필자 기준으로) 가장 큰 변화는 v2에 있었던 tiller를 없앴다는 것입니다.

v2에서는 클라이언트의 요청을 처리해주는 tiller라는 Helm Server를 클러스터 내에 설치하고 권한을 설정을 해야 했습니다. 이제 v3에서는 그러한 과정 없이도 클러스터 내에 리소스들의 설치가 가능해졌습니다. 🎉

이외에도 커맨드 등의 변화가 있으니 더 살펴보고 싶은 분들은 해당 문서를 참고하시면 될 것 같습니다.

plugin 사용하기

Helm에서는 plugin을 설치하여 사용할 수 있도록 지원합니다.

스포카에서는 helm diff 플러그인을 사용하고 있습니다. 배포 전 CI에서 helm diff를 이용하여 배포 전후 k8s 리소스에 어떤 변화가 일어나는지 미리 체크하는 목적으로 유용하게 활용하고 있습니다.

helm diff 외에도 다른 여러 플러그인이 있으니 문서를 참고하면 좋을 듯합니다.

다른 Chart와 함께 사용하기

위에서 만든 Chart는 package 명령어를 통해 패키징하고 원격저장소(repository)에 올리고 공유 할 수 있습니다. Github에서 많은 Chart들을 확인 할 수 있습니다.

$ helm repo add stable https://kubernetes-charts.storage.googleapis.com
$ helm install psql stable/postgresql

위와 같이 repository에서 직접 내려받아 사용할 수 있습니다.

dependencies:
  - name: apache
    version: 1.2.3
    repository: https://example.com/charts
  - name: mysql
    version: 3.2.1
    repository: https://another.example.com/charts

또한 Chart.yaml에 정의하여 만들고 있는 Chart에 dependency로 지정해줄 수도 있습니다. 자세한 방법은 이번 글에서는 다루지 않을 예정이니 해당 문서를 참고해주세요.

맺으며

Helm을 도입한 후 서론에서 언급했던 k8s를 사용하며 지니고 있었던 불편한 점들의 대부분은 커버할 수 있었습니다. 특히 리비전이 관리되기 때문에 롤백할 때 실수를 범하는 일이 훨씬 줄일 수 있고, 언제 배포되었는지 배포 시점을 정확히 확인 할 수 있어 만족스러웠습니다.

필자 개인적으로 Helm에 관한 예제들이 부족하고 대부분의 글이 v2를 기준으로 하고 있어 불편했습니다. 이 글로 여러분의 k8s에 Helm을 도입하는 데 많은 도움이 되었으면 좋겠습니다.

Happy Helming!

  1. helm ls로 줄여 쓸 수 있습니다. 

  2. 이 글에서 사용되는 manifest 파일들은 예시를 위해 변형된 파일들입니다. 

  3. 리소스가 적용되는 순서는 Helm Github 코드에서 확인 할 수 있습니다. 

  4. value를 정의하지 않은 경우에도 발생합니다. 

  5. --set key=null을 통해 null을 지정할 수 있기는 하지만 옵션을 누락할 확률이 위험이 있어 사용하지 않았습니다. 

  6. values.yamlstaging.values.yaml에도 각각 env: prodenv: staging을 추가했습니다. 

스포카에서는 프론트엔드 프로그래머, 시니어 풀스택 프로그래머를 채용 중입니다! 웹 프론트엔드에 관심을 가지고 공부하는 디자이너, 뛰어난 서버 개발자 등 각자의 분야에서 전문적인 사람들이 능력있는 분들과 함께 일하기를 기대하고 있습니다. 채용 정보 페이지를 확인해주세요!