기타/인프라 구축과정

[Infra] 6. 무중단 배포 - HelloWorld Project

배발자 2023. 5. 24.
반응형

 

이전에 정리하는 게시글에서 Back과 Front를 배포를 할 때 “deploy.sh”를 통해 진행된다. 해당 파일은 어떠한 컨테이너를 띄울지에 대한 쉘 스크립트로 작성된 파일이며, 젠킨스 파이프라인에서 해당 파일을 실행하여 “Blue-Green” 무중단 배포를 진행하게 된다. 필자가 진행한 프로젝트는 EC2 서버 하나로 진행을 했었고 컨테이너들을 이용하여 "Blue-Green" 배포 전략을 활용하였다. 

 

* 추후 EC2 서버 하나를 뒤늦게 발급받긴하였음

 

애플리케이션 서버를 실행한다는 것은 최소 1개의 관련 컨테이너가 구동중이여야하며, 만약 해당 애플리케이션 서버가 업데이트를 하는 경우, 구동 중인 컨테이너는 다운되고 빌드와 배포하는 그 시간동안 페이지를 찾을 수 없게된다.

해당 프로젝트에서 진행한 “Blue-Green” 무중단 배포는 각 애플리케이션 서버 컨테이너가 2개가 띄워지는 순간이 있다. Down Time Zero를 실행하기 위함인데, 먼저 Blue 서버 컨테이너가 구동 중이라면 Green 서버 컨테이너를 띄우고 Green 서버 컨테이너가 완료된 후에 Blue 서버 컨테이너를 다운시켜준다.

 

Back (back, notify, story)과 Front 모두 이와 같은 방법으로 진행이 되었기 때문에, 하나의 애플리케이션 실행 방식만 정리하려고 한다. 코드는 거의 동일하다.

 

 

[back]

stage('Building back image'){
            steps{
                script {
                  dockerImage = docker.build("bae3007/back-zero-downtime", "/var/jenkins_home/workspace/deploy_test/back")
                  withCredentials([usernamePassword(credentialsId: 'bae3007', usernameVariable: 'DOCKER_HUB_USERNAME', passwordVariable: 'DOCKER_HUB_PASSWORD')]) {
                     sh 'docker login -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_PASSWORD'
                     sh 'docker push bae3007/back-zero-downtime'
                  }
                }
            }
        }

 

먼저, 이전 게시글에서 작성한 back_build_pipeline 파이프라인에서 작성한 스크립트의 일부분이다. 

해당 스크립트는 /home/ubuntu/jenkins/jenkins_home/workspace/deploy_test/back/ 경로에서 Dockerfile을 찾아서 이미지로 생성하고 DockerHub에 푸쉬하는 과정이다. 이때 작성한 경로에 존재하는 Dockerfile을 활용하게 된다.

 

이전 게시글에서 빌드를 진행할 때 만들어진 JAR 파일을 위의 경로로 복사하였다. 그러므로 해당 경로에는 빌드 파일을 가지고 있기때문에 해당 파일을 가지고 이미지를 생성하게 된다.

 

위의 스크립트에서는 /var/jenkins_home/으로 시작하는데 volume 설정이 되어있기때문에 컨테이너 외부 경로는 다음과 같다.

 

 

[/home/ubuntu/jenkins/jenkins_home/workspace/deploy_test/back/Dockerfile]

FROM adoptopenjdk/openjdk11
CMD ["./gradlew build"]
ARG JAR_FILE_PATH=./*.jar
COPY ${JAR_FILE_PATH} app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

 

  1. FROM adoptopenjdk/openjdk11: 이 라인은 Docker 이미지의 베이스를 정의한다. 이 이미지는 adoptopenjdk/openjdk11라는 이미지를 기반으로 만들어진다. 즉, adoptopenjdk/openjdk11에 있는 모든 파일과 설정을 그대로 물려받게 된다.
  2. CMD ["./gradlew build"]: 이 라인은 Docker 컨테이너가 시작될 때 실행할 명령어를 정의한다. 이 라인을 통해 Gradle Wrapper를 이용해 Gradle 프로젝트를 빌드하게 된다. Dockerfile에 여러 CMD 명령어가 있을 경우 마지막 CMD 명령어만 유효하므로, 이 CMD 명령어는 이전의 CMD 명령어를 무시하게 된다.
  3. ARG JAR_FILE_PATH=./*.jar: 이 라인은 빌드 인자를 정의한다. 이 경우, JAR_FILE_PATH라는 이름의 인자가 현재 디렉토리의 모든 .jar 파일을 가리키도록 설정되어 있다.
  4. COPY ${JAR_FILE_PATH} app.jar: 이 라인은 JAR_FILE_PATH에서 지정한 파일을 Docker 이미지 내부에 app.jar라는 이름으로 복사한다.
  5. ENTRYPOINT ["java", "-jar", "app.jar"]: 이 라인은 Docker 컨테이너가 시작되면 항상 실행되는 명령어를 정의한다. 이 경우, Java 애플리케이션을 실행하는 java -jar app.jar가 ENTRYPOINT로 설정되어 있다. 이렇게 하면 Docker 컨테이너가 시작될 때마다 자동으로 이 Java 애플리케이션을 실행한다.

 

 

[/home/ubuntu/jenkins/jenkins_home/workspace/deploy_test/back/docker-compose.blue.yaml]

version: '3.7'
services:
  api:
    image: ${DOCKER_REGISTRY}/${DOCKER_APP_NAME}:${IMAGE_TAG}
    container_name: ${DOCKER_APP_NAME}-blue
    environment:
      - LANG=ko_KR.UTF-8
    ports:
      - '8081:8080'
    volumes:
      - back-logs:/var/log/app
volumes:
  back-logs:

 

  • services: 이 섹션에는 해당 Docker Compose 설정에서 관리되는 모든 서비스(컨테이너)를 나열한다.
    • api: 이는 'api'라는 이름의 서비스를 정의한다.
      • image: 컨테이너의 기반 이미지를 지정하고 환경 변수를 통해 도커 레지스트리, 앱 이름, 이미지 태그를 동적으로 설정한다.
      • container_name: 생성되는 컨테이너의 이름을 지정하고 환경 변수를 이용해 동적으로 설정한다.
      • environment: 컨테이너 내부에서 사용될 환경 변수를 설정한다. 여기서는 'LANG' 환경 변수에 'ko_KR.UTF-8' 값을 설정하여, 컨테이너 내의 로케일을 한국어로 설정한다.
      • ports: 호스트와 컨테이너 사이에 포트를 매핑한다. 여기서는 호스트의 8081 포트를 컨테이너의 8080 포트에 연결한다.
      • volumes: 호스트와 컨테이너 사이에 볼륨을 공유한다. 여기서는 **back-logs**라는 이름의 볼륨을 만들고, 이를 컨테이너의 /var/log/app 디렉토리와 연결한다.
  • volumes: 이 섹션에는 Docker Compose 설정에서 사용되는 모든 볼륨을 나열한다. 여기서는 **back-logs**라는 이름의 볼륨을 생성한다. 이 볼륨은 api 서비스에서 사용된다. 

 

 

[/home/ubuntu/jenkins/jenkins_home/workspace/deploy_test/back/docker-compose.green.yaml]

version: '3.7'
services:
  api:
    image: ${DOCKER_REGISTRY}/${DOCKER_APP_NAME}:${IMAGE_TAG}
    container_name: ${DOCKER_APP_NAME}-green
    environment:
      - LANG=ko_KR.UTF-8
    ports:
      - '8082:8080'
    volumes:
      - back-logs:/var/log/app
volumes:
  back-logs:

 

blue yml과 유사하지만 여기서는 호스트의 8082 포트를 컨테이너의 8080 포트에 연결한다.

 

blue와 green compose.yaml에서 기재되어있는 로그 볼륨설정은 ELK Stack에서 로그 수집을 위함이다. 

그렇기 때문에 아래와 같이 application-db.yml 파일에 로그 저장 위치를 설정하였다. 

(프로젝트 파일 빌드를 진행하기전에 서버에 저장된 application-db.yml 파일을 프로젝트 디렉토리 경로로 옮기는 작업을 진행한다. 해당 내용은 "[Infra] 3-2. Jenkins Back 빌드 & 배포 - HelloWorld Project" 에 정리해뒀다.)

 

 

[application-db.yml]

logging:
  level:
    root: INFO
  file:
    name: /var/log/app/app.log

 


 

 

[/home/ubuntu/jenkins/jenkins_home/workspace/deploy_test/back/deploy.sh]

#!/bin/bash

export DOCKER_REGISTRY=bae3007 DOCKER_APP_NAME=back-zero-downtime IMAGE_TAG=latest

EXIST_BLUE=$(docker compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yaml ps | grep Up)

if [ -z "$EXIST_BLUE" ]; then
    echo "blueis is not exist. so make blue container"
    echo "blue up"
    docker compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yaml up -d
    BEFORE_COMPOSE_COLOR="green"
    AFTER_COMPOSE_COLOR="blue"
    echo "end"
else
    echo "blue is exist. so make green container"
    echo "green up"
    docker compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yaml up -d
    BEFORE_COMPOSE_COLOR="blue"
    AFTER_COMPOSE_COLOR="green"
fi

sleep 20

EXIST_AFTER=$(docker compose -p ${DOCKER_APP_NAME}-${AFTER_COMPOSE_COLOR} -f docker-compose.${AFTER_COMPOSE_COLOR}.yaml ps | grep Up)
if [ -n "$EXIST_AFTER" ]; then

    docker compose -p ${DOCKER_APP_NAME}-${BEFORE_COMPOSE_COLOR} -f docker-compose.${BEFORE_COMPOSE_COLOR}.yaml down
    echo "$BEFORE_COMPOSE_COLOR down"
fi

 

 

해당 쉘 스크립트는 도커 컨테이너를 스위칭하는 기능을 수행한다.

먼저, DOCKER_REGISTRYDOCKER_APP_NAMEIMAGE_TAG 환경 변수를 설정한다. 그 다음, docker-compose 명령어를 사용하여 Blue 컨테이너가 존재하는지 확인한다. Blue 컨테이너가 존재하지 않으면 docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yaml up -d 명령어를 사용하여 Blue 컨테이너를 생성한다. Blue 컨테이너가 존재하면 Green 컨테이너를 생성한다. 

 

sleep 20은 새로 생성한 컨테이너가 완전히 시작되고 실행될 수 있도록 시간을 제공하는 역할이다.

컨테이너를 시작하면 즉시 사용 가능한 상태가 되지 않을 수 있다. 특히, 애플리케이션의 시작 시간이 길거나 데이터베이스와 같은 다른 서비스에 연결해야 하는 경우에는 이런 지연이 발생할 수 있다.

 

따라서 sleep 20은 컨테이너가 완전히 시작되고 실행될 시간을 제공하고, 이후에 서비스가 정상적으로 실행 중인지 확인한다. 이 시간은 넉넉하게 설정된 것으로 보이며, 실제 필요한 시간은 사용하는 이미지, 애플리케이션의 복잡성, 서버의 성능 등에 따라 달라질 수 있다.

 

새로운 컨테이너가 제대로 떴는지 확인한 후, 이전 컨테이너를 종료한다. 이를 통해 Zero-Downtime Deployment를 구현할 수 있다. Notify, Story, Front 배포 디렉토리 구조에서도 dockerfile, docker-compose-blue, docker-compose-green, deploy.sh가 존재하여 컨테이너 실행할 때 deploy.sh에서 실행하게 된다.

 

* ".sh"는 셸 스크립트(Shell script) 파일의 확장자이다. 셸 스크립트는 일련의 명령어들을 포함하는 텍스트 파일로, 리눅스나 맥 OS 등의 유닉스 기반 운영 체제에서 사용되며 사용자가 명령어를 일일이 입력하지 않아도 자동으로 명령어를 실행하는데 도움을 준다.

 

반응형

댓글