CI/CD 스터디 2주차 내용인 Helm과 Tekton에 대해서 다뤄 보겠습니다.

사전 준비

이번에도 실습을 위해서 kind를 사용하여 진행합니다. 사전에 kind를 통해 클러스터를 생성해줍니다.

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

helm이란 무엇이고 왜 사용하는가?

  • helm은 쿠버네티스에서 배포 정의서 중 일 부 값을 변경하거나 환경별로 값을 다르게 할 때 매번 전체를 수정하는 것이 아니라 특정 템플릿을 통해 값을 변경하여 효율적인 배포환경을 구성할 수 있다.
  • helm은 템플릿 기반 솔루션이며 패키지 관리자 처럼 동작하여 버전 관리, 공유, 배포 가능 이티팩트를 생성한다.
  • helm과 유사한 개념으로 kustomize가 있다. 둘 다 비슷하나 helm은 템플릿 기반으로 values에 의해 동적으로 값을 만들어 낸다는 점에서 완전히 gitops 적이지 못한다고 할 수 있다.

Helm 프로젝트 만들기

간단한 helm 프로젝트를 만드는 실습을 해본다. service, deployment를 helm chart로 만들어 관리해본다.

# 1. 차트 디렉터리 생성
mkdir pacman
mkdir pacman/templates
cd pacman

# 2. Chart.yaml 작성
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

# 3. templates/deployment.yaml 작성
# 템플릿 문법(Go 템플릿)을 활용해 Chart.Name, Chart.AppVersion, Values 등을 참조하고 있습니다.
# toYaml 함수, nindent 함수 등을 사용하여 YAML 객체 들여쓰기까지 처리하는 예시입니다.
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

# 4. 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

5. values.yaml 작성 (기본값 설정)
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

securityContext란?

  • securityContext 는 쿠버네티스에서 Pod 또는 Container 레벨에서 적용할 수 있는 보안 설정들의 집합
  • 이 설정을 통해 이 컨테이너는 어떤 사용자 ID로 실행돼야 하는가, 파일시스템 권한은?, 루트 권한을 가질 수 있는가? 등 실행 시점 권한 및 격리 정책을 정의
  • securityContext는 애플리케이션이 클러스터, 노드, 파일시스템 등에 대해 가지는 권한을 최소한으로 유지하면서 보안을 강화하는 수단

디렉터리 구조확인

.
├── Chart.yaml
├── templates
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml

.Chart, .Values 통해 값이 주입된다.

helm 차트를 렌더링 하기

배포하기 전에 사전에 렌더링을 진행 할 수 있다. 실제 Chart.yaml, values.yaml 파일을 통해 주입된 값을 가지고 완상된 템플릿을 추출해준다.

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
              
# values.yaml 을 수정하지 않고 cli 단에서 바로 수정할 수도 있다.
# set 이 우선순위가 더 높기에 set으로 지정하면 values를 덮어쓴다.

helm template --set replicaCount=3 .
...
spec:
  replicas: 3     # replicaCount 속성을 넣을 자리 placeholder

helm chart 배포 하기

# 설치
helm install pacman .
# 확인
helm list
---
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
pacman  default         1               2025-10-20 11:22:13.929231 +0900 KST    deployed        pacman-0.1.0    1.0.0      

# 배포된 리소스 확인
kubectl get deploy,pod,svc,ep
kubectl get pod -o yaml *| kubectl neat | yq*  # kubectl krew install neat 
kubectl get pod -o json | grep securityContext -A1

# 기록조회
helm history pacman
---
REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION     
1               Mon Oct 20 11:22:13 2025        deployed        pacman-0.1.0    1.0.0           Install complete

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

# secret 값 조회
k describe secrets sh.helm.release.v1.pacman.v1
---
Name:         sh.helm.release.v1.pacman.v1
Namespace:    default
Labels:       modifiedAt=1760926934
              name=pacman
              owner=helm
              status=deployed
              version=1
Annotations:  <none>

Type:  helm.sh/release.v1

Data
====
release:  2760 bytes

업그레이드 및 메타 데이터 조회

# replica 2로 업그레이드, --reuse-values: 기존 values 재 사용, --set: replicaCount 값 덮어쓰기
helm upgrade pacman --reuse-values --set replicaCount=2 .
kubectl get pod
NAME                      READY   STATUS             RESTARTS   AGE
pacman-576769bb86-mn9tt   0/1     ImagePullBackOff   0          3m39s
pacman-576769bb86-mwf2q   0/1     ErrImagePull       0          3s

# 메타데이터 변경사항 조회
kubectl get secret
NAME                           TYPE                 DATA   AGE
sh.helm.release.v1.pacman.v1   helm.sh/release.v1   1      11m
sh.helm.release.v1.pacman.v2   helm.sh/release.v1   1      20s

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

# 삭제 후 secret 확인
helm uninstall pacman
kubectl get secret

템플릿 재사용

  • helm의 목적은 템플릿을 재사용 하여 유지보수를 높이는 것이다. 일회성으로 매번 생성한다면 생산성이 크게 떨어지기 때문이다.
  • 템플릿 함수를 정의하는 파일명으로는 _helpers.tpl 을 사용하는 것이 일반적이지만, 사실 _로 시작하기만 하면 된다.
  • _ 로 시작하는 팔일은 쿠버네티스 매니패스트 파일로 취급받지 않는다.
  • _helpers.tpl 파일이란?
    • Helm 차트의 templates/ 디렉터리 내에서 파일명이 언더바로 시작하는 파일들은 일반 매니페스트로 바로 렌더링되지 않고, 헬름 템플릿 엔진 내부에서 헬퍼 또는 부분 템플릿 용도로 사용
    • 사용하는 이유
      • 코드 중복제거: 리소스 생성로직이 반복된다면 _helpers.tpl에 정의해두고 각 템플릿에서는 호출만해서 사용
      • 템플릿 유지보수성 향상: _helpers.tpl 한 곳만 변경하면 모든 곳 동일하게 변경
      • 복잡한 계산/포맷 캡슐화
      • 템플릿 가독성 향상
# 시나리오
# deployment.yaml, service.yaml 에 selector 필드가 동일
## deployment.yaml
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name}}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name}}

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

## 이 필드를 업데이트하려면(selector 필드에 새 레이블 추가 등) 3곳을 똑같이 업데이트 해야함(유지보수성 떨어짐)

# 템플릿 디렉터리에 _helpers.tpl 파일을 만들고 그 안에 재사용 가능한 템플릿 코드를 두어 재사용할 수 있게 기존 코드를 디렉터링하자
## _helpers.tpl 파일 작성
cat << EOF > templates/_helpers.tpl
{{- define "pacman.selectorLabels" -}}   # stetement 이름을 정의 나중에 해당 필드로 값을 접근함.
app.kubernetes.io/name: {{ .Chart.Name}} # 해당 stetement 가 하는 일을 정의
{{- end }}
EOF

## 파일 수정. 반드시 nindent를 고려해야 한다. nindent 는 white space이다.
### deployment.yaml 수정
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "pacman.selectorLabels" . | nindent 6 }}   # pacman.selectorLabels를 호출한 결과를 6만큼 들여쓰기하여 주입
  template:
    metadata:
      labels:
        {{- include "pacman.selectorLabels" . | nindent 8 }} # pacman.selectorLabels를 호출한 결과를 8만큼 들여쓰기하여 주입
        
##3 service.yaml 수정
  selector:
    {{- include "pacman.selectorLabels" . | nindent 6 }} # pacman.selectorLabels를 호출한 결과를 6만큼 들여쓰기하여 주입

# 변경된 차트를 로컬에서 YAML 렌더링 : _helpers.tpl 설정된 값으로 갱신 확인
helm template .
---
# Source: pacman/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
      # stetement 이름을 정의
      app.kubernetes.io/name: pacman # 해당 stetement 가 하는 일을 정의
   

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

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

helm 컨테이너 이미지 변경

  • 배포 파일에서 컨테이너 이미지를 갱신하고 실행 중인 인스턴스를 업그레이드 할 수 있다
  • 배포된 helm chart를 upgrade 만으로 새로운 revision을 만들어서 배포하게 된다. 버전 관리는 Chart에서 appVersion 또는 이미지 tag로 관리할 수 있다.
# _helpers.tpl 파일 초기 설정으로 수정
cat << EOF > templates/_helpers.tpl
{{- define "pacman.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name}}
{{- end }}
EOF

# helm 배포
helm install pacman .

# 확인 : 리비전 번호, 이미지 정보 확인
helm history pacman
---
REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION     
1               Mon Oct 20 11:43:34 2025        deployed        pacman-0.1.0    1.0.0           Install complete

# 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

# 배포 업그레이드
helm upgrade pacman .
---
Release "pacman" has been upgraded. Happy Helming!
NAME: pacman
LAST DEPLOYED: Mon Oct 20 11:44:16 2025
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None

# 확인
helm history pacman
---
REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION     
1               Mon Oct 20 11:43:34 2025        superseded      pacman-0.1.0    1.0.0           Install complete
2               Mon Oct 20 11:44:16 2025        deployed        pacman-0.1.0    1.1.0           Upgrade complete

kubectl get secret
kubectl get deploy,replicaset -owide
  • 롤백
# 이전 버전으로 롤백
helm history pacman
helm rollback pacman 1 && kubectl get pod -w

# 확인, 롤백한 버전이 revision 3이 되었다.
helm history pacman
---
REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION     
1               Mon Oct 20 11:43:34 2025        superseded      pacman-0.1.0    1.0.0           Install complete
2               Mon Oct 20 11:44:16 2025        superseded      pacman-0.1.0    1.1.0           Upgrade complete
3               Mon Oct 20 11:46:44 2025        deployed        pacman-0.1.0    1.0.0           Rollback to 1   

kubectl get secret
kubectl get deploy,replicaset -owide
  • 새로운 values 파일 override. 새로운 values를 기존 values에 덮어 씌울 수 있다. 이 부분은 멀티 환경에서 매우 유용할 것 같다.
# values 새 파일 작성
cat << EOF > newvalues.yaml
image:
  tag: "1.2.0"
EOF

# template 명령 실행 시 새 values 파일 함께 전달 : 결과적으로 values.yaml 기본값을 사용하지만, image.tag 값은 override 함
helm template pacman -f newvalues.yaml .
...
  - image: "quay.io/gitops-cookbook/pacman-kikd:1.2.0"
...

helm chart 패키징과 배포

  • 다른 유저가 helm chart를 재사용 할 수 있도록 패키징하여 배포할 수 있다.
  • helm package 명령어를 사용하여 패키징한다.
# pacman 차트를 .tgz 파일로 패키징
helm package .
Successfully packaged chart and saved it to: .../pacman/pacman-0.1.0.tgz

gzcat pacman-0.1.0.tgz

# 해당 차트를 차트 저장소 repository 에 게시
# 차트 저장소는 차트 및 .tgz 차트에 대한 메타데이터 정보를 담은 index.html 파일이 있는 HTTP 서버
# 차트를 저장소에 게시하려면 index.html 파일을 새 메타데이터 정보로 업데이트하고 아티팩트를 업로드해야 한다.

## index.html 파일 생성
helm repo index .
cat index.yaml
---
apiVersion: v1
entries:
  pacman:
  - apiVersion: v2
    appVersion: 1.1.0
    created: "2025-10-20T11:51:20.865546+09:00"
    description: A Helm chart for Pacman
    digest: 95d1fb6e020038e41139eafc95b171d026d484b2776262e0bd7a98df1864ef87
    name: pacman
    type: application
    urls:
    - pacman-0.1.0.tgz
    version: 0.1.0
generated: "2025-10-20T11:51:20.865074+09:00"

# 디렉터리 구조
tree .
---
.
├── Chart.yaml
├── index.yaml
├── pacman-0.1.0.tgz
├── templates
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml

저장소로 부터 helm chart 배포

내가 차트를 만들지 않고 다른 유저가 만들어 놓은 차트를 저장소로 부터 가져와서 배포할 수 있다.

# repo 추가
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo list
---
NAME   	URL
bitnami	https://charts.bitnami.com/bitnami

# repo로 부터 차트 검색
helm search repo postgresql
helm search repo postgresql -o json | jq
---
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...

# repo로 부터 차트를 배포하기
helm install my-db \
--set postgresql.postgresqlUsername=my-default,postgresql.postgresqlPassword=postgres,postgresql.postgresqlDatabase=mydb,postgresql.persistence.enabled=false \
bitnami/postgresql

# 확인
helm list
NAME  	NAMESPACE	REVISION	UPDATED                             	STATUS  	CHART             	APP VERSION
my-db 	default  	1       	2025-10-20 13:47:54.1578 +0900 KST  	deployed	postgresql-18.0.17	18.0.0

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

# 실습 후 삭제
helm uninstall my-db

의존성과 함께 차트 배포

  • 어떤 차트가 다른 차트에 의존한다는 사실을 선언할 수 있고 Chart.yaml 에서 dependencies 섹션에 명시할 수 있다.
  • PostgreSQL 데이터베이스에 저장된 노래 목록을 반환하는 Java 서비스를 가지고 실습을 진행해본다.
mkdir -p music/templates && cd music

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

# service 파일
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

# 의존성 명시: psql 10.16.2 차트 책 버전 사용 시 
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: 10.16.2
    repository: "https://charts.bitnami.com/bitnami"
EOF

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...

# 의존성 명시: psql 버전 명시 의 경우, 현재 18.0.17 이 최신 버전이므로 해당 버전 사용
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

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

replicaCount: 1

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

# tree
.
├── Chart.yaml
├── templates
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml

# 의존성으로 선언된 차트를 다운로드하여 차트 디렉터리에 어장
helm dependency update
---
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "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

# update 이후 tree
.
├── Chart.lock
├── Chart.yaml
├── charts
│   └── postgresql-18.0.17.tgz
├── templates
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml

3 directories, 6 files

# Chart.lock
# 의존성 업데이트 이후 lock 파일도 생성되며 의존성 정보가 기입되어 있다.
dependencies:
- name: postgresql
  repository: https://charts.bitnami.com/bitnami
  version: 18.0.17
digest: sha256:4af693b17381c8b2e34298ce6eaf6e63d831bdfd0b2b88ca37b1abac4f5d556e
generated: "2025-10-20T13:58:57.261927+09:00"

# 차트 배포
helm install music-db .

# 확인
kubectl get sts,pod,svc,ep,secret,pv,pvc

# TS 1 : secret 에 키/값 추가
kubectl edit secret music-db-postgresql
postgresql-password: cG9zdGdyZXMK

# 에러가 발생하는데 뭔가 비번이 잘못됬는지 에러가 난다. 설정도 이상한게 없는데 뭔지는 모르겠다.

# database 접근하기
echo "M011bEMzNWpWMw==" | base64 -d
3MulC35jV3
kubectl exec -it music-db-postgresql-0 -- psql -U postgres -c "\du"
Password for user postgres: 3MulC35jV3
                             List of roles
 Role name |                         Attributes
-----------+------------------------------------------------------------
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS

(책에는 정확한 정보가 안나와있다)

helm chart 롤링업데이트

  • sha256sum 템플릿 함수를 활용해서 Deployment 리소스의 Pod 템플릿에 변경값(체크섬)을 주입한다.
  • Kustomize 사용 시: ConfigMapGenerator를 통해 ConfigMap이 변경되면 메타데이터 이름에 해시값을 붙이고, Deployment가 그 해시 이름을 참조하도록 구성.
  • Helm 사용 시: 각 템플릿 파일(예: configmap.yaml)에 대해 체크섬을 계산해 Deployment annotation에 삽입하면, ConfigMap 내용이 바뀔 때마다 Pod 템플릿(metadata)이 바뀌고 결과적으로 롤링 업데이트가 트리거된다.
  • 참고: ‘Secret, ConfigMap 변경 시 자동으로 Deployments, StatefulSets 등에 롤아웃’을 지원하는 도구로 Stakater Reloader 가 있다.
  • 참고로 책에서 제공하는 이미지는 접근이 불가능하다. 새로운 이미지를 사용한다.
mkdir -p greetings/templates && cd greetings

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

# configmap
cat << EOF > templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: greeting-config
data:
  greeting: Aloha
  BLUE_GREEN_CANARY_COLOR: "#6bbded"
  BLUE_GREEN_CANARY_MESSAGE: "Hello"
EOF

# 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

cat << EOF > Chart.yaml
apiVersion: v2
name: greetings
type: application
version: 0.1.0
appVersion: "1.0.0"
EOF

# values 파일 작성
cat << EOF > values.yaml
image:
  repository: quay.io/rhdevelopers/blue-green-canary
  tag: latest
  pullPolicy: Always
  containerPort: 8080

replicaCount: 1

configmap:
  name: greeting-config
EOF

helm install greetings .

이렇게 설치하고 configmap 의 greeting을 변경해본다.

# configmap.yaml
greeting: Alohas

helm upgrade greetings .

# 확인
k get pods

pod를 확인해보면 아무런 변화가 없고 pod는 그대로이다. 즉 configmap 이 변경되면 pod 가 재시작해야 새로운 configmap을 업로드하는데 자동으로 재시작을 안한다.

자동으로 재시작을 하기 위해서 sha256 checksum을 이용한다.

deployment.yaml에서 annotaions에 configmap checksum을 넣어주면 자동으로 재시작을 한다.

  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name}}
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}  
    spec:
      containers:
        - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion}}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          name: {{ .Chart.Name}}
          ports:

# helm 삭제후 재설치
helm uninstall greetings .
helm install greetings .

# configmap.yaml 변경
greeting: Alohas again

helm upgrade greetings .

# 확인
k get pods -w
NAME                         READY   STATUS    RESTARTS   AGE
greetings-76fc647fdb-2gbqh   1/1     Running   0          9s
greetings-6648dc9d5-vvr2f    0/1     Pending   0          0s
greetings-6648dc9d5-vvr2f    0/1     Pending   0          0s
greetings-6648dc9d5-vvr2f    0/1     ContainerCreating   0          0s
greetings-6648dc9d5-vvr2f    1/1     Running             0          1s

rolling update 함을 확인 할 수 있다.

Tekton

tekton이란?

  • Tekton은 쿠버네티스 기반 오픈소스 클라우드 네이티브 CI/CD 시스템이다.
  • Git을 통해 수행되는 작업에 기반하여 자동화를 지원하고, GitOps 워크플로우의 기본 구성 요소가 된다.
  • Tekton은 쿠버네티스 클러스터에 확장 모듈 형태로 설치되며, CI/CD 파이프라인 구축에 쓰이는 CRD(Custom Resource Definition)도 제공한다.
  • 주요 개념
    • Task: 특정 기능(e.g., 컨테이너 이미지 빌드)을 수행하는 재사용 가능하고 느슨하게 결합된 여러 단계(steps). 태스크는 파드(Pod)로 실행되고, 각 단계는 컨테이너에 대응된다.
    • Pipeline: 앱을 빌드 및 또는 배포하는 데 필요한 Task의 목록.
    • TaskRun: Task 인스턴스의 실행 및 그 결과.
    • PipelineRun: Pipeline 인스턴스의 실행 및 그 결과. 다수의 TaskRun을 포함할 수 있다.
    • Trigger: 이벤트를 감지하고 다른 CRD에 연결하여 해당 이벤트가 발생했을 때 어떤 일이 발생하는지를 지정.
  • 구성 요소
    • Tekton Pipelines: Task 및 Pipeline을 포함.
    • Tekton Triggers: Trigger 및 EventListener 포함.
    • Tekton Dashboard: 파이프라인과 로그를 시각화 할 수 있는 웹 기반 대시보드.
    • Tekton CLI (tkn): Tekton 객체를 관리하기 위한 CLI (파이프라인 및 작업 시작/중지, 로그 확인 등).
    • Tekton Hub: 카탈로그에 접근하기 위한 웹 기반 그래픽 인터페이스. (26년 1월 종료)
    • Tekton Operator: k8s 클러스터에 tekton 프로젝트를 설치, 업데이트, 제거할 수 있는 kubernetes operator 패턴.
    • Tekton Chain: tekton pipeline으로 구축된 아티팩트의 출처를 생성, 저장하고 서명하는 도구
  • 공식문서 링크

tekton 설치하기

아래의 명령어를 통해 pipeline, trigger, dashboard를 설치하고 http://localhost:30000 으로 접속해서 tekton dashboard 브라우저가 뜬다면 성공한 것이다.

# pipeline 설치
kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
# 확인
kubectl get pod -n tekton-pipelines
tekton-dashboard-7d4499b584-tdfqk                    1/1     Running   2 (13h ago)   22h
tekton-events-controller-99665746c-8r44r             1/1     Running   2 (13h ago)   22h
tekton-pipelines-controller-7595d6585d-zsphc         1/1     Running   2 (13h ago)   22h
tekton-pipelines-webhook-5967d74cc4-hwvl8            1/1     Running   2 (13h ago)   22h

# trigger 설치
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 설치
kubectl apply -f https://storage.googleapis.com/tekton-releases/dashboard/latest/release.yaml

# 전체 확인
kubectl get all -n tekton-pipelines
NAME                                                     READY   STATUS    RESTARTS      AGE
pod/tekton-dashboard-7d4499b584-tdfqk                    1/1     Running   2 (13h ago)   22h
pod/tekton-events-controller-99665746c-8r44r             1/1     Running   2 (13h ago)   22h
pod/tekton-pipelines-controller-7595d6585d-zsphc         1/1     Running   2 (13h ago)   22h
pod/tekton-pipelines-webhook-5967d74cc4-hwvl8            1/1     Running   2 (13h ago)   22h
pod/tekton-triggers-controller-74fccfc888-nbvpx          1/1     Running   2 (13h ago)   22h
pod/tekton-triggers-core-interceptors-7b8dcb59fb-769m7   1/1     Running   1 (13h ago)   22h
pod/tekton-triggers-webhook-5465cc8d5b-8h6qr             1/1     Running   2 (13h ago)   22h

NAME                                        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                              AGE
service/tekton-dashboard                    ClusterIP   10.96.161.51    <none>        9097/TCP                             22h
service/tekton-events-controller            ClusterIP   10.96.47.176    <none>        9090/TCP,8008/TCP,8080/TCP           22h
service/tekton-pipelines-controller         ClusterIP   10.96.190.192   <none>        9090/TCP,8008/TCP,8080/TCP           22h
service/tekton-pipelines-webhook            ClusterIP   10.96.20.47     <none>        9090/TCP,8008/TCP,443/TCP,8080/TCP   22h
service/tekton-triggers-controller          ClusterIP   10.96.144.156   <none>        9000/TCP                             22h
service/tekton-triggers-core-interceptors   ClusterIP   10.96.39.11     <none>        8443/TCP                             22h
service/tekton-triggers-webhook             ClusterIP   10.96.146.196   <none>        443/TCP                              22h

NAME                                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/tekton-dashboard                    1/1     1            1           22h
deployment.apps/tekton-events-controller            1/1     1            1           22h
deployment.apps/tekton-pipelines-controller         1/1     1            1           22h
deployment.apps/tekton-pipelines-webhook            1/1     1            1           22h
deployment.apps/tekton-triggers-controller          1/1     1            1           22h
deployment.apps/tekton-triggers-core-interceptors   1/1     1            1           22h
deployment.apps/tekton-triggers-webhook             1/1     1            1           22h

NAME                                                           DESIRED   CURRENT   READY   AGE
replicaset.apps/tekton-dashboard-7d4499b584                    1         1         1       22h
replicaset.apps/tekton-events-controller-99665746c             1         1         1       22h
replicaset.apps/tekton-pipelines-controller-7595d6585d         1         1         1       22h
replicaset.apps/tekton-pipelines-webhook-5967d74cc4            1         1         1       22h
replicaset.apps/tekton-triggers-controller-74fccfc888          1         1         1       22h
replicaset.apps/tekton-triggers-core-interceptors-7b8dcb59fb   1         1         1       22h
replicaset.apps/tekton-triggers-webhook-5465cc8d5b             1         1         1       22h

NAME                                                           REFERENCE                             TARGETS               MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/tekton-pipelines-webhook   Deployment/tekton-pipelines-webhook   cpu: <unknown>/100%   1         5         1          22h

# dashboard service의 Nodeport 설정 : nodePort 30000
kubectl patch svc -n tekton-pipelines tekton-dashboard -p '{"spec":{"type":"NodePort","ports":[{"port":9097,"targetPort":9097,"nodePort":30000}]}}'
kubectl get svc,ep -n tekton-pipelines tekton-dashboard
---
NAME                       TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
service/tekton-dashboard   NodePort   10.96.161.51   <none>        9097:30000/TCP   22h

NAME                         ENDPOINTS         AGE
endpoints/tekton-dashboard   10.244.0.3:9097   22h

# 텍톤 대시보드 접속
open http://localhost:30000   # macOS 경우

tekton cli 설치

# macOS
brew install tektoncd-cli

tkn version
---
Client version: 0.42.0
Pipeline version: v1.5.0
Triggers version: v0.33.0
Dashboard version: v0.62.0

# 명령어들
tkn taskrun logs <task 명> # task 로그 조회
tkn taskrun list # 전체 taskrun 조회

tekton task 만들기

  • 간단한 tekton task를 만들어 본다.
  • tekton에서 task는 작업 수행에 필요한 로직을 순차적으로 실행하는 일련의 step 들로 정의 된다.
  • 모든 task는 pod로 실행되며, 각 step은 자체 컨테이너에서 실행된다.
# 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

# 확인
tkn task list
---
NAME    DESCRIPTION   AGE
hello                 21 seconds ago

kubectl get tasks
---
NAME    AGE
hello   2m32s

kubectl get pod
---
# task가 실행되지는 않아 pipeline pod 들만 있다.

task 실행하기

# 신규 터미널 : 파드 상태 모니터링, task pod가 실행되며 2개의 init container가 실행된것을 볼 수 있다.
kubectl get pod -w
---
NAME                     READY   STATUS    RESTARTS         AGE
hello-run-xbxpr-pod      0/1     Pending            0                0s
hello-run-xbxpr-pod      0/1     Pending            0                0s
# 2개의 init container
hello-run-xbxpr-pod      0/1     Init:0/2           0                0s
hello-run-xbxpr-pod      0/1     Init:1/2           0                7s
hello-run-xbxpr-pod      0/1     PodInitializing    0                11s
# 실제 step 실행
hello-run-xbxpr-pod      1/1     Running            0                16s
hello-run-xbxpr-pod      1/1     Running            0                16s
hello-run-xbxpr-pod      0/1     Completed          0                18s
hello-run-xbxpr-pod      0/1     Completed          0                19s
# tkn CLI로 task 시작 
tkn task start --showlog hello

# init container 로그 확인
# initial과 관련된 로그들이 보인다.
kubectl logs -l tekton.dev/task=hello -c prepare
2025/10/21 15:21:38 Entrypoint initialization
kubectl logs -l tekton.dev/task=hello -c place-scripts
2025/10/21 15:21:42 Decoded script /tekton/scripts/script-0-mcmkj

# step이 실행된 container의 로그 확인
kubectl logs -l tekton.dev/task=hello -c step-echo
---
Hello World

# kubectl 뿐만 아니라 tekton cli로도 확인할 수 있다.
tkn task logs hello
tkn task describe hello

# task 삭제
kubectl delete taskruns --all

Create a Task to Compile and Package an App from Git

  • 텍톤을 사용하여 Git 저장소에 보관된 앱 코드를 컴파일하고 패키징하는 작업을 자동화하는 방법에 대해서 다뤄 본다.
  • 추후 파이프라인을 만들 때 쓸 수 있도록 input과 output이 잘 규정된 Task를 만들어 본다.

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

  • Task에 속한 각 단계 step 와 Task 는 텍톤 워크스페이스 workspace 라 불리는 파일 시스템을 공유할 수 있다.
  • 이 공유 파일 시스템은 PVC로 만들어진 파일 시스템이거나 ConfigMap 혹은 emptyDir 불륨을 사용한다.
  • Task 는 param 인자를 받을 수 있으며, 그 인자를 통해 실제 작업 내용을 동적으로 결정할 수 있다.
  • 시나리오
    • git repo를 input으로 주입받아 정의된 clone을 하는 파이프라인을 구성한다.
# 파이프라인 파일 작성
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

# 확인
tkn pipeline list
---
NAME         AGE              LAST RUN   STARTED   DURATION   STATUS
clone-read   26 seconds ago   ---        ---       ---        ---

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

# 딱히 별도의 pipeline이 생성된 것 같지는 않다.
kubectl get pod -A
---
tekton-pipelines-resolvers   tekton-pipelines-remote-resolvers-86f56b6664-prkbs   1/1     Running            2 (14h ago)      23h
tekton-pipelines             tekton-dashboard-7d4499b584-tdfqk                    1/1     Running            2 (14h ago)      23h
tekton-pipelines             tekton-events-controller-99665746c-8r44r             1/1     Running            2 (14h ago)      23h
tekton-pipelines             tekton-pipelines-controller-7595d6585d-zsphc         1/1     Running            2 (14h ago)      23h
tekton-pipelines             tekton-pipelines-webhook-5967d74cc4-hwvl8            1/1     Running            2 (14h ago)      23h
tekton-pipelines             tekton-triggers-controller-74fccfc888-nbvpx          1/1     Running            2 (14h ago)      23h
tekton-pipelines             tekton-triggers-core-interceptors-7b8dcb59fb-769m7   1/1     Running            1 (14h ago)      23h
tekton-pipelines             tekton-triggers-webhook-5465cc8d5b-8h6qr             1/1     Running            2 (14h ago)      23h

Step2: 파이프라인 실행(에러 발생), https://github.com/tektoncd/website 을 params로 주입

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

# 확인
kubectl get pipelineruns -o yaml | kubectl neat | yq
kubectl get pipelineruns
---
NAME                   SUCCEEDED   REASON           STARTTIME   COMPLETIONTIME
clone-read-run-pzxnk   False       CouldntGetTask   2m56s       2m56s

# 로그 확인 에러 발생 "git-clone" 명령어 없음으로 인한 에러
tkn pipelinerun logs clone-read-run-pzxnk
---
Pipeline default/clone-read can't be Run; it contains Tasks that don't exist: Couldn't retrieve Task "git-clone": tasks.tekton.dev "git-clone" not found

# task 확인, git-clone task가 없다.
k get tasks
---
NAME        AGE
hello       27m

git-clone task 가 없다고 하면서 pipeline 에러가 발생한다. 왜나면 git-clone task를 pipeline에 포함시켜 생성했지만 현재 git-clone task가 없기 때문이다.

Step3: 에러 해결, git-clone task생성하기

# 파이프라인에서 git clone 작업을 사용하려면 먼저 클러스터에 설치 필요 : tacket hub 에서 가져오기
tkn hub install task git-clone
---
WARN: This version has been deprecated
Task git-clone(0.9) installed in default namespace

# 추가된 task 확인, git-clone task 추가됨
kubectl get tasks
---
NAME        AGE
git-clone   71s

# 파이프라인 재실행
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

# task 가 정상적으로 실행되므로 task의 pod가 새로 생성되었고 running으로 바뀌면서 로그 조회가 가능해진다.
tkn pipelinerun list
NAME                   STARTED         DURATION   STATUS
clone-read-run-n4p6s   5 seconds ago   ---        Running
clone-read-run-pzxnk   9 minutes ago   0s         Failed(CouldntGetTask)

tkn pipelinerun logs clone-read-run-n4p6s
---
[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":1761061643.1798418,"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":1761061643.1893837,"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

# pv,pvc 확인, pv/pvc가 생성됨을 확인할 수 있다. 왜냐하면 pipeline에서 volumeClaim 을 설정했기 때문에...
kubectl get pod,pv,pvc
NAME                                        READY   STATUS             RESTARTS       AGE
pod/clone-read-run-n4p6s-fetch-source-pod   0/1     Completed          0              46s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/pvc-0c2c3586-e367-42f4-8e40-a30fa8ffd6f2   1Gi        RWO            Delete           Bound    default/pvc-5aaa65334e               standard       <unset>                          43s

NAME                                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/pvc-5aaa65334e               Bound    pvc-0c2c3586-e367-42f4-8e40-a30fa8ffd6f2   1Gi        RWO            standard       <unset>                 46s

# 실습 완료 후 삭제
kubectl delete pipelineruns.tekton.dev --all

Create a Task to Compile and Package an App from Private Git

  • 이번 실습은 비공개 Git 저장소에서 clone하여 tekton으로 자동화 하는 것이다.
  • 이전 실습은 공개 github이였기에 아무런 문제가 없었지만 비공개 git은 인증절차가 필요하다.
  • 사전 준비
    • git repo 생성: my-sample-app (반드시 private으로 생성한다)
    • 인증토큰 발급: PAT 방식의 인증토큰을 발급한다. [참조]
    • ssh 인증보다는 token 으로 인증한다. 실제 라이브 환경에서 ssh 인증을 잘 사용하지 않는다.

Step1: 샘플 앱 생성 및 Git 초기화

# 작업 폴더 생성
mkdir my-sample-app
cd my-sample-app

# 샘플 파일 만들기 (Node.js 예시)
echo 'console.log("Hello GitHub!");' > app.js
echo "# sample app" > readme.md

# Git 초기화
git init

# Git 사용자 설정 (처음이라면)
git config --global user.name "Your Name"
git config --global user.email "you@example.com"

# 파일 추가 및 커밋
git add .
git commit -m "Initial commit - sample app"

Step2: GitHub remote 연결 및 push

# origin remote 등록:
git remote add origin https://github.com/<your-username>/my-sample-app.git

# 메인 브랜치 이름을 main으로 변경 (GitHub 기본 브랜치와 맞춤):
git branch -M main

# Push!
git push -u origin main
---
Username for 'https://github.com': <your-username>
Password for 'https://<your-username>@github.com': <2번 토큰>

Step3: git credential 생성

# git credential 생성
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: git-credentials
  annotations:
    tekton.dev/git-0: https://github.com/<your-username>/my-sample-app.git # 나의 git 주소
type: kubernetes.io/basic-auth
stringData:
  username: <your-username>
  password: <your-token>
EOF

# 확인
kubectl get secret
NAME                             TYPE                       DATA   AGE
git-credentials                  kubernetes.io/basic-auth   2      19s

# ServiceAccount 에 Secret 속성 지정
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: build-bot
secrets:
  - name: git-credentials
EOF

# 확인
kubectl get sa
NAME                  SECRETS   AGE
build-bot             1         8s

Step4: pipeline 실행

# 파이프라인 파일 작성
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 git token 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
      value: \$(params.repo-url)
  - name: show-readme # add task
    runAfter: ["fetch-source"]
    taskRef:
      name: show-readme
    workspaces:
    - name: source
      workspace: shared-data
EOF

# 확인
tkn pipeline list
NAME            AGE              LAST RUN   STARTED   DURATION   STATUS
my-clone-read   3 seconds ago    ---        ---       ---        ---

tkn pipeline describe

kubectl get pipeline
NAME            AGE
my-clone-read   29s

# yaml로 상세확인
kubectl get pipeline -o yaml | kubectl neat | yq

# task를 시작하지 않으므로 아무런 pod 없음
kubectl get pod

# show-readme task
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

# 파이프라인 실행
# params에 꼭 자신의 github주소를 지정하고 token 으로 credential을 생성했기에 https 주소로 입력한다.
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: https://github.com/<your-username>/my-sample-app.git # 사용자 github 주소로 지정하기!
EOF

# 결과 확인 : 2개의 step 으로 각기 2개의 파드가 실행됨을 확인
kubectl get pod,pv,pvc
NAME                                        READY   STATUS             RESTARTS         AGE
pod/affinity-assistant-365a7c8dcd-0         1/1     Running            0                6s
pod/clone-read-run-sn5pc-fetch-source-pod   0/1     PodInitializing    0                6s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/pvc-9754b648-fbf1-46e3-b13b-bbc2b5b43fb1   1Gi        RWO            Delete           Bound    default/pvc-d4206cb781               standard       <unset>                          4s

NAME                                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/pvc-d4206cb781               Bound    pvc-9754b648-fbf1-46e3-b13b-bbc2b5b43fb1   1Gi        RWO            standard       <unset>                 6s

# task 를 실행하는 파드에 service acount 정보 확인
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:  build-bot
Service Account:  default
Service Account:  music-db-postgresql

실행 결과

Containerize an Application Using a Tekton Task and Buildah

  • 텍톤 Task를 사용하여 git를 통하여 소스코드를 가지고 이미지를 빌드하고 container registry에 pus하는 파이프라인을 구성하는 실습이다.
  • 텍톤의 확장 가능한 모델 덕분에 이전에서 사용한 Task를 재사용하는 것이 가능하다.
  • 이전 단계 step 의 결과물을 가져와 컨테이너 이미지를 만드는 새로운 단계를 그림같이 추가하는 방식이다
    Build Push app
  • 동작
    1. Git 저장소에서 소스 코드를 복제하는 작업을 만듭니다.
    2. 복제된 코드를 사용하여 Docker 이미지를 빌드 kaniko 하고 레지스트리에 푸시하는 두 번째 작업을 만듭니다.
  • 사전 준비 : 비공개 컨테이너 이미지 저장소 인증 정보(토큰 등)
# task 설치 : https://hub.tekton.dev/tekton/task/kaniko
tkn hub install task kaniko
kubectl get tasks
---
NAME          AGE
git-clone     41m
hello         67m
kaniko        55s
show-readme   13m

# Docker 자격 증명으로 Secret을 적용
## macOS 경우
-------------------------------------------------
# 아래 명령어를 실행하면 username과 secret 정보를 얻을 수 있다.
echo "https://index.docker.io/v1/" | docker-credential-osxkeychain get | jq
---
{
  "ServerURL": "https://index.docker.io/v1/",
  "Username": "이 값과", 
  "Secret": "요값을 사용함,dckr_pat_Cs5gwal97c6sjsHhe_Z34dAt4co"
}

echo -n "<Username>:<Secret>" | base64
AXDFGHXXCFGFGF==

# ~/.docker/config.json 대신 임시 파일 dsh.txt 작성
cat > dsh.txt <<'EOF'
{
  "auths": {
    "https://index.docker.io/v1/": {
      "auth": "AXDFGHXXCFGFGF=="
    }
  }
}
EOF

# dsh.txt 파일 내용을 다시 base64 적용
DSH=$(cat dsh.txt | 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
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: build-sa
  namespace: default
secrets:
  - name: docker-credentials

# 파이프라인 파일 작성
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/hanship0915/docsy:1.0.0    # 각자 자신의 저장소
EOF

# 결과 확인
kubectl get pod,pv,pvc
---
NAME                                              READY   STATUS             RESTARTS       AGE
pod/affinity-assistant-02e684853d-0               1/1     Running            0              7s
pod/clone-build-push-run-nvxwq-fetch-source-pod   1/1     Running            0              7s
pod/clone-read-run-qfvq8-fetch-source-pod         0/1     Completed          0              11m
pod/clone-read-run-qfvq8-show-readme-pod          0/1     Completed          0              11m
pod/clone-read-run-sn5pc-fetch-source-pod         0/1     Error              0              22m
pod/clone-read-run-tq2z4-fetch-source-pod         0/1     Completed          0              16m
pod/clone-read-run-tq2z4-show-readme-pod          0/1     Error              0              15m
pod/clone-read-run-zbnhr-fetch-source-pod         0/1     Completed          0              18m
pod/clone-read-run-zbnhr-show-readme-pod          0/1     Error              0              18m

tkn pipelinerun list
NAME                         STARTED          DURATION   STATUS
clone-build-push-run-nvxwq   1 minute ago     1m8s       Succeeded

tkn pipelinerun logs  clone-build-push-run-nvxwq -f

# 로그 확인
kubectl stern clone-build-push-run-5fn7x-build-push-pod
[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/gasida/docsy-example.git' '-revision=' '-refspec=' '-path=/workspace/output/' '-sslVerify=true' '-submodules=true' '-depth=1' '-sparseCheckoutDirectories='
[fetch-source : clone] {"level":"info","ts":1761064488.694023,"caller":"git/git.go:176","msg":"Successfully cloned https://github.com/gasida/docsy-example.git @ 36776dc210006efaa6b487fe5cc772d466436cc6 (grafted, HEAD) in path /workspace/output/"}
[fetch-source : clone] {"level":"info","ts":1761064488.7060614,"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=36776dc210006efaa6b487fe5cc772d466436cc6
[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=1760966900
[fetch-source : clone] + printf '%s' 1760966900
[fetch-source : clone] + printf '%s' 36776dc210006efaa6b487fe5cc772d466436cc6
[fetch-source : clone] + printf '%s' https://github.com/gasida/docsy-example.git

[build-push : build-and-push] 2025/10/21 16:35:07 ERROR failed to get CPU variant os=linux error="getCPUVariant for OS linux: not implemented"
[build-push : build-and-push] INFO[0001] Retrieving image manifest floryn90/hugo:ext-alpine
[build-push : build-and-push] INFO[0001] Retrieving image floryn90/hugo:ext-alpine from registry index.docker.io
[build-push : build-and-push] INFO[0003] Built cross stage deps: map[]
[build-push : build-and-push] INFO[0003] Retrieving image manifest floryn90/hugo:ext-alpine
[build-push : build-and-push] INFO[0003] Returning cached image manifest
[build-push : build-and-push] INFO[0003] Executing 0 build triggers
[build-push : build-and-push] INFO[0003] Unpacking rootfs as cmd RUN apk add git &&   git config --global --add safe.directory /src requires it.
[build-push : build-and-push] INFO[0020] USER root
[build-push : build-and-push] INFO[0020] cmd: USER
[build-push : build-and-push] INFO[0020] RUN apk add git &&   git config --global --add safe.directory /src
[build-push : build-and-push] INFO[0020] Taking snapshot of full filesystem...
[build-push : build-and-push] INFO[0022] cmd: /bin/sh
[build-push : build-and-push] INFO[0022] args: [-c apk add git &&   git config --global --add safe.directory /src]
[build-push : build-and-push] INFO[0022] util.Lookup returned: &{Uid:0 Gid:0 Username:root Name:root HomeDir:/root}
[build-push : build-and-push] INFO[0022] performing slow lookup of group ids for root
[build-push : build-and-push] INFO[0022] Running: [/bin/sh -c apk add git &&   git config --global --add safe.directory /src]
[build-push : build-and-push] fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/main/aarch64/APKINDEX.tar.gz
[build-push : build-and-push] fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/community/aarch64/APKINDEX.tar.gz
[build-push : build-and-push] OK: 88 MiB in 68 packages
[build-push : build-and-push] INFO[0023] Taking snapshot of full filesystem...
[build-push : build-and-push] INFO[0023] Pushing image to docker.io/hanship0915/docsy:1.0.0
[build-push : build-and-push] INFO[0031] Pushed image to 1 destinations

[build-push : write-url] docker.io/<username>/docsy:1.0.0

# 다음 실습을 위해 삭제
kubectl delete taskruns,pipelineruns.tekton.dev --all

docker hub에 이미지가 올라갔음을 볼 수 있다.

기타: bitnami 공개 카탈로그 삭제

GeekNews

  • docker.io/Bitnami 삭제 예정 · 링크

Broadcom, Bitnami Secure Images 발표

  • “Production-Ready Containerized Applications” 발표 문서 [링크]
    • 기업용으로 하드닝된 이미지, 낮은 공격면(attack surface), 지속적인 보안 패치 제공.
    • Helm 차트 포함, SBOM(소프트웨어 자재 명세서), CVE 투명성 등 강조됨.

카탈로그 변경 예정 (2025.08.28 이후)

  • 저장소 및 제공 정책 변화 · 이슈
    • docker.io/bitnami 의 기존 이미지 대부분이 더 이상 업데이트되지 않거나 지원되지 않는 Legacy 저장소로 이전 예정.
    • 무료 커뮤니티용은 매우 제한된 hardened 이미지 + latest 태그로만 제공될 예정.

Bitnami Secure Images (BSI)

  • Docker Hub: bitnamisecure 사용자 네임스페이스에서 현재 일부 이미지 제공됨 · 링크
  • GitHub: 저장소 bitnami/containers · 링크
  • 예) bitnamisecure/nginx:latest 이미지 사용법
docker pull bitnamisecure/nginx:latest
docker run -d -p 8080:8080 --name nginx bitnamisecure/nginx:latest
curl -s 127.0.0.1:8080
docker logs nginx
docker rm -f nginx
  • GitHub에서 커스텀 이미지 빌드 가능:
git clone https://github.com/bitnami/containers.git
cd bitnami/APP/VERSION/OPERATING-SYSTEM
docker build -t REGISTRY_NAME/bitnami/APP:latest .

Bitnami Legacy Registry (“더 이상 업데이트되지 않는 저장소”)

  • docker.io/bitnamilegacy 네임스페이스에 기존 버전 이미지 보관됨.
  • 예) bitnamilegacy/nginx:1.28.0-debian-12-r4 등.
docker pull bitnamilegacy/nginx:1.28.0-debian-12-r4
docker run -d -p 8080:8080 --name nginx bitnamilegacy/nginx:1.28.0-debian-12-r4
curl -s 127.0.0.1:8080
docker logs nginx
docker rm -f nginx

OCI 방식으로 Helm 차트 저장/배포 가능

  • 기존 Helm repo 방식 vs OCI registry 방식 비교
항목Helm repo 방식 (기존)OCI 방식 (신규)
저장소 별도 Helm repo (예: https://charts.bitnami.com/bitnami) OCI 호환 컨테이너 레지스트리 (예: oci://registry-1.docker.io/...)
배포 helm repo add → helm install helm install oci://…
인증 Helm repo 별도 인증 필요할 수 있음 Docker 레지스트리 인증 방식 재사용
장점 익숙함 CI/CD 친화적, 표준화
단점 별도 repo 운영 필요 Helm 버전 3.8 이상 필요
  • 예시 명령:
helm pull oci://registry-1.docker.io/bitnamicharts/nginx --version 22.0.11
helm install my-nginx oci://registry-1.docker.io/bitnamicharts/nginx --version 22.0.11
 

'스터디 > [gasida] ci-cd 스터디 1기' 카테고리의 다른 글

Vault Production & Kubernetes  (0) 2025.12.05
Image Build  (0) 2025.12.05
Vault  (0) 2025.11.26
OpenLDAP + KeyCloak + Argo CD + Jenkins  (0) 2025.11.23
ArgoCD ApplicationSet  (0) 2025.11.23

+ Recent posts