CI/CD 도입하게 된 계기는 클라우드 서버에서 깃허브 코드를 PULL 받고 다시 배포하는 반복 작업을 없애기 위해서 사용했습니다.
해당 글에서는 별도의 도커 설치와 젠킨스 접속 방법은 알고 있다는 가정 하에, Spring Project CI/CD 구축만을 다루는 글입니다. private 저장소를 사용하거나 git lab을 사용하면 그냥 프로젝트에 모두 올려서 freestyle 방식으로 빠르게 배포하는걸 추천합니다. 해당 글은 pipeline을 사용해서 작성했습니다.
1. 왜 많은 CI/CD 중 Jenkins일까?
깃허브 액션은 마이크로 소프트 사의 제품이고 젠킨스는 오픈 소스입니다. 사용하기 더 편하고 성능적으로 큰 문제 없는 Github Action이지만, 이전 회사에서 사용했던 경험이 있어 아무래도 젠킨스를 선택하게 된 것 같습니다.
그래도 아직 깃허브 액션보다 젠킨스가 더 많은 플러그인을 제공합니다. Batch 스케줄링 등 그리고 젠킨스는 자체 서버에서 동작하기 때문에 데이터 노출에 대한 안전성이 있습니다. 그래서 민감한 정보가 많고 보안적인 요소가 많은 백엔드는 젠킨스를 통해서 CI/CD를 구축하기로 정했습니다.
docker pull jenkins/jenkins:lts-jdk17
시도 1) 민감 정보 암호화하기
application-secretkey 암호화해서 커밋하기
결과적으로 윈도우 환경에서 해당 방법으로 진행하기에 적합하지도 않고 검색으로 자료를 찾기 힘들었습니다. WSL 우분투 리눅스 환경에서 개발하거나 MAC 환경에서 개발하면 해당 방식도 효율적이라고 생각합니다. 그래서 다른 방법을 시도해봅니다.
2. 민감 정보 파일 감추기(Jenkins credentials)
해당 기능을 사용하면 시크릿 파일로 중요 민감 정보를 노출하지 않을 수 있습니다.

3. 깃허브 웹훅 설정
1. 웹훅 설정

어렵지 않은 과정입니다. 실제 URL + 포트번호 + /github-webhook/ 을 작성합니다.
2. 젠킨스에 깃허브 인증 정보 추가

인증 정보를 추가합니다.
4. 젠킨스 서버에서 원격서버 SSH 접속하기
1. 도커 젠킨스 키 생성하기
//젠킨스 접속
docker exec -it jenkins-server bash
//젠킨스에서 키 생성
ssh-keygen -t rsa -b 4096 -m PEM
//ssh 서버에 키 등록
cat /var/jenkins_home/.ssh/id_rsa
//ssh 원격 서버 쪽에 설정해야합니다.
cat /var/jenkins_home/.ssh/id_rsa.pub
//사실 docker로 run하던 아니던 jenkins가 돌아가는 도커 환경의 운영체제 home/user/.ssh 의 정보로 돌려도 문제 없습니다...
2. 배포서버 /home/유저명/.ssh/authorized_keys 생성
해당 파일에 public 키를 등록해야 합니다. cat /var/jenkins_home/.ssh/id_rsa.pub 로 확인한 rsa.pub public 키를 해당 파일에 붙여넣습니다.
3. 젠킨스 관리 -> System -> Publish over SSH 설정하기
> cat id_rsa 명령어로 확인한 코드를 집어 넣습니다. publish over SSH가 존재하지 않는다면 plugin 에서 다운로드 해주세요!.


4. publish over ssh 사용해서 원격 서버에 빌드 파일 저장하기
steps {
sshPublisher(publishers: [
sshPublisherDesc(
configName: 'web-prod-server',
transfers: [
sshTransfer(
sourceFiles: "${APP_JAR}",
remoteDirectory: 저장할 저장소,
removePrefix: "build/libs" sourceFiles에서 폴더명 제거,
),
],
verbose: true, 디버그 모드 true
)
])
}
해당 작업으로 원격 서버에 파일이 저장되는 것 까지 확인했습니다.
5. SSH Agent 플러그인으로 ssh 접속하기
또 발생한 문제점
pipeline에서 빌드 후 execComment가 제대로 동작하지 않는 문제 발생.. 구글링 많이 해봤지만, 5시간 동안 해결하지 못해서 SSH Agent로 직접 연결해서 해결하기로 마음 먹었습니다.
1. Credentials 생성하기

2. 파이프라인 새 stage 추가
유의사항. ''' ''' 사이의 ' ' 절대 빼면 안돼요. 이거 없으면 ssh로 접속한 원격 서버가 아니라 jenkins 서버에서 리눅스 명령어가 동작합니다. 해당 에러 잡는데 생각보다 시간이 많이 들었어요..
environment {
APP_JAR = ""
SSH_CREDENTIALS_ID = 'gcp-home'
REMOTE_HOST = '서버 아이피
REMOTE_USER = 유저명
}
stage('restart server') {
steps {
sshagent([SSH_CREDENTIALS_ID]) {
sh '''ssh ${REMOTE_USER}@${REMOTE_HOST} '
# 실행시킬 스크립트 동작시키기
'
'''
}
}
}
}
6. 최종 완성된 파이프라인 및 성공..
pipeline {
agent any
environment {
// 환경 변수
}
stages {
stage('git_clone') {
steps {
//깃허브 정보
}
}
stage('generate_application_properties') {
steps {
sh 'mkdir -p src/main/resources'
withCredentials([file(credentialsId: 'db-config-yml', variable: 'db'),
file(credentialsId: 'secret-api-yml', variable: 'api')]) {
script {
sh "cp -f $api src/main/resources/application-api-key.yml"
sh "cp -f $db src/main/resources/application-db.yml"
}
}
}
}
stage('build') {
steps {
sh 'chmod +x ./gradlew'
sh './gradlew clean build'
}
}
stage('deploy') {
steps {
sshPublisher(publishers: [
sshPublisherDesc(
configName: 'web-prod-server',
transfers: [
sshTransfer(
sourceFiles: "보낼 소스파일 폴더 경로 및 파일명",
remoteDirectory: '/srv/bookshop-backend',
removePrefix: "build/libs",
),
],
verbose: true,
)
])
}
}
stage('restart server') {
steps {
sshagent([SSH_CREDENTIALS_ID]) {
sh '''ssh ${REMOTE_USER}@${REMOTE_HOST} '
// 실행시킬 쉘 스크립트 명령어들
'
'''
}
}
}
}
}

7. 느낀 점
파이프라인으로 만드니까 생각보다 복잡하고 오래 걸렸습니다. 만약에 다음에 하게 되면 그냥 민감 정보들을 jenkins 서버의 config 폴더에 넣고 cp 명령어로 복사해서 깃 허브에서 프로젝트를 가지고 온 뒤 빌드 하기 이전에 resource를 추가하는 방식으로 진행하려고 합니다. 아무래도 pipeline보다 freestyle 방식이 훨씬 정말 정말 편합니다.. 2일 정도 CI/CD 하면서 freestyle 방식과 pipeline 방식 모두 적용하면서 삽질했습니다.. 그래서 글로 남겨야 겠다는 생각이 들었습니다.