Helm, 트러블슈팅, 기타
- 25.1 Helm 소개
- 25.2 설치와 구성
- 25.3 Helm2 vs Helm3
- 25.4 Helm 컴포넌트
- 25.5 Helm Charts
- 25.6 Helm 기본 사용법
- 25.7 차트 파라미터 커스터마이징
- 25.8 Helm 라이프사이클 관리
- 25.9 애플리케이션 장애
- 25.10 컨트롤 플레인 장애
- 25.11 워커 노드 장애
- 25.12 JSONPath
Kubernetes 패키지 관리자인 Helm의 기본 개념과 사용법, 클러스터 트러블슈팅 전략, 그리고 JSONPath를 활용한 고급 쿼리까지 다뤄보자. CKA 시험에서도 자주 나오는 실무 필수 영역들이다.
Helm은 Kubernetes의 패키지 관리자야. 왜 필요한지부터 얘기하자면, Kubernetes에 배포하는 애플리케이션은 보통 굉장히 복잡해. 간단한 워드프레스 사이트만 해도 배포(Deployment), 영구 볼륨, 영구 볼륨 클레임, 서비스, 시크릿, 크론잡 같은 여러 오브젝트가 필요하거든. 각 오브젝트마다 YAML 파일이 필요하고, 전부 kubectl apply로 적용해야 해. 게다가 기본값을 바꾸고 싶으면 여러 YAML 파일을 열어서 일일이 편집해야 하고, 업그레이드할 때도 잘못된 위치에서 잘못된 걸 변경하지 않도록 조심해야 해. 삭제할 때도 앱에 속한 모든 오브젝트를 기억해서 하나씩 지워야 하고.
Helm은 이 패러다임을 바꿔. Kubernetes는 개별 오브젝트만 알지, 그것들이 하나의 앱의 일부라는 걸 모르거든. 하지만 Helm은 처음부터 이런 오브젝트들을 하나의 패키지로 묶어서 봐. 그래서 작업할 때 "워드프레스 패키지를 설치해줘"라고만 하면, 수백 개의 오브젝트가 있어도 알아서 처리해줘.
컴퓨터 게임 설치하는 거랑 비슷해. 게임은 수십만 개의 파일로 구성되어 있는데, 각각을 다운로드하진 않잖아. 설치 프로그램이 알아서 수천 개의 파일을 적절한 위치에 배치해주는 것처럼, Helm도 애플리케이션을 구성하는 YAML 파일과 오브젝트를 한 번에 처리해줘.
Helm이 해주는 일을 정리하면 이래. 하나의 명령으로 전체 앱을 설치하는데, 수백 개의 오브젝트가 필요해도 세부 사항은 신경 쓸 필요가 없어. values.yaml 같은 단일 파일에서 모든 커스텀 설정을 선언할 수 있어서, 여러 YAML을 편집할 필요가 없어. 명령 한 번으로 업그레이드할 수 있고, Helm이 어떤 오브젝트를 어떻게 변경해야 하는지 알아서 파악해. 모든 변경 사항을 추적하니까 이전 리비전으로 롤백도 가능해. 삭제도 한 명령이면 끝이야. 결국 Helm은 패키지 관리자이자, 설치/제거 마법사이자, 릴리스 관리자로 동작하는 거야.
설치 얘기로 넘어가면, Helm을 설치하기 전에 먼저 로컬 컴퓨터에 제대로 작동하는 Kubernetes 클러스터가 있어야 하고, kubectl이 올바른 kubeconfig 파일로 구성되어 있어야 해. Helm은 Linux, Windows, macOS에 설치할 수 있어. Linux에서는 snap이 설치된 시스템이면 간단하게 설치 가능해.
snap install helm --classic
--classic 옵션은 좀 더 편안한 샌드박스로 설치해서 시스템 전체에 대한 접근 권한을 줘. 이렇게 하면 Helm이 홈 디렉터리에 있는 kubeconfig 파일에 쉽게 접근해서 Kubernetes 클러스터에 연결할 수 있어. Debian이나 Ubuntu 같은 APT 베이스 시스템에서는 키와 소스 목록을 추가하는 지침을 따르면 돼. FreeBSD에서는 pkg install helm으로 설치하면 되고. 최신 버전에 맞는 설치 방법은 항상 공식 문서 페이지를 참조하는 게 좋아.
이제 Helm 버전 차이를 알아두자. Helm의 역사를 보면, Helm 1.0이 2016년 2월, Helm 2.0이 2016년 11월, Helm 3.0이 2019년 11월에 출시됐어. 온라인에서 차트나 블로그를 검색하면 두 버전이 섞여 있을 수 있어서 차이점을 알아둬야 해.
가장 큰 변화는 Tiller의 제거야. Helm 2가 출시됐을 때 Kubernetes에는 RBAC이나 커스텀 리소스 정의 같은 기능이 없었어. 그래서 Tiller라는 추가 컴포넌트를 클러스터에 설치해야 했거든. Helm 클라이언트가 Tiller에게 요청하고, Tiller가 Kubernetes와 통신해서 실행하는 중개자 역할이었지. 문제는 Tiller가 기본적으로 신(God) 모드로 실행돼서 뭐든 할 수 있는 권한이 있었다는 거야. 차트 설치에는 편하지만, Tiller 접근 권한이 있는 사용자가 클러스터에서 아무 작업이나 할 수 있다는 보안 문제가 있었어. Kubernetes에 RBAC이 도입되면서 Tiller의 필요성이 줄었고, Helm 3에서 완전히 제거됐어. 이제 Helm이 직접 Kubernetes와 통신하고, 사용자 권한은 RBAC으로 관리해.
두 번째 큰 차이는 3-way strategic merge patch야. Helm은 차트를 설치하거나 업그레이드할 때마다 리비전을 생성하는 스냅샷 기능이 있어. Helm 2에서 롤백하면 현재 차트와 이전 차트만 비교했어. 그래서 누군가 kubectl set image 같은 명령으로 수동 변경을 했으면, 롤백 시 이전 차트와 현재 차트가 같으니까 변경을 감지 못하고 수동 변경이 그대로 남았어. Helm 3는 더 똑똑해. 현재 차트, 이전 차트, 그리고 라이브 상태(실제 Kubernetes 오브젝트의 현재 상태) 세 가지를 비교해. 그래서 수동 변경이 있어도 라이브 상태를 확인해서 원래 상태로 올바르게 되돌릴 수 있어.
Helm의 구성 요소를 보면, 먼저 로컬 시스템에 Helm CLI가 있어. 차트 설치, 업그레이드, 롤백 같은 작업을 수행하는 데 쓰이지. **차트(Chart)**는 파일의 모음으로, Kubernetes 클러스터에서 필요한 오브젝트를 생성하기 위해 Helm이 알아야 하는 모든 지침이 들어 있어. 차트를 클러스터에 적용하면 **릴리스(Release)**가 생성돼. 릴리스는 Helm 차트를 사용해 애플리케이션을 한 번 설치한 것을 뜻해. 각 릴리스 안에서 여러 **리비전(Revision)**을 가질 수 있고, 이미지 업그레이드나 설정 변경을 할 때마다 새 리비전이 만들어져.
차트는 공개 리포지토리에서 찾을 수 있어. Docker Hub에서 이미지를 찾는 것처럼, artifacthub.io에서 Helm 차트를 찾을 수 있지. Helm은 설치한 릴리스 정보, 사용된 차트, 리비전 상태 같은 메타데이터를 Kubernetes 클러스터에 직접 시크릿으로 저장해. 로컬에 저장하면 다른 사람이 쓸 수 없잖아. 클러스터에 저장하니까 팀원 누구든 접근할 수 있고, Helm이 모든 작업과 단계를 항상 추적할 수 있어.
간단한 Hello World 앱으로 보면, 배포(Deployment)와 서비스(Service) 두 개의 오브젝트가 있어. 템플릿 파일에서 이미지 이름이나 복제본 수가 {{ .Values.image }} 같은 형식으로 지정돼 있는데, 이를 템플리팅이라고 해. 실제 값은 values.yaml 파일에 정의돼 있어. 릴리스가 필요한 이유는 동일한 차트로 여러 릴리스를 설치할 수 있기 때문이야. helm install my-site bitnami/wordpress로 하나 만들고, helm install my-second-site bitnami/wordpress로 또 하나 만들면 완전히 독립된 두 개의 워드프레스 사이트가 생기는 거야.
차트 구조를 좀 더 자세히 보면, Helm CLI에서 "이거 설치해줘"라고 하면 뒤에서 모든 무거운 작업을 대신 해줘. 차트 안에서 가장 자주 다루게 되는 파일이 values.yaml이야. 차트에 전달할 수 있는 매개변수가 정의돼 있어서, 원하는 설정으로 모든 걸 설치할 수 있게 해줘.
values.yaml 외에 모든 차트에는 Chart.yaml이라는 파일도 있어. apiVersion은 차트의 API 버전이야. Helm 3용으로 만들어진 차트는 v2로 설정하고, Helm 2용 차트는 v1이거나 이 필드가 아예 없어. appVersion은 차트가 배포하는 애플리케이션의 버전이야. 워드프레스 차트라면 워드프레스 버전이 되겠지. version은 차트 자체의 버전이야. type은 차트의 유형인데, application(기본값, 앱 배포용)과 library(차트 작성에 도움이 되는 유틸리티 제공) 두 가지가 있어. dependencies는 이 차트가 의존하는 다른 차트를 지정해. 워드프레스는 MariaDB 데이터베이스가 필요한데, MariaDB에 자체 Helm 차트가 있으니까 종속성으로 추가하면 돼.
차트 디렉터리 구조를 보면, templates 디렉터리에 템플릿 파일들이 있고, values.yaml과 Chart.yaml이 있어. 추가로 라이선스 파일, readme 파일, 그리고 종속 차트가 있는 charts 디렉터리가 있을 수 있어.
실제 사용법으로 넘어가보자. Helm 작업은 전부 CLI로 해. helm 명령을 실행하면 되고, helm --help로 사용 가능한 명령어 목록을 확인할 수 있어. WordPress를 설치한다고 하면 두 명령이면 돼.
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-release bitnami/wordpress
이게 끝이야. Kubernetes 클러스터에 워드프레스가 배포돼. CLI에서 직접 검색할 수도 있어. helm search hub wordpress로 Artifact Hub 전체를 검색하거나, helm search repo wordpress로 로컬에 추가된 리포지토리에서 검색할 수 있지.
기존 릴리스를 나열하려면 helm list 명령을 실행해. 앱을 삭제하려면 helm uninstall my-release 한 줄이면 돼. Helm이 해당 릴리스에 속한 모든 오브젝트를 추적하고 있으니까 전부 알아서 제거해줘. 리포지토리 관련 명령도 몇 가지 있어. helm repo add로 추가, helm repo list로 목록 확인, helm repo update로 로컬에 캐시된 리포지토리 정보를 최신으로 갱신할 수 있어. helm repo update는 sudo apt-get update랑 비슷한 거야.
차트를 설치할 때 기본값을 그대로 쓰고 싶지 않을 수 있어. 커스터마이징하는 방법은 세 가지야. 첫 번째 방법은 --set 옵션으로 명령줄에서 값을 직접 전달하는 거야.
helm install my-release bitnami/wordpress \
--set wordpressBlogName="Helm Tutorial" \
--set wordpressEmail="john@example.com"
여러 매개변수를 바꾸고 싶으면 --set을 여러 번 쓰면 돼. 이 값들이 values.yaml의 기본값을 재정의(override)해.
두 번째 방법은 커스텀 values 파일을 만드는 거야. 값이 너무 많으면 명령줄이 복잡해지니까. customvalues.yaml 같은 파일을 만들고 거기에 변수를 넣은 다음, --values 옵션으로 전달해.
helm install my-release bitnami/wordpress --values customvalues.yaml
세 번째 방법은 기본 values.yaml 파일 자체를 수정하는 거야. 이 경우 helm pull로 차트를 다운로드하고, --untar 옵션으로 압축을 풀어서 values.yaml을 직접 편집한 다음, 로컬 디렉터리 경로로 설치하면 돼.
helm pull bitnami/wordpress --untar
helm install my-release ./wordpress
라이프사이클 관리는 릴리스가 생성된 후 업그레이드, 롤백 같은 변경을 관리하는 거야. 차트의 특정 버전을 설치하려면 --version 옵션을 써.
helm install nginx-release bitnami/nginx --version 7.1.0
업그레이드가 필요하면 한 명령으로 전부 처리할 수 있어.
helm upgrade nginx-release bitnami/nginx
업그레이드하면 리비전 1이 리비전 2로 바뀌어. 릴리스 히스토리를 보려면 helm history nginx-release를 실행해. 각 리비전에서 어떤 차트 버전이 사용됐는지, 앱 버전은 뭔지, 어떤 작업(설치, 업그레이드, 롤백)이 리비전을 만들었는지 다 볼 수 있어.
업그레이드가 마음에 안 들면 롤백할 수 있어.
helm rollback nginx-release 1
리비전 1의 상태로 되돌리는 건데, 기술적으로 리비전 1로 돌아가는 게 아니라 리비전 1과 동일한 구성의 리비전 3이 생성돼. 주의할 점은 롤백은 Kubernetes 오브젝트의 매니페스트만 복원하는 거야. 영구 볼륨이나 외부 데이터베이스의 실제 데이터는 복원되지 않아. 데이터베이스 백업/복원은 **차트 훅(Chart Hook)**이라는 별도의 메커니즘으로 처리해야 해.
여기서부터 트러블슈팅 영역으로 넘어가자. 애플리케이션 문제 해결의 핵심은 전체 아키텍처를 이해하고, 앞에서부터 뒤까지 모든 구성 요소를 체계적으로 확인하는 거야. 웹 서버와 데이터베이스 서버가 있는 2계층 애플리케이션을 예로 들어보자. 데이터베이스 파드가 데이터베이스 서비스를 통해 웹 서버에 서비스하고, 웹 서버는 웹 서비스를 통해 사용자에게 서비스하는 구조야.
시작하기 전에 애플리케이션이 어떻게 구성되는지 지도를 그려두는 게 좋아. 사용자가 애플리케이션에 접근할 수 없다고 신고하면, 먼저 프런트엔드부터 확인해. 웹 애플리케이션이면 curl로 노드 포트 IP에서 웹 서버에 접근 가능한지 테스트해봐.
다음으로 서비스가 웹 파드의 엔드포인트를 발견했는지 확인해. 서비스에 구성된 셀렉터와 파드의 레이블이 일치하는지 비교해야 해.
파드 자체도 확인해. 실행 중인 상태인지, 재시작 횟수가 많은지 봐. kubectl describe pod 명령으로 이벤트를 확인하고, kubectl logs 명령으로 애플리케이션 로그를 봐. 파드가 장애로 재시작되고 있다면 현재 로그에 이전 실패 원인이 안 나올 수 있으니까, -f 옵션으로 실시간 로그를 보면서 다시 실패할 때까지 기다리거나, --previous 옵션으로 이전 파드의 로그를 확인해. 그 다음은 DB 서비스 상태, DB 파드 자체를 같은 방식으로 확인해.
컨트롤 플레인 장애를 해결하는 순서도 알아두자. 먼저 kubectl get nodes로 클러스터 노드 상태를 확인해서 전부 정상인지 봐. kubeadm으로 배포한 클러스터라면 컨트롤 플레인 구성 요소가 파드로 돌아가니까, kube-system 네임스페이스의 파드가 실행 중인지 확인하면 돼.
kubectl get pods -n kube-system
컨트롤 플레인 구성 요소가 서비스로 배포된 경우에는 마스터 노드에서 kube-apiserver, controller-manager, scheduler 서비스 상태를 확인하고, 워커 노드에서 kubelet과 kube-proxy 서비스 상태를 확인해야 해. 그 다음은 컨트롤 플레인 구성 요소의 로그를 확인하는 거야. kubeadm 환경이면 kubectl logs 명령으로 해당 파드의 로그를 봐. 서비스로 구성된 환경이면 journalctl -u kube-apiserver 같은 호스트의 로깅 솔루션으로 서비스 로그를 확인해.
자주 나오는 패턴이 있어. 파드가 Pending 상태이면 보통 스케줄러에 문제가 있는 거야. 스케줄러가 파드를 노드에 할당하는 역할이니까. kube-system 네임스페이스에서 스케줄러 파드가 CrashLoopBackOff 상태라면 kubectl describe로 이벤트를 보고, 매니페스트 파일(/etc/kubernetes/manifests/kube-scheduler.yaml)에서 잘못된 명령어나 설정이 있는지 확인해. 배포를 스케일업했는데 파드가 안 늘어나면 컨트롤러 매니저 문제일 가능성이 높아. 컨트롤러 매니저 파드의 로그를 확인해서 kubeconfig 파일 경로가 잘못됐다든가, 인증서 경로가 틀렸다든가 하는 문제를 찾아.
워커 노드 장애 해결은 kubectl get nodes로 노드 상태를 확인하는 것부터 시작해. NotReady로 나오면 kubectl describe node 명령으로 세부 정보를 봐.
각 노드에는 일련의 **조건(Conditions)**이 있어서 노드가 왜 실패했는지 방향을 알려줘. OutOfDisk가 True면 디스크 공간이 없는 거고, MemoryPressure가 True면 메모리가 부족한 거야. DiskPressure는 디스크 용량 부족, PIDPressure는 프로세스가 너무 많은 경우야. Ready가 True면 노드가 정상인 거야. 크래시로 워커 노드가 마스터와 통신을 중단하면 이 상태들이 Unknown으로 설정돼. LastHeartbeatTime 필드를 확인해서 노드가 크래시했을 수 있는 시점을 파악해.
노드 자체에 접속해서 확인할 것들이 있어. 가장 먼저 확인해야 할 건 kubelet 상태야. service kubelet status로 상태를 봐. inactive(비활성) 상태면 service kubelet start로 시작하면 돼. kubelet이 실행 중인데도 문제가 있으면 journalctl -u kubelet으로 로그를 확인해. CA 파일을 로드할 수 없다는 에러가 나오면 /var/lib/kubelet/config.yaml에서 clientCAFile 경로가 올바른지 확인해. 컨트롤 플레인에 연결이 거부된다는 에러가 나오면 kubelet의 kubeconfig 파일(/etc/kubernetes/kubelet.conf)에서 API 서버 포트 번호가 올바른지(6443이어야 해) 확인해.
워커 노드 문제 해결 패턴을 정리하면: 컨트롤 플레인에서 노드 상태 확인 -> 문제가 있는 노드에 접속 -> kubelet 서비스 상태 확인 -> kubelet이 실행 중이면 로그 확인 -> 로그에서 원인 찾아서 수정 -> 서비스 재시작 -> 컨트롤 플레인에서 노드 상태 재확인. 이 흐름을 따라가면 돼.
마지막으로 JSONPath 얘기를 하자. 수백 개 노드, 수천 개 오브젝트가 있는 프로덕션 환경에서 필요한 정보를 빠르게 뽑아내려면 JSONPath를 알아야 해. kubectl은 kube API 서버와 통신할 때 JSON 형식으로 데이터를 주고받거든. kubectl get nodes를 실행하면 API 서버에서 받은 JSON 데이터를 사람이 읽기 좋은 형태로 변환해서 보여주는 건데, 이 과정에서 많은 정보가 숨겨져.
JSONPath를 사용하는 네 단계가 있어. 1단계는 필요한 정보를 제공하는 kubectl 명령을 파악하는 거야. 2단계는 그 명령의 출력을 -o json 옵션으로 JSON 형식으로 확인하는 거야. 3단계는 JSON 문서 구조를 보고 JSONPath 쿼리를 작성하는 거야. 4단계는 그 쿼리를 kubectl 명령에 넣는 거야.
kubectl get nodes -o jsonpath='{.items[*].metadata.name}'
이렇게 하면 클러스터의 모든 노드 이름이 나와. 여러 쿼리를 합칠 수도 있어. 노드 이름과 CPU 수를 함께 가져오려면 두 쿼리를 나란히 놓으면 돼. 줄 바꿈을 넣으려면 {"\n"}을, 탭을 넣으려면 {"\t"}를 쓰면 되고.
range 루프를 사용하면 각 항목을 반복하면서 깔끔한 형식으로 출력할 수 있어.
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.capacity.cpu}{"\n"}{end}'
custom-columns를 쓰면 루프보다 더 쉬울 때도 있어. 열 이름과 JSONPath를 쉼표로 구분해서 지정하면 돼. 이때 .items 부분은 빼야 해.
kubectl get nodes -o custom-columns=NODE:.metadata.name,CPU:.status.capacity.cpu
정렬도 가능해. --sort-by 옵션에 JSONPath 쿼리를 지정하면 그 값 기준으로 정렬해.
kubectl get nodes --sort-by=.status.capacity.cpu
처음에는 -o json으로 출력을 보고, jsonpath.com 같은 평가기에서 쿼리를 테스트한 다음, 완성된 쿼리를 kubectl 명령에 넣는 방식으로 연습하는 게 좋아.
정리
25장 읽고 기억할 거 세 가지:
- Helm은 Kubernetes 패키지 관리자로, 복잡한 앱의 설치/업그레이드/롤백/삭제를 단일 명령으로 처리하며, Helm 3에서 Tiller를 제거하고 3-way merge로 라이브 상태까지 비교해 더 안전해졌다.
- 트러블슈팅은 체계적 흐름이 핵심이다. 애플리케이션은 프런트부터 백까지 순서대로, 컨트롤 플레인은 노드 상태 -> 파드 상태 -> 로그 확인, 워커 노드는 kubelet 상태 -> 로그 -> 설정 파일 검증 순서로 따라간다.
- JSONPath와 custom-columns, sort-by를 활용하면 대규모 클러스터에서 원하는 정보만 정확히 추출해서 보고서 형태로 만들 수 있다.