Skip to content

Commit

Permalink
Merge pull request #10 from kduoh99/Deploy/#9
Browse files Browse the repository at this point in the history
Deploy: CI/CD 구축
  • Loading branch information
kduoh99 authored Oct 30, 2024
2 parents a932aab + 84df23f commit e7cc074
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 0 deletions.
26 changes: 26 additions & 0 deletions appspec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
version: 0.0 #CodeDeploy 버전을 이야기합니다. 무조건 0.0으로 고정합니다.
os: linux
files:
- source: / #destination으로 이동시킬 파일. 여기서는 전체 파일을 의미.
destination: /home/ec2-user/app/zip/ #source에서 지정된 파일을 받는 위치
overwrite: yes #기존 파일들을 덮어쓸지 여부

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

hooks:
AfterInstall:
- location: stop.sh #엔진엑스와 연결되어 있지 않은 스프링 부트를 종료합니다.
timeout: 60 #스크립트 60초 이상 수행되면 실패합니다.
runas: ec2-user #stop.sh를 ec2-user 권한으로 실행하게 합니다.
ApplicationStart:
- location: start.sh #엔진엑스와 연결되어 있지 않은 Port로 새 버전의 스프링 부트를 시작합니다.
timeout: 60
runas: ec2-user
ValidateService:
- location: health.sh #새 스프링 부트가 정상적으로 실행됐는지 확인합니다.
timeout: 60
runas: ec2-user
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,7 @@ dependencies {
tasks.named('test') {
useJUnitPlatform()
}

jar {
enabled = false
}
39 changes: 39 additions & 0 deletions scripts/health.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash

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://127.0.0.1:$IDLE_PORT/profile"
sleep 10

for RETRY_COUNT in {1..10}
do
RESPONSE=$(curl -s http://127.0.0.1:${IDLE_PORT}/profile)
UP_COUNT=$(echo ${RESPONSE} | grep 'real' | wc -l)

if [ ${UP_COUNT} -ge 1 ] # Nginx와 연결되지 않은 포트로 스프링 부트가 잘 실행되었는지 체크합니다
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
35 changes: 35 additions & 0 deletions scripts/profile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash

# 쉬고 있는 profile 찾기: real1이 사용 중이면 real2가 쉬고 있고 반대면 real1이 쉬고 있음

function find_idle_profile() {
RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://duoh.site/profile) # 현재 Nginx가 바라보고 있는 스프링 부트가 정상적으로 수행 중인지 확인하고 응답값으로 상태코드를 전달받음

if [ ${RESPONSE_CODE} -ge 400 ] # 400번대 이상의 오류일 경우 real2를 사용
then
CURRENT_PROFILE=real2
else
CURRENT_PROFILE=$(curl -s https://duoh.site/profile)
fi

if [ ${CURRENT_PROFILE} == real1 ]
then
IDLE_PROFILE=real2
else
IDLE_PROFILE=real1
fi

echo "${IDLE_PROFILE}" # bash 스크립트는 반환 기능이 없기 때문에 echo로 값을 출력하고 클라이언트에서 그 값을 잡아서 사용
}

# 쉬고 있는 profile의 port 찾기
function find_idle_port() {
IDLE_PROFILE=$(find_idle_profile)

if [ ${IDLE_PROFILE} == real1 ]
then
echo "8081"
else
echo "8082"
fi
}
32 changes: 32 additions & 0 deletions scripts/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash

ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh

REPOSITORY=/home/ec2-user/app

echo "> Build 파일을 복사합니다."

cp $REPOSITORY/zip/*.jar $REPOSITORY/

echo "> 새 애플리케이션 배포"
JAR_NAME=$(ls -tr $REPOSITORY/*.jar | tail -n 1)

echo "> JAR NAME: $JAR_NAME"

echo "> $JAR_NAME 에 실행 권한을 부여합니다."

chmod +x $JAR_NAME

IDLE_PROFILE=$(find_idle_profile)

echo "> 새 애플리케이션을 $IDLE_PROFILE 로 실행합니다."

# 설정 파일의 위치를 지정하고 active profile을 통해 구동될 포트를 지정합니다.
nohup java -jar \
-Dspring.config.location=$REPOSITORY/config/application.yml,\
$REPOSITORY/config/application-prod.yml,\
$REPOSITORY/config/application-$IDLE_PROFILE.yml \
-Dspring.profiles.active=$IDLE_PROFILE,prod \
$JAR_NAME > $REPOSITORY/nohup.out 2>&1 &
20 changes: 20 additions & 0 deletions scripts/stop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash

ABSPATH=$(readlink -f $0) # 현재 stop.sh가 속해 있는 경로를 찾습니다.

ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh # 일종의 import 구문으로 stop.sh에서도 profile.sh의 function을 사용할 수 있게 합니다.

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 -9 $IDLE_PID"
kill -9 ${IDLE_PID}
sleep 5
fi
16 changes: 16 additions & 0 deletions scripts/switch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash

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 전환"
echo "set \$service_url http://127.0.0.1:${IDLE_PORT};" | sudo tee /etc/nginx/conf.d/service-url.inc # 엔진엑스가 변경할 프록시 주소를 생성하여 service-url.inc로 덮어 씁니다.

echo "> 엔진엑스 Reload"
sudo systemctl reload nginx
}
31 changes: 31 additions & 0 deletions src/main/java/com/hackathon/momento/global/ProfileController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.hackathon.momento.global;

import io.swagger.v3.oas.annotations.Hidden;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Hidden
@RestController
public class ProfileController {

private final Environment env;

public ProfileController(Environment env) {
this.env = 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);

return profiles.stream()
.filter(realProfiles::contains)
.findAny()
.orElse(defaultProfile);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class SecurityConfig {
private final String[] PERMIT_ALL_URLS = {
"swagger-ui/**",
"v3/api-docs/**",
"profile",
"api/v1/auth/**",
"api/v1/member/**"
};
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application-real1.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
server:
port: 8081
2 changes: 2 additions & 0 deletions src/main/resources/application-real2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
server:
port: 8082

0 comments on commit e7cc074

Please sign in to comment.