티스토리 뷰

1. Blue_Green 배포란?

8080 포트로 연결된 컨테이너

8081포트로 다른 다른 버전의 컨테이너 띄우기

nginx.conf 수정후 reload (업스트립 8081 수정)

8080 컨테이너 제거

 

새로 배포할 때마다 새로운 컨테이너들을 띄우고
nginx 연결(upstream)을 새로 띄운 컨테이너 포트로 연결한 뒤
이전 컨테이너는 내립니다.

❗️뒤에 설명드릴때는 green이 8082, blue가 8081 포트입니다❗️

2. github actions 작성

name: Deploy

on:
  push:
    branches: [ master ]       

jobs:
  build:                    
    runs-on: ubuntu-latest    
    env :
      working-directory: ./
      APPLICATION: ${{ secrets.APPLICATION }}
      GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
      
    steps:
    - name: Checkout repository
      uses: actions/checkout@v2

    - name: Set up JDK 11
      uses: actions/setup-java@v2
      with:
        java-version: '11'
        distribution: 'adopt'
        
    - name: Cache Gradle packages
      uses: actions/cache@v2
      with:
        path: |
             ~/.gradle/caches
             ~/.gradle/wrapper
        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
        restore-keys: |
              ${{ runner.os }}-gradle-
  
    - uses: actions/checkout@v2
    - run: mkdir -p ./src/main/resources
    - run: touch ./src/main/resources/application.yml
    - run: echo "${{env.APPLICATION}}" > ./src/main/resources/application.yml
    - uses: actions/upload-artifact@v2
      with:
        name: application.yml
        path: ./src/main/resources/application.yml
    - run: touch ./src/main/resources/firebase-key.json
    - run: echo "${{env.GOOGLE_APPLICATION_CREDENTIALS}}" | base64 --decode > ./src/main/resources/firebase-key.json
    - uses: actions/upload-artifact@v2
      with:
        name: firebase-key.json
        path: ./src/main/resources/firebase-key.json
        
        
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
#      working-directory: ${{ env.working-directory }}
      
    - name: Build with Gradle
      run: ./gradlew clean build
#      working-directory: ${{ env.working-directory }}
      
    - name: Cleanup Gradle Cache
      if: ${{ always() }}
      run: |
           rm -f ~/.gradle/caches/modules-2/modules-2.lock
           rm -f ~/.gradle/caches/modules-2/gc.properties
    - name: 스프링부트 애플리케이션 빌드
      run: ./gradlew clean build


#    - name: Login to DockerHub
#      run:
#         docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}

    - name: entry.sh push to ec2
      uses: appleboy/scp-action@master
      with:
        host: ${{ secrets.EC2_SERVER_HOST }}
        username: ec2-user
        key: ${{ secrets.PRIVATE_KEY }}
        source: "build/libs/*.jar"
        target: "source"
        rm: true
    - name: /src/main/resources/ push to ec2
      uses: appleboy/scp-action@master
      with:
        host: ${{ secrets.EC2_SERVER_HOST }}
        username: ec2-user
        key: ${{ secrets.PRIVATE_KEY }}
        source: "./src/main/resources"
        target: "./"
        rm: false

    - name: entry.sh
      uses: appleboy/ssh-action@master
      with:
          host: ${{ secrets.EC2_SERVER_HOST }}
          username: ec2-user
          key: ${{ secrets.PRIVATE_KEY }}
          script: |
              ls -al
              chmod 777 entry.sh
              sh /home/ec2-user/entry.sh
  1. checkout repository
    현재 GitHub repository를 체크아웃합니다.
  2. Set up JDK 11
    Java 11 버전을 사용하기 위해 JDK 11을 설치합니다.
  3. Cache Gradle packages
    Gradle 패키지 캐시를 저장하고, 빌드 시 캐시된 패키지를 사용하여 빌드 시간을 단축합니다.
  4. Create application.yml
    application.yml 파일을 생성하고, 애플리케이션 구성 정보를 해당 파일에 씁니다.
  5. Create firebase-key.json
    Firebase 구성 정보를 담은 firebase-key.json 파일을 생성합니다.
  6. Grant execute permission for gradlew
    gradlew 파일에 실행 권한을 부여합니다.
  7. Build with Gradle
    Gradle을 사용하여 애플리케이션을 빌드합니다.
  8. Cleanup Gradle Cache
    Gradle 캐시를 정리합니다.
  9. SCP action
    빌드된 jar 파일을 EC2 인스턴스로 전송합니다.
  10. SCP action
    EC2 인스턴스의 /src/main/resources 폴더로 로컬 리소스 파일을 전송합니다.
  11. SSH action
    EC2 인스턴스에서 entry.sh 파일을 실행하여 애플리케이션을 배포합니다

 

3. EC2-server 안에 파일 구조

|-🌕 docker-compose.green.yml  

|-🌑 docker-compose.blue.yml  

|-🌖 nginx.blue.conf   

|-🌘 nginx.green.conf  

|-🌝 Dockerfile         

|-🌚 entry.sh    

|-🌜 source (**application.yml파일로 자동 생성**)

     ㄴ build

          ㄴ libs

              ㄴ BE-0.0.1-SNAPSHOT-plain.jar  

              ㄴ BE-0.0.1-SNAPSHOT.jar

🌛 src (**applciation.yml파일로 자동 생성**)

     ㄴ main

          ㄴ resources
              ㄴ application.yml  
              ㄴ firebase-key.json

 

🌚  entry.sh

DOCKER_APP_NAME=meetup
# Blue 를 기준으로 현재 떠있는 컨테이너를 체크한다.

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

ls
pwd

# 컨테이너 스위칭
if [ -z "$EXIST_BLUE" ]; then
    echo "blue up"
    docker-compose -p ${DOCKER_APP_NAME}-blue-1 -f docker-compose.blue.yml up -d --force-recreate --build
    docker cp ./src meetup-blue-1_blue_1:./
    BEFORE_COMPOSE_COLOR="green"
    AFTER_COMPOSE_COLOR="blue"
else
    echo "green up"
    docker-compose -p ${DOCKER_APP_NAME}-green-1 -f docker-compose.green.yml up -d --force-recreate --build
docker cp ./src meetup-green-1_green_1:./
    BEFORE_COMPOSE_COLOR="blue"
    AFTER_COMPOSE_COLOR="green"
fi

sleep 10

# 새로운 컨테이너가 제대로 떴는지 확인
EXIST_AFTER=$(docker-compose -p ${DOCKER_APP_NAME}-${AFTER_COMPOSE_COLOR}-1 -f docker-compose.${AFTER_COMPOSE_COLOR}.yml ps | grep Up)
if [ -n "$EXIST_AFTER" ]; then

  # nginx.config를 컨테이너에 맞게 변경해주고 reload 한다
  cp ./nginx.${AFTER_COMPOSE_COLOR}.conf /etc/nginx/nginx.conf
	sudo  nginx -s reload

  # 이전 컨테이너 종료
  docker-compose -p ${DOCKER_APP_NAME}-${BEFORE_COMPOSE_COLOR}-1 -f docker-compose.${BEFORE_COMPOSE_COLOR}.yml down
  echo "$BEFORE_COMPOSE_COLOR down"
fi

blue-green 배포를 위한 컨테이너 스위칭 및 Nginx 리로드를 자동화해주는 역할을 합니다. 이를 통해, 서비스 중단 없이 새로운 버전의 애플리케이션을 배포할 수 있게 됩니다.

 

1. 현재 띄워져 있는 컨테이너를 체크합니다.
2-1. 체크 결과, 현재 띄워져 있는 컨테이너가 blue인 경우, green 컨테이너를 띄웁니다.

2-2. 현재 띄워져 있는 컨테이너가 green인 경우, blue 컨테이너를 띄웁니다.

도커컴포저  컨네이너 삭제후 재빌드 
> docker-compose up -d --force-recreate --build
호스트->컨테이너 파일 복사
> docker cp [host 파일경로] [container name]:[container 내부 경로]

3. 새로운 컨테이너가 정상적으로 떴는지 체크합니다.
4. Nginx의 설정 파일을 새로운 컨테이너에 맞게 변경하고, Nginx를 리로드합니다.
5. 이전 컨테이너를 종료합니다.

 

밑에서 내 IP에 IP를 적어주면 됩니다! 

🌖 nginx.blue.conf

# user www-data;
# worker_processes auto;
# pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    upstream backend {
        server 내 IP :8081; # blue

    }

     access_log /var/log/nginx/access.log;

    server {
	     listen 80;

        location / {
                  proxy_pass http://localhost:8081;
                  proxy_set_header Host $host;
                  proxy_set_header X-Real-IP $remote_addr;
                  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                  proxy_set_header X-Forwarded-Proto $scheme;
        }

    }
}

현재 상태가 blue인 경우 entry.sh에서 4번 과정에서 이 설정파일을 복사합니다.

 

🌘 nginx.green.conf

# user www-data;
# worker_processes auto;
# pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    upstream backend {
        server 내 IP:8082; # green

    }

     access_log /var/log/nginx/access.log;

    server {
         listen 80;

        location / {
                  proxy_pass http://localhost:8082;
                  proxy_set_header Host $host;
                  proxy_set_header X-Real-IP $remote_addr;
                  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                  proxy_set_header X-Forwarded-Proto $scheme;
        }

    }
}

현재 상태가 green인 경우 entry.sh에서 4번 과정에서 이 설정파일을 복사합니다.

 

 

🌕 docker-compose.green.yml  

version: '3'

services:

  blue:
    build:
      context: .
      dockerfile: /home/ec2-user/Dockerfile

    ports:
      - "8081:8080"

현재 상태가 green인 경우 2-1에서 이 yml파일을 build합니다.
현재 위치에 있는 Dockerfile을 이용하여 이미지를 빌드하고, 빌드한 이미지를 사용하여 컨테이너를 실행합니다.
이 서비스는 호스트의 8081 포트와 컨테이너의 8080 포트를 매핑하여 외부에서 해당 컨테이너에 접근할 수 있도록 설정되어 있습니다. 즉, 호스트에서 8081 포트로 요청이 들어오면 컨테이너 내부의 8080 포트로 포워딩이 됩니다.

 

🌑 docker-compose.blue.yml  

version: '3'

services:


  green:
      build:
        context: .
        dockerfile: /home/ec2-user/Dockerfile
      ports:
        - "8082:8080"

현재 상태가 blue인 경우 2-2에서 이 yml파일을 build합니다.

 

🌝 Dockerfile

FROM openjdk:11
ARG JAR_FILE=./source/build/libs/BE-0.0.1-SNAPSHOT.jar
ARG SPRING_CONFIG=./src/main/resources/application.yml
COPY ${JAR_FILE} app.jar
COPY ${SPRING_CONFIG} application.yml
ENTRYPOINT ["java","-jar","/app.jar","--spring.config.location=file:/application.yml"]

해당 Dockerfile은 OpenJDK 11을 베이스 이미지로 사용하여 Docker 이미지를 빌드하는 과정을 정의하고 있습니다.
ARG를 통해 JAR_FILE과 SPRING_CONFIG 환경 변수를 설정하고 있습니다. 
${SPRING_CONFIG} 환경 변수를 사용하여 소스 코드 루트 경로 내의 ./src/main/resources/application.yml 파일을 application.yml로 복사하고 있습니다.
ENTRYPOINT를 사용하여 java -jar 명령을 실행하는데, —spring.config.location 옵션으로 /application.yml을 사용하여 실행합니다. 이를 통해 Docker 컨테이너 내부의 /application.yml 경로에 저장된 Spring 설정 파일을 사용하도록 설정하고 있습니다.

 


참고

 

Github Action, Nginx, Docker 무중단 배포하기(블루/그린)

어제 새벽에 kkini 프로젝트에 blue/green 방식으로 무중단 배포를 완료했습니다. 1. 무중단 배포를 도입한 이유 이번 주 부터 kkini 프로젝트를 리팩토링해서, 빠른 시일내에 운영을 해보려고 합니다.

mr-popo.tistory.com