우리는 에서 코드를 도커 이미지(Docker Image)로 굽는 화려한 기술을 배웠습니다. FROM node:14-alpine 같은 베이스 이미지를 가져와서 무심코 컨테이너를 말아 올렸죠. 그런데 만약 그 베이스 이미지 안에 해커가 악용할 수 있는 치명적인 OpenSSL 해킹 취약점(예: Heartbleed)이나 탈취 가능한 루트 권한 버그가 심어져 있다면 어떨까요? 젠킨스가 아무리 배포를 잘해도, 결과적으로는 '보안 폭탄'을 운영 서버 한가운데에 아주 빠르고 정확하게 배달해 주는 꼴이 됩니다. 바야흐로 배포 파이프라인 내부에 보안(Security)을 욱여넣어 강제화하는 DevSecOps(개발-보안-운영)의 시대입니다.
이번에는 전 세계에서 가장 사랑받는 초경량 오픈소스 컨테이너 보안 스캐너인 'Trivy(트리비)'를 젠킨스와 연동하여, 보안 결함이 있는 이미지는 배포를 원천 차단해 버리는 방어 체계에 대하여 다뤄보겠습니다.
1. 왜 애플리케이션 코드가 아닌 '컨테이너'를 스캔하는가?
이전에 배운 SonarQube()는 개발자가 직접 타이핑한 Java나 Python 소스코드 자체의 논리적 버그를 잡아냅니다. 반면 Trivy는 코드가 아니라, 그 코드를 둘러싸고 있는 OS 패키지 환경(Alpine, Ubuntu 베이스 이미지)의 알려진 취약점(CVE)을 잡아냅니다.
개발자가 짠 console.log()는 완벽할지 몰라도, 그 밑바탕이 되는 리눅스의 curl 패키지나 openssl 버전에 심각한 구멍(Critical)이 뚫려있다면 시스템은 해킹당합니다. Trivy는 방금 갓 구워낸 도커 이미지의 배를 갈라서, 내부에 설치된 수백 개의 리눅스 라이브러리 목록을 뽑아낸 뒤 전 세계 보안 취약점 DB(CVE Database)와 실시간으로 대조하는 무서운 검열관 역할을 합니다.
2. Trivy의 무자비한 차단 로직 (Exit Code 1)
젠킨스 쉘(sh) 스크립트의 특징은, 명령어의 결과 값(Exit Code)이 0이면 성공(계속 진행)으로 간주하고, 0이 아닌 숫자(예: 1)가 떨어지면 즉시 파이프라인을 폭파(Failure) 시킨다는 점입니다.
Trivy에게 --exit-code 1 이라는 옵션과 --severity CRITICAL,HIGH (치명적, 높음 수준의 취약점만 찾아라) 옵션을 조합해서 던져봅시다. Trivy가 이미지를 훑어보다가 'LOW(낮음)' 수준의 자잘한 취약점만 발견하면 "에이, 이 정도는 봐준다"라며 0을 뱉고 통과시킵니다. 하지만 'HIGH(높음)' 이상의 무시무시한 취약점이 단 한 개라도 발견되는 순간, Trivy는 자비 없이 에러 코드 1을 내뿜어버립니다. 그 순간 젠킨스 파이프라인은 붉게 피를 흘리며(실패) 도커 푸시(Push) 단계로 넘어가지 못하고 완전히 차단됩니다. 완벽한 'Quality Gate(보안 게이트)'가 탄생하는 순간입니다.
3. 실무 적용 극한의 DevSecOps 파이프라인 코드
젠킨스 워커 노드(호스트) 서버에 trivy 바이너리가 설치되어 있다고 가정하고(apt-get install trivy), 도커 빌드와 푸시 사이에 보안 검사 스테이지를 교묘하게 끼워 넣은 마스터피스 코드를 살펴봅시다.
pipeline {
agent any
environment
IMAGE_NAME = "my-dockerhub-id/my-secure-app:${env.BUILD_NUMBER}"
}
stages {
stage('1. Docker Build') {
steps {
echo "도커 이미지를 굽습니다..."
sh "docker build -t ${IMAGE_NAME} ."
}
}
stage('2. Security Vulnerability Scan (Trivy)') {
steps {
echo " 방금 구운 이미지에 대한 강력한 보안 스캔을 시작합니다!"
// Trivy 스캔 명령어 분석:
// --no-progress: 젠킨스 로그가 지저분해지지 않게 로딩바 숨김
// --severity HIGH,CRITICAL: 중간(MEDIUM) 이하는 무시하고 고위험군만 색출
// --exit-code 1: 하나라도 걸리면 파이프라인 즉시 폭파 (실무 핵심)
sh """
trivy image \
--no-progress \
--severity HIGH,CRITICAL \
--exit-code 1 \
${IMAGE_NAME}
"""
}
}
stage('3. Docker Push (Only if Passed)') {
steps {
// 이 스테이지에 도달했다는 것은, 앞선 Trivy 보안 게이트를 무사히 통과한 청정 이미지라는 뜻입니다!
echo " 보안 스캔을 완벽하게 통과했습니다. 안전한 이미지를 레지스트리로 푸시합니다."
withCredentials([usernamePassword(credentialsId: 'dockerhub-creds', passwordVariable: 'PASS', usernameVariable: 'USER')]) {
sh "echo \$PASS | docker login -u \$USER --password-stdin"
sh "docker push ${IMAGE_NAME}"
}
}
}
}
}
4. 마무리 및 주요 발전 방향
지금까지 단순히 배포를 자동화하는 것을 넘어, 운영 환경에 독극물(취약점)이 살포되는 것을 기계적으로 막아내는 최강의 수문장, Trivy 스캐너와 젠킨스 Pipeline의 완벽한 DevSecOps 결합 기법에 대해 다루어 보았습니다. 이제 여러분의 회사는 보안 감사를 받을 때 "우리는 CI 과정에서 배포 전 모든 컨테이너의 CVE(취약점)를 실시간 차단하고 있습니다"라고 자랑스럽게 말할 수 있게 되었습니다.
코드 품질도 챙겼고(SonarQube), 도커 이미지 보안도 챙겼습니다(Trivy). 이제 여러분의 파이프라인은 완벽한 자동화 요새가 되었습니다. 그런데 Github에서 여러 개발자가 협업을 하다 보면 main 브랜치에 코드를 직접 밀어 넣는 것이 아니라, PR(Pull Request)을 올려서 코드 리뷰를 받고 머지(Merge)하는 것이 실무의 정석입니다. "누군가 PR을 올렸을 때, 그 코드를 머지하기 전에 젠킨스가 미리 슬쩍 가져와서 빌드가 잘 되는지 테스트해 주고 Github에 O, X 마크를 달아줄 순 없을까?" 본 설명에서는 Github 협업 프로세스의 꽃, 'Github PR(Pull Request) 빌드 자동화와 커밋 스테이터스 연동법'에 대해 아주 치밀하게 파헤쳐 보겠습니다!
현대의 소프트웨어 개발은 더 이상 혼자서 코드를 짜고 메인(main) 브랜치에 직접 푸시(Push)하는 방식으로 이루어지지 않습니다. 아무리 실력이 뛰어난 개발자라도 실수를 할 수 있기 때문에, 반드시 PR(Pull Request)을 올려서 동료들에게 코드 리뷰(Code Review)를 받고 안전하게 병합(Merge)하는 절차를 거칩니다. 그런데 리뷰어가 코드를 눈으로만 보고 "음, 버그는 없어 보이네요. LGTM(Looks Good To Me)!"이라고 승인했는데, 막상 머지하고 보니 컴파일 에러가 나거나 단위 테스트가 실패하는 경우가 비일비재합니다. 이를 방지하기 위해 "누군가 PR을 올리면 젠킨스가 미리 그 코드를 가져와서 몰래 빌드해 보고, 통과했을 때만 Github에 초록색 V(체크) 표시를 달아주는" 완벽한 협업 자동화 프로세스가 필요합니다. 본 글에서는 PR 빌드 자동화의 모든 것에 대하여 다뤄보겠습니다.
1. Github Status Checks (상태 검사)의 위력
Github 저장소 설정(Settings) -> Branches 메뉴에 가면 Branch Protection Rules (브랜치 보호 규칙)을 설정할 수 있습니다. 여기서 Require status checks to pass before merging 옵션을 켜면, 젠킨스(또는 다른 CI)가 빌드를 성공했다고 보고(Report)해 주기 전까지는 'Merge pull request' 버튼 자체가 비활성화(회색)되어 아무도 코드를 합칠 수 없게 됩니다. 불량 코드가 메인 브랜치로 유입되는 것을 시스템적으로 완벽하게 원천 차단하는 가장 훌륭한 장치입니다.
2. Jenkins 연동 셋업: Multibranch Pipeline 활용
이 기능을 구현하려면 에서 배웠던 Multibranch Pipeline과 GitHub Branch Source Plugin의 조합이 필수적입니다.
1. Github Token 발급: 젠킨스가 Github API를 찔러서 상태를 변경하려면 권한이 필요합니다. Github에서 Personal Access Token(repo, admin:repo_hook 권한 포함)을 발급받아 젠킨스 Credentials에 Secret text로 등록합니다.
2. Multibranch Job 설정: 젠킨스에서 Multibranch Pipeline을 생성하고 Branch Sources에 Github을 추가합니다. 이때 Behaviors 항목에서 Discover pull requests from origin 옵션을 반드시 추가해야 합니다. (기본적으로 Merging the pull request with the current target branch revision 옵션을 선택하면, PR이 병합되었을 때의 미래 상태를 미리 젠킨스가 계산해서 빌드해 줍니다.)
3. 실무 적용 극한의 Jenkinsfile 코드 파헤치기
일단 Multibranch가 셋업되면, 젠킨스는 누군가 PR을 올리는 즉시 이를 감지(Webhook)하고 파이프라인을 돌립니다. 파이프라인이 성공적으로 끝나거나 실패했을 때, 그 결과를 Github의 PR 화면으로 예쁘게 쏴주는 로직(Post Action)을 Jenkinsfile에 구성해야 합니다. (※ 최근 플러그인은 이 과정을 자동으로 해주기도 하지만, 명시적으로 커스텀 메시지를 쏘는 것이 실무의 정석입니다.)
pipeline {
agent any
stages {
stage('Build & Test') {
steps {
echo "PR 코드를 빌드하고 테스트합니다..."
sh 'npm ci'
sh 'npm run test'
}
}
}
// 빌드가 완료된 후, 성공/실패 여부를 Github PR 화면으로 직접 콜백(Callback)합니다.
post {
success {
// 플러그인 내장 함수(githubNotify)를 사용하여 Status를 PENDING -> SUCCESS 로 변경
githubNotify context: 'Jenkins CI - Unit Test',
status: 'SUCCESS',
description: ' 모든 테스트가 통과했습니다! 안심하고 Merge 하세요.',
targetUrl: "${env.BUILD_URL}"
}
failure {
// 실패 시 PR 창에 빨간색 엑스 마크(FAILURE)와 함께 에러 원인 링크를 달아줍니다.
githubNotify context: 'Jenkins CI - Unit Test',
status: 'FAILURE',
description: ' 빌드/테스트가 실패했습니다! Details 링크를 눌러 로그를 확인하세요.',
targetUrl: "${env.BUILD_URL}console"
}
}
}
4. 중급 꿀팁: PR과 일반 브랜치 분기 처리
Multibranch Pipeline 하나로 메인 브랜치 배포와 PR 검증을 동시에 처리하다 보면 문제가 생깁니다. PR이 올라왔을 때는 '빌드와 테스트'만 해야지, '실제 운영 서버 배포(Deploy)'까지 해버리면 큰일 나겠죠? env.CHANGE_ID 환경변수를 통해 현재 돌고 있는 파이프라인이 PR인지 아닌지를 식별할 수 있습니다.
stage('Production Deploy') {
// env.CHANGE_ID가 없다는 것은 PR이 아니라 순수 브랜치(main)라는 뜻이므로 배포를 진행합니다.
when {
expression { return env.CHANGE_ID == null }
}
steps {
echo "운영 서버로 상용 배포를 시작합니다!"
}
}
5. 마무리 및 주요 발전 방향
지금까지 동료의 눈에만 의존하던 불완전한 코드 리뷰 프로세스를, 젠킨스의 기계적인 테스트 빌드와 Github Status Checks 연동을 통해 완벽한 무결점 협업 프로세스로 승화시키는 마법에 대해 다루어 보았습니다. 이제 리뷰어들은 젠킨스가 달아준 초록색 체크 마크를 확인한 뒤에만 가벼운 마음으로 코드를 살펴보면 됩니다.
자, 파이프라인의 구조와 협업 프로세스는 완벽해졌습니다. 그런데 최근 프론트엔드 팀에서 "A 프로젝트는 Node.js 14 버전을 써야 하고, B 프로젝트는 최신 Node.js 20 버전을 써야 하는데, 젠킨스 서버 하나에서 이거 두 개 동시에 빌드 가능해요?"라는 어려운 질문을 던졌습니다. 호스트 서버에 Node를 여러 개 깔아야 할까요?
다음글에서는 Node.js 다중 환경 파이프라인 구축에 대해 알아보겠습니다.
'CI. CD > Jenkins' 카테고리의 다른 글
| [Jenkins] 파이프라인 나누기(Upstream / Downstream) (0) | 2026.10.15 |
|---|---|
| [Jenkins] Node.js 다중 버전 환경 완벽 통제 가이드 (0) | 2026.10.15 |
| [Jenkins] Retry 블록 네트워크 장애를 극복하는 파이프라인 (0) | 2026.10.14 |
| [Jenkins] Timeout의 파이프라인 무한 루프 방어 (0) | 2026.10.12 |
| [Jenkins] Maven Gradle 빌드 캐시 최적화 빌드 시간 10배 단축의 비밀 (0) | 2026.10.08 |