티스토리 뷰

반응형

 

 

 

JenkinsNginxSpringbootredis

 

 

 

 

우리가 서비스를 구축해서 사용하다보면 

업데이트, 버그픽스시 서비스를 순단하고 배포하고 오류수정 후 오픈하는 식의 과정을 가졌었다.

 

옛날에는 거의 사용자가 없는 시간대에 배포를 하곤 했었다는...ㅋ

 

요즘엔 AWS codedeploy 를 이용해서 blue/green 배포를 하거나

아니면  도커 컨테이너를 새로 올려서 배포판으로 연결하거나 하는 여러 방식이 있다.

각각의 서비스 구축 상태에 따라 여러 방식으로 blue/green 무중단 배포 시스템을 구성 할 수 있다.

 

 

 

 

과거했던 프로젝트들이 대부분 Jenkins - apache - tomcat 이런식으로 구성되어있고

서비스 배포할때마다 1~5분 정도 순단되는 상황을 겪고 있다.

(프로젝트 시작 당시에는 내부프로젝트니까 퇴근시간에 배포하지 뭐~ 이런 마인드 였나보다...)

 

문제는 사용자들이 남아있을 수도 있고

해당 사이트의 api 를 이용하는 타 서비스에 문제를 야기할 수도 있다.

 

그래서 올해 새로운 프로젝트에는 현재 구성상태에서 간단한 무중단배포 방식을 적용하려고 한다.

내가 적용한 방식은 가장 편법적(?)으로 알려진 아래와 같은 방식이다.

 

 

배포 구성도

 

 

너무나 잘 알려진 방식이다.

개발하고 커밋하고 커밋훅 받아서 빌드/배포하고 포트스위칭하여 무중단 배포하는 방식.

 

원래 세션관리까지는 생각안하고 있다가 무중단으로 배포해도

세션이 날라가면 기존 사용자들에겐 무의미가 되어 버리기 때문에...  급 redis를 연결해줌;;

 

 

그럼 아래와 같은 순서로 글을 이어가도록 하겠습니다.

 

 

 

 

 

 

 

1. github - Jenkins webhook 연동

2. 프로젝트 active profile 설정 및 profile 체크 url 작성

3. 서비스 기동 체크 및 nginx 구동 쉘스크립트 작성

4. 테스트

 

 

 

 

 

 

 

 


 

1. Github - Jenkins webhook 연동

 

 

 

 

일단 github와 jenkins의 webhook을 연결합니다.

(기존에 포스팅했던 글로 대체합니다.)

 

[Jenkins] Git webhook 설정하기.

 

 

[Jenkins] Git webhook 설정하기.

깃에 프로젝트가 존재하고 서버에 젠킨스가 설치되고 셋팅되어 있다는 전제하에 진행하도록 하겠습니다. 설정해보니 아래와 같은 진행순서를 가지면 될 것 같네요. Jenkins - Github 연결 설정 Github

jong-bae.tistory.com

 

 

 

 

 

그리고 프로젝트 빌드 후 조치 (Send build artifacts over SSH) 을 설정합니다.

 

jenkins - Send build artifacts over SSH 설정

 

 

젠킨스가 빌드를 성공하면 jenkins 위치에 workspace 에 파일을 배포해놓습니다.

(제 기준 /var/lib/jenkins/workspace/ 하위)

배포된 jar 파일을 /deploy 폴더에 보낸 후  아래 작성할 쉘스크립트를 수행하라고 작성했습니다.

 

 

 

 

 

 

 

 

 


2. 프로젝트 active profile 설정 및 profile 체크 url 작성

 

 

 

 

 

 

application.yml에서 port 구성을 나눠 줍시다.

 

#SERVER

...
server:
  port: 8080
  servlet:
    application-display-name: Deploy
    context-path: /
    encoding:
      charset: UTF-8
...


---
spring:
  config:
    activate:
      on-profile: was1

server:
  port: 8081

---
spring:
  config:
    activate:
      on-profile: was2

server:
  port: 8082

 

profile을 was1 , was2로 나눠줬고  각각으로 포트를 지정해줬습니다.

해당명칭으로  jar 실행시킬때 옵션을 줄꺼에요 : -Dspring.profiles.active=was1(or was2)

 

 

 

그리고 현재 구동되고 있는 profile의 정보를 얻기 위해 get 링크를 작성해줍시다.

 

@RestController
public class ProfileController {

    @Autowired private Environment env;
    
    @GetMapping("/profile")
    public String getProfile() {
        return Arrays.stream(env.getActiveProfiles())
                .findFirst()
                .orElse("");
    }
}

 

그럼 이제 active된 profile의 상태에 따라  was1 또는 was2를 리턴해줄겁니다.

 

 

 

 

 

 

 

 

 


3. 서비스 기동 체크 및 nginx 구동 쉘스크립트 작성

 

 

 

 

 

 

이제 리눅스에서 기동중인 서비스를 체크하여 신규를 배포하고 서비스를 전환하는 쉘 스크립트를 작성해야합니다.

사실 저도 리눅스에서 쉘스크립트는 많이 안써봐서 

이곳저곳 참고 많이 해서 작성했습니다.

 

#!/bin/bash

echo "=============== deploy start ===============" >> deploy.log

## 변수셋팅
BASE_PATH="/home/jenkins-ssh/deploy"
JAR_PATH="$(ls -t $BASE_PATH/sample-*.jar | head -1)"
JAVA_CMD="java -jar"
JVM_OPTIONS="-Xmx1024m"

echo " profile check..." >> deploy.log

CURRENT_PROFILE=$(curl -s http://localhost/profile)

echo "  > current profile: $CURRENT_PROFILE" >> deploy.log

## profile에 따른 셋팅
if [ $CURRENT_PROFILE == was1 ] 
then

  echo " setting target server #2..." >> deploy.log

  SET_PROFILE=was2
  SET_PORT=8082

elif [ $CURRENT_PROFILE == was2 ] 
then

  echo " setting target server #1..." >> deploy.log

  SET_PROFILE=was1
  SET_PORT=8081

else

  echo " not matching target... current profile : $CURRENT_PROFILE"
  echo " setting target server #1..." >> deploy.log

  SET_PROFILE=was1
  SET_PORT=8081
fi

echo "  > profile: $SET_PROFILE" >> deploy.log 
echo "  > port: $SET_PORT" >> deploy.log

echo " application symbol-link create..." >> deploy.log

APP_NAME="sample.jar"
DEPLOY_APP=$SET_PROFILE-$APP_NAME
DEPLOY_APP_PATH=$BASE_PATH/$DEPLOY_APP

ln -fs $JAR_PATH $DEPLOY_APP_PATH

## 실행중인 프로세스 kill
if pgrep -f "$DEPLOY_APP" > /dev/null; then
  echo " $DEPLOY_APP is alive... "
  
  sudo pkill -f "$DEPLOY_APP"

  echo " $DEPLOY_APP  process kill..." >> deploy.log
  
  sleep 5
  
  if [ $? -eq 0 ]; then
	echo " $DEPLOY_APP process kill success! " 
  else
	echo " $DEPLOY_APP process kill fail... "
  fi

else
  echo " 현재 구동중인 APP이 없습니다." >> deploy.log
fi


echo "================< Start deploy Jar >===============" >> deploy.log

## jar execute
BUILD_ID=dontKillMe nohup $JAVA_CMD $JVM_OPTIONS -Dspring.profiles.active=$SET_PROFILE $DEPLOY_APP_PATH > /dev/null 2>&1 & 

echo " $SET_PROFILE health check..." >> deploy.log

sleep 5

## health check logic
for reCnt in {1..21}
do
  response=$(curl -s http://localhost:$SET_PORT/actuator/health)
  upCount=$(echo $response | grep 'UP' | wc -l) 


  if [ $upCount -ge 1 ]
  then
    echo "  > Deploy Success!!" >> deploy.log
    
    ## nginx proxy_pass change.
    /home/jenkins-ssh/nginx-switch.sh
    break
  else
    echo "  > No response And Unknown Status..." >> deploy.log
  fi

  if [ $reCnt -eq 20 ]
  then 
    echo "  > Deploy Fail..." >> deploy.log
    exit 1
  fi

  echo "  > connection fail... Retry! - $reCnt / 10" >> deploy.log
  sleep 1
done
#!/bin/bash

echo "=============== <Nginx proxy switch> ==============" >> deploy.log

CURRENT_PROFILE=$(curl -s http://localhost/profile)

echo "  > current profile:  $CURRENT_PROFILE" >> deploy.log

if [ $CURRENT_PROFILE == was1 ]
then
  SET_PORT=8082
elif [ $CURRENT_PROFILE == was2 ]
then
  SET_PORT=8081
else
  echo "  > not matching target... current profile : $CURRENT_PROFILE"
  echo "  > use target server #1  port 8081"
  SET_PORT=8081
fi

echo "  > set port : $SET_PORT" >> deploy.log
echo "set \$service_port $SET_PORT;" | sudo tee /etc/nginx/conf.d/service-url.inc 

echo " Nginx reload" >> deploy.log
sudo systemctl reload nginx

echo " ********** THE END ********** " >> deploy.log

exit 0

 

제가 작성한 쉘스크립트는 위와 같습니다.

 

위 스크립트는 현재 사이트의 profile을 확인하여 port를 변수에 셋팅해주고

배포할 파일이 프로세스에 올라가있는지 확인 후 프로세스 킬을 하고

설정할 profile에 맞게 jar를 구동 시킵니다.

그리고 10초동안 사이트 구동을 체크하고 확인되면 nginx의 proxy port를 변경하여 재기동하는 스크립트입니다.

 

 

몇문장 코멘트 달아보자면

 

JAR_PATH="$(ls -t $BASE_PATH/sample-*.jar | head -1)"
# 배포되어 있는 sample-*.jar 중에 최신파일


BUILD_ID=dontKillMe nohup $JAVA_CMD $JVM_OPTIONS -Dspring.profiles.active=$SET_PROFILE $DEPLOY_APP_PATH > /dev/null 2>&1 & 
# BUILD_ID=dontKillMe  
## : Jenkins에서 job 수행 후 자식프로세스를 모두 kill해버려서 nohup 명령어가 제대로 수행안될때가 있었음.
# -Dspring.profiles.active=was1(or was2) 지정하여 실행
# > /dev/null 2>&1 &
## : 로그를 남기지 말고 에러로그(2)도 로그처럼(&1) 설정해라(null)


echo "set \$service_port $SET_PORT;" | sudo tee /etc/nginx/conf.d/service-url.inc 
# nginx service-url.inc 파일 내용을 tee 명령어로 치환하기

 

nginx 설정파일에 proxy_pass를 위 service-url.inc 를 볼수 있게 변경을 해야합니다.

 

server {
    listen       80;
    server_name  www.sample.co.kr

    include /etc/nginx/conf.d/service-url.inc;

    location / {
        proxy_pass http://127.0.0.1:$service_port; # service-url.inc의 변수 사용
        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;
    }
        
}

 

 

 

이제 작성한 파일들이 권한적인 문제가 없다면 잘 수행될 것 입니다.

(저는 몇가지 권한이 잘못 주어져서 막 수행이 안되고 그랬음...;;)

 

 

이제 깃 main 브런치에 소스가 커밋되면 jenkins webhook이 걸리면서 위 쉘스크립트를 수행하여 무중단 배포를 수행합니다.

 

 

세션정보를 redis에 담으면서 로그인 후 무중단 배포한 뒤 세션이 종료되지 않고 유지하는 것을 확인했습니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


참고블로그

 

 

7) 스프링부트로 웹 서비스 출시하기 - 7. Nginx를 활용한 무중단 배포 구축하기

이번 시간엔 무중단 배포 환경을 구축하겠습니다. (모든 코드는 Github에 있습니다.) 7-1. 이전 시간의 문제점? 이전 시간에 저희는 스프링부트 프로젝트를 Travis CI를 활용하여 배포 자동화 환경을

jojoldu.tistory.com

 

 

기존 프로젝트를 무중단 배포로 바꿔보자! (via jenkins)

도입

hyunminh.github.io

 

반응형
댓글
반응형
최근에 올라온 글
«   2025/02   »
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
Total
Today
Yesterday