실제로 우리팀에서 사용했던 무중단 배포 방식이다.
Spring을 예로 들면 전체적인 흐름은 jenkins를 통해 빌드 후, 빌드 된 jar파일을 배포를 관리하는 폴더로 복사한다. sh 스크립트를 통해 관리하기 되는데 docker compose를 통해 새로운 버전의 컨테이너를 띄우고 이전 버전을 내린다.. 말로는 참 쉽다. ;-;
블루 그린 배포 - 개념
블루 그린 배포는 새로운 버전의 인스턴스를 띄운 뒤, 로드밸런서를 통해 새로운 버전의 인스턴스로 트래픽을 요청한다.
블루 그린 배포를 이용하면 기존에 인스턴스를 유지하던 비용의 2배가 들어가는 시점이 존재한다, 또한 새로운 버전에 치명적인 오류가 있다면 전환된 서버는 모두 내려갈 것이다. 하지만 구버전의 인스턴스가 그대로 남아있기 때문에 롤백이 손쉽고, 차마 테스트하지 못한 부분에서 치명적인 오류가 발생했다면 구버전으로 다시 트래픽을 돌리기 간편하다.
블루 그린 배포 - 실습
1. jenkins pipe line
# 스토리 관련 스테이지
stage('Building story image'){
steps{
script {
dockerImage = docker.build("jodong2/story-zero-downtime", "/var/jenkins_home/workspace/deploy_test/StoryModule")
withCredentials([usernamePassword(credentialsId: 'jodong2', usernameVariable: 'DOCKER_HUB_USERNAME', passwordVariable: 'DOCKER_HUB_PASSWORD')]) {
sh 'docker login -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_PASSWORD'
sh 'docker push jodong2/story-zero-downtime'
}
}
}
}
stage('Run docker story image') {
steps {
dir('/var/jenkins_home/workspace/deploy/StoryModule'){
sh 'chmod +x deploy.sh'
sh './deploy.sh'
}
}
}
이미지를 빌드하고 deploy.sh를 실행한다.
#!/bin/bash
#story deploy
export DOCKER_REGISTRY=jodong2 DOCKER_APP_NAME=story-zero-downtime IMAGE_TAG=latest
EXIST_BLUE=$(docker compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yaml ps | grep Up)
# 블루가 떠있는지 확인
if [ -z "$EXIST_BLUE" ]; then
echo "blueis is not exist. so make blue container"
echo "blue up"
docker compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yaml up -d
BEFORE_COMPOSE_COLOR="green"
AFTER_COMPOSE_COLOR="blue"
echo "end"
else
echo "blue is exist. so make green container"
echo "green up"
docker compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yaml up -d
BEFORE_COMPOSE_COLOR="blue"
AFTER_COMPOSE_COLOR="green"
fi
sleep 10
EXIST_AFTER=$(docker compose -p ${DOCKER_APP_NAME}-${AFTER_COMPOSE_COLOR} -f docker-compose.${AFTER_COMPOSE_COLOR}.yaml ps | grep Up)
if [ -n "$EXIST_AFTER" ]; then
docker compose -p ${DOCKER_APP_NAME}-${BEFORE_COMPOSE_COLOR} -f docker-compose.${BEFORE_COMPOSE_COLOR}.yaml down
echo "$BEFORE_COMPOSE_COLOR down"
fi
deploy.sh파일은 떠있는 blue, green에 따라 각 docker-compose.yaml파일을 실행하게 된다.
컨테이너가 띄워지는 시간으로 sleep 10초를 걸어놨는데, spring 메인 서버 같은 경우엔 애플리케이션을 시작하는데 약 20초를 필요로 한다. (사실 무중단임에도 팀원이 잠깐 내려가는거 같은데? 하길래 부랴부랴 수정했다..) 따라서 해당 메인 서버의 sh파일은 sleep이 25초로 설정했다.
이후 다른 색의 컨테이너가 성공적으로 띄워졌다면 이전 컨테이너를 중지한다.
version: '3.7'
services:
api:
image: ${DOCKER_REGISTRY}/${DOCKER_APP_NAME}:${IMAGE_TAG}
container_name: ${DOCKER_APP_NAME}-blue
environment:
- LANG=ko_KR.UTF-8
ports:
- '{port1}:8080'
volumes:
- story-logs:/var/log/app
volumes:
story-logs:
port의 경우 blue와 green을 다르게 두고 해당 포트를 기준으로 로드밸런서가 구분할 수 있도록 하였다.
예를들어 blue는 8081, green은 8082를 열어둔다. aws에서 보안설정도 잘 확인하자.... ;-;
# 문제가 좀 있는.. upstream 구성..
upstream story {
least_conn;
server {ip}:{blue port1};
server {ip}:{green port2};
server {ip}:{blue port1};
server {ip}:{green port2};
}
Nginx에서 upstream을 통해 blue green 각 서버로 연결한다.
이외에도 헬스체크 기능을 활용해서 죽은 서버에는 요청을 할 수 있지만 연결을 실패할 경우 nginx는 정해진 알고리즘에따라 다음 서버로 요청을 보낸다. 따라서 별도의 설정 없이도 올라가있는 컨테이너에 요청을 보낼 수 있다. 가장 처음 시도한 방식이고,,, 죽은 서버에 요청을 보내고 실패를 돌려받은 뒤 다음 서버에 요청을 보내는건 문제가 있는 방식이다.
Nginx - Health Check
죽은 서버로 요청을 보내지 않기 위해 health check하는 방법이 있다. 서버가 정상 상태인지 실시간으로 계속 확인하는 방법으로 정상서버의 서버에게만 트래픽을 분배한다.
upstream story {
least_conn;
server {ip}:{blue port1};
server {ip}:{blue port1};
check interval=3000 rise=2 fall=5 timeout=4000 type=http;
check_http_send "HEAD / HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
추가된 내용을 보면 3초단위로 2번 성공하면 정상, 5번 실패하면 비정상으로 판단한다. health check는 http프로토콜을 사용하고 HTTP1.0을 사용하여 보낸다. HTTP1.1~2를 주로 사용하지만 1.0을 사용한 이유는 연결에 성공해도 지속하지 않고 바로 끊는 특징을 갖고 있어서 1.0을 사용했다. 또한 응답코드가 2xx, 3xx라면 정상상태로 판단한다.
죽은서버에 요청을 보내며 네트워크에 지연이 발생할 수 있었던 설정을 위와 같이 죽은 서버에는 요청을 보내지않도록하는 Nginx의 health check기능을 사용하는 방법이 있었다.
2개의 nginx 설정파일과 deploy.sh 변경
docker compose로 컨테이너를 삭삭(팀원말투인데 머리속에 떠오르 짧고 간결한 표현이 이거밖에 안떠오른다ㅂㄷㅂㄷ; ) 바꾸는데 Nginx도 같이 갈아끼우고 reload하면 되지 않을까? reload하는 순간 짧은 down time이 있지만 health check한다고 자원을 계속 소모하는 방법보단 짧은 down time으로 자원을 아낄 수도 있다.
변경된 deploy.sh이다.
#!/bin/bash
#story deploy
export DOCKER_REGISTRY=jodong2 DOCKER_APP_NAME=story-zero-downtime IMAGE_TAG=latest
EXIST_BLUE=$(docker compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yaml ps | grep Up)
# 블루가 떠있는지 확인
if [ -z "$EXIST_BLUE" ]; then
echo "blueis is not exist. so make blue container"
echo "blue up"
docker compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yaml up -d
BEFORE_COMPOSE_COLOR="green"
AFTER_COMPOSE_COLOR="blue"
echo "end"
else
echo "blue is exist. so make green container"
echo "green up"
docker compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yaml up -d
BEFORE_COMPOSE_COLOR="blue"
AFTER_COMPOSE_COLOR="green"
fi
sleep 10
EXIST_AFTER=$(docker compose -p ${DOCKER_APP_NAME}-${AFTER_COMPOSE_COLOR} -f docker-compose.${AFTER_COMPOSE_COLOR}.yaml ps | grep Up)
if [ -n "$EXIST_AFTER" ]; then
sudo ln -sf /etc/nginx/sites-available/nginx.${AFTER_COMPOSE_COLOR}.conf /etc/nginx/sites-enabled/nginx.conf
sudo service nginx reload
docker compose -p ${DOCKER_APP_NAME}-${BEFORE_COMPOSE_COLOR} -f docker-compose.${BEFORE_COMPOSE_COLOR}.yaml down
echo "$BEFORE_COMPOSE_COLOR down"
fi
다음 색깔의 컨테이너가 제대로 떴다면 다음색깔에 맞는 nginx 설정파일로 변경한뒤 reload 한다.
reload하는 과정에서 짧은 down time이 발생할 수 있어서 무중단배포라기엔 칭호가 조금 아쉽지만 처음 생각한 방식은 이렇다..
nginx의 설정파일은
upstream story {
least_conn;
server {ip}:{port1};
}
이렇게 간결해졌고 그에맞는 ip:port만 다른 파일을 설정해뒀다.
server가 한대라면 location에서 바로 쏴도 될 듯 싶다 !
저번주에 올렸어야됐는데.. 할 게 많았다.
프로젝트하랴 CS공부하랴..싸피 마지막 플젝이 끝났다 !
끝나도 시키는게 뭔가 많다. 서버 관련 설정파일은 나중에 다시 보기위해 저장해놔야되는데.. 아이고. .언제다하지..
다음주 주말에는 별일이 없다면 자바나 파이썬에 관련된 글을 올릴듯 싶다.
왜냐면.. 저번주에 자바에 SOLID에 대해 공부했었다. 옛날옛적에 면접준비한다고 부랴부랴 공부했을때는 이해가 잘 안됐는데 이번엔 "아이고 내 코드는 객체지향을 개떡같이 사용하고 있구나!" 를 느낄 수 있었다. 그래서 다음에 진행하게 될 프로젝트나 기회가 된다면, 지금까지 해왔던 프로젝트 하나를 잡고 수정해볼 생각이다.
파이썬을 포스팅한다면... 파이썬의 GIL에 대해 시간을 길게 잡고 자세하게 포스팅해볼 생각이다..
'Web > Ops' 카테고리의 다른 글
무중단 배포 - 롤링 배포 (2/4) (0) | 2023.03.05 |
---|---|
무중단 배포 - 개념 (1/4) (0) | 2023.03.02 |
ec2, docker를 활용한 프로젝트 배포하기 - 1 (0) | 2023.02.11 |
ubuntu에서 docker를 이용한 mysql 설치 (0) | 2023.02.11 |
ubuntu에서 Nginx설치, 설정 및 HTTPS (Certbot) (0) | 2023.02.11 |