오늘은 Blue green 배포에 대해 써보겠다.
1) blue/green 방식에 대한 이해
blue/green 배포 방식은 트래픽을 한번에 구버전에서 신버전으로 옮기는 방식으로 blue와 green을 나란히 구성해 두 상태로 배포 시점에 트래픽을 blue에서 green으로 일제히 전환시킨다.

현재 blue 컨테이너 8081포트를 바라보고 있지만

green 컨테이너가 활성화 되는 동안에도 요청은 blue 컨테이너로 reverse proxy 되기에 서비스는 중단되지 않는다.
green 컨테이너 가 활성화 되면, blue 컨테이너로 보내던 요청을 green으로 향하도록 바꾸고 nginx를 reload시켜준다.
따라서 nginx는 green 컨테이너 를 바라보고 reverse proxy 시켜준다.
이를 통해 서버가 로드되는 시간을 nginx가 reload 되는 시간 만큼으로 줄일 수 있다.
-> 실제 실행시켜보니 reload 되는 약 30초 동안만 api 서버가 502 gateway 에러로 사용 불가능하고, reload 끝난 후 바로 사용 가능하다!!!
2) 포트포워딩 포트 연결
80으로 접속하면 → 8081에서 → 8080으로
2-1) nginx 설치
#업데이트
$ sudo apt update
# nginx 설치
$ sudo apt install nginx
# Nginx 실행 확인
$ sudo systemctl start nginx
$ sudo systemctl status nginx
2-2) /etc/nginx/sites-available/nginx.conf 수정
없으면 만들기! 원래 default 있었는데 삭제해주고 만들었다.

server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
server_name _;
location / {
try_files $uri $uri/ =404;
}
}
sites-enabled에 symlink 만들기
cd /etc/nginx/sites-enabled
sudo ln -s /etc/nginx/sites-available/nginx.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo service nginx restart
3) DockerFile

FROM openjdk:17-jdk-alpine
ARG JAR_FILE=build/libs/BlueGreen-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
##
ENTRYPOINT [ "java", "-jar", "-Dspring.profiles.active=prod", "/app.jar" ]
4) Docker-compose.yml

# docker-compose.blue.yml
version: '3'
services:
blue:
image: DOCKER REPO
ports:
- 8081:8080
# docker-compose.green.yml
version: '3'
services:
green:
image: DOCKER REPO
ports:
- 8082:8080
# nginx.blue.conf
events {
worker_connections 1024;
}
http {
upstream backend {
server {public 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;
}
}
}
# nginx.green.conf
events {
worker_connections 1024;
}
http {
upstream backend {
server {public 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;
}
}
}
5) deploy.sh
docker-compose ps를 통해 blue가 실행중인지 확인.
실행중이 아니라면
blue up,
before-compose-color=green, after-compose-color=blue
실행중이라면
green up
before-compose-color=blue, after-compose-color=green
새로운 컨테이너 띄운 후에 서버(aws-linux2) 의 nginx.conf 파일 수정
sudo nginx reload
이전 컨테이너 종료
# deploy.sh
DOCKER_APP_NAME=meetup
# Blue 를 기준으로 현재 떠있는 컨테이너를 체크한다.
EXIST_BLUE=$(docker-compose -p ${DOCKER_APP_NAME}-blue-1 -f docker-compose.blue.yml ps | grep Up)
# 컨테이너 스위칭
if [ -z "$EXIST_BLUE" ]; then
echo "blue up"
docker-compose -p ${DOCKER_APP_NAME}-blue-1 -f docker-compose.blue.yml up -d
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
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 한다
sudo 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
위의 로직이 담긴 쉘 스크립트 명령어 파일을 github actions workflow yml에서 실행.
blue/green으로 따로 빌드업을 하고 blue/green 방식에 따라 바꾸는 sh 작성
ec2 서버의 nginx를 이용해 프록시를 진행하려한다.
그렇기 위해서는
docker가 8081,8082 포트로 연결해주고,
ec2의 nginx가 실행중인 8081,8082 포트를 80으로 연결해주는것.
[ docker가 8081,8082 포트로 연결해주고, ] 이 과정은 docker-compose에서 진행하고,
[ nginx가 실행중인 8081,8082 포트를 80으로 연결해주는것. ] 은 /etc/nginx/nginx.conf 파일 변경을 통해 이루어 진다고 보면 된다.
이 일련의 과정은 모두 deploy.sh에서 수행된다.
6) Github Actions 설정
# github repository Actions 페이지에 나타낼 이름
name: CI/CD
# event trigger
on:
push:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-22.04
steps:
## jdk setting
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin' # https://github.com/actions/setup-java
## gradle caching
- name: Gradle Caching
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Grant execute permission for gradlew
run:
chmod +x gradlew
## create application-prod.yml
- name: create application.yml
if: contains(github.ref, 'main')
run: |
cd ./src/main
mkdir -p resources
cd ./resources
touch ./application.yml
ls *
echo "${{ secrets.PROPERTIES_PROD }}" > ./application-prod.yml
shell: bash
- name: Build With Gradle
if: contains(github.ref, 'main')
run: ./gradlew build -x test
- name: Login to Docker Hub
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile-prod
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}
## deploy to production
- name: Deploy to prod
uses: appleboy/ssh-action@v0.1.6
id: deploy-prod
if: contains(github.ref, 'main')
with:
host: ${{ secrets.EC2_HOST_PROD }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_PRIVATE_KEY }}
envs: GITHUB_SHA
script: |
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}
chmod 777 ./deploy.sh
./deploy.sh
docker image prune -f
deploy가 실행되게 설정해준다.

잘 안보이지만 ./deploy.sh 명령어를 치면 green이 올라가고 blue이 removing 되는 걸 볼 수 있다!!!
이슈!!!

자세히 보면 Permission denied라 쓰여있다. 이는 deploy.sh cp부분에 sudo를 안써줬기 떄문이다.!!
'DevOps' 카테고리의 다른 글
| [CICD] Docker + Github Action + Spring Boot 자동배포환경 구축하기 (2) | 2024.01.08 |
|---|