리나 Dev토리

CI/CD 성공기 - SpringBoot, AWS EC2, RDS, ALB, S3, Github Actions 본문

SpringBoot 개인플젝(TodayMaker)

CI/CD 성공기 - SpringBoot, AWS EC2, RDS, ALB, S3, Github Actions

리나lina 2023. 4. 30. 08:03

Today 할일관리 프로젝트를 CI/CD 하는데 성공하였다.

 

먼저 CI, CD 개념부터 요약하면

 

CI는 Continuous Integration 

지속적인 통합

 

여러 branch로 작업한 코드들이 빈번하게 merge되게 해주는 것이다.

자주 합쳐줘야 변경내용의 간극이 적어서 merge시 충돌을 줄일 수가 있다.

 

특히 여러 개발자가 같이 개발하다보면, 다른 개발자가 변경한 내용을 내꺼에 반영안한 채로 개발하다보면

합칠때 기능이 안되거나, 어느 코드로 적용해야하는지 힘들게 resolve 할수 있다.

 

CD는 Continuous Delivery/Deploy  

지속적인 제공/배포

 

CI에서도 build 하면서 Test Code가 실행되서 테스트되긴 하지만,

CD에서도 다른 시스템과의 통합 테스트, 부하 테스트를 거쳐서 

통과되면 실제 운영서버에 배포한다.

 

CI, CD를 하기 위해

1. Github Action을 위한 IAM 사용자를 만들어 주기

Github Actions에서 S3에 업로드하고, CodeDeploy를 실행해야 하므로

AmazonS3FullAccess, CodeDeployFullAccess 권한을 추가해준다.

 

사용자 생성 화면이

참고하는 글의 이미지 vs 내 화면이 달라서 헷갈렸었는데, 

 

아래 그림에서 console 권한 제공은 체크하지 말고, 다음 눌러서

위 권한 2개 추가하고 -> 사용자 생성한 뒤에

'보안 자격 증명' 탭에서 액세스 키를 생성해주면 된다.

액세스 키, 시크릿 키는 잘 보관해두고

Github 레포지토리 Settings 탭에서 

- Secrets and variables - Actions에서  New repository secret 으로 저장해준다.

나는 S3 액세스키, 시크릿 키 뿐만 아니라

버킷 지역, application.properties파일 내용도 저장해주었다.

 

2. CI.yml, CD.yml 파일 작성

Github 레포지토리 - Actions 탭 -   set up a workflow yourself 를 클릭하면 

자동으로  .github 폴더 - workflows 폴더에 위치가 되고

CI.yml, CD.yml 파일을 각각 작성한다.

 

ci.yml 코드

브랜치 이름, version은 각자에 맞게 수정한다.

더보기
# workflow의 이름
name: CI

# 해당 workflow가 언제 실행될 것인지에 대한 트리거를 지정
on:
  push:
    branches: [ master ] # master branch로 push 될 때 실행
  pull_request:
    branches: [ master ]  # master branch로 pull request될 때 실행

# workflow는 한개 이상의 job을 가지며, 각 job은 여러 step에 따라 단계를 나눌 수 있음
jobs:
  build:
    name: CI
    # 해당 jobs에서 아래의 steps들이 어떠한 환경에서 실행될 것인지를 지정
    runs-on: ubuntu-20.04

    steps:
     # 작업에서 액세스할 수 있도록 $GITHUB_WORKSPACE에서 저장소를 체크아웃
      - uses: actions/checkout@v3
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          distribution: 'oracle'
          java-version: '17'

      # application.properties를 프로젝트에 포함
      - name: add Application.properties
        run:
          touch ./src/main/resources/application.properties
        shell: bash

      - name: copy Application.properties
        run:
          echo "${{ secrets.PROPERTIES }}" > ./src/main/resources/application.properties
        shell: bash

      # gradle 권한 설정
      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew
        shell: bash

      # 빌드
      - name: Build with Gradle
        run: ./gradlew build
        shell: bash

 

CD.yml 코드

버킷 이름, 버킷 폴더 이름은 맞게 수정한다.

# workflow의 이름
name: CD

# 해당 workflow가 언제 실행될 것인지에 대한 트리거를 지정
on:
  push:
    branches: [ master ] # main branch로 push 될 때 실행됩니다.

# 해당 yml 내에서 사용할 key - value
env:
  S3_BUCKET_NAME: todaymaker
  PROJECT_NAME: cicd
  
# workflow는 한개 이상의 job을 가지며, 각 job은 여러 step에 따라 단계를 나눌 수 있습니다.
jobs:
  build:
    name: CD
    # 해당 jobs에서 아래의 steps들이 어떠한 환경에서 실행될 것인지를 지정합니다.
    runs-on: ubuntu-20.04

    steps:
     # 작업에서 액세스할 수 있도록 $GITHUB_WORKSPACE에서 저장소를 체크아웃합니다.
      - uses: actions/checkout@v3
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          distribution: 'oracle'
          java-version: '17'

      # application.properties를 프로젝트에 포함
      - name: add Application.properties
        run: touch ./src/main/resources/application.properties
        shell: bash
        
      - name: copy Application.properties
        run: echo "${{ secrets.PROPERTIES }}" > ./src/main/resources/application.properties
        shell: bash
        
      # gradle 권한 설정
      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew
        shell: bash
        
      # 빌드
      - name: Build with Gradle
        run: ./gradlew build
        shell: bash
        
      - name: Make zip file
        run: zip -r ./$GITHUB_SHA.zip .
        shell: bash

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.ACCESS_KEY }}
          aws-secret-access-key: ${{ secrets.SECRET_KEY }}
          aws-region: ${{ secrets.REGION }}
          
      # script files 복사
      - name: Copy script
        run: cp ./scripts/*.sh ./deploy
      
      # S3에 빌드 결과 업로드
      - name: Upload to S3
        run: aws s3 cp --region ${{ secrets.REGION }} ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$PROJECT_NAME/$GITHUB_SHA.zip
        
      # Deploy 실행
      - name: Deploy
        run: |
          aws deploy create-deployment \
          --application-name today-deploy \
          --deployment-config-name CodeDeployDefault.AllAtOnce \
          --deployment-group-name today-deploy-group \
          --file-exists-behavior OVERWRITE \
          --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$PROJECT_NAME/$GITHUB_SHA.zip \
          --region ${{ secrets.REGION }} \

 

3. deploy.sh 파일 작성

deploy.sh는 AWS EC2 인스턴스에서 배포를 위한 스크립트이다.

현재 실행중인 애플리케이션의 pid를 확인하고, 있으면 kill 하는 것까지 포함되있다.

 

최상단에 scripts 폴더를 만들고, 그 안에 생성한다.

경로는 각자에 맞게 수정

#!/bin/bash
BUILD_JAR=$(ls /home/ubuntu/action/build/libs/*.jar)
JAR_NAME=$(basename $BUILD_JAR)
echo "> build 파일명: $JAR_NAME" >> /home/ubuntu/action/deploy.log

echo "> build 파일 복사" >> /home/ubuntu/action/deploy.log
DEPLOY_PATH=/home/ubuntu/action/
cp $BUILD_JAR $DEPLOY_PATH

echo "> 현재 실행중인 애플리케이션 pid 확인" >> /home/ubuntu/action/deploy.log
CURRENT_PID=$(pgrep -f $JAR_NAME)

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

DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME
echo "> DEPLOY_JAR 배포"    >> /home/ubuntu/today/deploy.log
nohup java -jar $DEPLOY_JAR >> /home/ubuntu/today/deploy.log 2>/home/ubuntu/action/deploy_err.log &

 

4-1. IAM에서 역할 생성

IAM에서 역할 생성 - AWS 서비스 선택

- 사용사례에서 EC2 선택 - 권한에 AmazonEC2RoleforAWSCodeDeploy 추가 - 생성

 

EC2 인스턴스에서 보안 - IAM 역할 수정 - 방금 생성한 역할을 설정 

역할 설정 후에 인스턴스 재부팅!

4-2. EC2에 CodeDeploy Agent 설치 - 20.04

나는 EC2 인스턴스에 ubuntu 20.04를 설치했는데, ubuntu 22에서는 ruby 에러가 난다고 한다.

아래 명령어로 codedeploy agent를 설치한다.

sudo apt update
sudo apt install ruby-full
sudo apt install wget
cd ~   (== cd /home/ubuntu)
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto
sudo service codedeploy-agent status

5-1. CodeDeploy 역할 생성

IAM에서 역할 생성

AWS 서비스 - 사용 사례 '다른 AWS서비스' - CodeDeploy 검색하여 선택 - 생성한다.

 

5-2. AWS CodeDeploy 배포 생성

AWS CodeDeploy 콘솔에 들어가서 - 애플리케이션 생성 - 컴퓨팅 플랫폼 "EC2/온프레미스" 선택

EC2가 로드밸런서에 연결되어 있어서

✅ 로드 밸런싱 활성화를 체크해주었다.

 

생성하고 - 배포그룹 탭 - "배포 그룹 생성" 을 클릭

만든 역할을 선택하고,

환경구성 - EC2 인스턴스를 선택 - Name과 값을 선택

맞게 선택하면 일치하는 인스턴스가 1개라고 뜬다.

6. appspec.yml 작성

appspec.yml는 빌드 파일 전송 위치 등을 지정하는 스크립트이다.

version: 0.0
os: linux
# S3에 있는 zip 파일이 EC2에 배포될 위치를 지정
files:
  - source: / # CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 루트로 지정(전체파일)
    destination: /home/ubuntu/action/ # source에서 지정된 파일을 받을 위치, 이후 jar를 실행하는 등은 destination에서 옮긴 파일들로 진행
    overwrite: yes

permissions: # CodeDeploy에서 EC2서버로 넘겨준 파일들을 모두 ubuntu권한을 갖도록 합니다.
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu

# ApplicationStart 단계에서 deploy.sh를 실행시키도록 합
hooks: # CodeDeploy배포 단계에서 실행할 명령어를 지정합니다.
  ApplicationStart: # deploy.sh를 ubuntu권한으로 실행합니다.
    - location: scripts/deploy.sh
      timeout: 60 # 스크립트 실행 60초 이상 수행되면 실패가 됩니다.
      runas: ubuntu

 

 

트러블슈팅1. Wrapper properties file '/home/runner/work/TodayMaker/TodayMaker/gradle/wrapper/gradle-wrapper.properties' does not exist.  

.gitignore에서 .properties를 해놨더니, gradle-wrapper.properties 파일이 없다고 에러를 냈다.

 

.gitignore파일에 이 파일은 추가해달라고 작성하면 된다.

!gradle/wrapper/gradle-wrapper.properties

 

트러블슈팅2. 6 tests completed, 6 failed

DB 연결이 안되서 datasource 빈 생성이 안된다는 에러

 

이유는 내가 RDS 보안그룹에서- 보안상 인바운드 규칙에 내 IP만 허용해주었었다 ㅎㅎ → 해당 포트만 모든 IP로 열어주고EC2에서도 SSH 22를 내 IP에서만 허용해줬었는데, 모든 IP로 허용하였다. 그리고 IAM 사용자에서도 RDS full 권한을 추가해주었다.

 

트러블슈팅3. 6 tests completed, 1 failed

테스트코드에서 6 tests completed, 1 failed 이 나오면서 action이 중단되었었다.

로컬 PC에서는 빌드도 잘 되고, 테스트도 다 성공하는데...

찾아보니 다른 환경이라 실패할 수 있다고 해서

build.gradle에서 'mysql:mysql-connector-java'가 runtimeOnly였는데, 빌드할때 테스트할때도 필요하겠구나 싶어서

implementation 'mysql:mysql-connector-java' 로 수정하고

null에러 난 곳이 연관관계 메서드가 있는 곳이여서

왜 에러가 날까 하다가

현재 테스트에서는 user만 생성하는데, todo 까지 같이 가져오다가 에러나는 것 같아서

Todo 클래스의 기본생성자에 접근권한을 삭제해주었다.

//@NoArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor
public class Todo {

그랬더니 CI.yml은 성공하였다.!!

 

그랬더니 이번에는 CD.yml에서 회원가입 test failed...

 

트러블슈팅4. 회원가입 테스트 fail

6 tests completed, 1 failed 인데 회원가입 테스트만 실패

내 PC에는 oracle jdk 11을 설치되있었고,

yml에서 처음에 actions/checkout@v2 java 11버전 공급처를 zulu로 했었는데

JAVA 공급처가 달라서 그런가 해서 찾아보니

 

actions/checkout@v2에서는 oracle이 없고,  v3에서 oracle JAVA 17부터만 제공해서

 

yml에서는 다른 배포처를 테스트해보았다.

"adopt"로도 해보고, "temurin"로 하니 CD.yml 이 성공하여

ci.yml도 "temurin"로 수정했더니 ci, cd 둘다 실패 ㅎㅎ

 

jdk 같은 버전이어도 공급처에 따라 다르구나...

 

그래서 내 PC에 Oracle JDK 17을 설치하였다. (환경변수도 변경)

그랬더니 Actions탭에서 CI, CD 모두 성공하였다.!!

 

 

 

 

출처: https://dkswnkk.tistory.com/674

 

Comments