Jenkins와 GitLab CI/CD로 완성하는 자동화 배포 파이프라인

빠르게 변화하는 소프트웨어 개발 환경 속에서, 수작업 배포는 이제 과거의 유물이 되어가고 있습니다. 자동화된 CI/CD 파이프라인은 개발 생산성을 극대화하고, 품질을 유지하며, 신속한 제품 릴리스를 가능하게 합니다. 이 글에서는 Jenkins와 GitLab CI/CD를 함께 활용하여 실제 현업에 바로 적용 가능한 파이프라인 구축 방법을 단계별로 안내합니다.


Jenkins와 GitLab CI/CD로 완성하는 자동화 배포 파이프라인

📚 목차

  1. 들어가며: 자동화 배포가 왜 중요한가?
  2. CI/CD 개념 빠르게 짚고 가기
  3. Jenkins와 GitLab CI/CD 비교와 장단점
  4. 시나리오 소개: 우리가 구축할 파이프라인
  5. GitLab 설정: 저장소, Runner 등록, CI 파일 구성
  6. Jenkins 설치 및 초기 설정
  7. Jenkins + GitLab 연동하기
  8. 실제 파이프라인 구성 예제 (코드 포함)
  9. 테스트 자동화: 유닛/통합 테스트 연계
  10. 자동 배포 구성: Staging → Production까지
  11. 에러 핸들링과 로깅: 문제 발생 시 대처 전략
  12. 보안 고려사항 및 권한 관리 팁
  13. 지속적인 개선: 파이프라인의 최적화 전략
  14. 맺으며: DevOps의 핵심, 지속 가능한 자동화

1. 들어가며: 자동화 배포가 왜 중요한가?

수많은 프로젝트들이 여전히 ‘수동 배포’라는 낡은 관성에서 벗어나지 못하고 있습니다. FTP로 파일을 전송하고, SSH로 서버에 접속해 배포 스크립트를 돌리는 과정은 비효율적일 뿐 아니라, 언제든 실수를 유발할 수 있는 위험 요소입니다.

CI/CD(Continuous Integration / Continuous Delivery or Deployment)는 이러한 문제를 해결하기 위한 최적의 방법론입니다. 코드가 저장소에 커밋되는 순간부터 테스트, 빌드, 배포까지의 전 과정을 자동화함으로써, 빠르고 안정적인 개발 사이클을 가능하게 만듭니다. 특히 팀 규모가 커지고 배포 빈도가 높아질수록 자동화는 선택이 아닌 필수가 됩니다.

이 글에서는 “Jenkins + GitLab CI/CD”라는 강력한 조합을 통해 실제 현장에서 바로 사용할 수 있는 자동화 배포 파이프라인을 설계하고 구축하는 방법을 안내할 것입니다. 단순한 설정 소개를 넘어, 실전에서 마주칠 수 있는 장애 상황이나 보안 이슈까지 고려한 전략적 접근을 함께 소개합니다.

자동화된 파이프라인은 단지 ‘편리함’을 위한 도구가 아니라, 코드의 품질을 보장하고, 조직의 민첩성을 확보하는 **핵심 기반 인프라**입니다. 그만큼 신중하게, 그리고 깊이 있게 접근할 필요가 있습니다.

그렇다면, 본격적인 CI/CD 구성에 앞서 먼저 “CI/CD란 무엇인지”, 그리고 “왜 Jenkins와 GitLab을 선택했는지”에 대해 짚고 넘어가 보겠습니다.


2. CI/CD 개념 빠르게 짚고 가기

CI/CD는 단순한 자동화 그 이상의 의미를 지닙니다. 소프트웨어 개발과 배포 과정을 체계적으로 최적화하기 위한 전략적 프레임워크이며, 궁극적으로는 품질 보증과 민첩한 대응을 동시에 달성하는 방법론입니다.

✅ CI(지속적 통합, Continuous Integration)란?

지속적 통합은 개발자가 작성한 코드가 메인 브랜치에 통합되는 순간, 자동으로 빌드와 테스트가 실행되어 코드 품질을 지속적으로 검증하는 과정입니다. 이를 통해 다음과 같은 이점이 확보됩니다:

  • 버그 조기 발견 및 수정
  • 개발자 간의 충돌 최소화
  • 테스트 자동화로 QA 효율 향상

CI의 핵심은 코드가 메인 브랜치에 머지되기 전에 자동으로 검증되는 시스템을 갖추는 것입니다.

✅ CD(지속적 전달 vs. 지속적 배포)

CD는 크게 두 가지 방식으로 구분됩니다:

구분 설명 특징
지속적 전달 (Continuous Delivery) 코드가 테스트 및 빌드를 거쳐 프로덕션에 배포할 ‘준비 상태’까지 자동화됨 최종 배포는 수동으로 승인
지속적 배포 (Continuous Deployment) 코드가 자동으로 운영 환경까지 배포됨 전체 과정이 100% 자동화됨

CI는 코드 변경을 안전하게 통합하는 것을 의미하고, CD는 해당 코드를 사용자에게 신속하게 전달하는 것을 의미합니다. 둘은 함께 사용될 때 최대의 시너지를 발휘합니다.

💡 왜 이 개념을 알고 넘어가야 할까?

많은 개발자들이 CI/CD를 단지 “자동 빌드, 자동 배포”로만 생각하는 경우가 많습니다. 하지만 개념적 차이를 명확히 이해하고 있어야, 자신이 구축하려는 파이프라인의 목적과 범위를 올바르게 설정할 수 있습니다.

이제 CI/CD에 대한 핵심을 이해했으니, 다음 단계에서는 Jenkins와 GitLab CI/CD 각각의 도구가 어떤 역할을 맡을 수 있으며, 어떤 차이점이 있는지 구체적으로 비교해 보겠습니다.


3. Jenkins와 GitLab CI/CD 비교와 장단점

CI/CD를 구축하려 할 때 가장 먼저 고민하게 되는 것은 바로 도구 선택입니다. 그중에서도 Jenkins와 GitLab CI/CD는 각각 오랜 시간 사랑받아 온 대표적인 솔루션으로, 각기 다른 철학과 기능을 기반으로 하고 있습니다.

이 두 도구를 단순히 비교하는 것이 아니라, 서로 보완적인 구조로 함께 사용할 수 있다는 점이 핵심입니다. 실제로 많은 기업들이 GitLab의 코드 저장소와 Jenkins의 파이프라인 유연성을 결합하여 최적의 CI/CD 환경을 구축하고 있습니다.

🔍 Jenkins란 무엇인가?

Jenkins는 Java 기반의 오픈소스 자동화 서버로, 다양한 플러그인을 통해 개발, 빌드, 테스트, 배포까지 모든 작업을 자동화할 수 있습니다. 특히 자유도와 유연성이 높아 복잡한 빌드/배포 파이프라인 구성에 강점을 보입니다.

🔍 GitLab CI/CD란 무엇인가?

GitLab CI/CD는 GitLab에 내장된 통합형 CI/CD 시스템으로, .gitlab-ci.yml 파일만 작성하면 저장소 중심으로 자동화된 파이프라인을 구축할 수 있습니다. GitLab Runner라는 실행 엔진을 통해 파이프라인 단계를 처리합니다.

📊 기능 비교

항목 Jenkins GitLab CI/CD
설치 및 구성 독립 서버에 설치 필요, 초기 설정 복잡 GitLab 내장 기능으로 즉시 사용 가능
플러그인 생태계 수천 개의 플러그인으로 무한 확장 가능 기본 제공 기능 중심, 확장성은 낮음
학습 곡선 Groovy 기반 파이프라인 DSL, 진입 장벽 있음 YAML 기반 구성, 비교적 직관적
사용 목적 복잡한 워크플로우 구현, 고도화된 파이프라인 단순하고 빠른 통합 작업에 적합

💡 함께 사용할 때의 시너지

GitLab은 깔끔한 UI와 Git 저장소 중심의 관리 기능을 제공하며, Jenkins는 복잡한 연동과 조건 분기, 고급 처리 로직을 손쉽게 구성할 수 있습니다. 두 도구를 연동하면 다음과 같은 이점이 있습니다:

  • GitLab은 코드 중심의 간결한 파이프라인을 관리
  • Jenkins는 커스터마이징된 배포 로직, 외부 서비스 연동을 담당
  • 서로의 부족한 기능을 보완하며 확장성 있는 구조 완성

따라서 본 포스팅에서는 GitLab을 기반 저장소 및 기본 파이프라인 작성 도구로 사용하고, Jenkins를 통해 고급 처리와 배포 로직을 통합하는 방식으로 구성해 나갈 것입니다.

이제 본격적으로 우리가 구축할 파이프라인의 시나리오를 소개하고, 어떤 흐름으로 자동화를 구성할 것인지 설명하겠습니다.


4. 시나리오 소개: 우리가 구축할 파이프라인

이제부터는 이론을 넘어 실전입니다. 본 가이드에서는 실제 현업에서 널리 활용되는 구조를 바탕으로, 프론트엔드와 백엔드가 공존하는 풀스택 애플리케이션의 CI/CD 파이프라인을 단계적으로 구축해보겠습니다.

🎯 구축 대상 프로젝트 개요

우리가 다룰 예제는 다음과 같은 구성의 웹 애플리케이션입니다:

  • Frontend: React 기반 SPA (Single Page Application)
  • Backend: Spring Boot 기반 RESTful API 서버
  • Database: PostgreSQL
  • 배포 환경: AWS EC2 (테스트/운영 분리), Docker 기반 컨테이너 환경

🔁 전체 파이프라인 흐름

CI/CD 파이프라인은 다음과 같은 흐름으로 구성됩니다:

  1. 개발자가 GitLab 저장소의 feature/* 브랜치에 기능 커밋
  2. Merge Request 생성 → 자동 빌드 및 테스트 실행
  3. QA 승인 후 develop 브랜치로 병합 → Staging 서버에 자동 배포
  4. 검증이 끝나면 main 브랜치로 머지 → 운영 서버에 자동 배포

🔧 Jenkins와 GitLab 역할 분담

본 예제에서는 다음과 같이 역할을 분담합니다:

도구 주요 역할
GitLab CI 코드 변경 감지, 기본 빌드/테스트 트리거링, 환경 변수 관리
Jenkins 고급 파이프라인 정의, Docker 이미지 빌드 및 배포 자동화, 외부 시스템 연동

🧱 기술 스택 및 요구 사항

  • Jenkins: 최신 LTS 버전, Docker 설치
  • GitLab: SaaS 또는 Self-hosted (GitLab Runner 포함)
  • 배포 대상: AWS EC2 인스턴스, Ubuntu 20.04 이상
  • 빌드 도구: Maven, npm, Docker CLI

📌 우리가 구축할 주요 CI/CD 단계

  1. 코드 푸시 감지 및 파이프라인 트리거
  2. React 앱 빌드 및 테스트
  3. Spring Boot API 서버 빌드 및 테스트
  4. Docker 이미지 생성 및 레지스트리에 푸시
  5. Staging/Production 서버로 자동 배포

위 흐름을 바탕으로, 각 도구의 설정 방법과 구성 예제를 하나씩 살펴보며, 실제로 사용할 수 있는 자동화 파이프라인을 완성해 나가겠습니다. 다음 단계는 GitLab에서 프로젝트 저장소를 구성하고 CI 파이프라인의 시작점을 만드는 것입니다.


5. GitLab 설정: 저장소, Runner 등록, CI 파일 구성

GitLab은 코드 저장소부터 이슈 관리, CI/CD까지 통합된 기능을 제공하는 DevOps 플랫폼입니다. 이번 단계에서는 GitLab에서 CI 파이프라인의 출발점이 되는 설정들을 하나씩 진행해보겠습니다.

🔹 1단계: GitLab 프로젝트 생성

먼저, 프론트엔드와 백엔드 각각의 저장소를 다음과 같이 생성합니다:

  • frontend – React 앱 소스 저장소
  • backend – Spring Boot 소스 저장소

GitLab에 로그인한 후, New ProjectCreate from template 또는 Blank Project를 선택하여 각각 생성하세요.

🔹 2단계: GitLab Runner 설치 및 등록

CI/CD를 실행할 환경인 Runner는 프로젝트 또는 그룹 단위로 등록할 수 있습니다. 가장 일반적인 방법은 VM 혹은 EC2에 Runner를 직접 설치하는 것입니다.

다음은 Ubuntu 서버 기준 GitLab Runner 설치 예시입니다:

curl -L --output gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
sudo mv gitlab-runner /usr/local/bin/gitlab-runner
sudo chmod +x /usr/local/bin/gitlab-runner
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
sudo gitlab-runner start

설치가 완료되면 다음 명령으로 Runner를 GitLab에 등록합니다:

sudo gitlab-runner register
# GitLab URL 입력: https://gitlab.com or self-hosted URL
# Token 입력: GitLab에서 제공된 registration token
# Executor 선택: docker, shell, ssh 중 선택 (예: docker)
# 태그 입력: 예) node, java, deploy 등

🔹 3단계: .gitlab-ci.yml 구성

이제 프로젝트 루트에 .gitlab-ci.yml 파일을 작성하여 파이프라인 흐름을 구성합니다. 예를 들어, React 프론트엔드 저장소의 기본 설정은 다음과 같습니다:

stages:
  - install
  - test
  - build

variables:
  NODE_ENV: "development"

cache:
  paths:
    - node_modules/

install-dependencies:
  stage: install
  script:
    - npm install

unit-tests:
  stage: test
  script:
    - npm run test

build-app:
  stage: build
  script:
    - npm run build
  artifacts:
    paths:
      - dist/

Spring Boot 백엔드도 유사한 방식으로 구성하되, Java 및 Maven 중심의 빌드와 테스트 스크립트를 포함시킵니다:

stages:
  - build
  - test

variables:
  MAVEN_CLI_OPTS: "-B -DskipTests"

build:
  stage: build
  script:
    - mvn $MAVEN_CLI_OPTS clean package

test:
  stage: test
  script:
    - mvn test

📌 CI 설정 팁

  • Runner 태그only, tags 키워드로 파이프라인 실행 조건을 세밀하게 설정할 수 있습니다.
  • 변수(variables)를 통해 환경별 구성도 유연하게 조절 가능합니다.
  • artifacts는 다음 단계로 파일을 넘기거나 배포 시 활용하는 데 유용합니다.

이제 GitLab 저장소는 커밋만 해도 자동으로 빌드 및 테스트가 실행되는 상태가 되었습니다. 하지만 아직은 코드 품질 검증까지만 구성된 상태입니다. 다음 단계에서는 Jenkins를 통해 이 흐름을 외부 서비스와 연결하고, 실제 배포가 가능한 수준으로 확장해 보겠습니다.


6. Jenkins 설치 및 초기 설정

Jenkins는 오픈소스 기반의 자동화 서버로, 복잡한 파이프라인 구성을 유연하게 처리할 수 있다는 점에서 대규모 프로젝트나 다양한 배포 전략을 사용하는 팀에서 자주 활용됩니다. 이번 단계에서는 Jenkins의 설치와 초기 설정 과정을 중심으로 설명하겠습니다.

🔹 1단계: Jenkins 설치 (Ubuntu 기준)

Jenkins는 Java 기반으로 동작하므로 Java 환경이 먼저 준비되어야 합니다. 아래는 Ubuntu 환경에서 Jenkins를 설치하는 기본적인 과정입니다:

sudo apt update
sudo apt install openjdk-17-jdk -y
wget -q -O - https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add -
sudo sh -c 'echo deb https://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
sudo apt update
sudo apt install jenkins -y
sudo systemctl enable jenkins
sudo systemctl start jenkins

설치 후 Jenkins는 기본적으로 http://localhost:8080에서 접근 가능합니다.

🔹 2단계: 초기 접속 및 관리자 비밀번호 확인

웹 브라우저에서 http://<your-server-ip>:8080에 접속하면 초기 설정 화면이 나타납니다. 다음 명령어로 관리자 비밀번호를 확인합니다:

sudo cat /var/lib/jenkins/secrets/initialAdminPassword

비밀번호를 입력한 후, 플러그인 설치 화면이 나타납니다. “Suggested plugins 설치”를 선택해 기본 플러그인을 자동 설치하세요.

🔹 3단계: 필수 플러그인 확인 및 추가 설치

Jenkins의 유연함은 다양한 플러그인으로부터 나옵니다. CI/CD 구축에 꼭 필요한 주요 플러그인은 다음과 같습니다:

플러그인 설명
GitLab Plugin GitLab Webhook 및 인증 연동용
Pipeline Declarative 및 Scripted Pipeline 지원
Docker Pipeline Docker 컨테이너 내에서 빌드/배포 수행
Credentials Binding 시크릿 키, 토큰 등 보안 정보 설정

🔹 4단계: 사용자 계정 및 권한 설정

설치 직후에는 관리자 계정이 유일한 사용자입니다. 운영 중인 Jenkins에서는 반드시 다음과 같은 보안 조치를 취해야 합니다:

  • 개발자별 계정 등록 및 역할별 권한 그룹 구성
  • API 토큰, SSH 키 등의 민감 정보는 Credentials 플러그인으로 관리
  • GitLab OAuth를 이용한 Single Sign-On(SSO) 연동도 고려

📌 Jenkins 구성 체크리스트

  • Jenkins는 항상 LTS 버전 사용을 권장합니다.
  • 서버 자원(메모리, 디스크 용량)을 주기적으로 모니터링하세요.
  • 플러그인 업데이트와 백업 설정을 주기적으로 점검하세요.

이제 Jenkins 서버는 기본 설정을 마쳤고, 다양한 프로젝트와 파이프라인을 구성할 준비가 되었습니다. 다음 단계에서는 Jenkins와 GitLab을 연동하여 코드 변경을 자동으로 감지하고, 빌드와 배포를 트리거할 수 있는 구조를 만들겠습니다.


7. Jenkins + GitLab 연동하기

Jenkins와 GitLab을 연동하면 GitLab의 저장소에서 발생한 코드 변경 사항(예: Push, Merge Request 등)을 Jenkins가 감지하고, 자동으로 파이프라인을 실행할 수 있게 됩니다. 이러한 자동화는 Webhook, API 토큰, Jenkins Job 설정을 통해 실현됩니다.

🔹 1단계: GitLab Plugin 설치 확인

Jenkins에서 GitLab과의 연동을 위해 필요한 플러그인은 다음과 같습니다:

  • GitLab Plugin – GitLab 이벤트 수신 및 인증 연동
  • Git plugin – Git 저장소 클론 및 브랜치 추적 기능

Jenkins 관리 → 플러그인 관리 → 설치됨 탭에서 두 플러그인이 설치되어 있는지 확인하고, 없다면 ‘사용 가능’ 탭에서 설치하세요.

🔹 2단계: Jenkins에 GitLab 인증 정보 등록

GitLab은 Jenkins에서 저장소에 접근하거나 커밋 상태를 업데이트할 때 인증이 필요합니다. 이를 위해 Personal Access Token을 생성하고 Jenkins에 등록해야 합니다.

  1. GitLab → User Settings → Access Tokens → api, read_repository 권한 선택
  2. 생성된 토큰을 복사
  3. Jenkins → Credentials → System → Global → Add Credentials
  4. Kind: Secret text / Secret: 토큰 값 / ID: gitlab-token 등 지정

🔹 3단계: GitLab 저장소에 Webhook 설정

GitLab 저장소에서 Jenkins로 이벤트를 전송하려면 Webhook을 등록해야 합니다.

  1. GitLab 프로젝트 → Settings → Webhooks
  2. URL: http://your-jenkins-url/project/your-job-name
  3. Trigger: Push events, Merge request events 체크
  4. Secret Token: Jenkins Job 구성 시 동일하게 입력

Jenkins Job이 GitLab에서 오는 Webhook을 수신하려면 GitLab Plugin이 설치된 상태에서 GitLab Project 설정이 필요합니다.

🔹 4단계: Jenkins에서 Freestyle Job 또는 Pipeline Job 생성

가장 간단한 연동은 Freestyle Job을 이용해 구성할 수 있습니다. 그러나 고급 구성과 멀티스테이지 처리에는 Pipeline Job을 사용하는 것이 일반적입니다.

Pipeline Job 생성 절차:

  1. Jenkins 대시보드 → 새로 만들기 → Pipeline 선택
  2. 설정에서 다음 항목을 입력:
    • GitLab connection: 위에서 등록한 token 인증 정보 선택
    • Pipeline script from SCM: 선택 후 Git 저장소 정보 입력
    • SCM: Git / Repository URL: GitLab 저장소 주소
    • Credentials: GitLab 인증 정보 선택

그 후 Jenkinsfile을 저장소 루트에 구성하여 파이프라인 스크립트를 자동으로 인식하도록 설정합니다.

🔹 5단계: Jenkinsfile 기본 예제

다음은 Spring Boot 프로젝트를 Jenkins에서 빌드하고 Docker 이미지로 생성하는 기본 예시입니다:

pipeline {
    agent any

    environment {
        REGISTRY = "your-docker-registry"
        IMAGE_NAME = "spring-api"
    }

    stages {
        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://gitlab.com/your/project.git'
            }
        }

        stage('Build') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
        }

        stage('Docker Build & Push') {
            steps {
                script {
                    docker.build("${REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER}")
                          .push()
                }
            }
        }
    }
}

📌 실전 팁

  • Webhook 연결 시, 방화벽 또는 프록시 설정으로 인해 이벤트 수신이 안 되는 경우가 많습니다. 포트(8080) 개방 확인 필수
  • Jenkins URL은 외부에서 접근 가능한 도메인이거나 IP여야 함
  • Job이 수신한 Webhook 요청 내역은 GitLab Hook Log에서 확인 가능

이제 Jenkins는 GitLab으로부터 자동으로 코드를 받아 빌드와 배포를 수행할 수 있는 상태가 되었습니다. 다음 단계에서는 본격적으로 멀티 스테이지 파이프라인을 구성하고, 각 단계별 코드 예제를 통해 전체 흐름을 완성해 나가겠습니다.


8. 실제 파이프라인 구성 예제 (코드 포함)

이번에는 실제로 Jenkins에서 동작할 수 있는 엔드 투 엔드(End-to-End) CI/CD 파이프라인을 구성해보겠습니다. 이 파이프라인은 프론트엔드와 백엔드를 각각 빌드한 뒤, Docker 이미지로 통합하여 배포 서버에 자동으로 배포하는 구조를 가집니다.

🔹 파이프라인 흐름 요약

  1. 프론트엔드 React 앱 빌드
  2. 백엔드 Spring Boot 앱 빌드
  3. 각각의 Docker 이미지 생성 및 레지스트리에 푸시
  4. Staging 또는 Production 서버로 자동 배포 (SSH)

🔧 Jenkinsfile 예제: 풀스택 CI/CD 파이프라인

아래는 백엔드와 프론트엔드를 통합한 Jenkins Pipeline 스크립트입니다:

pipeline {
    agent any

    environment {
        DOCKER_REGISTRY = "your-registry.com"
        FRONT_IMAGE = "react-frontend"
        BACK_IMAGE = "spring-backend"
        DEPLOY_USER = "ubuntu"
        DEPLOY_HOST = "your-server-ip"
        DEPLOY_DIR = "/home/ubuntu/deploy"
    }

    stages {

        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://gitlab.com/your/project.git'
            }
        }

        stage('Frontend Build') {
            steps {
                dir('frontend') {
                    sh '''
                    npm install
                    npm run build
                    '''
                }
            }
        }

        stage('Backend Build') {
            steps {
                dir('backend') {
                    sh 'mvn clean package -DskipTests'
                }
            }
        }

        stage('Docker Build & Push') {
            steps {
                script {
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-credentials-id') {
                        dir('frontend') {
                            def front = docker.build("${DOCKER_REGISTRY}/${FRONT_IMAGE}:${BUILD_NUMBER}")
                            front.push()
                        }

                        dir('backend') {
                            def back = docker.build("${DOCKER_REGISTRY}/${BACK_IMAGE}:${BUILD_NUMBER}")
                            back.push()
                        }
                    }
                }
            }
        }

        stage('Deploy to Server') {
            steps {
                sshagent(['ssh-credentials-id']) {
                    sh """
                    ssh ${DEPLOY_USER}@${DEPLOY_HOST} 'docker pull ${DOCKER_REGISTRY}/${FRONT_IMAGE}:${BUILD_NUMBER} && docker pull ${DOCKER_REGISTRY}/${BACK_IMAGE}:${BUILD_NUMBER}'
                    ssh ${DEPLOY_USER}@${DEPLOY_HOST} 'docker-compose -f ${DEPLOY_DIR}/docker-compose.yml up -d'
                    """
                }
            }
        }
    }

    post {
        success {
            echo '✅ 배포가 완료되었습니다.'
        }
        failure {
            echo '❌ 배포에 실패했습니다. 로그를 확인하세요.'
        }
    }
}

📌 주요 포인트 해설

  • dir() 명령어를 활용해 프론트/백엔드 디렉토리 분리
  • docker.withRegistry()로 Docker Hub 또는 프라이빗 레지스트리에 로그인
  • sshagent()를 통해 원격 서버에 접속 및 배포 자동화
  • post 블록으로 성공/실패 후 행동 정의

🔐 보안 정보 처리

Jenkins의 Credentials 시스템을 활용해 다음과 같은 민감 정보를 안전하게 관리할 수 있습니다:

  • docker-credentials-id: Docker 로그인 정보
  • ssh-credentials-id: 배포 서버 SSH 키

해당 정보를 Jenkins 관리 → Credentials → Global → Add Credentials에서 사전에 등록해야 합니다.

🧪 테스트와 QA 연동 확장

실제 프로젝트에서는 유닛 테스트, 통합 테스트, QA 승인 프로세스를 추가할 수 있습니다. 다음과 같은 추가 단계를 고려해 보세요:

  • Frontend: npm run test → 커버리지 검증
  • Backend: mvn test + JUnit 결과 리포트
  • Slack, Mattermost: 배포 알림 연동

이로써 단일 Jenkinsfile로 전체 빌드부터 배포까지 자동화된 파이프라인을 완성할 수 있습니다. 다음 단계에서는 테스트 자동화 구성과 연동에 대해 자세히 살펴보겠습니다.


9. 테스트 자동화: 유닛/통합 테스트 연계

CI/CD 파이프라인의 목적은 단순히 코드를 자동 배포하는 것이 아닙니다. 진정한 가치는 코드 품질의 자동 검증에 있으며, 이는 테스트 자동화를 통해 달성됩니다. 유닛 테스트는 기능 단위의 오류를 조기에 탐지하고, 통합 테스트는 시스템 간 연계 문제를 사전에 검증하는 데 핵심적인 역할을 합니다.

🔎 유닛 테스트란?

유닛 테스트(Unit Test)는 클래스 또는 함수 단위의 최소 기능을 독립적으로 검증하는 테스트입니다. 예를 들어, 계산기의 덧셈 함수가 정확한 값을 반환하는지 확인하는 것이 이에 해당합니다.

🔎 통합 테스트란?

통합 테스트(Integration Test)는 여러 컴포넌트가 함께 작동할 때의 정합성을 검증합니다. API 요청과 응답, DB 연동 등 실제 운영에 가까운 시나리오를 구성하여 테스트합니다.

🧪 Jenkins 파이프라인에 테스트 연동하기

Jenkins에서는 테스트 실행 → 리포트 수집 → 실패 시 파이프라인 중단까지 자동으로 처리할 수 있습니다.

Spring Boot 백엔드 예제 기준, 테스트 실행 단계는 다음과 같이 구성할 수 있습니다:

stage('Run Tests') {
    steps {
        dir('backend') {
            sh 'mvn test'
            junit 'target/surefire-reports/*.xml'
        }
    }
}
  • mvn test는 테스트 실행 명령어입니다.
  • junit 스텝은 테스트 리포트를 수집하고 Jenkins UI에서 시각화합니다.

🧪 프론트엔드 테스트 연동

React 프로젝트에서 Jest를 사용하는 경우, --ci --coverage 옵션으로 리포트를 생성할 수 있습니다:

npm run test -- --ci --coverage

Jenkins에서는 다음과 같이 커버리지 리포트를 HTML로 시각화할 수 있습니다:

stage('Frontend Tests') {
    steps {
        dir('frontend') {
            sh 'npm install'
            sh 'npm run test -- --ci --coverage'
            publishHTML(target: [
                reportDir: 'coverage/lcov-report',
                reportFiles: 'index.html',
                reportName: 'React Coverage Report'
            ])
        }
    }
}

📊 테스트 리포트 시각화

Jenkins는 다양한 플러그인을 통해 테스트 결과를 시각적으로 보여줍니다. 주요 플러그인은 다음과 같습니다:

플러그인 설명
JUnit Plugin Java 테스트 결과(junit xml)를 Jenkins UI에 시각화
HTML Publisher Plugin 테스트 커버리지 등 HTML 리포트 출력
Warnings Next Generation 정적 분석 도구(Sonar, ESLint 등) 결과 시각화

🛑 품질 게이트 구성

테스트 실패 시 파이프라인을 자동으로 중단하는 설정은 품질 확보에 매우 중요합니다. 다음과 같이 catchErrorfailFast 정책을 통해 테스트 실패를 감지할 수 있습니다:

catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
    sh 'mvn test'
}

📌 테스트 자동화 베스트 프랙티스

  • 단계별 유닛/통합 테스트 분리하여 관리
  • Pull Request 시 자동 테스트 실행 필수화
  • 배포 전 최소 커버리지 기준 설정 (예: 80%)
  • Slack, 이메일 등으로 테스트 실패 알림 연동

테스트 자동화는 단지 ‘코드가 돌아간다’는 것을 확인하는 것이 아니라, 배포 가능한 품질의 보장선을 설정하는 것입니다. 다음 단계에서는 성공적으로 테스트를 통과한 코드가 어떻게 자동으로 Staging 및 Production에 배포되는지 살펴보겠습니다.


10. 자동 배포 구성: Staging → Production까지

CI/CD의 종착지는 ‘배포’입니다. 자동화된 테스트를 통과한 코드를 어떻게 신뢰할 수 있는 방식으로 운영 서버에 반영할 것인가는 매우 중요한 결정입니다. 이번 단계에서는 Jenkins를 활용해 Staging과 Production 환경에 자동으로 배포하는 구조를 설계하고, 신중하게 전환하는 방식을 다룹니다.

📌 Staging과 Production 환경의 구분

환경 설명 전형적인 배포 조건
Staging 운영과 유사한 테스트 서버 환경 develop 브랜치 병합 시
Production 최종 사용자에게 서비스되는 운영 환경 main/master 브랜치 병합 시

각 환경은 별도의 서버 또는 Docker Compose 파일로 관리되며, Jenkins에서는 브랜치 조건에 따라 환경을 분기하여 처리할 수 있습니다.

🔧 브랜치 기반 배포 분기

environment {
    TARGET_ENV = (env.BRANCH_NAME == 'main') ? 'production' : 'staging'
}

이처럼 브랜치명을 기준으로 대상 환경을 동적으로 결정하고, 해당 환경에 맞는 배포 명령을 수행할 수 있습니다.

🚀 Jenkins에서 SSH 자동 배포 구성

서버에 SSH로 접속하여 Docker 컨테이너를 갱신하고, 운영 환경을 무중단으로 전환할 수 있습니다.

stage('Deploy to Target') {
    steps {
        sshagent(['ssh-credentials-id']) {
            sh """
            ssh ubuntu@${DEPLOY_HOST} '
                docker pull ${DOCKER_REGISTRY}/${APP_IMAGE}:${BUILD_NUMBER} &&
                docker-compose -f ${DEPLOY_DIR}/docker-compose-${TARGET_ENV}.yml up -d
            '
            """
        }
    }
}

여기서 docker-compose-staging.ymldocker-compose-production.yml 파일을 별도로 구성하여 환경별 설정을 분리합니다.

📦 Docker Compose 예시

아래는 Production 환경을 위한 예시입니다:

version: '3.8'

services:
  frontend:
    image: your-registry.com/react-frontend:latest
    ports:
      - "80:80"
    restart: always

  backend:
    image: your-registry.com/spring-backend:latest
    ports:
      - "8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: prod
    restart: always

🛡️ 운영 배포 시 고려할 점

  • 배포 전 스냅샷 백업 또는 이미지 태깅 필수
  • 배포 이력 로깅: 배포자, 시간, 버전 기록
  • 롤백 전략: 이전 Docker 태그로 재배포 가능하게 구성
  • Blue/Green 또는 Canary 배포 전략도 고려

📌 배포 완료 후 알림 구성

Jenkins는 Slack, Email 등 다양한 알림 시스템과 연동이 가능합니다. 예를 들어 Slack 알림 구성은 다음과 같이 설정할 수 있습니다:

post {
    success {
        slackSend(channel: '#deployments', message: "✅ ${TARGET_ENV} 배포 완료")
    }
    failure {
        slackSend(channel: '#deployments', message: "❌ ${TARGET_ENV} 배포 실패. 확인 요망")
    }
}

이제 Jenkins는 브랜치 기준으로 자동으로 환경을 분기하여, 코드가 Staging을 거쳐 Production까지 매끄럽게 반영될 수 있는 구조를 갖추게 되었습니다. 다음 단계에서는 배포 중 발생할 수 있는 에러 상황과 대응 전략, 그리고 로그 관리를 어떻게 자동화할 수 있을지 살펴보겠습니다.


11. 에러 핸들링과 로깅: 문제 발생 시 대처 전략

CI/CD 파이프라인은 자동화되어 있지만, 모든 것이 항상 정상적으로 작동하는 것은 아닙니다. 빌드 실패, 테스트 오류, 배포 중 예외 등 다양한 문제들이 발생할 수 있으며, 이러한 상황에 효과적으로 대응하지 못하면 심각한 장애로 이어질 수 있습니다.

🚨 파이프라인 에러 발생 시 자동 중단

Jenkins에서는 각 Stage별로 에러 발생 시 전체 파이프라인을 중단하거나 예외 처리를 할 수 있습니다. 특히 테스트 실패 → 배포 중단은 기본 전략으로 구성해야 합니다.

stage('Build') {
    steps {
        catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
            sh 'mvn clean package'
        }
    }
}

이 구조는 빌드 실패 시 Jenkins UI에서 해당 스테이지를 붉은색으로 표시하고, 전체 프로세스를 종료시킵니다.

🧾 오류 로그 추적 및 분석

Jenkins는 각 Job의 콘솔 로그를 기본적으로 제공하지만, 보다 체계적인 분석을 위해 외부 로그 시스템과 연동하는 것이 좋습니다.

  • ELK Stack (Elasticsearch + Logstash + Kibana)
  • Grafana + Loki
  • Promtail + CloudWatch (AWS)

Docker 컨테이너의 로그는 docker logs 명령으로 확인하거나, 로그 드라이버(json-file, syslog 등)를 활용해 중앙집중화가 가능합니다.

🔔 에러 발생 시 알림 전송

빠른 대응을 위해 Jenkins는 Slack, 이메일, Webhook 등을 활용해 알림을 보낼 수 있습니다. Slack 알림 예시는 다음과 같습니다:

post {
    failure {
        slackSend (
            channel: "#ci-alerts",
            message: "❗ 파이프라인 실패: ${env.JOB_NAME} - #${env.BUILD_NUMBER}",
            color: "danger"
        )
    }
}

🧯 실패 시 롤백 전략

배포 이후 오류가 발생한 경우, 자동 또는 반자동 롤백 시스템이 중요합니다. 다음은 Docker 기반 프로젝트의 예시입니다:

docker pull your-image:previous-stable
docker tag your-image:previous-stable your-image:latest
docker-compose up -d

Jenkins에서도 빌드 성공 시마다 이미지 태그를 versioning하여 rollback 지점을 확보하는 것이 좋습니다:

def tag = (currentBuild.previousBuild != null) ? currentBuild.previousBuild.number : "latest"

📌 Jenkins 장애 대응 전략 요약

  • 각 스테이지 별 예외 처리 및 로그 기록 강화
  • Slack/Email/Webhook 기반 빠른 알림 시스템 구축
  • 최소 1단계 이상의 rollback 전략 마련
  • 로그 중앙집중화 및 장기 보관 설정

에러는 피할 수 없지만, 그에 대한 대비와 신속한 대응은 충분히 가능하고 자동화할 수 있습니다. 다음 단계에서는 보다 장기적인 관점에서 파이프라인을 지속적으로 개선하고 최적화하는 전략을 살펴보겠습니다.


12. 보안 고려사항 및 권한 관리 팁

CI/CD 파이프라인은 강력한 자동화 수단이지만, 동시에 민감한 자격 정보와 운영 서버 접근 권한을 포함한 ‘핵심 보안 경로’이기도 합니다. 잘못된 설정 하나로 인해 비밀번호, 토큰, 인증서 등이 외부에 유출될 수 있으며, 이는 코드보다 더 치명적인 사고로 이어질 수 있습니다.

🔐 자격 정보는 반드시 암호화된 저장소에 보관

CI/CD에서 사용되는 토큰, API 키, 비밀번호 등은 코드에 하드코딩해서는 절대 안 됩니다. Jenkins에서는 다음과 같이 Credentials 시스템을 활용해 안전하게 관리할 수 있습니다:

  • Jenkins Dashboard → Credentials → Global → Add Credentials
  • Username/Password, Secret Text, SSH key 등 다양한 유형 지원
  • Jenkinsfile에서는 ID만 참조하여 노출 없이 사용 가능
withCredentials([string(credentialsId: 'docker-token', variable: 'DOCKER_TOKEN')]) {
    sh 'docker login -u myuser -p $DOCKER_TOKEN'
}

🧱 환경 변수 보안 관리

환경 변수는 유연한 구성에 유용하지만, 민감한 정보가 포함될 경우 노출 위험이 있습니다. Jenkins에서는 다음 사항을 지켜야 합니다:

  • 환경변수는 Credentials에 위임하고, Jenkinsfile 내에 직접 노출 금지
  • Build With Parameters 기능을 사용할 경우, 민감 정보는 숨김 처리
  • 환경별로 분리된 변수를 설정해 오용 방지

🔒 GitLab 저장소 권한 구성

GitLab 저장소에도 다음과 같은 권한 구조를 적용하여 접근을 최소화해야 합니다:

역할 권한 범위
Reporter 코드 열람만 가능 (일반 QA 권한)
Developer 브랜치 생성/푸시 가능 (기능 개발자 권한)
Maintainer 머지, Webhook 설정 등 전체 관리 (CI 관리자 권한)

🧪 코드 수준 보안 검사 연동

Jenkins 파이프라인에 정적 분석 도구(Static Code Analysis)를 통합하면 코드 수준의 취약점을 조기에 탐지할 수 있습니다. 대표적인 도구는 다음과 같습니다:

  • SonarQube: Java, JS 등 주요 언어 지원, 품질 게이트 기준 설정 가능
  • Trivy: Docker 이미지 취약점 검사
  • ESLint: 프론트엔드 보안 및 코드 규칙 점검
stage('Static Analysis') {
    steps {
        sh 'mvn sonar:sonar -Dsonar.projectKey=my-project'
    }
}

📌 DevSecOps 실천 요약

  • Credentials를 적극 활용하여 비밀번호/토큰 노출 방지
  • 환경별 변수 및 자격 정보는 분리 관리
  • 정적 분석, 취약점 스캐닝 도구를 파이프라인에 통합
  • GitLab 권한 분리 및 브랜치 보호 정책 적용
  • CI/CD 관리자 계정은 최소화하며 접근 제어 강화

CI/CD 보안은 단순히 비밀번호를 숨기는 것 이상의 영역입니다. 자동화의 모든 흐름 속에 보안도 함께 설계되는 것, 그것이 DevSecOps의 핵심입니다. 이제 마지막으로, 현재까지 구축한 파이프라인을 어떻게 지속적으로 개선하고, 운영 효율성을 높일 수 있는지 살펴보겠습니다.


13. 지속적인 개선: 파이프라인의 최적화 전략

CI/CD 파이프라인은 단순히 ‘자동화’가 목적이 아닙니다. 진정한 가치는 빠르고, 안정적이며, 품질이 보장된 배포를 얼마나 효율적으로 반복할 수 있는가에 있습니다. 이를 위해서는 구축 후에도 지속적인 점검과 최적화가 필수입니다.

⚡ 13.1. 빌드/테스트 시간 단축: 캐시 전략 활용

CI/CD의 속도는 개발자 경험과 배포 민첩성에 큰 영향을 미칩니다. 다음은 빌드/테스트 속도를 높이는 실용 전략입니다:

  • 의존성 캐시: node_modules/, .m2/repository 등을 캐싱
  • 중복 없는 변경 감지: 변경된 모듈만 빌드
  • Docker 레이어 캐시: 빈번히 변경되는 파일은 마지막에 COPY
cache:
  paths:
    - .m2/repository
    - node_modules

⚙️ 13.2. 파이프라인 병렬화

Jenkins는 여러 Job 또는 스테이지를 병렬로 실행할 수 있어 전체 처리 시간을 크게 줄일 수 있습니다.

stage('Test') {
    parallel {
        stage('Frontend Tests') {
            steps {
                dir('frontend') {
                    sh 'npm test'
                }
            }
        }
        stage('Backend Tests') {
            steps {
                dir('backend') {
                    sh 'mvn test'
                }
            }
        }
    }
}

📈 13.3. 데이터 기반 개선: 파이프라인 인사이트 수집

성능, 실패율, 실행 시간 등의 메트릭을 시각화하고 분석하면 병목 지점을 파악할 수 있습니다. 이를 위해 다음 도구와 연동이 효과적입니다:

  • Prometheus + Grafana: Jenkins Exporter를 통한 지표 수집
  • Datadog, New Relic: 통합 모니터링 플랫폼 연계
  • Build History Trends Plugin: Jenkins 내장 분석

🛠️ 13.4. 유지보수 용이성 향상

복잡한 파이프라인은 유지보수 비용이 높기 때문에 다음과 같은 기준을 지켜야 합니다:

  • Jenkinsfile 분리: 프로젝트별/환경별 별도 파일 구성
  • 템플릿화: 공통 스크립트, 파이프라인 함수화 (Shared Library)
  • 코드 리뷰 적용: Jenkinsfile도 Git으로 관리하며 MR 리뷰

🚦 13.5. 품질 게이트 및 정책 강화

파이프라인이 제대로 작동하더라도, 품질 기준이 느슨하면 실질적인 효과는 떨어집니다. 다음과 같은 기준을 정책적으로 적용하세요:

  • 테스트 커버리지 80% 이상 → SonarQube로 자동 검증
  • 배포는 반드시 코드 리뷰 + 승인 후 진행
  • 모든 주요 환경 변수는 암호화 관리

📌 CI/CD 개선 체크리스트

항목 점검 포인트
속도 불필요한 단계 제거, 캐시 적용, 병렬 처리
신뢰성 품질 게이트, 테스트 실패 감지 및 중단
유지보수 파이프라인 코드화, 리뷰 및 문서화

자동화는 ‘완성’이 아닌 ‘진화’의 과정입니다. 팀의 요구가 변할수록 파이프라인도 유연하게 대응할 수 있어야 하며, 그것이 진정한 DevOps 문화를 완성하는 방향입니다.

이제 마지막으로, 우리가 구축한 파이프라인의 전반적인 요점을 되짚고, 이 글이 전달하고자 했던 핵심 메시지를 정리해보겠습니다.


14. 맺으며: DevOps의 핵심, 지속 가능한 자동화

우리는 이 글을 통해 GitLab과 Jenkins를 기반으로 한 CI/CD 파이프라인 구축의 전 과정을 함께 걸어왔습니다. 아이디어에서 코드, 테스트를 거쳐 배포에 이르기까지, 자동화된 흐름은 개발팀의 민첩함과 제품의 신뢰성을 동시에 이끌어내는 강력한 동력이 됩니다.

그러나 진정한 CI/CD는 단순히 코드를 ‘자동으로 배포하는 것’을 넘어서야 합니다. 그 속에는 개발자의 책임을 자동화하고, 품질을 보장하며, 실패를 빠르게 감지하고, 팀 전체의 흐름을 일관되게 만드는 과정이 포함되어 있습니다.

✔️ 정리하며 되짚는 핵심 키워드

  • CI/CD는 빠름보다 ‘안정성과 반복 가능성’을 우선한다
  • Jenkins는 복잡성과 유연함을, GitLab은 일체성과 간결함을 제공한다
  • 배포 자동화는 단계적 접근이 핵심이며, Staging → Production으로의 흐름은 명확하게 분리되어야 한다
  • 테스트 자동화, 보안 관리, 에러 핸들링은 ‘선택’이 아니라 ‘기본’이다

자동화된 파이프라인을 성공적으로 구축한 후 진짜 중요한 질문은 이것입니다: “우리 팀의 개발 흐름은 이 시스템 안에서 더 나아질 수 있는가?”

DevOps는 기술이 아니라 문화입니다. 그 문화는 끊임없는 개선과 학습, 협업을 통해 만들어지며, 이 글에서 소개한 CI/CD는 그 문화를 위한 가장 실용적인 시작점입니다.

이제 여러분의 팀에서도, 자동화된 흐름 안에서 개발자답게 문제 해결에 집중할 수 있는 환경을 만들어가시길 바랍니다. 그리고 그 시스템은 단단하고, 반복 가능하며, 점점 더 좋아져야 합니다. 지속 가능한 자동화, 그것이 진짜 DevOps입니다.

지금 바로, 자동화된 미래를 위한 한 걸음을 시작해 보세요.

댓글 남기기

Table of Contents

Table of Contents