(MOOK - 5) Jenkins CI/CD Webhook 연결 + CD 구축

NORMAN·2024년 10월 22일

Mook

목록 보기
5/5

프로젝트 Mook 을 개발 및 배포하는 과정을 정리하는 글입니다.

워크스페이스 정리

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

EC2의 볼륨을 8GB에서 16GB로 올려서 해결했다.
현재 EC2가 범용SSD인데 HDD로 바꿔도 될지 고민을 했다.
비용이 범용 SSD도 0.1/GB/월로16GB로올려도0.8/GB/월 로 16GB로 올려도 0.8 차이라 부담되지 않아 범용 SSD를 유지하기로 결정했다.

실패 테스트

빌드가 성공하는 것은 확인했으나 실패하는 것을 테스트하지 못하여 테스트해보았다.
단순하게 세미콜론 하나를 빼고 푸시를 하고 빌드를 돌려보았다.

> Task :compileJava

/var/lib/jenkins/workspace/mook/src/main/java/org/teamMook/mook/MookApplication.java:10: error: ';' expected

성공적으로 테스트가 완료되었다.

Webhook 연결

1. Github Access Token


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

2. Jenkins Credentials 수정

3. item configure 수정

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

4. Github Repository Webhook 추가

5. 테스트

완료.

6. 브랜치 설정

이제 특정 브랜치(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만 체크해주고 저장한다.

7. 테스트


main에 pr/merge 하고 pipeline이 빌드되는 것을 확인했다.

만약 dev에 pr/merge를 하면 어떻게 되는지 확인해보겠다.

의도한대로 pipeline이 빌드되지 않는 것 또한 확인했다.

CD 구축

1. Jenkins 서버에 도커 설치

sudo apt-get update
sudo apt-get install -y docker.io

-> 이후 docker --version을 통해 확인 완료
Docker version 24.0.7, build 24.0.7-0ubuntu4.1

2. Jenkins Docker 플러그인 설치

Docker, Docker Pipeline 플러그인 설치 완료

3. SSH Agent 플러그인 설치

Jenkins Pipeline Script에서 SSH를 사용하여 Spring Boot Server의 명령어 실행을 할 수 있도록 하기 위해 설치

4. Credentials 추가

.pem 키 값을 복사한 후 추가해주었다.

5. 스크립트 추가

추후 수정 예정

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
                    '''
                }
            }
        }

6. deploy 테스트

여기서 많이 실패했다.

6-1. Failed to run ssh-add

SSH 연결하는 과정에서 계속 Failed to run ssh-add 가 나왔다..

서칭을 통해 stack-overflow 사이트에서 credentials에 추가한 키 값의 끝에 엔터키가 들어가야 한다는 것을 알았고 수정을 통해 해결할 수 있었다.

6-2. 자원에 접근 요청 denied

denied: requested access to the resource is denied

도커 허브에 Push를 하는 과정에서 리소스에 접근할 수 없다는 에러인데 보통 2가지 경우에 발생한다고 한다.

  1. Docker Hub에 로그인 하지 않은 경우
    -> EC2 SSH에 접근하여 이미 docker login을 통해 로그인을 한 상태였기에 이 경우는 해당하지 않았다.

  2. Docker Hub ID 와 태그된 이미지의 이름이 일치하지 않음
    -> 1번이 아니었기에 당연히 2번이라 생각했고 스크립트에서 image의 이름을 변경하였다.

나의 Docker Hub Username은 normaninha 였기에 기존 이미지의 이름을 normaninha/mook 으로 수정하여 해결했다.

6-3. 배포는 성공했는데 실제로 배포한 서버 URL로 접근했을 때 실패

분명 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 활성화
}

코드를 추가하여 해결하였다.

6-4. 컨테이너 삭제 후 생성 실패

현재 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
    '
"""

이렇게 수정하여 해결할 수 있었다.

7. 성공


약 30번 가량의 Pipeline build 실패를 겪었지만.. 마침내 성공하였다.

회고

아무래도 Jenkins를 사용하여 CI/CD를 구축하는 것이 처음이다보니 여러 에러와 실패를 겪었다.

하지만, 공식문서나 서칭을 통해 모두 해결할 수 있었다.

단순히 GitHub에서 dev -> main 으로 PR 후 Merge만 하면 자동으로 테스트 후 성공하면 배포까지 자동으로 해준다는 것이 굉장히 효율적이라고 생각하여 시작했는데, 오히려 DevOps에 대해 여러가지를 배울 수 있어 좋았다.

Reference

Stack-Overflow ssh-key error
도커 Push Error - 1, 뱀귤 블로그
도커 Push Error - 2, eunsilson.log

profile
백엔드 개발자 노먼입니다.

0개의 댓글