티스토리 뷰
<사용 기술 스택>
- AWS EC2 (Amazon Linux2 -> CentOS 기반)
- Nginx -> 리버스 프록시 용도
- Let's Encrypt(Certbot) -> SSL 인증서 발급 용도
- SpringBoot 및 쉘 스크립트
0. 사전 개념 정리!
# 프록시, 리버스 프록시란?
"프록시 = 중개" 해주는 역할로 이해하면 쉽다!
- 사용자를 대신해 서버에 접근 해주는 역할
-> 보안, 성능, 안정성 향상 + 캐시 서버 역할 가능
-> 포워드 프록시와 리버스 프록시로 구분할 수 있다
<포워드 프록시>
- 내부 망에서 사용해서 외부 인터넷 망으로 연결하기 전에 걸러내는 역할
- 클라이언트가 특정 웹 사이트에 접근하는 것을 사전 차단하는 역할 (학교에서 유해사이트 차단 등... ㅋㅋ)
- 포워드 프록시 서버의 IP를 대신 사용하는 것으로 클라이언트가 누구인지를 감추는 역할도 할 수 있다 (IP 우회)
- 동일한 요청이 들어올 경우 캐싱된 데이터를 곧바로 전달해주는 역할도 할 수 있다 (캐싱)
<리버스 프록시>
- 서버 단의 맨 앞에 위치해서 요청을 분배해주는 역할 (로드 밸런싱 등...)
- 애플리케이션 서버는 프록시 뒤에 감춰버리는 효과를 얻을 수 있다
# Nginx란?
- 비동기 이벤트 기반으로 동시접속 처리에 특화된 "웹 서버"
- Apache보다 단순하고, 전달자 역할에 특화. 가볍고 빠르다
1. 정적 파일을 제공하는 웹 서버의 역할
2. 리버스 프록시 역할
3. 비동기 이벤트 처리 역할
# HTTPS는 어떻게 적용하는지?
1. Nginx 서버에 SSL 인증서를 발급해둔다
2. Nginx 서버가 443 포트로 들어오는 요청을 모두 받는다
- 뿐만 아니라 HTTP(80) 포트로 들어오는 요청도 HTTPS(443)으로 강제 리다이렉트도 시켜줄 수 있다
3. Nginx 단에서 세션키(대칭키)를 이용해 데이터를 복호화 -> 복호화된 데이터를 애플리케이션 서버로 전송해준다
정리 코드 및 순서
1. EC2 서버에 Certbot 이용해 인증서 발급받기 (Nginx 방식)
sudo certbot --nginx
2. Nginx 환경 설정
/etc/nginx/nginx.conf
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
#클라이언트가 보낼 수 있는 requst의 총 크기
client_max_body_size 30M;
server {
# server_name www.smarttownnotice.gq;
server_name [내 도메인 주소];
# 무중단 배포 시 교체할 url이 담긴 파일
include /etc/nginx/conf.d/service-url.inc;
location / {
# 해당 주소로 요청 전송 (service-url.inc에서 가져온다)
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;
}
listen 443 ssl; # 443 포트로 요청이 들어왔을 때
ssl_certificate /etc/letsencrypt/live/www.smarttownnotice.gq/fullchain.pem; # 공개키
ssl_certificate_key /etc/letsencrypt/live/www.smarttownnotice.gq/privkey.pem; # 비밀키
...
}
}
이후 Nginx reload (!= restart)
sudo systemctl reload nginx
* restart = 끊김 O / reload = 끊김 X
3. 배포 스크립트 만들기
* 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 기반으로 만들어진 스크립트입니다
1. start.sh
#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
REPOSITORY=/home/ec2-user/app/smartnotice_build
PROJECT_NAME=smartnotice
echo "> Build 파일 복사"
echo "> cp ${REPOSITORY}/zip/*.jar ${REPOSITORY}/"
cp ${REPOSITORY}/zip/build/libs/*.jar ${REPOSITORY}/
echo "> 새 애플리케이션 배포"
JAR_NAME=$(ls -tr ${REPOSITORY}/*.jar | tail -n 1)
echo "> JAR Name: ${JAR_NAME}"
echo "> ${JAR_NAME} 에 실행 권한 추가"
chmod +x ${JAR_NAME}
echo "> ${JAR_NAME} 실행"
IDLE_PROFILE=$(find_idle_profile) # SubShell을 호출해 함수의 결과값을 리턴받는다
echo "> ${JAR_NAME} 을 profile=${IDLE_PROFILE} 로 실행합니다"
nohup java -jar \
-Dspring.config.location=classpath:/application.properties,/home/ec2-user/app/application-real-db.properties,classpath:/application-$IDLE_PROFILE.properties,/home/ec2-user/app/application-api.properties \
-Dspring.profiles.active=${IDLE_PROFILE} \
${JAR_NAME} > ${REPOSITORY}/nohup.out 2>&1 &
2. stop.sh
#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH) # 현재 stop.sh가 속해있는 경로를 찾는다
source ${ABSDIR}/profile.sh # java import 생각하면 됨 - 다른 .sh 파일의 function을 사용할 수 있게 해줌
IDLE_PORT=$(find_idle_port) #subShell
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
3. profile.sh
#!/usr/bin/env bash
# 쉬고있는 profile 찾기
function find_idle_profile() {
# 현재 애플리케이션이 몇번 포트로 실행되고 있는지 확인 (Nginx가 443으로 받아서 포워드)
RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://www.smarttownnotice.gq/profile)
if [ "${RESPONSE_CODE}" -ge 400 ] || [ "${RESPONSE_CODE}" -eq 000 ] # 400보다 크거나, 000(TimeOut)이면 -> Error 발생
then
CURRENT_PROFILE=real1 # 에러 발생 시 real1 포트로 보내도록 세팅
else # 정상 상태(200) 이라면
CURRENT_PROFILE=$(curl -s https://www.smarttownnotice.gq/profile) # 사이트에서 현재 사용중인 포트를 응답해줌(real1/real2)
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) # SubShell 호출
if [ "${IDLE_PROFILE}" == real1 ]
then
echo "8081"
else
echo "8082"
fi
}
4. 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)
if [ ${UP_COUNT} -ge 1 ] # real 문자열이 있는지 검증
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 "> Nginx에 연결하지 않고 배포를 종료합니다"
exit 1
fi
echo "> Health Check 연결 실패. 재시도..."
sleep 10
done
5. 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가 변경할 프록시 주소 생성해서 파이프라인으로 넘김 | 덮어씌우기
echo "set \$service_url http://127.0.0.1:${IDLE_PORT};" | sudo tee /etc/nginx/conf.d/service-url.inc
echo "> Nginx Reload"
sudo service nginx reload # restart(끊김 O) != reload(끊김 X)
}
4. 배포!
스프링부트 애플리케이션 내 appsepc.yml
# CodeDeploy Version
version: 0.0
os: linux
files:
- source: / # CodeDeploy에서 전달해준 파일 중 destination으로 이동시킬 대상 지정 (/ = 루트, 전체 파일)
destination: /home/ec2-user/app/smartnotice_build/zip # source에서 지정된 파일을 받을 위치. 이후 Jar은 destination에서 옮겨진 파일들로 진행.
overwrite: yes
# CodeDeploy에서 EC2 서버로 넘겨준 파일들 모두 ec2-user 권한을 갖도록 한다
permissions:
- object: /
pattern: "**"
owner: ec2-user
group: ec2-user
# CodeDeploy 배포 단계에서 실행할 명령어 지정
hooks:
AfterInstall: # 제일 먼저 수행
- location: /scripts/stop.sh # nginx와 연결되어있지 않는 스프링부트 종료
timeout: 60
runas: ec2-user
ApplicationStart: # 그 다음 수행
- location: /scripts/start.sh # nginx와 연결되어있지 않은 포트로 새 버전의 스프링부트 실행
timeout: 60
runas: ec2-user
ValidateService: # 마지막으로 수행
- location: /scripts/health.sh # 새 스프링 부트가 정상적으로 실행됬는지 확인
timeout: 60
runas: ec2-user
'웹' 카테고리의 다른 글
Docker + 쿠버네티스 공부 (0) | 2023.02.15 |
---|---|
Github Commit Status(?) 적용해보기 (Jenkins 이용) (0) | 2022.09.14 |
EC2에 Jenkins 설치해서 CI/CD 환경 구축해보기 (0) | 2022.09.13 |
URI와 URL, URN의 차이점에 대해 (0) | 2022.09.07 |
Travis CI를 이용한 CI/CD 환경 구성 실습 (2) (0) | 2022.09.02 |