
프로젝트 중 무중단 배포 필요성을 느껴 ci/cd 구축을 해봤습니다.
여러 블로그에 리눅스, 우분투 혼용해서 설정한 정보글이 많기도 하고,
java코드는 기록이 남지만 aws ubuntu , S3, codedeploy 설정은 기록이 안남아서 까먹을 까봐 찾아본 정보를 기록합니다.
Nginx로 무중단 배포 구축하기
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
프로젝트 설정
무중단 배포시 옮겨주는 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
}
배포 쉘 스크립트 작성
프로젝트 폴더에 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
}
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
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 |