AWS에 Jenkins를 구축해보자 - (4) 파이프라인 구축 및 CodeDeploy 설정

Jongwon·2023년 10월 16일
0

에피메테우스

목록 보기
4/6
post-thumbnail

이전 단계

이전 3단계까지의 작업에서 아래의 내용들이 완료되었을 것입니다.

  • VPC 생성
  • EC2 인스턴스(Jenkins, Bastion) 생성
  • Jenkins 설치
  • SSH 터널링 완료
  • Lambda+API Gateway 설정
  • Github Webhook 설정
  • Jenkins 플러그인 추가


CI/CD PipeLine 구축

이제 가장 초기 목적이었던 Spring Boot 프로젝트의 CICD 파이프라인 구축을 진행하겠습니다.

Job 생성

  1. Pipeline Project를 생성합니다.
    => 이름은 server cicd로 하겠습니다.

  1. General에 Github 프로젝트 URL을 작성합니다.

  1. Build Trigger의 Generic Webhook Trigger를 선택합니다. 아래와 같이 main에 대한 push만 반응하기 위해 refs/heads/main으로 설정합니다.
    -> 보이지 않는다면 Generic Webhook Trigger 플러그인을 설치해주세요.

  1. Generic Webhook Trigger에서 토큰을 설정하는 부분에서 사용할 임의의 토큰 값을 입력합니다.
    -> 어떤 스트링값이든 상관없으나 이후 Lambda에서 이 값을 사용하게 됩니다.

  2. PipeLine에서 Pipeline Script를 작성합니다.
    -> 주석으로 필요한 부분들에 대한 설명이 있습니다.

pipeline {
	//어떤 agent든 파이프라인 실행을 허용합니다.
    agent any
    
    //파이프라인의 Step들을 정의합니다.
    stages {
    	//Checkout이라는 이름의 Stage입니다.
        stage('Checkout') {
            steps {
                sh '''
                	//이전 저장 폴더를 삭제하고 다시 생성하기 위한 명령입니다.
                    rm -rf Epimetheus
                    mkdir Epimetheus
                    cd Epimetheus
                    //깃헙 프로젝트를 받아옵니다. NAT를 통해 외부 인터넷에 접근가능 해야합니다.
                    git init
                    git remote add -f origin https://github.com/UOS-CSDESIGN/Epimetheus/
                    //sparseCheckout으로 backend/epimetheus아래에 있는 파일들만 가져올 예정입니다.
                    git config core.sparseCheckout true
                    echo "backend/epimetheus/" >> .git/info/sparse-checkout
                    git pull origin main
                '''
            }
        }

		//가져온 SpringBoot 프로젝트를 clean & Build합니다.
        stage('Clean and Build') {
            steps {
            	//디렉토리 오류가 종종 발생하여 직접 이동하여 실행합니다.
                dir('Epimetheus/backend/epimetheus') {
                    sh 'chmod +x ./gradlew'
                    sh './gradlew clean build'
                }
            }
        }
        
        //빌드파일을 압축하여 S3로 전송합니다.
        stage('Zip & Send To S3 Bucket') {
            steps {
                script {
                	//파일명은 springServer-빌드번호.zip입니다.
                    def zipFileName = "springServer-${env.BUILD_NUMBER}.zip"
                    dir('Epimetheus/backend/epimetheus') {
                    	//깃헙에서 가져온 파일들 중 jar, appspec.yml, scripts파일을 압축합니다.
                        sh "zip -r ${zipFileName} build/libs/epimetheus-*SNAPSHOT.jar appspec.yml scripts/"
                        //s3로 복사합니다.
                        sh "aws s3 cp ${zipFileName} s3://spring-build-bucket/"
                    }
                }
            }
        }
        
        //CodeDeploy에서 S3 버켓의 zip을 가져와 EC2로 배포합니다.
        stage('Deploy to AWS CodeDeploy') {
            steps {
                script {
                    def zipFileName = "springServer-${env.BUILD_NUMBER}.zip"
                    sh '''
                        aws deploy create-deployment \
                          --application-name spring-deploy \
                          --deployment-config-name CodeDeployDefault.OneAtATime \
                          --deployment-group-name SpringBootDeployGroup \
                          --s3-location bucket=spring-build-bucket,key=''' + zipFileName + ''',bundleType=zip \
                          --region ap-northeast-2
                    '''
                }
            }
        }
    }
}

현재 스프링부트 프로젝트는 깃허브에서 backend/epimetheus 아래에 존재하여 위와 같은 스크립트를 작성하였습니다.


  1. 저장을 눌러 파이프라인을 완성합니다.

Jenkins EC2에 zip + aws cli 설치

Jenkins 파이프라인에서 압축하고 S3로 보내는 등의 작업을 위해 아래와 같은 명령어로 zipaws-cli를 추가해야합니다.

sudo apt-get install zip
sudo apt-get install awscli

Lambda 코드 변경

Token을 생성했으니 코드를 수정하겠습니다.

import json
import requests

def lambda_handler(event, context):
    payload = json.loads(event['body'])

    relevant_changes = any(
        file.startswith("backend/epimetheus") 
        for commit in payload['commits'] 
        for file in commit['modified'] + commit['added'] + commit['removed']
    )
    
    if payload.get('ref') != 'refs/heads/main':
        return {
            'statusCode': 200,
            'body': 'Not pushed to main branch'
        }
        
    body_content = event['body']


    if relevant_changes:
        jenkins_url = "http://[Jenkins Private IP 주소]:8080/generic-webhook-trigger/invoke?token=[Jenkins Pipeline에서 입력한 토큰]"
        headers = {
            "Content-Type": "application/json"
        }
        response = requests.post(jenkins_url, data=body_content, headers=headers)
        return {
            'statusCode': 200,
            'body': response.text
        }
    else:
        return {
            'statusCode': 200,
            'body': 'No relevant changes detected'
        }

jenkins_url 부분을 변경하여 Pipeline으로 정상 요청하도록 합니다.


S3 버킷 생성

파이프라인에서 만든 zip파일을 저장하기 위해 S3 버킷을 생성하겠습니다. Jenkins Script에서 spring-build-bucket로 작성하였기 때문에 해당 이름으로 진행하지만, 바꾸셔도 무관합니다.

  1. 버킷 만들기를 클릭합니다.

  2. 버킷명을 설정하고 리전은 서울로 합니다.

  3. 소유권과 접근 권한을 아래와 같이 설정합니다.

  4. 버킷 버전 관리는 비활성화를 하고, 암호화는 Amazon S3 관리형 키(SSE-S3)를 사용한 서버 측 암호화로 설정합니다.


CodeDeploy위한 Script 파일 추가

Repository에서 appspec.ymlscripts를 추가하여 CodeDeploy의 실행 작업을 기술해 주어야 합니다.
저는 SpringBoot 프로젝트가 /backend/epimetheus아래에 있기 때문에 이곳에서 작성하겠습니다.

  1. appspec.yml을 root에 추가
version: 0.0
os: linux
files:
	//zip파일 압축 해제 후부터 / 폴더에 대하여
  - source: /
    //ec2의 /home/ubuntu/jenkins로 복사하겠다.
    destination: /home/ubuntu/jenkins
    overwrite: true
hooks:
  //application stop을 위한 script
  ApplicationStop:
    - location: scripts/stop_server.sh
      timeout: 300
  //application start를 위한 script
  ApplicationStart:
    - location: scripts/start_server.sh
      timeout: 300

  1. Project Root아래에 /scripts 폴더를 만들고 stop_server.sh를 만듭니다.
PROJECT_ROOT="/home/ubuntu/jenkins"
JAR_FILE="$PROJECT_ROOT/epimetheus.jar"

CURRENT_PID=$(pgrep -f $JAR_FILE)
DEPLOY_LOG="$PROJECT_ROOT/deploy.log"

if [ -z $CURRENT_PID ]; then
  echo "$TIME_NOW > 이전에 실행중인 SpringBoot Server가 존재하지 않습니다." >> $DEPLOY_LOG
else
  echo "$TIME_NOW > 실행 중인 $CURRENT_PID 애플리케이션을 종료합니다." >> $DEPLOY_LOG
  sudo kill -9 $CURRENT_PID
fi

실행의 결과가 deploy.log 파일에 저장됩니다.


  1. start_server.sh를 만듭니다.
PROJECT_ROOT="/home/ubuntu/jenkins"
JAR_FILE="$PROJECT_ROOT/epimetheus.jar"

APP_LOG="$PROJECT_ROOT/application.log"
ERROR_LOG="$PROJECT_ROOT/error.log"
DEPLOY_LOG="$PROJECT_ROOT/deploy.log"

echo "$TIME_NOW > Rename to $JAR_FILE" >> $DEPLOY_LOG
cp $PROJECT_ROOT/build/libs/epimetheus-*\.jar $JAR_FILE

echo "$TIME_NOW > Run $JAR_FILE" >> $DEPLOY_LOG
nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG &

CURRENT_PID=$(pgrep -f $JAR_FILE)
echo "$TIME_NOW > Current PID: $CURRENT_PID" >> $DEPLOY_LOG

CodeDeploy 설정

배포 그룹 생성 시 원할한 진행을 위해 Root 계정으로 진행하시길 권장합니다.

  1. CodeDeploy에서 어플리케이션 생성


  2. 어플리케이션 탭으로 가 방금 생성한 어플리케이션의 배포 그룹 생성


  3. 배포 그룹명 및 서비스 역할 설정

    이때 CodeDeployRole에는 AWSCodeDeployRole역할이 추가되어 있습니다. 없다면 IAM 페이지로 이동하여 생성하고 돌아오시면 됩니다.


  1. 배포 유형을 현재 위치로 선택. 환경 구성을 EC2 인스턴스로 선택.


  2. AWS CodeDeploy 에이전트 설치를 안함으로 체크하고, 배포설정을 OneAtATime으로 선택.

  3. CLI를 통한 사례 선택.

Spring EC2 생성

마지막으로 배포가 진행될 EC2를 Public Subnet에 생성합니다.

이때 아래의 작업들을 추가적으로 진행해야합니다.

  • EC2에 CodeDeploy+S3 IAM Role 주기

이를 위해 CodeDeployAndS3라는 권한을 만들었습니다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::spring-build-bucket",
                "arn:aws:s3:::spring-build-bucket/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "codedeploy:CreateDeployment",
                "codedeploy:GetDeployment",
                "codedeploy:GetDeploymentConfig",
                "codedeploy:GetApplication",
                "codedeploy:RegisterApplicationRevision"
            ],
            "Resource": "*"
        }
    ]
}
  • 인스턴스에 접속하여 CodeDeploy Agent를 설치해야 합니다.

다음 블로그에서 자세히 설명해주고 있습니다.
https://countrymouse.tistory.com/entry/awsec2cicd2

  • 빌드파일 실행을 위한 JRE 17을 설치해야 합니다.


완성

이제 파이프라인 구축이 완성되었습니다. Github에서 main으로 merge나 push가 발생했을 때 Pipeline이 정상적으로 실행되는지 Jenkins에서 확인해보세요!

미리 진행을 하고 이후 블로그를 작성하여 빠진 부분이 존재할 수 있습니다. 동작이 되지 않는다면 에러와 함께 알려주세요!

profile
Backend Engineer

1개의 댓글

comment-user-thumbnail
2024년 11월 24일

아키텍처에 궁금증이 생겨 질문드립니다.
결국 서버를 public subnet에 위치시키면 젠킨스를 private-subnet에 위치하는것이
어떤 의미가 있는지 알고싶습니다!!

답글 달기