티스토리 뷰

<사용 기술 스택>

- AWS EC2 (Amazon Linux2 -> CentOS 기반)

- Nginx -> 리버스 프록시 용도

- Let's Encrypt(Certbot) -> SSL 인증서 발급 용도

- SpringBoot 및 쉘 스크립트

 

0. 사전 개념 정리!

# 프록시, 리버스 프록시란?

"프록시 = 중개" 해주는 역할로 이해하면 쉽다!

- 사용자를 대신해 서버에 접근 해주는 역할

-> 보안, 성능, 안정성 향상 + 캐시 서버 역할 가능

-> 포워드 프록시와 리버스 프록시로 구분할 수 있다

 

<포워드 프록시> 

- 내부 망에서 사용해서 외부 인터넷 망으로 연결하기 전에 걸러내는 역할

- 클라이언트가 특정 웹 사이트에 접근하는 것을 사전 차단하는 역할 (학교에서 유해사이트 차단 등... ㅋㅋ)

- 포워드 프록시 서버의 IP를 대신 사용하는 것으로 클라이언트가 누구인지를 감추는 역할도 할 수 있다 (IP 우회)

- 동일한 요청이 들어올 경우 캐싱된 데이터를 곧바로 전달해주는 역할도 할 수 있다 (캐싱)

 

<리버스 프록시>

- 서버 단의 맨 앞에 위치해서 요청을 분배해주는 역할 (로드 밸런싱 등...)

- 애플리케이션 서버는 프록시 뒤에 감춰버리는 효과를 얻을 수 있다

# Nginx란?

- 비동기 이벤트 기반으로 동시접속 처리에 특화된 "웹 서버"

- Apache보다 단순하고, 전달자 역할에 특화. 가볍고 빠르다

 

1. 정적 파일을 제공하는 웹 서버의 역할

2. 리버스 프록시 역할

3. 비동기 이벤트 처리 역할

 

# HTTPS는 어떻게 적용하는지?

https://aws-hyoh.tistory.com/38 - 미닉스 김인성님의 블로그

 

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

 

 

 

 

 

 

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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 27 28 29 30 31
글 보관함