* "스프링 부트와 AWS로 혼자 구현하는 웹 서비스 " 책을 보고 저에게 맞게 적용한 내용을 정리하였습니다.
1. 설정 파일
- /etc/nginx/nginx.conf
- proxy_pass : 80 포트로 연결할 주소 및 포트 (ex)localhost:8081)
- proxy_set_header : 80 포트의 헤더의 요청값을 그대로 가져와 연결
- include : service_url 변수의 값을 할당 받기 위해 import
-
1234567include /etc/nginx/conf.d/service-url.inc;location / {proxy_pass $service_url;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header Host $http_host;}
cs - /etc/nginx/conf.d/service-url.inc
1set $service_url http://127.0.0.1:8082;
cs - service_url : 80 포트에 연결할 주소 http://127.0.0.1:8082 || http://127.0.0.1:8081
2. Shell Script 파일
- deploy.sh
1234#!/usr/bin/env bashsh script/stop.shsh script/start.shsh script/health.sh
cs - #!/usr/bin/env bash : 실행 시에 bash 를 쓴다는 뜻
- 배포 시에 명령어로 실행하는 스크립트
- profile.sh
1234567891011121314151617181920212223242526272829303132333435#!/usr/bin/env bashfunction find_idle_profile(){RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/profile)if [ ${RESPONSE_CODE} -ge 400 ] # 400 보다 크면 (즉, 40x/50x 에러 모두 포함)thenCURRENT_PROFILE=dev2elseCURRENT_PROFILE=$(curl -s http://localhost/profile)fiif [ ${CURRENT_PROFILE} == dev1 ]thenIDLE_PROFILE=dev2elseIDLE_PROFILE=dev1fiecho "${IDLE_PROFILE}"}function find_idle_port(){IDLE_PROFILE=$(find_idle_profile)if [ ${IDLE_PROFILE} == dev1 ]thenecho "8081"elseecho "8082"fi}
cs - bash는 return value가 안되니 제일 마지막 줄에 echo로 해서 결과 출력후, 클라이언트에서 값을 사용한다.
- find_idle_profile() : 쉬고 있는 profile 찾기: dev1이 사용중 이면 dev2가 쉬고 있고, 반대면 dev1이 쉬고 있음
- find_idle_port(): 쉬고 있는 profile의 port 찾기
- switch.sh
12345678910111213141516#!/usr/bin/env bashABSPATH=$(readlink -f $0)ABSDIR=$(dirname $ABSPATH)source ${ABSDIR}/profile.shfunction 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.incecho "> 엔진엑스 Reload"sudo service nginx restart}
cs - switch_proxy : 쉬고 있는 포트를 80 포트로 연결
- stop.sh
1234567891011121314151617181920#!/usr/bin/env bashABSDIR=$(pwd)/scriptecho $ABSDIRsource ${ABSDIR}/profile.shIDLE_PORT=$(find_idle_port)echo "> $IDLE_PORT 에서 구동중인 애플리케이션 pid 확인"IDLE_PID=$(lsof -ti tcp:${IDLE_PORT})if [ -z ${IDLE_PID} ]thenecho "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."elseecho "> kill -15 $IDLE_PID"kill -15 ${IDLE_PID}sleep 5fi
cs - 현재 80에 포워딩 되지 않은 포트 pid 검색 및 종료
- start.sh
1234567891011121314151617181920212223242526272829303132333435#!/usr/bin/env bashABSPATH=$(readlink -f $0)ABSDIR=$(dirname $ABSPATH)source ${ABSDIR}/profile.shREPOSITORY=$(pwd)PROJECT_NAME=MyprojectIDLE_PROFILE=$(find_idle_profile)echo "> Build 파일 복사"./gradlew clean build -Pprofile=$IDLE_PROFILEecho ">./gradlew clean build -Pprofile=$IDLE_PROFILE"echo "> 새 어플리케이션 배포"JAR_NAME=$(ls -tr $REPOSITORY/build/libs/*.war | tail -n 1)echo "> JAR Name: $JAR_NAME"sudo chmod +x $JAR_NAMEecho "> $JAR_NAME 에 실행권한 추가"echo "> $JAR_NAME 실행"echo ${REPOSITORY}echo "> $JAR_NAME 를 profile=$IDLE_PROFILE 로 실행합니다."nohup java -jar -Dspring.profiles.active=$IDLE_PROFILE$JAR_NAME > $REPOSITORY/nohup.out 2>&1 &echo "> cat $REPOSITORY/nohup.out"echo "end"
cs - 다음 배포할 profile을 받고 빌드
- war 파일에 실행 권한을 주고 실행
- nohup java -jar -Dspring.profiles.active=$IDLE_PROFILE $JAR_NAME > $REPOSITORY/nohup.out 2>&1 & : war파일 실행 명령어
- nohup : 터미널 세션이 끝나도 계속 실행
- java -jar $JAR_NAME: $JAR_NAME .war 파일 실행
- -Dspring.profiles.active=$IDLE_PROFILE : spring profile을 IDLE_PROFILE로 실행
- $REPOSITORY/nohup.out : stdout 로그를 nohup.out 파일에 기록
- 2>&1 : stderr도 stdout에 함께 기록(모두 nohup.out 파일에 기록됨)
- & : 백그라운드로 실행
- health.sh
123456789101112131415161718192021222324252627282930313233343536373839#!/usr/bin/env bashABSPATH=$(readlink -f $0)ABSDIR=$(dirname $ABSPATH)source ${ABSDIR}/profile.shsource ${ABSDIR}/switch.shIDLE_PORT=$(find_idle_port)echo "> Health Check Start!"echo "> IDLE_PORT: $IDLE_PORT"echo "> curl -X GET http://127.0.0.1:$IDLE_PORT/profile "sleep 10for RETRY_COUNT in {1..10}doRESPONSE=$(curl -s http://localhost:${IDLE_PORT}/profile)UP_COUNT=$(echo ${RESPONSE} | grep 'dev' | wc -l)if [ ${UP_COUNT} -ge 1 ]then # $up_count >= 1 ("dev" 문자열이 있는지 검증)echo "> Health check 성공"switch_proxybreakelseecho "> Health check의 응답을 알 수 없거나 혹은 실행 상태가 아닙니다."echo "> Health check: ${RESPONSE}"fiif [ ${RETRY_COUNT} -eq 10 ]thenecho "> Health check 실패. "echo "> 엔진엑스에 연결하지 않고 배포를 종료합니다."exit 1fiecho "> Health check 연결 실패. 재시도..."sleep 10done
cs - 새로 배포된 포트에 profile 과 정상 응답이 오는지 체크
- 10번 반복될 동안 모두 실패한다면 원래 80에 연결 되었는 포트에 배포
3. profile check api
- {root}/profile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
import lombok.RequiredArgsConstructor;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
@RestController
@RequiredArgsConstructor
public class EnvController {
private final Environment environment;
@GetMapping("/profile")
public String profile(){
List<String> profiles = Arrays.asList(environment.getActiveProfiles());
List<String> activeProfiles = Arrays.asList("dev1","dev2","prod1","prod2");
System.out.println(profiles);
System.out.println(activeProfiles);
String defaultProfile = "dev";
System.out.println(defaultProfile);
return profiles.stream().filter(activeProfiles::contains).findAny().orElse("ASDF");
}
}
|
cs |
- environment에서 현재 적용된 profile 목록을 모두 가져옴
- "dev1","dev2","prod1","prod2" 중 포함된 값을 return;
- 아무것도 없으면 ASDF(default로 설정하지 않은 것은 빠른 체크를 위해)
- dev는 개발서버 / prod는 실서버