이전에 CI를 구현했다는 가정하에 진행하도록 하겠다 아래 링크를 참조해서 CI를 먼저 하길 바란다

 

https://vuddus526.tistory.com/269

 

[DevOps] Github Actions CI 구현하기 (1)

1. Github Actions 이란? Github Actions 는 Github 에서 제공하는 CI/CD 툴이다 build, test, deploy 등 필요한 Workflow 를 등록해두면 Gihtub 의 특정 이벤트 (push, pull request) 가 발생했을 때 해당 워크 플로우를 수행

vuddus526.tistory.com

 

조건

1) CI 구현해둔 상태

2) EC2, S3 만들어진 상태

 

위 조건이 다 되었으면 아래부터 천천히 따라 해보도록 한다

 

1. EC2 설정 추가하기

1) Tag 추가

CodeDeploy를 생성할 때 어떤 인스턴스에서 수행할 지 구분하는 값으로 태그를 사용한다

 

인스턴스 클릭 -> 작업 -> 인스턴스 설정 -> 태그관리

 

2) 태그 추가하기

 

3) 태그확인

 

4) IAM 역할 추가

EC2 인스턴스에서 S3에 올려놓은 파일에 접근할 수 있도록 권한을 추가해준다

 

IAM 에서 왼쪽에 역할을 눌러서 관리 페이지로 이동해서 역할을 만든다

 

AWS서비스를 선택하고 사용사례는 EC2를 선택한다

 

S3 접근 권한을 추가 해준다

 

원하는 이름으로 설정을 해준다 그리고 다른건 디폴트 맨밑에서 생성하기

 

EC2 인스턴스에서 IAM연결하기, 작업에 보안에 IAM 역할 수정을 누른다

 

아까 만든 IAM 역할을 연결해 준다

 

2. EC2에서 CodeDeploy 설치하기

1) 설치 가능한 패키지 리스트를 최신화 하기

$ sudo apt update

 

2) 루비 다운로드 받기

$ sudo apt install ruby-full

 

3) wget 다운로드 받기

$ sudo apt install wget

 

4) ubuntu 폴더로 접근하기

$ cd /home/ubuntu

 

5) codeDeploy 다운로드 받기

$ wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install

 

6) 권한 설정

$ chmod +x ./install

 

7) 최신버전으로 설치

$ sudo ./install auto > /tmp/logfile

 

8) 설치된거 확인하기

$ sudo service codedeploy-agent status

 

 

3. CodeDeploy 생성

1) CodeDeploy 전용 IAM 역할 만들기

아까 S3 역할 만들때 처럼 똑같이 만들어주는데

 

여기서 하나 다른점은 엔터티 선택에서

EC2 말고 다른 AWS 서비스의 사용 사례 에서 CdoeDeploy 검색해서 찾는다

 

2) CodeDeploy 어플리케이션 생성하기

AWS 검색창에 CodeDeploy 찾아서 들어온다

그리고 왼쪽에 배포 밑에 애플리케이션이라는 카테고리를 클릭해서 들어간다

 

나는 미리 만들어둔게 있는데 저렇게 이름 마음데로 설정하고

컴퓨팅 플랫폼을 EC2/온프레미스로 설정하면 된다

 

3) CdoeDeploy 배포 그룹 생성

방금 만든 애플리케이션을 클릭하면 화면처럼 나오는데 여기서 배포그룹 생성을 누른다

 

배포그룹이름 마음데로 설정하고 서비스 역할에

이전에 만든 codeDeploy IAM을 선택하고 배포 유형은 현재위치로 한다

 

환경구성은 Amazon EC2 인스턴스로 하고

아래 키는 맨처음 만든 태그를 선택해주면 된다

온프레미스 인스턴스는 체크 안해도된다

 

사용한 에이전트 구성은 한번만으로하고 배포설정은 화면처럼 놔둔다

 

로드밸런서는 나중에 HTTPS를 하게되면 활성화 시키고

아니면 체크 풀어서 배포그룹을 생성하면 된다

 

4. Github Actions 에서 사용할 IAM 사용자 추가하기

먼저 IAM에서 왼쪽에 사용자를 클릭해서 사용자 추가를 해준다

 

이전에 사용자 역할 각각 만들때 권한을 하나씩 해줬는데

여기서는 둘다 선택한다

  • AWSCodeDeployFullAccess
  • AmazonS3FullAccess

 

여기서 중요한거 마지막에 Access Key와 Secret Key를 확인해서 꼭 저장해둔다

 

5. Github Repositoy에서 Secrets를 추가해준다

 

6. 자동배포를 위한 코드설정하기

구글 어디를 찾아봐도 폴더 구조를 보여주는 사진이 하나도 없어서 내가 올린다...

 

아래에 있는 코드 다 추가하기전에 확인해 봐야하는것은

Nginx를 사용하고 있지않는다 그러면 switch.sh는 작성할 필요없고

deploy.sh에서도 switch 부분은 주석을 해야 오류가 나지않는다

main.yml에서는 중간중간 slack web hook 같이 다루지 않은것도

껴있는데 주석하길 바란다

 

Nginx를 이용한 무중단 배포 게시글도 작성할 예정이라

그 글에서 사용하는 방법을 작성하도록 하겠다

 

1) CI에서 추가했던 main.yml을 아래와같이 수정해 준다

name: Java CI TEST with Gradle

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]

permissions:
  contents: read

env:
  RESOURCE_PATH: ./src/main/resources/application.yml
  PROJECT_NAME: mountainz
  # Database
  DB_URL: ${{ secrets.DB_URL }}
  DB_USERNAME: ${{ secrets.DB_USERNAME }}
  DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
  # JWT Secret
  JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }}
  # AWS S3
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
  S3_REGION: ${{ secrets.S3_REGION }}
  AWS_REGION: ap-northeast-2
  # CODE_DEPLOY
  APPLICATION_NAME: ${{ secrets.APPLICATION_NAME }}
  DEPLOY_GROUP_NAME: ${{ secrets.DEPLOY_GROUP_NAME }}
  # SLACK WEBHOOK
  SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'

      - name: Generate Environment Variables File for Properties
        uses: microsoft/variable-substitution@v1
        with:
          files: ${{ env.RESOURCE_PATH }}
        env:
          spring.datasource.url: ${{ env.DB_URL }}
          spring.datasource.username: ${{ env.DB_USERNAME }}
          spring.datasource.password: ${{ env.DB_PASSWORD }}
          jwt.secretKey: ${{ env.JWT_SECRET_KEY }}
          cloud.aws.credentials.access-key: ${{ env.AWS_ACCESS_KEY_ID }}
          cloud.aws.credentials.secret-key: ${{ env.AWS_SECRET_ACCESS_KEY }}
          cloud.aws.s3.bucket: ${{ env.S3_BUCKET_NAME }}
          cloud.aws.region.static: ${{ env.S3_REGION }}
          logging.slack.webhook-uri: ${{ env.SLACK_WEBHOOK_URL }}

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      # Build
      - name: Build with Gradle
        run: ./gradlew clean build

      # 전송할 파일을 담을 디렉토리 생성
      - name: Make Directory for deliver
        run: mkdir deploy

      # Jar 파일 Copy
      - name: Copy Jar
        run: cp ./build/libs/*.jar ./deploy/

      # appspec.yml Copy
      - name: Copy appspec
        run: cp ./appspec.yml ./deploy/

      # script file Copy
      - name: Copy shell
        run: cp ./scripts/* ./deploy/

      # 압축파일 형태로 전달
      - name: Make zip file
        run: zip -r -qq -j ./$PROJECT_NAME.zip ./deploy

      # S3 Bucket으로 copy
      - name: Deliver to AWS S3
        env:
          AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}
        run: aws s3 cp --region $S3_REGION --acl private ./$PROJECT_NAME.zip s3://$S3_BUCKET_NAME/

      # Deploy
      - name: Deploy
        env:
          AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}
        run: aws deploy create-deployment --application-name $APPLICATION_NAME --deployment-group-name $DEPLOY_GROUP_NAME --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$PROJECT_NAME.zip --region $S3_REGION

 

2) 최상단 root에서 scripts 폴더를 만들고 그안에 deploy.sh 랑 switch.sh를 만들어준다

deploy.sh

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

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

RESPONSE_CODE=${curl -s -o /dev/null -w "%http_code" http://localhost/api/nginx/profile}
echo "> $RESPONSE_CODE response code"

if [ ${RESPONSE_CODE} -ge 400 ]
then
  echo "> NO RUNNING APP"
  CURRENT_PROFILE=port2
else
  CURRENT_PROFILE=$(curl -s http://localhost/api/nginx/profile)
fi

echo "> $CURRENT_PROFILE current profile"

if [ $CURRENT_PROFILE == port1 ]
then
  IDLE_PROFILE=port2
  IDLE_PORT=8082
elif [ $CURRENT_PROFILE == port2 ]
then
  IDLE_PROFILE=port1
  IDLE_PORT=8081
else
  echo "> no coincidence profile: $CURRENT_PROFILE"
  echo "> set profile: port1"
  IDLE_PROFILE=port1
  IDLE_PORT=8081
fi

IDLE_APPLICATION=$IDLE_PROFILE-$JAR_NAME
IDLE_APPLICATION_PATH=$DEPLOY_PATH$IDLE_APPLICATION

ln -Tfs $DEPLOY_PATH$JAR_NAME $IDLE_APPLICATION_PATH

echo "> 현재 실행중인 애플리케이션 pid 확인" >> /home/ubuntu/app/deploy/deploy.log

IDLE_PID=$(pgrep -f $IDLE_APPLICATION)

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

echo "> $IDLE_PROFILE 배포"    >> /home/ubuntu/app/deploy/deploy.log
nohup java -jar -Duser.timezone=GMT+9 -Dspring.profiles.active=$IDLE_PROFILE $IDLE_APPLICATION_PATH >> /home/ubuntu/app/deploy/deploy.log 2>/home/ubuntu/app/deploy/deploy_err.log &

for RETRY in {1...10}
do
  RESPONSE=$(curl -s http://localhost:$IDLE_PORT/api/nginx/health)
  HEALTH_WORD_COUNT=$(echo $RESPONSE | grep 'up' | wc -l)

  if [ ${HEALTH_WORD_COUNT} -ge 1 ]
  then
    echo "> health check done"
    break
  else
    echo "> health check fail"
    echo "> $RESPONSE"
  fi

  if [ ${RETRY} -eq 10 ]
  then
    echo "> health check loop fail"
    echo "> NGINX Switching fail"
    exit 1
  fi

  echo "> health check retrying"
  sleep 10
done

echo "> Profile Switch"
sleep 10
sudo sh /home/ubuntu/app/deploy/switch.sh

 

switch.sh

echo "> 현재 구동중인 Port 확인"
CURRENT_PROFILE=$(curl -s http://localhost/api/nginx/profile)

# 쉬고 있는 set 찾기: port1이 사용중이면 port2가 쉬고 있고, 반대면 port1이 쉬고 있음
if [ $CURRENT_PROFILE == port1 ]
then
  IDLE_PORT=8082
elif [ $CURRENT_PROFILE == port2 ]
then
  IDLE_PORT=8081
else
  echo "> 일치하는 Profile이 없습니다. Profile: $CURRENT_PROFILE"
  echo "> 8081을 할당합니다."
  IDLE_PORT=8081
fi

echo "> 전환할 Port: $IDLE_PORT"
echo "> Port 전환"
echo "set \$service_url http://127.0.0.1:${IDLE_PORT};" |sudo tee /etc/nginx/conf.d/service-url.inc

PROXY_PORT=$(curl -s http://localhost/api/nginx/profile)
echo "> Nginx Current Proxy Port: $PROXY_PORT"

echo "> Nginx Reload"
sudo nginx -s reload

 

3) 최상단 root에 appspec.yml 파일도 만들어준다

version: 0.0
os: linux

files:
  - source: /
    destination: /home/ubuntu/app/deploy
    overwrite: yes

permissions:
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu

hooks:
  ApplicationStart:
    - location: deploy.sh
      timeout: 60
      runas: ubuntu

 

7. push 해서 자동배포되는지 확인하기

세팅이 끝났으면 push해서 자동배포 되는지 확인해보겠다

main.yml을 보면 알겠지만 main브랜치에서 push나 requestfull 할때

자동배포가 이루어진다

 

1) push 했을때 github에서 repository에 Actions에서 ci 하듯이 빌드된다

 

2) 빌드 완료되면 AWS에 CodeDeploy에서 배포 상태를 보여준다