개발자가 코드를 수정하면 로컬에서 빌드하고, FTP로 서버에 접속해서 jar 파일을 덮어쓰기 하고, 스크립트로 재시작을 했습니다. 하루에 배포가 한두 번일 때는 괜찮았지만, 팀원이 늘어나고 배포 횟수가 잦아지면서 문제가 터지기 시작했습니다. "아, 로컬에서 빌드할 때 설정 파일 잘못 넣었다", "누가 지금 배포 중이야?" 같은 커뮤니케이션 비용이 급증했죠. 이를 해결하기 위해 가장 범용적이고 커뮤니티가 강력한 Jenkins를 도입하여 배포 파이프라인을 구축했습니다.
EC2 인스턴스 하나에 Jenkins Master를 설치하고, 실제 빌드 작업은 별도의 Docker Container(Agent)에서 수행되도록 구성했습니다. 이렇게 하면 빌드 환경이 오염되는 것을 방지하고, 필요할 때만 Agent를 띄워 리소스를 효율적으로 사용할 수 있습니다.
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee /usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian-stable binary/ | sudo tee /etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt-get update
sudo apt-get install jenkins fontconfig openjdk-17-jre
sudo systemctl enable jenkins
sudo systemctl start jenkins
초기 설정 마법사에서 'Install suggested plugins'를 진행하고, 추가로 다음 플러그인들을 설치했습니다.
Freestyle 프로젝트보다는 코드로 파이프라인을 관리할 수 있는 Pipeline 프로젝트를 권장합니다.
프로젝트 루트에 Jenkinsfile을 작성하여 Git에 함께 버전 관리합니다.
pipeline {
agent { docker { image 'gradle:8.0.0-jdk17' } }
environment {
DOCKER_CREDENTIAL = credentials('docker-hub-credentials')
SSH_KEY = credentials('web-server-ssh-key')
}
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'https://github.com/my-org/my-project.git'
}
}
stage('Test') {
steps {
sh './gradlew test'
}
}
stage('Build') {
steps {
sh './gradlew build -x test'
}
}
stage('Docker Build & Push') {
when {
branch 'main'
}
steps {
sh "docker build -t my-org/my-app:${BUILD_NUMBER} ."
sh "echo $DOCKER_CREDENTIAL_PSW | docker login -u $DOCKER_CREDENTIAL_USR --password-stdin"
sh "docker push my-org/my-app:${BUILD_NUMBER}"
}
}
stage('Deploy') {
steps {
sshagent(['web-server-ssh-key']) {
sh "ssh -o StrictHostKeyChecking=no ubuntu@10.0.0.5 'docker pull my-org/my-app:${BUILD_NUMBER} && docker compose up -d'"
}
}
}
}
post {
always {
cleanWs()
}
success {
slackSend (channel: '#deploy-alarm', color: 'good', message: "배포 성공: ${env.JOB_NAME} #${env.BUILD_NUMBER}")
}
failure {
slackSend (channel: '#deploy-alarm', color: 'danger', message: "배포 실패: ${env.JOB_NAME} #${env.BUILD_NUMBER}")
}
}
}
초기에 t3.micro 인스턴스에서 Jenkins와 빌드 작업을 동시에 돌리다 보니 Gradle 빌드 중 자주 서버가 멈췄습니다. Swap 메모리를 2GB 추가하여 급한 불을 끄고, 이후 빌드 전용 노드(Slave Node)를 분리하여 해결했습니다.
Jenkins 유저가 Docker 명령어를 실행할 수 없어 permission denied 에러가 발생했습니다.
sudo usermod -aG docker jenkins 명령어로 jenkins 계정을 docker 그룹에 추가하고 서비스를 재시작하여 해결했습니다.
Jenkins 도입 후 하루 10회 이상의 배포도 부담 없이 진행할 수 있게 되었습니다. 개발자는 오직 코드 작성에만 집중할 수 있게 되었고, 배포 과정이 투명해져 팀 전체의 신뢰도가 올라갔습니다. 다음 단계로는 GitHub Actions로의 마이그레이션도 고려하고 있습니다.