CICD STUDY-2주차

dev_suha·2025년 10월 25일
0

HELM

HELM 무엇일까?

HELM K8S 패키지 관리

charts를 사용한다.

charts는 k8s yaml 파일을 템플릿으로 만들고 메타정보 파일을 압축한 파일

Helm charts의 경우 기본적으로 artifacthub 에서 찾아서 설치 (https://artifacthub.io/)

출처: https://www.youtube.com/watch?v=ajcyC_6velc

출처 : https://www.youtube.com/watch?v=m7iZtjeIHJw
관리해야하는 매니페스트가 많아지고 → 많아질수록 유지보수가 힘듦

하나의 template을 통해 동적으로 생성하게 해주는 tool 이 필요하다.

출처 : https://www.youtube.com/watch?v=m7iZtjeIHJw

  • template을 통해 여러 구성에서 사용가능
  • helm commands 를 통해서 다양한 환경에서 사용 가능

간단한 helm project 만들기

kind(k8s 배포)

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

helm chart directory layout 생성

Chart.yaml (차트의 정의서)

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# cat Chart.yaml
apiVersion: v2
name: pacman
description: A Helm chart for Pacman
type: application
version: 0.1.0        # 차트 버전, 차트 정의가 바뀌면 업데이트한다
appVersion: "1.0.0"   # 애플리케이션 버전
항목의미
apiVersion: v2Helm 차트 스펙 버전. Helm 3 이상에서 사용
name: pacman차트 이름
description차트 설명
type: application앱 배포용 차트임을 의미
version: 0.1.0Helm 차트 자체의 버전
appVersion: "1.0.0"실제 Pacman 애플리케이션 버전

values.yaml (설정값)

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# cat values.yaml
image:     # image 절 정의
  repository: quay.io/gitops-cookbook/pacman-kikd
  tag: "1.0.0"
  pullPolicy: Always
  containerPort: 8080

replicaCount: 1
securityContext: {}     # securityContext 속성의 값을 비운다
설명
image.repository사용할 컨테이너 이미지 경로
image.tag이미지 버전
image.pullPolicy이미지를 언제 새로 받아올지
image.containerPort컨테이너 내부에서 열리는 포트
replicaCount배포 시 파드 복제 개수
securityContextPod의 보안 컨텍스트
# 헬름 차트 디렉터리 레이아웃 생성
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

# 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 vales 이 담긴 파일 작성 : 애플리케이션 배포 시점에 다른 값으로 대체될 수 있는, 기본 설정을 담아두는 곳
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   # 차트 기본값

helm chart yaml 랜더링

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm template .
---
# Source: pacman/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: pacman
  name: pacman
spec:
  ports:
    - name: http
      port: 8080
      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     # Chart.yaml 파일에 appVersion 여부에 따라 버전을 설정
    app.kubernetes.io/version: "1.0.0"     # 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"   # 이미지 지정 placeholder, 이미지 태그가 있으면 넣고, 없으면 Chart.yaml에 값을 설정
          imagePullPolicy: Always
          securityContext:
              {} # securityContext의 값을 YAML 객체로 지정하며 14칸 들여쓰기
          name: pacman
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP
**helm template --set replicaCount=3 .**
  • set 설정 사용시 values.yaml 보다 우선 적용됨
spec:
  replicas: 3     # 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"   # 이미지 지정 placeholder, 이미지 태그가 있으면 넣고, 없으면 Chart.yaml에 값을 설정
          imagePullPolicy: Always
          securityContext:
              {} # securityContext의 값을 YAML 객체로 지정하며 14칸 들여쓰기
          name: pacman
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP

차트를 kind ( k8s) 배포 및 helm 확인

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm install pacman .
NAME: pacman
LAST DEPLOYED: Mon Oct 20 00:15:50 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm list
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
pacman  default         1               2025-10-20 00:15:50.938977487 +0900 KST deployed        pacman-0.1.0    1.0.0

업그레이드

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# kubectl get pod
NAME                      READY   STATUS    RESTARTS   AGE
pacman-576769bb86-vbm9x   1/1     Running   0          2m4s
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm upgrade pacman --reuse-values --set replicaCount=2 .
Release "pacman" has been upgraded. Happy Helming!
NAME: pacman
LAST DEPLOYED: Mon Oct 20 00:18:17 2025
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# kubectl get pod
NAME                      READY   STATUS              RESTARTS   AGE
pacman-576769bb86-ks4wn   0/1     ContainerCreating   0          4s
pacman-576769bb86-vbm9x   1/1     Running             0          2m30s
부분의미
helm upgrade기존에 설치된 Helm 릴리스를 업그레이드 (재배포)
pacman업그레이드할 릴리스(배포 인스턴스)의 이름
.현재 디렉터리(Chart.yamlvalues.yaml이 있는 곳)를 차트 소스로 사용
--reuse-values이전에 사용했던 values 설정을 그대로 유지
--set replicaCount=2단일 값만 덮어쓰기(override) — replicas를 1 → 2로 변경

helm 배포 정보 확인

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm get all pacman
NAME: pacman
LAST DEPLOYED: Mon Oct 20 00:18:17 2025
NAMESPACE: default
STATUS: deployed
REVISION: 2
CHART: pacman
VERSION: 0.1.0
APP_VERSION: 1.0.0
TEST SUITE: None
USER-SUPPLIED VALUES:
replicaCount: 2

COMPUTED VALUES:
image:
  containerPort: 8080
  pullPolicy: Always
  repository: quay.io/gitops-cookbook/pacman-kikd
  tag: 1.0.0
replicaCount: 2
securityContext: {}

HOOKS:
MANIFEST:
---
# Source: pacman/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: pacman
  name: pacman
spec:
  ports:
    - name: http
      port: 8080
      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     # Chart.yaml 파일에 appVersion 여부에 따라 버전을 설정
    app.kubernetes.io/version: "1.0.0"     # appVersion 값을 가져와 지정하고 따움표 처리
spec:
  replicas: 2     # 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"   # 이미지 지정 placeholder, 이미지 태그가 있으면 넣고, 없으면 Chart.yaml에 값을 설정
          imagePullPolicy: Always
          securityContext:
              {} # securityContext의 값을 YAML 객체로 지정하며 14칸 들여쓰기
          name: pacman
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm get values pacman
USER-SUPPLIED VALUES:
replicaCount: 2
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm get manifest pacman
---
# Source: pacman/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: pacman
  name: pacman
spec:
  ports:
    - name: http
      port: 8080
      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     # Chart.yaml 파일에 appVersion 여부에 따라 버전을 설정
    app.kubernetes.io/version: "1.0.0"     # appVersion 값을 가져와 지정하고 따움표 처리
spec:
  replicas: 2     # 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"   # 이미지 지정 placeholder, 이미지 태그가 있으면 넣고, 없으면 Chart.yaml에 값을 설정
          imagePullPolicy: Always
          securityContext:
              {} # securityContext의 값을 YAML 객체로 지정하며 14칸 들여쓰기
          name: pacman
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP

 
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm get notes pacman
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm uninstall pacman
release "pacman" uninstalled
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# kubectl get secret
No resources found in default namespace.

여러 파일에서 같은 템플릿 코드를 재사용

재사용 가능한 코드 블록을 정의하려면 _helpers.tpl 사용하면 된다.

템플릿 함수를 정의하는 파일명으로는 _helpers.tpl 을 사용하는 것이 일반적이지만, 사실 _로 시작하기만 하면 된다.

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# tree
.
├── Chart.yaml
├── templates
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml

_helpers.tpl

{{- define "pacman.selectorLabels" -}}   # stetement 이름을 정의
app.kubernetes.io/name: {{ .Chart.Name}} # 해당 stetement 가 하는 일을 정의
{{- end }}

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:
        {{- include "pacman.selectorLabels" . | nindent 6 }}

deployments.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 }}
  selector:
    matchLabels:
      {{- include "pacman.selectorLabels" . | nindent 6 }}   # pacman.selectorLabels를 호출한 결과를 6만큼 들여쓰기하여 주입
  template:
    metadata:
      labels:
        {{- include "pacman.selectorLabels" . | nindent 8 }} # pacman.selectorLabels를 호출한 결과를 8만큼 들여쓰기하여 주입
    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

yaml 랜더링

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm template .
---
# Source: pacman/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: pacman
  name: pacman
spec:
  ports:
    - name: http
      port: 8080
      targetPort: 8080
  selector:
      # stetement 이름을 정의
      app.kubernetes.io/name: pacman # 해당 stetement 가 하는 일을 정의
---
# Source: pacman/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pacman            # Chart.yaml 파일에 설정된 이름을 가져와 설정
  labels:
    app.kubernetes.io/name: pacman     # Chart.yaml 파일에 appVersion 여부에 따라 버전을 설정
    app.kubernetes.io/version: "1.0.0"     # appVersion 값을 가져와 지정하고 따움표 처리
spec:
  replicas: 1
  selector:
    matchLabels:
      # stetement 이름을 정의
      app.kubernetes.io/name: pacman # 해당 stetement 가 하는 일을 정의   # pacman.selectorLabels를 호출한 결과를 6만큼 들여쓰기하여 주입
  template:
    metadata:
      labels:
        # stetement 이름을 정의
        app.kubernetes.io/name: pacman # 해당 stetement 가 하는 일을 정의 # pacman.selectorLabels를 호출한 결과를 8만큼 들여쓰기하여 주입
  spec:
      containers:
        - image: "quay.io/gitops-cookbook/pacman-kikd:1.0.0"   # 이미지 지정 placeholder, 이미지 태그가 있으면 넣고, 없으면 Chart.yaml에 값을 설정
          imagePullPolicy: Always
          securityContext:
              {} # securityContext의 값을 YAML 객체로 지정하며 14칸 들여쓰기
          name: pacman
          ports:
            - containerPort: 8080
              name: http
              protocol: TC
  • 들여쓰기가 잘먹히면서 랜더링 되는것을 확인할 수 있다.

배포 파일에서 컨테이너 이미지 갱신 및 실행 중인 인스턴스 업그레이드

chart 배포

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm install pacman .
NAME: pacman
LAST DEPLOYED: Mon Oct 20 00:46:16 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm history pacman
REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION
1               Mon Oct 20 00:46:16 2025        deployed        pacman-0.1.0    1.0.0           Install complete
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# kubectl get deploy -o wide
NAME     READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES                                      SELECTOR
pacman   1/1     1            1           20s   pacman       quay.io/gitops-cookbook/pacman-kikd:1.0.0   app.kubernetes.io/name=pacman

이미지 갱신

# 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

# 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

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm upgrade pacman .
Release "pacman" has been upgraded. Happy Helming!
NAME: pacman
LAST DEPLOYED: Mon Oct 20 00:48:00 2025
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm history pacman
REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION
1               Mon Oct 20 00:46:16 2025        superseded      pacman-0.1.0    1.0.0           Install complete
2               Mon Oct 20 00:48:00 2025        deployed        pacman-0.1.0    1.1.0           Upgrade complete
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# kubectl get secret
NAME                           TYPE                 DATA   AGE
sh.helm.release.v1.pacman.v1   helm.sh/release.v1   1      2m22s
sh.helm.release.v1.pacman.v2   helm.sh/release.v1   1      38s
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# kubectl get deploy,replicaset -o wide
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES                                      SELECTOR
deployment.apps/pacman   1/1     1            1           2m35s   pacman       quay.io/gitops-cookbook/pacman-kikd:1.1.0   app.kubernetes.io/name=pacman

NAME                                DESIRED   CURRENT   READY   AGE     CONTAINERS   IMAGES                                      SELECTOR
replicaset.apps/pacman-576769bb86   0         0         0       2m35s   pacman       quay.io/gitops-cookbook/pacman-kikd:1.0.0   app.kubernetes.io/name=pacman,pod-template-hash=576769bb86
replicaset.apps/pacman-64c54b85f9   1         1         1       51s     pacman       quay.io/gitops-cookbook/pacman-kikd:1.1.0   app.kubernetes.io/name=pacman,pod-template-hash=64c54b85f9

rollback

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm history pacman
REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION
1               Mon Oct 20 00:46:16 2025        superseded      pacman-0.1.0    1.0.0           Install complete
2               Mon Oct 20 00:48:00 2025        deployed        pacman-0.1.0    1.1.0           Upgrade complete
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm rollback pacman 1 && kubectl get pod -w
Rollback was a success! Happy Helming!
NAME                      READY   STATUS              RESTARTS   AGE
pacman-576769bb86-lq669   0/1     ContainerCreating   0          0s
pacman-64c54b85f9-9mb5k   1/1     Running             0          97s
pacman-576769bb86-lq669   1/1     Running             0          2s
pacman-64c54b85f9-9mb5k   1/1     Terminating         0          99s
pacman-64c54b85f9-9mb5k   0/1     Error               0          100s
pacman-64c54b85f9-9mb5k   0/1     Error               0          100s
pacman-64c54b85f9-9mb5k   0/1     Error               0          100s
^C(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm history pacman
REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION
1               Mon Oct 20 00:46:16 2025        superseded      pacman-0.1.0    1.0.0           Install complete
2               Mon Oct 20 00:48:00 2025        superseded      pacman-0.1.0    1.1.0           Upgrade complete
3               Mon Oct 20 00:49:37 2025        deployed        pacman-0.1.0    1.0.0           Rollback to 1
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# kubectl get secret
NAME                           TYPE                 DATA   AGE
sh.helm.release.v1.pacman.v1   helm.sh/release.v1   1      3m57s
sh.helm.release.v1.pacman.v2   helm.sh/release.v1   1      2m13s
sh.helm.release.v1.pacman.v3   helm.sh/release.v1   1      36s
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# kubectl get deploy,replicaset -o wide
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES                                      SELECTOR
deployment.apps/pacman   1/1     1            1           4m10s   pacman       quay.io/gitops-cookbook/pacman-kikd:1.0.0   app.kubernetes.io/name=pacman

NAME                                DESIRED   CURRENT   READY   AGE     CONTAINERS   IMAGES                                      SELECTOR
replicaset.apps/pacman-576769bb86   1         1         1       4m10s   pacman       quay.io/gitops-cookbook/pacman-kikd:1.0.0   app.kubernetes.io/name=pacman,pod-template-hash=576769bb86
replicaset.apps/pacman-64c54b85f9   0         0         0       2m26s   pacman       quay.io/gitops-cookbook/pacman-kikd:1.1.0   app.kubernetes.io/name=pacman,pod-template-hash=64c54b85f9
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman#

values yaml 파일 override

# values 새 파일 작성
cat << EOF > newvalues.yaml
image:
  tag: "1.2.0"
EOF

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm template pacman -f newvalues.yaml . | grep image
        - image: "quay.io/gitops-cookbook/pacman-kikd:1.2.0"   # 이미지 지정 placeholder, 이미지 태그가 있으면 넣고, 없으면 Chart.yaml에 값을 설정
          imagePullPolicy: Always

재사용할 수 있도록 helm chart를 패키징하여 배포해 보자

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm package .
Successfully packaged chart and saved it to: /home/suha/pacman/pacman-0.1.0.tgz
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# ls
Chart.yaml  newvalues.yaml  pacman-0.1.0.tgz  templates  values.yaml

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# zcat pacman-0.1.0.tgz
pacman/Chart.yaml0000644000000000000000000000016415075407062012437 0ustar0000000000000000apiVersion: v2
appVersion: 1.1.0
description: A Helm chart for Pacman
name: pacman
type: application
version: 0.1.0
pacman/values.yaml0000644000000000000000000000034015075407062012671 0ustar0000000000000000image:     # image 절 정의
  repository: quay.io/gitops-cookbook/pacman-kikd
  tag: "1.1.0"
  pullPolicy: Always
  containerPort: 8080

replicaCount: 1
securityContext: {}     # securityContext 속성의 값을 비운다
pacman/templates/_helpers.tpl0000644000000000000000000000024615075407062015033 0ustar0000000000000000{{- define "pacman.selectorLabels" -}}   # stetement 이름을 정의
app.kubernetes.io/name: {{ .Chart.Name}} # 해당 stetement 가 하는 일을 정의
{{- end }}
pacman/templates/deployment.yaml0000644000000000000000000000267615075407062015566 0ustar0000000000000000apiVersion: 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 }}
  selector:
    matchLabels:
      {{- include "pacman.selectorLabels" . | nindent 6 }}   # pacman.selectorLabels를 호출한 결과를 6만큼 들여쓰기하여 주입
  template:
    metadata:
      labels:
        {{- include "pacman.selectorLabels" . | nindent 8 }} # pacman.selectorLabels를 호출한 결과를 8만큼 들여쓰기하여 주입
    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
pacman/templates/service.yaml0000644000000000000000000000050415075407062015032 0ustar0000000000000000apiVersion: 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:
        {{- include "pacman.selectorLabels" . | nindent 6 }}
pacman/newvalues.yaml0000644000000000000000000000002615075407062013404 0ustar0000000000000000image:
  tag: "1.2.0"
  
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm repo index .
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# cat index.yaml
apiVersion: v1
entries:
  pacman:
  - apiVersion: v2
    appVersion: 1.1.0
    created: "2025-10-20T19:27:52.033099902+09:00"
    description: A Helm chart for Pacman
    digest: 5b96f3e9aecccabc08d4d04d25fe5bdda9ed8e662ab6a5cccfda9c1035288cbd
    name: pacman
    type: application
    urls:
    - pacman-0.1.0.tgz
    version: 0.1.0
generated: "2025-10-20T19:27:52.032770711+09:00"

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# tree
.
├── Chart.yaml
├── index.yaml
├── newvalues.yaml
├── pacman-0.1.0.tgz
├── templates
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml

2 directories, 8 files

chart 저장소는 chart 및 .tgz 차트에 대한 메타데이터 정보를 담은 index.yaml 파일이 있는 HTTP 서버다.

helm repo는 Helm에서 사용되는 Helm Chart 저장소(Repository)를 관리하는 명령어.

helm chart의 서명 파일을 생성하여 무결성 검증

서명 파일을 사용하여 차트가 임의로 수정되지 않은 올바른 차트인지 확인 할 수 있다.

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# sudo apt install gnupg
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
gnupg is already the newest version (2.4.4-2ubuntu17.3).
gnupg set to manually installed.
0 upgraded, 0 newly installed, 0 to remove and 108 not upgraded.

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# gpg --full-generate-key
gpg (GnuPG) 2.4.4; Copyright (C) 2024 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

gpg: directory '/root/.gnupg' created
gpg: keybox '/root/.gnupg/pubring.kbx' created
Please select what kind of key you want:
   (1) RSA and RSA
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
   (9) ECC (sign and encrypt) *default*
  (10) ECC (sign only)
  (14) Existing key from card
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072)
gpg: signal Interrupt caught ... exiting

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# gpg --full-generate-key
gpg (GnuPG) 2.4.4; Copyright (C) 2024 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:
   (1) RSA and RSA
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
   (9) ECC (sign and encrypt) *default*
  (10) ECC (sign only)
  (14) Existing key from card
Your selection? 9
Please select which elliptic curve you want:
   (1) Curve 25519 *default*
   (4) NIST P-384
   (6) Brainpool P-256
Your selection? 1
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 0
Key does not expire at all
Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.

Real name: suha
Email address: hp980724@gmail.com
Comment: cicd-study
You selected this USER-ID:
    "suha (cicd-study) <hp980724@gmail.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: /root/.gnupg/trustdb.gpg: trustdb created
gpg: directory '/root/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/root/.gnupg/openpgp-revocs.d/EAD35F7026C2C95E96F8BE4F4044423E61017A62.rev'
public and secret key created and signed.

pub   ed25519 2025-10-20 [SC]
      EAD35F7026C2C95E96F8BE4F4044423E61017A62
uid                      suha (cicd-study) <hp980724@gmail.com>
sub   cv25519 2025-10-20 [E]

설치 및 RSA 생성

gpg list keys

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   ed25519 2025-10-20 [SC]
      EAD35F7026C2C95E96F8BE4F4044423E61017A62
uid           [ultimate] suha (cicd-study) <hp980724@gmail.com>
sub   cv25519 2025-10-20 [E]
helm package --sign --key F035A1D797A8F00DB779BBA3B3B8B968F1CBB8B9 --keyring /root/.gnupg/pubring.kbx ./pacman

error

Error: openpgp: invalid data: tag byte does not have MSB set

자체 서명은 잘됨

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# gpg --armor --detach-sign ./pacman-0.1.0.tgz
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# ls
Chart.yaml  index.yaml  newvalues.yaml  pacman-0.1.0.tgz  pacman-0.1.0.tgz.asc  templates  values.yaml

저장소에 보관한 헬름 차트를 배포

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm repo add bitnami https://charts.bitnami.com/bitnami
"bitnami" has been added to your repositories
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm repo list
NAME    URL
bitnami https://charts.bitnami.com/bitnami
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm search repo postgresql
NAME                    CHART VERSION   APP VERSION     DESCRIPTION
bitnami/postgresql      18.0.17         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...
bitnami/cloudnative-pg  1.0.11          1.26.1          CloudNativePG is an open-source tool for managi...
bitnami/supabase        5.3.6           1.24.7          DEPRECATED Supabase is an open source Firebase ...
bitnami/minio-operator  0.2.9           7.1.1           MinIO(R) Operator is a Kubernetes-native tool f...
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm search repo postgresql -o json | jq
[
  {
    "name": "bitnami/postgresql",
    "version": "18.0.17",
    "app_version": "18.0.0",
    "description": "PostgreSQL (Postgres) is an open source object-relational database known for reliability and data integrity. ACID-compliant, it supports foreign keys, joins, views, triggers and stored procedures."
  },
  {
    "name": "bitnami/postgresql-ha",
    "version": "16.3.2",
    "app_version": "17.6.0",
    "description": "This PostgreSQL cluster solution includes the PostgreSQL replication manager, an open-source tool for managing replication and failover on PostgreSQL clusters."
  },
  {
    "name": "bitnami/cloudnative-pg",
    "version": "1.0.11",
    "app_version": "1.26.1",
    "description": "CloudNativePG is an open-source tool for managing PostgreSQL databases on Kubernetes, from setup to ongoing upkeep."
  },
  {
    "name": "bitnami/supabase",
    "version": "5.3.6",
    "app_version": "1.24.7",
    "description": "DEPRECATED Supabase is an open source Firebase alternative. Provides all the necessary backend features to build your application in a scalable way. Uses PostgreSQL as datastore."
  },
  {
    "name": "bitnami/minio-operator",
    "version": "0.2.9",
    "app_version": "7.1.1",
    "description": "MinIO(R) Operator is a Kubernetes-native tool for deploying and managing high-performance, S3-compatible MinIO(R) object storage across hybrid cloud infrastructures."
  }
]
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm list
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
pacman  default         3               2025-10-20 00:49:37.795103667 +0900 KST deployed        pacman-0.1.0    1.0.0
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm install my-db \
--set postgresql.postgresqlUsername=my-default,postgresql.postgresqlPassword=postgres,postgresql.postgresqlDatabase=mydb,postgresql.persistence.enabled=false \
bitnami/postgresql
NAME: my-db
LAST DEPLOYED: Mon Oct 20 21:03:50 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: postgresql
CHART VERSION: 18.0.17
APP VERSION: 18.0.0WARNING: Since August 28th, 2025, only a limited subset of images/charts are available for free.
    Subscribe to Bitnami Secure Images to receive continued support and security updates.
    More info at https://bitnami.com and https://github.com/bitnami/containers/issues/83267

** Please be patient while the chart is being deployed **

PostgreSQL can be accessed via port 5432 on the following DNS names from within your cluster:

    my-db-postgresql.default.svc.cluster.local - Read/Write connection

To get the password for "postgres" run:

    export POSTGRES_PASSWORD=$(kubectl get secret --namespace default my-db-postgresql -o jsonpath="{.data.postgres-password}" | base64 -d)

To connect to your database run the following command:

    kubectl run my-db-postgresql-client --rm --tty -i --restart='Never' --namespace default --image registry-1.docker.io/bitnami/postgresql:latest --env="PGPASSWORD=$POSTGRES_PASSWORD" \
      --command -- psql --host my-db-postgresql -U postgres -d postgres -p 5432

    > NOTE: If you access the container using bash, make sure that you execute "/opt/bitnami/scripts/postgresql/entrypoint.sh /bin/bash" in order to avoid the error "psql: local user with ID 1001} does not exist"

To connect to your database from outside the cluster execute the following commands:

    kubectl port-forward --namespace default svc/my-db-postgresql 5432:5432 &
    PGPASSWORD="$POSTGRES_PASSWORD" psql --host 127.0.0.1 -U postgres -d postgres -p 5432

WARNING: The configured password will be ignored on new installation in case when previous PostgreSQL release was deleted through the helm command. In that case, old PVC will have an old password, and setting it through helm won't take effect. Deleting persistent volumes (PVs) will solve the issue.
WARNING: Rolling tag detected (bitnami/postgresql:latest), please note that it is strongly recommended to avoid using rolling tags in a production environment.
+info https://techdocs.broadcom.com/us/en/vmware-tanzu/application-catalog/tanzu-application-catalog/services/tac-doc/apps-tutorials-understand-rolling-tags-containers-index.html
WARNING: Rolling tag detected (bitnami/os-shell:latest), please note that it is strongly recommended to avoid using rolling tags in a production environment.
+info https://techdocs.broadcom.com/us/en/vmware-tanzu/application-catalog/tanzu-application-catalog/services/tac-doc/apps-tutorials-understand-rolling-tags-containers-index.html

WARNING: There are "resources" sections in the chart not set. Using "resourcesPreset" is not recommended for production. For production installations, please set the following values according to your workload needs:
  - primary.resources
  - readReplicas.resources
+info https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm list
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                  APP VERSION
my-db   default         1               2025-10-20 21:03:50.50731746 +0900 KST  deployed        postgresql-18.0.17     18.0.0
pacman  default         3               2025-10-20 00:49:37.795103667 +0900 KST deployed        pacman-0.1.0           1.0.0
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# kubectl get sts,pod,svc,ep,secret
NAME                                READY   AGE
statefulset.apps/my-db-postgresql   1/1     52s

NAME                          READY   STATUS    RESTARTS       AGE
pod/my-db-postgresql-0        1/1     Running   0              52s
pod/pacman-576769bb86-lq669   1/1     Running   1 (109m ago)   20h

NAME                          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/kubernetes            ClusterIP   10.96.0.1      <none>        443/TCP    4d23h
service/my-db-postgresql      ClusterIP   10.96.75.52    <none>        5432/TCP   52s
service/my-db-postgresql-hl   ClusterIP   None           <none>        5432/TCP   52s
service/pacman                ClusterIP   10.96.55.229   <none>        8080/TCP   20h

NAME                            ENDPOINTS         AGE
endpoints/kubernetes            172.18.0.2:6443   4d23h
endpoints/my-db-postgresql      10.244.1.4:5432   52s
endpoints/my-db-postgresql-hl   10.244.1.4:5432   52s
endpoints/pacman                10.244.1.2:8080   20h

NAME                                  TYPE                 DATA   AGE
secret/my-db-postgresql               Opaque               1      52s
secret/sh.helm.release.v1.my-db.v1    helm.sh/release.v1   1      52s
secret/sh.helm.release.v1.pacman.v1   helm.sh/release.v1   1      20h
secret/sh.helm.release.v1.pacman.v2   helm.sh/release.v1   1      20h
secret/sh.helm.release.v1.pacman.v3   helm.sh/release.v1   1      20h

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# helm uninstall my-db
release "my-db" uninstalled
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman# kubectl get sts,pod,svc,ep,secret
NAME                          READY   STATUS    RESTARTS       AGE
pod/pacman-576769bb86-lq669   1/1     Running   1 (110m ago)   20h

NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP    5d
service/pacman       ClusterIP   10.96.55.229   <none>        8080/TCP   20h

NAME                   ENDPOINTS         AGE
endpoints/kubernetes   172.18.0.2:6443   5d
endpoints/pacman       10.244.1.2:8080   20h

NAME                                  TYPE                 DATA   AGE
secret/sh.helm.release.v1.pacman.v1   helm.sh/release.v1   1      20h
secret/sh.helm.release.v1.pacman.v2   helm.sh/release.v1   1      20h
secret/sh.helm.release.v1.pacman.v3   helm.sh/release.v1   1      20h
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/pacman#
  • bitnami 차트 저장소에는 다양한 Helm 차트가 포함 그러나 docker.io/Bitnami는 더이상 사용되지 않는다.
  • bitnami라는 이름으로 Bitnami의 Helm 차트 저장소를 클러스터에 추가
  • bitnami 저장소에서 PostgreSQL 관련 차트를 검색
  • bitnami/postgresql 차트를 사용하여 PostgreSQL을 클러스터에 배포

다른 차트를 의존성으로 사용하는 차트를 배포 (다시 해보기)

어떤 chart가 다른 chart를 의존하는 것을 선언하는 경우 chart.yaml 파일의 dependencies 섹션을 사용한다.

mkdir music
mkdir music/templates
cd music

templates/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

templates/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

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.0.17
    repository: "https://charts.bitnami.com/bitnami"***
EOF
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/music/templates# helm search repo bitnami/postgresql --versions
NAME                    CHART VERSION   APP VERSION     DESCRIPTION
bitnami/postgresql      18.0.17         18.0.0          PostgreSQL (Postgres) is an open source object-...
bitnami/postgresql      18.0.16         18.0.0          PostgreSQL (Postgres) is an open source object-...
bitnami/postgresql      18.0.15         18.0.0          PostgreSQL (Postgres) is an open source object-...
bitnami/postgresql      18.0.14         18.0.0          PostgreSQL (Postgres) is an open source object-...
bitnami/postgresql      18.0.12         18.0.0          PostgreSQL (Postgres) is an open source object-...

최신 버전 사용

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
  postgresqlUsername: my-default
  postgresqlPassword: **postgres**
  postgresqlDatabase: **mydb**  
  secretName: music-db-postgresql
  secretKey: postgresql-password
EOF
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/music# tree
.
├── Chart.yaml
├── templates
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml

helm dependency update

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/music# helm dependency update
Hang tight while we grab the latest from your chart repositories...
...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
Pulled: registry-1.docker.io/bitnamicharts/postgresql:18.0.17
Digest: sha256:84b63af46f41ac35e3cbcf098e8cf124211c250807cfed43f7983c39c6e30b72
Deleting outdated charts
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/music# tree
.
├── Chart.lock
├── Chart.yaml
├── charts
│   └── postgresql-18.0.17.tgz
├── templates
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml

helm dependency update 명령어는 Helm 차트의 의존성(dependencies)을 자동으로 다운로드 및 갱신하는 기능

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/music# helm install music-db .
NAME: music-db
LAST DEPLOYED: Tue Oct 21 00:26:23 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/music# kubectl get sts,pod,svc,ep,secret,pv,pvc
NAME                                   READY   AGE
statefulset.apps/music-db-postgresql   1/1     3m21s

NAME                          READY   STATUS             RESTARTS       AGE
pod/music-6c45d566f4-v58fk    0/1     CrashLoopBackOff   4 (20s ago)    3m21s
pod/music-db-postgresql-0     1/1     Running            0              3m21s
pod/pacman-576769bb86-lq669   1/1     Running            1 (169m ago)   21h

NAME                             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/kubernetes               ClusterIP   10.96.0.1       <none>        443/TCP    5d
service/music                    ClusterIP   10.96.184.92    <none>        8080/TCP   3m21s
service/music-db-postgresql      ClusterIP   10.96.168.136   <none>        5432/TCP   3m21s
service/music-db-postgresql-hl   ClusterIP   None            <none>        5432/TCP   3m21s
service/pacman                   ClusterIP   10.96.55.229    <none>        8080/TCP   21h

NAME                               ENDPOINTS         AGE
endpoints/kubernetes               172.18.0.2:6443   5d
endpoints/music                                      3m21s
endpoints/music-db-postgresql      10.244.1.7:5432   3m21s
endpoints/music-db-postgresql-hl   10.244.1.7:5432   3m21s
endpoints/pacman                   10.244.1.2:8080   21h

NAME                                    TYPE                 DATA   AGE
secret/music-db-postgresql              Opaque               1      3m21s
secret/sh.helm.release.v1.music-db.v1   helm.sh/release.v1   1      3m21s
secret/sh.helm.release.v1.pacman.v1     helm.sh/release.v1   1      21h
secret/sh.helm.release.v1.pacman.v2     helm.sh/release.v1   1      21h
secret/sh.helm.release.v1.pacman.v3     helm.sh/release.v1   1      21h

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/pvc-4fe9328a-23ea-4a2f-a069-c7c762a32308   8Gi        RWO            Delete           Bound    default/data-my-db-postgresql-0      standard       <unset>                          60m
persistentvolume/pvc-cdc45d2b-45f7-49bb-a519-de539198f069   8Gi        RWO            Delete           Bound    default/data-music-db-postgresql-0   standard       <unset>                          3m18s

NAME                                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/data-music-db-postgresql-0   Bound    pvc-cdc45d2b-45f7-49bb-a519-de539198f069   8Gi        RWO            standard       <unset>                 3m21s
persistentvolumeclaim/data-my-db-postgresql-0      Bound    pvc-4fe9328a-23ea-4a2f-a069-c7c762a32308   8Gi        RWO            standard       <unset>                 60m
  • describe
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/music# kubectl get pods
NAME                      READY   STATUS                       RESTARTS        AGE
music-6c45d566f4-v44bl    0/1     CreateContainerConfigError   0               113s
music-db-postgresql-0     1/1     Running                      0               113s
pacman-576769bb86-lq669   1/1     Running                      1 (5h13m ago)   23h
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/music# kubectl describe pod music-6c45d566f4-v44bl
Name:             music-6c45d566f4-v44bl
Namespace:        default
Priority:         0
Service Account:  default
Node:             myk8s-worker/172.18.0.3
Start Time:       Tue, 21 Oct 2025 00:26:24 +0900
Labels:           app.kubernetes.io/name=music
                  pod-template-hash=6c45d566f4
Annotations:      <none>
Status:           Pending
IP:               10.244.1.22
IPs:
  IP:           10.244.1.22
Controlled By:  ReplicaSet/music-6c45d566f4
Containers:
  music:
    Container ID:
    Image:          quay.io/gitops-cookbook/music:1.0.0
    Image ID:
    Port:           8080/TCP (http)
    Host Port:      0/TCP (http)
    State:          Waiting
      Reason:       CreateContainerConfigError
    Ready:          False
    Restart Count:  0
    Environment:
      QUARKUS_DATASOURCE_JDBC_URL:  jdbc:postgresql://music-db-postgresql:5432/mydb
      QUARKUS_DATASOURCE_USERNAME:  my-default
      QUARKUS_DATASOURCE_PASSWORD:  <set to the key 'postgresql-password' in secret 'music-db-postgresql'>  Optional: false
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-7rtj6 (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True
  Initialized                 True
  Ready                       False
  ContainersReady             False
  PodScheduled                True
Volumes:
  kube-api-access-7rtj6:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    Optional:                false
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason     Age                 From               Message
  ----     ------     ----                ----               -------
  Normal   Scheduled  118s                default-scheduler  Successfully assigned default/music-6c45d566f4-v44bl to myk8s-worker
  Normal   Pulled     117s                kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 828ms (828ms including waiting). Image size: 94836428 bytes.
  Normal   Pulled     115s                kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 954ms (954ms including waiting). Image size: 94836428 bytes.
  Normal   Pulled     101s                kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 4.518s (4.518s including waiting). Image size: 94836428 bytes.
  Normal   Pulled     87s                 kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 787ms (787ms including waiting). Image size: 94836428 bytes.
  Normal   Pulled     75s                 kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 2.06s (2.06s including waiting). Image size: 94836428 bytes.
  Normal   Pulled     58s                 kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 3.694s (3.694s including waiting). Image size: 94836428 bytes.
  Normal   Pulled     43s                 kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 1.014s (1.014s including waiting). Image size: 94836428 bytes.
  Normal   Pulled     30s                 kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 1.993s (1.993s including waiting). Image size: 94836428 bytes.
  Normal   Pulled     16s                 kubelet            Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 878ms (878ms including waiting). Image size: 94836428 bytes.
  Warning  Failed     4s (x10 over 117s)  kubelet            Error: couldn't find key postgresql-password in Secret default/music-db-postgresql
  Normal   Pulling    4s (x10 over 118s)  kubelet            Pulling image "quay.io/gitops-cookbook/music:1.0.0"
  Normal   Pulled     4s                  kubelet            (combined from similar events): Successfully pulled image "quay.io/gitops-cookbook/music:1.0.0" in 736ms (736ms including waiting). Image size: 94836428 bytes.

trouble shooting 1 (password authentication failed)

  Warning  Failed     4s (x10 over 117s)  kubelet            Error: couldn't find key postgresql-password in Secret default/music-db-postgresql
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/music# kubectl get secrets
NAME                             TYPE                 DATA   AGE
music-db-postgresql              Opaque               1      4m20s
sh.helm.release.v1.music-db.v1   helm.sh/release.v1   1      4m20s
sh.helm.release.v1.pacman.v1     helm.sh/release.v1   1      23h
sh.helm.release.v1.pacman.v2     helm.sh/release.v1   1      23h
sh.helm.release.v1.pacman.v3     helm.sh/release.v1   1      23h
**kubectl edit secret music-db-postgresql**
postgresql-password: cG9zdGdyZXMK
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/music# kubectl describe secrets music-db-postgresql
Name:         music-db-postgresql
Namespace:    default
Labels:       app.kubernetes.io/instance=music-db
              app.kubernetes.io/managed-by=Helm
              app.kubernetes.io/name=postgresql
              app.kubernetes.io/version=18.0.0
              helm.sh/chart=postgresql-18.0.17
Annotations:  meta.helm.sh/release-name: music-db
              meta.helm.sh/release-namespace: default

Type:  Opaque

Data
====
postgresql-password:  9 bytes
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/music#  kubectl get secret music-db-postgresql -o jsonpath="{.data.postgresql-password}" | base64 -d
postgres
kubectl rollout restart deployment music

trouble shooting 2

2025-10-21 15:19:51,229 WARN  [io.agr.pool] (agroal-11) Datasource '<default>': FATAL: password authentication failed for user "my-default"
2025-10-21 15:19:51,263 WARN  [org.hib.eng.jdb.env.int.JdbcEnvironmentInitiator] (JPA Startup Thread: <default>) HHH000342: Could not obtain connection to query metadata: 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)

2025-10-21 15:19:51,681 WARN  [io.agr.pool] (agroal-11) Datasource '<default>': FATAL: password authentication failed for user "my-default"
2025-10-21 15:19:51,682 WARN  [org.hib.eng.jdb.spi.SqlExceptionHelper] (JPA Startup Thread: <default>) SQL Error: 0, SQLState: 28P01
2025-10-21 15:19:51,683 ERROR [org.hib.eng.jdb.spi.SqlExceptionHelper] (JPA Startup Thread: <default>) FATAL: password authentication failed for user "my-default"
2025-10-21 15:19:51,871 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)
  • 해결 못함

ConfigMap 객체가 변경될 때 deployment rolling update 가 자동으로 시작되도록 구성

  • sha256sum 템플릿 함수를 사용하여 deployment 파일에 변경 내용을 주입
  • helm은 SHA-256 해시를 계산하고 그결과를 템플릿에 포함하는 템플릿 함수 제공

폴더 구조

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/greetings# tree
.
├── Chart.yaml
├── templates
│   ├── configMap.yaml
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml

2 directories, 5 files

templates/service.yaml

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/greetings/templates# cat 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}}

templates/deployment.yaml

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/greetings/templates# cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}
  labels:
    app.kubernetes.io/name: {{ .Chart.Name }}
    {{- if .Chart.AppVersion }}
    app.kubernetes.io/versions: {{ .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  # "greetin" -> "greeting"

templates/configMap.yaml

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/greetings/templates# cat configMap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: greeting-config
data:
  greeting: Aloha

Chart.yaml

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/greetings# cat Chart.yaml
apiVersion: v1
name: greetings
description: A Helm chart for Greetings
type: application
version: 0.1.0        # 차트 버전, 차트 정의가 바뀌면 업데이트한다
appVersion: "1.0.0"   # 애플리케이션 버전

values.yaml

port-forwarding

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/greetings# helm install greetings .
NAME: greetings
LAST DEPLOYED: Tue Oct 21 22:25:26 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/greetings# kubectl port-forward service/greetings 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Handling connection for 8080

configmap 수정

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/greetings/templates# cat configMap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: greeting-config
data:
  greeting: Hola

helm upgrade

helm upgrade greetings .
Aloha Alexandra(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/greetings/templates# curl localhost:8080
  • configMap data.greeting Hola로 변경했는데 Aloha로 그대로 나옴
  • configMap object가 갱신되지만 deployment 리소스에는 변경된 부분이 없으므로 pod는 다시 시작안함

sha256sum 이용

sha256sum을 이용해서 configMap.yaml 파일콘텐츠의 SHA-256 값 계산하고 pod annotation으로 설정하면 configMap의 내용이 바뀔때마다 pod 정의도 바뀌면서 rolling update 자동으로 됨

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/versions: {{ .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 }}  # .Files.Get을 사용하여 템플릿 파일을 가져오기
    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  # "greetin" -> "greeting"
  • 기본 chart uninstall Chart.yaml 1.0.0 돌려놓기
  • ConfigMap 수정
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/greetings/templates# cat configMap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: greeting-config
data:
  greeting: Namaste
helm upgrade greetings .
  • kubectl port-forward 프로세스 다시 시작
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/greetings# curl localhost:8080
Namastee Ada(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/greetings# curl localhost:8080
Namastee Alexandra(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/greetings# curl localhost:8080
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/greetings# kubectl describe pod greetings-c85547b5-bnx8r
Name:             greetings-c85547b5-bnx8r
Namespace:        default
Priority:         0
Service Account:  default
Node:             myk8s-worker/172.18.0.2
Start Time:       Tue, 21 Oct 2025 23:13:49 +0900
Labels:           app.kubernetes.io/name=greetings
                  pod-template-hash=c85547b5
Annotations:      checksum/config: 840f32730bba68080f033d5c1cff7fc0854e0c7da35f3675292029ca76bf87ee
Status:           Running
IP:               10.244.1.13
IPs:
  IP:           10.244.1.13
Controlled By:  ReplicaSet/greetings-c85547b5
Containers:
  greetings:
    Container ID:   containerd://58acc16679414b82fc3a4bc45c1024d44a70a3a0af47501c1103f45fb43ee269
    Image:          quay.io/gitops-cookbook/greetings:1.0.0
    Image ID:       quay.io/gitops-cookbook/greetings@sha256:e76247a12671afd2540af4dd59368a5188983a92d77c52b22762d76c34eb52ab
    Port:           8080/TCP (http)
    Host Port:      0/TCP (http)
    State:          Running
      Started:      Tue, 21 Oct 2025 23:13:50 +0900
    Ready:          True
    Restart Count:  0
    Environment:
      GREETING:  <set to the key 'greeting' of config map 'greeting-config'>  Optional: false
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-8mxft (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True
  Initialized                 True
  Ready                       True
  ContainersReady             True
  PodScheduled                True
Volumes:
  kube-api-access-8mxft:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    Optional:                false
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  61s   default-scheduler  Successfully assigned default/greetings-c85547b5-bnx8r to myk8s-worker
  Normal  Pulling    61s   kubelet            Pulling image "quay.io/gitops-cookbook/greetings:1.0.0"
  Normal  Pulled     60s   kubelet            Successfully pulled image "quay.io/gitops-cookbook/greetings:1.0.0" in 807ms (807ms including waiting). Image size: 349721197 bytes.
  Normal  Created    60s   kubelet            Created container: greetings
  Normal  Started    60s   kubelet            Started container greetings
  • Annotations: checksum/config: 840f32730bba68080f033d5c1cff7fc0854e0c7da35f3675292029ca76bf87ee

Tekton

Tekton 이란 ?

쿠버네티스 기반 오픈 소스 클라우드 네이티비 CI/CD 시스템

출처 : https://nangman14.tistory.com/85

구성요소설명
Task하나의 작업 단위 (예: 빌드, 테스트, 배포). 여러 Step으로 구성됨.
쿠버네티스 관점에서 pod로 실행된다.
Step컨테이너 하나에서 실행되는 실제 명령 또는 스크립트 단위.
쿠버네티스 관점에서 컨테이너 이미지로 동작한다.
Pipeline여러 Task를 순서대로 또는 병렬로 실행하는 흐름 정의. Task들의 데이터 공유 설정 가능
쿠버네티스 관점에서 실행에 필요한 pod를 생성하고 순서대로 실행되도록 보장한
PipelineRun정의된 Pipeline을 실제로 실행하는 객체.
TaskRun특정 Task의 실행 인스턴스.
WorkspaceTask 간에 공유되는 볼륨 (예: 소스코드, 빌드 산출물).

Tekton component

Tekton Pipeline

  • Tekton의 핵심 컴포넌트로, CI/CD 워크플로우를 정의하는 역할을 합니다.
  • Pipeline, Task, Step, PipelineRun, TaskRun 등의 리소스로 구성되어 있습니다.
  • Kubernetes CRD(Custom Resource Definition)로 동작하며, YAML 파일로 정의됩니다.
  • 각 Step은 컨테이너 단위로 실행되어 재사용성과 확장성이 높습니다.

Tekton Dashboard

  • Tekton 리소스(Pipeline, Task, Run 등)를 시각적으로 관리할 수 있는 웹 UI입니다.
  • 파이프라인 실행 상태, 로그, 파라미터 등을 실시간으로 모니터링할 수 있습니다.
  • Kubernetes 클러스터 내 Tekton 리소스 상태를 한눈에 파악할 수 있습니다.
  • CLI 없이도 손쉽게 파이프라인 실행 및 관리 가능

Tekton Triggers

  • 외부 이벤트(예: GitHub push, GitLab webhook 등)를 감지하여 자동으로 파이프라인 실행을 트리거하는 컴포넌트입니다.

Tekton CLI

  • Tekton 리소스를 터미널에서 관리하기 위한 명령줄 도구입니다.

Tekton Hub

  • Tekton Task 및 Pipeline의 공식 공유 레지스트리입니다.
  • 오픈소스 커뮤니티에서 제공하는 공용 Task 템플릿을 재사용할 수 있습니다.

Tekton 설치

pipeline 설치

kubectl apply -f https://storage.googleapis.com/tekton-releases/**pipeline/latest**/release.yaml

Trigger 설치

# **Tekton Trigger 설치 :** 현재 v0.33.0
kubectl apply -f https://storage.googleapis.com/tekton-releases/**triggers**/latest/release.yaml
kubectl apply -f https://storage.googleapis.com/tekton-releases/**triggers**/latest/interceptors.yaml

Dashboard 설치

**# Tekton Dashboard 설치 :** 현재 v0.62.0
kubectl apply -f https://storage.googleapis.com/tekton-releases/**dashboard**/latest/release.yaml

(|kind-myk8s:default) root@DESKTOP-8S932ET:~# kubectl patch svc -n tekton-pipelines tekton-dashboard -p '{"spec":{"type":"NodePort","ports":[{"port":9097,"targetPort":9097,"nodePort":30000}]}}'
service/tekton-dashboard patched
(|kind-myk8s:default) root@DESKTOP-8S932ET:~# kubectl get svc,ep -n tekton-pipelines tekton-dashboard
NAME                       TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
service/tekton-dashboard   NodePort   10.96.81.64   <none>        9097:30000/TCP   31s

NAME                         ENDPOINTS          AGE
endpoints/tekton-dashboard   10.244.1.12:9097   31s
(|kind-myk8s:default) root@DESKTOP-8S932ET:~#

takton CLI 설치

# Windows WSL - Ubuntu Linux 경우
sudo apt update;sudo apt install -y gnupg
sudo mkdir -p /etc/apt/keyrings/
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/tektoncd.gpg --keyserver keyserver.ubuntu.com --recv-keys 3EFE0E0A2F2F60AA
echo "deb [signed-by=/etc/apt/keyrings/tektoncd.gpg] http://ppa.launchpad.net/tektoncd/cli/ubuntu eoan main"|sudo tee /etc/apt/sources.list.d/tektoncd-ubuntu-cli.list
sudo apt update && sudo apt install -y tektoncd-cli

**tkn version
...**
  • CLI 설치시 문제 발생

CLI

해당 페이지 참조해서 설치 진행

간단한 Task 를 하나 만들어 텍톤을 사용

출처 : https://tekton.dev/docs/concepts/concept-model/

  • task는 파이프라인을 만드는 가장 기본 단위
  • task는 pod로 실행된다.
  • task안의 각 단계들은 순차적 실행되지만, task 끼리는 병렬 실행 가능
  • task는 일련의 입력을 사용하고 일련의 출력을 생성하는 파이프라인의 일부로 실행되는 순차적인 단계들의 집합
  • task는 필요한 입력 매개변수와 리소스 및 출력 리소스를 제공하는 TaskRun이 생성될 때 실행

task 생성

# task 생성
**cat << EOF | kubectl apply -f -**
apiVersion: tekton.dev/**v1**
kind: **Task**
metadata:
  name: **hello**
spec:
  **steps:**
    - name: echo    *# step 이름*
      image: alpine *# step 수행 컨테이너 이미지*
      script: |
        #!/bin/sh
        echo "Hello World"
EOF

# 확인 
kubectl get tasks

tkn CLI로 task 시작

image.png

확인

(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# kubectl logs -l tekton.dev/task=hello -c step-echo
Hello World

(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# tkn task logs hello
Hello World

(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# tkn task describe hello
Name:        hello
Namespace:   default

🦶 Steps

 ∙ echo

🗂  Taskruns

NAME              STARTED         DURATION   STATUS
hello-run-cs4bj   2 minutes ago   21s        Succeeded

Task 내에 step 별 컨테이너 실습

# task 생성
**cat << EOF | kubectl apply -f -**
apiVersion: tekton.dev/**v1**
kind: **Task**
metadata:
  name: **two-step**
spec:
  **steps:**
    - name: **echo1**
      image: alpine
      script: |
        #!/bin/sh
        echo "Hello World 11111"
    - name: **echo2**
      image: alpine
      script: |
        #!/bin/sh
        echo "Hello World 22222"
EOF

# taskrun
**tkn task start --showlog two-step**

two-step-run-jvnvd-pod    0/2     Pending            0                0s
two-step-run-jvnvd-pod    0/2     Pending            0                0s
two-step-run-jvnvd-pod    0/2     Init:0/2           0                0s
two-step-run-jvnvd-pod    0/2     Init:0/2           0                1s
two-step-run-jvnvd-pod    0/2     Init:1/2           0                2s
two-step-run-jvnvd-pod    0/2     PodInitializing    0                3s
two-step-run-jvnvd-pod    2/2     Running            0                7s
two-step-run-jvnvd-pod    2/2     Running            0                7s
two-step-run-jvnvd-pod    0/2     Completed          0                10s
two-step-run-jvnvd-pod    0/2     Completed          0                11s

1개의 pod에 2개의 실행 컨테이너 확인 

텍톤을 사용하여 Git 저장소에 보관된 앱 코드를 컴파일하고 패키징하는 작업을 자동화

Tekton Pipelines를 사용하여 git에서 소스 코드를 복제 : Git 저장소에서 소스 코드를 복제하는 작업

|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# cat << EOF | kubectl apply -f -
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: clone-read
spec:
  description: |
    This pipeline clones a git repo, then echoes the README file to the stout.
  params:     # 매개변수 repo-url
  - name: repo-url
    type: string
    description: The git repo URL to clone from.
  workspaces: # 다운로드할 코드를 저장할 공유 볼륨인 작업 공간을 추가
  - name: shared-data
    description: |
      This workspace contains the cloned repo files, so they can be read by the
      next task.
  tasks:      # task 정의
  - name: fetch-source
    taskRef:
      name: git-clone
    workspaces:
    - name: output
      workspace: shared-data
    params:
    - name: url
      value: \$(params.repo-url)
EOF
pipeline.tekton.dev/clone-read created
(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# tkn pipeline list
NAME         AGE              LAST RUN   STARTED   DURATION   STATUS
clone-read   30 seconds ago   ---        ---       ---        ---
(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# tkn pipeline describe
Name:          clone-read
Namespace:     default
Description:   This pipeline clones a git repo, then echoes the README file to the stout.

⚓ Params

 NAME         TYPE     DESCRIPTION              DEFAULT VALUE
 ∙ repo-url   string   The git repo URL to...   ---

📂 Workspaces

 NAME            DESCRIPTION              OPTIONAL
 ∙ shared-data   This workspace cont...   false

🗒  Tasks

 NAME             TASKREF     RUNAFTER   TIMEOUT   PARAMS
 ∙ fetch-source   git-clone              ---       url: string
(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# kubectl get pipeline
NAME         AGE
clone-read   40s
(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# cat << EOF | kubectl create -f -
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  generateName: clone-read-run-
spec:
  pipelineRef:
    name: clone-read
  taskRunTemplate:
    podTemplate:
      securityContext:
        fsGroup: 65532
  workspaces: # 작업 공간 인스턴스화, PVC 생성
  - name: shared-data
    volumeClaimTemplate:
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
  params:    # 저장소 URL 매개변수 값 설정
  - name: repo-url
    value: https://github.com/tektoncd/website
EOF
pipelinerun.tekton.dev/clone-read-run-9rrtg created
(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# kubectl get pipelineruns
NAME                   SUCCEEDED   REASON           STARTTIME   COMPLETIONTIME
clone-read-run-9rrtg   False       CouldntGetTask   7s          7s
(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# tkn pipelinerun list
NAME                   STARTED          DURATION   STATUS
clone-read-run-9rrtg   13 seconds ago   0s         Failed(CouldntGetTask)
(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# tkn hub install task git-clone
WARN: This version has been deprecated
Task git-clone(0.9) installed in default namespace
(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# kubectl get tasks
NAME        AGE
git-clone   7s
hello       91m
two-step    15m
(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# cat << EOF | kubectl create -f -
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  generateName: clone-read-run-
spec:
  pipelineRef:
    name: clone-read
  taskRunTemplate:
    podTemplate:
      securityContext:
        fsGroup: 65532
  workspaces:
  - name: shared-data
    volumeClaimTemplate:
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
  params:
  - name: repo-url
    value: https://github.com/tektoncd/website
EOF
pipelinerun.tekton.dev/clone-read-run-pkb66 created
(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# tkn pipelinerun list
NAME                   STARTED          DURATION   STATUS
clone-read-run-pkb66   7 seconds ago    ---        Running
clone-read-run-9rrtg   57 seconds ago   0s         Failed(CouldntGetTask)
(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# tkn pipelinerun logs
? Select pipelinerun: clone-read-run-pkb66 started 13 seconds ago
Pipeline still running ...
[fetch-source : clone] + '[' false '=' true ]
[fetch-source : clone] + '[' false '=' true ]
[fetch-source : clone] + '[' false '=' true ]
[fetch-source : clone] + CHECKOUT_DIR=/workspace/output/
[fetch-source : clone] + '[' true '=' true ]
[fetch-source : clone] + cleandir
[fetch-source : clone] + '[' -d /workspace/output/ ]
[fetch-source : clone] + rm -rf '/workspace/output//*'
[fetch-source : clone] + rm -rf '/workspace/output//.[!.]*'
[fetch-source : clone] + rm -rf '/workspace/output//..?*'
[fetch-source : clone] + test -z
[fetch-source : clone] + test -z
[fetch-source : clone] + test -z
[fetch-source : clone] + git config --global --add safe.directory /workspace/output
[fetch-source : clone] + /ko-app/git-init '-url=https://github.com/tektoncd/website' '-revision=' '-refspec=' '-path=/workspace/output/' '-sslVerify=true' '-submodules=true' '-depth=1' '-sparseCheckoutDirectories='
[fetch-source : clone] {"level":"info","ts":1761232233.046652,"caller":"git/git.go:176","msg":"Successfully cloned https://github.com/tektoncd/website @ e6d8959b05b8bbd4aa798b28153b25c0f8766dc7 (grafted, HEAD) in path /workspace/output/"}
[fetch-source : clone] {"level":"info","ts":1761232233.0987167,"caller":"git/git.go:215","msg":"Successfully initialized and updated submodules in path /workspace/output/"}
[fetch-source : clone] + cd /workspace/output/
[fetch-source : clone] + git rev-parse HEAD
[fetch-source : clone] + RESULT_SHA=e6d8959b05b8bbd4aa798b28153b25c0f8766dc7
[fetch-source : clone] + EXIT_CODE=0
[fetch-source : clone] + '[' 0 '!=' 0 ]
[fetch-source : clone] + git log -1 '--pretty=%ct'
[fetch-source : clone] + RESULT_COMMITTER_DATE=1760686100
[fetch-source : clone] + printf '%s' 1760686100
[fetch-source : clone] + printf '%s' e6d8959b05b8bbd4aa798b28153b25c0f8766dc7
[fetch-source : clone] + printf '%s' https://github.com/tektoncd/website

(|kind-myk8s:default) root@DESKTOP-8S932ET:/usr/bin# kubectl get pod,pv,pvc
NAME                                        READY   STATUS             RESTARTS       AGE
pod/clone-read-run-pkb66-fetch-source-pod   0/1     Completed          0              37s
pod/music-7c495495bf-qfdpn                  0/1     CrashLoopBackOff   45 (38s ago)   2d
pod/music-db-postgresql-0                   0/1     CrashLoopBackOff   37 (83s ago)   2d
pod/pacman-576769bb86-lq669                 1/1     Running            6 (115m ago)   3d23h
pod/two-step-run-jvnvd-pod                  0/2     Completed          0              16m

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/pvc-4fe9328a-23ea-4a2f-a069-c7c762a32308   8Gi        RWO            Delete           Bound    default/data-my-db-postgresql-0      standard       <unset>                          3d3h
persistentvolume/pvc-74d9ecdf-a900-4748-ae2b-39032c0714e7   8Gi        RWO            Delete           Bound    default/data-music-postgresql-0      standard       <unset>                          2d23h
persistentvolume/pvc-cdc45d2b-45f7-49bb-a519-de539198f069   8Gi        RWO            Delete           Bound    default/data-music-db-postgresql-0   standard       <unset>                          3d2h
persistentvolume/pvc-d831c42c-392a-4bf6-beef-9442947f2c1e   1Gi        RWO            Delete           Bound    default/pvc-a4c5537716               standard       <unset>                          32s

NAME                             

비공개 Git 저장소의 앱을 컴파일하고 패키징하는 과정을 텍톤으로 자동화

echo "# my-sample-app" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/hp0724/my-sample-app.git
git push -u origin main

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha# SSHKH=$(ssh-keyscan github.com | grep ecdsa-sha2-nistp256 | base64 -w0)
# github.com:22 SSH-2.0-85ba476
# github.com:22 SSH-2.0-85ba476
# github.com:22 SSH-2.0-85ba476
# github.com:22 SSH-2.0-85ba476
# github.com:22 SSH-2.0-85ba476
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha# kubectl get secrets
NAME                             TYPE                 DATA   AGE
git-credentials                  Opaque               0      15h
music-db-postgresql              Opaque               1      3d10h
sh.helm.release.v1.music-db.v1   helm.sh/release.v1   1      3d10h
sh.helm.release.v1.pacman.v1     helm.sh/release.v1   1      5d10h
sh.helm.release.v1.pacman.v2     helm.sh/release.v1   1      5d10h
sh.helm.release.v1.pacman.v3     helm.sh/release.v1   1      5d10h
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha# cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: git-credentials
data:
  id_rsa: $SSHPK
  known_hosts: $SSHKH
EOF
secret/git-credentials configured
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha# cat << EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: build-bot
secrets:
  - name: git-credentials
EOF
serviceaccount/build-bot unchanged
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha# cat << EOF | kubectl apply -f -
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: my-clone-read
spec:
  description: |
    This pipeline clones a git repo, then echoes the README file to the stout.
  params:     # 매개변수 repo-url
  - name: repo-url
    type: string
    description: The git repo URL to clone from.
  workspaces: # 다운로드할 코드를 저장할 공유 볼륨인 작업 공간을 추가
  - name: shared-data
    description: |
      This workspace contains the cloned repo files, so they can be read by the
      next task.
  - name: git-credentials
    description: My ssh credentials
  tasks:      # task 정의
  - name: fetch-source
    taskRef:
      name: git-clone
    workspaces:
    - name: output
      workspace: shared-data
    - name: ssh-directory
      workspace: git-credentials
    params:
    - name: url
EOF   workspace: shared-data"]sk
pipeline.tekton.dev/my-clone-read configured
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha# tkn pipeline list
NAME            AGE            LAST RUN               STARTED        DURATION   STATUS
clone-read      1 day ago      ---                    ---            ---        ---
my-clone-read   15 hours ago   clone-read-run-cq5jq   15 hours ago   10s        Failed
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha#
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha#
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha#
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha# tkn pipeline describe
? Select pipeline: clone-read
Name:          clone-read
Namespace:     default
Description:   This pipeline clones a git repo, then echoes the README file to the stout.

⚓ Params

 NAME         TYPE     DESCRIPTION              DEFAULT VALUE
 ∙ repo-url   string   The git repo URL to...   ---

📂 Workspaces

 NAME            DESCRIPTION              OPTIONAL
 ∙ shared-data   This workspace cont...   false

🗒  Tasks

 NAME             TASKREF     RUNAFTER   TIMEOUT   PARAMS
 ∙ fetch-source   git-clone              ---       url: string
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha# kubectl get pipeline
NAME            AGE
clone-read      34h
my-clone-read   15h
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha# ssh -i ~/.ssh/id_ed25519 -T git@github.com
Hi hp0724! You've successfully authenticated, but GitHub does not provide shell access.
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha# ls
chapters             get-docker.sh  hello.sh  music          node-js-sample-app  pacman-0.1.0.tgz
docker-compose.yaml  greetings      kube-ps1  my-sample-app  pacman              tkn
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha# cd my-sample-app/
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# ls
app.js  readme.md
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# git branch
* main
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# SSHPK=$(cat ~/.ssh/id_ed25519 | base64 -w0)
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# ssh-keygen -t ed25519 -C "^C
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# ls -l  ~/.ssh | grep ed25519
-rw------- 1 root root 411 Oct 24 19:37 id_ed25519
-rw-r--r-- 1 root root 100 Oct 24 19:37 id_ed25519.pub
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# eval "$(ssh-agent -s)"
Agent pid 8721
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# ssh-add ~/.ssh/id_ed25519
Identity added: /root/.ssh/id_ed25519 (hp980724@gmail.com)
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# ssh -i ~/.ssh/id_ed25519 -T git@github.com
Hi hp0724! You've successfully authenticated, but GitHub does not provide shell access.
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# SSHPK=$(cat ~/.ssh/id_ed25519 | base64 -w0)
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app#
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# SSHPK=$(cat ~/.ssh/id_ed25519 | base64 -w0)
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# cat ~/.ssh/known_hosts | grep github
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# SSHKH=$(ssh-keyscan github.com | grep ecdsa-sha2-nistp256 | base64 -w0)
# github.com:22 SSH-2.0-85ba476
# github.com:22 SSH-2.0-85ba476
# github.com:22 SSH-2.0-85ba476
# github.com:22 SSH-2.0-85ba476
# github.com:22 SSH-2.0-85ba476
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# echo $SSHKH
Z2l0aHViLmNvbSBlY2RzYS1zaGEyLW5pc3RwMjU2IEFBQUFFMlZqWkhOaExYTm9ZVEl0Ym1semRIQXlOVFlBQUFBSWJtbHpkSEF5TlRZQUFBQkJCRW1LU0VOalFFZXpPbXhrWk15N29wS2d3RkI5bmt0NVlScllNak51RzVOODd1UmdnNkNMcmJvNXdBZFQveTZ2MG1LVjBVMncwV1oyWUIvKytUcG9ja2c9Cg==
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# kubectl get sa
NAME                  SECRETS   AGE
build-bot             1         15h
default               0         9d
music-db-postgresql   0         3d11h
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# cat << EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: build-bot
secrets:
  - name: git-credentials
EOF
serviceaccount/build-bot unchanged
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# kubectl get sa
NAME                  SECRETS   AGE
build-bot             1         15h
default               0         9d
music-db-postgresql   0         3d11h
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# cat << EOF | kubectl apply -f -
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: my-clone-read
spec:
  description: |
    This pipeline clones a git repo, then echoes the README file to the stout.
  params:     # 매개변수 repo-url
  - name: repo-url
    type: string
    description: The git repo URL to clone from.
  workspaces: # 다운로드할 코드를 저장할 공유 볼륨인 작업 공간을 추가
  - name: shared-data
    description: |
      This workspace contains the cloned repo files, so they can be read by the
      next task.
  - name: git-credentials
    description: My ssh credentials
  tasks:      # task 정의
  - name: fetch-source
    taskRef:
      name: git-clone
    workspaces:
    - name: output
      workspace: shared-data
    - name: ssh-directory
      workspace: git-credentials
    params:
    - name: url
EOF   workspace: shared-data"]sk
pipeline.tekton.dev/my-clone-read configured
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# cat << EOF | kubectl apply -f -
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: show-readme
spec:
  description: Read and display README file.
  workspaces:
  - name: source
  steps:
  - name: read
    image: alpine:latest
    script: |
      #!/usr/bin/env sh
      cat \$(workspaces.source.path)/readme.md
EOF
task.tekton.dev/show-readme configured
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# cat << EOF | kubectl create -f -
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  generateName: clone-read-run-
spec:
  pipelineRef:
    name: my-clone-read
  taskRunTemplate:
    serviceAccountName: build-bot
    podTemplate:
      securityContext:
        fsGroup: 65532
  workspaces:
  - name: shared-data
    volumeClaimTemplate:
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
  - name: git-credentials
    secret:
      secretName: git-credentials
  params:
  - name: repo-url
    value: git@github.com:hp0724/my-sample-app.git # 제가 사용하는 것 or 자신의 private repo 지정
EOF
pipelinerun.tekton.dev/clone-read-run-fljlt created
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# kubectl get pod,pv,pvc
NAME                                        READY   STATUS              RESTARTS         AGE
pod/affinity-assistant-e3ce80ce97-0         0/1     ContainerCreating   0                5s
pod/clone-read-run-cq5jq-fetch-source-pod   0/1     Error               0                15h
pod/clone-read-run-fljlt-fetch-source-pod   0/1     Init:0/2            0                5s
pod/clone-read-run-kpcd6-fetch-source-pod   0/1     Error               0                15h
pod/music-7c495495bf-qfdpn                  0/1     CrashLoopBackOff    68 (2m11s ago)   3d11h
pod/music-db-postgresql-0                   0/1     CrashLoopBackOff    60 (2m2s ago)    3d11h
pod/pacman-576769bb86-lq669                 1/1     Running             8 (19m ago)      5d10h
pod/two-step-run-jvnvd-pod                  0/2     Completed           0                35h

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/pvc-2129fdec-ebfe-48fc-a167-f32e59db49f2   1Gi        RWO            Delete           Bound    default/pvc-77e7882074               standard       <unset>                          15h
persistentvolume/pvc-4fe9328a-23ea-4a2f-a069-c7c762a32308   8Gi        RWO            Delete           Bound    default/data-my-db-postgresql-0      standard       <unset>                          4d14h
persistentvolume/pvc-74d9ecdf-a900-4748-ae2b-39032c0714e7   8Gi        RWO            Delete           Bound    default/data-music-postgresql-0      standard       <unset>                          4d10h
persistentvolume/pvc-7934fd60-0a97-4fd9-964a-9dd2226bf102   1Gi        RWO            Delete           Bound    default/pvc-3d83f4ae8a               standard       <unset>                          2s
persistentvolume/pvc-cdc45d2b-45f7-49bb-a519-de539198f069   8Gi        RWO            Delete           Bound    default/data-music-db-postgresql-0   standard       <unset>                          4d13h
persistentvolume/pvc-e3763428-6c8b-4534-ad6e-7dd5b6133f74   1Gi        RWO            Delete           Bound    default/pvc-c89fc044ac               standard       <unset>                          15h

NAME                                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/data-music-db-postgresql-0   Bound    pvc-cdc45d2b-45f7-49bb-a519-de539198f069   8Gi        RWO            standard       <unset>                 4d13h
persistentvolumeclaim/data-music-postgresql-0      Bound    pvc-74d9ecdf-a900-4748-ae2b-39032c0714e7   8Gi        RWO            standard       <unset>                 4d10h
persistentvolumeclaim/data-my-db-postgresql-0      Bound    pvc-4fe9328a-23ea-4a2f-a069-c7c762a32308   8Gi        RWO            standard       <unset>                 4d14h
persistentvolumeclaim/pvc-3d83f4ae8a               Bound    pvc-7934fd60-0a97-4fd9-964a-9dd2226bf102   1Gi        RWO            standard       <unset>                 5s
persistentvolumeclaim/pvc-77e7882074               Bound    pvc-2129fdec-ebfe-48fc-a167-f32e59db49f2   1Gi        RWO            standard       <unset>                 15h
persistentvolumeclaim/pvc-c89fc044ac               Bound    pvc-e3763428-6c8b-4534-ad6e-7dd5b6133f74   1Gi        RWO            standard       <unset>                 15h
(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/my-sample-app# kubectl describe pod | grep 'Service Account'
Service Account:  default
Service Account:  build-bot
Service Account:  build-bot
Service Account:  build-bot
Service Account:  build-bot
Service Account:  default
Service Account:  music-db-postgresql
Service Account:  default
Service Account:  default

텍톤 Task를 사용하여 앱 컴파일, 패키징, 컨테이너 이미지 생성까지의 과정을 처리

Tekton을 사용하여 이미지를 빌드하고 푸시

  • Git 저장소에서 소스 코드를 복제하는 작업
  • 복제된 코드를 사용하여 Docker 이미지를 빌드 kaniko 하고 레지스트리에 푸시하는 두 번째 작업
# task 설치 : https://hub.tekton.dev/tekton/task/kaniko
tkn hub install task **kaniko**
kubectl get tasks
kubectl get tasks kaniko -o yaml | k neat | yq

# Docker 자격 증명으로 Secret을 적용

-------------------------------------------------
(|kind-myk8s:default) root@DESKTOP-8S932ET:~/.docker# cat ~/.docker/config.json
{
        "auths": {
                "https://index.docker.io/v1/": {
                        "username":"hp980724",
                        "password":"password",
                        "email":"email",
                        "auth": "auth"
                },

#
cat ~/.docker/config.json | base64 -w0
DSH=$(cat ~/.docker/config.json | base64 -w0)
echo $DSH
-------------------------------------------------

**cat << EOF | kubectl apply -f -**
apiVersion: v1
kind: Secret
metadata:
  name: docker-credentials
data:
  config.json: $DSH
EOF

# ServiceAccount 생성 및 Secert 연결
kubectl create sa **build-sa**
kubectl patch sa build-sa -p '{"secrets": [{"name": "docker-credentials"}]}'
kubectl get sa build-sa -o yaml | kubectl neat | yq

# 파이프라인 파일 작성
**cat << EOF | kubectl apply -f -**
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: **clone-build-push**
spec:
  description: | 
    This pipeline clones a git repo, builds a Docker image with Kaniko and pushes it to a registry
  **params**:
  - name: repo-url
    type: string
  - name: image-reference
    type: string
  **workspaces**:
  - name: shared-data
  - name: docker-credentials
  **tasks**:
  **- name: fetch-source**
    taskRef:
      name: **git-clone**
    **workspaces**:
    - name: output
      workspace: shared-data
    **params**:
    - name: url
      value: \$(params.repo-url)
  **- name: build-push**
    runAfter: ["fetch-source"]
    taskRef:
      name: **kaniko**
    workspaces:
    - name: source
      workspace: shared-data
    - name: dockerconfig
      workspace: docker-credentials
    params:
    - name: IMAGE
      value: \$(params.image-reference)
EOF

# 파이프라인 실행
**cat << EOF | kubectl create -f -**
apiVersion: tekton.dev/v1
kind: **PipelineRun**
metadata:
  generateName: clone-build-push-run-
spec:
  **pipelineRef:
    name: clone-build-push**
  taskRunTemplate:
    serviceAccountName: **build-sa**
    podTemplate:
      securityContext:
        fsGroup: 65532
  **workspaces:**
  - name: shared-data
    volumeClaimTemplate:
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
  - name: docker-credentials
    secret:
      secretName: docker-credentials
  params:
  - name: repo-url
    value: https://github.com/gasida/docsy-example.git  *# 유형욱님이 제보해주신 대로 Dockerfile 에 USER root 추가해두었습니다*
  - name: image-reference
    value: *docker.io/hp980724/docsy:1.0.0    # 각자 자신의 저장소*
EOF

Speeding Up Container Image Builds in Tekton Pipelines 내용 기술(캐싱 등) 실습 및 정리

빌드가 항상 동일한 서버에서 실행되고 레이어를 로컬에 저장할 수 있는 환경에서는 캐시된 레이어를 사용하는 것이 비교적 쉽다

빌드가 Kubernetes의 비영구 컨테이너에서 실행되는 환경에서는 캐시된 레이어를 사용하는 것이 어렵다.

Kaniko 캐싱 기능을 사용하여 Tekton Pipeline에서 빌드 속도를 높여보자

Kaniko를 사용하여 Tekton 파이프라인에서 캐싱을 사용하여 이미지 빌드 속도 높이기

  • Kaniko = Dockerfile에서 컨테이너 이미지를 빌드하는 데 사용할 수 있는 오픈 소스 도구
  • Kubernetes 클러스터와 같이 Docker 데몬을 사용할 수 없거나 실행할 수 없는 환경에서 이미지를 빌드하는 데 이상적

github 주소

https://github.com/tektoncd/website

takton-assets

git-clone.yaml

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: git-clone
  labels:
    app.kubernetes.io/version: "0.5"
  annotations:
    tekton.dev/pipelines.minVersion: "0.21.0"
    tekton.dev/categories: Git
    tekton.dev/tags: git
    tekton.dev/displayName: "git clone"
    tekton.dev/platforms: "linux/amd64,linux/s390x,linux/ppc64le,linux/arm64"
spec:
  description: >-
    These Tasks are Git tasks to work with repositories used by other tasks
    in your Pipeline.

    The git-clone Task will clone a repo from the provided url into the
    output Workspace. By default the repo will be cloned into the root of
    your Workspace. You can clone into a subdirectory by setting this Task's
    subdirectory param. This Task also supports sparse checkouts. To perform
    a sparse checkout, pass a list of comma separated directory patterns to
    this Task's sparseCheckoutDirectories param.
  workspaces:
    - name: source
      description: The git repo will be cloned onto the volume backing this Workspace.
  params:
    - name: url
      description: Repository URL to clone from.
      type: string
  results:
    - name: commit
      description: The short commit SHA that was fetched by this Task.
    - name: url
      description: The precise URL that was fetched by this Task.
  steps:
    - name: clone
      image: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.21.0"
      env:
      - name: PARAM_URL
        value: $(params.url)
      - name: WORKSPACE_OUTPUT_PATH
        value: $(workspaces.source.path)
      script: |
        #!/usr/bin/env sh
        set -eu

        set -x

        CHECKOUT_DIR="${WORKSPACE_OUTPUT_PATH}"

        cleandir() {
          # Delete any existing contents of the repo directory if it exists.
          #
          # We don't just "rm -rf ${CHECKOUT_DIR}" because ${CHECKOUT_DIR} might be "/"
          # or the root of a mounted volume.
          if [ -d "${CHECKOUT_DIR}" ] ; then
            # Delete non-hidden files and directories
            rm -rf "${CHECKOUT_DIR:?}"/*
            # Delete files and directories starting with . but excluding ..
            rm -rf "${CHECKOUT_DIR}"/.[!.]*
            # Delete files and directories starting with .. plus any other character
            rm -rf "${CHECKOUT_DIR}"/..?*
          fi
        }

        cleandir

        /ko-app/git-init \
          -url="${PARAM_URL}" \
          -path="${CHECKOUT_DIR}" \
          -submodules="true" \
          -depth="1"
        cd "${CHECKOUT_DIR}"
        RESULT_SHA="$(git rev-parse --short HEAD)"
        EXIT_CODE="$?"
        if [ "${EXIT_CODE}" != 0 ] ; then
          exit "${EXIT_CODE}"
        fi
        printf "%s" "${RESULT_SHA}" > "$(results.commit.path)"
        printf "%s" "${PARAM_URL}" > "$(results.url.path)"

image-build.yaml

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: image-build
  labels:
    app.kubernetes.io/version: "0.6"
  annotations:
    tekton.dev/pipelines.minVersion: "0.17.0"
    tekton.dev/categories: Image Build
    tekton.dev/tags: image-build
    tekton.dev/displayName: "Build and upload container image using Kaniko"
    tekton.dev/platforms: "linux/amd64"
spec:
  description: >-
    This Task builds a simple Dockerfile with kaniko and pushes to a registry.
  params:
    - name: IMAGE
      description: Name (reference) of the image to build.
    - name: DOCKERFILE
      description: Path to the Dockerfile to build.
      default: $(workspaces.source.path)/sample-app/Dockerfile
    - name: CONTEXT
      description: The build context used by Kaniko.
      default: $(workspaces.source.path)/sample-app/
    - name: commit
      description: Short Commit SHA.
  workspaces:
    - name: source
      description: Holds the context and Dockerfile
  results:
    - name: IMAGE_DIGEST
      description: Digest of the image just built.
    - name: IMAGE_URL
      description: URL of the image just built.
  steps:
    - name: build-and-push
      workingDir: $(workspaces.source.path)/sample-app/
      image: gcr.io/kaniko-project/executor:latest
      args:
        - --dockerfile=$(params.DOCKERFILE)
        - --context=$(params.CONTEXT) # The user does not need to care the workspace and the source.
        - --destination=$(params.IMAGE):$(params.commit)
        - --digest-file=$(results.IMAGE_DIGEST.path)
      # kaniko assumes it is running as root, which means this example fails on platforms
      # that default to run containers as random uid (like OpenShift). Adding this securityContext
      # makes it explicit that it needs to run as root.
      securityContext:
        runAsUser: 0
    - name: write-url
      image: docker.io/library/bash:5.1.4@sha256:b208215a4655538be652b2769d82e576bc4d0a2bb132144c060efc5be8c3f5d6
      script: |
        set -e
        image="$(params.IMAGE):$(params.commit)"
        echo -n "${image}" | tee "$(results.IMAGE_URL.path)"

pipeline.yaml

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: pipeline-clone-and-build
spec:
  params:
    - name: git-url
    - name: image-name
  workspaces:
    - name: source
  tasks:
    - name: git-clone
      taskRef:
        name: git-clone
      params:
        - name: url
          value: $(params.git-url)
      workspaces:
        - name: source
          workspace: source
    - name: image-build
      taskRef:
        name: image-build
      runAfter:
        - git-clone
      params:
        - name: IMAGE
          value: $(params.image-name)
        - name: commit
          value: "$(tasks.git-clone.results.commit)"
      workspaces:
        - name: source
          workspace: source

source-pvc

(|kind-myk8s:default) root@DESKTOP-8S932ET:/home/suha/website/content/en/blog/2023/speeding-up-container-image-builds-in-tekton-pipelines/tekton-speed-builds/tekton-assets# cat source-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: source-pvc
spec:
  resources:
    requests:
      storage: 10Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce(

권한 지정

https://tekton.dev/docs/how-to-guides/kaniko-build-push/#container-registry-authentication

Protect Signing Secrets for Tekton Chains using Confidential Computing 안전한 환경 빌드 실습 및 정리

takton chain

  1. 서명 비밀번호 생성(예: cosign 키 쌍 - cosign.pub, cosign.key, cosign.password)
  2. 서명 비밀을 K8s 비밀로 저장합니다. tekton-chains ns의 signing-secrets라는 이름이 지정되었습니다.
  3. pipelineRun 객체를 생성합니다(파이프라인을 실행합니다)
  4. 체인 컨트롤러는 파이프라인 실행 완료를 감시하고 스냅샷을 생성합니다.
  5. 서명 비밀의 비공개 부분(예: cosign.key)을 사용하여 스냅샷에 서명합니다.
  6. 소비자의 경우 서명 비밀의 공개 부분(예: cosign.pub)을 사용하여 서명을 확인하여 출처를 확인합니다.

기밀 컴퓨팅

  • 노출되기 쉬운 키(예: 서명용 개인키)가 운영체제나 클러스터 관리자에게 노출되지 않도록 하는 기술
  • 사용 중인 데이터를 보호하여 저장 데이터, 전송 중 데이터, 사용 중인 데이터라는 세 가지 데이터 보안의 핵심을 완성

TEE (Trusted Execution Environment, TEE)

TEE는 운영체제(OS), 하이퍼바이저, 관리자(root)조차도 접근할 수 없는 하드웨어 수준의 보안 구역(enclave)을 제공합니다.

특징설명
격리(Isolation)일반 프로세스나 커널로부터 메모리와 실행 영역이 완전히 분리되어 있습니다.
무결성(Integrity)내부 코드나 데이터가 외부에서 변조되지 않았음을 보장합니다.
기밀성(Confidentiality)외부(운영체제, 관리자, 클라우드 제공자)에서 내부 데이터를 읽을 수 없습니다.
원격 증명(Remote Attestation)외부 시스템이 “이 코드가 신뢰할 수 있는 환경(TEE)에서 실행 중”임을 암호학적으로 검증할 수 있습니다.

k8s에서 기밀 컴퓨팅 활용

기밀 클러스터 : 쿠버네티스 클러스터 전체가 기밀 환경에서 실행됩니다. 클러스터 노드는 기밀 VM이며, 이러한 클러스터 노드에 배포된 모든 워크로드는 기밀성의 이점을 누립니다.

기밀 컨테이너 : 쿠버네티스 포드는 기밀 환경 내에서 실행됩니다. 이 접근 방식은 더욱 세분화되어 선택한 워크로드에 대해 기밀성을 유지할 수 있습니다.

0개의 댓글