Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 소수찾기 java
- kotest testcontainers
- spring DI
- Spring Cloud Gateway
- 형상관리
- ObjectOptimisticLockingFailureException
- spring aop
- interface
- 우아한 테크러닝
- 알고리즘
- RefreshToken
- Invalid property 'principal.username' of bean class
- springsecurity
- OptimisticLock
- jpa
- AccessToken
- TestContainers
- 백준
- multimodule testcontainers
- aop
- redissonlock aop
- S3
- java
- 낙관적 락 재시도
- 멀티모듈 테스트컨테이너
- ObjectOptimisticLockingFailureException 처리
- 낙관적 락 롤백
- @transactional
- netty
- DI
Archives
- Today
- Total
조급하면 모래성이 될뿐
무중단 배포 적용하기 본문
Why?
- 프로젝트를 하며 배포 과정에서 아래와 같이 이미 존재하는 포트를 띄우는 상황이 종종 발생했고, 그때마다 서버가 죽어버렸다.
- 서버가 죽게 되면 프런트팀에서 슬랙을 통해 요청했고, 그때마다 서버에 접속해서 다시 애플리케이션을 구동시켰다.
- 이런 경우를 최소화하고자 무중단 배포를 적용했다.
How?
- AWS CodeDeploy를 사용했기 때문에 블루 그린 무중단 배포를 적용하는 방식
- 이 경우는 ec2를 여러 개 생성해서 사용한다고 이해했다. 현재 프로젝트에서는 조금 오버스럽다고 느껴서 다른 방식을 선택했다.
- Nginx를 통한 무중단 배포 - 이걸로 적용함
- 2개의 애플리케이션을 실행한 후(8080, 8081), Nginx를 통해 포워딩한다.
- 포워딩 규칙은 현재 Nginx가 8080 포트를 바라보고 있다면, 8081에 배포 후, 성공했으면 8081로 변경한다.
- 실패했다면 그대로 둔다.
- 반대의 경우도 동일하다. (8081이면 8080으로)
- 2개의 애플리케이션을 실행한 후(8080, 8081), Nginx를 통해 포워딩한다.
참조
이해하기
1. 2개의 application을 실행시키기 위해 profile을 분리한다.
- profile을 dev, dev2로 두었다.
- dev는 8080, dev2는 8081 포트로 동작하며, 나머지 설정은 동일하다.
- application-dev.yml
server:
port: 8080
spring:
config:
activate:
on-profile: dev
... 나머지 설정은 동일함 !
- application-dev2.yml
server:
port: 8081
spring:
config:
activate:
on-profile: dev2
... 나머지 설정은 동일함 !
2. 현재 Nginx가 바라보지 않는 애플리케이션을 찾는다. (active-profile, port)
- 쉽게 Nginx의 proxy_pass설정 url이 무엇인지 확인한다.
- 현재 8080 포트로 설정돼있으면 8081이 바라보지 않는 애플리케이션이다.
2-1) 현재 동작중인 profile을 확인하기 위한 end-point 추가
- /profile 호출을 통해 현재 어떤 profile로 active 중인지 확인하기 위함이다.
package com.prgrms.tenwonmoa.domain.common.deploy;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class ProfileController {
private static final List<String> DEV_PROFILES = Arrays.asList("dev", "dev2");
private static final String DEFAULT_PROFILE = "default";
private final Environment env;
@GetMapping("/profile")
public String profile() {
List<String> profiles = Arrays.asList(env.getActiveProfiles());
String defaultProfile = profiles.isEmpty() ? DEFAULT_PROFILE : profiles.get(0);
return profiles.stream().filter(DEV_PROFILES::contains).findAny().orElse(defaultProfile);
}
}
2-2) Nginx 설정
- 아래는 현재 배포서버의 Nginx설정 파일이다. (/etc/nginx/sites-available 경로에 존재)
- proxy_pass가 변수로 설정돼있다.
- 이 변수는 /etc/nginx/conf.d/service-url.inc에 정의되어있다.
server {
listen 80;
server_name {domain_name};
include /etc/nginx/conf.d/service-url.inc; # 무중단 배포를 위해 추가
location / { # location 블록
include /etc/nginx/proxy_params;
proxy_pass $service_url;
}
...
- 아래는 service-url.inc 파일이다.
- 처음에 {ip_address} 부분을 localhost로 두었더니 제대로 동작하지 않았다.
- 동작은 curl {domain_name}으로 확인했다. 서비스 주소로 입력했을 때 port를 적지 않아도 요청이 잘 가야 한다.
- 그러나 no resolver defined to resolve localhost라는 에러와 함께 502 예외가 발생했다.
- 예외는 /var/log/nginx/error.log 경로에서 확인할 수 있다.
- 아래와 같이 직접 ec2 주소를 적어주었더니 해결되었다.
- 처음에 {ip_address} 부분을 localhost로 두었더니 제대로 동작하지 않았다.
set $service_url http://{ip_address}:8080;
2-3) profile.sh에서 /profile을 호출하여, active-profile이 dev면 8080 포트를 dev2면 8081 포트를 반환한다.
- 스크립트 내용은 참조링크에 자세하게 나와있어서 생략한다.
#!/usr/bin/env bash
# 쉬고있는 profile 찾기
function find_idle_profile()
{
...
}
# 쉬고 있는 profile 의 port 찾기
function find_idle_port()
{
...
}
예시로 현재 Nginx가 바라보고 있지 않은 애플리케이션이 dev2(8081)이라고 가정하고 설명.
3. 2를 통해 찾은 현재 Nginx가 바라보고 있지 않은 애플리케이션을 종료한다.
- stop.sh이 수행된다. 이 또한 생략
4. 배포를 수행한다.
- start.sh이 수행된다. 이 또한 생략
- 수행될 때 역시 바라보지 않는 애플리케이션을 찾은 후, 띄우게 된다.
...
IDLE_PROFILE=$(find_idle_profile)
echo "> $JAR_NAME 을 profile=$IDLE_PROFILE 로 실행."
nohup java -jar -Dspring.profiles.active=${IDLE_PROFILE} \
$JAR_NAME > $REPOSITORY/nohup.out 2>&1 &
...
5. Nginx가 바라보고 있지 않은 애플리케이션이 정상적으로 재구동 되었는지 확인한 후, Nginx의 설정을 바꾼다.
- health.sh이 수행된다.
- 보면 IDLE_PORT를 찾은 후 /profile요청을 포트와 함께 보낸다.
- 그리고 active profile로 dev를 포함하는지 grep으로 잡고 있다.
- 서버가 구동되는 시간이 걸릴 수 있기 때문에 10초씩에 한 번씩 총 10번을 보낸다. 그동안 서버 구동이 확인되지 않는다면 Nginx설정은 바꾸지 않는다. 만약 올바른 응답을 받았다면 Nginx설정을 바꾼다.
- Nginx설정을 바꾸기 위해 switch.sh이 수행된다.
- switch.sh에서도 변경될 주소를 localhost가 아닌 {ip_address}로 설정해주어야 정상적으로 switch 가 된다.
- "set \$service_url http://{ip_address}:${IDLE_PORT};"...
...
for RETRY_COUNT in {1..10}
do
RESPONSE=$(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_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
...
6. 작업의 순서는 appspec.yml에 의해 제어된다.
- appspec.yml
hooks:
AfterInstall:
- location: stop.sh # Nginx 와 연결되지 않은 부트 종료
timeout: 60
runas: ubuntu
ApplicationStart:
- location: start.sh # Nginx 와 연결되어 있지 않은 Port로 새 버전의 부트 시작
timeout: 60
runas: ubuntu
ValidateService:
- location: health.sh # 새 스프링 부트가 정상적으로 실행됐는지 확인
timeout: 60
runas: ubuntu
결과 확인
- 2번 이상 배포가 성공했다면, 서버에는 2개의 애플리케이션이 수행되어야 한다.
- ps -ef | grep java
- Nginx가 8080을 보고 있을 때 한번 더 배포가 성공했다면
- /etc/nginx/conf.d/service-url.inc 에서 $service_url의 포트가 8081로 변경되어야 한다.
- vi /etc/nginx/conf.d/service-url.inc 해서 확인하면 변경된 걸 확인할 수 있다.
반응형
'TroubleShooting > 데브코스' 카테고리의 다른 글
서브모듈 적용해서 DB접속정보 보호하기 ! (0) | 2022.08.07 |
---|---|
Restdocs와 Swagger UI 연동해서 EC2에 배포하기 (0) | 2022.08.04 |
CodeDeploy 적용기 (0) | 2022.07.26 |
Service에서 DataIntegrityViolationException을 Catch 못함 (0) | 2022.07.14 |
S3 파일 업로드 (0) | 2022.07.11 |