무중단 배포 서비스

박찬미·2022년 5월 30일
0

Spring Boot

목록 보기
17/17

전에 배포 자동화를 구현했는데, 배포되는 동안 서비스가 중단되는 문제가 있었다. 이제 무중단 배포를 해보겠다.

  • 무중단 배포 방식
    1. AWS에서 블루 그린 무중단 배포
    2. 도커를 이용한 웹서비스 무중단 배포

나는 Nginx를 이용해 무중단 배포를 할 것이다.

  • Nginx
    웹 서버, 리버스 프록시, 캐싱, 로드 밸런싱, 미디어 스트리밍 등을 위한 웹 서버이자 오픈소스 소프트웨어
  • 리버스 프록시
    엔진엑스가 외부의 요청을 받아 백엔드 서버로 요청을 전달하는 행위
    리버스 프록시 서버(엔진엑스)는 요청을 전달하고 실제 요청에 대한 처리는 뒷단의 웹 애플리케이션 서버들이 처리한다.

하나의 EC2 혹은 리눅스 서버에 엔진엑스 1대와 스프링 부트 Jar를 2대를 사용한다.

  1. 엔진엑스는 80(http), 443(https) 포트를 할당한다.
  2. 스프링 부트 1은 8081 포트
  3. 스프링 부트 2는 8082 포트

엔진엑스 무중단 배포 - 1


1. 사용자는 서비스 주소로 접속한다.(80 또는 443 포트)
2. 엔진엑스는 사용자의 요청을 받아 현재 연결된 스프링 부트로 요청 전달(1은 8081)
3. 스프링 부트2는 엔진엑스와 연결된 상태가 아니니 요청받지 못한다.

엔진엑스 무중단 배포 - 2


1. 배포하는 동안에도 서비스는 중단되지 않는다.
2. 배포가 끝나고 정상적으로 스프링 부트2가 구동 중인지 확인
3. 정상 구동 중이면 nginx reload 명령어를 통해 8081 대신 8082를 바라보도록 한다.
4. nginx reload는 0.1초 이내에 완료된다.

엔진엑스 무중단 배포 - 3


1. 위 과정대로라면 엔진엑스와 연결된 것은 스프링부트2다.
2. 스프링 부트1이 배포를 해서 끝나면 다시 엔진엑스는 스프링 부트1을 바라보도록 변경하고 nginx reload 실행한다.
3. 이후 요청부터는 엔진엑스가 다시 스프링 부트1로 요청을 전달한다.

무중단 배포 전체 구조

1. 엔진엑스 설치와 스프링 부트 연동

  • ec2에 엔진엑스 설치

    설치를 하려고 했더니 안된다. ⇒ Amazon Linux 2에서는 yum을 통한 nginx 설치가 지원되지 않는다.

    위 명령어로 설치했다.

    버전 확인 후 서비스 실행한다.

  • 보안 그룹 추가
    이제 엔진엑스의 포트번호를 보안 그룹에 추가한다. 기본적으로 80 포트이다. 해당 포트 번호가 보안 그룹에 없기 때문에 추가해주겠다.

  • 리다이렉션 주소 추가
    8080이 아닌 80포트로 주소가 변경되기 때문에 구글과 네이버 로그인에도 변경된 주소를 추가로 등록해야 한다.(ec2 껐다가 켰어가지고 새로운 주소로도 바꿔주었다.)
    이후 80 포트로 접속하면

    nginx 초기 화면이 보인다.

  • 엔진엑스와 스프링 부트 연동
    이제 엔진엑스가 현재 실행 중인 스프링 부트 프로젝트를 바라볼 수 있도록 프록시 설정을 하겠다.

sudo vim /etc/nginx/nginx.conf


엔진엑스 설정 파일을 수정한다.

소스 설명
- proxy_pass
엔진엑스로 요청이 오면 위 써져있는 주소로 전달
- proxy_set_header ~
실제 요청 데이터를 header의 각 항목에 할당
예) proxy_set_header X-Real-IP $remote_addr: Request Header의 X-Real-IP에 요청자의 IP를 저장

저장하고 엔진엑스를 재시작한다.

sudo service nginx restart


이제 엔진엑스가 프로젝트를 프록시한다.

2. 무중단 배포 스크립트 생성

스크립트 작업 전에 배포 시 8081을 쓸지, 8082를 쓸지 판단하는 기준이 되는 API를 추가하겠다.

로컬과 개발서버, 운영서버 분리

위처럼 .yml에 코드를 작성하면 —- 기준으로 값들이 구분된다.
spring.profile: local, dev, real 등이 —- 가 활성화되는 파라미터가 된다.
즉, 스프링부트 프로젝트를 실행시킬 때 nohup java -jar -Dspring.profiles.active=real
와 같이 사용하면 real
에 있는 값들이 프로젝트에 할당된다.

  • profile API 추가
    먼저 실행 중인 프로젝트의 Profile이 뭔지 확인할 수 있는 API를 만들겠다.
@RequiredArgsConstructor
@RestController
public class ProfileController {
    private final Environment env;

    @GetMapping("/profile")
    public String profile() {
        List<String> profiles = Arrays.asList(env.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);
    }
}

이제 이 코드가 제대로 작동하는지 테스트 코드를 작성해본다.

그리고 /profile이 인증 없이도 호출될 수 있도록 SecurityConfig 클래스에 제외 코드를 추가한다.

맨 마지막에 /profile 추가해주면 된다.

이제 위 SecurityConfig도 제대로 설정이 되었는지 테스트 하는 검증 코드를 작성한다.
ProfileController와 같은 위치이다.

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ProfileControllerTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void profile은_인증없이_호출된다() throws Exception {
        String expected = "default";

        ResponseEntity<String> response = restTemplate.getForEntity("/profile", String.class);
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).isEqualTo(expected);
    }
}

모두 테스트가 성공했으니 깃허브로 배포한다.


뒤에 /profile로 접속하면 real로 나온다.

  • real1, rea2 profile 생성
    현재 EC2 환경에서 실행되는 profile은 real밖에 없다. 해당 profile은 Travis CI 배포 자동화를 위한 profile이니 무중단 배포를 위한 profile 2개(real1, real2)를 추가한다.

    이렇게 하고 깃허브에 푸시했다.

  • 엔진엑스 설정 수정
    배포 때마다 엔진엑스의 프록시 설정(스프링 부트로 요청을 흘려보내는)이 순식간에 교체된다. 프록시 설정이 교체될 수 있도록 설정을 추가하겠다.

sudo vim /etc/nginx/conf.d/service-url.inc

  • 배포 스크립트 작성
    step1/2의 스크립트와 중복되지 않도록 step3 디렉토리를 생성한다.

원래 step2에 배포되도록 appspec.yml에 설정이 되어있었는데 step3로 경로를 바꿔준다.
무중단 배포를 진행할 스크립트는 5개이다. 각각 스크립트를 설정한다.

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/app/step3/zip/
    overwrite: yes

permissions:
  - object: /
    pattern: "**"
    owner: ec2-user
    group: ec2-user

hooks:
  AfterInstall:
    - location: stop.sh # 엔진엑스와 연결되어 있지 않은 스프링 부트를 종료.
      timeout: 60
      runas: ec2-user
  ApplicationStart:
    - location: start.sh # 엔진엑스와 연결되어 있지 않은 Port로 새 버전의 스프링 부트를 시작.
      timeout: 60
      runas: ec2-user
  ValidateService:
    - location: health.sh # 새 스프링 부트가 정상적으로 실행됐는지 확인.
      timeout: 60
      runas: ec2-user

스크립트 설명
- stop.sh
기존 엔진엑스에 연결되어 있진 않지만, 실행 중이던 스프링 부트 종료(이전 버전 종료)
- start.sh
배포할 신규 버전 스프링 부트 프로젝트를 stop.sh로 종료한 profile로 실행(신규 버전 돌릴(종료되어있는) 곳에 실행)
- health.sh
start.sh로 실행시킨 프로젝트가 정상적으로 실행됐는지 체크
- switch.sh
엔진엑스가 바라보는 스프링 부트를 최신 버전으로 변경(바라보는 곳 스위치)
- profile.sh
앞선 4개 스크립트 파일에서 공용으로 사용할 profile 과 포트 체크 로직

jar 파일이 복사된 이후부터 차례로 앞선 스크립트들이 실행된다고 생각하면 쉽다.
사용되는 스크립트 모두 작성(소스는 깃헙 scripts에..)

3. 무중단 배포 테스트

배포 테스트 전에 추가 작업을 해주겠다.
배포를 많이하면 Jar 파일명이 겹칠 수 있고 매번 버전을 올리는 것이 귀찮으므로 자동으로 버전 값이 변경되도록 하겠다.

  • build.gradle



    위 로그를 보면 2개의 애플리케이션이 실행되고 있다.

이제 마스터 브랜치에 푸시가 발생하면 자동으로 서버 배포가 진행되고 서버 중단이 없는 시스템이 되었다!

노션에 정리해뒀던 내용을 다시 블로그로 복사해온거라 중간중간 빠진 부분이 있을 수도 있다.

0개의 댓글