ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Infra] Blue-Green 배포 전략을 활용한 무중단 서비스
    프로젝트/뉴스타 2024. 8. 9. 12:40

    Blue-Green 배포 전략을 활용한 무중단 서비스 왜 궁금했을까❓

    뉴스타 프로젝트에 Blue-Green 배포 전략을 도입하여 무중단 서비스를 구축한 과정에 대해 작성해보려고 한다. 또한, 배포 전략 중에 Blue-Green을 선택한 이유도 작성해보겠다.

     

     

    [Infra] 무중단 배포 전략 (Rolling / Blue-Green / Canary)

    무중단 배포 전략 (Rolling / Blue-Green / Canary) 왜 궁금했을까❓뉴스타 서비스 CI/CD 과정에서 서비스가 중단되는 문제점으로 인해 사용자가 일정 시간동안 서비스를 이용하지 못하는 문제점이 있었

    pslog.co.kr

    위 포스팅을 통해 무중단 배포 전략에 대한 개념을 학습할 수 있다.

    1. 왜 Blue-Green 배포 전략인가?

    • 1개의 서버와 어플리케이션당 하나씩 Docker를 이용해서 띄우기 때문에 롤링과 카나리 방식은 적합하지 않았다.
    • 프로젝트 기간이 짧아 기능의 품질을 보장하기 쉽지 않았고 이로 인해 잦은 백업이 필요했다. 보다 쉽게 롤백을 이뤄낼 수 있기 때문에 Blue-Green을 선택했다.
    • Blue-Green의 경우 리소스가 많이 사용된다는 점이 있지만 어플리케이션을 3개 밖에 구동중이지 않아 전환되는 과정에서 부하가 많지 않을 것이라 예상되었다.

    2. 프로젝트 구축 과정

    2.1. Blue-Green 무중단 서비스 Flow

    1. 개발자가 코드 변경 사항을 GitLab에 PUSH
    2. Webhook을 통해서 신버전 코드 빌드 및 이미지 배포
    3. Blue-Green 전환 배포 스크립트 Deploy.sh 실행
    4. Nginx 라우팅을 통해서 구버전 -> 신버전 전환
     

    [CI/CD] Jenkins / GitLab / Docker / EC2 연동 (1/2)

    SSAFY 2학기 프로젝트 "Archiview"를 Jenkins, GitLab, Docker, EC2를 이용해서 CI/CD를 구축해보겠다.처음에는 Jenkins를 Docker에 올렸는데 Jenkins에서 docker를 실행하기 위해서는 docker를 또 설치해야 하는 문제점

    pslog.co.kr

    위 포스팅을 통해서 Jenkins, GitLab을 활용한 CI/CD 과정을 볼 수 있다.

    2.2. Nginx

    service-url.inc

    set $spring_url http://172.17.0.1:8080;
    set $react_url http://172.17.0.1:3000;
    set $fastapi_url http://172.17.0.1:8000;
    • 서버 IP를 각 변수에 저장하고 해당 IP를 변경하여 Blue-Green 전환이 이뤄지게 된다.

    default.conf

    server {
        ...
        location /api {
            proxy_pass $spring_url;
            proxy_set_header X-Forwarded-Host $server_name;
        }
    
        location /api/data/ {
            proxy_pass $fastapi_url;
            proxy_set_header X-Forwarded-Host $server_name;
        }
    
        location / {
            proxy_pass $react_url;
        }
    }
    • proxy_pass에 각 서버에 해당되는 변수를 입력하여 서버 IP를 지정하여 라우팅한다.

    2.3. Docker

    docker-compose.blue.yaml

    version: '3'
    
    services:
        spring:
            container_name: spring-blue
            image: newstar_back
            ports:
              - 8080:8080
            restart: always
            env_file:
              - /env/.env
            environment:
              TZ: Asia/Seoul
        react:
            container_name: react-blue
            image: newstar_front
            ports:
              - 3000:3000
        fastapi:
            container_name: fastapi-blue
            image: fastapi_back
            ports:
              - 8000:8000
            restart: always
            env_file:
              - /env/.env
            environment:
              TZ: Asia/Seoul
    networks:
      default:
        name: app-net
        external: true

    docker-compose.green.yaml

    version: '3'
    
    services:
        spring:
            container_name: spring-green
            image: newstar_back
            ports:
              - 8081:8080
            restart: always
            env_file:
              - /env/.env
            environment:
              TZ: Asia/Seoul
    
        react:
            container_name: react-green
            image: newstar_front
            ports:
              - 3001:3000
        fastapi:
            container_name: fastapi-green
            image: fastapi_back
            ports:
              - 8001:8000
            restart: always
            env_file:
              - /env/.env
            environment:
              TZ: Asia/Seoul
    networks:
      default:
        name: app-net
        external: true

    2.4. Jenkins Pipeline Script

    pipeline {
        agent any
        
        stages {
            stage('clone') {
                steps {
                    echo 'git clone'
                    git branch: 'master', 
                    credentialsId: '[비밀키]', 
                    url: 'https://lab.ssafy.com/s10-bigdata-recom-sub2/S10P22B302.git'
                }
            }
            stage('API Server build') {
                steps {
                    dir('back') {
                        sh 'docker build -t newstar_back .'
                    }
                }
            }
            stage('GPU Server build') {
                steps {
                    dir('pydata') {
                        sh 'docker build -t fastapi_back .'
                    }
                }
            }
            stage('Web Server build') {
                steps {
                    dir('front/newstar') {
                        sh 'docker build -t newstar_front .'
                    }
                }
            }
            stage('Blue-Green Deploy'){
                steps{
                    dir('exec/deploy'){
                        sh 'chmod +x deploy.sh'
                        sh './deploy.sh'
                    }
                }
            }
        }
    }
    1. WebHook에 의해 파이프라인이 실행되는데 GitLab에 있는 Repo로부터 코드를 Clone 받아온다.
    2. API, GPU, Web Server를 순으로 신버전의 도커 이미지를 빌드한다.
    3. deploy.sh를 통해 Blue-Green 배포를 실시한다.

    2.5. deploy.sh

    #!/bin/bash
    
    echo 'CI/CD Deploy Start'
    
    cd ../exec/deploy
    # Working container check
    EXIST_BLUE=$(docker compose -p deploy-blue -f docker-compose.blue.yaml ps | grep Up)
    
    if [ -z "$EXIST_BLUE" ]; then
        # blue
        docker compose -p deploy-blue -f docker-compose.blue.yaml up -d
        BEFORE_COLOR="green"
        AFTER_COLOR="blue"
        BEFORE_SPRING_PORT=8081
        BEFORE_REACT_PORT=3001
        BEFORE_FASTAPI_PORT=8001
        AFTER_SPRING_PORT=8080
        AFTER_REACT_PORT=3000
        AFTER_FASTAPI_PORT=8000
    else
        # green
        docker compose -p deploy-green -f docker-compose.green.yaml up -d
        BEFORE_COLOR="blue"
        AFTER_COLOR="green"
        BEFORE_SPRING_PORT=8080
        BEFORE_REACT_PORT=3000
        BEFORE_FASTAPI_PORT=8000
        AFTER_SPRING_PORT=8081
        AFTER_REACT_PORT=3001
        AFTER_FASTAPI_PORT=8001
    fi
    
    # Spring Server health checking
    for retry_count in {1..60}
    do
        response=$(curl -s http://172.17.0.1:${AFTER_SPRING_PORT}/api/actuator/health)
        up_count=$(echo $response | grep 'UP' | wc -l)
    
        if [ $up_count -ge 1 ]
        then
            echo "=========================="
            echo "> Spring Server is working"
            echo "=========================="
            break
        else
            echo "> Spring Health is not working: ${response}"
        fi
        # about 10 minuetes
        if [ $retry_count -eq 60 ]
        then
            echo "> Spring Server working failed"
            docker compose -p deploy-${AFTER_COLOR} -f docker-compose.${AFTER_COLOR}.yaml down
            exit 1;
        fi
        # wating 10 seconds
        sleep 10
    done
    
    # Fastapi Server health checking
    for retry_count in {1..60}
    do
        response=$(curl -s http://172.17.0.1:${AFTER_FASTAPI_PORT}/api/data/health)
        up_count=$(echo $response | grep 'UP' | wc -l)
    
        if [ $up_count -ge 1 ]
        then
            echo "=========================="
            echo "> Fastapi Server is working"
            echo "=========================="
            break
        else
            echo "> Fastapi Health is not working: ${response}"
        fi
        # about 10 minuetes
        if [ $retry_count -eq 60 ]
        then
            echo "> Fastapi Server working failed"
            docker compose -p deploy-${AFTER_COLOR} -f docker-compose.${AFTER_COLOR}.yaml down
            exit 1;
        fi
        # wating 10 seconds
        sleep 20
    done
    
    echo "${AFTER_COLOR} server up(spring_port:${AFTER_SPRING_PORT}, react_port:${AFTER_REACT_PORT}, fastapi_port:${AFTER_FASTAPI_PORT})"
    
    EXIST_AFTER=$(docker compose -p deploy-${AFTER_COLOR} -f docker-compose.${AFTER_COLOR}.yaml ps | grep Up)
    
    if [ -n "$EXIST_AFTER" ]; then
        echo "nginx Setting"
        docker exec -i nginx /bin/bash -c "echo -e 'set \$spring_url http://172.17.0.1:${AFTER_SPRING_PORT};\nset \$react_url http://172.17.0.1:${AFTER_REACT_PORT};\nset \$fastapi_url http://172.17.0.1:${AFTER_FASTAPI_PORT};' | tee /etc/nginx/conf.d/service-url.inc && nginx -s reload"
        
        echo "Completed Deploy!"
        echo "$BEFORE_COLOR server down(spring_port:${BEFORE_SPRING_PORT}, react_port:${BEFORE_REACT_PORT}, fastapi_port:${BEFORE_FASTAPI_PORT})"
        docker compose -p deploy-${BEFORE_COLOR} -f docker-compose.${BEFORE_COLOR}.yaml down
    fi
    1. 현재 실행중인 Docker-compose의 색깔을 확인(Blue or Green)
    2. 만약, Blue일 경우 Green의 Docker-compose를 띄우고 port번호를 Green에 맞게 저장
    3. Green일 경우 Blue의 Docker-compose를 띄우고 port번호를 Blue에 맞게 저장
    4. 각 어플리케이션 API, GPU Server의 Health Check API를 통해 요청을 날려보며 신버전의 코드가 정상적으로 운영되고 있는지 확인
    5. 만약, 정상적으로 운영되지 않으면 신버전의 서버 다운 후 스크립트 종료
    6. 신버전의 Docker-compose가 정상적으로 띄워져 있다면 service-url.inc에 있는 각 어플리케이션 port를 신버전으로 변경 후 Nginx 재시작
    7. 기존에 운영되던 구버전의 docker compose 다운
Designed by Tistory.