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
- checkout repository
현재 GitHub repository를 체크아웃합니다. - Set up JDK 11
Java 11 버전을 사용하기 위해 JDK 11을 설치합니다. - Cache Gradle packages
Gradle 패키지 캐시를 저장하고, 빌드 시 캐시된 패키지를 사용하여 빌드 시간을 단축합니다. - Create application.yml
application.yml 파일을 생성하고, 애플리케이션 구성 정보를 해당 파일에 씁니다. - Create firebase-key.json
Firebase 구성 정보를 담은 firebase-key.json 파일을 생성합니다. - Grant execute permission for gradlew
gradlew 파일에 실행 권한을 부여합니다. - Build with Gradle
Gradle을 사용하여 애플리케이션을 빌드합니다. - Cleanup Gradle Cache
Gradle 캐시를 정리합니다. - SCP action
빌드된 jar 파일을 EC2 인스턴스로 전송합니다. - SCP action
EC2 인스턴스의 /src/main/resources 폴더로 로컬 리소스 파일을 전송합니다. - 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
'Infra > CI&CD' 카테고리의 다른 글
[CI/CD] Github-Actions 할 때 Secrets에 Application 및 Key 파일 넣을 때 주의할 점 (2) | 2023.04.20 |
---|---|
[CI/CD] Spring boot 프로젝트를 Docker와 Github Action이용해서 자동배포하기 (4) | 2023.03.14 |