IT/DevOps

[DevOps/번역글] Helm vs Kustomize: 어떻게 배포할 것인가?

wookiist 2021. 7. 24. 10:43

Prologue

패키징 작업을 하다보면 Helm과 Kustomize를 사용하는 예제들을 종종 찾아볼 수 있습니다. 그러나 정작 둘의 차이점은 명확하게 알지 못한 채, "그냥 클러스터에 패키지 배포 쉽게 해주는 녀석 아니야?"라는 추상적인 개념만 갖고 있다면, 오늘 이 글이 도움이 되지 않을까 싶습니다. 과연 Helm, Kustomize는 무엇이고 어떤 차이점이 있는 걸까요? 그리고 둘 다 비슷한 역할을 한다면, 무엇을 사용하는 것이 더 좋을까요?

오늘 포스트에선 이 두 엔진을 비교해본 좋은 글을 소개합니다.

Helm vs Kustomize: How to deploy your applications in 2020?

본 포스트는 위 글을 읽고 번역한 글임을 밝힙니다.

Introduction

한 때, Helm은 위대한 컨테이너와 쿠버네티스 환경에서 가장 배척받던 도구 중 하나였습니다. 아무런 이유 없이 배척 받는 것도 아니였죠. 그러나 몇 주 전, 필자(원글쓴이)의 팀은 쿠버네티스 클러스터에 애플리케이션 배포를 관리하는 기능을 결정해야 하는 상황에 놓였습니다.

필자는 구글링을 시도했고, 남은 것은 Kustomize와 Helm이었습니다. 그러나 많은 비교글이 대부분 오랜 시간 전에 작성된 내용이었고, 이에 여러분의 결정을 돕고자 직접 최신의 비교 포스트를 작성하게 되었습니다.

TLDR;

Helm v3 (이하 Helm)을 사용하세요. 만족할 겁니다!

Helm은 잘 알려져 있고, 이해하기 쉬우며, 강력한 패턴을 기반으로 하고 있습니다. 또, 괜찮은 CLI를 가지고 있죠. Helm을 이용하면 팀원들은 현재 애플리케이션의 배포 상황을 빠르게 이해할 수 있고, 쿠버네티스 클러스터의 모든 리소스를 쉽게 정리할 수 있습니다.

이유 1: Helm은 여러분이 원하는 곳에 추상화 레이어를 추가해줍니다.

쿠버네티스에 애플리케이션을 배포할 때는 보통 두 번 이상 배포하게 됩니다. 예를 들어, 여러분의 마이크로서비스를 test와 staging이라는 두 환경에 동시에 배포하길 원할 수도 있습니다. 또는 서로 다른 백엔드를 위한 두 대의 로드 밸런서를 배포하길 희망할 수도 있죠.

같은 애플리케이션에 대해 모든 배포마다 저수준의 쿠버네티스 리소스(Deployment, Service, ConfigMap 등)를 하나하나 작성해주는 것은 매우 성가신 일이고, 이 리소스를 kubectl 하나로만 제어하게 되는 것도 불-편 합니다.

더욱이, 같은 애플리케이션을 test와 staging에 배포하기 위한 리소스들은 보통 90% 이상 그 정의가 동일합니다. 그렇지만 서로 다른 DB 주소를 가리켜야 하는 등의 일부 자그마한 수정 작업은 또 필요하죠.

우리 모두는 코드를 중복해서 쓰는 것이 매우 나쁜 일이라고 배웠습니다. 그렇기 때문에, 90% 이상의 동일한 항목은 한 번만, 그러나 변경이 필요한 일부 항목은 변경해줄 수 있는 시스템을 희망하곤 합니다.

  • Helm은 템플릿 방식을 이용해 이 문제를 해결합니다. 그저 필요한 쿠버네티스 정의들을 여느 때처럼 작성해주면 됩니다. 배포 A와 B에서 일부 항목이 달라야 하는 부분이 있다면, 그저 해당 항목을 적을 수 있는 공간 하나만 추가해주고, 설치 시점에 Helm이 해당 공간을 유저가 추가해준 값으로 대체하도록 두면 됩니다.
  • Kustomize는 폴리모픽(다형성의)한 상속 방식으로 이 문제를 해결합니다. 또한 패치와 같은 후처리를 위한 도메인에 특정된 언어와 결합되어 있습니다. 풀어 설명하자면, staging 배포에 관한 내용을 서술한 여러분의 파일은 'generic' 파일에서 상속된다는 의미입니다. 그리고 커스텀 설정 언어를 통해 수정사항과 패치를 추가합니다.

후자의 설명이 굉장히 난해하죠? Kustomize가 난해하기 때문입니다. 게다가, Kustomize는 객체 지향 언어가 상속을 사용할 때 겪는 문제를 똑같이 겪습니다. 예를 들자면, leaky한 추상화죠. 다시 말해, 취약한 기본 클래스를 상속 받아왔을 때 발생하는 문제와 같습니다.

한 마디로, 여러분은 여러분의 애플리케이션 배포를 설정하기 위해 kustomization.yaml 파일을 작성할 때, 상속해온 'generic' 애플리케이션의 배포 설정이 어떻게 되어 있는지도 파악하고 있어야 한다는 것이 문제라는 것입니다. 더 문제가 되는 것은, 이렇게 해서 애플리케이션을 한 번 작성하고 나면, 'generic' 애플리케이션의 배포 설정을 함부로 바꿀 수가 없습니다. 이를 바꾸게 되면 이것을 상속하고 있는 여러분의 애플리케이션까지 설정이 망가지게 되니까요. 만약 'generic' 애플리케이션 배포 설정을 바꾸려면, 이것과 연결된 모든 애플리케이션을 끊어줘야 합니다.

반면에 Helm은 애플리케이션과 그것의 설정이라는 두 요소 사이에 훌륭한 인터페이스를 놓아줍니다. 이는 곧, 두 요소의 연결도를 낮추고, 장기간의 관리가 가능하게 합니다.

Helm Chart라고 불리는 일반적인 애플리케이션 manifest는 소량의 value만을 외부에 노출하여, 쿠버네티스 리소스가 어떻게 구현되는 지에 대해서는 완전히 숨겨 줍니다. 따라서, staging 배포를 구성할 때, value 설정만 잘 해주고, 이를 추적해주면 되는 거죠.

Helm을 이용해 애플리케이션을 배포할 때는, ConfigMap이 로깅을 위한 사이드카 컨테이너가 사용하게 될 리소스 이름을 가지고 있는지, 고민할 필요가 없습니다.

이유 2: 명시적으로 작성하는 것이 암시적인 것보다 낫습니다.

Kustomize

Kustomize는 YAML 기반의 자체 설정 언어를 사용합니다. 이 언어는 유용하지만, 어떤 면에서는 복잡하고 이해하기 쉽지 않은 작업을 수행하는 몇 가지 키워드를 가지고 있습니다. 다음의 예를 살펴보겠습니다.

# file: microserviceA-staging/kustomization.yaml
bases:
  - ../_bases/microserviceA/
commonLabels:
  app: microserviceA-staging

위 파일의 구문을 해석해보겠습니다. bases 키워드는 제너릭 microserviceA 리소스에서 모든 쿠버네티스 리소스의 상속을 처리합니다. 그리고 모든 리소스에 추가로 microserviceA-staging 레이블을 붙여줍니다.

이것은 확실히 훌륭한 방식입니다. 그러나 실제로는 commonLabels가 동작하는 방식은 그저 여러분의 리소스 정의에서 metadata.labels 필드를 변경하는 것에 지나지 않습니다.

  • commonLabels는 레이블을 파드 템플릿에 추가합니다.
  • commonLabels는 레이블을 디플로이먼트, 서비스, 또는 인그레스 리소스의 Selector에 추가합니다.

아, 오해는 말아주세요! Kustomize가 이렇게 동작하는 것은 괜찮은 것입니다. 이렇게 동작하면, 일반적인 유즈케이스에서 이러한 방식이 상당히 쉽기 때문입니다. 그러나 문서화되지 않았을 뿐만 아니라, 새로운 사용자에게는 예상치 못한 동작일 수 있습니다.

몇 가지 '이상하며' 문서화되지 않은 동작을 더 나열해보겠습니다.

  • namespace 필드가 bases 에 나열된 파일에서 상속되지 않습니다.
  • 필요한 설정 파일들을 추가하기 위해 configMapGenerator 를 사용하면, 이 파일에 줄 바꿈 작업이나 공백 제거 작업이 수행되어 원본 파일이 변경됩니다. 이는 공백에 유의해야 하는 설정 파일이나 바이너리 파일을 오염시킬 수 있습니다.

Helm

이전 Kustomize와 Helm의 템플릿 및 설치 명령어를 비교해보세요.

# file: _charts/microserviceA/templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
stringData:
  database_password: {{ .Values.databasePassword }}

staging 배포를 위한 설치 명령어는 이렇습니다.

$ helm install \
  --set databasePassword=mypassword \
  microserviceA-staging ./_charts/microserviceA

별 다른 설명이 없어도 둘의 차이를 이해하실 수 있겠죠?

이유 3: Helm은 리소스를 추적합니다.

Kustomize는 기본적으로 참조하고 있는 제너릭 쿠버네티스 리소스를 커스텀화하는 컴파일러입니다. 따라서 Kustomize로 빌드한 파일은 kubectl 명령어를 통해서 바로 배포할 수 있습니다.

kustomize build . | kubectl apply -f -

이런 방식은 매우 '유닉스스러운' 방식이긴 합니다만, 이렇게 하면 쿠버네티스 클러스터에서 변경된 내용을 추적할 수 없다는 문제가 있습니다. 그리고 쉽게 롤백 하는 방법도 없죠. 거기다가 지금은 사용하지 않는 오래된 리소스를 남겨둘 위험이 있습니다. 이전 버전과의 관계는 무시하고 새로운 버전을 배포하기만 하니까요.

지금까지는 템플릿을 이용해서 value를 변경하는 시스템으로서의 Helm에 대해 이야기해보았습니다. 이를 Helm의 'packaging' 기능이라고 합니다. 그러나 Helm은 클러스터의 '구성 관리' 기능도 갖추고 있습니다. 예를 들어, 이 클러스터에 어떤 패키지가 설치되어 있고, 어떤 리소스가 이 패키지에 속하며, 각각의 설정은 어떻게 이루어져 있는지 등을 관리하고 있는 것이죠. 이런 의미에서 보면 Helm은 쿠버네티스 세계에서의 'apt-get' 이나 'yum'이라고 부를만 합니다.

결과적으로, Helm에는 install 명령어만 있는 것이 아닙니다.

helm list <...>         # 현재 설치된 패키지를 보여줍니다
helm install <...>      # 새로운 패키지를 설치합니다
helm upgrade <...>      # 이미 설치된 패키지를 업그레이드 합니다
helm get values <...>   # 현재 설의 설정을 보여줍니다
helm uninstall <...>    # 이미 설치된 패키지를 제거합니다

마무리

이 포스트는 쿠버네티스 디플로이먼트를 유지 관리하는 데에 도움이 될 수 있는 Helm과 Kustomize를 모두 시도해본 결과를 정리한 글입니다.

필자는 이 테스트에서 먼저 Helm을 이용했고, 괜찮다는 느낌을 받았습니다. 그러고 나서 Kustomize가 어떻게 사용되고, 어떻게 동작하는 지 이해하기 위해 Kustomize에 대해 자세히 살펴보았습니다. 아쉽게도 Helm과 Kustomize를 비교한 글을 많이 찾을 수 없었습니다.

Kustomize를 사용하게 되면서 느낀 점은 다음의 순서로 변해갔습니다.

  1. Kustomize가 어떻게 동작하는 것인지 정말로 이해하지 못했습니다. 그래서 처음엔 Kustomize를 시작하는 것을 좋아하지 않았습니다. 또한 시간도 소요되었습니다.
  2. 요점을 파악한 뒤엔 '그리 나쁘지 않네' 라 생각했습니다.
  3. 하지만 이상한 상황들을 맞이하게 되었고, 문서가 부족하다는 것을 발견하게 되었습니다.

위와 같은 변화를 거쳐 결국 Kustomize 도전을 포기하고 Helm으로 돌아가게 되었습니다.

이러한 결과를 단순히 필자의 감정이 아니라 '공식화'하기 위해 최선을 다했고, 그 결과 이 글이 되었습니다. 부디 이 글이 여러분이 방향성을 정의할 때 도움이 되길 바랍니다.

참고

마무리

여기까지 따라오시느라 고생이 많으셨습니다. 만약 이 글이 도움이 되셨다면 글 좌측 하단의 하트❤를 눌러주시면 감사하겠습니다.

혹시라도 글에 이상이 있거나, 이해가 가지 않으시는 부분, 또는 추가적으로 궁금하신 내용이 있다면 주저 마시고 댓글💬을 남겨주세요! 빠른 시간 안에 답변을 드리겠습니다 😊

반응형