GitHub Actions + AWS EC2 + Docker + Nginx 기반 Blue/Green 무중단 배포 구축(2)

동준·2024년 10월 30일
0

개발일지

목록 보기
9/9

이전 글을 읽고 해당 글을 읽으셔야 합니다!
https://velog.io/@kim00ngjun_0112/GitHub-Actions-AWS-EC2-Docker-Nginx-%EA%B8%B0%EB%B0%98-BlueGreen-%EB%AC%B4%EC%A4%91%EB%8B%A8%EB%B0%B0%ED%8F%AC-%EA%B5%AC%EC%B6%951

4. 빌드 & 배포 개요

간략한 모식도는 WAS 빌드 과정(도커 이미지화 및 푸시)을 거치고 EC2 인스턴스에 배포(도커 이미지 풀 및 트래픽 스위칭)를 통한 CI/CD 파이프라인 구축을 통해 무중단 배포를 실현한다. 그리고 이 과정을 GitHub Actions에서 스크립트로 작성한다.

그렇기 위해서 일단 배포의 대상이 되는 SpringBoot WAS를 간단하게 구현하고 배포 자동화에 참여할 수 있도록 설정 파일을 세팅해야 한다. 우선 스프링부트 기반으로 앱 서버를 간단히 구축해보자.

5. SpringBoot WAS 구현 & 도커 추가 세팅

해당 포스트는 자바, 스프링 문법을 기술하는 글이 아니므로 관련 내용은 생략한다. 일단 내가 배포하려는 WAS는 나의 백엔드 기술 스택에 맞춰 스프링부트를 기반으로 간단하게 구현한다. 핵심은 WAS의 실시간 업데이트에도 배포 서버가 중단되지 않고도 버전 업데이트가 이뤄지는 것을 확인하는 점이다.

1) 의존성 및 코드

정말 간단한 WAS여서 의존성도 2~3개에, 코드도 컨트롤러 코드 밖에 없다. 포스트맨 등을 활용해서 확인할 수도 있지만, 배포 서비스를 가정하기 위해서 직접 웹 브라우저에서 확인하기 위한 타임리프 의존성을 추가했다.

// gradle dependency...

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
// main controller code

@Controller
public class WebController {

    @Value("${server.env}")
    private String env;

    @Value("${server.port}")
    private String port;

    @Value("${serverName}")
    private String serverName;

    @Value("${common.message}")
    private String commonMessage;

    @GetMapping("/")
    public String index(Model model) {
        model.addAttribute("commonMessage", commonMessage);
        model.addAttribute("port", port);
        model.addAttribute("serverName", serverName);

        return "index";
    }

    @GetMapping("/env")
    public ResponseEntity<?> env() {
        return ResponseEntity.ok(env);
    }

    // ...
    
}

WAS는 마음대로 구현하면 되지만, 일단 내 방식의 블루/그린 무중단 배포 구현에 있어 env() 컨트롤러 메소드 및 관련 필드는 필수적으로 포함시킨다. 후술하겠지만, 어느 컨테이너에 해당하는 서버 애플리케이션인지를 확인하기 위함이다. 자세한 내용은 WAS 설정 파일(yml)과 관련해서 추가 설명한다.

2) 설정 파일 작성

구현 방식에 있어 중요도의 비중은 천차만별이지만, 내 방식을 기반으로 블루/그린 무중단 배포를 구현한다면 앱 기능 구현보다 더 중요한 게 WAS의 설정 관리다. 그리고 여기서 말하는 설정 파일은 src/main/resources 경로에 위치한 application.yml이다. 무중단 배포가 실제로 구축됐는지 확인하기 위해 블루/그린 컨테이너에 할당된 포트별로 설정을 다르게 해서 내용을 출력하게 한다. 내용은 다르게 작성해도 되지만 server.portserver.address는 명시해야 한다. WAS 역시 도커 컨테이너에 띄워서 동작시키기 위해서다.

아래는 설정 파일이다. 나는 별도로 구별 없이 개발 환경, 블루 환경, 그린 환경, 공통 환경을 한 곳에 작성했지만, 설명을 위해 따로 분리해서 서술한다(참고로 단일 파일 내에서 설정을 분리하는 방법은 설정 단위 사이에 ---를 입력하면 된다).

# application.yml for proflie setting

spring:
  application:
    name: cicd-practice

  profiles:
    active: local  # spring:profiles:active local? blue? green?
    group:
      local: local, common
      blue: blue, common
      green: green, common

server:
  env: blue

WAS의 개발 환경을 포괄하는 기본적인 내용을 담으며, 여기서 프로필 지정이 이뤄진다. 프로필 지정이라 함은, 앱 서버가 동작하기 위한 환경을 지정하는 것을 의미한다. spring.profiles.group을 보면 각각의 프로필이 명시됐고, 그 프로필이 그룹으로 묶여있다. 즉, 저 의미는 평상시 로컬(개발 환경)로 동작하는 것은 그냥 따라가되, 모든 프로필에서 공통 환경까지 포함해서 해당하는 환경의 내용을 가져가도록 명시해둔 것이다.

server.env가 왜 엉뚱한 곳에 있는지, 왜 blue라는 값으로 할당됐는지 궁금할 수 있는데, 이는 조금 있다가 GitHub Actions를 작성할 때 같이 설명한다. 내 방식에서는 중요한 변수이자, 구동 환경의 이정표 역할을 하는 변수다.

# local
spring:
  config:
    activate:
      on-profile: local

server:
  port: 8080
  address: localhost

serverName: local_server

개발 환경에서는 포트를 8080번을 할당하고, 주소를 로컬호스트로 지정했다. 또한 임의로 구동되는 서버의 명칭을 local_server로 정했다. 별도의 의존성이 없는 상태인 앱 서버를 개발 환경에서 (IDE 등을 통해) 곧바로 구동(jar 파일 기반)시키게 된다.

참고로 나는 타임리프를 써서 브라우저에서 설정 내용을 확인할 수 있도록 추출해서 컨트롤러 변수에 담았다. 개발 환경에서 구동된 화면을 보면 IP가 localhost에 포트 번호가 8080번으로 할당됐으며, 서버 네임이 local_server로 찍히는 것을 확인할 수 있다.


# blue
spring:
  config:
    activate:
      on-profile: blue

server:
  port: 8080
  address: 0.0.0.0

serverName: blue_server

---

# green
spring:
  config:
    activate:
      on-profile: green

server:
  port: 8081
  address: 0.0.0.0

serverName: green_server

블루 환경과 그린 환경에 대해서는 포트 번호를 각각 8080번과 8081번을 할당하고 서버 네임을 각 색을 지닌 명칭으로 정했다. 이를 통해 배포 환경에서의 웹 화면에 접속하면 현재 어떤 환경이 구동되고 있는지 확인할 수 있을 것이다.

server.address0.0.0.0, 즉 모든 네트워크로부터의 수신을 열어둔 이유는 해당 WAS가 EC2 인스턴스에서 직접 구동되는 것이 아닌 도커 컨테이너를 통해 동작하기 때문이다. 일반적인 컨테이너 환경에서 내부 네트워크의 모든 요청을 수신할 수 있도록 정한 것이다.

server.address 지정을 잘못하면 일단 배포가 정상적으로 이뤄지지 않을 뿐더러 직접 EC2 인스턴스에서 구동해도 BindException이 발생하기에 중요하게 작성한다. 만약 도커를 기반으로 동작하지 않는다면 해당 EC2 인스턴스의 private IP로 지정해야 안전할 것이다.

# common
spring:
  config:
    activate:
      on-profile: common

common:
  message: Same regardless of settings

이것은 중요한 설정은 아니지만, 블루 환경이든 그린 환경이든 공통적으로 적용될 사항을 직접 명시해서 메세지로써 확인하기 위해 내용을 추가해뒀다. 혹은 스프링 시큐리티 등을 통한 중요한 키값이나 변수 등을 보관할 때 별개의 프로필을 지정해서 추가할 수도 있을 것이다.

3) 도커 추가 세팅

EC2 인스턴스와 스프링부트 WAS에 각각 도커 관련해서 추가 세팅이 필요하다. 우선 WAS에는 해당 앱이 도커 이미지화를 할 수 있도록 Dockerfile을 작성한다. 해당 파일의 위치는 프로젝트 소스 패키지에 위치시키면 된다.

FROM openjdk:17
ARG JAR_FILE=build/libs/*.jar

# 두 ARG는 ec2 도커 컴포즈에 세팅된 변수 값을 따라가게 됨
ARG PROFILES
ARG ENV

COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-DSpring.profiles.active=${PROFILES}", "-DServer.env=${ENV}", "-jar", "app.jar"]

ENTRYPOINT에서, 도커파일에 지정된 두 개의 변수를 바탕으로 설정 프로필을 변경하고 지정값을 바꾼다. 지정값을 바꾸는 것에 대해서는 아래에서 후술한다. 이어 EC2 인스턴스에서 멀티 컨테이너 관리(즉, 신 버전 컨테이너 실행)를 위한 Docker-Compose를 작성한다. 나 같은 경우는 blue의 yml과 green의 yml을 작성해서 별도로 배포 관리를 수행했다.

WAS에 작성한 도커파일에 지정한 두 개의 변수(ARG)의 지정이 바로 이 도커 컴포즈에서 이뤄진다. 그리고 이 도커 컴포즈의 실행은 아래에서 후술할 깃헙 액션의 워크플로우 스크립트에서 담당한다.


이렇게 WAS와 관련된 내용을 마무리하고 마지막으로 GitHub Actions의 워크플로우 스크립트를 작성한다.

6. GitHub Actions 도입

지금까지 작성한 내용은 자동화 처리를 위한 일종의 장비를 배치한 셈이다. 장비가 배치됐기 때문에 이제 자동으로 장비들을 순차적으로 작동하게 하기 위한 대본 작성을 해야되는데, 나의 무중단 배포에서 이 대본 역할을 GitHub Actions가 맡게 된다.

1) GitHub Actions 개념

GitHub Actions는 GitHub 리포지토리에서 자동화된 워크플로우를 생성하고 관리할 수 있는 기능이다. 이를 통해 CI/CD(Continuous Integration/Continuous Deployment) 파이프라인을 설정하여 코드 변경 시 자동으로 빌드, 테스트, 배포 등의 작업을 수행한다. 보통 yml 형식의 파일을 사용하여 워크플로우 스크립트를 작성하며, 깃허브 레포와의 호환성이 매우 뛰어나다.

Jenkins, CircleCI, Travis CI 등의 CI/CD 툴들도 존재하지만, 깃허브 레포와의 호환성 및 템플릿 기반으로 동작 명시의 간편화 등을 활용하기 위해 깃헙 액션을 활용했다.

2) GitHub Actions 워크플로우 스크립트

(1) 스크립트 생성 및 경로 지정

yml 스크립트의 경로는 보통 .github/workflows에 위치한다. 별도로 디렉토리를 만들어도 되지만 버튼 클릭 한번으로도 충분하다.

배포하려는 깃헙 레포의 Actions 탭에서 저 set up 어쩌고 버튼을 클릭하면 본인만의 워크플로우를 스크립트로 작성할 수 있도록 yml 파일이 생성된다.


워크플로우 스크립트는 간단한 블루/그린 무중단 배포여도 이벤트 트리깅에 따라 배포 환경을 번갈아 선택할 수 있도록 코드 로직에 대한 고려가 있어야 한다. 그렇기 때문에 개인적으로 가장 어려운 부분이었으며 아직도 완벽하게 이해는 되지 않았으나 몇 번의 실패를 통해 얻은 나름의 내용으로 복습 겸 기억하고자 작성한다.

일단, CI/CD의 자동화는 다음 순서로 이뤄진다.

  1. 빌드 : 애플리케이션의 소스 코드를 컴파일하고 실행 가능하도록 패키지 처리
  2. 배포 : 빌드한 패키지를 프로덕션 환경에 설치해 실행

보통 빌드 과정에 테스트도 포함되며 워크플로우 스크립트는 순서 단위에 맞춰 각 스탭을 코드로 작성해서 CI/CD 파이프라인의 내용을 단계별로 명시한다. 즉, 빌드와 관련돼서 단계별로 작성 후, 배포와 관련해서 단계별로 작성하는 것이 워크플로우 스크립트의 작성 방법이다.

(2) 스크립트 작성 - 개요

이제 워크플로우 스크립트를 보자. 해당 양이 상당히 많기 때문에 전부를 서술하지는 않고, 부분부분 중요한 내용에 비중을 맞춰 설명한다.

name: CICD

# event trigger: main push or main pull request
on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

# read permit
permissions:
  contents: read

스프링부트 WAS의 설정 파일처럼 해당 작업의 명칭을 지정한다. 그리고 on을 작성해서 해당 자동화 과정을 동작시킬 트리거를 명시한다. 현재 나의 블루/그린 무중단 배포는 main 브랜치에 push 혹은 pull request가 워크플로우의 이벤트 트리거로 동작한다.

엄청 중요하진 않지만, 해당 워크플로우가 내 레포지토리를 수정하거나 건드리지 않게 하려고 읽기 권한만 부여해둔 상태다.

# job unit
# 1.build -> 2.deploy
jobs:
  build:
    runs-on: ubuntu-latest  # 우분투 최신버전(가상 PC가 주어짐)
    steps:  # name 단위로 나눠서 스탭을 밟음

	  #...

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
    
      # ...

작업들은 jobs의 하위에서 명시하며 보통 builddeploy로 나눠서 작업을 정의하게 된다. 정의된 내용에 맞춰서, 깃헙 액션에서 제공하는 가상머신을 기반으로 작업 단계가 이뤄지는데, 현재 내 워크플로우 스크립트는 ubuntu 최신버전을 가상머신 운영체제로 지정했다. 작업의 순서를 보장하기 위해서 build 작업이 완료되고 나서야 deploy 작업이 이뤄진다.

(3) 스크립트 주요 로직 - CI

워크플로우 스크립트를 전부 여기서 설명하기에는 양이 많기 때문에 전체 스크립트는 맨 아래에 작성된 깃헙 레포 링크를 참조하길 바란다. 여기서는 어떻게 배포 환경 프로필을 동적으로 지정하고 무중단 배포를 실현하는지에 대해서만 설명한다.

일단 빌드 과정의 순서는 아래와 같다.

# 자바 17 세팅
- name: Install JDK 17

# 실행 권한 부여 후, gradle 기반 test 없이 빌드해서 jar 파일 생성
- name: Build with Gradle

# 도커 이미지화해서 도커허브에 푸시하고 ec2에서 풀 받기 위해 도커 로그인
- name: Login to DockerHub

# 도커파일 기반으로 빌드
- name: Build Docker

# 도커허브에 푸시
- name: Push Docker

기본적으로 EC2 인스턴스에 전달하기 위해 도커 허브로 나의 WAS를 jar 파일 기반으로 패키지 처리해서 푸시하는 과정이 빌드 과정이다. 여기서 깃헙 액션의 장점인 템플릿 제공을 활용할 수 있는데, 자바 실행 및 패키징을 위해서 가상머신에 자바를 설치해야 할 테고, 아마 apt를 통해 일일이 설치하는 명령어를 작성해야 할 것이다.

이 과정을 복잡한 구문 없이 간단한 템플릿으로 처리하여 특정 작업을 수행하는 액션을 호출할 수 있다. 빌드 과정에서 필요한 액션언 깃 세팅, 자바 JDK 세팅, 도커 로그인이며, 각각의 템플릿은 uses 구문을 통해 호출할 수 있다.

build:
  runs-on: ubuntu-latest  # 우분투 최신버전(가상 PC가 주어짐)
  steps:  # name 단위로 나눠서 스탭을 밟음
  - uses: actions/checkout@v3  # 우분투에 깃 세팅, pull까지
  
  # 자바 17 세팅
  - name: Install JDK 17
    uses: actions/setup-java@v3  # JDK 17 환경 세팅
  
  # ...

  # 도커 이미지화해서 도커허브에 푸시하고 ec2에서 풀 받기 위해 도커 로그인
  - name: Login to DockerHub
    uses: docker/login-action@v1  # 도커 허브 레지스트리 로그인

여기서, 도커 로그인 같은 경우는 EC2 인스턴스에서의 환경 설정 과정을 기억하면 좋다. 당시 도커 세팅을 할 때, 도커 허브에 접근할 수 있도록 도커 로그인을 했었고 이는 깃헙 액션의 가상머신에서도 마찬가지다. 즉, 도커 로그인 템플릿을 통한 액션 호출에서 도커 허브의 계정 정보를 with 구문과 더불어 넘겨줘야 한다.

다만 그렇다고 도커 계정 정보를 그대로 기재하면 외부에 노출돼서 보안에 심각한 하자가 생길 것이기 때문에 깃헙 시크릿 변수 관리 기능을 활용해야 한다.

내가 저장한 시크릿 변수는 WAS 설정 yml 파일, 도커 허브 ID와 토큰, EC2 인스턴스의 pem 키파일의 암호화 데이터와 탄력적 IP다. WAS 설정 파일은 base64 인코딩 처리한 값으로, pem 키파일 암호화 데이터는 텍스트 에디터로 추출해서 전체 내용을 저장한다.

소기의 목적인 도커 관련 변수들을 시크릿 변수에 담은 다음, 해당 변수들을 워크플로우 스크립트에 들고 와서 with 구문을 활용하면 도커 계정 정보를 읽어내 깃헙 액션 VM에서 도커 허브 로그인을 할 수 있다.

# 도커 이미지화해서 도커허브에 푸시하고 ec2에서 풀 받기 위해 도커 로그인
- name: Login to DockerHub
  uses: docker/login-action@v1
  with:
    username: ${{ secrets.DOCKERHUB_USERNAME }}
    password: ${{ secrets.DOCKERHUB_TOKEN }}

아까 언급했던 내용 중에 빌드 과정에 테스트 과정이 포함된다고 했었는데, 자바 스프링 기반 WAS에서 JVM 언어(자바, 코틀린) 기반 자동 빌드를 담당하는 툴인 Gradle, Maven 등이 테스트 실행 관리를 맡는다. 이것들이 빌드되는 과정 속에서 테스트를 맡는 것이다. IDE 등으로 Gradle 빌드를 하는 과정 속에 테스트 과정도 포함됐는데, 이 전체 과정을 깃헙 액션이 자동으로, 반복적으로 처리하는 것이다. 이것이 CI(Continuous Integration : 지속적 통합)에 해당한다.

참고로 나는 테스트 코드가 없을 뿐더러 굳이 테스트 과정을 포함시키지 않기 위해서 테스트를 배제하고 빌드하는 설정의 코드를 산입해서 처리했다.

# 실행 권한 부여 후, gradle 기반 test 없이 빌드해서 jar 파일 생성
- name: Build with Gradle
  run: |
    echo ${{ secrets.APPLICATION }} | base64 --decode > ./src/main/resources/application.yml
    cat ./src/main/resources/application.yml
    chmod +x ./gradlew
    ./gradlew clean build -x test

(4) 스크립트 주요 로직 - CD

빌드 로직 작성이 완료되고 이어 배포 로직을 작성한다. 개요에 작성한 문법은 배포 로직에서도 동일하게 적용하고 CI 로직에서 소개된 uses, with 구문들 역시 그대로 적용된다.

배포 로직의 순서 및 적용 템플릿은 다음과 같다.

 deploy:
   needs: build
   runs-on: ubuntu-latest
   steps:
     # 응답 코드를 기반으로 nginx 업스트림 지정
     # /env 요청을 보내 받은 응답 및 응답코드를 바탕으로 blue? green? 선택
     - name: Set target IP

     # EC2에 세팅한 도커 컴포즈 실행
     # EC2 인스턴스에 SSH로 접속하여 도커 컴포즈 명령을 실행
     - name: Docker compose
       uses: appleboy/ssh-action@master 

     # Nginx 상태 확인을 위한 Health Check
     # 지정된 URL에 대한 HTTP 응답 상태를 확인
     - name: Check deploy server URL
       uses: jtalk/url-health-check-action@v3 

     # 도커 Nginx에 저장해둔 컨테이너 이름 변경 및 Nginx 리로드
     # EC2 인스턴스에 SSH로 접속하여 Nginx 설정을 업데이트하고 리로드
     - name: Change nginx upstream
       uses: appleboy/ssh-action@master 

     # 기존 컨테이너 중지 후, 삭제
     # EC2 인스턴스에 SSH로 접속하여 현재 실행 중인 Docker 컨테이너를 중지하고 삭제
     - name: Stop current server
       uses: appleboy/ssh-action@master 

각 순서를 조금 더 상세히 설명하자면, 우선 현재 동작하고 있는 배포 컨테이너의 특정 엔드포인트 응답(혹은 초기화일 경우, 상태 코드)을 통해 배포해야 할 환경을 지정한다. 상세 동작 로직은 아래서 설명하고, 그 다음에 EC2 인스턴스에 접근해서 해당하는 배포 환경의 도커 컴포즈를 실행하고 Nginx를 통해 기존 배포 환경의 health check를 해서 Nginx를 리로드함과 동시에 다음 배포를 대비해서 참조 변수를 변경한다. 이후, 기존 컨테이너를 중지해서 삭제한다.

여기서 번갈아가며 기존 배포 환경과 새로운 배포 환경 구분이 필요한데, 그것에 대한 로직이 Set target IP 단계에서 담당한다.

 # 응답 코드를 기반으로 nginx 업스트림 지정
 # /env 요청을 보내 받은 응답 및 응답코드를 바탕으로 blue? green? 선택
 - name: Set target IP
   run: |
     STATUS=$(curl -o /dev/null -w "%{http_code}" "http://${{ secrets.LIVE_SERVER_IP }}/env")
     echo $STATUS
     if [ $STATUS = 200 ]; then
       CURRENT_UPSTREAM=$(curl -s "http://${{ secrets.LIVE_SERVER_IP }}/env")
     else
       CURRENT_UPSTREAM=green
     fi
     echo CURRENT_UPSTREAM=$CURRENT_UPSTREAM >> $GITHUB_ENV
     if [ $CURRENT_UPSTREAM = blue ]; then
       echo "CURRENT_PORT=8080" >> $GITHUB_ENV
       echo "STOPPED_PORT=8081" >> $GITHUB_ENV
       echo "TARGET_UPSTREAM=green" >> $GITHUB_ENV
     elif [ $CURRENT_UPSTREAM = green ]; then
       echo "CURRENT_PORT=8081" >> $GITHUB_ENV
       echo "STOPPED_PORT=8080" >> $GITHUB_ENV
       echo "TARGET_UPSTREAM=blue" >> $GITHUB_ENV
     else
       echo "error"
       exit 1
     fi

여기서 생각할 수 있는 시나리오 및 그에 대한 코드에서의 대응책 구현은 두 가지다.

1. 처음 배포를 진행했다.

어차피 아무런 배포가 없을 테니 해당 엔드포인트에 요청을 보내도 아무런 응답이 없다. 즉 상태 코드가 200이 아니다. 상태 코드가 200이 아니므로 자연스럽게 현재 배포 환경, 즉 업스트림 서버가 green으로 지정된다. 그와 동시에 다음 타겟 환경은 blue가 된다.

2. 배포가 진행된 이력이 있다.

이렇다면 해당 엔드포인트로 요청을 보냈을 때 String 타입의 응답이 온다. 응답값에는 blue 혹은 green이 포함되어 있다.

위에서 언급했던 WAS의 설정 파일 윗쪽에 작성된 server.env 값이 2번 시나리오에서 적용된다. 처음 정해진 값이 blue였는데, 1번 시나리오에서는 green 환경에서의 배포가 이뤄지면서 다음 순서가 blue로 지정된다. 이 과정에서 한 번 더 배포가 이뤄진다면 도커 컴포즈가 실행되면서 WAS에 지정한 ARG(프로필, env 값)를 바탕으로 배포 환경을 번갈아 지정하는 것이다.

배포 환경이 지정되고 나면 기존 배포 환경, 즉 기존 컨테이너의 동작을 확인 후에 Nginx로 라우팅 스위치를 하고 리로드한 다음에 기존 컨테이너를 중단, 삭제한다.

위의 시나리오 순서대로 깃헙 액션의 VM이 EC2 인스턴스로 접근한 다음, 지속적 통합 과정에서 도커 허브에 올려진 신 버전 WAS 이미지를 pull 받아서 도커 컴포즈를 통해 실행시키는 로직이 이뤄지는 과정이 이뤄진다. 이렇게 배포 환경에 관여하면서 새로운 업데이트 내용을 배포하는 일련의 과정을 지속적 제공 / 배포(Continuous Delivery / Deployment)라고 한다.


이전 포스팅부터 이제까지 CI / CD 파이프라인 구축을 한 것이며 신 버전 업데이트 내용을 이제까지의 방식을 통해 무중단으로 배포하는 것을 블루/그린 무중단 배포라고 한다.

여기까지 작성하면 이제 모든 준비가 완료됐다(길고도 참 길었다...).

깃헙 액션의 워크플로우 스크립트에서 이벤트 트리거로써 main 브랜치에 대한 pushpull request를 지정했기 때문에, 해당 이벤트가 발생하면 무중단 배포가 일어날 것이다. 한번 테스트 해보자.

7. 무중단 배포 테스트

처음 배포를 하면 깃헙 액션 워크플로우에서 Stop current server 부분에서 에러가 발생할 것이다. 당연한 게, 동작하는 현재 서버가 없으니... 저 과정이 에러가 난다고 배포가 멈추진 않으니 안심하고 진행하자.

초기화를 green 컨테이너 및 관련 환경으로 배포를 진행했기 때문에 EC2 인스턴스에서는 green 컨테이너가 확인될 것이고, 배포된 웹 서비스에서도 green 배포 환경과 관련된 내용을 확인할 수 있다. 아래를 참조하자.

이제 여기서 main 브랜치에 push 이벤트를 발생시켜서 배포를 진행해보자. 기존 컨테이너를 삭제하거나 인스턴스를 다운할 필요는 없다.

위처럼 깃헙 액션 워크플로우에 명시된 build와 deploy가 성공적으로 진행될 것이다.

보면 기존의 green 컨테이너가 삭제됐고 blue 컨테이너가 새롭게 떴으며, 서비스에서도 blue 환경과 관련된 포트 번호 및 서버네임이 확인된다. 즉, 블루/그린 기반 무중단 배포에 성공한 것이다!

이 상황에서 env() 메소드를 호출하면 위에서 언급했던 WAS 설정 값(yml 파일에서 blue로 초기화했던 값)이 변경된 것을 확인할 수 있다. 즉, 도커 컴포즈에 작성된 ARG를 기반으로 도커파일을 가변 빌드하면서 다음 배포 환경에 대한 정보 명시도 잘 이뤄지고 있는 것을 확인할 수 있다.


다음 과제는 MSA에서의 무중단 배포 적용이다.
물론 개별적으로 모든 인스턴스에 적용하는 것도 방법이지만... 쿠버네티스와 같이 활용하는 방법으로 고민해야겠다.

끝!

깃헙 레포 링크
https://github.com/kimD0ngjun/cicd

profile
scientia est potentia / 벨로그 이사 예정...

0개의 댓글