[NHN] CI/CD 전체 설정 완전 사이클

2024. 10. 29. 17:05·CNU SW 아카데미/15주차(최종 프로젝트)

서버 인스턴스 생성
깃 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개의 포트 전부 사용하게 됩니다.

 

# 블루-그린 배포

  1. 새로운 애플리케이션을 실행
  2. 트래픽을 새로운 애플리케이션으로 전부 전환
  3. 기존 애플리케이션 종료

 

# 롤링 배포

  1. 새로운 애플리케이션 실행
  2. 트래픽을 점진적으로 기존 애플리케이션에서 새로운 애플리케이션으로 전환
  3. 기존 애플리케이션 종료

 

각각의 장단점은 다음과 같습니다.

배포 방식 장점 단점 비고
재배포 가장 간단함 서비스 중단 발생  
블루-그린 배포 빠른 롤백 가능 운영 비용 증가  
롤링 배포 점진적 업데이트 롤백 어려움  

 

각 장단점을 파악하고 프로젝트에 적절히 적용하는 것이 중요합니다 !

 

'CNU SW 아카데미 > 15주차(최종 프로젝트)' 카테고리의 다른 글

[NHN] Github Action CI/CD  (0) 2024.10.23
[NHN] 최종 과제 시작  (0) 2024.10.23
'CNU SW 아카데미/15주차(최종 프로젝트)' 카테고리의 다른 글
  • [NHN] Github Action CI/CD
  • [NHN] 최종 과제 시작
하가네
하가네
  • 하가네
    하 렌
    하가네
  • 전체
    오늘
    어제
    • 분류 전체보기 (93)
      • Computer Science (23)
        • 운영체제 (7)
        • 데이터통신 (6)
        • 자료구조 (4)
        • 논리회로 (0)
        • 확률 및 통계 (0)
        • 데이터베이스 (2)
        • AI소프트웨어 (3)
        • 컴퓨터네트워크 (1)
      • Language (0)
        • Java (0)
      • Framework (8)
        • Spring (8)
      • Tips (2)
        • 터미널 명령어 (1)
        • 우분투 명령어 에러 (1)
      • SSA (6)
        • Front (1)
        • Back (4)
        • DB (0)
        • 기획 (1)
      • 우아한테크코스 (0)
        • 7기 프리코스 (0)
      • CNU SW 아카데미 (42)
        • 1주차 (5)
        • 2주차 (5)
        • 3주차 (2)
        • 4주차 (1)
        • 5주차 (3)
        • 6주차 (2)
        • 7주차 (0)
        • 8주차 (1)
        • 9주차 (14)
        • 10주차 (0)
        • 11주차 (1)
        • 12주차 (0)
        • 13주차 (2)
        • 14주차 (2)
        • 15주차(최종 프로젝트) (3)
        • 최종 프로젝트 이후 (1)
      • 모각코 (6)
        • 2023 동계 (6)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.
하가네
[NHN] CI/CD 전체 설정 완전 사이클
상단으로

티스토리툴바