무중단 배포?
무중단 배포는 서비스 운영 중 서버나 애플리케이션 업데이트를 수행하면서도 사용자가 서비스 중단을 느끼지 않도록 하는 배포 방식입니다. 무중단 배포를 적용하지 않은 기본 CI / CD의 경우 웹훅이 발생하여 새롭게 파이프라인이 빌드 되면 기존 컨테이너를 내리고 변경사항이 적용된 새로운 컨테이너를 올리게 됩니다.
이 과정에서 새로운 컨테이너가 올라갈 때 까지 지연 시간이 존재하게되고 사용자 입장에서는 서비스를 일정시간 사용할 수 없게 됩니다.
무중단 배포를 사용하면 업데이트나 유지 보수 중에도 사용자에게 중단 없는 서비스를 제공할 수 있기에 사용자로 하여금 우수한 서비스 사용 경험을 제공할 수 있게 됩니다.
Blue-Green 무중단
무중단을 구현하는 방식에는 대표적으로 카나리, 롤링, 블루그린 3가지 방식이 있습니다. 저는 이중 블루 그린 방식을 선택하여 무중단 배포를 구현하고자 합니다.
블루-그린 배포는 기존(블루) 버전과 새(그린) 버전이 동시에 가동 중이기 때문에, 새 버전에서 문제가 발생하더라도 즉시 트래픽을 이전 버전으로 돌려 안정성을 유지할 수 있습니다. 따라서 아직 무중단 배포에 익숙하지 않아 여러 문제 상황에 직면할 수 있는 현재 상황에 가장 적합하다고 판단했습니다.
동작 과정
1. 기존 서버 운영 중
현재 서버는 블루 서버(8080 포트) 에서 실행중
Nginx는 백엔드 요청 트래픽을 수신하고 이를 블루 서버(8080 포트) 로 전달
2. 새로운 서버가 배포
깃에 새로운 푸시가 발생하여 새로운 버전의 서버 컨테이너 그린(8081포트)가 실행
3. 새로운 서버가 배포
기존 블루 서버를 바로 다운시키는 것이 아닌 새로운 서버(그린)의 상태를 확인
Spring Actuactor를 통해 새로운 그린서버의 Health Check를 진행하여 새로운 서버의 정상 구동을 확인
4. Nginx 설정 변경 및 트래픽 전환
- Nginx가 기존 8080 포트를 8081 포트로 전환
- Nginx를 재실행하여 변경된 포트를 반영(Nginx Restart는 1초 내로 수행되므로 서버 중단없이 지속적인 서비스 가능
- 기존 컨테이너 블루(8080포트) 다운
- Nginx의 리버스 프록시가 클라이언트 요청을 적절한 서버(블루 또는 그린 환경)로 전달하는 중간 서버로서의 역할을 수행하기에 리버스 프록시 설정만 해두면 일일이 바뀐 포트를 지정해줄 필요 없이 동일한 주소로 서비스 사용 가능
무중단 배포 구현
spring:
application:
name: travelus
...
---
# 프로파일 dev-blue 설정
spring:
config:
activate:
on-profile: blue
server:
port: 8082 # blue 인스턴스의 포트
---
# 프로파일 dev-green 설정
spring:
config:
activate:
on-profile: green
server:
port: 8083 # green 인스턴스의 포트
management:
endpoints:
web:
exposure:
include: health,info # 헬스체크와 정보 엔드포인트를 노출
endpoint:
health:
show-details: always # health 엔드포인트에서 세부 정보 표시
on-profile 옵션을 통해 blue, green 환경에서 다른 포트로 동작하도록 합니다.
endpoint를 설정하여 헬스체크를 통해 어플리케이션 상태를 확인할 수 있도록 합니다.
Backend-Compose.yml 수정
services:
backend-blue:
image: travelus-backend
container_name: backend-blue
environment:
- SPRING_PROFILES_ACTIVE=blue
- TZ=Asia/Seoul
env_file:
- /home/ubuntu/env/backend/.env
ports:
- "8080:8080"
networks:
- travelus-network
volumes:
- /home/ubuntu/backend/image:/root/test # 마운트 설정
backend-green:
image: travelus-backend
container_name: backend-green
environment:
- SPRING_PROFILES_ACTIVE=green
- TZ=Asia/Seoul
env_file:
- /home/ubuntu/env/backend/.env
ports:
- "8081:8081"
networks:
- travelus-network
volumes:
- /home/ubuntu/backend/image:/root/test # 마운트 설정
networks:
travelus-network:
external: true # 외부에서 정의된 네트워크 사용
- SPRING_PROFILES_ACTIVE : 위 yml에 설정한 profile을 활성화하여 Blue, Green 각각 다른 포트에서 실행합니다.
deploy.sh
#!/bin/bash
#1
echo "실행"
EXIST_BLUE=$(docker-compose -f backend-compose.yml ps | grep "backend-blue" | grep "running")
echo "$EXIST_BLUE ==== "
if [ -n "$EXIST_BLUE" ]; then
echo "Blue server is running. Starting backend-green..."
echo "$EXIST_BLUE"
docker-compose -f backend-compose.yml up -d backend-green
BEFORE_COLOR="blue"
AFTER_COLOR="green"
BEFORE_PORT=8080
AFTER_PORT=8081
else
echo "Blue server is not running. Proceeding with backend-blue..."
docker-compose -f backend-compose.yml up -d backend-blue
BEFORE_COLOR="green"
AFTER_COLOR="blue"
BEFORE_PORT=8081
AFTER_PORT=8080
fi
echo "===== ${AFTER_COLOR} server up(port:${AFTER_PORT}) ====="
# 2
for cnt in {1..10}
do
echo "===== 서버 응답 확인중(${cnt}/10) =====";
UP=$(curl -s http://j11d209.p.ssafy.io:${AFTER_PORT}/api/v2/actuator/health)
if [ -z "${UP}" ]
then
sleep 10
continue
else
break
fi
done
if [ $cnt -eq 10 ]
then
echo "===== 서버 실행 실패 ====="
exit 1
fi
# 3
echo "===== Nginx 설정 변경 ====="
sed -i "s/${BEFORE_PORT}/${AFTER_PORT}/g" nginx/default.conf && docker exec -it nginx nginx -s reload
echo "$BEFORE_COLOR server down(port:${BEFORE_PORT})"
docker-compose -f backend-compose.yml stop backend-${BEFORE_COLOR}
docker-compose restart nginx
작성한 compose 파일을 실행하고 헬스체크를 진행한 뒤 nginx 설정을 바꾸는 무중단배포를 위한 일련의 과정을 수행하는 bash 파일을 생성합니다.
1. 실행중인 컨테이너 정보 확인
EXIST_BLUE=$(docker-compose -f backend-compose.yml ps | grep "backend-blue" | grep "running")
grep 명령어를 통해 backend-blue 를 필터링하고 상태가 running인 컨테이너 정보를 추출하여 EXIST_BLUE 변수에 할당합니다.
결과적으로, backend-blue가 실행 중이면 EXIST_BLUE에 해당 정보가 저장되고, 그렇지 않으면 빈 값이 저장됩니다.
2. 컨테이너 실행 분기
if [ -n "$EXIST_BLUE" ]; then
echo "Blue server is running. Starting backend-green..."
echo "$EXIST_BLUE"
docker-compose -f backend-compose.yml up -d backend-green
BEFORE_COLOR="blue"
AFTER_COLOR="green"
BEFORE_PORT=8080
AFTER_PORT=8081
else
echo "Blue server is not running. Proceeding with backend-blue..."
docker-compose -f backend-compose.yml up -d backend-blue
BEFORE_COLOR="green"
AFTER_COLOR="blue"
BEFORE_PORT=8081
AFTER_PORT=8080
fi
- EXIST_BLUE가 비어 있으면(backend-blue가 실행 중이지 않은 경우) BLUE 인스턴스를, 그렇지 않다면 Green 인스턴스를 실행하도록 분기시켜 자동으로 블루 또는 그린 서버를 선택해 실행합니다.
3. 신규 서버 실행 상태 확인
# 2
for cnt in {1..10}
do
echo "===== 서버 응답 확인중(${cnt}/10) =====";
UP=$(curl -s http://j11d209.p.ssafy.io:${AFTER_PORT}/api/v2/actuator/health)
if [ -z "${UP}" ]
then
sleep 10
continue
else
break
fi
done
if [ $cnt -eq 10 ]
then
echo "===== 서버 실행 실패 ====="
exit 1
fi
최대 10번까지 새 서버의 헬스 체크 URL에 접속해 응답을 확인하고, 최종적으로 서버가 응답하지 않으면 스크립트를 중단합니다
4. Nginx 설정 변경
echo "===== Nginx 설정 변경 ====="
sed -i "s/${BEFORE_PORT}/${AFTER_PORT}/g" nginx/default.conf && docker exec -it nginx nginx -s reload
sed -i "s/${BEFORE_PORT}/${AFTER_PORT}/g" nginx/default.conf로 Nginx 설정 파일에서 이전 포트(BEFORE_PORT)를 새 포트(AFTER_PORT)로 변경합니다.
예를 들어, 8080(BEFORE_PORT)를 8081(AFTER_PORT)로 변경하면, /api/v1/ 경로로 들어오는 모든 요청이 새 컨테이너의 포트(8081)로 전달됩니다.
server {
listen 80;
server_name 43.203.182.118; // 서버에 할당된 IP 주소나 도메인 이름
...
location /api/v1/ {
proxy_pass http://43.203.182.118:8080;
...
}
포트 변경이 필요한 이유:
- 블루-그린 배포에서 새 버전 컨테이너를 실행할 때, Nginx의 Reverse Proxy 기능을 통해 동일한 경로(/api/v1/)로 요청을 유지하면서 새로운 컨테이너 포트로 라우팅할 수 있습니다.
- 이를 통해 사용자가 포트를 명시적으로 변경할 필요 없이, 기존 경로(host/api/v1)로 요청을 보내면 자동으로 새 포트로 연결됩니다.
echo "$BEFORE_COLOR server down(port:${BEFORE_PORT})"
docker-compose -f backend-compose.yml stop backend-${BEFORE_COLOR}
docker-compose restart nginx
Nginx 설정 변경 후 docker exec -it nginx nginx -s reload로 Nginx를 재시작해 설정을 적용하고, 기존 컨테이너는 중지하여 새 인스턴스로 요청이 이어지도록 합니다
default.conf
server {
listen 80;
server_name 43.203.182.118; // 서버에 할당된 IP 주소나 도메인 이름
// host/api/v1/ 로 시작하는 요청을 리버스 프록시로 처리합니다.
// 해당 경로로 요청이 들어오면 http://43.203.182.118:8080(백엔드)로 트래픽을 전달합니다.
location /api/v1/ {
proxy_pass http://43.203.182.118:8080;
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;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
// 기본 경로로 들어오면/usr/share/nginx/html 경로의 프론트엔드 정적파일을 서빙합니다.
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
'DevOps 공부 > CI CD' 카테고리의 다른 글
3. Jenkins CI / CD 구축하기 - 프론트엔드 배포 (with Nginx) (0) | 2024.10.30 |
---|---|
2. Jenkins CI / CD 구축하기 - 젠킨스 & 깃허브 웹훅 설정 (0) | 2024.10.24 |
1. Jenkins CI / CD 구축하기 - 스프링 Boot EC2 배포하기 with Docker (0) | 2024.10.14 |