본문 바로가기
프로젝트/협업 프로젝트(2023.12.18-2024.01.25)

[Key Word 개발기] AWS 배포 - jenkins 지속적 배포 구축하기

by dal_been 2024. 1. 19.
728x90

간단하게 우리 협업프로젝트 CI/CD구조가 이러하다. 이번 게시글에서는 jenkins를 사용해서 어떻게 지속적 배포를 구축했는지 설명하겠다.

 


앞서 게시글에도 이야기했지만 github actions에서 우선 pr코드에 대한 검사를 진행한다. 그래서 오류가 발생시 병합을 불가능하게 하였다.(다만 지금 우리조는 restdocs관련해서 에러가 나고 있지만 그냥 파일이 없다는 간단한 문제라서 이부분은 pass하고 진행중이다)

 

이후 병합하면 webhook에서 jenkins로 이벤트 발생했다고 알려준다. 

그럼여기서 jenkins는 이제 jar파일을 생성하여 ec2인스턴스에게 전달하여 스프링부트 어플리케이션을 실행하여야한다.

 

젠킨스에서 잡을 생성하는 방법에는 두가지가 있다.

 

1. 프리스타일

  • 젠킨스에서 기본틀을 정해주고 안에 빈칸을 채우는 방식이다
  • 웹기반 GUI를 통해 여러 플로그인을 쉽게 사용할 수 있다

다만... 파이프라인 변경을 위해서는 젠킨스에 로그인해서 각각의 프리스타일을 변경해야한다. 뿐만아니라 설정과정이 다소 난잡하게 느껴질 수 도 있다

 

2. 파이프라인

  • 잡을 생성하는 일련의 과정을 코드로 작성하는것이다.
  • Jenkinsfile라고 불리는 파일로 관리가능하여, GIT처럼 버전관리가 가능하다.
  • 스테이지 단위로 작업을 나누어 단위별로 소용되는 시간, 실패여부를 시각화 가능하다

다만.. 스크립트를 짜야하고 해당 문법을 알아야한다.

 

 

우리팀은 파이프라인을 택했다. 가장 큰 이유는 스테이지 별로 실패여부를 알수 있다는 점이다. 아직 jenkinsfile로 관리하는 것은 적용하지 않았지만 추후 적용할 예정이다.

 

파이프라인 문법에도 Scripted 와 Declartive가 있다고 한다. Scripte는 Groovy라는 언어로 작성되고, 변수 선언등과 같이 프로그래밍을 할 수 있다고 한다. 반면 Declarative는 Scripted에 비해 간단하고, Groovy를 알지 않아도 사용할 수 있다. 

다만 Scripted가 Declarative에 비해 유연성이 확장성이 좋지만, 복잡도가 높다...

 

블로그를 보면 Declarative로 스타일이 많이 이동하고 있다고 한다. 뿐만 아니라 찾아보니 작성방법도 Declarative가 비교적 쉬워보여 우리팀은 Declarative로 사용하기로 했다

 

 


자자 이제 진짜 Job을 생성해보자

 

Jenkins로 들어가서 새로운 ITem을 클릭하고 PipeLine을 선택하여 새로운 잡을 생성한다.

 

GitHub URL 설정

General -> GitHub project -> Project Url에 깃허브 프로젝트 주소를 적는다.

 

오래된 빌드 삭제 체크

초반에 오래된 빌드 삭제 안했다가 이게 쌓여서 ec2에서 용량이 부족하여 빌드조차 되지 않았다. 물론 프리티어이기에 그럴수 있지만 

aws 프리티어를 사용하신다면 체크하시는게 좋을 것같다

 

Build Trigger설정

Github hook trigger for GITScm polling 체크해주면 Github Webhook을 통해 빌드가 된다는 옵션이다.

해당 Github Plugin을 설치되있지 않다면 작동하지 않기때문에 Plugin들어가서 확인바란다

 

 

파이프라인 작성

일단 pipline from script??를 선택한다. 이후 각자 프로젝트에 맞는 스크립트를 복붙한다.

 

pipeline {
   agent any
   stages {
       stage('Github') {
           steps {
               git branch: 'develop', url: 'https://github.com/GIT.git'
           }
       }
       
       stage('Build') {
           steps {
               sh "chmod +x gradlew"
               sh "./gradlew bootJar"
           }
       }
       
       stage('Deploy') {
           steps {
                dir('build/libs') {
                    script {
                        sshagent(credentials: ['git_ssh']) {
                            // 파일 복사
                            sh 'scp -o StrictHostKeyChecking=no git-0.0.1-SNAPSHOT.jar ubuntu@1.123.123.12:./'
        
                            // 스크립트 실행
                            def result = sh(script: 'ssh ubuntu@1.123.123.12 "sh git.sh"', returnStatus: true)
                            
                            // 스크립트 실행 결과 출력
                            echo "스크립트 실행 결과: ${result}"
        
                            // 스크립트가 실패한 경우 Jenkins를 중단
                            if (result != 0) {
                                error "스크립트 실행 중 오류 발생"
                            }
                        }
                    }
                }
           }
       }
   }
}

 

GitHub

깃허브의 저장소 주소와 브랜치를 설정한다. 명시된 저장소의 브랜치를 기준으로 코드를 가져와준다

 

Build 

gradlew 파일을 사용하여 빌드한다

 

Deploy

Jenkins관리에 manage credentials에서
ssh username with private key를 통해서 aws에서 발급받은 pem키를 먼저 등록해야한다

또한 SSh Agent플로그인을 설치해야한다

ssh agent플로그인을 통해 젠킨스에 등록해둔 pem키의 이름을 "[]"안에 작성해준다.

(->Directive Block내부에서 sh를 통해 ssh관련 명령어를 수행할 수 있게 해준다)

 

사실 저 스크립트 실해하고 실패했는지는 안해도된다. 다만 가끔 ec2??에서 저 스크립트 파일을 못찾는 경우가 있어서.. 저렇게 해놨다.

 

 

스크립트

ec2인스턴스 안에 git.sh라는 쉘 스크립트를 미리 작성하였다.

해당 스크립트는 현재 실행중인 스프링부트 애플리케이션의 프로세스를 제거하고, 환경변수 설정, jar파일 실행을 해준다.

 

#!/bin/bash

PROJECT_NAME=git
CURRENT_PID=$(pgrep -f ${PROJECT_NAME}-.*.jar | head -n 1)

if [ -z "$CURRENT_PID" ]; then
    echo "구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
    echo "구동중인 애플리케이션을 종료했습니다. (pid : $CURRENT_PID)"
    kill -15 $CURRENT_PID
fi

echo "SpringBoot 환경변수"

export DB_PORT=
export DB_HOST=
export DB_DATABASE=
export DB_USERNAME=
export DB_PASSWORD=
export REDIS_PORT=
export REDIS_HOST=
export CLOUD_ACCESS=
export CLOUD_SECRET=
export S3_BUCKET=
export S3_FOLDER=
export S3_DOMAIN=
export JWT_SECRET=
export OAUTH_CLIENT_ID=
export OAUTH_CLIENT_SECRET=

LOG_DIR=/home/ubuntu/logs
LOG_FILE=$LOG_DIR/LOG.log

# 로그 디렉토리 생성 (없는 경우)
mkdir -p $LOG_DIR

echo "SpringBoot 애플리케이션을 실행합니다"

JAR_NAME=$(ls | grep .jar | head -n 1)
sudo -E nohup java -jar -Dserver.port=8080 /home/ubuntu/$JAR_NAME >> $LOG_FILE 2>&1 &

 

export를 통해 환경변수를 주입해주고 있다..근데 이부분은 매번 환경변수를 추가할때마다 수정해야한느 번거로움이 있어 따로 방법을 찾아볼 계획이다.

 

주의해야할 점

1. host key verification failed

찾아보니 충돌이 발생한 것이다. 이전의 IP 서버와 RSA공유키를 교환 후 , 서버의 교체나 아이피 변경한 후 기존 서버 키정보가 남아 있어 충돌이 발생한다고 한다.

 

그래서 

ssh-keygen -R <IP>

 

를 통해 키 값을 초기화 한후 다시 접속을 시도하면 성공적으로 접속된다고 한다.

그러나  정확한 원인은 알수 없지만 known_hosts로 인해 제대로 수행되지 않았다.

그래서 "-o StrictHostKeyChecking=no" 를 추가했다. 해당옵션을 통해 Host key checking을 비활성화 한것이다. 찾아보니 중간자 공격을 할 수 있다곤 하지만 기본적으로 젠킨스와 백엔드 서버사이에 private ip로 통신을 하고 있기 때문에 괜찮지 않을까??라고 판단하여 해당 옵션을 붙이게 되었따.

 

2. 젠킨스에서 빌드가 안되요...그냥 계속 빌드중이에요..

그 이유는 프리티어라서..ㅎㅎ 프리티어 ec2의 경우 ram 이 1기가 라고 한다. 그래서 aws에서는 해당 ram을 늘리는 방법을 알려주었다.

 

// 스왑 파일 생성
// aws에서는 count=32로 예제가 써있는데 절반인 16을 적은 이유는 권장 크기가 2GB이기 때문이다
$ sudo dd if=/dev/zero of=/swapfile bs=128M count=16

//스왑 파일에 권한 설정
$ sudo chmod 600 /swapfile

//스왑 영역을 설정
$ sudo mkswap /swapfile

// 스왑 공간에 스왑파일을 추가하여 스왑파일을 즉시 사용가능하게 설정
$ sudo swapon /swapfile

//프로시저 성공적인지 확인
$ sudo swapon -s

//파일을 편집하여 부팅시 스왑파일 시작
$ sudo vi /etc/fstab

//파일 끝에 아래 문구 추가하고 저장(vi명령어는 삽일할때 a를 누르고 변경 끝나면 esc후 :wq입력)
/swapfile swap swap defaults 0 0

 

이후 다시 빌드해주면 빌드가 잘 된다..


처음해본 지속적배포라서... 한 2주일은 공부하다가 1주일정도 지났을때 아 이제 감좀 잡히네 시작하다가 갑자기 급하게 3일만에 구현했다... 연동테스트를 해봐야하는 상황이라... 그래도 다행히 잘 작동한다..

 

https://hudi.blog/continuous-deploy-with-jenkins-1-backend/