Spring Boot, Jenkins, Kubernetes 및 보안 스캔을 활용한 엔드‑투‑엔드 CI/CD 파이프라인 구축
Source: Dev.to
위에 제공된 소스 링크만으로는 번역할 실제 텍스트가 포함되어 있지 않습니다. 번역을 원하는 본문 내용을 그대로 복사해서 알려주시면, 요청하신 대로 마크다운 형식과 코드 블록을 유지하면서 한국어로 번역해 드리겠습니다.
왜 이 프로젝트를 만들었는가
저는 CI/CD 개념과 도구들을 사용해 본 적이 있지만, 도구를 아는 것과 완전한 시스템을 구축하는 것 사이에는 항상 격차가 있었습니다.
대부분의 튜토리얼은 “파이프라인이 성공적으로 실행되었습니다” 라는 단계에서 멈추고, 실제 CI/CD 시스템은 다음과 같은 요소들을 포함합니다:
- 버전 관리 전략
- 웹훅
- 코드 품질 게이트
- 보안 스캔
- 쿠버네티스 롤아웃
- 실패, 재시작 및 광범위한 디버깅
저는 Git 푸시부터 시작해 쿠버네티스에서 실행되는 완전하게 배포되고, 보안이 강화되며, 관찰 가능한 Spring Boot 애플리케이션까지 이어지는 진정한 엔드‑투‑엔드 CI/CD 파이프라인을 구축하기로 결심했습니다. 이 블로그에서는 제가 만든 내용, 발생한 문제, 해결 방법, 그리고 배운 교훈을 기록합니다.
파이프라인 개요
| 레이어 | 기술 |
|---|---|
| 애플리케이션 | Spring Boot |
| 버전 관리 | Maven + /release API |
| SCM | GitHub |
| CI/CD | Jenkins (Pipeline as Code) |
| 코드 품질 | SonarQube |
| 노출 | Ngrok |
| 컨테이너 | Docker |
| 보안 | Trivy |
| 오케스트레이션 | Kubernetes |
| 데이터베이스 | MongoDB |
| 알림 | Jenkins Mailer |
파이프라인은 로컬에서 실행되지만 프로덕션 시스템처럼 동작합니다:
Git push → automatic Jenkins trigger
→ Maven build & tests
→ Application versioning with /release endpoint
→ Docker image build (immutable)
→ SonarQube code quality gates
→ Trivy image security scanning
→ Kubernetes deployment & rollout
→ Email notifications
→ MongoDB‑backed persistence
GitHub 푸시 및 Jenkins 트리거
- 개발자가 코드를 GitHub에 푸시합니다.
- Ngrok을 통해 노출된 웹훅이 Jenkins 파이프라인을 자동으로 트리거합니다—수동 시작이 필요 없습니다.
- Jenkins가 저장소를 클론하고 빌드를 실행합니다.
Maven 빌드 및 테스트
mvn clean test
보장:
- 코드가 컴파일됩니다.
- 단위 테스트가 통과합니다.
- 진행하기 전에 빌드가 배포 가능함을 확인합니다.
SonarQube Quality Gates
- Jenkins는 분석 보고서를 SonarQube에 전송합니다.
- 파이프라인은 Quality Gate 결과를 기다립니다.
- 게이트가 실패하면 빌드가 중단되어 엔지니어링 표준이 자동으로 적용됩니다.
Docker 이미지 빌드 (멀티‑스테이지)
멀티‑스테이지 Dockerfile이 사용됩니다:
- Build stage – Maven과 모든 빌드 종속성을 포함합니다.
- Runtime stage – JRE와 컴파일된 JAR만 포함합니다.
Result: 더 작고, 보다 안전한 프로덕션 이미지.
Trivy를 이용한 보안 스캔
이미지를 레지스트리에 푸시하기 전에 Trivy가 스캔합니다:
trivy image
- Critical 및 High 취약점을 감지합니다.
- 파이프라인을 심각도에 따라 실패하도록 구성할 수 있어, 취약한 이미지가 배포에 도달하지 않도록 보장합니다.
쿠버네티스 배포 및 롤아웃
모든 검사가 통과된 후:
docker push /:${BUILD_NUMBER}
kubectl apply -f deployment.yaml
kubectl rollout status deployment/
젠킨스 컨테이너 문제 및 해결책
| 문제 | 해결책 |
|---|---|
Jenkins가 Docker 컨테이너 내부에서 실행되지만 Docker 이미지를 빌드해야 하므로 /var/run/docker.sock에 대한 권한 문제가 발생합니다. | - 호스트 Docker 소켓을 젠킨스 컨테이너에 마운트합니다 (-v /var/run/docker.sock:/var/run/docker.sock).- 젠킨스 이미지 내부에 docker 그룹을 추가하고 jenkins 사용자를 해당 그룹에 포함시킵니다. |
Jenkins(컨테이너)가 Windows 호스트에서 127.0.0.1을 사용해 쿠버네티스 API 서버에 접근하지 못했습니다. | 젠킨스 컨테이너를 --add-host=localhost:host-gateway 옵션으로 실행하여 컨테이너의 localhost를 호스트 게이트웨이와 매핑하면 KIND 클러스터와 통신할 수 있습니다. |
배포가 여전히 :latest 태그를 참조하고 있으면 이미지가 변경되어도 쿠버네티스가 새로운 롤아웃을 트리거하지 않습니다. | 동적 이미지 태깅을 구현합니다: deployment.yaml의 IMAGE_PLACEHOLDER를 고유 버전 ${BUILD_NUMBER} 로 교체합니다. 이렇게 하면 모든 변경에 대해 롤아웃이 보장되고, 결정적인 롤백 및 추적 가능한 이미지 버전을 제공합니다. |
/release Endpoint
Spring Boot 애플리케이션은 /release 엔드포인트를 노출하여 다음을 반환합니다:
- 애플리케이션 버전
- Jenkins 빌드 번호
- 런타임 환경 메타데이터
이를 통해 파드 내부에서 실행 중인 빌드를 애플리케이션에서 직접 쉽게 확인할 수 있습니다.
맞춤형 Jenkins Docker 이미지
맞춤형 Jenkins 이미지는 다음 도구들이 사전 설치된 상태로 빌드됩니다:
- Maven
- Docker CLI
- Kubectl
이점:
- 환경 전반에 걸친 재현성.
- 새 팀원의 빠른 온보딩.
- 일관된 도구 버전.
교훈
- Shift‑left 보안: Trivy는 배포 전에 중요한 CVE(예: Tomcat RCE)를 식별했습니다.
- 네트워크 경계가 중요: Jenkins, SonarQube, Kubernetes, Docker 간의 통신 관리가 가장 큰 과제였습니다.
- 이벤트 기반 파이프라인: 수동 트리거를 제거하고 GitHub 웹훅에 의존함으로써 파이프라인을 진정한 CI/CD로 전환했습니다.
- 자동화가 신뢰를 구축: 시스템 간의 상호 작용을 이해하고 실패를 체계적으로 처리함으로써 DevOps를 스크립트 수준에서 엔지니어링 수준으로 끌어올립니다.
참고문헌
- GitHub 저장소:
- LinkedIn 프로필: