Post

Helm 소개

Helm 소개

CloudNet@ Gasida님이 진행하는 CI/CD + ArgoCD + Vault Study 를 진행하며 학습한 내용을 공유합니다.

이번 포스트에서는 O’Reilly GitOps Cookbook 의 4장에 해당 하는 Helm에 대해 알아보겠습니다.


1. Helm 소개

Helm은 Go Template 언어를 사용하여 Kubernetes Application을 설치하고 관리하는 과정을 더 편리하게 돕는 솔루션입니다. HelmKustomize와 유사하지만 Chart라는 개념이 존재합니다. Chart는 패키지 매니저처럼 동작하며 여러 개의 Kubernetes Manifest를 패키지 형태로 묶고, 다른 차트에 대한 의존성, 버전 관리, 배포 가능한 Artifact와 같은 요소를 포함합니다.


2. Helm Project의 구조와 구성 요소

Helm Project는 아래와 같은 구성 요소와 구조를 갖습니다.

What is a Helm Chart?

SimplyBlock - What is a Helm Chart? - https://www.simplyblock.io/glossary/what-is-a-helm-chart

2.1. Helm Project의 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
##############################################################
# Helm Chart생성
##############################################################
helm create myapp                            
# Creating myapp

##############################################################
# Helm Chart 구조 확인
##############################################################
tree myapp 
# myapp
# ├── Chart.yaml
# ├── charts
# ├── templates
# │   ├── NOTES.txt
# │   ├── _helpers.tpl
# │   ├── deployment.yaml
# │   ├── hpa.yaml
# │   ├── ingress.yaml
# │   ├── service.yaml
# │   ├── serviceaccount.yaml
# │   └── tests
# │       └── test-connection.yaml
# └── values.yaml

# 4 directories, 10 files

2.2. Helm Project의 구성 요소

항목설명
Chart.yamlChart의 이름, 버전, 의존성 등 메타데이터가 정의된 파일
values.yaml템플릿에서 참조하는 기본 값이 정의되어 있으며, -f 옵션으로 오버라이드 가능
charts/의존성으로 포함된 서브 차트가 위치
templates/Kubernetes Manifest 템플릿이 위치하며, Go Template({{ }}) 문법 사용
_helpers.tpl공통 함수와 템플릿 헬퍼가 정의된 파일
NOTES.txt설치 완료 후 출력되는 안내 메시지가 정의
tests/Chart 테스트 리소스가 포함되어 있으며, helm test로 검증에 사용

3. Creating a Helm Project

3.1. Helm Chart 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
##############################################################
# 실습을 위한 Kind Kubernetes Cluster 배포
##############################################################
kind create cluster --name myk8s --image kindest/node:v1.32.8 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
EOF


##############################################################
# 헬름 차트 디렉터리 레이아웃 생성
##############################################################
mkdir pacman
mkdir pacman/templates
cd pacman

##############################################################
# 루트 디렉터리에 차트 정의 파일 작성 : 버전, 이름 등 정보
##############################################################
cat << EOF > Chart.yaml
apiVersion: v2
name: pacman
description: A Helm chart for Pacman
type: application
version: 0.1.0        # 차트 버전, 차트 정의가 바뀌면 업데이트한다
appVersion: "1.0.0"   # 애플리케이션 버전
EOF

##############################################################
# Helm Chart 구성 요소 생성
##############################################################
# templates 디렉터리에 Go 템플릿 언어와 Sprig 라이브러리의 템플릿 함수를 사용해 정의한 배포 템플릿 파일 작성 : 애플리케이션 배포
## deployment.yaml 파일에서 템플릿화 : dp 이름, app 버전, replicas 수, 이미지/태그, 이미지 풀 정책, 보안 컨텍스트, 포트 
cat << EOF > templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name}}            # Chart.yaml 파일에 설정된 이름을 가져와 설정
  labels:
    app.kubernetes.io/name: {{ .Chart.Name}}
    {{- if .Chart.AppVersion }}     # Chart.yaml 파일에 appVersion 여부에 따라 버전을 설정
    app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}     # appVersion 값을 가져와 지정하고 따옴표 처리
    {{- end }}
spec:
  replicas: {{ .Values.replicaCount }}     # replicaCount 속성을 넣을 자리 placeholder
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name}}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name}}
    spec:
      containers:
        - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion}}"   # 이미지 지정 placeholder, 이미지 태그가 있으면 넣고, 없으면 Chart.yaml에 값을 설정
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          securityContext:
            {{- toYaml .Values.securityContext | nindent 14 }} # securityContext의 값을 YAML 객체로 지정하며 14칸 들여쓰기
          name: {{ .Chart.Name}}
          ports:
            - containerPort: {{ .Values.image.containerPort }}
              name: http
              protocol: TCP
EOF

## service.yaml 파일에서 템플릿화 : service 이름, 컨테이너 포트
cat << EOF > templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: {{ .Chart.Name }}
  name: {{ .Chart.Name }}
spec:
  ports:
    - name: http
      port: {{ .Values.image.containerPort }}
      targetPort: {{ .Values.image.containerPort }}
  selector:
    app.kubernetes.io/name: {{ .Chart.Name }}
EOF

# 차트 기본값(default values)이 담긴 파일 작성 : 애플리케이션 배포 시점에 다른 값으로 대체될 수 있는 기본 설정을 담아두는 곳
cat << EOF > values.yaml
image:     # image 절 정의
  repository: quay.io/gitops-cookbook/pacman-kikd
  tag: "1.0.0"
  pullPolicy: Always
  containerPort: 8080

replicaCount: 1
securityContext: {}     # securityContext 속성의 값을 비운다
EOF

# 디렉터리 레이아웃 확인
tree
├── Chart.yaml    # 차트를 설명하며, 차트 관련 메타데이터를 포함
├── templates     # 차트 설치에 사용되는 모든 템플릿 파일
│   ├── deployment.yaml  # 애플리케이션 배포에 사용되는 헬름 템플릿 파일들
│   └── service.yaml
└── values.yaml   # 차트 기본값

# (참고) securityContext의 값을 채우기 위해 toYaml 함수를 사용하였으므로 values.yaml 의 보안 컨텍스트 속성값은 YAML 객체여야 한다.
# 예를 들면 다음과 같다.
securityContext:
  capabilities:
    drop:
    - ALL
  readOnlyRootFilesystem: true
  runAsNonRoot: true
  runAsUser: 1000

3.2. Helm Chart Rendering (Template)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#
helm template .
---
# Source: pacman/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: pacman
  name: pacman           # Chart.yaml 파일에 설정된 이름을 가져와 설정
spec:
  ports:
    - name: http
      port: 8080         # values.yaml 파일에서 가져옴
      targetPort: 8080
  selector:
    app.kubernetes.io/name: pacman
---
# Source: pacman/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pacman            # Chart.yaml 파일에 설정된 이름을 가져와 설정
  labels:
    app.kubernetes.io/name: pacman
    app.kubernetes.io/version: "1.0.0"     # Chart.yaml appVersion 값을 가져와 지정하고 따옴표 처리
spec:
  replicas: 1     # replicaCount 속성을 넣을 자리 placeholder
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman
    spec:
      containers:
        - image: "quay.io/gitops-cookbook/pacman-kikd:1.0.0"  # 두 속성의 내용이 하나로 연결
          imagePullPolicy: Always
          securityContext: # 보안 컨텍스트는 빈 값
              {} # securityContext의 값을 YAML 객체로 지정하며 14칸 들여쓰기
          name: pacman
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP

# --set 파라미터를 사용하여 기본값을 재정의
helm template --set replicaCount=3 .
...
spec:
  replicas: 3     # replicaCount 속성을 넣을 자리 placeholder
... 

3.3. Helm Chart 배포 및 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
##############################################################
# Helm Chart 배포
##############################################################
helm install pacman .
# NAME: pacman
# LAST DEPLOYED: Sun Oct 26 02:39:32 2025
# NAMESPACE: default
# STATUS: deployed
# REVISION: 1
# TEST SUITE: None

##############################################################
# 배포된 Helm Chart 및 리소스 확인
##############################################################
helm list
# NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
# pacman  default         1               2025-10-26 02:39:32.102294317 +0900 KST deployed        pacman-0.1.0    1.0.0      

kubectl get deploy,pod,svc,ep
kubectl get pod -o yaml | kubectl neat | yq  # kubectl krew install neat 
kubectl get pod -o json | grep securityContext -A1

##############################################################
# Helm Release의 배포 이력 확인 (History)
##############################################################
helm history pacman
# REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION     
# 1               Sun Oct 26 02:39:32 2025        deployed        pacman-0.1.0    1.0.0           Install complete

##############################################################
# Helm Secret 확인
##############################################################
# Helm 자체가 배포 릴리스 메타데이터를 저장하기 위해 자동으로 Secret 리소스 생성 : Helm이 차트의 상태를 복구하거나 rollback 할 때 이 데이터를 이용
kubectl get secret
# NAME                           TYPE                 DATA   AGE
# sh.helm.release.v1.pacman.v1   helm.sh/release.v1   1      5m17s

3.4. Helm Chart Upgrade, Metadata 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
##############################################################
# 특정 Value 값에 대한 설정 값을 오버라이드 하며 Helm Chart Upgrade
##############################################################
helm upgrade pacman --reuse-values --set replicaCount=2 .


##############################################################
# 배포 리소스 및 Secret 정보 확인
##############################################################

kubectl get pod
# NAME                      READY   STATUS              RESTARTS   AGE
# pacman-576769bb86-bkgfc   0/1     ContainerCreating   0          0s
# pacman-576769bb86-lfzkf   1/1     Running             0          5m26s

kubectl get secret
# NAME                           TYPE                 DATA   AGE
# sh.helm.release.v1.pacman.v1   helm.sh/release.v1   1      11m
# sh.helm.release.v1.pacman.v2   helm.sh/release.v1   1      20s

# helm 배포 정보 확인
helm get all pacman      # 모든 정보
helm get values pacman   # values 적용 정보
helm get manifest pacman # 실제 적용된 manifest
helm get notes pacman    # chart notes

# 삭제 후 secret 확인
helm uninstall pacman
kubectl get secret
# No resources found in default namespace.

4. Reusing Statements Between Templates

_helpers.tpl에 재사용 가능한 Code Block을 정의함으로써, 여러 파일에서 같은 Template Code를 재사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
##############################################################
# deployment.yaml, service.yaml 에 selector 필드가 동일
##############################################################
## deployment.yaml
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name}}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name}}

## service.yaml
  selector:
    app.kubernetes.io/name: {{ .Chart.Name }}

## 위와 같이 중복되는 selector 필드를 업데이트하려면(selector 필드에 새 레이블 추가 등) 3곳을 똑같이 업데이트 해야함

##############################################################
# _helpers.tpl 파일을 사용해 기존 코드를 리펙터링
##############################################################
## _helpers.tpl 파일 작성
cat << EOF > templates/_helpers.tpl
{{- define "pacman.selectorLabels" -}}   # statement 이름을 정의
app.kubernetes.io/name: {{ .Chart.Name}} # 해당 statement 가 하는 일을 정의
{{- end }}
EOF

## deployment.yaml 수정
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "pacman.selectorLabels" . | nindent 6 }}   # pacman.selectorLabels를 호출한 결과를 6만큼 들여쓰기하여 주입
  template:
    metadata:
      labels:
        {{- include "pacman.selectorLabels" . | nindent 8 }} # pacman.selectorLabels를 호출한 결과를 8만큼 들여쓰기하여 주입
        
## service.yaml 수정
  selector:
    {{- include "pacman.selectorLabels" . | nindent 6 }}


# 변경된 차트를 로컬에서 YAML 렌더링 : _helpers.tpl 설정된 값으로 갱신 확인
helm template .

# _helpers.tpl 파일 수정 : 새 속성 추가
cat << EOF > templates/_helpers.tpl
{{- define "pacman.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name}}
app.kubernetes.io/version: {{ .Chart.AppVersion}}
{{- end }}
EOF

# 변경된 차트를 로컬에서 YAML 렌더링 : _helpers.tpl 설정된 값으로 갱신 확인
helm template .

Template 함수를 정의하는 파일명으로는 _helpers.tpl을 사용하는 것이 일반적이지만, 실제로는 _로 시작하기만 하면 됩니다. 이와 같은 파일들은 Kubernetes Manifest 파일로 취급되지 않습니다.


5. Updating a Container Image in Helm

helm upgrade 명령을 사용해 배포 파일에서 컨테이너 이미지를 갱신하고 실행 중인 인스턴스를 업그레이드 할 수 있고, helm rollback 명령어를 통해 이전 버전으로 롤백할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
##############################################################
# _helpers.tpl 파일 초기 설정으로 수정
##############################################################
cat << EOF > templates/_helpers.tpl
{{- define "pacman.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name}}
{{- end }}
EOF

# helm 배포
helm install pacman .

# 확인 : 리비전 번호, 이미지 정보 확인
helm history pacman
# REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION     
# 1               Sun Oct 26 13:16:31 2025        deployed        pacman-0.1.0    1.0.0           Install complete
kubectl get deploy -owide
# NAME     READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES                                      SELECTOR
# pacman   1/1     1            1           11s   pacman       quay.io/gitops-cookbook/pacman-kikd:1.0.0   app.kubernetes.io/name=pacman

##############################################################
# 1.0.0 -> 1.1.0 으로 업그레이드
##############################################################
# Chart.yaml 파일에 appVersion 필드 갱신
cat << EOF > Chart.yaml
apiVersion: v2
name: pacman
description: A Helm chart for Pacman
type: application
version: 0.1.0
appVersion: "1.1.0"
EOF

# values.yaml 에 이미지 태그 업데이트
cat << EOF > values.yaml
image:
  repository: quay.io/gitops-cookbook/pacman-kikd
  tag: "1.1.0"
  pullPolicy: Always
  containerPort: 8080

replicaCount: 1
securityContext: {}
EOF

# 배포 업그레이드
helm upgrade pacman .
# Release "pacman" has been upgraded. Happy Helming!
# NAME: pacman
# LAST DEPLOYED: Sun Oct 26 13:19:01 2025
# NAMESPACE: default
# STATUS: deployed
# REVISION: 2  # 새 Revision 번호
# TEST SUITE: None

# 확인
helm history pacman
# REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION     
# 1               Sun Oct 26 13:16:31 2025        superseded      pacman-0.1.0    1.0.0           Install complete
# 2               Sun Oct 26 13:19:01 2025        deployed        pacman-0.1.0    1.1.0           Upgrade complete
kubectl get secret
# NAME                           TYPE                 DATA   AGE
# sh.helm.release.v1.pacman.v1   helm.sh/release.v1   1      2m56s
# sh.helm.release.v1.pacman.v2   helm.sh/release.v1   1      26s
kubectl get deploy,replicaset -owide

##############################################################
# 이전 버전으로 롤백
##############################################################
# history 확인
helm history pacman
# REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION     
# 1               Sun Oct 26 13:16:31 2025        superseded      pacman-0.1.0    1.0.0           Install complete
# 2               Sun Oct 26 13:19:01 2025        deployed        pacman-0.1.0    1.1.0           Upgrade complete

# 현재 (2) -> 이전 (1) 롤백
helm rollback pacman 1 && kubectl get pod -w

# 확인
helm history pacman
# REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION     
# 1               Sun Oct 26 13:16:31 2025        superseded      pacman-0.1.0    1.0.0           Install complete
# 2               Sun Oct 26 13:19:01 2025        superseded      pacman-0.1.0    1.1.0           Upgrade complete
# 3               Sun Oct 26 13:21:52 2025        deployed        pacman-0.1.0    1.0.0           Rollback to 1   
kubectl get secret
# NAME                           TYPE                 DATA   AGE
# sh.helm.release.v1.pacman.v1   helm.sh/release.v1   1      6m24s
# sh.helm.release.v1.pacman.v2   helm.sh/release.v1   1      3m54s
# sh.helm.release.v1.pacman.v3   helm.sh/release.v1   1      63s

kubectl get deploy,replicaset -owide

##############################################################
# 리소스 정리
##############################################################
helm uninstall pacman

6. Packaging and Distributing a Helm Chart

helm package 명령을 사용해 Helm Chart를 패키징하고 공개하여 다른 차트의 의존성으로 이용될 수 있도록 하거나, 다른 사용자가 시스템에 배포할 수 있도록 할 수 있습니다.

helm package로 패키징한 차트는 차트 저장소(Repository) 에 게시할 수 있습니다. 차트 저장소는 Package(.tgz 파일) 와 해당 차트들의 메타데이터 정보를 담은 index.html 파일을 포함한 HTTP 서버 형태로 구성됩니다.

새로운 차트를 저장소에 게시할 때는, index.yaml 파일에 새 메타데이터 정보를 갱신한 뒤 패키지 아티팩트를 함께 업로드해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
##############################################################
# 현재 
##############################################################
tree
.
├── Chart.yaml
├── templates
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml

2 directories, 5 files

##############################################################
# Helm Chart Packaging
##############################################################
helm package .
# Successfully packaged chart and saved it to: /home/kkamji/Code/kkamji-lab/study/ci-cd-study/2w/pacman/pacman-0.1.0.tgz

# 확인
ls
# drwxr-xr-x    - kkamji 26 Oct 02:55 -N  templates
# .rw-r--r--  118 kkamji 26 Oct 13:18 -N  Chart.yaml
# .rw-r--r-- 1.2k kkamji 26 Oct 13:29 -N  pacman-0.1.0.tgz
# .rw-r--r--  152 kkamji 26 Oct 13:18 -N  values.yaml

# index.html 파일 생성
helm repo index .
cat index.yaml
# apiVersion: v1
# entries:
#   pacman:
#   - apiVersion: v2
#     appVersion: 1.1.0
#     created: "2025-10-18T18:33:41.240749+09:00"
#     description: A Helm chart for Pacman
#     digest: 1a68e0069016d96ab64344e2d4c2fde2b7368e410f93da90bf19f6ed8ca9495a
#     name: pacman
#     type: application
#     urls:
#     - pacman-0.1.0.tgz
#     version: 0.1.0
# generated: "2025-10-18T18:33:41.239645+09:00"

7. Deploying a Chart From a Repository

helm repo add 명령을 사용하여 원격 저장소를 추가하고, helm install 명령을 사용해 해당 저장소에 저장되어있는 Helm Chart를 배포할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
##############################################################
# bitnami/postgresql 배포 실습
##############################################################
# helm repository add
helm repo add bitnami https://charts.bitnami.com/bitnami

# 현재 추가된 helm repository 확인
helm repo list

# postgresql chart 찾기 및 확인
helm search repo postgresql
helm search repo postgresql -o json | jq

# 배포
helm install my-db bitnami/postgresql \
  --set postgresql.postgresqlUsername=my-default \
  --set postgresql.postgresqlPassword=postgres \
  --set postgresql.postgresqlDatabase=mydb \
  --set postgresql.persistence.enabled=false

# 확인
helm list
# NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
# my-db   default         1               2025-10-26 14:41:54.131687554 +0900 KST deployed        postgresql-18.0.8       18.0.0     

# 배포 리소스 확인
kubectl get sts,pod,svc,ep,secret

# 서드 파티 차트 사용 시, 기본값(default value)나 override 파라미터를 직접 확인 할 수 없고, helm show 로 확인 가능
helm show values bitnami/postgresql

# 실습 후 삭제
helm uninstall my-db

8. Deploying a Chart with Dependency

Chart.yaml 파일의 dependencies 섹션에 다른 차트에 대한 의존성을 추가할 수 있습니다. 위에서 차트를 통해 배포한 서비스들은 간단했지만, 일반적으로 서비스는 데이터베이스, 메일 서버, 분산 캐시 등에 의존하는 경우가 많습니다.

아래 실습에서 PostgreSQL 데이터베이스에 저장된 노래 목록을 반환하는 Java 서비스를 배포해 보겠습니다.

Music application overview

O’Reilly GitOps Cookbook: Kubernetes Automation in Practice
Music application overview - 5.6. Deploying a Chart with Dependency p.111

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
##############################################################
# 의존성을 가진 Helm Chart 생성
##############################################################
mkdir music
mkdir music/templates
cd music

# deployment.yaml
cat << EOF > templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name}}
  labels:
    app.kubernetes.io/name: {{ .Chart.Name}}
    {{- if .Chart.AppVersion }}
    app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
    {{- end }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name}}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name}}
    spec:
      containers:
        - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion}}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          name: {{ .Chart.Name}}
          ports:
            - containerPort: {{ .Values.image.containerPort }}
              name: http
              protocol: TCP
          env:
            - name: QUARKUS_DATASOURCE_JDBC_URL
              value: {{ .Values.postgresql.server | default (printf "%s-postgresql" ( .Release.Name )) | quote }}
            - name: QUARKUS_DATASOURCE_USERNAME
              value: {{ .Values.postgresql.postgresqlUsername | default (printf "postgres" ) | quote }}
            - name: QUARKUS_DATASOURCE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: {{ .Values.postgresql.secretName | default (printf "%s-postgresql" ( .Release.Name )) | quote }}
                  key: {{ .Values.postgresql.secretKey }}
EOF

##############################################################
# service.yaml 생성
##############################################################
cat << EOF > templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: {{ .Chart.Name }}
  name: {{ .Chart.Name }}
spec:
  ports:
    - name: http
      port: {{ .Values.image.containerPort }}
      targetPort: {{ .Values.image.containerPort }}
  selector:
    app.kubernetes.io/name: {{ .Chart.Name }}
EOF

##############################################################
# Chart.yaml 생성 (책에 존재하는 14.2.3 버전의 postgresql dependencies 사용)
##############################################################
cat << EOF > Chart.yaml
apiVersion: v2
name: music
description: A Helm chart for Music service
type: application
version: 0.1.0
appVersion: "1.0.0"
dependencies:
  - name: postgresql
    version: 14.2.3
    repository: "https://charts.bitnami.com/bitnami"
EOF

##############################################################
# 책에 존재하는 해당 차트 버전이 존재하는지 확인
##############################################################
helm search repo postgresql
# NAME                                                    CHART VERSION   APP VERSION     DESCRIPTION                                       
# bitnami/postgresql                                      18.0.8          18.0.0          PostgreSQL (Postgres) is an open source object-...
# bitnami/postgresql-ha                                   16.3.2          17.6.0          This PostgreSQL cluster solution includes the P...
# ...

helm search repo bitnami/postgresql --versions
# NAME                    CHART VERSION   APP VERSION     DESCRIPTION                                       
# bitnami/postgresql      18.0.8          18.0.0          PostgreSQL (Postgres) is an open source object-...
# bitnami/postgresql      18.0.7          18.0.0          PostgreSQL (Postgres) is an open source object-...
# ...
# bitnami/postgresql      14.2.3          16.2.0          PostgreSQL (Postgres) is an open source object-...

##############################################################
# value.yaml 파일 생성
##############################################################
cat << EOF > values.yaml
image:
  repository: quay.io/gitops-cookbook/music
  tag: "1.0.0"
  pullPolicy: Always
  containerPort: 8080

replicaCount: 1

postgresql:
  server: jdbc:postgresql://music-db-postgresql:5432/mydb
  postgresqlUsername: my-default
  postgresqlPassword: postgres
  postgresqlDatabase: mydb  
  secretName: music-db-postgresql
  secretKey: postgresql-password
EOF

##############################################################
# 프로젝트 구조 확인 (결과)
##############################################################
tree  
.
├── Chart.yaml
├── templates
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml

##############################################################
# 의존성으로 선언된 차트를 현재 차트에 다운로드 후 확인
##############################################################
# 의존성 업데이트
helm dependency update
# Hang tight while we grab the latest from your chart repositories...
# ...Successfully got an update from the "aws-observability" chart repository
# ...
# ...Successfully got an update from the "bitnami" chart repository
# Update Complete. ⎈Happy Helming!⎈
# Saving 1 charts
# Downloading postgresql from repo https://charts.bitnami.com/bitnami
# Deleting outdated charts

# 재확인
tree
.
├── Chart.lock
├── Chart.yaml
├── charts
│   └── postgresql-14.2.3.tgz
├── templates
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml


##############################################################
# Helm Chart 배포
##############################################################
helm install music-db .
# NAME: music-db
# LAST DEPLOYED: Sun Oct 26 14:58:53 2025
# NAMESPACE: default
# STATUS: deployed
# REVISION: 1
# TEST SUITE: None

##############################################################
# 배포 확인 (Trouble Shooting 필요)
##############################################################
kubectl get sts,pod,svc,ep,secret,pv,pvc
# NAME                                   READY   AGE
# statefulset.apps/music-db-postgresql   0/1     85s
# statefulset.apps/my-db-postgresql      1/1     18m

# NAME                         READY   STATUS                       RESTARTS   AGE
# pod/music-6c45d566f4-9qdlr   0/1     CreateContainerConfigError   0          85s
# pod/music-db-postgresql-0    0/1     ImagePullBackOff             0          85s
# ...

8.1. Trouble Shooting 1 (music-db-postgresql-0 - ImagePullBackOff)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
##############################################################
# 문제 확인 (music-db-postgresql-0)
##############################################################
## Bitnami 유료화에 따른 docker.io/bitnami 공개 카탈로그 삭제로 인해 이미지 pull 실패
## GeekNews - docker.io/Bitnami 삭제
# https://news.hada.io/topic?id=22803
## Broadcom Introduces Bitnami Secure Images For Production-Ready Containerized Applications 
# - https://news.broadcom.com/app-dev/broadcom-introduces-bitnami-secure-images-for-production-ready-containerized-applications
kubectl describe po music-db-postgresql-0
# ...
# Events:
#   Type     Reason     Age                   From               Message
#   ----     ------     ----                  ----               -------
#   Normal   Scheduled  4m49s                 default-scheduler  Successfully assigned default/music-db-postgresql-0 to myk8s-control-plane
#   Normal   Pulling    104s (x5 over 4m49s)  kubelet            Pulling image "docker.io/bitnami/postgresql:16.2.0-debian-12-r5"
#   Warning  Failed     102s (x5 over 4m42s)  kubelet            Failed to pull image "docker.io/bitnami/postgresql:16.2.0-debian-12-r5": rpc error: code = NotFound desc = failed to pull and unpack image "docker.io/bitnami/postgresql:16.2.0-debian-12-r5": failed to resolve reference "docker.io/bitnami/postgresql:16.2.0-debian-12-r5": docker.io/bitnami/postgresql:16.2.0-debian-12-r5: not found
#   Warning  Failed     102s (x5 over 4m42s)  kubelet            Error: ErrImagePull
#   Warning  Failed     44s (x15 over 4m42s)  kubelet            Error: ImagePullBackOff
#   Normal   BackOff    7s (x18 over 4m42s)   kubelet            Back-off pulling image "docker.io/bitnami/postgresql:16.2.0-debian-12-r5"

##############################################################
# 임시 해결책으로 PostgreSQL 최신 차트 사용
##############################################################
# bitnami/postgresql 최신 차트 버전 확인 확인 (18.1.1)
helm search repo bitnami/postgresql
# NAME                    CHART VERSION   APP VERSION     DESCRIPTION                                       
# bitnami/postgresql      18.1.1          18.0.0          PostgreSQL (Postgres) is an open source object-...

# Dependency 업데이트
cat << EOF > Chart.yaml
apiVersion: v2
name: music
description: A Helm chart for Music service
type: application
version: 0.1.0
appVersion: "1.0.0"
dependencies:
  - name: postgresql
    version: 18.1.1 # 수정
    repository: "https://charts.bitnami.com/bitnami"
EOF

##############################################################
# 최신 버전의 postgresql 차트로 재배포
##############################################################
# 의존성 업데이트 및 확인
helm dependency update

tree
# .
# ├── Chart.lock
# ├── Chart.yaml
# ├── charts
# │   └── postgresql-18.1.1.tgz
# ├── templates
# │   ├── deployment.yaml
# │   └── service.yaml
# └── values.yaml

# 기존 차트 삭제 (upgrade)
helm uninstall music-db

# 재배포
helm install music-db .

NAME: music-db
LAST DEPLOYED: Sun Oct 26 15:27:27 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

##############################################################
# 배포 확인 (postgresql Running, Log 정상)
##############################################################
kubectl get sts,pod,svc,ep,secret,pv,pvc
# NAME                                   READY   AGE
# statefulset.apps/music-db-postgresql   1/1     25s

# NAME                         READY   STATUS                       RESTARTS   AGE
# pod/music-6c45d566f4-9qdlr   0/1     CreateContainerConfigError   0          25s
# pod/music-db-postgresql-0    1/1     Running                      0          25s
# ...

kubectl logs music-db-postgresql-0

8.2. Trouble Shooting 2 (music-6c45d566f4-9qdlr)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
##############################################################
# 문제 확인 (music-6c45d566f4-9qdlr) 
##############################################################
# values.yaml 에서 secretName과 secretKey를 지정했으나 시크릿 키 값이 맞지 않음
kubectl describe po music-6c45d566f4-9qdlr
# ...
# Events:
#   Type     Reason     Age                   From               Message
#   ----     ------     ----                  ----               -------
#   Normal   Scheduled  3m35s                 default-scheduler  Successfully assigned default/music-6c45d566f4-9qdlr to myk8s-control-plane
#   Normal   Pulled     3m26s                 kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 8.791s (8.791s including waiting). Image size: 94836428 bytes.
#   Normal   Pulled     3m23s                 kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 684ms (1.242s including waiting). Image size: 94836428 bytes.
#   Normal   Pulled     3m7s                  kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 750ms (1.156s including waiting). Image size: 94836428 bytes.
#   Normal   Pulled     2m51s                 kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 730ms (730ms including waiting). Image size: 94836428 bytes.
#   Normal   Pulled     2m36s                 kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 752ms (752ms including waiting). Image size: 94836428 bytes.
#   Normal   Pulled     2m20s                 kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 668ms (668ms including waiting). Image size: 94836428 bytes.
#   Normal   Pulled     2m7s                  kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 685ms (685ms including waiting). Image size: 94836428 bytes.
#   Normal   Pulled     115s                  kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 1.048s (1.048s including waiting). Image size: 94836428 bytes.
#   Normal   Pulled     101s                  kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 769ms (769ms including waiting). Image size: 94836428 bytes.
#   Normal   Pulled     56s (x3 over 85s)     kubelet            (combined from similar events): Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 715ms (715ms including waiting). Image size: 94836428 bytes.
#   Normal   Pulling    45s (x13 over 3m34s)  kubelet            Pulling image "quay.io/gitops-cookbook/music:1.0.0"
#   Warning  Failed     16s (x15 over 3m26s)  kubelet            Error: couldn't find key postgresql-password in Secret default/music-db-postgresql

##############################################################
# 시크릿 확인 (key 값이 postgres-password임 => 불일치 발생)
##############################################################
kubectl get secret music-db-postgresql -o yaml
kubectl get secret music-db-postgresql -o yaml | yq '.data | ...|path|join(".")'
# data
# data.postgres-password
# data.postgres-password

##############################################################
# 시크릿 수정 
##############################################################
kubectl edit secret music-db-postgresql
# data.postgres-password -> data.postgresql-password로 수정

##############################################################
# 시크릿 재 확인
##############################################################
kubectl get secret music-db-postgresql -o yaml
kubectl get secret music-db-postgresql -o yaml | yq '.data | ...|path|join(".")'
# data
# data.postgresql-password
# data.postgresql-password

##############################################################
# 배포 확인 (CreateContainerConfigError -> CrashLoopBackOff)
##############################################################
kubectl get sts,pod,svc,ep,secret,pv,pvc
# NAME                                   READY   AGE
# statefulset.apps/music-db-postgresql   1/1     16m

# NAME                         READY   STATUS             RESTARTS        AGE
# pod/music-6c45d566f4-9qdlr   0/1     CrashLoopBackOff   6 (4m37s ago)   16m
# pod/music-db-postgresql-0    1/1     Running            0               16m

##############################################################
# 바뀐 문제 확인 (로그 및 이벤트 확인)
##############################################################
kubectl describe po music-6c45d566f4-9qdlr
# Events:
#   Type     Reason     Age                   From               Message
#   ----     ------     ----                  ----               -------
# ...
#   Normal   Pulled     15m (x2 over 15m)     kubelet            (combined from similar events): Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 725ms (725ms including waiting). Image size: 94836428 bytes.
#   Warning  Failed     11m (x26 over 17m)    kubelet            Error: couldn't find key postgresql-password in Secret default/music-db-postgresql
#   Warning  BackOff    2m28s (x41 over 11m)  kubelet            Back-off restarting failed container music in pod music-6c45d566f4-9qdlr_default(8a0a07b0-a93a-45ea-8ba0-64c37a020411)
#   Normal   Pulling    32s (x34 over 17m)    kubelet            Pulling image "quay.io/gitops-cookbook/music:1.0.0"

kubectl logs music-6c45d566f4-9qdlr  
# 2025-10-26 06:44:34,957 WARN  [io.agr.pool] (agroal-11) Datasource '<default>': FATAL: password authentication failed for user "my-default"
# 2025-10-26 06:44:34,957 WARN  [org.hib.eng.jdb.spi.SqlExceptionHelper] (JPA Startup Thread: <default>) SQL Error: 0, SQLState: 28P01
# 2025-10-26 06:44:34,957 ERROR [org.hib.eng.jdb.spi.SqlExceptionHelper] (JPA Startup Thread: <default>) FATAL: password authentication failed for user "my-default"
# 2025-10-26 06:44:35,031 ERROR [io.qua.run.Application] (main) Failed to start application (with profile prod): org.postgresql.util.PSQLException: FATAL: password authentication failed for user "my-default"
#         at org.postgresql.core.v3.ConnectionFactoryImpl.doAuthentication(ConnectionFactoryImpl.java:623)
#         at org.postgresql.core.v3.ConnectionFactoryImpl.tryConnect(ConnectionFactoryImpl.java:163)
#         at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:215)
#         at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:51)
#         at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:225)
#         at org.postgresql.Driver.makeConnection(Driver.java:466)
#         at org.postgresql.Driver.connect(Driver.java:265)
#         at io.agroal.pool.ConnectionFactory.createConnection(ConnectionFactory.java:210)
#         at io.agroal.pool.ConnectionPool$CreateConnectionTask.call(ConnectionPool.java:513)
#         at io.agroal.pool.ConnectionPool$CreateConnectionTask.call(ConnectionPool.java:494)
#         at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
#         at io.agroal.pool.util.PriorityScheduledExecutor.beforeExecute(PriorityScheduledExecutor.java:75)
#         at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1126)
#         at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
#         at java.base/java.lang.Thread.run(Thread.java:834)

##############################################################
# 문제 진단
##############################################################
# 신규 차트의 values 확인
helm show values bitnami/postgresql --version="18.1.1"

# 위에서 지정한 postgresqlUsername이 -> auth.username으로 바뀐 것을 확인
helm show values bitnami/postgresql --version="18.1.1" | yq '...|path|join(".")' | grep -i username
# global.postgresql.auth.username
# global.postgresql.auth.username
# auth.username
# auth.username

# 위의 내용을 기반으로 신규 차트에서 바뀐 옵션 값 확인
# https://artifacthub.io/packages/helm/bitnami/postgresql/18.1.1

Bitnami Postgresql Global Parameters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
##############################################################
# 문제 해결 (차트 및 템플릿 수정)
##############################################################

# 차트 수정 전 values를 참조하는 곳 확인 (deployment.yaml 수정하면 될 듯)
grep -R "postgresqlUsername" .                                                                              
# ./values.yaml:  postgresqlUsername: my-default
# ./templates/deployment.yaml:              value: {{ .Values.postgresql.postgresqlUsername | default (printf "postgres" ) | quote }}

grep -R "postgresqlPassword" .       
# ./values.yaml:  postgresqlPassword: postgres

grep -R "postgresqlDatabase" . 
# ./values.yaml:  postgresqlDatabase: mydb

grep -R "secretName"          
values.yaml:  secretName: music-db-postgresql
templates/deployment.yaml:                  name: {{ .Values.postgresql.secretName | default (printf "%s-postgresql" ( .Release.Name )) | quote }}

grep -R "secretKey" 
values.yaml:  secretKey: postgresql-password
templates/deployment.yaml:                secretKeyRef:
templates/deployment.yaml:                  key: {{ .Values.postgresql.secretKey }}

# values.yaml 수정
cat << EOF > values.yaml
image:
  repository: quay.io/gitops-cookbook/music
  tag: "1.0.0"
  pullPolicy: Always
  containerPort: 8080

replicaCount: 1

postgresql:
  server: jdbc:postgresql://music-db-postgresql:5432/mydb
  auth:
    username: my-default # 사용자 username
    password: postgres   # 사용자 password
    database: mydb       # 사용자 db name
  secretName: music-db-postgresql
  secretKey: password    # postgresql-password는 관리자 계정 password
EOF

# 수정
cat << EOF > templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name}}
  labels:
    app.kubernetes.io/name: {{ .Chart.Name}}
    {{- if .Chart.AppVersion }}
    app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
    {{- end }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name}}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name}}
    spec:
      containers:
        - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion}}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          name: {{ .Chart.Name}}
          ports:
            - containerPort: {{ .Values.image.containerPort }}
              name: http
              protocol: TCP
          env:
            - name: QUARKUS_DATASOURCE_JDBC_URL
              value: {{ .Values.postgresql.server | default (printf "%s-postgresql" ( .Release.Name )) | quote }}
            - name: QUARKUS_DATASOURCE_USERNAME
              value: {{ .Values.postgresql.auth.username | default (printf "postgres" ) | quote }}
            - name: QUARKUS_DATASOURCE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: {{ .Values.postgresql.secretName | default (printf "%s-postgresql" ( .Release.Name )) | quote }}
                  key: {{ .Values.postgresql.secretKey }}
EOF

##############################################################
# 문제 해결 확인
##############################################################
helm uninstall music-db
helm install music-db .

kubectl get sts,pod,svc,ep,secret,pv,pvc
# NAME                                   READY   AGE
# statefulset.apps/music-db-postgresql   1/1     7m1s

# NAME                         READY   STATUS    RESTARTS   AGE
# pod/music-7cb6dd76c7-9wqkq   1/1     Running   0          4m19s
# pod/music-db-postgresql-0    1/1     Running   0          7m1s

# 포트포워딩
kubectl port-forward service/music 8080:8080

# 확인 
curl -s http://localhost:8080/song
# [{"id":1,"artist":"DT","name":"Quiero Munchies"},{"id":2,"artist":"Lin-Manuel Miranda","name":"We Don't Talk About Bruno"},{"id":3,"artist":"Imagination","name":"Just An Illusion"},{"id":4,"artist":"Txarango","name":"Tanca Els Ulls"},{"id":5,"artist":"Halsey","name":"Could Have Been Me"}]%


##############################################################
# 해결 확인, 자원 정리
##############################################################
helm uninstall music-db
kubectl delete pvc --all

9. Triggering a Rolling Update Automatically

일반적으로 ConfigMap이 변경되더라도 이미 실행 중인 Pod에는 자동으로 반영되지 않습니다. 즉, PodConfigMap의 변경 사실을 인식하지 못하고, 수동으로 재배포(kubectl rollout restart)를 수행해야만 최신 설정이 반영됩니다. 이와 같이, ConfigMap 값이 수정되어도 Pod가 재시작되지 않으면, 애플리케이션은 여전히 구 버전의 설정으로 동작하기 때문에, ConfigMap 에 설정된 내용에 의존하며 동작하는 서비스에서는 장애나 일관성 문제를 야기할 수 있습니다.

이러한 문제를 해결하기 위해 Helmsha256sum 템플릿 함수를 사용하여 ConfigMap 파일의 해시를 Pod Annotation에 주입할 수 있습니다. 해당 방식은 ConfigMap 내용이 바뀔 때마다 해시 값이 달라져 Deployment 템플릿의 스펙이 자동으로 변경되므로, Kubernetes는 이를 새로운 버전의 Deployment로 인식하고 자동으로 Rolling Update를 수행합니다.

9.1. greetings Helm Chart 생성 및 배포

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
##############################################################
# 프로젝트 초기 설정
##############################################################
mkdir -p greetings/templates
cd greetings


# deployment.yaml
cat << EOF > templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}
  labels:
    app.kubernetes.io/name: {{ .Chart.Name }}
    {{- if .Chart.AppVersion }}
    app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
    {{- end }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name }}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name }}
    spec:
      containers:
        - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          name: {{ .Chart.Name }}
          ports:
            - containerPort: {{ .Values.image.containerPort}}
              name: http
              protocol: TCP
          env:
            - name: GREETING
              valueFrom:
                configMapKeyRef:
                  name: {{ .Values.configmap.name }}
                  key: greeting
EOF

# configmap.yaml
cat << EOF > templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: greeting-config
data:
  greeting: Aloha
EOF

# service.yaml
cat << EOF > templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: {{ .Chart.Name }}
  name: {{ .Chart.Name }}
spec:
  ports:
    - name: http
      port: {{ .Values.image.containerPort }}
      targetPort: {{ .Values.image.containerPort }}
  selector:
    app.kubernetes.io/name: {{ .Chart.Name }}
EOF

# values.yaml
cat << EOF > values.yaml
image:
  repository: quay.io/gitops-cookbook/greetings
  tag: "1.0.0"
  pullPolicy: Always
  containerPort: 8080

replicaCount: 1

configmap:
  name: greeting-config
EOF

# Chart.yaml
cat << EOF > Chart.yaml
apiVersion: v2
name: greetings
description: A Helm chart for greetings
type: application
version: 0.1.0
appVersion: "1.0.0"
EOF

##############################################################
# Helm 배포
##############################################################
helm install greetings .

##############################################################
# 리소스 확인
##############################################################
kubectl get deploy,pod,svc,cm 
# NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
# deployment.apps/greetings   1/1     1            1           37s

# NAME                             READY   STATUS    RESTARTS   AGE
# pod/greetings-6b869f6d54-4wqrr   1/1     Running   0          37s

# NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
# service/greetings    ClusterIP   10.96.155.175   <none>        8080/TCP   37s
# service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP    18h

# NAME                         DATA   AGE
# configmap/greeting-config    1      37s
# configmap/kube-root-ca.crt   1      18h

##############################################################
# 포트포워딩으로 확인
##############################################################
kubectl port-forward service/greetings 8080:8080

curl localhost:8080;echo 
# Aloha Alexandra

9.2. greetings Helm Chart ConfigMap 수정 후 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
##############################################################
# ConfigMap 및 appVersions 수정
##############################################################
# configmap.yaml
cat << EOF > templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: greeting-config
data:
  greeting: Hola
EOF

# Chart.yaml
cat << EOF > Chart.yaml
apiVersion: v2
name: greetings
description: A Helm chart for greetings
type: application
version: 0.1.0
appVersion: "1.0.1"
EOF

##############################################################
# 변경된 차트로 업그레이드
##############################################################
helm upgrade greetings .

# pod 상태 확인 (재시작 X)
kubectl get pods                                 
# NAME                         READY   STATUS    RESTARTS   AGE
# greetings-6b869f6d54-4wqrr   1/1     Running   0          7m52s

##############################################################
# 포트포워딩 후 바뀌었는지 다시 확인
##############################################################
kubectl port-forward service/greetings 8080:8080

curl localhost:8080;echo 
# Aloha Alexandra # 바뀌지 않음 (Hola로 바뀌어야 함)

아래와 같이 ConfigMap Object가 변경되어도 Deployment 리소스에는 변경된 부분이 존재하지 않으므로, Pod는 재시작하지 않습니다. 따라서, 환경 변수의 값도 갱신되지 않습니다.

Greetings application with new configuration value

O’Reilly GitOps Cookbook: Kubernetes Automation in Practice
Greetings application with new configuration value - 5.7. Triggering a Rolling Update Automatically p.121

9.3. sha256sum 함수를 사용해 ConfigMap 에 대한 변경사항 Rolling Update Triggering

위와 같은 문제를 해결하기 위해 Helm Chartsha256sum 함수를 사용할 수 있습니다. configmap.yaml 파일 내용에 대한 SHA-256 값을 계산하고 이를 Pod Annotation으로 설정하면 ConfigMap의 내용이 바뀔 때마다 Pod Manifest도 바뀌므로, Rolling Update가 자동으로 시작됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
##############################################################
# Deployment의 pod annotation에 sha256sum 함수 추가
##############################################################
# deployment.yaml (spec.template.metadata.annotation 추가)
cat << EOF > templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}
  labels:
    app.kubernetes.io/name: {{ .Chart.Name }}
    {{- if .Chart.AppVersion }}
    app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
    {{- end }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name }}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name }}
      annotations: # 추가 
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} # 추가
    spec:
      containers:
        - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          name: {{ .Chart.Name }}
          ports:
            - containerPort: {{ .Values.image.containerPort}}
              name: http
              protocol: TCP
          env:
            - name: GREETING
              valueFrom:
                configMapKeyRef:
                  name: {{ .Values.configmap.name }}
                  key: greeting
EOF

##############################################################
# ConfigMap 변경 후 Rolling Update 가 되는지 확인
##############################################################
# 현재 Helm Chart 삭제
helm uninstall greetings

# Chart.yaml (appVersion: "1.0.0" 으로 되돌린 뒤 배포)
cat << EOF > Chart.yaml
apiVersion: v2
name: greetings
description: A Helm chart for greetings
type: application
version: 0.1.0
appVersion: "1.0.0"
EOF

##############################################################
# Helm Chart 배포 및 확인
##############################################################
helm install greetings .

# 배포 확인
kubectl get deploy,pod,svc,cm 
# NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
# deployment.apps/greetings   1/1     1            1           6s

# NAME                            READY   STATUS    RESTARTS   AGE
# pod/greetings-dcd6dbddf-dfmls   1/1     Running   0          6s

# NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
# service/greetings    ClusterIP   10.96.214.104   <none>        8080/TCP   6s
# service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP    18h

# NAME                         DATA   AGE
# configmap/greeting-config    1      6s

##############################################################
# 포트포워딩 후 현재 적용된 Config 내용 확인
##############################################################
kubectl port-forward service/greetings 8080:8080

curl localhost:8080;echo 
# Aloha Ada

##############################################################
# Chart.yaml & ConfigMap 수정 후 helm upgrade 진행
##############################################################
# configmap.yaml
cat << EOF > templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: greeting-config
data:
  greeting: Namaste
EOF

# Chart.yaml
cat << EOF > Chart.yaml
apiVersion: v2
name: greetings
description: A Helm chart for greetings
type: application
version: 0.1.0
appVersion: "1.0.1"
EOF

# Helm Upgrade
helm upgrade greetings .

# 배포 확인 (기존 pod -> Terminating, 신규 pod 생성 중)
kubectl get deploy,pod,svc,cm 
# NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
# deployment.apps/greetings   1/1     1            1           3m27s

# NAME                             READY   STATUS        RESTARTS   AGE
# pod/greetings-6c5c98bb7c-vwcpr   1/1     Running       0          25s
# pod/greetings-dcd6dbddf-dfmls    1/1     Terminating   0          3m27s

# NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
# service/greetings    ClusterIP   10.96.214.104   <none>        8080/TCP   3m27s
# service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP    18h

# NAME                         DATA   AGE
# configmap/greeting-config    1      3m58s

##############################################################
# 포트포워딩 후 현재 적용된 Config 내용 확인 (Aloha -> Namaste로 잘 변경됨)
##############################################################
kubectl port-forward service/greetings 8080:8080

curl localhost:8080;echo 
# Namaste Ada


##############################################################
# 실습 마무리 (자원 삭제)
##############################################################
kind delete cluster --name myk8s

10. 마무리

이번 글에서는 Helm의 기본 구조부터 의존성 관리, 그리고 ConfigMap 변경을 체크섬 기반 롤링 업데이트로 자동화하는 패턴에 대해 알아봤습니다. Kustomize가 원본 YAML을 유지하며 템플릿 없이 변경을 적용하는 데 중점을 둔다면, HelmChart라는 패키징 개념과 Release라는 버전 관리, 그리고 Go Template을 통한 강력한 동적 구성 기능을 제공합니다.


11. Reference


궁금하신 점이나 추가해야 할 부분은 댓글이나 아래의 링크를 통해 문의해주세요.
Written with KKamJi

This post is licensed under CC BY 4.0 by the author.