 
        
      프로젝트 중 무중단 배포 필요성을 느껴 ci/cd 구축을 해봤습니다.
여러 블로그에 리눅스, 우분투 혼용해서 설정한 정보글이 많기도 하고,
java코드는 기록이 남지만 aws ubuntu , S3, codedeploy 설정은 기록이 안남아서 까먹을 까봐 찾아본 정보를 기록합니다.
1. Nginx로 무중단 배포 구축하기
1.1. Nginx 설정
EC2 ubuntu 접속하여 nginx 설치하고 실행되고 있는지 확인합니다.
sudo apt-get install nginx
sudo service nginx start
ps -ef | grep nginx
아래 명령어도 service-url 파일 생성합니다.
sudo vim /etc/nginx/conf.d/service-url.inc
service-url 안에 아래 명령어를 입력 저장하고 파일에서 나옵니다.
set $service_url http://127.0.0.1:8081;
etc/nginx/sites-available 안 default 파일로 들어갑니다.
cd /etc/nginx/sites-available
sudo vim default
default 파일을 아래와 같이 수정 하고 저장합니다.

설정이 끝났으니 아래 명령어로 nginx를 재시작합니다.
sudo service nginx restart
1.2. 프로젝트 설정
무중단 배포시 옮겨주는 port 는 정하는 API를 만들어줍니다.
(쉘 스크립트에서 API로 작동하는 메소드이기 때문에 WebSecurityConfig를 쓰고 있다면 예외 처리 해야됩니다.)
package com.sparta.post.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
@RequiredArgsConstructor
@RestController
public class ProfileController {
    private final Environment env;
    @GetMapping("/profile")
    public String profile() {
        List<String> profiles = Arrays.asList(env.getActiveProfiles());
        List<String> realProfiles = Arrays.asList("real1", "real2");
        String defaultProfile = profiles.isEmpty()? "default" : profiles.get(0);
        // real, real1, real2 중 하나라도 있으면 그 값 반환
        return profiles.stream()
                .filter(realProfiles::contains)
                .findAny()
                .orElse(defaultProfile);
    }
}
작동되는 포트를 옮기기 위해 application.properties 파일을 두개 생성합니다.
# application-real1.properties
server.port=8081# application-real2.properties
server.port=8082
.jar 파일 bliud시 plant jar 파일 생성을 막아 주기 위해 bulid.gradle에 아래 코드를 추가해 둡니다.
// 빌드시 plain 생성 방지 
jar {
    enabled=false
}
1.3. 배포 쉘 스크립트 작성
프로젝트 폴더에 scripts 폴더를 생성하고 5개의 sh 파일을 작성합니다.
#!/usr/bin/env bash
# stop.sh
ABSPATH=$(readlink -f $0)
# ABSDIR : 현재 stop.sh 파일 위치의 경로
ABSDIR=$(dirname $ABSPATH)
# import profile.sh
source ${ABSDIR}/profile.sh
IDLE_PORT=$(find_idle_port)
echo "> $IDLE_PORT 에서 구동중인 애플리케이션 pid 확인"
IDLE_PID=$(lsof -ti tcp:${IDLE_PORT})
if [ -z ${IDLE_PID} ]
then
  echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
  echo "> kill -15 $IDLE_PID"
  kill -15 ${IDLE_PID}
  sleep 5
fi#!/usr/bin/env bash
# start.sh
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
REPOSITORY=/home/ubuntu/sparta/deploy
echo "> Build 파일 복사"
echo "> cp $REPOSITORY/*.jar $REPOSITORY/"
cp $REPOSITORY/*.jar $REPOSITORY/
echo "> 새 어플리케이션 배포"
JAR_NAME=$(ls -tr $REPOSITORY/*.jar | head -n 1)
echo "> JAR Name: $JAR_NAME"
echo "> $JAR_NAME 에 실행권한 추가"
chmod +x $JAR_NAME
echo "> $JAR_NAME 실행"
IDLE_PROFILE=$(find_idle_profile)
echo "> $JAR_NAME 를 profile=$IDLE_PROFILE 로 실행합니다."
nohup java -jar \
    -Dspring.config.location=classpath:/application.properties,classpath:/application-${IDLE_PROFILE}.properties \
    -Dspring.profiles.active=${IDLE_PROFILE} \
    ${JAR_NAME} > ${REPOSITORY}/nohup.out 2>&1 &#!/usr/bin/env bash
# profile.sh
# 미사용 중인 profile을 잡는다.
function find_idle_profile()
{
    # curl 결과로 연결할 서비스 결정
    RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/profile)
    if [ ${RESPONSE_CODE} -ge 400 ] # 400 보다 크면 (즉, 40x/50x 에러 모두 포함)
    then
        CURRENT_PROFILE=real2
    else
        CURRENT_PROFILE=$(curl -s http://localhost/profile)
    fi
    # IDLE_PROFILE : nginx와 연결되지 않은 profile
    if [ ${CURRENT_PROFILE} == real1 ]
    then
      IDLE_PROFILE=real2
    else
      IDLE_PROFILE=real1
    fi
    # bash script는 값의 반환이 안된다.
    # echo로 결과 출력 후, 그 값을 잡아서 사용한다.
    echo "${IDLE_PROFILE}"
}
# 쉬고 있는 profile의 port 찾기
function find_idle_port()
{
    IDLE_PROFILE=$(find_idle_profile)
    if [ ${IDLE_PROFILE} == real1 ]
    then
      echo "8081"
    else
      echo "8082"
    fi
}#!/usr/bin/env bash
# health.sh
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
source ${ABSDIR}/switch.sh
IDLE_PORT=$(find_idle_port)
echo "> Health Check Start!"
echo "> IDLE_PORT: $IDLE_PORT"
echo "> curl -s http://localhost:$IDLE_PORT/profile"
sleep 10
for RETRY_COUNT in {1..10}
do
  RESPONSE=$(curl -s http://localhost:${IDLE_PORT}/profile)
  UP_COUNT=$(echo ${RESPONSE} | grep 'real' | wc -l)
  if [ ${UP_COUNT} -ge 1 ]
  then # $up_count >= 1 ("real" 문자열이 있는지 검증)
      echo "> Health check 성공"
      switch_proxy
      break
  else
      echo "> Health check의 응답을 알 수 없거나 혹은 실행 상태가 아닙니다."
      echo "> Health check: ${RESPONSE}"
  fi
  if [ ${RETRY_COUNT} -eq 10 ]
  then
    echo "> Health check 실패. "
    echo "> 엔진엑스에 연결하지 않고 배포를 종료합니다."
    exit 1
  fi
  echo "> Health check 연결 실패. 재시도..."
  sleep 10
done#!/usr/bin/env bash
# switch.sh
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
function switch_proxy() {
    IDLE_PORT=$(find_idle_port)
    echo "> 전환할 Port: $IDLE_PORT"
    echo "> Port 전환"
    # nginx와 연결한 주소 생성
    # | sudo tee ~ : 앞에서 넘긴 문장을 service-url.inc에 덮어씀
    echo "set \$service_url http://127.0.0.1:${IDLE_PORT};" | sudo tee /etc/nginx/conf.d/service-url.inc
    echo "> 엔진엑스 Reload"
    # nignx reload. restart와는 다르게 설정 값만 불러옴
    sudo service nginx reload
}
1.4. appspec.yml 코드 추가
version: 0.0
os: linux
files:
  - source:  /
    destination: /home/ubuntu/sparta/deploy/
    overwrite: yes
permissions:
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu
hooks:
  AfterInstall:
    - location: stop.sh # 엔진엑스와 연결되어 있지 않은 스프링 부트를 종료합니다.
      timeout: 60
      runas: ubuntu
  ApplicationStart:
    - location: start.sh # 엔진엑스와 연결되어 있지 않은 Port로 새 버전의 스프링 부트를 시작합니다.
      timeout: 100
      runas: ubuntu
  ValidateService:
    - location: health.sh # 새 스프링 부트가 정상적으로 실행됐는지 확인 합니다.
      timeout: 60
      runas: ubuntu
1.5. gradle.yml 코드 추가
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: Java CI with Gradle
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Set up JDK 1.8
        uses: actions/setup-java@v1
        with:
          java-version: 1.8
      - name: Grant execute permission for gradlew
        run: chmod +x gradlew
        shell: bash
      - name: Build with Gradle
        run: ./gradlew build
        shell: bash
      - name: Make Directory for deliver
        run: mkdir deploy
        # Jar file Copy
      - name: Copy Jar
        run: cp ./build/libs/*.jar ./deploy/
        
        # appspec.yml file Copy
      - name: Copy appspec
        run: cp ./appspec.yml ./deploy/
        
        # script file Copy
      - name: Copy shell
        run: cp ./scripts/* ./deploy/
      # 압축파일 형태로 전달(codedeploy는 zip형식만 읽을 수 있음)
      - name: Make zip file
        run: zip -r -qq -j ./S3에 넣을 파일명 입력.zip ./deploy
      - name: Deliver to AWS S3
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          aws s3 cp \
          --region 버킷 리전 입력 \
          --acl private \
          ./S3에 넣을 파일명 입력.zip s3://버킷 이름 입력/
          
          
      - name: deploy
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          aws deploy create-deployment \
          --application-name 애플리케이션 이름 입력 \
          --deployment-group-name 배포 그룹 이름 입력 \
          --file-exists-behavior OVERWRITE \
          --s3-location bucket=버킷 이름 입력,bundleType=zip,key=S3에 넣은 파일명 입력.zip \
          --region 리전 입력
github push로 여러번 CI/CD Test를 해봅니다.

아래 코드 입력해서 배포가 잘 되고있으면 성공입니다.
ps -ef | grep java


Github : https://github.com/Jemoo1060/cicdPractice
GitHub - Jemoo1060/cicdPractice
Contribute to Jemoo1060/cicdPractice development by creating an account on GitHub.
github.com
출처 : https://shinsunyoung.tistory.com/120
출처 : https://stalker5217.netlify.app/devops/github-action-aws-ci-cd-1/
'Spring-boot' 카테고리의 다른 글
| RDS MySQL , Spring-boot 연동하기 (0) | 2022.06.04 | 
|---|---|
| Spring-boot 란? (0) | 2022.06.03 | 
| Spring-boot에서 github Action & nginx CI/CD구축하기 3 (0) | 2022.06.01 | 
| Spring-boot에서 github Action & nginx CI/CD구축하기 2 (0) | 2022.06.01 | 
| Spring-boot에서 github Action & nginx CI/CD구축하기 1 (0) | 2022.06.01 |