본문 바로가기
CI&CD

[CI/CD] Tekton + ArgoCD로 만드는 CI/CD 파이프라인

by interlude-3 2025. 11. 1.

1. CI/CD란? (Continuous Integration / Continuous Delivery,Deployment)


CI/CD는 **지속적 통합(Continuous Integration)**과 **지속적 배포(Continuous Delivery 또는 Continuous Deployment)**를 합친 용어로, 애플리케이션 개발부터 배포까지의 전 과정을 자동화하여 소프트웨어를 더 빠르고, 안정적이며, 빈번하게 사용자에게 제공하는 개발 방법론 또는 문화입니다. DevOps(개발과 운영의 통합)의 핵심적인 실천 방안 중 하나입니다.

 

* 지속적 통합 (Continuous Integration, CI)

CI는 개발자들이 작성한 코드를 주기적으로 **공유 저장소(예: Git)**에 **통합(Merge)**하는 프로세스와, 통합 후 자동화된 테스트 및 빌드를 수행하는 과정을 의미합니다.

  • 목표: 코드 충돌을 최소화하고, 버그를 개발 초기에 신속하게 발견하고 수정하여 통합 문제로 인한 시간 낭비를 줄이는 것입니다.
  • 주요 단계:
    1. Code (코드 작성): 개발자가 코드를 작성하고 Git 등의 버전 관리 시스템에 커밋(Commit)합니다.
    2. Build (빌드): 커밋된 코드를 가져와 실행 가능한 아티팩트(예: JAR, WAR, Docker Image)로 만듭니다.
    3. Test (테스트): 유닛 테스트(Unit Test), 통합 테스트(Integration Test) 등을 자동으로 수행하여 코드가 제대로 작동하는지 검증합니다.

 

* 지속적 전달 (Continuous Delivery, CD) vs. 지속적 배포 (Continuous Deployment, CD)

CI 과정을 거쳐 검증된 소프트웨어를 사용자에게 제공하는 과정을 CD라고 합니다. CD에는 두 가지 개념이 있습니다.

  • 지속적 전달 (Continuous Delivery):
    • 빌드되고 테스트를 통과한 소프트웨어 변경 사항이 배포 가능한 상태준비되어 **저장소(Artifact Repository)**에 보관됩니다.
    • **운영 환경(Production)**으로의 최종 배포는 수동적인 결정 (예: 버튼 클릭)에 의해 이루어집니다.
    • 장점: 최종 배포 시점을 유연하게 결정할 수 있어, 비즈니스적인 판단(예: 마케팅 일정)에 맞춰 서비스를 출시할 수 있습니다.
  • 지속적 배포 (Continuous Deployment):
    • 지속적 전달의 개념을 확장하여, 빌드 및 테스트를 통과한 모든 변경 사항이 자동적으로 운영 환경에 배포됩니다. 사람의 개입이 최소화됩니다.
    • 장점: 개발 속도가 극대화되고, 고객 피드백을 매우 빠르게 서비스에 반영할 수 있습니다. 높은 수준의 자동화와 신뢰할 수 있는 테스트 환경이 필수입니다.

 

2. 컨테이너 이미지 빌드 방식 비교: DinD / DooD / Daemonless


CI/CD 파이프라인에서 애플리케이션을 **도커(Docker)**와 같은 컨테이너 이미지로 만드는 것은 필수적인 단계입니다. 이때, CI 환경(대부분 컨테이너) 내에서 또 다른 컨테이너 이미지를 빌드하는 방식에 따라 보안효율성에 차이가 발생합니다.

방식 DinD (Docker in Docker) DooD (Docker out of Docker) Daemonless (Kaniko, Buildah 등)
개념 컨테이너 안에 별도의 도커 데몬을 실행하여 이미지를 빌드합니다. 호스트의 도커 데몬을 컨테이너와 공유하여 이미지를 빌드합니다. 도커 데몬 없이 독립적인 프로세스로 컨테이너 이미지를 빌드합니다.
보안 매우 취약 (DinD 컨테이너에 --privileged 옵션 필요). 호스트 시스템의 모든 권한을 가질 수 있어, 보안 사고 발생 시 호스트 전체가 위험해질 수 있습니다. 비교적 취약 (도커 소켓 공유). 호스트의 도커 데몬을 오용할 수 있어 보안 위험이 존재합니다. 가장 안전. 호스트의 도커 데몬이나 소켓에 접근하지 않으므로, 컨테이너 격리성이 유지되어 보안 위험이 낮습니다.
격리성 호스트와 격리된 독립적인 도커 환경 구축이 가능합니다. (하지만 --privileged 옵션 때문에 무의미해질 수 있음) 호스트의 도커 데몬에 의존하므로 격리성이 낮습니다. 완벽하게 격리된 환경에서 빌드되어 격리성이 높습니다.
복잡성 별도의 도커 데몬 실행/관리 필요, 복잡합니다. 소켓 공유만 하면 되므로 비교적 간단합니다. 별도의 도구(Kaniko, Buildah 등)에 대한 학습이 필요하지만, 설정 자체는 깔끔합니다.
성능 컨테이너 내의 가상화 오버헤드 때문에 느립니다. 호스트의 데몬을 사용하므로 가장 빠릅니다. 오버헤드가 적어 매우 빠르거나 DinD보다 빠릅니다.
주요 도구 Docker (자체) Docker (Volume Mount) Kaniko (Google), Buildah (Red Hat)

 

DinD vs DooD (https://p373r.net/study/20250424-dind-vs-dood/)

 

** Daemonless 방식이 선호되는 이유

DinD와 DooD 방식은 도커의 핵심 철학인 격리성(Isolation)을 무너뜨릴 수 있다는 치명적인 보안 문제를 가지고 있습니다. 특히 쿠버네티스(Kubernetes)와 같은 클라우드 네이티브 환경에서는 각 워크로드가 격리되는 것이 매우 중요하며, Daemonless 빌드 방식인 Kaniko는 도커 데몬에 의존하지 않고 이미지 빌드를 수행하여 이러한 보안 문제와 복잡성을 해결합니다. 따라서 보안을 최우선으로 하는 클라우드 네이티브 CI/CD 환경에서는 Kaniko와 같은 Daemonless 방식이 가장 권장됩니다.

 

3. 실습 아키텍처 이해: Tekton 파이프라인 & Argo CD


최신 클라우드 네이티브 CI/CD 환경에서는 쿠버네티스(Kubernetes) 위에서 CI(빌드/테스트)와 CD(배포)를 처리하는 도구들을 사용하며, 이번 실습 아키텍처는 TektonArgo CD를 결합하여 GitOps 원칙을 따릅니다.

 

1) Tekton 파이프라인 (CI 담당)

Tekton쿠버네티스 위에서 실행되도록 설계된 강력하고 유연한 CI/CD 파이프라인 프레임워크입니다.

 

* Tekton의 장점

  • 클라우드 네이티브: 쿠버네티스의 CRD(Custom Resource Definition)를 사용하여 파이프라인을 정의하고, 모든 작업은 쿠버네티스 파드(Pod) 내에서 실행됩니다. 이는 확장성이식성이 뛰어남을 의미합니다.
  • 재사용성 및 모듈화:
    • Task: 파이프라인을 구성하는 가장 작은 단위 (예: 소스 코드 클론, 이미지 빌드, 테스트 실행). 각 Task는 독립적으로 정의되어 재사용이 가능합니다.
    • Pipeline: 여러 Task를 순서 또는 DAG(Directed Acyclic Graph) 형태로 연결하여 전체 CI 워크플로우를 정의합니다.
  • 격리성: 각 TaskRun은 새로운 파드에서 실행되므로, 작업 환경이 완전히 격리되어 안전하고 일관된 빌드를 보장합니다.

* Tekton의 동작 방식

  1. Code Commit: 개발자가 Git 저장소(Private Repo)에 코드를 푸시(Push)합니다.
  2. Trigger: Tekton의 Trigger 컴포넌트가 이 Git 이벤트를 감지합니다.
  3. Pipeline 실행: Trigger는 미리 정의된 PipelineRun을 생성하여 Tekton 파이프라인을 시작합니다.
  4. Task 실행: 파이프라인은 순서대로 정의된 Task(예: git-clone Task, kaniko-build Task)들을 실행합니다. 각 Task는 쿠버네티스 파드로 실행됩니다.
  5. Artifact 생성:
    • git-clone Task가 소스 코드를 가져옵니다.
    • kaniko-build Task (Daemonless 방식)가 소스 코드를 사용하여 도커 이미지를 빌드하고 **레지스트리(Registry)**에 푸시합니다.
  6. GitOps Repo 업데이트: 빌드된 이미지의 태그(Tag) 정보를 **배포 설정 파일(Manifest)**이 있는 GitOps 저장소자동으로 업데이트합니다. (이것이 Argo CD에게 배포를 시작하라고 알려주는 신호가 됩니다.)

 

2) Argo CD (CD 담당)

Argo CDGitOps 원칙을 구현하는 **선언적(Declarative)**이고 자동화된 CD 도구입니다.

 

* GitOps란?

GitOps는 Git을 **진실의 단일 출처(Single Source of Truth)**로 사용하여 인프라 및 애플리케이션의 상태를 관리하는 방법론입니다. 애플리케이션의 최종 상태를 YAML과 같은 선언적인 파일로 Git에 저장하고, Git에 저장된 상태와 실제 쿠버네티스 클러스터의 상태가 일치하도록 도구가 지속적으로 동기화합니다.

 

* Argo CD의 장점

  • GitOps 원칙 준수: 배포의 모든 변경 사항이 Git에 기록되므로 변경 이력 추적이 용이하고, 롤백이 간단하며, **감사(Audit)**에 유리합니다.
  • 선언적 관리: 쿠버네티스 Manifest(YAML 파일)에 원하는 상태(Desired State)를 선언하면, Argo CD가 현재 상태(Actual State)를 확인하여 원하는 상태와 일치하도록 자동으로 배포(동기화)합니다.
  • 자동 동기화 및 Drift 감지: GitOps 저장소의 변경 사항을 지속적으로 감시하고, 클러스터의 상태가 Git의 상태와 다를 경우(Drift) 자동으로 수정하거나 알림을 보냅니다.
  • 다양한 배포 전략 지원: Blue/Green, Canary 등 복잡한 배포 전략을 쉽게 구현할 수 있도록 Argo Rollouts과 같은 부가 컴포넌트와 잘 연동됩니다.
  • 직관적인 UI: 배포된 애플리케이션의 상태, 히스토리, 차이점(Diff) 등을 시각적으로 확인할 수 있는 대시보드를 제공합니다.

* Argo CD의 동작 방식

  1. 감시 (Watch): Argo CD는 GitOps 저장소를 지속적으로 감시합니다.
  2. 변경 감지: Tekton 파이프라인에 의해 이미지 태그가 업데이트되면(Git Commit 발생), Argo CD는 Git의 Desired State가 변경된 것을 감지합니다.
  3. Diff 확인: Argo CD는 Git에 명시된 Desired State와 실제 쿠버네티스 클러스터의 Actual State를 비교합니다.
  4. 동기화 (Sync): 차이점(Diff)이 발견되면, Argo CD는 자동으로 또는 수동 승인을 거쳐 쿠버네티스 클러스터에 변경 사항을 적용합니다. (YAML 파일에 명시된 대로 새로운 이미지 태그를 가진 Pod를 배포합니다.)
  5. Health Check: 배포된 애플리케이션의 상태(Pod, Service 등)를 모니터링하여 정상적으로 배포되었는지 확인합니다.

 

4. 실습 진행 (github + Tekton + ArgoCD)


1) kind cluster 배포

$ kind create cluster --name myk8s --image kindest/node:v1.32.8 --config - <<EOF

a> kind: Cluster
> apiVersion: kind.x-k8s.io/v1alpha4
orking:> networking:
>   apiServerAddress: "0.0.0.0"
> nodes:
> - role: control-plane
tMappin>   extraPortMappings:
>   - containerPort: 30000
>     hostPort: 30000
>   - containerPort: 30001
>     hostPort: 30001
ntainerP>   - containerPort: 30002
>     hostPort: 30002
 30003
 >   - containerPort: 30003
>     hostPort: 30003
orker
E> - role: worker
> EOF
Creating cluster "myk8s" ...
 ✓ Ensuring node image (kindest/node:v1.32.8) 🖼
 ✓ Preparing nodes 📦 📦  
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
 ✓ Joining worker nodes 🚜 
Set kubectl context to "kind-myk8s"
You can now use your cluster with:

kubectl cluster-info --context kind-myk8s

Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂

$ kind get nodes --name myk8s
myk8s-worker
myk8s-control-plane

$ kubens default
✔ Active namespace is "default"

 

2) github repo 준비

2-1. test code 작성

2-2. github에 작업 서버 SSH 등록

2-3. [dev] github private repo 생성 및 dev app PUSH

git init
git add .
git config --global user.email "<email>"
git config --global user.name "<name>"
git commit -m "cicd test app"
git branch -M main
git remote add origin git@github.com:ria-choi/dev-lab1.git
git push -u origin main

2-4. [devops] github private repo 생성 및 devops app PUSH

git init
git add .
git commit -m "cicd test app"
git branch -M main
git remote add origin git@github.com:ria-choi/devops-lab1.git
git push -u origin main

 

3) tekton으로 CI 구현

3-1. tekton 설치

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

3-2. Secret & ServiceAccount 만들기

  • Docker Hub 푸시 권한 Secret 생성
kubectl create secret docker-registry regcred \
--docker-server=index.docker.io \
--docker-username=<YOUR_DOCKERHUB_ID> \
--docker-password=<YOUR_DOCKERHUB_PASSWORD> \
--docker-email=<YOUR_DOCKERHUB_EMAIL>\
-n default
  • devops 저장소에 git push 권한 Secret 생성
kubectl create secret generic git-credentials \
--from-literal=username=<YOUR_GITHUB_NAME>\
--from-literal=password=<YOUR_GITHUB_PAT> \
-n default
  • Secret 참조 ServiceAccount 생성
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ci-bot
  namespace: default
secrets:
  - name: regcred
  - name: git-credentials
imagePullSecrets:
  - name: regcred

3-3.  tekton test 정의

더보기

(⎈|kind-myk8s:default) riachoi@DESKTOP-HUU6SC7:~/cicd_study/cicd-labs/tekton$ cat task-pipeline/clone-source.yaml 
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: clone-source
  namespace: default
spec:
  params:
    - name: app_repo_url        # 예: github.com/ria-choi/dev-lab1.git
      type: string
    - name: app_revision        # 예: main
      type: string
      default: main
  workspaces:
    - name: source              # /workspace/source 로 마운트됨
  results:
    - name: commit-sha
      description: "short git commit SHA for this build"
  steps:
    - name: clone
      image: alpine/git:latest
      workingDir: /workspace/source
      env:
        - name: GIT_USER
          valueFrom:
            secretKeyRef:
              name: git-credentials
              key: username
        - name: GIT_PASS
          valueFrom:
            secretKeyRef:
              name: git-credentials
              key: password
      script: |
        #!/bin/sh
        set -eu

        echo "[clone-source] cleanup workspace"
        rm -rf ./* ./.git

        echo "[clone-source] git clone"
        git clone --branch "$(params.app_revision)" \
          "https://${GIT_USER}:${GIT_PASS}@$(params.app_repo_url)" .

        echo "[clone-source] extract short sha"
        SHORT_SHA="$(git rev-parse --short HEAD)"
        echo -n "${SHORT_SHA}" > /tekton/results/commit-sha

(⎈|kind-myk8s:default) riachoi@DESKTOP-HUU6SC7:~/cicd_study/cicd-labs/tekton$ cat task-pipeline/build-and-push-image-buildah.yaml 
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: build-and-push-image-buildah
  namespace: default
spec:
  params:
    - name: image_url      # 예: docker.io/riachoi/dev-lab1
      type: string
    - name: image_tag      # 예: 커밋 short SHA
      type: string
  workspaces:
    - name: source         # /workspace/source
  steps:
    - name: build-and-push
      image: quay.io/buildah/stable:v1.37
      workingDir: /workspace/source
      securityContext:
        privileged: true
      env:
        - name: REG_USERNAME
          valueFrom:
            secretKeyRef:
              name: regcred-basic
              key: username
        - name: REG_PASSWORD
          valueFrom:
            secretKeyRef:
              name: regcred-basic
              key: password
      script: |
        #!/bin/sh
        set -eux

        FULL_IMAGE="$(params.image_url):$(params.image_tag)"

        # build
        buildah bud -t "$FULL_IMAGE" .

        # push
        buildah login -u "$REG_USERNAME" -p "$REG_PASSWORD" docker.io
        buildah push "$FULL_IMAGE"

(⎈|kind-myk8s:default) riachoi@DESKTOP-HUU6SC7:~/cicd_study/cicd-labs/tekton$ cat task-pipeline/update-manifest.yaml 
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: update-manifest
  namespace: default
spec:
  params:
    - name: devops_repo_url        # github.com/ria-choi/devops-lab1.git
      type: string
    - name: image_url              # docker.io/riachoi/dev-lab1
      type: string
    - name: image_tag              # 커밋 short SHA
      type: string
    - name: commit_user
      type: string
      default: "ria-choi"
    - name: commit_email
      type: string
      default: "maria456852@gmail.com"
  steps:
    - name: update-and-push
      image: alpine/git:latest
      workingDir: /workspace
      env:
        - name: GIT_USER
          valueFrom:
            secretKeyRef:
              name: git-credentials
              key: username
        - name: GIT_PASS
          valueFrom:
            secretKeyRef:
              name: git-credentials
              key: password
        - name: DEVOPS_REPO
          value: "$(params.devops_repo_url)"
        - name: IMAGE_URL
          value: "$(params.image_url)"
        - name: IMAGE_TAG
          value: "$(params.image_tag)"
        - name: COMMIT_USER
          value: "$(params.commit_user)"
        - name: COMMIT_EMAIL
          value: "$(params.commit_email)"
      script: |
        #!/bin/sh
        set -eu

        echo "[update-manifest] clone devops repo"
        rm -rf devops
        git clone "https://${GIT_USER}:${GIT_PASS}@${DEVOPS_REPO}" devops

        cd devops

        echo "[update-manifest] git config"
        git config user.name "${COMMIT_USER}"
        git config user.email "${COMMIT_EMAIL}"

        TARGET_FILE="overlays/dev/patch-image.yaml"
        NEW_IMAGE="${IMAGE_URL}:${IMAGE_TAG}"

        echo "[update-manifest] patch ${TARGET_FILE} -> ${NEW_IMAGE}"
        sed -i "s|^\([[:space:]]*image:[[:space:]]*\).*|\1${NEW_IMAGE}|" "${TARGET_FILE}"

        git add "${TARGET_FILE}"

        if git diff --cached --quiet; then
          echo "no change, skip commit"
          exit 0
        fi

        git commit -m "ci: update image to ${NEW_IMAGE}"
        git push origin main

(⎈|kind-myk8s:default) riachoi@DESKTOP-HUU6SC7:~/cicd_study/cicd-labs/tekton$ cat task-pipeline/pipeline.yaml 
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: build-and-update-deploy
  namespace: default
spec:
  workspaces:
    - name: shared-workspace

  params:
    - name: app_repo_url           # github.com/ria-choi/dev-lab1.git
      type: string
    - name: app_revision           # main
      type: string
      default: main
    - name: image_url              # docker.io/riachoi/dev-lab1
      type: string
    - name: devops_repo_url        # github.com/ria-choi/devops-lab1.git
      type: string

  tasks:
    - name: clone-source
      taskRef:
        name: clone-source
      workspaces:
        - name: source
          workspace: shared-workspace
      params:
        - name: app_repo_url
          value: $(params.app_repo_url)
        - name: app_revision
          value: $(params.app_revision)

    - name: build-push
      runAfter:
        - clone-source
      taskRef:
        name: build-and-push-image-buildah
      workspaces:
        - name: source
          workspace: shared-workspace
      params:
        - name: image_url
          value: $(params.image_url)
        - name: image_tag
          value: $(tasks.clone-source.results.commit-sha)

    - name: update-manifest
      runAfter:
        - build-push
      taskRef:
        name: update-manifest
      params:
        - name: devops_repo_url
          value: $(params.devops_repo_url)
        - name: image_url
          value: $(params.image_url)
        - name: image_tag
          value: $(tasks.clone-source.results.commit-sha)
        - name: commit_user
          value: "ria-choi"
        - name: commit_email
          value: "maria456852@gmail.com"

$ ls
build-and-push-image-buildah.yaml  clone-source.yaml  pipeline.yaml  update-manifest.yaml

$ kubectl apply -f .

 

4) ArgoCD로 CD 구현

4-1. argocd 설치

$ kubectl create namespace argocd
namespace/argocd created

$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

4-2. application 배포

더보기

(⎈|kind-myk8s:default) riachoi@DESKTOP-HUU6SC7:~/cicd_study/cicd-labs/argocd$ cat application.yaml 
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: test-dev
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://ria-choi: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@github.com/ria-choi/devops-lab1.git
    targetRevision: main
    path: overlays/dev
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

$ kubectl create -f argocd.yaml

$ kubectl get application -n argocd
NAME             SYNC STATUS   HEALTH STATUS
test-dev   Unknown       Healthy

$ kubectl get pods
NAME                                      READY   STATUS      RESTARTS   AGE
sample-app-f54d9c6c8-frffd                1/1     Running     0          7m13s

 

5) 동작 테스트

5-1. pipelinerun 배포

apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  name: sample-ci-run-sha-2
  namespace: default
spec:
  pipelineRef:
    name: build-and-update-deploy
  params:
    - name: app_repo_url
      value: github.com/ria-choi/dev-lab1.git
    - name: app_revision
      value: main
    - name: image_url
      value: docker.io/riachoi/dev-lab1
    - name: devops_repo_url
      value: github.com/ria-choi/devops-lab1.git
  workspaces:
    - name: shared-workspace
      volumeClaimTemplate:
        metadata:
          name: pr-workspace-sample-ci-run-sha-2
        spec:
          accessModes: ["ReadWriteOnce"]
          resources:
            requests:
              storage: 1Gi

5-2. image tag 자동 변경 후 pod 재시작 확인

$ kubectl get pods
NAME                                      READY   STATUS      RESTARTS   AGE
sample-app-f54d9c6c8-frffd                1/1     Running     0          12m
sample-ci-run-sha-2-build-push-pod        0/1     Completed   0          17m
sample-ci-run-sha-2-clone-source-pod      0/1     Completed   0          17m
sample-ci-run-sha-2-update-manifest-pod   0/1     Completed   0          16m

$ kubectl describe pod sample-app-f54d9c6c8-frffd

Name:             sample-app-f54d9c6c8-frffd
Namespace:        default
Priority:         0
Service Account:  default
Node:             myk8s-worker/172.18.0.2
Start Time:       Sun, 02 Nov 2025 10:10:22 +0900
Labels:           app=sample-app
                  pod-template-hash=f54d9c6c8
Annotations:      <none>
Status:           Running
IP:               10.244.1.36
IPs:
  IP:           10.244.1.36
Controlled By:  ReplicaSet/sample-app-f54d9c6c8
Containers:
  sample-app:
    Container ID:   containerd://6e1e916bab633029731b17a47f893dec7c252f75f6a6940c028a6bc44344b26c
    Image:          docker.io/riachoi/dev-lab1:628facd
    Image ID:       docker.io/riachoi/dev-lab1@sha256:63682516f62406e7b9748def33c7e05f2c615684d1515b9324647a835bcd6898
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Sun, 02 Nov 2025 10:10:31 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-pcglj (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True
  Initialized                 True
  Ready                       True
  ContainersReady             True
  PodScheduled                True
Volumes:
  kube-api-access-pcglj:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  17m   default-scheduler  Successfully assigned default/sample-app-f54d9c6c8-frffd to myk8s-worker
  Normal  Pulling    17m   kubelet            Pulling image "docker.io/riachoi/dev-lab1:628facd"
  Normal  Pulled     17m   kubelet            Successfully pulled image "docker.io/riachoi/dev-lab1:628facd" in 8.302s (8.302s including waiting). Image size: 12906907 bytes.
  Normal  Created    17m   kubelet            Created container: sample-app
  Normal  Started    17m   kubelet            Started container sample-app

 

 

** 추후 dev-lab1 환경에 Trigger 기능 적용 예정

 

5. 운영 확장 시 고려해야 할 부분 (안전성 확보)


CI/CD를 실제 서비스 운영으로 확장할 때, 보안과 안정성을 위해 수립해야 할 정책들입니다.

 

1) 보안 / Secret 관리: 권한 분리 원칙

  • 문제: 모든 저장소(Git), 레지스트리, 클러스터가 프라이빗이므로 접근을 위한 자격증명(Secret) 관리가 필수입니다.
  • 해결책 (권한 분리):
    • Tekton (CI): 코드 읽기, 이미지 푸시, GitOps Repo 쓰기 권한만 가집니다.
    • Argo CD (CD): GitOps Repo 읽기, 클러스터 배포 권한만 가집니다.
    • 철학: CI는 코드/이미지만, CD는 배포만 담당하게 하여, 어느 한쪽이 해킹당해도 전체 시스템에 대한 권한을 갖지 못하게 설계해야 안전합니다.

 

2) 이미지 태그 전략: 불변성(Immutable) 확보

  • 문제: latest처럼 내용이 바뀌는 가변적인 태그를 사용하면, 배포된 버전의 코드를 정확히 추적할 수 없어 장애 발생 시 재현과 분석이 불가능해집니다.
  • 해결책: 커밋 SHA (소스 코드의 고유 ID)를 이미지 태그로 사용합니다. (예: my-service:3f1a9c2)
  • 장점:
    • 1:1 추적: 태그를 보면 정확히 어떤 소스 코드로 빌드되었는지 즉시 알 수 있습니다.
    • 쉬운 롤백: 문제 발생 시 이전 커밋 SHA 태그로 안전하게 롤백할 수 있습니다.

 

3) 환경 분리 및 승인 게이트

  • 문제: 개발(Dev)과 운영(Prod) 환경은 설정이 다르고, 운영 환경은 안전을 위해 자동화 외에 추가적인 검증이 필요합니다.
  • 해결책 (GitOps 환경 분리):
    • GitOps Repo 안에 overlays/dev와 overlays/prod처럼 환경별 디렉터리를 분리합니다.
    • Dev: Tekton이 이미지를 빌드하면 overlays/dev에 자동으로 태그를 반영하고, Argo CD가 완전 자동으로 배포합니다. (빠르고 신속하게)
    • Prod: 검증된 이미지 태그만 **사람의 승인(Manual Approval)**을 거쳐 overlays/prod에 수동/반자동으로 커밋합니다.
    • Argo CD는 overlays/prod의 변경 사항만 확인하여 배포를 진행합니다.