회사에서 CI/CD를 도입하고자 하여 Jenkins를 이용한 정적분석 요청, 배포를 하면서 경험한 내용입니다.
개인 스터디 30주차에 공유했던 내용이며, 깊이는 부족할 수 있습니다.
CI(Continuous Integration) : 직역하면 지속적인 통합으로, 코드의 변경 사항을 확인하고 빌드/테스트를 후 공유 레포지토리에 통합하는 것.
CD(Continuous Delivery | Deployment) : 지속적인 배포. 영문으로는 (자동/수동)으로 배포하느냐에 따른 단어 차이가 있습니다.
CI/CD 툴로는 Jenkins, Travis CI, TeamCity, Bamboo, GitLab CI, GitHub Actions 등이 있습니다. 저는 Jenkins를 사용하겠습니다. (이유 : 회사에서 이거 쓰자고 해서, 오픈 소스로 무료로 사용 가능해서)
CI/CD 툴은 파이프라인을 설계하여, 원하는 이벤트를 스크립트화 하여 자동화 할 수 있습니다.
대표적인 예로 테스트 실행, 배포 파일 생성, 배포 작업입니다. 스크립트를 이용해 정의하므로, 배포 환경에 따른 다양한 설정이 가능합니다. (직접 서버에 접속하여 배포 파일 실행 | docker-compose를 통해 컨테이너 실행 등)
둘 다 DevOps 환경을 구성하기 위해 많이 사용되는 도구이나, 둘의 목적은 조금 다릅니다.
쿠버네티스는 컨테이너 오케스트레이션 툴입니다. 컨테이너 오케스트레이션 툴이란 쉽게 말하면 컨테이너 배포 관리 도구입니다. 특히 여러대의 컨테이너를 관리하기 위한 목적으로 활용합니다.
특징으로는 자동 배포 기능을 넘어 자동 복구, 로드 밸런싱 등을 제공하여 편리합니다.
쿠버네티스와 유사한 도구로는 Docker Swarm, Apache Mesos가 있습니다.
도커는 컨테이너 기반의 서비스입니다. 가상머신과 달리 운영체제를 분리하고 각각의 다른 환경을 쉽게 관리할 수 있습니다. 그래서 쿠버네티스 없이 도커만 있어도 배포 관리가 가능합니다. 하지만 컨테이너가 여러대일 경우, 이를 효율적으로 관리하기 위해 쿠버네티스를 활용합니다.
deploy.sh
를 실행하는 식으로 관리합니다.여기에 필요하다면 도커(컨테이너를 통한 배포), 쿠버네티스(여러 컨테이너의 통합 관리), 프로메테우스(서버 상태 수집), 그라파나(서버 상태의 대시보드화)를 붙여서 활용합니다.
단순한 자동배포만 활용할 경우 1번도 충분하나, 세부적인 설정 및 동작을 위해서는 pipeline을 활용하는 것이 좋습니다.
pipeline script는 Declarative Syntax
와 Scripted Syntax
2가지 문법을 지원합니다.
Declarative Syntax는 Groovy 문법을 따르되, 일부 차이가 있습니다.
pipeline { }
;
과 같은 문법 구분자는 사용하지 않습니다.input()
이 아닌 input
형태로 호출하여 사용합니다.Scripted Syntax는 DSL 표준에 가까운 범용 방법으로, Groovy 언어가 제공하는 대부분의 기능을 사용할 수 있습니다.
작성할 때, 두 문법 차이를 이해하고 작성해야 합니다.
pipeline {
agent any
stages {
stage('clone') {
steps {
git branch: 'main', credentialsId: 'gitlab-******', url: 'https://gitlab.**.com/**.git'
sh 'chmod +x ./gradlew'
}
}
stage('SonarQube analysis') {
steps {
withSonarQubeEnv('SonarQube Server') {
sh './gradlew sonar --stacktrace'
}
}
}
stage('wait for quality Gate') {
options {
timeout(time: 2, unit: 'MINUTES')
}
steps {
// abortPipeline : true 일 경우, qualityGate를 통과 못했을 때 파이프라인 중단
waitForQualityGate abortPipeline: false
}
}
stage('build') {
steps {
sh './gradlew build'
}
}
stage('deploy') {
steps {
dir('build/libs') {
sh 'java -jar -Dserver.port=8880 ./jenkinstest-0.0.1-SNAPSHOT.jar'
}
}
}
post {
success {
addGitLabMRComment comment: 'SonarQube Quality Gate is Success'
}
failure {
addGitLabMRComment comment: 'SonarQube Quality Gate is Failed'
}
}
}
node {
try {
stage('SCM') {
updateGitlabCommitStatus name: 'jenkins', state: 'pending'
checkout scm
}
stage('Sonarqube Analysis') {
withSonarQubeEnv() {
sh "./gradlew clean sonar --stacktrace"
}
}
stage('Quality Gate Check') {
timeout(time: 2, unit: 'MINUTES') {
def qg = waitForQualityGate()
if (qg.status != 'OK') {
error "Pipeline Aborted due to Quality Gate Failure: ${qg.status}"
}
}
}
stage('success') {
updateGitlabCommitStatus name: 'jenkins', state: 'success'
addGitLabMRComment comment: 'SonarQube Quality Gate is success'
}
} catch(e) {
updateGitlabCommitStatus name: 'jenkins', state: 'failed'
addGitLabMRComment comment: 'SonarQube Quality Gate is failure'
}
}