본문 바로가기

INFRA/Kubernetes

쿠버네티스_추가 (2, CI/CD실습 )

1. 기본 프로젝트 구성

- 프론트는 next, 백엔드는 spring boot로 구성 

- 소스코드의 원본 깃 레포지토리와 argoCD용 manifests 레포지토리를 구성하여 연동 

 

- manifests 저장소의 yaml파일 (이외에도 업데이트를 위한 helm차트가 존재함)

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: todo-list
  namespace: argocd
spec:
  project: default
  source:
    repoURL: 'https://github.com/ellanelee/manifests-repository.git'
    targetRevision: 'main' 
    path: 'apps/todo-list/backend/helm/todo-list-chart' 
    helm:
      parameters:
        - name: 'backend.image.tag'
          value: 'v1.0' 
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: 'default' 
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
 
 
2. 도커허브에 빌드된 이미지 Push

 

 

 

3. heml구성 

 

1) 차트 생성 

#helm chart생성
helm create <차트이름>

 

2) 생성된 차트의 구성변경  

- 프론트엔드는 CI/CD에서 제외하고 백엔드만 배포

- Backend는 2개의 replicas로 지정 , DB관련 설정은 configmap적용 

- DB는 stateful로 구성하고 3개의 replica지정, headless service 적용

- Backend와 DB의 접속정보는 secret으로 설정 

 

helm의 template를 프로젝트 구조에 맞게 설정변경\

 

3) values.yaml을 구성하고 내용에 맞게 helm 차트내 manifests구성 

# bakend-deployment구성
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "todo-list-chart.fullname" . }}-backend-deployment
  labels:
    {{- include "todo-list-chart.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.backend.replicaCount }}
  selector:
    matchLabels:
      {{- include "todo-list-chart.backend.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "todo-list-chart.backend.selectorLabels" . | nindent 8 }}
    spec:
      containers:
        - name: todo-backend
          image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}"
          # command: ["/bin/sh", "-c"] # 연장
          # args: ["sleep 60"]  #연장 
          ports:
            - containerPort: 8080
          env:
            - name: DB_HOST
              value: {{ .Values.database.host }}
            - name: CORS_ALLOWED_ORIGINS
              value: "{{ .Values.backend.cors_allowed_origins }}"
          envFrom:
            - configMapRef:
                name: {{ include "todo-list-chart.fullname" . }}-db-configmap
            - secretRef:
                name: {{ include "todo-list-chart.fullname" . }}-db-secret

#backend-service구성 
apiVersion: v1
kind: Service
metadata:
  name: {{ include "todo-list-chart.fullname" . }}-backend-service
  labels:
    {{- include "todo-list-chart.labels" . | nindent 4 }}
spec:
  type: {{ .Values.backend.service.type }}
  ports:
  - port: {{ .Values.backend.service.port }}
    targetPort: {{ .Values.backend.service.targetPort }}
    {{- if and (eq .Values.backend.service.type "NodePort") .Values.backend.service.nodePort.enabled }}
    nodePort: {{ .Values.backend.service.nodePort.port }}
    {{- end }}
  selector:
    {{- include "todo-list-chart.backend.selectorLabels" . | nindent 6 }}
    
# db configmap구성 
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "todo-list-chart.fullname" . }}-db-configmap
  labels: {{- include "todo-list-chart.labels" . | nindent 4 }}
data:
  MYSQL_DATABASE: {{ .Values.database.name }}
  MYSQL_USER: {{ .Values.database.user }}
  
#Secret구성 
apiVersion: v1
kind: Secret
metadata:
  name: {{ include "todo-list-chart.fullname" . }}-db-secret
  labels: {{- include "todo-list-chart.labels" . | nindent 4 }}
type: Opaque
stringData:
  MYSQL_ROOT_PASSWORD: {{ .Values.database.rootPassword | quote }}
  MYSQL_PASSWORD: {{ .Values.database.password | quote }}

#db-headless-service구성
apiVersion: v1
kind: Service
metadata:
  name: todo-headless-db-service
  labels: {{- include "todo-list-chart.labels" . | nindent 4 }}
spec:
  ports:
  - port: 3306
  selector:
    app.kubernetes.io/name: {{ include "todo-list-chart.name" . }}-db
    app.kubernetes.io/instance: {{ .Release.Name }}
  clusterIP: None
  
#db-statefulset구성
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ include "todo-list-chart.fullname" . }}-db-statefulset
  labels:
    {{- include "todo-list-chart.labels" . | nindent 4 }}
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ include "todo-list-chart.name" . }}-db
      app.kubernetes.io/instance: {{ .Release.Name }}
  serviceName: "todo-headless-db-service" 
  replicas: 1
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ include "todo-list-chart.name" . }}-db
        app.kubernetes.io/instance: {{ .Release.Name }}
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
        envFrom:
        - secretRef:
            name: {{ include "todo-list-chart.fullname" . }}-db-secret
        - configMapRef:
            name: {{ include "todo-list-chart.fullname" . }}-db-configmap
        volumeMounts:
        - name: todo-persistent-storage
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: todo-persistent-storage
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

 

4.  GitHub workflow구성 

- 깃헙의 workflow구성 전에 DB접근과 manifests저장소 접근을 위해 해당 레포지토리에 action key설정 필요 

name: CI for Todo List Backend

# 워크플로우가 실행될 조건 (Triggers)
on:
  push:
    branches: ["main"]
    paths:
      - "apps/todo-list/backend/**"

# 실행될 작업(Job)들
jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      # 1. 소스 코드 체크아웃
      - name: Checkout source code
        uses: actions/checkout@v4

      # 2. JDK 17 설정
      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: "17"
          distribution: "temurin"

      # 3. Gradle 캐싱. #의존성을 처리하면서 오래 걸리므로 일부 caching
      - name: Gradle Caching
        uses: actions/cache@v4
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      # 4. gradlew에 실행 권한 부여
      - name: Grant execute permission for gradlew
        run: chmod +x gradlew
        working-directory: ./apps/todo-list/backend

      # 5. Gradle로 프로젝트 빌드
      - name: Build with Gradle
        run: ./gradlew build -x test
        working-directory: ./apps/todo-list/backend

      # 6. Docker Hub에 로그인
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}. # 프로젝트의 settings에서 설정
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      # 7. Docker 이미지 빌드 및 푸시
      - name: Build and push Docker image
        id: build-push
        uses: docker/build-push-action@v5
        with:
          context: ./apps/todo-list/backend
          file: ./apps/todo-list/backend/Dockerfile.prod
          push: true
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/k8s-labs-todo-backend:${{ github.sha }}

      # 8. 생성된 이미지 태그 출력
      - name: Print image tag
        run: echo "Image tagged with:${{ secrets.DOCKERHUB_USERNAME }}/k8s-labs-todo-backend:${{ github.sha }}"



5.  argoCD 설치

# namespace만들기 
kubectl create namespace argocd
namespace/argocd created

# 내용받아오기
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Argo cd 서버구동
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'

#구동 및 접속 위치 확인 
minikube service argocd-server -n argocd
|-----------|---------------|-------------|---------------------------|
| NAMESPACE |     NAME      | TARGET PORT |            URL            |
|-----------|---------------|-------------|---------------------------|
| argocd    | argocd-server | http/80     | http://192.168.49.2:30976 |
|           |               | https/443   | http://192.168.49.2:31686 |
|-----------|---------------|-------------|---------------------------|
🏃  Starting tunnel for service argocd-server.
|-----------|---------------|-------------|------------------------|
| NAMESPACE |     NAME      | TARGET PORT |          URL           |
|-----------|---------------|-------------|------------------------|
| argocd    | argocd-server |             | http://127.0.0.1:50627 |
|           |               |             | http://127.0.0.1:50628 |
|-----------|---------------|-------------|------------------------|
[argocd argocd-server  http://127.0.0.1:50627
http://127.0.0.1:50628]
❗  Because you are using a Docker driver on darwin, the terminal needs to be open to run it.

#비번확인 
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

 - localhost:50627 (admin, 확인된 비번으로 로그인 )에서 App구동 정보 입력하여 구동 

 

 

6. ArgoCD에서 배포 확인 

k get pods
NAME                                                            READY   STATUS    RESTARTS   AGE
todo-list-todo-list-chart-backend-deployment-588688b7b6-72hnv   1/1     Running   0          62s
todo-list-todo-list-chart-backend-deployment-588688b7b6-qrgv5   1/1     Running   0          72s
todo-list-todo-list-chart-db-statefulset-0                      1/1     Running   0          11h
(⎈|minikube:default) elena@ijieun-ui-MacBookAir k8s-labs-todo-list-manifests % k 
get deployments
NAME                                           READY   UP-TO-DATE   AVAILABLE   AGE
todo-list-todo-list-chart-backend-deployment   2/2     2            2           13h
(⎈|minikube:default) elena@ijieun-ui-MacBookAir k8s-labs-todo-list-manifests % k get all
NAME                                                                READY   STATUS    RESTARTS      AGE
pod/todo-list-todo-list-chart-backend-deployment-588688b7b6-72hnv   1/1     Running   2 (28s ago)   5h7m
pod/todo-list-todo-list-chart-backend-deployment-588688b7b6-qrgv5   1/1     Running   2 (29s ago)   5h7m
pod/todo-list-todo-list-chart-db-statefulset-0                      1/1     Running   0             16h

NAME                                                TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/kubernetes                                  ClusterIP   10.96.0.1       <none>        443/TCP          5d7h
service/todo-headless-db-service                    ClusterIP   None            <none>        3306/TCP         16h
service/todo-list-todo-list-chart-backend-service   NodePort    10.96.246.214   <none>        8080:30001/TCP   18h

NAME                                                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/todo-list-todo-list-chart-backend-deployment   2/2     2            2           18h

NAME                                                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/todo-list-todo-list-chart-backend-deployment-5468f7f47c   0         0         0       7h1m
replicaset.apps/todo-list-todo-list-chart-backend-deployment-54c9675bb9   0         0         0       6h17m
replicaset.apps/todo-list-todo-list-chart-backend-deployment-55cf88ddfc   0         0         0       6h13m
replicaset.apps/todo-list-todo-list-chart-backend-deployment-588688b7b6   2         2         2       5h7m
replicaset.apps/todo-list-todo-list-chart-backend-deployment-58d84768cb   0         0         0       6h29m
replicaset.apps/todo-list-todo-list-chart-backend-deployment-6dd9cd858f   0         0         0       17h
replicaset.apps/todo-list-todo-list-chart-backend-deployment-75fc864597   0         0         0       6h26m
replicaset.apps/todo-list-todo-list-chart-backend-deployment-7ccb674bdc   0         0         0       17h
replicaset.apps/todo-list-todo-list-chart-backend-deployment-86f47f6cf4   0         0         0       5h11m
replicaset.apps/todo-list-todo-list-chart-backend-deployment-88977bfb6    0         0         0       5h37m
replicaset.apps/todo-list-todo-list-chart-backend-deployment-dc48696fd    0         0         0       16h

NAME                                                        READY   AGE
statefulset.apps/todo-list-todo-list-chart-db-statefulset   1/1     16h

 

-----------------------------------------------------------------------

2개의 간단한 프로젝트를 구현해서 CI/CD로 구성하는 실습을 진행했다. 

Local의 minikube를 이용했으며, 1개의 프로젝트를 배포하는데 성공했다. 해당 프로젝트는 상당히 간단한 구조임에도 helm구성이나 배포를 스스로 해보는건 처음이라 어려웠다. 마지막의 pod생성 기록에 desired 0, current 0인 것들은 배포되었다가 오류로 인해 terminate된 pod들의 History이다. 사실 실패해서 그늘 속으로 사라진 파드는 더 많은데 .. 백업을 위해 10개까지만 보관된다고 한다. 

 

arm구성의 기기를 사용하는데, github action은 amd기준의 이미지를 생성하므로 이미지를 불러오면서부터 에러가 났다. 원인을 찾으면서 ( 컨테이너 구성이 하드웨어에 영향을 받지 않는다고 배웠으므로 ) 상당한 시간을 소요했다.

helm 처음 작성해본지라 indent의 문제라던가, headless service db-service 한꺼번에 이용하면서 설정이 맞지 않아 원인을 찾고 해결하기 위해 db-service를 삭제하고 stateful리소스를 직접 host와 연결해줬는데 바람직한 설정인지는 모르겠다. 또한, persistent volume설정으로 인해 db설정의 초기치가 잡히면 수정값을 주어도 반영되지 않아(초기치는 재설정 되지 않는다는데 맞는 얘기인지..) db 구성과 로그까지 확인해서 values.yaml 수정한 후에 완료할수 있었다.

 

또 하나의 프로젝트는 O-Auth와 외부 저장소를 이용한 조금 더 복잡한 구조인데, DB연결 에러가 나고 있어 오류 로그를 살펴보고 있는 중이다. 쿠버 동아리에서 사전에 흟어주신 내용이 아니었다면 진행하기가 몹시 어려웠을텐데 사전에 여러가지 지식을 알려주신 김*님에게 감사를 전하고 싶다.   

'INFRA > Kubernetes' 카테고리의 다른 글

쿠버네티스_추가 (1)  (0) 2025.08.10