AWS/Project

CodeSeries를 활용한 CI/CD 구성 (Backend Pipeline)

jih0ssang 2024. 6. 28. 15:31

 

구성도

Backend CI/CD Pipeline

  • CodeCommit에 Dev, Prod 브랜치 생성 (two-branch 전략)
  • 환경별(Dev,Prod) 프론트, 백엔드 파이프라인 구성 (총 4개)  -- 이 페이지에서는 백엔드파이프라인만 설명!
  • CodeBuild는 buildspec.yml(dev-buildspec.yml과 prd-buildspec.yml)을 통해 빌드 및 테스트 진행
  • CodeDeploy는 appspec.yml을 통해 S3 내 아티팩트를 가져와 EC2 및 Auto Scaling Group에 배포
  • Dev 환경은 EC2에 In-place 배포, Prod환경에서는 Auto Scaling Group에 Blue/Green 배포 전략 상이함

* CodePipeline은 승인자의 허가에 의해 진행된다.

 

이 글 내용은 Backend CI/CD Pipeline 구성을 진행하는 내용이다.

AWS CodeCommit 생성 및 로컬 컴퓨터와 연동은 Frontend CI/CD Pipeline 구성 페이지에서 작성하였으니 참고바란다.

 


아티팩트 보관용 S3 버킷 생성

웹 호스팅 용이 아니고 아티팩트 보관용이므로 정적 웹 사이트 호스팅 활성화 하지 않는다.

 

내부 사용자만 접근하므로 퍼블릭 액세스 차단이 필요하다.

 

 

Bucket Policy 구성

표시할 정책이 없습니다.

 

ACL 비활성화

 

내부 사용자만 접근하므로 ACL 활성화 필요 없다.

 

$ git init
- 새로운 Git 저장소로 초기화
- 이 명령어를 실행하면 .git 디렉토리가 생성되고, 현재 디렉토리는 Git 저장소로 전환됨

$ git clone https://git-codecommit.ap-northeast-2.amazonaws.com/v1/repos/CODECOMMIT-REPOSITORY
- 원격 저장소를 로컬 저장소로 복제

$ cd backend/
- 복제된 로컬 저장소 폴더로 이동

$ git checkout test
test branch로 전환

$ git add .
- 현재 작업 디렉터리의 모든 변경사항을 스테이징 영역에 추가
- 새 파일 추가, 파일 수정, 파일 삭제 모두 포함

$ git status
- 변경된 파일 확인

$ git commit -m "commit message"
- '-m "commit message"' 부분은 커밋 메시지를 지정


$ git push origin test
- 로컬 저장소의 commit된 변경 사항을 원격 저장소로 전송
- '-u' : 로컬 저장소의 branch와 원격 저장소의 branch를 추적 관계로 설정. 특정 branch에서만 계속 배포할 때 권고. 설정하면 git push, git pull 할 때 브랜치 이름 생략가능.


$ git remote add origin https://git-codecommit.ap-northeast-2.amazonaws.com/v1/repos/CODECOMMIT-REPOSITORY
- origin: 원격 저장소의 이름 의미(꼭 오리진 아니어도 됨)
- https://git-codecommit : 원격저장소의 URL

 

앞서 프론트엔드 파이프라인에서 설명했던 부분은 생략한다.

 

 

buildspec-dev.yml

version: 0.2

env:
  variables:
    ENV: "dev"

phases:
  install:
    runtime-versions:
      java: corretto17
  build:
    commands:
      - echo Build Starting on `date`
      - chmod +x ./gradlew
      - ./gradlew build
  post_build:
    commands:
      - chmod +x ./scripts/modify_script.sh
      - ./scripts/modify_script.sh $ENV
      - echo $(basename ./build/libs/*.jar)
      - pwd
artifacts:
  files:
    - build/libs/*.jar
    - scripts/**
    - appspec.yml
  discard-paths: yes
cache:
  paths:
    - '/root/.gradle/caches/**/*'

 

 

buildspec-prd.yml (내용 모두 동일)

version: 0.2

env:
  variables:
    ENV: "prd"

phases:
  install:
    runtime-versions:
      java: corretto17
  build:
    commands:
      - echo Build Starting on `date`
      - chmod +x ./gradlew
      - ./gradlew build
  post_build:
    commands:
      - chmod +x ./scripts/modify_script.sh
      - ./scripts/modify_script.sh $ENV
      - echo $(basename ./build/libs/*.jar)
      - pwd
artifacts:
  files:
    - build/libs/*.jar
    - scripts/**
    - appspec.yml
  discard-paths: yes
cache:
  paths:
    - '/root/.gradle/caches/**/*'
  • 환경변수 ENV는 값 "prd"으로 설정
  • backend는 Java 17 설치
  • build: ./gradlew build 명령어를 통해 build/libs/*.jar  생성   (자바 빌드 파일)
  • post_build:
    • chmod +x ./scripts/modify_scripts.sh :   modify_scripts.sh 스크립트 실행 권한 부여
    • ./scripts/modify_scripts.sh $ENV  :   modify_scripts.sh 스크립트 실행 및 인자 값(ENV) 전달
    • echo (첫 번째 jar 파일 이름 출력), pwd  (잘 설정되었는지 확인 용)
  • artifacts.base-directory는 빌드된 아티팩트가 생성될 기본 디렉터리 지정한다. 빌드된 아티팩트가 해당 디렉터리 내에 생성된다.
  • artifacts.files는 아티팩트로 포함될 파일 지정한다. 파일 경로는 AWS Codecommit 기준으로 명시해야 한다.
  • build/libs/*.jar 는 확장자가 jar인 파일 모두 지정하는 것이다.  여기서 java 실행을 위한 빌드된 코드를 의미한다.
  • scripts 디렉터리 하위에는 buildspec-prd.yml, deploy-prd.sh 파일이 존재한다. CodeBuild 및 CodeDeploy가 참조한다.
  • appspec.yml은 CodeDeploy가 참조하는 파일이다.
  • discard-paths: yes  는 아티팩트를 S3에 배포할 때, 리포지토리 기준으로 가져올 때의 저장 경로들을 무시하고 S3 내부에는 모든 파일이 flat하게 저장된다. flat 저장 이유는 CodeDeploy가 아티팩트 가져와서 실행시킬 때 jar 디렉터리로 이동한 후 실행하지 않고, 가져와서 바로 실행시키기 위해서이다.
  • **/* 는 경로에 있는 모든 하위 파일 및 디렉터리를 포함한다는 뜻이다.
  • cache는 빌드 과정에서 빌드 속도를 높이기 위해 캐시 설정한다. 첫 배포는 캐싱 안되지만, 두번째부터 캐싱에 의해 좀 더 빨라진다. path는 캐시할 파일 경로를 지정한다.

 


CodeBuild 생성

Source가 AWS CodeCommit 이므로 지정하고, 브랜치 test 를 지정한다.

아티팩트는 Codebuild가 빌드한 아티팩트의 저장소 (ex. S3, ECR etc.)를 지정하는 것이다.

이 설정은 CodePipeline으로 만들지 않고 수동 빌드할 때 의미있는 설정이다. 아티팩트부터 설정이 다 무시될 예정..

CodePipeline으로 연결시 위의 설정들은 다 무시되고 CodePipeline 기준 설정을 따른다.

 

 

 

IAM Role 생성

빌드 프로젝트를 생성하면 기본적으로 생성되는 역할이 생긴다.

하지만 AWS Codebuild가 S3에 접근하려면 AmazonS3FullAccess 권한이 추가적으로 필요하다.

 

고로, 필요한 권한은 다음과 같다.

  • CodeBuild 빌드 프로젝트 생성시 기본적으로 생성되는 Role
  • AmazonS3FullAccess

 


 

CodeDeploy 생성

CodeDeploy가 EC2에게 배포를 수행할 수 있는 IAM Role 생성

자동적으로 AWSCodeDeployRole 정책이 추가된다.

AWS CodeDeploy Role에는 배포 대상 및 모니터링 권한이 포함되어 있다. ( AutoScaling, EC2, ELB, SNS, CloudWatch)

 

CodeDeploy 배포 그룹이 EC2 일 경우에는 기본적으로 생기는 AWSCodeDeployRole만으로도 권한 문제가 없다.

+ 그런데 AutoScaling Group인 경우, AWS CodeDeployRole 기본 권한만으로 부족하므로 추가해야한다.

 

추가할 Policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "iam:PassRole",
                "ec2:CreateTags",
                "ec2:RunInstances"
            ],
            "Resource": "*"
        }
    ]
}

이런식으로 필요한 권한은 추가해야 한다. 

 

AutoScaling Group의 기능이 다양해서 AWS CodeDeployRole이 AutoScaling의 기능에 대한 모든 접근 권한을 갖고 있지 않는 것 같다.

 

 

 

애플리케이션 생성

CodeDeploy > 애플리케이션

컴퓨팅 플랫폼은 EC2/온프레미스 으로 애플리케이션을 생성한다.

 

 

 

배포 그룹 생성

한 애플리케이션에서 환경별(dev, prod) 배포 그룹을 생성한다.

 

배포 유형 : In-place 인 경우

 

배포 유형 : Blue/Green 인 경우

우리는 Auto Scaling 시작 템플릿에 CodeDeploy agent 설치가 포함되어있어서 따로 설치하지 않았다.

 

현재 ALB와 연결하였다.

배포 실패하는 경우 롤백하도록 구성하였다.

주의해야할 점은 배포 시 장애로 인해 배포되지 않고 롤백이 되었을 때, 개발자 입장에서는 정상적으로 배포되었다고 착각할 수 있으므로 콘솔상 확인도 반드시 필요하다.

 

 

수동으로 EC2 CodeDeploy agent 설치

cd /home/ec2-user
wget https://aws-codedeploy-ap-northeast-2.s3.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto
sudo service codedeploy-agent restart

 

 

appspec.yml

version: 0.0
os: linux

files:
  - source:  /
    destination: /solution
    overwrite: yes
    owner: user01
hooks:
  ApplicationStart:
    - location: deploy.sh
      timeout: 300
      runas: user01
  ValidateService:
    - location: healthcheck.sh
      timeout: 60
      runas: user01
  • CodeDeploy가 배포할 때 참조하는 파일
  • ApplicationStart 할 때, deploy.sh 실행
  • ValidateService 할 때, healthcheck.sh 실행
  • CodePipeline 배포 단계의 view events에서 해당 hooks에서 잘못된 경우, 로그 확인이 필요함

 

deploy.sh

#!bin/bash
REPOSITORY=/solution/java 
cd $REPOSITORY

echo $PWD
RESULT=$(ls -al)
echo $RESULT

# plan 이름의 파일을 제외한 jar파일 이름을 값으로 설정
JAR_NAME=$(ls $REPOSITORY | grep '.jar' | grep -v 'plain')

# jar파일 PID를 값으로 설정
CURRENT_PID=$(pgrep -f $JAR_NAME)

if [ -z $CURRENT_PID ]
then
  echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
  echo "> kill -9 $CURRENT_PID"
  kill -9 $CURRENT_PID
  sleep 3
fi


echo "> $JAR_NAME 배포"

# java 실행
nohup java -jar -Dspring.profiles.active=dev $JAR_NAME > /dev/null 2> /dev/null < /dev/null &

-Dcom.amazonaws.sdk.disableEc2Metadata=true

  • properties와 같은 파일들을 AWS KMS 키로 암호화 시 복호화할 때, EC2 메타 데이터를 가져오므로 disable하면 안됨

sudo kill -9 $CURRENT_PID

  • sudo를 쓰게 되면 사용자 비밀번호를 입력받게 됨. 입력하지 않을 경우, 해당 명령어 실행되지 않아 배포가 되지 않음

 

 

healthcheck.sh

#!/bin/bash
echo "> Health check 시작"

# 리포지토리 이동
REPOSITORY=/solution
cd $REPOSITORY

# plan 이름의 파일을 제외한 jar파일 이름을 값으로 설정
JAR_NAME=$(ls $REPOSITORY | grep '.jar' | grep -v 'plain')

for RETRY_COUNT in {1..15}
do
    # jar파일 PID를 값으로 설정
    CURRENT_PID=$(pgrep -f $JAR_NAME)
    
    # 6060포트로 돌고있는지 결과 출력을 값으로 설정
    NETSTAT=$(netstat -nltp | grep 6060)
    
    # -n : $CURRENT_PID 값이 있으면 True, 없으면 False
    if [ -n "$CURRENT_PID" ] && [ -n "$NETSTAT" ];
    then
        echo ">Health check 성공"
        break
    else
        echo ">Health check의 응답을 알 수 없거나 혹은 status가 UP이 아닙니다"
    fi

    # -eq: equal.  10번 시도 안되면 exit 1
    if [ $RETRY_COUNT -eq 10 ]
    then
        echo "> Health check 실패. "
        exit 1
    fi
    echo "> Health check 연결 실패. 재시도..."
    sleep 10
done

 

 

 

modify_script.sh

#!/bin/bash

ENV=$1

# modify healtcheck script if needed 
if [ "$ENV" == "prd" ]; then
    sed -i 's/-Dspring.profiles.active=dev/-Dspring.profiles.active=prd/g' ./scripts/deploy.sh
fi

echo "modify deploy scripts for $ENV environment"

 

  • 환경별(Dev, Prod) deploy.sh이 통합되어있다.
  • 어차피 CodeCommit 내 파일은 기본 dev로 되어있으므로 Prod로 돌릴 때는 Prod로 ENV 변수를 바꾼다
  • Buildspec.yml에서 해당 스크립트가 실행되어 환경별 백엔드 파이프라인을 실행하도록 한다.

 

 

 


CodePipeline 생성

앞서 생성한 AWS CodeCommit CodeBuild를 자동화하는 파이프라인을 생성한다.

 

Step 1 파이프라인 설정 선택

자동으로 생성되는 역할을 지정한다.

 

 

Step 2 소스 스테이지 추가

AWS CodeCommit을 레파지토리로 사용할 것이므로 지정한다.

가져오고자 하는 레파지토리의 브랜치 이름 test 를 작성한다.

 

 

Step 3 빌드 스테이지 추가

CodeBuild를 사용하므로, 앞서 생성했던 Build 프로젝트를 지정한다.

저 Build 프로젝트에 S3로 배포한다는 내용이 포함되어 있다.

 

 

 

Step 4 배포 스테이지 추가

배포 공급자는 CodeDeploy를 선택한다.

CodeDeploy가 S3 내 올라와있는 아티팩트를 가져와 EC2에 배포한다.

 

 

배포 결과

 

성공적으로 배포된 모습을 관찰할 수 있다.