프로젝트 목록으로

DEVOPS PROJECT

FLOWSHIP

Jenkins, Docker, ArgoCD, Kubernetes 기반 GitOps CI/CD 자동 배포 파이프라인 구축 프로젝트

개인 프로젝트GitOps 기반 CI/CD

Tech Skills

Infrastructure

VagrantVirtualBoxUbuntu

Kubernetes

kubeadmKubesprayIngress-NGINXMetalLB

CI

JenkinsDockerDocker Hub

CD / GitOps

ArgoCD

App / DB

DjangoMySQL

Project Summary

이 프로젝트의 문제 배경과 해결 방식, 핵심 아이디어를 간단히 정리했습니다.

Why I Built This

반복되는 배포

코드를 수정할 때마다 빌드와 이미지 생성, 서버 반영을 직접 반복해야 했습니다. 배포 한 번에 약 30분이 걸리고 작은 실수도 중단으로 이어질 수 있었습니다.

환경 불일치

로컬에서는 되는데 서버에서 동작하지 않는 경우가 생겼습니다. 설정 관리가 사람 손에 달려 있어 일관성을 유지하기 어려웠습니다.

운영 대응 한계

Pod 장애를 수동으로 확인하고 다시 띄워야 했습니다. 트래픽이 몰릴 때도 서버 확장을 직접 처리해야 했습니다.

My Approach

01

CI 자동화

App Repo에 코드를 Push하면 Jenkins가 감지해 이미지를 빌드하고 Docker Hub에 올리도록 구성했습니다. 빌드가 끝나면 Ops Repo의 이미지 태그도 자동으로 갱신되게 연결했습니다.

02

GitOps 배포

Ops Repo 변경을 ArgoCD가 감지해 클러스터 상태를 자동으로 맞추도록 구성했습니다. Git을 배포 기준으로 두고 원하는 상태를 한곳에서 관리했습니다.

03

K8s 운영 구조

4노드 클러스터에서 Web과 DB 워크로드를 나눠 배치했습니다. ReplicaSet과 Rolling Update를 기반으로 복구와 배포 흐름을 안정적으로 정리했습니다.

System Architecture

애플리케이션 코드 변경이 CI/CD 파이프라인을 거쳐 이미지 빌드, 배포 설정 업데이트, Kubernetes 반영까지 이어지는 전체 흐름을 정리한 구조입니다.

FlowShip architecture

CI Pipeline

App Repo → Jenkins → Docker Hub

App Repo에 코드가 반영되면 Jenkins가 실행되어 새로운 Docker 이미지를 빌드하고 Docker Hub에 푸시합니다.

GitOps Delivery

Ops Repo → Argo CD → Kubernetes

Jenkins가 Ops Repo의 이미지 태그를 갱신하고, Argo CD가 변경된 배포 설정을 감지해 Kubernetes 클러스터와 동기화합니다.

Kubernetes Runtime

Ingress → Django Pods → MySQL

사용자 요청은 Ingress를 통해 Django Pods로 전달되고, 애플리케이션은 MySQL과 Persistent Storage를 기반으로 동작합니다.

Core Implementation

CI 파이프라인, GitOps 기반 배포 흐름, Kubernetes 런타임 환경으로 나누어 핵심 구현 내용을 정리했습니다.

01

인프라 환경 구성

Vagrant + Kubespray로 4노드 K8s 클러스터 구성

VirtualBox와 Vagrant를 사용해 4대의 VM을 자동으로 생성하고, Kubespray의 Ansible playbook으로 Kubernetes v1.31.9 클러스터를 설치했습니다. 이후 Worker Node에 role=web, role=db 라벨을 부여해 웹과 데이터베이스 워크로드를 역할별로 분리했습니다.

가상 환경 구성

Vagrant + VirtualBox, VM 4대 자동 생성

클러스터 설치

Kubespray Ansible playbook, Kubernetes v1.31.9

노드 역할 분리

role=web (kube-node1, kube-node2) / role=db (kube-node3)

노드 라벨링

# 노드 라벨링
kubectl label node kube-node1 role=web
kubectl label node kube-node2 role=web
kubectl label node kube-node3 role=db
02

앱 컨테이너화

Django와 MySQL 환경에 맞는 Docker 이미지를 구성했습니다.

python:3.10-slim 기반 Dockerfile을 작성하고, MySQL 연동에 필요한 라이브러리를 포함해 Django 애플리케이션이 컨테이너 환경에서 실행되도록 구성했습니다. 또한 DB 연결 HOST를 고정 IP가 아닌 Kubernetes Service 이름인 pybo-mysql로 설정해, 클러스터 내부 DNS 기반으로 동작하도록 설계했습니다.

Dockerfile

FROM python:3.10-slim

RUN apt-get update && apt-get install -y \
    default-libmysqlclient-dev \
    build-essential \
    pkg-config \
 && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app/
EXPOSE 8000
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

settings.py DB 설정

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'pybo',
        'USER': 'pybo',
        'PASSWORD': 'pybo1234',
        'HOST': 'pybo-mysql',
        'PORT': '3306',
    }
}
03

Kubernetes 배포 설계

웹 서비스와 데이터베이스를 분리하고, Kubernetes 리소스를 역할에 맞게 구성했습니다.

모든 리소스는 mysite 네임스페이스 안에서 관리되도록 구성했습니다. DB 인증 정보는 Secret으로 분리해 관리하고, MySQL은 role=db 노드에만 배치되도록 nodeSelector를 설정했습니다. 또한 MySQL 데이터는 Persistent Volume Claim을 통해 유지하고, Ingress를 통해 외부 요청이 Django 서비스로 전달되도록 설계했습니다.

django-deploy.yaml 핵심

spec:
  replicas: 2
  template:
    spec:
      nodeSelector:
        role: web
      containers:
        - name: django
          image: jeegle16/django-pybo:v5
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8000

mysql-deploy.yaml 핵심

      nodeSelector:
        role: db
      containers:
        - name: mysql
          image: quay.io/sclorg/mysql-80-c9s
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-secret
                  key: mysql-root-password
          volumeMounts:
            - name: mysql-data
              mountPath: /var/lib/mysql/data

ingress.yaml 핵심

spec:
  ingressClassName: nginx
  rules:
    - host: pybo.mysite.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: pybo-web-svc
                port:
                  number: 8000
04

End-to-End 배포 자동화

Jenkins CI + ArgoCD CD — 코드 Push 한 번으로 클러스터 반영까지

Jenkinsfile을 5개 스테이지로 구성해 CI 파이프라인을 설계했습니다. 특히 핵심은 Update Ops Repo 단계로, 빌드가 끝난 뒤 Jenkins가 직접 Ops Repo를 clone하고 django-deploy.yaml의 이미지 태그를 새 버전으로 교체한 뒤 다시 push하도록 구성했습니다. 이후 Argo CD가 저장소 변경을 감지해 Kubernetes 클러스터와 자동으로 동기화되도록 설계했습니다.

Checkout

Set Version

Docker Build

Docker Login & Push

Update Ops Repo

Jenkinsfile 트리거 + Update Ops Repo stage

triggers {
    pollSCM('H/1 * * * *')
}

stage('Update Ops Repo') {
    steps {
        script {
            withCredentials([usernamePassword(
                credentialsId: 'github-token',
                usernameVariable: 'GIT_USER',
                passwordVariable: 'GIT_TOKEN'
            )]) {
                sh """
                rm -rf ops-repo
                git clone https://${GIT_USER}:${GIT_TOKEN}@github.com/\
jeegle16-alt/minipro2_opsrepo.git ops-repo
                cd ops-repo
                sed -i 's#image: .*/django-pybo:.*#image: \
${DOCKER_IMAGE}:${VERSION}#' k8s/mysite/django-deploy.yaml
                git config user.name "jenkins"
                git config user.email "jenkins@example.com"
                git add k8s/mysite/django-deploy.yaml
                git commit -m "Update image tag to ${VERSION}"
                git push origin main
                """
            }
        }
    }
}

ArgoCD Application syncPolicy

syncPolicy:
  automated:
    prune: true
    selfHeal: true
syncOptions:
  - CreateNamespace=true

Jenkins Pipeline Job 설정 화면

Results

자동 배포 파이프라인이 실제로 동작하는지, GitOps 기반 반영부터 Self-Healing까지 세 가지 관점에서 검증했습니다.

결과 01 — GitOps 자동 배포 검증

Ops Repo의 manifest 변경이 Argo CD를 통해 자동으로 동기화되고, 실제 클러스터 상태까지 반영되는 것을 확인했습니다.

  1. 1. django-deploy.yaml의 replicas 값을 2에서 3으로 변경
  2. 2. Argo CD가 변경 사항을 자동으로 감지하고 Sync 수행
  3. 3. 클러스터에서 Django Pod 수가 3개로 증가한 상태 확인

Argo CD가 Sync된 상태

Django pod가 3개 떠 있는 결과

결과 02 — 전체 CD 자동화 검증

코드 수정부터 브라우저 화면 반영까지 전체 파이프라인이 자동으로 연결되는지 확인했습니다.

  1. 1. navbar.html 문구 Pybo → Auto CI/CD Test v4 수정 후 Git push
  2. 2. Jenkins 자동 빌드 트리거 → jeegle16/django-pybo:v4 이미지 생성
  3. 3. Ops Repo django-deploy.yaml 이미지 태그 v3 → v4 자동 업데이트
  4. 4. ArgoCD 자동 배포
  5. 5. 브라우저에서 변경된 문구 Auto CI/CD Test v4 확인

Jenkins 빌드 결과

Argo CD 자동 배포

브라우저 반영 결과

결과 03 — Self-Healing 검증

실행 중인 Pod를 강제로 삭제한 뒤, ReplicaSet이 새로운 Pod를 자동으로 생성해 서비스 상태를 복구하는지 확인했습니다.

  1. 1. kubectl delete pod pybo-web-5845df55f4-8mfjm 실행
  2. 2. 약 5초 내 새로운 Pod가 자동으로 생성되는지 확인
  3. 3. Replica 수가 다시 정상 상태로 유지되는 것을 확인

터미널 출력

pybo-web-5845df55f4-hptj7   1/1   Running   0   37h
pybo-web-5845df55f4-ks9tg   0/1   Pending   0   1s
pybo-web-5845df55f4-ks9tg   1/1   Running   0   5s

Self-Healing 결과 화면

What I Learned

01

CI/CD는 빌드가 아니라 배포 전체 흐름이라는 점

코드 변경부터 Kubernetes 반영까지 직접 연결해보며, CI/CD가 전체 배포 과정을 다루는 구조라는 점을 이해하게 되었습니다.

02

배포 자동화는 코드 저장소 하나만으로 완성되지 않는다는 점

App Repo와 Ops Repo를 분리해 운영하면서, 코드와 배포 설정은 연결되지만 다르게 관리될 수 있다는 점을 배웠습니다.