플젝에서 CI/CD
처음 CI/CD 파이프라인을 구축했을 때 상당히 기뻤다. 이게 없었을 때 소요되는 시간이 너무 아까웠다.. 코드를 수정한 후 push까지 마치면 ec2 인스턴스(혹은 GCP 인스턴스)로 접속해서 실행 중인 어플리케이션에 수정사항을 반영하는 작업을 일일이 진행했었기 때문이다. 다행히 해당 작업의 반은 스크립트로 해결할 수 있었지만 나머지 반은 가내수공업이었다. 그러니 테스트 빌드도 해주고, 직접 배포까지 해주는 기술은 무적권으로 도입해야 했다.
당시 처음 다뤄본 건 Github Actions였다. 소규모 팀프로젝트였기 때문에 CI와 CD 작업을 Github Actions 하나로 처리하기 충분했다. 그러나 점점 구현할 수 있는 폭이 넓어지고, 지금에 와선 사용자를 유치할 수 있도록 프로젝트를 준비하면서 더 확장성 있는 설계를 해봐야 하지 않을까란 생각이 들었다.
예를 들어, 테스트 파이프라인에서 단순 빌드만이 아니라 부하 및 성능 테스트까지 더해야 한다면? 복잡한 태스크를 추가해야 한다거나 온프레미스 환경에서 해당 작업을 실행해야만 한다면? 다양한 상황에 대응하고 적절한 솔루션을 선택하기 위해서 다룰 수 있는 도구는 다양할 필요가 있다. 물론 깊이도 게을리해서는 안된다.
그렇게 이번 프로젝트에서 Jenkins를 선택하였다. 비용최소화도 목적 중 하나였지만 선택한 이유는 다음과 같다.
- 배움을 더 우선시하고자 한다.
- 향후 상황에 따른 확장성이 필요하다.
- 확장성의 연장선이지만 클라우드 친화적인 플러그인이 다수 존재한다. s3 및 code deploy 등의 서비스를 곧바로 다룰 수 있다.
직접 프로젝트에서 다룬 이야기는 다음 기회에 하도록 하고 이번 글에서는 Jenkins에 대해서 설명하려고 한다. 혹시 설치가 궁금한 사람들은 공식 문서를 보자!
https://www.jenkins.io/download/
Download and deploy
Jenkins – an open source automation server which enables developers around the world to reliably build, test, and deploy their software
www.jenkins.io
Jenkins란?
Jenkins는 전 세계적으로 가장 널리 사용되는 오픈 소스 자동화 서버로, 지속적 통합(CI)과 지속적 배포(CD) 파이프라인을 구축하는 데 활용되는 강력한 도구다. 소프트웨어 개발 과정의 빌드, 테스트, 배포 과정을 자동화하여 개발팀의 생산성을 크게 향상시키는 역할을 한다. 개발자들이 코드를 변경할 때마다 자동으로 빌드하고 테스트하여 소프트웨어의 품질을 지속적으로 보장할 수 있게 해준다.
Jenkins의 핵심 가치
- 개발자 생산성 향상: 수동 반복 작업을 줄이고 개발에 집중할 수 있게 함
- 소프트웨어 품질 보장: 자동화된 퀄리티 게이트를 통해 소프트웨어의 품질을 지속적으로 확보
- 빠른 피드백: 코드 변경 사항에 대한 빠른 피드백으로 문제를 조기에 발견
- 휴먼 에러 방지: 자동화된 프로세스로 인간의 실수를 최소화
Jenkins를 사용하면 코드 변경 사항이 발생할 때마다 자동으로 빌드, 테스트, 배포 과정을 수행할 수 있어 개발자는 이러한 과정을 수동으로 진행할 필요가 없어진다. 이는 개발 속도를 높이고 코드 품질을 향상시킨다.
Jenkins의 역사
Hudson에서 Jenkins로의 전환
Jenkins의 이야기는 Hudson이라는 이름으로 시작된다.
- 2004~2005년: Sun Microsystems의 개발자인 Kohsuke Kawaguchi가 반복적인 빌드 문제를 해결하기 위해 Hudson을 개발
- 2009년: Sun이 Oracle에 인수되며 Hudson 프로젝트도 Oracle 소유로 이전
- 2011년: Oracle과 커뮤니티 간의 상표권 및 거버넌스 문제로 인해 Jenkins라는 이름으로 공식 포크됨
- 현재: Jenkins는 독립적인 프로젝트로 커뮤니티 중심의 발전을 이어가고 있음
CI/CD 패러다임과 Jenkins
"나이틀리 빌드를 망가뜨리지 말라!"라는 소프트웨어 개발 조직의 기본규칙에서 Jenkins의 필요성이 대두되었다. 이전에는 코드 커밋 전에 로컬 머신에서 빌드와 테스트를 수행해야 했지만, Jenkins의 등장으로 이러한 제한이 사라졌다.
Jenkins 아키텍처와 구조
Jenkins는 Master-Agent(이전에는 Master-Slave라고 불림) 구조를 기반으로 동작한다. 이러한 구조를 통해 작업을 효율적으로 분산하고 시스템 자원을 최적화할 수 있다.
+-------------------+ +-------------------+
| Jenkins Master | | Jenkins Agent |
| (Controller) | | (Node 1) |
| - 웹 UI 제공 | | - 실제 빌드 수행 |
| - 빌드 스케줄링 | | - 테스트 실행 |
| - 결과 저장/관리 | | - 배포 작업 |
+-------------------+ +-------------------+
^ ^
| |
v v
+-------------------+ +-------------------+
| 저장소 | | Jenkins Agent |
| - 설정파일, 로그 | | (Node 2) |
| - 플러그인 | | - 특정 환경 빌드 |
| - 보안정보 | | - 별도 작업 실행 |
+-------------------+ +-------------------+
Master/Controller
- 웹 UI 제공
- 빌드 스케줄링 및 작업 할당
- 빌드 결과 저장 및 로그 관리
- 에이전트 관리 및 모니터링
Agent/Node
- 실제 빌드, 테스트, 배포 작업 수행
- 다양한 환경(OS, 설정)에서 빌드 가능
- 하나 이상의 Executor(동시 작업 단위) 보유
- Label을 통한 특정 작업 할당 가능
통신 프로토콜
Master와 Agent 간 통신은 두 가지 방식으로 이루어진다:
- SSH 방식: Jenkins에 내장된 SSH 클라이언트를 통해 에이전트와 통신
- Java Web Start 방식: 에이전트에서 실행된 자바 애플리케이션이 마스터와 TCP 연결
에이전트 구성 유형
- 영구 에이전트(Permanent Agent): 항상 사용 가능한 상태로 유지
- 영구 도커 호스트 에이전트: Docker 환경의 에이전트
- 젠킨스 스웜 에이전트: 동적으로 에이전트를 추가할 수 있는 방식(도커 스웜과 유사)
- 동적 프로비저닝 도커 에이전트: 필요할 때만 Docker 컨테이너를 생성
- 동적 프로비저닝 쿠버네티스 에이전트: Kubernetes 클러스터 내에서 동적으로 생성
Jenkins 파이프라인과 동작 원리
Jenkins 파이프라인은 CI/CD 워크플로우를 코드로 정의하고 관리할 수 있는 일련의 플러그인이다. 파이프라인은 Jenkinsfile이라는 텍스트 파일에 정의되며, 이는 Git과 같은 소스 코드 저장소에서 관리할 수 있다.
파이프라인 종류
Jenkins 파이프라인을 작성하는 데에는 두 가지 문법이 존재한다.
- 선언형(Declarative) 파이프라인
pipeline {
agent any
stages {
stage('빌드') {
steps {
echo '애플리케이션을 빌드하는 중...'
// 빌드 명령어
}
}
stage('테스트') {
steps {
echo '애플리케이션을 테스트하는 중...'
// 테스트 명령어
}
}
stage('배포') {
steps {
echo '애플리케이션을 배포하는 중...'
// 배포 명령어
}
}
}
}
- 스크립트형(Scripted) 파이프라인
node {
stage('빌드') {
// 빌드 스크립트
echo '애플리케이션을 빌드하는 중...'
}
stage('테스트') {
// 테스트 스크립트
echo '애플리케이션을 테스트하는 중...'
}
stage('배포') {
// 배포 스크립트
echo '애플리케이션을 배포하는 중...'
}
}
선언형 vs 스크립트형 파이프라인
- 선언형 파이프라인: Jenkins 파이프라인의 최신 기능으로, 더 간단하고 풍부한 기능 제공
- 스크립트형 파이프라인: Groovy 기반으로 구축된 첫 번째 파이프라인으로, 더 엄격한 Groovy 구문 사용
- 두 방식 모두 Groovy DSL(Domain Specific Language)을 기반으로 작동
파이프라인 구성 요소
- Node: Jenkins 환경의 일부인 머신으로 파이프라인을 실행
- Stage: 빌드, 테스트, 배포와 같은 파이프라인의 개념적 단계
- Step: Shell 명령어 실행과 같은 하나의 태스크를 정의
Jenkins 주요 플러그인과 활용
Jenkins의 강력함은 다양한 플러그인을 통해 확장될 수 있다는 점이다. 필요에 따라 적합한 플러그인을 설치하여 기능을 확장할 수 있다.
필수 플러그인
- Pipeline 플러그인: CI/CD 파이프라인을 정의하고 실행
- Blue Ocean: 파이프라인을 시각적으로 더 보기 쉽게 표시하는 UI 제공
- Git 플러그인: Git 저장소와의 연동
- GitHub 플러그인: GitHub와의 통합
빌드 도구 플러그인
- Maven Integration 플러그인: Maven 프로젝트 빌드
- Gradle 플러그인: Gradle 프로젝트 빌드
코드 품질 및 테스트 플러그인
- JUnit 플러그인: JUnit 테스트 결과 시각화
- Cobertura 플러그인: 코드 커버리지 리포트 생성 및 시각화
- SonarQube 플러그인: 코드 품질 분석
알림 및 통지 플러그인
- Email Extension 플러그인: 빌드 상태를 이메일로 알림
- Slack Notification 플러그인: 빌드 결과를 Slack 채널에 알림
컨테이너 관련 플러그인
- Docker 플러그인: Docker 이미지 빌드 및 컨테이너 관리
- Kubernetes 플러그인: Kubernetes 클러스터와 통합
Jenkins 활용 사례 및 예시
Jenkins는 다양한 상황에서 활용될 수 있으며, 일반적인 CI/CD 파이프라인 구축 외에도 다양한 자동화 작업에 사용된다.
CI/CD 파이프라인 구축
Jenkins를 활용한 기본적인 CI/CD 파이프라인은 다음과 같은 흐름으로 구성된다.
코드 변경(커밋) → 자동 빌드 → 자동 테스트 → 패키징 → 배포(스테이징) → 승인 → 배포(프로덕션)
이러한 파이프라인은 Jenkinsfile을 통해 아래와 같이 구현할 수 있다.
pipeline {
agent any
stages {
stage('소스코드 체크아웃') {
steps {
git 'https://github.com/your-repo/your-project.git'
}
}
stage('빌드') {
steps {
sh 'mvn clean install'
}
}
stage('테스트') {
steps {
sh 'mvn test'
}
post {
always {
junit 'target/surefire-reports/*.xml'
}
}
}
stage('배포') {
steps {
sh './deploy.sh'
}
}
}
post {
success {
slackSend channel: '#jenkins-builds',
color: 'good',
message: "빌드 성공: 프로젝트 ${env.JOB_NAME} (${env.BUILD_NUMBER})"
}
failure {
slackSend channel: '#jenkins-builds',
color: 'danger',
message: "빌드 실패: 프로젝트 ${env.JOB_NAME} (${env.BUILD_NUMBER})"
}
}
}
GitHub 웹훅을 통한 자동화
GitHub의 코드 변경 사항을 감지하여 자동으로 빌드를 시작할 수 있다.
- Jenkins에 GitHub 플러그인 설치
- GitHub 저장소의 웹훅 설정에서 Jenkins URL 추가
- Jenkins 프로젝트에서 빌드 트리거 설정
Docker와의 통합
Jenkins를 통해 Docker 이미지를 빌드하고 배포하는 과정
pipeline {
agent any
stages {
stage('소스코드 체크아웃') {
steps {
git 'https://github.com/your-repo/your-project.git'
}
}
stage('도커 이미지 빌드') {
steps {
sh 'docker build -t your-app:${BUILD_NUMBER} .'
}
}
stage('도커 이미지 푸시') {
steps {
withCredentials([string(credentialsId: 'docker-hub', variable: 'DOCKERHUB_CREDENTIALS')]) {
sh 'docker login -u username -p ${DOCKERHUB_CREDENTIALS}'
sh 'docker push your-app:${BUILD_NUMBER}'
}
}
}
stage('애플리케이션 배포') {
steps {
sh 'kubectl apply -f deployment.yaml'
sh 'kubectl set image deployment/your-app your-app=your-app:${BUILD_NUMBER}'
}
}
}
}
분산 빌드 환경 구성
대규모 프로젝트의 경우, 여러 에이전트를 활용한 분산 빌드 환경을 구성할 수 있다.
- Jenkins 마스터에 에이전트 노드 추가
- 각 에이전트에 특정 라벨 부여 (예: linux, windows, docker)
- 파이프라인에서 특정 작업을 특정 에이전트에 할당
pipeline {
agent none
stages {
stage('리눅스 빌드') {
agent {
label 'linux'
}
steps {
sh './build-linux.sh'
}
}
stage('윈도우 빌드') {
agent {
label 'windows'
}
steps {
bat 'build-windows.bat'
}
}
}
}
Jenkins의 장단점과 고려사항
장점
- 광범위한 플러그인 생태계로 거의 모든 도구 및 시스템과 통합 가능
- 대규모 커뮤니티와 풍부한 문서화
- 분산 빌드를 통한 확장성
- 파이프라인을 코드로 관리할 수 있는 유연성
- 무료 오픈소스 솔루션
단점
- 다양한 플러그인 사용 시 구성이 복잡해질 수 있음
- 플러그인 간의 버전 호환성 및 보안 이슈 고려 필요
- 초기 설정과 관리가 다소 복잡함
고려사항
- 프로젝트 규모와 복잡성에 따라 적절한 에이전트 구성 필요
- 보안 설정과 접근 권한 관리에 주의
- 정기적인 Jenkins 및 플러그인 업데이트
- 백업 및 복구 전략 수립