처음부터 끝까지, 실전 DevOps 파이프라인 구축기
왜 DevOps 파이프라인을 직접 구축했을까
최근 회사에서 배포 프로세스를 개선하면서, 각각의 DevOps 도구들이 어떻게 연결되고 협력하는지 깊이 이해하고 싶었습니다. 단순히 Jenkins 사용법이나 Docker 명령어를 외우는 게 아니라, 실제 엔터프라이즈 환경에서 코드가 어떤 여정을 거쳐 프로덕션에 배포되는지 말이죠.
그래서 GitHub부터 Grafana까지, 현업에서 사용하는 10가지 핵심 도구를 연결한 완전한 CI/CD 파이프라인을 처음부터 구축해봤습니다. 이론으로만 알던 DevSecOps가 실제로 어떻게 작동하는지 직접 경험할 수 있었던 유익한 시간이었습니다.
전체 파이프라인 아키텍처
음식 배달 앱을 생각해보면 이해가 쉽습니다. 주문(코드 푸시)이 들어오면 주방(CI)에서 요리를 만들고, 품질 검사를 거쳐, 포장(컨테이너화)한 다음, 배달원(CD)이 고객(프로덕션)에게 전달하는 과정이죠.
파이프라인 흐름:
개발자 → GitHub → Jenkins CI → OWASP 스캔 → SonarQube 분석 → Docker 빌드 → Trivy 보안 스캔 → DockerHub 푸시 → Jenkins CD → GitHub 매니페스트 업데이트 → ArgoCD → Kubernetes 배포 → Prometheus 모니터링 → Grafana 대시보드
각 단계마다 명확한 목적이 있고, 하나의 단계에서 문제가 발생하면 전체 파이프라인이 중단되도록 설계했습니다. 이게 바로 **품질 게이트(Quality Gate)**의 핵심이죠.
환경 구성과 사전 준비
시스템 요구사항:
| 항목 | 권장 사양 |
|---|---|
| RAM | 16GB 이상 |
| CPU | 4코어 이상 |
| OS | Ubuntu 22.04 LTS |
| 디스크 | 50GB 이상 |
| 네트워크 | 인터넷 연결 필수 |
실제로는 AWS EC2 t3.xlarge 인스턴스에서 구축했는데, 모든 도구를 동시에 실행하려면 메모리가 많이 필요합니다. 특히 SonarQube와 Jenkins가 상당한 리소스를 사용하더군요.
사전 준비사항:
- Docker 설치 및 권한 설정
sudo apt update && sudo apt install docker.io -y
sudo systemctl start docker && sudo systemctl enable docker
sudo usermod -aG docker $USER
# 로그아웃 후 재로그인 필요
- Kubernetes 환경 구성
sudo snap install kubectl --classic
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
minikube start --driver=docker
- 보안 스캔 도구 설치
# Trivy 설치
wget https://github.com/aquasecurity/trivy/releases/latest/download/trivy_0.50.1_Linux-64bit.deb
sudo dpkg -i trivy_0.50.1_Linux-64bit.deb
CI 파이프라인 구축하기
Jenkins와 SonarQube 연동
Jenkins와 SonarQube를 Docker 컨테이너로 실행했습니다. 프로덕션에서는 별도 서버에 설치하겠지만, 학습 목적으로는 컨테이너가 훨씬 편리하더군요.
# Jenkins 실행
docker run -d \
--name jenkins \
-p 8080:8080 \
-p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
jenkins/jenkins:lts
# SonarQube 실행
docker run -d \
--name sonarqube \
-p 9000:9000 \
sonarqube:lts-community
핵심 플러그인 설치:
- Docker Pipeline
- SonarQube Scanner
- OWASP Dependency Check
- Kubernetes CLI
- Email Extension
Jenkinsfile 작성의 핵심
실제 사용한 Jenkinsfile의 핵심 부분입니다:
pipeline {
agent any
environment {
IMAGE_NAME = "${DOCKERHUB_USERNAME}/flask-devops"
SCANNER_HOME = tool 'SonarQube-Scanner'
}
stages {
stage('Code Quality & Security') {
parallel {
stage('OWASP Dependency Check') {
steps {
dependencyCheck additionalArguments: '--scan ./'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'dependency-check-report',
reportFiles: 'dependency-check-report.html'
])
}
}
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv('SonarQube') {
sh '''$SCANNER_HOME/bin/sonar-scanner \
-Dsonar.projectKey=flask-devops \
-Dsonar.projectName=flask-devops \
-Dsonar.sources=. \
-Dsonar.exclusions=**/node_modules/**'''
}
}
}
}
}
stage('Quality Gate') {
steps {
timeout(time: 5, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
}
}
여기서 중요한 건 parallel 블록을 사용해서 OWASP 스캔과 SonarQube 분석을 동시에 실행한다는 점입니다. 시간도 절약하고, 각각의 품질 게이트가 독립적으로 작동합니다.
CD 파이프라인과 GitOps
ArgoCD로 구현하는 GitOps
CD 부분에서는 GitOps 패턴을 적용했습니다. Jenkins에서 직접 Kubernetes에 배포하는 대신, Git 저장소의 매니페스트 파일을 업데이트하고, ArgoCD가 변경사항을 감지해서 자동으로 배포하는 방식이죠.
# ArgoCD 설치
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# 초기 비밀번호 확인
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
Kubernetes 매니페스트 예시:
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-app
spec:
replicas: 2
selector:
matchLabels:
app: flask-app
template:
metadata:
labels:
app: flask-app
spec:
containers:
- name: flask-app
image: yourusername/flask-devops:latest
ports:
- containerPort: 5000
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
자동 이미지 태그 업데이트
Jenkins CD 파이프라인에서는 새로운 Docker 이미지가 빌드될 때마다 Git 저장소의 매니페스트 파일을 자동으로 업데이트합니다:
stage('Update Manifest') {
steps {
script {
sh '''
git config user.email "jenkins@example.com"
git config user.name "Jenkins"
sed -i "s|image: .*|image: ${IMAGE_NAME}:${BUILD_NUMBER}|g" k8s/deployment.yaml
git add k8s/deployment.yaml
git commit -m "Update image to ${BUILD_NUMBER}"
git push origin main
'''
}
}
}

모니터링과 관찰 가능성
Prometheus와 Grafana 스택
모니터링 스택은 Helm을 사용해서 설치했습니다. kube-prometheus-stack은 Prometheus, Grafana, AlertManager, 그리고 다양한 exporter들을 한 번에 설치해주는 편리한 차트입니다.
# Helm 설치
sudo snap install helm --classic
# 차트 저장소 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# 모니터링 네임스페이스 생성
kubectl create namespace monitoring
# Prometheus 스택 설치
helm install prometheus prometheus-community/kube-prometheus-stack -n monitoring
실용적인 알람 설정
단순히 대시보드만 보는 게 아니라, 실제 장애 상황에서 알림을 받을 수 있도록 Grafana에서 알람을 설정했습니다:
주요 알람 규칙:
- Pod 재시작 횟수 임계치 초과
- 메모리 사용률 80% 이상
- HTTP 응답 시간 5초 초과
- 애플리케이션 가용성 95% 미만
Slack과 이메일 알림을 모두 설정해뒀는데, 실제로 테스트해보니 응답 속도도 빠르고 알람 내용도 상세해서 만족스럽더군요.
보안과 품질 관리
다층 보안 스캔
이번 파이프라인에서 가장 신경 쓴 부분이 보안입니다. shift-left 개념을 적용해서 개발 초기 단계부터 보안을 검증하도록 구성했습니다:
- OWASP Dependency Check: 라이브러리 취약점 스캔
- SonarQube: 코드 품질 및 보안 이슈 분석
- Trivy: 컨테이너 이미지 취약점 스캔
- Kubernetes 보안 정책: Pod Security Standards 적용
특히 Trivy는 정말 인상적이었습니다. 컨테이너 이미지의 OS 패키지부터 애플리케이션 의존성까지 모든 레이어를 스캔해서, CVE 정보와 함께 상세한 보안 보고서를 제공하더군요.
SonarQube Quality Gate
SonarQube에서는 다음과 같은 품질 게이트 조건을 설정했습니다:
| 지표 | 임계값 |
|---|---|
| 코드 커버리지 | 80% 이상 |
| 중복 코드 | 3% 이하 |
| 보안 취약점 | 0개 |
| 코드 스멜 | 10개 이하 |
| 기술 부채 비율 | 5% 이하 |

배운 점과 개선 방향
전체 구축 과정에서 가장 까다로웠던 부분은 각 도구 간의 권한 관리와 네트워크 설정이었습니다. 특히 Jenkins에서 Kubernetes API에 접근하거나, ArgoCD가 private Git 저장소에 접근할 때 인증 설정에서 시간을 많이 소모했죠.
실제 프로덕션에서는 이런 점들을 개선하고 싶습니다:
- Infrastructure as Code: Terraform으로 전체 인프라 관리
- Secret 관리: HashiCorp Vault 또는 AWS Secrets Manager 도입
- Multi-cluster 환경: 개발/스테이징/프로덕션 환경 분리
- 백업 전략: ETCD 백업 및 재해복구 계획
- 성능 최적화: 빌드 캐시, 이미지 레이어 최적화
특히 보안 측면에서는 **RBAC(Role-Based Access Control)**을 더 세밀하게 설정하고, 네트워크 정책으로 Pod 간 통신을 제한하는 것이 필요하겠더군요.
직접 손으로 만져보면서 느낀 건데, DevOps는 결국 도구의 조합이 아니라 문화와 프로세스의 문제인 것 같습니다. 자동화된 파이프라인이 있어도 팀이 협업하는 방식이나 장애 대응 문화가 뒷받침되지 않으면 진정한 DevOps라고 할 수 없겠죠.