서버 인스턴스 생성
깃 CI/CD 설정
세팅에서 secrets IP, ID, KEY, PORT 설정
서버 내에 관리할 폴더 생성
startup.sh, target 생성
chmod 755 startup.sh 권한 설정
- option -
서버 인스턴스에 nginx 설치 후 테스트 및 실행
nginx 포트 접근 허용
서버 인스턴스에 java 설치
SonarQube 프로젝트 생성
세팅에서 secrets HOST, TOKEN 설정
# 서버 인스턴스 내부에서 진행
sudo adduser {project-name}
위와 같이 사용자를 추가합니다. 사용자명은 자유롭게 해도 됩니다.
sudo login {project-name}
만들어진 사용자로 접속합니다.
접속이 완료되었다면 키 파일을 생성해야 하는데 다음과 같습니다.
mkdir keygen
cd keygen
ssh-keygen -t rsa -f ./{key-name}
# 모두 Enter(return)
모든 명령어를 입력하고 완료했다면
- {key-name}
- 비공개 키
- {key-name}.pub
- 공개 키
위 2개의 키가 생성됩니다.
다음으로
mkdir ~/.ssh
chmod 700 ~/.ssh
cp ~/keygen/{key-name}.pub ~/.ssh/authorized_keys
chmod 644 ~/.ssh/authorized_keys
위 명령어를 순서대로 입력합니다.
각 줄의 의미는 다음과 같습니다.
1. mkdir keygen
- mkdir는 "make directory"의 줄임말로, 새로운 폴더(디렉토리)를 만드는 명령어, 여기서는 keygen이라는 이름의 폴더를 만듦
2. cd keygen
- cd는 "change directory"의 줄임말로, 현재 작업하고 있는 폴더를 바꾸는 명령어, 여기서는 방금 만든 keygen 폴더로 진입
3. ssh-keygen -t rsa -f ./{key-name}
- ssh-keygen은 SSH 키를 생성하는 명령어. SSH 키는 컴퓨터끼리 안전하게 연결하기 위해 사용하는 비밀 열쇠
- -t rsa: RSA라는 방법으로 키를 만들겠다는 뜻
- -f ./{key-name}: 키 파일의 이름을 {key-name}로 정하겠다는 의미, ./는 현재 폴더를 의미
4. #모두 enter
- 여기서 "모두 enter"는 키를 만들 때 여러 질문이 나오는데, 그때마다 그냥 엔터키를 눌러서 기본값을 선택하라는 뜻
5. cat github_rsa
- cat은 파일 내용을 보여주는 명령어. 여기서는 {key-name}라는 파일의 내용을 화면에 보여 줌, 이 파일에는 비밀 키가 들어있음
6. mkdir ~/.ssh
- 여기서 ~는 현재 사용자의 홈 폴더를 의미, mkdir ~/.ssh는 홈 폴더 안에 .ssh라는 숨겨진 폴더를 만드는 것, 이 폴더는 SSH 관련 파일을 저장하는 곳
7. chmod 700 ~/.ssh
- chmod는 파일이나 폴더의 권한을 설정하는 명령어, 700은 이 폴더를 오직 나만 사용할 수 있도록 설정하는 것
- 7: 읽고, 쓰고, 실행할 수 있는 권한
- 0: 다른 사람은 아무것도 할 수 없음
8. cp ~/keygen/github_rsa.pub ~/.ssh/authorized_keys
- cp는 파일을 복사하는 명령어. 여기서는 keygen 폴더에 있는 {key-name}.pub 파일을 .ssh 폴더 안의 authorized_keys라는 파일로 복사
- authorized_keys: 이 파일에 있는 키는 이 컴퓨터가 안전하게 연결할 수 있는 키
9. chmod 644 ~/.ssh/authorized_keys
- 이 명령어는 authorized_keys 파일의 권한을 설정. 644는 나만 이 파일을 수정할 수 있고, 다른 사람들은 읽기만 가능하다는 뜻
- 6: 읽고, 쓰기 가능 (나)
- 4: 읽기만 가능 (다른 사람)
10. chmod 400 {key-name}
- 마지막으로 다시 chmod 명령어를 사용해서 {key-name} 파일의 권한을 설정, 400은 오직 나만 이 파일을 읽을 수 있도록 설정. 다른 사람은 이 파일을 볼 수 없음
# .sh 파일 작성 ..
#!/bin/bash
# Gracefully stop application on specified port
stop_application() {
local port=$1
echo "Sending shutdown request to application on port $port..."
# Graceful Shutdown을 위해 /actuator/status 호출
curl -X POST http://133.186.132.193:$port/actuator/status || echo "Application on port $port is not running or shutdown endpoint failed."
sleep 40 # 애플리케이션 종료 대기
# 포트가 여전히 열려 있는 경우 강제 종료
local pid=$(lsof -t -i:$port)
if [ -n "$pid" ]; then
echo "Forcefully killing process on port $port (PID: $pid)..."
kill -9 $pid || echo "Failed to kill process on port $port."
fi
}
# Start application on specified port
start_application() {
local port=$1
echo "Starting application on port $port..."
/usr/lib/jvm/temurin-21-jdk-amd64/bin/java -Xmx512m -Dserver.port=$port -jar ~/2joping-book/target/*.jar --spring.profiles.active=prod > 2joping_book_$port.log 2>&1 &
echo "Application started on port $port."
}
# Deploy application on specified ports
deploy_instance() {
local stop_port=$1
local start_port=$2
# 1. 기존 애플리케이션 종료
stop_application $stop_port
# 2. 새로운 애플리케이션 실행
start_application $start_port
}
# 첫 번째 배포: 8084에서 종료하고 8084 실행
deploy_instance 8084 8084
# 두 번째 배포: 8085에서 종료하고 8085 실행
deploy_instance 8085 8085
저희 프로젝트에서 사용한 쉘 스크립트입니다. 처음 작성해 봐서 그런가 개선할 부분이 많이 있습니다. 차차 개선할 계획입니다.
위의 쉘 스크립트는 단순 재배포 방식입니다. 무중단 방식이 아닙니다. 따라서 블루-그린 방식으로 개선해 보자면 다음과 같습니다.
#!/bin/bash
# 애플리케이션 실행 여부 확인 함수
check_application_running() {
local port=$1
curl -s "http://133.186.132.193:$port/actuator/health" | grep -q "UP"
}
# 애플리케이션 종료 함수
stop_application() {
local port=$1
echo "Stopping application on port $port..."
curl -X POST http://133.186.132.193:$port/actuator/shutdown || echo "Shutdown request failed or application not running."
sleep 10
local pid=$(lsof -t -i:$port)
if [ -n "$pid" ]; then
echo "Forcefully killing process on port $port (PID: $pid)..."
kill -9 $pid
fi
}
# 애플리케이션 실행 함수
start_application() {
local port=$1
echo "Starting new application on port $port..."
nohup /usr/lib/jvm/temurin-21-jdk-amd64/bin/java -Xmx512m -Dserver.port=$port -jar ~/2joping-book/target/*.jar --spring.profiles.active=prod > 2joping_book_$port.log 2>&1 &
# 애플리케이션이 정상적으로 뜰 때까지 대기
for i in {1..30}; do
sleep 3
if check_application_running $port; then
echo "Application started successfully on port $port!"
return 0
fi
done
echo "Application failed to start on port $port."
exit 1
}
# Nginx 트래픽 변경 함수 (Blue-Green 전환)
switch_traffic() {
local new_port=$1
echo "Switching traffic to port $new_port..."
# Nginx 설정 변경 (기존 포트를 새 포트로 변경)
sed -i "s/set \$app_port .*/set \$app_port $new_port;/" /etc/nginx/conf.d/app.conf
nginx -s reload
echo "Traffic switched to port $new_port."
}
# 블루-그린 배포 수행
deploy_blue_green() {
local old_port=$1
local new_port=$2
echo "Starting Blue-Green Deployment: $old_port → $new_port"
# 1. 새로운 애플리케이션 실행
start_application $new_port
# 2. 트래픽을 새로운 포트로 변경
switch_traffic $new_port
# 3. 기존 애플리케이션 종료
stop_application $old_port
echo "Blue-Green Deployment completed."
}
# 현재 실행 중인 포트 확인 후 반대 포트 선택
if check_application_running 8084; then
deploy_blue_green 8084 8085
else
deploy_blue_green 8085 8084
fi
위와 같이 작성하면 블루-그린 방식으로 배포할 수 있습니다. 그러나 직접적인 포트 번호를 지정해 줘야 하는 등 개선할 부분이 아직도 남아 있습니다. 만약 포트가 8084, 8085에서 다른 포트로 바뀌면 직접 설정해 줘야 하기 때문입니다.
또한 위 스크립트를 롤링 방식으로 구현한다면 어떻게 작성하면 좋을지 공부해 보겠습니다.
롤링 방식으로 작성한다면 다음과 같습니다.
#!/bin/bash
# 현재 실행 중인 애플리케이션 확인 함수
check_application_running() {
local port=$1
curl -s "http://133.186.132.193:$port/actuator/health" | grep -q "UP"
}
# 애플리케이션을 특정 포트에서 실행하는 함수
start_application() {
local port=$1
echo "📢 새로운 애플리케이션을 $port 포트에서 실행합니다..."
# 애플리케이션을 백그라운드에서 실행 (nohup 사용)
nohup /usr/lib/jvm/temurin-21-jdk-amd64/bin/java -Xmx512m -Dserver.port=$port -jar ~/2joping-book/target/*.jar --spring.profiles.active=prod > 2joping_book_$port.log 2>&1 &
# 새로운 애플리케이션이 정상적으로 실행될 때까지 기다림
for i in {1..30}; do
sleep 3
if check_application_running $port; then
echo "✅ 애플리케이션이 $port 포트에서 정상적으로 실행되었습니다!"
return 0
fi
done
echo "❌ 애플리케이션이 $port 포트에서 시작되지 않았습니다. 배포 실패!"
exit 1
}
# Nginx에서 특정 포트를 라우팅에서 제외하는 함수
remove_from_nginx() {
local port=$1
echo "🛑 포트 $port 을 Nginx 라우팅에서 제거합니다..."
sed -i "/set \$app_port $port/d" /etc/nginx/conf.d/app.conf
nginx -s reload
}
# Nginx에 특정 포트를 다시 추가하는 함수
add_to_nginx() {
local port=$1
echo "🔄 포트 $port 을 Nginx 라우팅에 추가합니다..."
sed -i "/set \$app_port .*/a set \$app_port $port;" /etc/nginx/conf.d/app.conf
nginx -s reload
}
# 기존 애플리케이션을 종료하는 함수
stop_application() {
local port=$1
echo "🛑 기존 애플리케이션을 $port 포트에서 종료합니다..."
# 우아한 종료 요청 (Spring Boot Actuator의 shutdown 엔드포인트 호출)
curl -X POST http://133.186.132.193:$port/actuator/shutdown || echo "⚠️ 애플리케이션 종료 요청 실패 또는 이미 종료됨."
# 프로세스 종료까지 10초 대기
sleep 10
# 아직 프로세스가 살아 있다면 강제 종료
local pid=$(lsof -t -i:$port)
if [ -n "$pid" ]; then
echo "⚠️ 프로세스 $pid 강제 종료 중..."
kill -9 $pid
fi
echo "✅ 기존 애플리케이션이 종료되었습니다."
}
# 1개씩 롤링 업데이트 수행
deploy_rolling_update() {
local target_port=$1
echo "🚀 롤링 배포 시작: 포트 $target_port"
# 1. 기존 포트를 Nginx 라우팅에서 제외
remove_from_nginx $target_port
# 2. 기존 애플리케이션 종료
stop_application $target_port
# 3. 새로운 버전 실행
start_application $target_port
# 4. 정상적으로 실행되었으면 Nginx에 다시 추가
add_to_nginx $target_port
echo "✅ 포트 $target_port 롤링 배포 완료!"
}
# 실행 중인 포트를 확인하고 하나씩 롤링 배포 수행
if check_application_running 8084; then
deploy_rolling_update 8084
deploy_rolling_update 8085
else
deploy_rolling_update 8085
deploy_rolling_update 8084
fi
echo "🎉 모든 인스턴스가 성공적으로 업데이트되었습니다!"
차이점은 '기존 트래픽에 대한 포트 사용'에 있습니다.
예를 들어 8084, 8085 포트 2개를 사용하고 있을 경우
블루-그린 배포 전략은 2개의 포트 중 하나만 쓰게 되고
롤링 배포 전략은 2개의 포트 전부 사용하게 됩니다.
# 블루-그린 배포
- 새로운 애플리케이션을 실행
- 트래픽을 새로운 애플리케이션으로 전부 전환
- 기존 애플리케이션 종료
# 롤링 배포
- 새로운 애플리케이션 실행
- 트래픽을 점진적으로 기존 애플리케이션에서 새로운 애플리케이션으로 전환
- 기존 애플리케이션 종료
각각의 장단점은 다음과 같습니다.
배포 방식 | 장점 | 단점 | 비고 |
재배포 | 가장 간단함 | 서비스 중단 발생 | |
블루-그린 배포 | 빠른 롤백 가능 | 운영 비용 증가 | |
롤링 배포 | 점진적 업데이트 | 롤백 어려움 |
각 장단점을 파악하고 프로젝트에 적절히 적용하는 것이 중요합니다 !
'CNU SW 아카데미 > 15주차(최종 프로젝트)' 카테고리의 다른 글
[NHN] Github Action CI/CD (0) | 2024.10.23 |
---|---|
[NHN] 최종 과제 시작 (0) | 2024.10.23 |