Nginx
Nginx(엔진엑스)
는 동시 접속 처리에 특화된 웹 서버 프로그램
- Apache 보다는 단순하면서, 전달자 역할만 수행하기 때문에 동시 접속 처리에 용이
- 비동기 Event-Driven 기반 구조
- Nginx 의 역할로서,
- 정적 파일 처리 HTTP 서버 역할
- Reverse Proxy 서버 역할
Reverse Proxy 서버? Client 가 Server 로 요청한 경우, Proxy 서버 (NGINX) 가 Reverse 서버 (Application) 로부터 데이터를 가져오는 역할을 한다.
Event-Driven 구조?
여러 Connection 을 Event Handler 를 통해 비동기 방식으로 처리
Nginx 무중단 배포
구성
- Nginx 1대
- HTTP : 80 port
- HTTPS : 443 port
- Spring Boot Application 2대
- Spring Boot 1 : 8081 port
- Spring Boot 2 : 8082 port
1. Nginx 설치 및 Spring Boot 연동
EC2 에 Nginx 설치
$ sudo yum install nginx
# nginx 실행
$ sudo service nginx start
보안 그룹 추가
- 인바운드 규칙 >
HTTP port 80
지정 안되어 있는 경우 지정 - EC2 domain 주소 (port 제외) 접속 확인
Spring Boot + Nginx
/etc/nginx/nginx.conf
수정
...
server {
...
location \ {
# Nginx 로 요청이 들어오면 해당 주소로 전달
proxy_pass http://localhost:8080;
# 실제 요청 데이터를 header 의 각 항목에 할당
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;
}
...
}
...
Nginx 재시작
$ sudo service nginx restart
Spring Boot 연동 확인
- 프로젝트 Domain (port 제외) 접속 확인
2. Profile API 추가
- 실행중인 profile 조회하기 위한 API
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/profile")
public class ProfileController {
private final Environment environment;
@GetMapping
public String profile() {
// .getActiveProfiles() : 현재 실행 중인 Active Profile 목록 조회
List<String> profiles = Arrays.asList(environment.getActiveProfiles());
List<String> realProfiles = Arrays.asList("real", "real1", "real2");
String defaultProfile = profiles.isEmpty() ? "default" : profiles.get(0);
return profiles.stream()
.filter(realProfiles::contains)
.findAny()
.orElse(defaultProfile);
}
}
SecurityConfiguration.java 수정
/profile
API security permission 승인 처리
...
.antMatchers("/*/sign/**", "/*/sign/*/**", "/social/**", "/profile").permitAll()
...
3. Profile 파일 추가 생성
- real1, real2 각각 profile yml 생성
# application-real1.yml, application-real2.yml
server:
port: 8081 # real2 인 경우, 8082
spring:
profiles: real1 # real2 인 경우, real2
url:
base: [Application Domain]
logging:
level:
root: warn
com.demo.restapi: info
file:
path: /home/ec2-user/logs/rest-api
max-history: 7
4. Nginx 설정 수정
/etc/nginx/conf.d/service-url.inc
생성
$ sudo vi /etc/nginx/conf.d/service-url.inc
# service=url.inc
set $service_url http://127.0.0.1:8080;
/etc/nginx/nginx.conf
수정
...
server {
...
location \ {
# 추가
include /etc/nginx/conf.d/service-url.inc
# http://localhost:8080 -> $service_url 로 변경
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;
}
...
}
...
Nginx 재시작
$ sudo service nginx restart
5. Deploy Script 추가
추가할 Scrpit 목록
script | 설정 |
---|---|
profile.sh |
현재 profile 버전, port 확인 |
stop.sh |
실행 중이던 Spring Boot 종료 |
start.sh |
배포할 Spring Boot 종료 후 profile.sh 실행 |
health.sh |
start.sh 로 실행된 프로젝트 정상 동작 확인 |
switch.sh |
Nginx 의 Spring Boot 를 최신 profile 버전으로 변경 |
appspec.yml
수정
hooks:
# Nginx 와 연동된 Spring Boot 프로젝트 종료
AfterInstall:
- location: stop.sh
timeout: 60
runas: ec2-user
# Nginx 와 연동안된 profile 버전으로 Spring Boot 실행
ApplicationStart:
- location: start.sh
timeout: 60
runas: ec2-user
# 새로 실행된 Spring Boot 정상 동작 확인
ValidateService:
- location: health.sh
timeout: 60
runas: ec2-user
profile.sh
#!/usr/bin/env bash
# 쉬고 있는 profile 찾기
function find_idle_profile() {
# 현재 Nginx 와 연동된 프로젝트가 정상적인지 확인
# RESPONSE_CODE 는 HTTP status code 로 반환
RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/profile)
# 프로젝트 상태가 400 보다 큰 경우, 에러
if [ ${RESPONSE_CODE} -ge 400 ]
then
CURRENT_PROFILE=real2
else
CURRENT_PROFILE=$(curl -s http://localhost/profile)
fi
if [ ${CURRENT_PROFILE} == real1 ]
then
IDLE_PROFILE=real2
else
IDLE_PROFILE=real1
fi
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
}
stop.sh
#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
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
start.sh
#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
REPOSITORY=/home/ec2-user/apps
DEPLOY_DIRECTORY=/home/ec2-user/deploy
PROJECT_NAME=rest-api
echo "> old 파일 이동"
mv $REPOSITORY/$PROJECT_NAME/*.jar $REPOSITORY/$PROJECT_NAME/old/
echo "> Build 파일 복사"
cp $DEPLOY_DIRECTORY/$PROJECT_NAME/*.jar $REPOSITORY/$PROJECT_NAME/
echo "> 새 애플리케이션 배포"
JAR_NAME=$(ls -tr $REPOSITORY/$PROJECT_NAME/*.jar | tail -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.yml,classpath:/application-$IDLE_PROFILE.yml,/home/ec2-user/apps/config/application-db.yml \
-Dspring.profiles.active=$IDLE_PROFILE \
-Dfile.encoding=UTF-8 \
$JAR_NAME > $REPOSITORY/$PROJECT_NAME/nohup.out 2>&1 &
health.sh
#!/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://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)
# $UP_COUNT >= 1 ("real" 문자열이 있는지 검증)
if [ ${UP_COUNT} -ge 1 ]
then
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
switch.sh
#!/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 전환"
# Nginx 가 변경할 Proxy 주소를 생성 후, service-url.inc 에 over-write
echo "set \$service_url http://127.0.0.1:${IDLE_PORT};" | sudo tee /etc/nginx/conf.d/service-url.inc
echo "> 엔진엑스 Reload"
# Nginx 설정 reload(끊김 없이 다시 불러옴)
sudo service nginx reload
}
무중단 배포 확인
build.gradle
수정
version = '0.0.1-SNAPSHOT-' + new Date().format("yyyyMMddHHmmss")
Log 확인
# CodeDeploy Log
$ vi /opt/codedeploy-agent/deploayment-root/deployment-logs
# Spring Boot log
$ vi nohup.out
# Java application 실행 여부 확인
$ ps -ef | grep java