프로젝트 Mook 을 개발 및 배포하는 과정을 정리하는 글입니다.
Jenkins pipeline에서 빌드까지 성공적으로 진행되는 것을 확인했다.
실패하는 것도 확인하기 위해 실패할 코드를 push하고 pipeline을 빌드해보았는데 실행이 되지 않았다.
-> 디스크가 부족해서 Built-in-Node 가 오프라인이 되었다.

EC2의 볼륨을 8GB에서 16GB로 올려서 해결했다.
현재 EC2가 범용SSD인데 HDD로 바꿔도 될지 고민을 했다.
비용이 범용 SSD도 0.1 차이라 부담되지 않아 범용 SSD를 유지하기로 결정했다.
빌드가 성공하는 것은 확인했으나 실패하는 것을 테스트하지 못하여 테스트해보았다.
단순하게 세미콜론 하나를 빼고 푸시를 하고 빌드를 돌려보았다.
> Task :compileJava
/var/lib/jenkins/workspace/mook/src/main/java/org/teamMook/mook/MookApplication.java:10: error: ';' expected
성공적으로 테스트가 완료되었다.

생성 후 토큰값은 유출되지 않게 저장해두고

Build Triggers -> Github hook trigger for GITScm poolling 설정


완료.
이제 특정 브랜치(dev) 로 PR 후 Merge 하는 경우에만 Jenkins가 돌아가도록 수정하겠다.
목표는 main 브랜치로 PR 후 Merge 하는 경우에 pipeline이 빌드 되도록 하는 것.
먼저 Generic Webhook Trigger 플러그인을 다운로드한다.

그러고 Item의 Configure을 들어가보면

Build Trigger에 Generic Webhook Trigger가 생긴 것을 알 수 있다.
GitHub hook trigger for GITScm pooling은 해제 후 Generic Webhook Trigger 체크
Post content parameters 부분에서
$IF_MERGED, $BRANCH, $LABEL 관련 추가하고 token, 표현식 모두 설정 후 저장
이제 Github Webhook에 들어가서 추가해준다.

individual events는 PR과 Push만 체크해주고 저장한다.


main에 pr/merge 하고 pipeline이 빌드되는 것을 확인했다.
만약 dev에 pr/merge를 하면 어떻게 되는지 확인해보겠다.


의도한대로 pipeline이 빌드되지 않는 것 또한 확인했다.
sudo apt-get update
sudo apt-get install -y docker.io
-> 이후 docker --version을 통해 확인 완료
Docker version 24.0.7, build 24.0.7-0ubuntu4.1

Docker, Docker Pipeline 플러그인 설치 완료
Jenkins Pipeline Script에서 SSH를 사용하여 Spring Boot Server의 명령어 실행을 할 수 있도록 하기 위해 설치
.pem 키 값을 복사한 후 추가해주었다.
추후 수정 예정
stage('deploy') {
steps {
sshagent(['deploy-ssh-key']) {
sh '''
ssh -o StrictHostKeyChecking=no ubuntu@{Server EC2 ip} uptime
scp ~/Desktop/study/mook/build/libs/*.jar ubuntu@{Server EC2 ip}:/home/ubuntu/demo
ssh -t ubuntu@{Server EC2 ip} chmod +x ./deploy.sh
ssh -t ubuntu@{Server EC2 ip} ./deploy.sh
'''
}
}
}
여기서 많이 실패했다.
SSH 연결하는 과정에서 계속 Failed to run ssh-add 가 나왔다..
서칭을 통해 stack-overflow 사이트에서 credentials에 추가한 키 값의 끝에 엔터키가 들어가야 한다는 것을 알았고 수정을 통해 해결할 수 있었다.
denied: requested access to the resource is denied
도커 허브에 Push를 하는 과정에서 리소스에 접근할 수 없다는 에러인데 보통 2가지 경우에 발생한다고 한다.
Docker Hub에 로그인 하지 않은 경우
-> EC2 SSH에 접근하여 이미 docker login을 통해 로그인을 한 상태였기에 이 경우는 해당하지 않았다.
Docker Hub ID 와 태그된 이미지의 이름이 일치하지 않음
-> 1번이 아니었기에 당연히 2번이라 생각했고 스크립트에서 image의 이름을 변경하였다.
나의 Docker Hub Username은 normaninha 였기에 기존 이미지의 이름을 normaninha/mook 으로 수정하여 해결했다.
분명 Pipeline build가 성공했는데도 {배포 서버 ip}:8080 으로 접근을 해도 원했던 에러 화면이 나오지 않았다.
문제 해결을 위해 먼저 도커 컨테이너가 실행됐는지 확인을 해보았고
docker ps -a
이미 컨테이너는 실행이 되었다가 중지되었다는 것을 알 수 있었다.
문제를 찾기 위해 docker logs {컨테이너명}을 통해 로그를 찾아보았고 no main manifest attribute, in /app/mook-0.0.1-SNAPSHOT-plain.jar 문제를 찾을 수 있었다.
문제는 JAR 파일이 실행 가능한 형태로 빌드되지 않았다는 것이다. (실행이 아닌 plain JAR 로 빌드가 된 것)
해결을 위해 build.gradle 파일에
springBoot {
mainClass = 'org.teamMook.mook.MookApplication' // 실제 메인 클래스의 경로로 변경
}
jar {
enabled = false // 기본 JAR 생성 비활성화
}
bootJar {
enabled = true // Spring Boot 실행 JAR 활성화
}
코드를 추가하여 해결하였다.
현재 CI/CD 자동화 과정을 살펴보면
CI를 통해 검증하고 도커 이미지 생성, 허브에 Push, 배포 서버에서 Pull, 컨테이너 실행의 순서이다.
근데 여기서 이미 컨테이너가 생성되어 있다면?
-> 다음 Pipeline 빌드시에 분명히 이미 동일한 이름의 컨테이너가 존재한다고 에러가 뜰 것이라 생각하여 Jenkinsfile에 docker pull과 run 사이에 if문을 통해 컨테이너가 존재한다면 중지 후 삭제 코드를 넣어두었다.
기존 코드가 ssh -o StrictHostKeyChecking=no ubuntu@{ec2 ip} docker ps -q --filter name=mook-docker-container | grep -q . && docker rm -f $(docker ps -aq --filter name=mook-docker-container)
인데, 코드를 간략하게 설명하면 mook-docker-container 라는 이름의 실행 중인 컨테이너가 있는지 확인하고 있다면 중지 후 삭제한다는 코드이다.
동시에 여러 명령어를 처리하는 과정에서 문제가 발생하는 것 같다고 생각하여 &&가 아닌 `를 사용하여 수정하였다.
sh """
ssh -o StrictHostKeyChecking=no ubuntu@{EC2 ip} '
if docker ps -q --filter name=mook-docker-container | grep -q .; then
docker rm -f \$(docker ps -aq --filter name=mook-docker-container)
else
echo "No container to remove"
fi
'
"""
이렇게 수정하여 해결할 수 있었다.

약 30번 가량의 Pipeline build 실패를 겪었지만.. 마침내 성공하였다.
아무래도 Jenkins를 사용하여 CI/CD를 구축하는 것이 처음이다보니 여러 에러와 실패를 겪었다.
하지만, 공식문서나 서칭을 통해 모두 해결할 수 있었다.
단순히 GitHub에서 dev -> main 으로 PR 후 Merge만 하면 자동으로 테스트 후 성공하면 배포까지 자동으로 해준다는 것이 굉장히 효율적이라고 생각하여 시작했는데, 오히려 DevOps에 대해 여러가지를 배울 수 있어 좋았다.
Stack-Overflow ssh-key error
도커 Push Error - 1, 뱀귤 블로그
도커 Push Error - 2, eunsilson.log