| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 엔티티 생명주기
- 상속
- feignClient
- Docker
- Java
- 예외
- spring
- html
- ci/cd
- 어떤 개발자?
- Spring API
- 백엔드 로드맵
- 카카오인증토큰받기
- form
- oAuth2
- MIND 2023 #후기
- button
- Interface
- static
- jenkins
- 카카오인가코드받기
- 인스턴스
- tag
- 제로베이스
- 백엔드공부
- 엔티티 매니저
- 백엔드스쿨
- 카카오사용자정보가져오기
- GitHub_Actions
- input
- Today
- Total
HiDevelop
NGINX를 이용한 무중단 배포 본문
인턴 기간중에 새로운 프로젝트의 무중단 배포환경을 구축하여, 이에 대해 기록하고자 남기는 공간입니다 🥹
환경
- Amazone Linux2 (EC2)
무중단 배포란 무엇일까요..?
어플리케이션을 배포 과정을 자동화하여 어플리케이션을 지속적으로 업데이트하고 배포하는 방법으로 서비스가 중단되지 않고 배포되는 환경을 말합니다.
무중단 배포환경에 대한 방법론
무중단 배포환경에는 크게 3가지의 방법이 있습니다.
롤링 무중단 배포
새롭게 어플리케이샨을 업데이트 할 때, 서버 클러스터의 여러 인스턴스에 순차적으로 배포하여 전체 서비스 중단을 최소화하는 배포방식입니다.
- 새로운 버전을 전체 시스템에 동시에 배포하는 대신, 클러스터의 일부 노드나 인스턴스에 먼저 배포하는 방식으로, 이를 통해 전체 서비스의 중단없이 일부 노드에서 업데이트를 진행할 수 있습니다.
- 배포할 노드를 차례로 선택하고 업데이트를 적용한 후, 해당 노드가 정상적으로 동작하는지 확인하고 정상동작하면 순차적으로 모든 노드에 업데이트를 진행합니다.
- 각 단계엥서 배포 상태를 모니터링하면서 문제가 발생하면 전체 배포를 롤백하거나 해당 노드만 롤백할 수 있습니다.
카나리아 무중단 배포
새롭게 어플리케이션을 업데이트 할 때, 일부 사용자 또는 특정 환경에 먼저 배포하고, 이를 통해 새로운 버전의 안정성과 성능을 테스트한 후 전체 시스템으로 배포하는 방법입니다. 예를 들어, 게임의 새로운 버전을 A, B, C그룹 중 A그룹에게만 배포하여 버전의 안정성과 성능을 테스트한 뒤 나머지 그룹인 B, C 그룹에도 배포하는 것입니다.
- 새로운 업데이트를 전체 사용자에게 동시에 배포하는 대신, 일부 사용자 또는 특정 환경에 먼저 적용합니다. 이를 통해 실제 사용 환경에서 새로운 버전을 테스트할 수 있는 장점이 있습니다.
- 새로운 버전의 성능과 안정성을 모니터링 하면서 사용자들의 피드백을 수집할 수 있습니다.
그린-불루 무중단 배포
운영중인 서버(그린)와 새로운 버전을 동시에 운영하는 서버(블루)을 구축하여 전환하는 방법이니다.
- 그린 환경 : 현재 운영중인 서버로 새로운 버전이 업데이트 될 때까지, 사용자들은 이 서버를 사용합니다.
- 블루 환경 : 새로운 서버 버전을 구축합니다. 그린 버전에 몇가지 기능이 추가되거나 제거된 새로운 버전입니다.
- 새로운 버전과 운영중인 버전이 동시에 구축된 환경에서 NGINX와 같은 was를 통해 운영중인 버전과 연결을 새로운 버전과 연결하여 트래픽을 전환합니다.
- 트래픽 전환이 완료되면, 기존의 있던 그린 버전과 관련된 리소스와 어플리케이션을 종료합니다.
저 같은 경우에는 프로젝트의 규모가 작고, 프로젝트의 분야 매니악한 (사용자가 적은) 부분이고 프로젝트의 초기이다보니, 여러개의 서버를 구축할 필요가 없어 그린-블루 무중단 배포방식으로 구축해보겠습니다.
2.NGINX 구축하기
yum 패키지를 통해 nginx를 설치해줍시다.
//nginx 설치
$sudo yum install nginx
//nginx 버전 확인
$nginx -v

설치가 정상적으로 완료되었으면, nginx를 백그라운드 환경에서 실행시키고 확인해봅니다.
//nginx 백그라운드 환경 실행
$sudo systemctl start nginx
//nginx 실행 상태 확인
$sudo systemctl status nginx

2. Docker 설치
//yum 패키지 업데이트
$sudo yum update -y
//Docker 설치
sudo yum install docker -y
도커가 정삭적으로 설치가 완료되었는 지 확인하자.
//docker 버전확인
$docker -v

이제 도커를 실행하고, ec2에 접속 후 도커를 바로 사용할 수 있도록 Docker 그룹에 sudo 명령어를 추가해줘야한다.
//docker 실행
$sudo service docker start
//docker 그룹에 sudo 명령어 추가
$sudo usermod -aG docker ec2-user
//docker가 잘 실행되는지 확인
$docker run hello-word
❗️"usermod -aG docker ec2-user"하고 시스템을 재부팅해야 권한이 적용됩니다!!
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/create": dial unix /var/run/docker.sock: connect: permission denied.
재부팅하지 않을 시 위와같이 권한이 거부되었다고 오류가 뜹니다.
Docker Compose 설치
docker-compose를 설치하는 이유는 docker로 green, blue 실행 파일을 만들기 위해서입니다 😃
//docker compes 설치
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
여기서 짤막하게 설명하자면, curl은 curl(client url) 명령어는 프로토콜들을 이용해 URL로 데이터를 전송해 서버에 데이터를 보내거나 가져올 때 사용하기 위한 명령줄 도구 및 라이브러리입니다.
| short | long | 설명 |
| -L | --location | HTTP 301, 302 응답을 받은 경우 리디렉션 URL로 따라간다. --max-redirs 옵션 뒤에 숫자로 몇 번의 리디렉션까지 따라갈 것인지를 적을 수 있다. 기본 값은 50이다. |
| -o | --output FILE | curl로 받아온 내용을 FILE 이라는 이름의 파일로 저장 |
따라서 curl -L의 경우 해당 url에 리다이렉션 URL로 따라간 뒤에 usr/local/bin/docker-compose에 반환된 값을 파일로 저장하겠다는 뜻입니다.
docker-compose를 설치한뒤, 권한을 부여해줍시다.
//dokcer-compse 권한 부여
$ sudo chmod +x /usr/local/bin/docker-compose
이제 필요한 녀석들은 다 설치해주었습니다. 이제 본격적으로 설정을 시작해볼까요..?
3. NGINX 설정
저희가 사용하는 그린-블루 배포 방식은 NGINX가 기존의 서비스를 가리키고 있다가(블루) 새로운 버전이(그린)이 올라어면 블루와의 연결을 끊고 그린과 연결하는 방식입니다!
1. 블루 컨테이너 연결된 상황

2. 새로운 버전인 그린 컨테이너가 배포된 상황

3. 새로운 버전인 그린 컨테이너와 NGINX가 연결된 상황

4. 연결이 끊긴 블루컨테이너와 관련된 리소스와 프로세스 제거

이런식으로 진행될 거에요! 일단 이렇게 구성하기 위해서는 NGINX 설정을 한 번 해보겠습니다.
conf.d 파일로 이동해서 service-url.inc파일을 만들어 환경변수로 등록해줄겁니다.
// conf.d 파일로 이동
$cd /etc/nginx/conf.d
//service-url.inc 파일 생성
$sudo vim service-url.inc
//service-url.inc 파일에 service_url 환경변수 등록
set $service_url http://127.0.0.1:8000
sudo
service_url의 경우 nginx에서 통신할 컨테이너의 포트를 넣어주시면 됩니다. 추후 deploy.sh 파일을 작성할 건데 이 환경변수를 nginx환경변수를 8000, 8001으로 동적으로 변경해줄 겁니다.
이제 환경변수를 등록해줬으면, nginx에서 이를 사용할 수 있게 설정 파일에 등록해줘야합니다.
//nginx 설정 파일로 편집
$ sudo vim /etc/nginx/nginx.conf
해당 명령어를 입력해 설정 파일로 접속한 뒤 아래와 같이 바꿔줍니다.
server {
listen 80;
listen [::]:80;
server_name 도메인;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
include /tec/nginx/conf.d/service-url.inc
location / {
proxy_pass $service_url;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header x-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}
error_page 404 /404.html;
location = /404.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
설명해드리자면,
- include를 통해 저희가 등록해놓은 service_url을 가져옵니다.
- 등록해놓은 도메인이나 호스트 주소로 요청이 오면, proxy_pass에 등록되있는 호스트나 주소로 요청이 가게 됩니다.
- proxy_pass 부분에 저희가 설정해둔 환경변수 service_url이 들어가서 이를 동적으로 포트를 변환할 수 있습니다.
4. Docker compse 작성
이제 docker 컨테이너를 편하게 구동시키기 위해 docker compose를 작성해야 합니다.
저희가 자동으로 구동시켜야할 컨테이너는 blue(기존 버전), green(신 버전) 총 2개입니다.
4-1. Blue-container
//docker blue compose파일 작성
$sudo vi docker-compose.blue.yaml
docker-compose.blue.yaml
version: '3.1'
services:
api:
image: ${IMAGE_STORAGE}/${IMAGE_NAME}:${BUILD_NUMBER}
container_name: ${IMAGE_NAME}-blue
environment:
- LANG=ko_KR.UTF-8
- UWSGI_PORT=8080
ports:
- '8000:9000'
networks:
default:
external:
name: my-bridge
services에 실행할 이미지와 컨테이너 이름, 환경, 포트를 설정해줍니다. ( 적혀있는 변수 값은 추후 젠킨스 파일을 통해 넣어줄 겁니다 😃)
networks같은 경우에는 자신이 등록해 놓은 브릿지가 있으면 등록하시면 됩니다. 저 같은 경우는 레디스 컨테이너와 통신하기위해 my-bridge를 만들었기 때문에 해당 브릿지를 연결하기 위해 적어놓았습니다.
4-2. Green-container
//docker green compose파일 작성
$sudo vi docker-compose.green.yaml
docker-compose.green.yaml
version: '3.1'
services:
api:
image: ${IMAGE_STORAGE}/${IMAGE_NAME}:${BUILD_NUMBER}
container_name: ${IMAGE_NAME}-green
environment:
- LANG=ko_KR.UTF-8
- UWSGI_PORT=8080
ports:
- '8001:9000'
networks:
default:
external:
name: my-bridge
green 컨테이너의 compose 파일 또한 똑같이 적용했습니다.
5. deploy.sh 작성
deploy.sh은 젠킨스 파일을 통해 구동시킬 파일입니다.
$ sudo vi deploy.sh
#1 블루 컨테이너가 존재하는 지 확인
EXIST_BLUE=$(docker-compose -p ${IMAGE_NAME}-blue -f docker-compose.blue.yaml ps | grep Up)
#2 블루 컨테이너가 존재하지 않는다면, 블루 컨테이너를 가동
if [ -z "$EXIST_BLUE" ]; then
docker-compose -p ${IMAGE_NAME}-blue -f ~/docker-compose.blue.yaml up -d
BEFORE_COMPOSE_COLOR="green"
AFTER_COMPOSE_COLOR="blue"
BEFORE_PORT_NUMBER=8001
AFTER_PORT_NUMBER=8000
else #블루 컨테이너가 존재한다면, 그린 컨테이너를 가동
docker-compose -p ${IMAGE_NAME}-green -f ~/docker-compose.green.yaml up -d
BEFORE_COMPOSE_COLOR="blue"
AFTER_COMPOSE_COLOR="green"
BEFORE_PORT_NUMBER=8000
AFTER_PORT_NUMBER=8001
fi
echo "${AFTER_COMPOSE_COLOR} server up(port:${AFTER_PORT_NUMBER})"
#3 가동시킨 컨테이너와 정상적으로 호출하는 지 확인
for cnt in {1..10}
do
echo "서버 응답 확인중..(${cnt}/10)";
result=$(curl -s http://localhost:${AFTER_PORT_NUMBER}/actuator/health)
if [ -z "${result}" ]
then
sleep 5
continue
else
break
fi
done
if [ $cnt -eq 10 ]
then
echo "서버가 정상적으로 구동되지 않았습니다."
exit 1
fi
# 3
sudo sed -i "s/${BEFORE_PORT_NUMBER}/${AFTER_PORT_NUMBER}/" /etc/nginx/conf.d/service-url.inc
sudo nginx -s reload
echo "Deploy Completed!!"
# 4
echo "$BEFORE_COMPOSE_COLOR server down(port:${BEFORE_PORT_NUMBER})"
docker-compose -p ${IMAGE_NAME}-${BEFORE_COMPOSE_COLOR} -f docker-compose.${BEFORE_COMPOSE_COLOR}.yaml down
해당 코드의 경우 아래 블로그를 참고하여 작성했습니다.
천천히 설명해드리자면,
1. 블루컨테이너가 가동되고있는지 확인합니다.
2. 블루 컨테이너가 가동되고 있지 않다면, 블루 컨테이너를 실행하고, 블루 컨테이너가 가동되고 있다면, 그린 컨테이너를 실행하게 합니다.
3. 가동시킨 컨테이너가 잘 동작하는 지 확인하는 테스트 API와 (spring에 직접 제작했습니다 :) ) 통신하여 값이 있다면, for 반복문을 빠져나오고 카운트가 10까지 되었다면, 통신에 실패한 걸로 인식해 다음 명령문들을 실행하지 않고 종료합니다.
- curl 을 통해 api 요청 후 반환 된 값을 result 변수에 넣고 -z 옵션을 통해 result에 값이 들어있는 지 확인하고 값이 들어있다면 성공!
4. NGINX가 연결을 새롭게 실행한 컨테이너로 연결합니다.
5. 기존의 남아있던(연결이 끊어진) 컨테이너와 관련된 리소스와 프로세스를 중지하고 삭제합니다!
해당 명령어를 참고하시면 이해하는 데 큰 도움이 됩니다😃

이제 무중단 배포를 위한 준비가 다 끝났습니다. 젠킨스를 통해 저희가 만든 설정과 파일들을 동작만 시켜주면 됩니다.
5. 젠킨스 파일
젠킨스 관련한 설치 과정은 아래 포스트에서 확인해 보실 수 있습니다.
Jenkins와 Docker를 이용한 CI/CD - 1편
인턴 기간 중 맡은 프로젝트에서 CI/CD를 구축하고 이를 팀원들에게 공유하기 전, 스스로 개념과 과정을 익히고자 블로그에 관련 글을 포스팅 하고자 합니다😃 CI/CD 구축을 위한 환경 제가 구축
hidevelop.tistory.com
Jenkins와 Docker를 활용한 CI/CD -2편
이번 포스팅은 인턴 기간 동안 맡은 프로젝트의 ci/cd를 구축하고 이를 팀원들에게 공유하기 전, 지식과 기술을 다듬기 위한 포스팅입니다. 앞 포스팅에서 docker를 활용해 jenkins를 구축하는 포스
hidevelop.tistory.com
pipeline {
agent any
stages {
stage("Set Variable") {
steps {
script {
IMAGE_NAME = "이미지 이름"
IMAGE_STORAGE = "index.docker.io/v1/"
IMAGE_REPOSITORY = "이미지 저장소"
IMAGE_STORAGE_CREDENTIAL = "도커 자격증명 권한"
SSH_CONNECTION = "배포하는 호스트 IPv4 주소"
SSH_CONNECTION_CREDENTIAL = "배포하는 서버 SSH 자격증명 권한"
}
}
}
stage("Repository Clone"){
steps{
git branch : "master", credentialsId : "비트버켓 자격증명", url : "bitbuckt 주소"
}
}
stage("Clean Build") {
steps {
sh "chmod +x gradlew"
sh "./gradlew clean bootjar"
}
}
stage("Build Container Image") {
steps {
script {
image = docker.build("${IMAGE_REPOSITORY}/${IMAGE_NAME}")
}
}
}
stage("Push Container Image") {
steps {
script {
docker.withRegistry("https://${IMAGE_STORAGE}", IMAGE_STORAGE_CREDENTIAL) {
image.push("${env.BUILD_NUMBER}")
image.push("latest")
}
}
}
}
stage("Server Run") {
steps {
sshagent([SSH_CONNECTION_CREDENTIAL]) {
sh "ssh ec2-user@${SSH_CONNECTION} 'IMAGE_REPOSITORY=${IMAGE_REPOSITORY} IMAGE_NAME=${IMAGE_NAME} IMAGE_STORAGE=${IMAGE_STORAGE} BUILD_NUMBER=${BUILD_NUMBER} ./deploy.sh'"
}
}
}
}
}
젠킨스 파일을 작성해서 맨 마지막 단계인 Server Run단계에서 deploy.sh를 실행하는 문을 보실 수 있습니다.
sh "ssh ec2-user@${SSH_CONNECTION} 'IMAGE_REPOSITORY=${IMAGE_REPOSITORY} IMAGE_NAME=${IMAGE_NAME} IMAGE_STORAGE=${IMAGE_STORAGE} BUILD_NUMBER=${BUILD_NUMBER} ./deploy.sh'"
여기서 보면, 저희가 아까 위에서 compose.yaml 파일이나 deploy.sh에 있는 변수들에 들어갈 변수들을 설정해준 것을 볼 수 있습니다 :)
이제 해당 젠킨스 파일을 구동하면, 정상적으로 blue가 있다면 green을, green이 있다면 blue를 가동시키는 것을 확인해 보실 수 있을겁니다.😃