티스토리 뷰

IT/Devops

[cicd] 2주차 - Helm, Tekton

Hayley Shim 2025. 10. 25. 22:39

안녕하세요, CICD 학습을 위해 CloudNetaStudy 스터디 모임을 통해 진행한 내용을 정리하였습니다.

 

2주차는 [GitOps Cookbook] 5~6장(192p) - 헬름, Cloud Native CI/CD(Tekton, Github Action) 에 대해 중점적으로 학습합니다.

 

[개념]

개념 설명
Helm 쿠버네티스(Kubernetes)를 위한 패키지 관리자입니다. 커스터마이즈(Kustomize)와 유사하게 템플릿 기반 솔루션을 제공하지만, 패키지 관리자처럼 작동하여 버전 관리, 공유, 배포 가능한 아티팩트(artifact)를 생성합니다.
Chart Helm의 핵심 개념으로, 공유 가능한 쿠버네티스 패키지입니다. 애플리케이션을 배포하는 데 필요한 모든 리소스 정의(Deployment, Service 등)와 구성 정보를 포함하며, 다른 차트에 대한 의존성 등 다양한 요소를 담을 수 있습니다.

 

 

[실습환경] - kind(k8s)

Kind를 사용하여 로컬 환경에 쿠버네티스 클러스터를 구성

  • 클러스터 이름: myk8s
  • 쿠버네티스 버전: v1.32.8
  • 추가 포트 매핑: 호스트 포트 30000과 30001을 컨테이너 포트에 매핑하여 외부에서 접근 가능하도록 설정(NodePort 등 서비스 테스트에 유용)
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]

Creating a Helm Project

Helm Chart 디렉토리 레이아웃 생성

- pacman이라는 이름의 Helm Chart 디렉터리를 생성하고 기본 구성 파일들을 작성했습니다.

 

1) 디렉터리 레이아웃

pacman/
├── Chart.yaml       # 차트 메타데이터 (이름, 버전 등)
├── templates/       # 쿠버네티스 리소스 Go 템플릿 파일
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml      # 차트의 기본 설정값 (사용자 정의 가능)

 

파일 역할
템플릿 변수 사용 예
Chart.yaml 차트의 이름(pacman), 설명, 차트 버전(version: 0.1.0), 애플리케이션 버전(appVersion: "1.0.0") 등 메타데이터 정의.
{{ .Chart.Name}}, {{ .Chart.AppVersion }}
values.yaml 배포 시 적용될 기본 설정값을 정의. 배포 시점에 --set 파라미터 등으로 재정의 가능.
{{ .Values.replicaCount }}, {{ .Values.image.repository }}
templates/ Deployment, Service 등 쿠버네티스 리소스 정의 파일을 Go 템플릿 언어로 작성. Chart.yaml 및 values.yaml의 값을 가져와 동적으로 YAML을 생성.
{{ .Values.image.pullPolicy }}, {{- toYaml .Values.securityContext }}
# 헬름 차트 디렉터리 레이아웃 생성
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   # 차트 기본값

 

헬름 차트를 로컬에서 YAML로 렌더링

- helm template . 명령을 사용하여 실제로 쿠버네티스에 배포되기 전에, 템플릿이 values.yaml의 기본값과 결합되어 어떤 YAML 파일로 변환되는지 확인

  • Service와 Deployment YAML 파일 생성
  • 예시:
    • name: pacman은 {{ .Chart.Name}}에서 가져옴
    • replicas: 1과 image: "quay.io/...:1.0.0" 등은 values.yaml의 기본값으로 채움
#
helm template .
---
# Source: pacman/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: pacman
  name: pacman           # Chart.yaml 파일에 설정된 이름을 가져와 설정
spec:
  ports:
    - name: http
      port: 8080         # values.yaml 파일에서 가져옴
      targetPort: 8080
  selector:
    app.kubernetes.io/name: pacman
---
# Source: pacman/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pacman            # Chart.yaml 파일에 설정된 이름을 가져와 설정
  labels:
    app.kubernetes.io/name: pacman
    app.kubernetes.io/version: "1.0.0"     # Chart.yaml  appVersion 값을 가져와 지정하고 따움표 처리
spec:
  replicas: 1     # replicaCount 속성을 넣을 자리 placeholder
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman
    spec:
      containers:
        - image: "quay.io/gitops-cookbook/pacman-kikd:1.0.0"  # 두 속성의 내용이 하나로 연결
          imagePullPolicy: Always
          securityContext: # 보안 컨텍스트는 빈 값
              {} # securityContext의 값을 YAML 객체로 지정하며 14칸 들여쓰기
          name: pacman
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP

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

 

해당 차트를 kind 배포 및 helm 확인

- helm install pacman . 명령으로 Chart를 클러스터에 배포

 

- Helm은 릴리스 상태를 관리하기 위해 자동으로 Secret 리소스를 생성

  • kubectl get secret 명령으로 sh.helm.release.v1.pacman.v1과 같은 Secret이 생성된 것을 확인
  • 이 Secret은 릴리스 메타데이터를 저장하며, Helm이 차트의 상태를 복구하거나 롤백(rollback)할 때 사용
# 해당 차트 배포
helm install pacman .
helm list
NAME  	NAMESPACE	REVISION	UPDATED                             	STATUS  	CHART       	APP VERSION
pacman	default  	1       	2025-10-18 16:56:58.315737 +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       	Sat Oct 18 16:56:58 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      5m17s

 

Reusing Statements Between Templates

같은 코드 확인 및 재사용 가능 코드 블록 정의

- 템플릿 간 구문 재사용: _helpers.tpl 활용 

- 여러 쿠버네티스 리소스 정의 파일(예: deployment.yaml, service.yaml)에서 반복되는 코드 블록을 중앙 집중화하여 관리하고 재사용하는 방법

# 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

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


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


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

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

 

Updating a Container Image in Helm

이미지 업그레이드/롤백, value override

- Helm의 upgrade 및 rollback 명령을 사용하여 애플리케이션 버전을 효율적으로 관리하는 방법

1) 이미지 업그레이드

  1. 초기 배포: helm install pacman .으로 릴리스를 배포합니다. (REVISION: 1, Image: 1.0.0)
  2. 버전 업데이트:
    • values.yaml 파일의 image.tag를 "1.1.0"으로 변경
    • Chart.yaml 파일의 appVersion을 "1.1.0"으로 갱신
  3. 업그레이드 실행: helm upgrade pacman . 명령을 실행하면, Helm이 기존 릴리스의 설정을 새 설정으로 업데이트하고 새로운 쿠버네티스 리소스를 배포
    • 결과: REVISION 2가 생성되고, Deployment의 이미지가 1.1.0으로 변경됨

2) 이전 버전으로 롤백

  • 롤백 실행: helm rollback pacman 1 명령을 실행하여 릴리스를 이전 버전인 REVISION 1 상태로 되돌림
  • 결과: REVISION 3이 생성되고, Deployment의 이미지는 다시 1.0.0으로 변경됨

3) 값 재정의 (-f 사용)

  • newvalues.yaml 파일에 image.tag: "1.2.0"만 정의
  • helm template pacman -f newvalues.yaml .을 실행하면, 기본 values.yaml의 설정은 유지하면서 image.tag 값만 newvalues.yaml의 "1.2.0"으로 덮어씀(override). 이는 임시 테스트나 환경별 설정을 적용할 때 유용
# _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
kubectl get deploy -owide
# 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: Sat Oct 18 18:15:47 2025
NAMESPACE: default
STATUS: deployed
REVISION: 2      # 새 리비전 번호
TEST SUITE: None

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

# 확인
helm history pacman
kubectl get secret
kubectl get deploy,replicaset -owide
# 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"
...

 

Packaging and Distributing a Helm Chart

- 작성한 차트를 압축하고 공유 가능한 형태로 만드는 과정

# 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-18T18:33:41.240749+09:00"
    description: A Helm chart for Pacman
    digest: 1a68e0069016d96ab64344e2d4c2fde2b7368e410f93da90bf19f6ed8ca9495a
    name: pacman
    type: application
    urls:
    - pacman-0.1.0.tgz
    version: 0.1.0
generated: "2025-10-18T18:33:41.239645+09:00"

 

 

Deploying a Chart from a Repository

# repo
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo list
helm search repo postgresql
helm search repo postgresql -o json | jq

# 배포
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-19 11:53:08.860197 +0900 KST	deployed	postgresql-18.0.17	18.0.0

kubectl get sts,pod,svc,ep,secret

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

# 실습 후 삭제
helm uninstall my-db

 

Deploying a Chart with a Dependency

- 하나의 애플리케이션 차트(예: music 서비스)가 데이터베이스와 같은 다른 차트(예: postgresql)에 의존할 때 이를 함께 배포하는 방법

1) 의존성 선언

  • Chart.yaml 파일에 dependencies 섹션을 추가하여 의존성을 선언함
    • name: postgresql: 의존하는 차트의 이름
    • version: 18.0.17: 사용할 버전
    • repository: "https://charts.bitnami.com/bitnami": 차트가 위치한 저장소 주소

2) 의존성 다운로드

  • helm dependency update 명령을 실행하면, 선언된 postgresql 차트의 .tgz 파일이 외부 저장소에서 다운로드되어 charts/ 디렉터리에 저장됨

3) 메인 차트와 의존성 차트의 연동

  • music 서비스의 deployment.yaml 파일은 env 설정을 통해 PostgreSQL 데이터베이스 연결 정보를 환경 변수로 받도록 템플릿화됨
  • {{ .Values.postgresql.server | default (printf "%s-postgresql" ( .Release.Name )) | quote }} 와 같은 구문은 의존성 차트의 값을 상위 차트의 values.yaml에서 가져오거나, 그렇지 않으면 기본값을 사용하여 데이터베이스 서비스의 주소를 구성
  • 배포: helm install music-db .를 실행하면 music 차트와 그 의존성인 postgresql 차트가 동시에 배트됨

4) 서드 파티 차트 확인

  • 저장소 추가: helm repo add bitnami ...
  • 값 확인: 서드 파티 차트의 기본 설정값과 사용 가능한 파라미터는 helm show values bitnami/postgresql 명령으로 확인할 수 있음

5) 트러블슈팅 예시 (TS)

  • 배포 후 Pod가 Error 상태가 되고 로그에 "couldn't find key postgresql-password in Secret..." 오류가 발생
  • 해결책: PostgreSQL 차트가 생성한 Secret 리소스 (music-db-postgresql)에 데이터베이스 비밀번호를 담은 postgresql-password 키/값 쌍을 Base64 인코딩된 형태로 추가하여 Secret을 수동으로 수정함으로써 애플리케이션이 DB에 접근할 수 있도록 문제를 해결
#
mkdir music
mkdir music/templates
cd music

#
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

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

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   	11.6.2       	14.3.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/postgresql-ha	16.3.1       	17.6.0     	This PostgreSQL cluster solution includes the P...


# 현재 최신 차트 버전 사용
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 # book 10.16.2
    repository: "https://charts.bitnami.com/bitnami"
EOF

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

replicaCount: 1

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

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


# 의존성으로 선언된 차트를 다운로드하여 차트 디렉터리에 어장
helm dependency update

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

# 차트 배포
helm install music-db .
Warning  Failed     2s (x8 over 86s)  kubelet            Error: couldn't find key postgresql-password in Secret default/music-db-postgresql

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

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

# TS 2 : 직접 해결해보자!
kubectl logs -l app.kubernetes.io/name=music -f
	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)

# music service 에 port-forward 설정 후 호출하여 노래 목록 확인
kubectl port-forward service/music 8080:8080
curl -s http://localhost:8080/song
...

# 삭제
helm uninstall music-db
kubectl delete pvc --all

 

Triggering a Rolling Update Automatically

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

- Helm은 ConfigMap이나 Secret 객체의 내용이 변경될 때 Deployment의 롤링 업데이트를 자동으로 시작하도록 Pod 템플릿에 해당 객체의 콘텐츠 해시 값(Checksum)을 주입하는 방식으로 이 문제를 해결함

  • configmap.yaml 파일의 전체 콘텐츠에 대해 SHA-256 해시 값을 계산함
  • 이 해시 값을 Deployment의 Pod 템플릿 메타데이터Annotation 필드에 설정함
  • ConfigMap의 내용이 변경되면, 해시 값이 달라지고, 이 해시 값이 설정된 Pod 템플릿의 Annotation도 변경됨
  • Pod 템플릿 정의가 변경되었으므로, Deployment는 자동으로 롤링 업데이트를 시작함
...
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 }}
...

[Cloud Native CI/CD]

Install Tekton

- 쿠버네티스 클러스터에 텍톤 설치

  • 텍톤 Tekton 소개 - Home , Docs , Blog , Github
    • 지속적 통합 Continuous Integration 은 개발자가 만들 새 코드를 가져와서 빌드, 테스트, 실행하는 과정을 자동으로 처리하는 프로세스
    • 클라우드 네이티브 CI는 이 프로세스에 클라우드 컴퓨팅과 클라우드 서비스가 결합한 모델
    • Git을 통해 수행되는 작업에 기반하여 자동화를 지원하므로 GitOps 워크플로의 기본 구성 요소
    • 텍톤 Tekton 은 쿠버네티스 기반 오픈 소스 클라우드 네이티브 CI/CD 시스템

1) 텍톤 파이프라인 (Tekton Pipelines) 설치

  • 역할: CI/CD 워크플로의 핵심 엔진. 빌드, 테스트, 배포와 같은 작업을 정의하고 실행함
  • 설치: kubectl apply -f [release.yaml 주소] 명령을 사용해 커스텀 리소스 정의(CRD)와 컨트롤러를 배포함
  • 확인:
    • kubectl get crd: 파이프라인 관련 CRD(예: tasks.tekton.dev)가 생성되었는지 확인함
    • kubectl get pod -n tekton-pipelines: tekton-pipelines-controller와 tekton-pipelines-webhook 등의 핵심 파드들이 정상적으로 실행 중인지 (Running) 확인함
# Tekton dependency 파이프라인(pipeline) 설치 : 현재 v1.5.0
kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml

# Tekton dependency 파이프라인(pipeline) 설치 확인
kubectl get crd
kubectl get ns | grep tekton
kubectl get-all -n tekton-pipelines # kubectl krew install get-all
kubectl get all -n tekton-pipelines
kubectl get-all -n tekton-pipelines-resolvers
kubectl get all -n tekton-pipelines-resolvers

# 파드 확인
kubectl get pod -n tekton-pipelines

2) 텍톤 트리거 (Tekton Triggers) 설치

  • 역할: 외부 이벤트(예: GitHub 푸시, 웹훅 호출)에 응답하여 파이프라인을 자동으로 시작(트리거)하는 역할을 함
  • 설치: 두 개의 YAML 파일을 적용하여 트리거 컨트롤러와 인터셉터(중간 처리기)를 배포함
  • 확인: k get deploy -n tekton-pipelines | grep triggers 명령을 통해 tekton-triggers-controller 등의 트리거 관련 Deployment가 생성되었는지 확인함
# 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

# Tekton Trigger 설치 확인
kubectl get crd | grep triggers # trigger 관련 crd 확인
kubectl get all -n tekton-pipelines

# trigger 관련 deployment 설치 확인
k get deploy -n tekton-pipelines | grep triggers

3) 텍톤 대시보드 (Tekton Dashboard) 설치 및 접속

  • 역할: 웹 인터페이스를 통해 파이프라인, 태스크, 실행 결과 등의 CI/CD 워크플로를 시각적으로 모니터링할 수 있게 해줌
# Tekton Dashboard 설치 : 현재 v0.62.0
kubectl apply -f https://storage.googleapis.com/tekton-releases/dashboard/latest/release.yaml

# Tekton Dashboard 설치 확인
kubectl get crd | grep dashboard # dashboard 관련 crd 확인
kubectl get all -n tekton-pipelines

# Dashboard 관련 deployment 설치 확인
kubectl get deploy -n tekton-pipelines
tekton-dashboard                    1/1     1            1           81s
...

# service 정보 확인
kubectl get svc,ep -n tekton-pipelines tekton-dashboard
kubectl get svc -n tekton-pipelines tekton-dashboard -o yaml | kubectl neat | yq

# 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

# 텍톤 대시보드 접속
open http://localhost:30000   # macOS 경우
웹 브라우저 http://<Ubuntu IP>:30000   # Windows WSL2 Ubuntu 경우

4) 텍톤 CLI (tkn) 설치

tkn은 텍톤 파이프라인을 실행, 관리, 확인하기 위한 명령줄 도구(CLI)입니다.

  • 역할: 개발 및 운영 환경에서 텍톤 리소스를 쉽게 조작할 수 있도록 도와줌
  • 설치: 사용 환경(macOS의 경우 brew, Linux의 경우 PPA를 통한 apt 설치)에 맞춰 CLI 도구를 설치함
  • 확인: tkn version을 실행하여 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


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

 

Create a Hello World Task

- Tekton의 가장 기본적인 구성 요소인 Task를 이해하는 단계

- Task는 CI/CD 워크플로에서 수행되는 단일 작업 단위를 정의함

1) Task 정의 (hello)

  • kubectl apply -f -를 통해 hello라는 이름의 Task를 정의함
  • 이 Task는 하나의 step을 가짐
    • Step 이름: echo
    • 컨테이너 이미지: alpine (매우 가벼운 Linux 이미지)
    • 스크립트: echo "Hello World" 명령을 실행함.

2) Task 실행 및 확인

  • Task 실행: tkn task start --showlog hello 명령으로 Task를 실행
    • Task가 실행되면 TaskRun이라는 객체가 생성됨 (예: hello-run-722sp).
    • --showlog 덕분에 실행 결과를 즉시 터미널에서 볼 수 있음: [echo] Hello World.
  • 실행 원리: TaskRun이 시작되면 쿠버네티스에는 임시 Pod가 생성됨. 이 Pod는 정의된 step-echo 컨테이너 외에도, Tekton이 내부적으로 워크플로를 준비하는 2개의 Init Containers (예: prepare, place-scripts)를 포함
  • 로그 확인: kubectl logs -c <컨테이너 이름> 또는 tkn task logs hello 명령으로 각 컨테이너의 실행 로그를 확인

💡 핵심: Tekton Task는 쿠버네티스 Pod로 실행되며, Task의 각 step은 해당 Pod 내의 컨테이너로 실행됨

#
kubectl explain tasks.tekton.dev
GROUP:      tekton.dev
KIND:       Task
VERSION:    v1

# 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
kubectl get tasks
kubectl get tasks -o yaml | kubectl neat | yq
kubectl get pod

 

# 신규 터미널 : 파드 상태 모니터링
kubectl get pod -w

# tkn CLI로 task 시작 
tkn task start --showlog hello
TaskRun started: hello-run-722sp
Waiting for logs to be available...
[echo] Hello World

# 파드 내, '2개의 init 컨테이너, 1개의 컨테이너' 확인
kubectl describe pod -l tekton.dev/task=hello
...
Init Containers:
  prepare:
    Container ID:  containerd://f3555c826468ae6b6888f3406e42fe8cfa155c9f405941691d8aa9b4d4f3b40d
    Image:         ghcr.io/tektoncd/pipeline/entrypoint-bff0a22da108bc2f16c818c97641a296:v0.70.0@sha256:763d4cd4e362d381b46a5474d3d358e7731d7c13e22ebf632ef530b857521a48
    ...
  place-scripts:
    Container ID:  containerd://959bd6f8d513781ab7c3294b8be1ac92a9765897b5e4b007c94a21e37e49f100
    Image:         cgr.dev/chainguard/busybox@sha256:19f02276bf8dbdd62f069b922f10c65262cc34b710eea26ff928129a736be791
    ...
Containers:
  step-echo:
    Container ID:  containerd://98bef2bfee42c890df0bc0347fecfdebd52613ffda308a8248b359f4507fafc5
    Image:         alpine
    Image ID:      docker.io/library/alpine@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412
...

# 로그 확인
kubectl logs -l tekton.dev/task=hello -c prepare
kubectl logs -l tekton.dev/task=hello -c place-scripts
kubectl logs -l tekton.dev/task=hello -c step-echo
Hello World
혹은
kubectl stern -l tekton.dev/task=hello   # kubectl krew install stern

#
tkn task logs hello
tkn task describe hello
Name:        hello
Namespace:   default

🦶 Steps
 ∙ echo

🗂  Taskruns
NAME              STARTED         DURATION   STATUS
hello-run-722sp   6 minutes ago   19s        Succeeded

#
tkn taskrun logs
tkn taskrun list
NAME              STARTED         DURATION   STATUS
hello-run-722sp   7 minutes ago   19s        Succeeded

# 다음 실습을 위해 taskrun 삭제
kubectl delete taskruns --all

 

Create a Task to Compile and Package an App from Git

-  여러 Task를 연결하여 파이프라인(Pipeline)을 만드는 단계

- Pipeline은 Task들의 실행 순서와 의존성을 정의하는 워크플로

1) PipelineRun 정의와 실패

  • Task 이름: clone-read
  • Task 정의: fetch-source라는 Task를 실행하도록 정의했지만, taskRef: name: git-clone을 참조
  • PipelineRun 실행: kubectl create -f -로 PipelineRun을 실행하면 Failed(CouldntGetTask) 오류가 발생
  • 원인: git-clone Task가 클러스터에 정의되어 있지 않았기 때문

2) Tkn Hub를 이용한 Task 설치 및 재실행

  • Task 설치: tkn hub install task git-clone 명령으로 Tekton Hub에서 공식 git-clone Task를 클러스터에 설치
  • 재실행: PipelineRun을 다시 실행하면 Succeeded
    • 작업 공간 (Workspace): PipelineRun은 shared-data라는 volumeClaimTemplate을 정의하여 PVC (Persistent Volume Claim)를 생성합니다. 이 PVC는 fetch-source Task가 코드를 다운로드하고 다른 Task가 접근할 수 있는 공유 저장소 역할을 함
    • 실행 로그: 로그에서 Successfully cloned https://github.com/tektoncd/website 메시지를 통해 코드가 PVC에 성공적으로 다운로드되었음을 확인
# 파이프라인 파일 작성
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
tkn pipeline describe
kubectl get pipeline
kubectl get pipeline -o yaml | kubectl neat | yq
kubectl get pod

# 파이프라인 실행 : 파이프라인을 인스턴스화하고 실제 값 설정
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-lvkzq   False       CouldntGetTask   2m41s       2m41s

tkn pipelinerun list
NAME                   STARTED         DURATION   STATUS
clone-read-run-lvkzq   3 minutes ago   0s         Failed(CouldntGetTask)

tkn pipelinerun logs clone-read-run-lvkzq -f
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

#
kubectl get tasks
NAME    AGE
hello   13m

# 파이프라인에서 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 확인
kubectl get tasks
kubectl get tasks git-clone -o yaml | kubectl neat | yq

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

#
tkn pipelinerun list
NAME                   STARTED         DURATION   STATUS
clone-read-run-t9dfz   1 minute ago    17s        Succeeded
clone-read-run-lvkzq   7 minutes ago   0s         Failed(CouldntGetTask)

tkn pipelinerun logs clone-read-run-t9dfz -f
...
[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":1760857533.8664637,"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":1760857533.875513,"caller":"git/git.go:215","msg":"Successfully initialized and updated submodules in path /workspace/output/"}
...

# pv,pvc 확인
kubectl get pod,pv,pvc
NAME                                        READY   STATUS      RESTARTS   AGE
pod/clone-read-run-t9dfz-fetch-source-pod   0/1     Completed   0          2m24s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                    STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/pvc-7e5f5abc-801b-4de0-b38e-fc21bfe844c7   1Gi        RWO            Delete           Bound    default/pvc-035aa34a19   standard       <unset>                          2m21s

NAME                                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/pvc-035aa34a19   Bound    pvc-7e5f5abc-801b-4de0-b38e-fc21bfe844c7   1Gi        RWO            standard       <unset>                 2m24s

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

 

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

- Git-Clone Task가 SSH를 사용하여 Private 저장소에 접근하도록 인증 정보를 제공하는 고급 단계

1) 인증 정보 준비 및 Secret 생성

  • SSH 키 인코딩: 로컬 PC의 SSH 개인 키(~/.ssh/id_ed25519)와 Git 서버의 공개 키(known_hosts)를 Base64로 인코딩함
  • Secret 생성: 인코딩된 정보를 담아 git-credentials라는 Secret을 쿠버네티스에 생성함

2) ServiceAccount를 이용한 자격 증명 주입

  • ServiceAccount 생성: build-bot이라는 ServiceAccount를 생성하고, 여기에 git-credentials Secret을 연결합니다.
  • 파이프라인 수정: my-clone-read 파이프라인에 git-credentials라는 Workspace를 추가하고, 이를 fetch-source Task의 ssh-directory에 연결하도록 정의

3) PipelineRun 실행

  • PipelineRun 실행 시 serviceAccountName: build-bot을 지정하고, git-credentials Secret을 Workspace로 매핑하여 Task에 전달함
  • 결과: Task Pod는 build-bot ServiceAccount의 권한으로 git-credentials Secret에 접근하여 SSH 인증을 수행, Private Repository의 코드를 성공적으로 복제

[사전 준비 : github에 private repo 생성 후 sample app 코드 push]

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

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

# 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"

 

Github remote 연결 및 Push

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

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

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

 

SSH 키로 인증 설정

# ssh 키 생성
ssh-keygen -t ed25519 -C "yhshim17@gmail.com"

# 키 보기
ls -l  ~/.ssh | grep ed25519


#
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

# 복사해두기
cat ~/.ssh/id_ed25519.pub

-> GitHub → Settings → SSH and GPG keys → New SSH key → 붙여넣기

 

SSH 연결 방식 사용 테스트

#
ssh -i ~/.ssh/id_ed25519 -T git@github.com
Hi gasida! You've successfully authenticated, but GitHub does not provide shell access.

#
git remote set-url origin git@github.com:<your-username>/my-sample-app.git
git remote set-url origin git@github.com:hayleyshim/my-sample-app.git

#
echo 'cicd study' > readme.md
git add .
git commit -m "add readme.md file"
git push -u origin main

 

 

Tekton Pipelines를 사용하여 git에서 소스 코드를 복제 : 공유 작업 공간에서 소스 코드를 읽는 두 번째 작업

# Git 인증용 SSH 사설키를 base64 인코딩
SSHPK=$(cat ~/.ssh/id_ed25519 | base64 -w0)

# Git 인증서버 known_hosts 값을 base64 인코딩
cat ~/.ssh/known_hosts | grep github
SSHKH=$(ssh-keyscan github.com | grep ecdsa-sha2-nistp256 | base64 -w0)
echo $SSHKH

#
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: git-credentials
data:
  id_rsa: $SSHPK
  known_hosts: $SSHKH
EOF

#
kubectl get secret

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

 

# 파이프라인 파일 작성
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
      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
tkn pipeline describe
kubectl get pipeline
kubectl get pipeline -o yaml | kubectl neat | yq
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


# 파이프라인 실행
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:gasida/my-sample-app.git # 제가 사용하는 것 or 자신의 private repo 지정
EOF

# 결과 확인 : 2개의 step 으로 각기 2개의 파드가 실행됨을 확인
kubectl get pod,pv,pvc

# task 를 실행하는 파드에 service acount 정보 확인
kubectl describe pod | grep 'Service Account'
Service Account:  build-bot
Service Account:  build-bot

 

Containerize an Application Using a Tekton Task and Buildah

- 가장 복잡한 단계로, 코드를 다운로드한 후 Docker 이미지를 빌드하여 Registry에 푸시하는 CI의 핵심 기능을 구현

 

1) Kaniko Task 설치 및 Docker 인증 설정

  • Kaniko Task 설치: tkn hub install task kaniko 명령으로 Kaniko (Container 이미지를 빌드하는 도구) Task를 설치함
  • Docker 인증: Docker Registry (예: Docker Hub)에 이미지를 푸시하기 위한 인증 정보(Username:Password)를 Base64 인코딩하여 config.json 형태의 Secret docker-credentials에 저장
  • ServiceAccount 연결: build-sa ServiceAccount를 생성하고 이 Secret을 연결

2) Pipeline: clone-build-push 정의

Task 이름 TaskRef 역할
fetch-source git-clone Git에서 소스 코드 다운로드
build-push kaniko 다운로드된 코드를 바탕으로 Dockerfile을 읽어 이미지를 빌드하고 Registry에 푸시
  • 두 Task 모두 shared-data Workspace를 통해 소스 코드를 공유
  • build-push Task는 docker-credentials Workspace를 통해 인증 정보 Secret을 전달받아 Registry에 로그인

3) PipelineRun 실행 및 결과

  • PipelineRun 실행: repo-url과 image-reference (docker.io/gasida/docsy:1.0.0) 매개변수를 지정하고 serviceAccountName: build-sa를 사용하여 실행
  • 로그 확인: Kaniko Task의 로그를 통해 다음의 과정을 확인.
    1. step-build-and-push 컨테이너 시작.
    2. Dockerfile을 읽고 이미지를 빌드(RUN apk add git ...).
    3. INFO[0028] Pushed image to 1 destinations 메시지를 통해 Docker Hub에 이미지가 성공적으로 푸시되었음을 확인
# 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을 적용

## Windows WSL Ubuntu 경우
-------------------------------------------------
cat ~/.docker/config.json
{
  "auths": {
    "https://index.docker.io/v1/": {
      "auth": "ZZZZZZZZ=="  # 해당 값 echo -n "username:password" | base64
    }
  }
}

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


## macOS 경우
-------------------------------------------------
echo "https://index.docker.io/v1/" | docker-credential-osxkeychain get | jq
{
  "ServerURL": "https://index.docker.io/v1/",
  "Username": "gasida", -> 요값과 
  "Secret": "****"      -> 요값을 가지고 auth 생성
}

echo -n "gasida:****" | base64
AXDFGHXXCFGFGF

# ~/.docker/config.json 대신 임시 파일 dsh.txt 작성
vi dsh.txt
{
  "auths": {
    "https://index.docker.io/v1/": {
      "auth": "AXDFGHXXCFGFGF"
    }
  }
}
# 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


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


# 결과 확인
kubectl get pod,pv,pvc
tkn pipelinerun logs  clone-build-push-run-4kgjr -f

# 로그 확인
kubectl stern clone-build-push-run-5fn7x-build-push-pod
+ clone-build-push-run-5fn7x-build-push-pod › working-dir-initializer
+ clone-build-push-run-5fn7x-build-push-pod › place-scripts
+ clone-build-push-run-5fn7x-build-push-pod › step-build-and-push
+ clone-build-push-run-5fn7x-build-push-pod › prepare
+ clone-build-push-run-5fn7x-build-push-pod › step-write-url
clone-build-push-run-5fn7x-build-push-pod prepare 2025/10/20 13:29:16 Entrypoint initialization
clone-build-push-run-5fn7x-build-push-pod step-build-and-push 2025/10/20 13:29:18 ERROR failed to get CPU variant os=linux error="getCPUVariant for OS linux: not implemented"
- clone-build-push-run-5fn7x-build-push-pod › working-dir-initializer
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0001] Retrieving image manifest floryn90/hugo:ext-alpine 
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0001] Retrieving image floryn90/hugo:ext-alpine from registry index.docker.io 
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0004] Built cross stage deps: map[]                
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0004] Retrieving image manifest floryn90/hugo:ext-alpine 
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0004] Returning cached image manifest              
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0004] Executing 0 build triggers                   
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0004] Unpacking rootfs as cmd RUN apk add git &&   git config --global --add safe.directory /src requires it. 
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0011] USER root                                    
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0011] cmd: USER                                    
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0011] RUN apk add git &&   git config --global --add safe.directory /src 
clone-build-push-run-5fn7x-build-push-pod place-scripts 2025/10/20 13:29:16 Decoded script /tekton/scripts/script-1-tdjmw
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0011] Taking snapshot of full filesystem...        
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0014] cmd: /bin/sh                                 
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0014] args: [-c apk add git &&   git config --global --add safe.directory /src] 
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0014] util.Lookup returned: &{Uid:0 Gid:0 Username:root Name:root HomeDir:/root} 
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0014] performing slow lookup of group ids for root 
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0014] Running: [/bin/sh -c apk add git &&   git config --global --add safe.directory /src] 
clone-build-push-run-5fn7x-build-push-pod step-build-and-push fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/main/aarch64/APKINDEX.tar.gz
clone-build-push-run-5fn7x-build-push-pod step-build-and-push fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/community/aarch64/APKINDEX.tar.gz
clone-build-push-run-5fn7x-build-push-pod step-build-and-push OK: 88 MiB in 68 packages
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0014] Taking snapshot of full filesystem...        
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0014] Pushing image to docker.io/gasida/docsy:1.0.0 
clone-build-push-run-5fn7x-build-push-pod step-build-and-push INFO[0028] Pushed image to 1 destinations               
- clone-build-push-run-5fn7x-build-push-pod › step-build-and-push
- clone-build-push-run-5fn7x-build-push-pod › place-scripts
- clone-build-push-run-5fn7x-build-push-pod › prepare
clone-build-push-run-5fn7x-build-push-pod step-write-url docker.io/gasida/docsy:1.0.0
- clone-build-push-run-5fn7x-build-push-pod › step-write-url

 

결과적으로, Tekton을 사용하여 Git에서 코드를 가져와 이미지를 빌드하고 Docker Registry에 푸시하는 완전한 CI 워크플로를 성공적으로 구현

최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2025/11   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
글 보관함