이전 글에서 GithubAction + AWS(S3 / CodeDeploy / EC2 / RDS) 를 이용해 사이드 프로젝트인 FeedB RESTAPI 를 배포 하는 과정을 완료 후 기록 했습니다. https://hsdevstudy.tistory.com/33
GithubAction + AWS(S3, CodeDeploy, EC2 , RDS) + Nginx 를 이용한 CI/CD 무중단 배포 경험 (1 / 2)
이 글은 프론트엔트(4) + 디자이너(1) + 백엔드(2) 인원으로 진행한 FeedB 웹 애플리케이션을 진행하다 Infra 구축을 맡게 되어 진행 후 정리하여 기록함으로서 복습의 효과를 얻기 위해 작성한 글입
hsdevstudy.tistory.com
이번 글은 EC2 내부에서 Nginx 를 이용해 무중단 배포를 진행하는 과정을 기록해 보도록 하겠습니다.
☀️ Nginx 무중단 배포를 선택하게 된 이유
프론트 팀원들과 협업해서 FeedB 사이드 프로젝트에서 백엔드 RestAPI 를 맡아 진행하고 있는데 운영 서버에 배포를 EC2 환경에서 한 개의 애플리케이션을 가지고 진행했었습니다.
이와 같이 진행해보니 추가 또는 수정하기 위해 배포 서버를 재배포 할 때마다 다른 팀원들이 RsetAPI를 사용하지 못하고 배포과정에서 이슈가 생겨 중단되면 애플리케이션이 종료되는 문제가 있었고 팀의 효율성에 큰 문제라고 생각하여 재배포를 하더라도 기존 애플리케이션을 사용하는 것에 이상이 없도록 하나의 EC2 서버에 nginx 1대와 스프링부트 jar 2대를 이용한 무중단 배포를 진행하게 되었습니다.

☀️ 무중단 배포란?
말 그대로 서비스가 중단되지 않은 상태로 새로운 버전을 사용자에게 계속해서 배포하는 것입니다.
기존 Ci/CD 환경을 통한 배포는 개발자 중심이라면 , 무중단 배포는 사용자 중심이라고 볼 수 있다.
Nginx가 외부의 요청을 받아 뒷단(예시 - 8081 , 8082)로 요청을 전달하는 행위를 리버스 프록시라고 합니다.
이런 리버스 프록시 서버(Nginx)는 요청을 전달하고 실제 요청에 대한 처리는 뒷단의 웹서버들이 처리합니다.
대신 , 외부 요청을 뒷단 서버들에게 골고루 분배한다거나 , 한번 요청왔던 js , image , css 등은 캐시하여 리버스
프록시 서버에서 바로 응답을 주는 등의 여러 장점들이 있습니다.
☀️ 무중단 배포 구축하기
1. Nginx 설치
현재 EC2 , Ci/CD 파이프라인 구축 완료를 가정하며 순서를 진행합니다.
본인의 EC2 서버에서 Nginx 를 설치합니다.
// 자신의 EC2에 nginx를 설치.
sudo yum install nginx
// 설치가 완료되면 아래명령어로 Nginx 를 실행
sudo systemctl start nginx
//Nginx 실행되었는지 아래 명령어로 확인
sudon systemctl status nginx
Nginx 실행까지 확인 했으면 Nginx 설정 파일을 엽니다.
sudo vi /etc/nginx/nginx.conf
설정 내용 중 server 아래의 location / 부분을 찾아서 아래와 같이 추가.

- proxy_pass : 서버로 요청이 오면 해당 url 로 전달한다는 뜻.
- proxy_set_header XXX : 실제 요청 데이터를 header의 각 항목에 할당
- ex) proxy_set_header X-Real-IP $remote_addr: Request Header의 X-Real-IP에 요청자의 IP를 저장
수정 완료 후 Nginx를 재시작합니다.
sudo service nginx reload
위의 과정까지 진행하였다면 localhost:8080의 스프링 부트 애플리케이션으로 Nginx 가 리버스 프록시 역할을 하게 된다.
2. 스프링 부트 set1 , set2 Profile 설정
API를 만들기 전에 이후에 health check(배포 확인)를 위해 actuator를 이용합니다.
build.gradle에 추가
implementation 'org.springframework.boot:spring-boot-starter-actuator'
그 이후 실행중인 프로젝트의 Profile을 확인하는 API를 만듭니다.
@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class WebRestController {
private final Environment env;
@GetMapping("/profile")
public String getProfile () {
return Arrays.stream(env.getActiveProfiles())
.findFirst()
.orElse("");
}
}
운영 환경의 yml파일을 추가해서 profile별로 포트를 다르게 구성합니다.
---
spring:
profiles: set1
server:
port: 8081
---
spring:
profiles: set2
server:
port: 8082
외부에 있는 이 파일을 프로젝트가 호출 할 수 있도록 Application.java 코드를 아래와 같이 변경합니다.
@SpringBootApplication
@EnableJpaAuditing
public class PlantProjectApplication {
public static final String APPLICATION_LOCATIONS = "spring.config.location="
+ "classpath:application.properties,"
+ "classpath:real-application.yml";
public static void main(String[] args) {
new SpringApplicationBuilder(PlantProjectApplication.class)
.properties(APPLICATION_LOCATIONS)
.run(args);;
}
그리고 IntelliJ의 edit Configuration 기능을 이용해 profile별 set1 , set2 설정 후 테스트를 해봅니다.
테스트 시 localhost:8081/api/profile 은 set1 8082는 set2를 반환하면 현재까지의 과정을 성공한 것입니다.
3. Nginx 동적 프록시 설정
이전 글에서 deploy.sh 배포스크립트가 완료되었다는 가정하에 어플리케이션 실행 후 Nginx가 기존에 바라보던 Profile(포트)의 반대편을 바라보도록 변경 시켜야 합니다.
sudo vim /etc/nginx/nginx.conf
location / 부분을 아래와 같이 변경
include /etc/nginx/conf.d/service-url.inc;
location / {
proxy_pass $service_url;
- include /etc/nginx/conf.d/service-url.inc;
- service-url.inc 파일을 include 시킵니다.
- Java로 치면 import 패키지와 같다
- 위와 같이 설정하면 nginx.conf에서 service-url.inc 에 있는 변수들을 사용할 수 있습니다.
그럼 service-url.inc 파일을 생성해보겠습니다.
sudo vim /etc/nginx/conf.d/service-url.inc
그리고 아래 코드를 입력합니다.
set $service_url http://127.0.0.1:8081;
저장하셨으면 변경 내용 반영을 위해 nginx restart를 실행합니다.
sudo systemctl restart nginx
4. Nginx 스크립트 작성
Nginx을 배포 시점에 바라보는 Profile을 자동으로 변경하도록 스위치 스크립트를 생성하겠습니다.
sudo vim ~/etc/server/PlantBackend/scripts/switch.sh
[switch.sh]
echo "> 현재 구동중인 Port 확인"
CURRENT_PROFILE=$(curl -s http://localhost/api/profile)
# 쉬고 있는 set 찾기: set1이 사용중이면 set2가 쉬고 있고, 반대면 set1이 쉬고 있음
if [ $CURRENT_PROFILE == set1 ]
then
IDLE_PORT=8082
elif [ $CURRENT_PROFILE == set2 ]
then
IDLE_PORT=8081
else
echo "> 일치하는 Profile이 없습니다. Profile: $CURRENT_PROFILE"
echo "> 8081을 할당합니다."
IDLE_PORT=8081
fi
echo "> 전환할 Port: $IDLE_PORT"
echo "> Port 전환"
echo "set \$service_url http://127.0.0.1:${IDLE_PORT};" |sudo tee /etc/nginx/conf.d/service-url.inc
PROXY_PORT=$(curl -s http://localhost/api/profile)
echo "> Nginx Current Proxy Port: $PROXY_PORT"
echo "> Nginx Reload"
sudo service nginx reload
저장하신뒤 switch.sh에 실행권한을 줍니다.
sudo chmod +x ~/etc/server/PlantBackend/scripts/switch.sh
주의사항) 저는 여기서 switch.sh를 CI/CD 과정에서 복사되게 구현하였기 때문에 실행권한을 주는 명령어도 switch.sh에 추가했습니다!
위와 같이 nginx 리로드 스크립트도 작성 후 해당 스크립트 실행 명령어를 이전 글에서 작성한 deploy.sh 마지막에 추가하면 CI/CD 자동 배포 과정에서 배포 후 nginx reload 과정까지 자동으로 진행됩니다!!