๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Spring Boot/๊ฐœ๋ฐœ ๊ธฐ๋ก

Spring Boot ๋ฌด์ค‘๋‹จ ๋ฐฐํฌํ•˜๊ธฐ (AWS ec2 + Nginx + Github self -hosted runner)

by oliviarla 2024. 3. 11.

๋“ค์–ด๊ฐ€๋ฉฐ

์‹ค๋ฌด์— ์ ์šฉํ•˜๋Š” ํ”„๋กœ์ ํŠธ๊ฐ€ ์•„๋‹Œ ๊ฐ„๋‹จํ•œ ์‚ฌ์ด๋“œ ํ”„๋กœ์ ํŠธ๋ฅผ ๋ฐฐํฌํ•˜๋Š” ๊ฒฝ์šฐ์—๋„ ๋‹ค์–‘ํ•œ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฐํฌ ๋ฐฉ์‹์€ Github Action์„ ์ด์šฉํ•ด ๋ธŒ๋žœ์น˜๊ฐ€ ํ‘ธ์‹œ๋˜๋ฉด ๊ธฐ์กด ํ”„๋กœ๊ทธ๋žจ์„ ์ข…๋ฃŒํ•˜๊ณ  ์ƒˆ๋กœ์šด ํ”„๋กœ๊ทธ๋žจ์„ ๋‹ค์‹œ ๋„์šฐ๋Š” ๋ฐฉ์‹์ผ ๊ฒƒ์ด๋‹ค. ํ•˜์ง€๋งŒ ๊ธฐ์กด ํ”„๋กœ๊ทธ๋žจ์ด ์ข…๋ฃŒ๋˜๊ณ  ์ƒˆ๋กœ์šด ํ”„๋กœ๊ทธ๋žจ์„ ๋„์šฐ๋Š” ์‚ฌ์ด์— ์„œ๋น„์Šค๊ฐ€ ์ค‘๋‹จ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋Ÿฌํ•œ ์ค‘๋‹จ ์—†์ด ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ ๋ฅผ ํ•˜๊ณ ์ž ํ•œ๋‹ค.

 

๋ฌด์ค‘๋‹จ ๋ฐฐํฌ๋ฅผ ์ง„ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด Blue/Green ๋ฐฐํฌ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•œ๋‹ค. Blue/Green ๋ฐฐํฌ ๋ฐฉ์‹์€ ์˜›๋‚  ๋ฒ„์ „์œผ๋กœ ์‹คํ–‰์ค‘์ธ WAS๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด ํ•ด๋‹น WAS๋ฅผ ์ข…๋ฃŒ์‹œํ‚ค์ง€ ์•Š์€ ์ฑ„ ์ƒˆ๋กœ์šด ๋ฒ„์ „์˜ WAS๋ฅผ ๊ตฌ๋™์‹œํ‚จ๋‹ค. ์„ฑ๊ณต์ ์œผ๋กœ ๊ตฌ๋™๋˜์—ˆ๋‹ค๋ฉด Nginx๊ฐ€ ์ƒˆ๋กœ์šด ๋ฒ„์ „์˜ WAS๋ฅผ ๋ฐ”๋ผ๋ณด๊ฒŒ ๋ฆฌ๋กœ๋“œํ•œ๋‹ค. ๋ฆฌ๋กœ๋”ฉ์ด ์„ฑ๊ณตํ•˜๋ฉด ๋ชจ๋“  ํŠธ๋ž˜ํ”ฝ์ด ์ƒˆ๋กœ์šด ๋ฒ„์ „์˜ WAS๋กœ ๋“ค์–ด๊ฐ€๊ฒŒ ๋˜๋ฏ€๋กœ, ์˜ˆ์ „ ๋ฒ„์ „์˜ WAS๋ฅผ ์ข…๋ฃŒ์‹œํ‚ค๋ฉด ๋œ๋‹ค.

 

์ฒ˜์Œ์—๋Š” ํšŒ์‚ฌ์—์„œ๋„ ๊ฒฝํ—˜ํ•ด๋ณธ ์  ์žˆ๋Š” Jenkins๋ฅผ ์‚ฌ์šฉํ•˜๋ ค ํ–ˆ์œผ๋‚˜ jar ํŒŒ์ผ์„ ๋นŒ๋“œํ•˜๋Š” ๊ณผ์ •์—์„œ ์‚ฌ์–‘ ๋ฌธ์ œ(ํ”„๋ฆฌ ํ‹ฐ์–ด ํ™˜๊ฒฝ)๋กœ ์ธํ•ด ec2๊ฐ€ ์ฃฝ์–ด๋ฒ„๋ฆฌ๋Š” ๋ถˆ์ƒ์‚ฌ๊ฐ€ ๋ฐœ์ƒํ•ด.. github action์„ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. ๐Ÿฅฒ

gradlew build์—์„œ ๋ฌดํ•œ๋Œ€๊ธฐ๊ฐ€ ๊ฑธ๋ ค๋ฒ„๋ฆฐ Jenkins..

 

๊ฐ„๋‹จํžˆ ๋ฐฐํฌ ์ˆœ์„œ๋ฅผ ์š”์•ฝํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค. ์•„๋ž˜์—์„œ ํ•˜๋‚˜ํ•˜๋‚˜๊ฐ€ ์–ด๋–ค ๋™์ž‘์„ ํ•˜๋Š”์ง€ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์‚ดํŽด๋ณผ ๊ฒƒ์ด๋‹ค.

  • ec2(ubuntu) ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  • github action workflow๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.
  • ec2 ์ธ์Šคํ„ด์Šค์— Nginx, Github self-hosted runner๋ฅผ ์„ค์น˜ํ•œ๋‹ค.
  • ec2 ์ธ์Šคํ„ด์Šค์— deploy ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

 

EC2 ์ธ์Šคํ„ด์Šค ์ƒ์„ฑํ•˜๊ธฐ

๋‚˜๋Š” Ubuntu ํ™˜๊ฒฝ์ด ์ต์ˆ™ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋ฆฌํ‹ฐ์–ด ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•œ Ubuntu AMI๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด ์Šคํ† ๋ฆฌ์ง€๋ฅผ ์ตœ๋Œ€ 30GiB ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ตœ๋Œ€๋กœ ์„ค์ •ํ•ด๋‘์—ˆ๋‹ค.

8GiB๋กœ ์„ค์ •ํ•˜๋ฉด github runner ํŒŒ์ผ์ด ์€๊ทผ ์ปค์„œ ๊ฝ‰์ฐจ๋ฒ„๋ ค ๊ท€์ฐฎ๊ฒŒ ๋Š˜๋ ค์ฃผ์–ด์•ผ ํ•˜๋Š” ๋ถˆ์ƒ์‚ฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์„ค์ •์€ ์ด์ •๋„๋กœ ๋งˆ๋ฌด๋ฆฌํ•˜๊ณ  ์ธ์Šคํ„ด์Šค๋ฅผ ์‹œ์ž‘ํ•œ๋‹ค.

์ดํ›„, ๋ณด์•ˆ ๊ทธ๋ฃน์˜ ์ธ๋ฐ”์šด๋“œ ๊ทœ์น™์„ ์„ค์ •ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋ ค๋Š” ํฌํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค. 80 ํฌํŠธ๋Š” Nginx์—์„œ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค.

Github Action ํŒŒ์ผ ์ž‘์„ฑํ•˜๊ธฐ

ec2์˜ ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ๋Ÿ‰์„ ์ค„์—ฌ์ค„ Github Action์„ ์ž‘์„ฑํ•ด๋ณด๊ฒ ๋‹ค.

 

๋จผ์ € build-and-upload์™€ deploy๋ผ๋Š” ๋‘ ๊ฐœ์˜ job์œผ๋กœ ๋‚˜๋ˆ„์–ด์ง„๋‹ค.

build-and-upload job์€ github์—์„œ ์•Œ์•„์„œ ์ˆ˜ํ–‰ํ•ด์ฃผ๋Š” ๊ฒƒ์ด๊ณ , deploy job์€ ec2์—์„œ ์ˆ˜ํ–‰ํ•˜๋„๋ก ๊ตฌ์„ฑํ•œ๋‹ค.

  • build-and-upload job์—์„œ๋Š” gradle์œผ๋กœ jarํŒŒ์ผ์„ ๋งŒ๋“ค์–ด github artifact์— ์—…๋กœ๋“œํ•˜๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
  • deploy job์—์„œ๋Š” ec2์—์„œ github artifact๋กœ๋ถ€ํ„ฐ jar ํŒŒ์ผ์„ ๋‹ค์šด๋ฐ›์€ ํ›„ deploy ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰์‹œํ‚ด์œผ๋กœ์จ ๋‚ด๋ถ€์ ์œผ๋กœ ๋ฌด์ค‘๋‹จ๋ฐฐํฌ๊ฐ€ ์ง„ํ–‰๋˜๋„๋ก ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฐฐํฌ๊ฐ€ ์„ฑ๊ณตํ•˜๋ฉด ์Šฌ๋ž™์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๊ฒŒ ๋œ๋‹ค.
name: Backend develop CI/CD

on:
  push:
    branches:
      - develop

permissions:
  contents: read
  
jobs:
  build-and-upload:
    runs-on: ubuntu-22.04

    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
    
    - name: Set up JDK 19
      uses: actions/setup-java@v4
      with:
        distribution: 'corretto'
        java-version: '19'

    - name: Give permission for Gradle
      run: chmod +x gradlew

    - name: Cache Gradle
      id: cache-gradle
      uses: actions/cache@v4
      with:
        path: ~/.gradle/caches
        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
        restore-keys: |
          ${{ runner.os }}-gradle-

    - name: Build with Gradle
      run: ./gradlew :perfume-core:clean :perfume-api:clean :perfume-api:bootJar
        
    - name: Upload jar file to artifact
      uses: actions/upload-artifact@v3
      with:
        name: BackendApplication
        path: perfume-api/build/libs/perfume-api-0.0.1.jar

  deploy:
     runs-on: [ self-hosted, dev ]
     needs: build-and-upload
  
     steps:
      - name: Delete old jar file
        run: rm -rf /home/ubuntu/backend/build/*.jar 

      - name: Download jar file from artifact
        uses: actions/download-artifact@v3 
        with:
          name: BackendApplication
          path: /home/ubuntu/backend/build/

      - name: Deploy Application
        run: sh /home/ubuntu/backend/deploy.sh

      - name: Send Slack Message
        uses: 8398a7/action-slack@v3
        with:
          mention: 'here'
          if_mention: always
          status: ${{ job.status }}
          fields: workflow,job,commit,message,ref,author,took
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        if: always()

Github self-hosted runner ๋“ฑ๋กํ•˜๊ธฐ

ํ”„๋กœ์ ํŠธ์˜ Settings ํƒญ์— ๋“ค์–ด๊ฐ€ Actions-Runners์—์„œ ์ƒˆ๋กœ์šด Runner๋ฅผ ๋งŒ๋“ ๋‹ค. ec2๋Š” Linux ํ™˜๊ฒฝ์ด๋ฏ€๋กœ Runner image๋ฅผ Linux๋กœ ์„ ํƒํ•ด์ฃผ์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์— ์ œ๊ณต๋˜๋Š” ๋ช…๋ น์–ด๋“ค์„ ์ˆœ์ฐจ์ ์œผ๋กœ ec2์—์„œ ์‹คํ–‰ํ•˜๋ฉด ๋œ๋‹ค.

./run.sh ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ nohup ./run.sh & ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ˆ˜ํ–‰ํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

Nginx ์„ค์น˜ํ•˜๊ธฐ

sudo apt-get install nginx ๋ช…๋ น์–ด๋กœ nginx๋ฅผ ์„ค์น˜ํ•œ๋‹ค.

vi /etc/nginx/sites-enabled/default ๋ฅผ ํ†ตํ•ด ์•„๋ž˜์™€ ๊ฐ™์€ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

client_max_body_size 100M;
include conf.d/service-url.inc;
proxy_pass $service_url;

 

๋‹ค ์ž‘์„ฑ๋˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ๋ชจ์–‘์ด ๋  ๊ฒƒ์ด๋‹ค.

server {
	listen 80 default_server;
	listen [::]:80 default_server;

	client_max_body_size 100M;
	include conf.d/service-url.inc;

	root /var/www/html;

	index index.html index.htm index.nginx-debian.html;

	server_name _;

	location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to displaying a 404.
		proxy_pass $service_url;
	}
}

 

vi /etc/nginx/conf.d/service-url.inc ๋ฅผ ํ†ตํ•ด service url์„ ์„ค์ •ํ•ด์ค€๋‹ค. ์ด url์€ ๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ์— ์˜ํ•ด ์œ ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝ๋  ๊ฒƒ์ด๋‹ค.

set $service_url http://127.0.0.1:8089;

 

์ดํ›„ sudo systemctl start nginx ๋ฅผ ํ†ตํ•ด nginx๋ฅผ ์‹คํ–‰์‹œํ‚จ๋‹ค.

deploy ์Šคํฌ๋ฆฝํŠธ ์ž‘์„ฑํ•˜๊ธฐ

์‚ฌ์‹ค ์ƒ ์ด๋ฒˆ ๊ธ€์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ deploy.sh ์— ๋Œ€ํ•ด ์†Œ๊ฐœํ•œ๋‹ค.

ํ† ์ด ํ”„๋กœ์ ํŠธ์ธ ๋งŒํผ ์•ˆ์ •์„ฑ์ด ๋›ฐ์–ด๋‚˜์ง€๋Š” ์•Š์ง€๋งŒ ๊ฐ€์žฅ ์ง๊ด€์ ์ธ ๋ฐฉ๋ฒ•์ด๋‹ค.

 

์Šคํฌ๋ฆฝํŠธ์—์„œ ์ˆ˜ํ–‰๋˜๋Š” ์ž‘์—…์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

1. ์ผ๋‹จ ๋‹ค์–‘ํ•œ ์Šคํ”„๋ง ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์•Œ๋งž๊ฒŒ ์„ค์ •ํ•ด์ค€๋‹ค.

2. ๊ทธ๋ฆฌ๊ณ  nginx์— ์„ค์ •ํ•œ ํ˜„์žฌ ํฌํŠธ๊ฐ€ 8088์ธ์ง€, 8089์ธ์ง€ ํ™•์ธํ•˜์—ฌ ์ƒˆ๋กœ์šด ํ”„๋กœ์„ธ์Šค์˜ ํฌํŠธ๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.

3. ์ •์ƒ์ ์œผ๋กœ ์ƒˆ๋กœ์šด ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋„์›Œ์กŒ๋Š”์ง€ Spring Actuator๋ฅผ ํ†ตํ•ด ํ™•์ธํ•œ๋‹ค.

4. ์ •์ƒ์ ์œผ๋กœ ๊ตฌ๋™์ด ๋˜์—ˆ๋‹ค๋ฉด nginx๋ฅผ ๋ฆฌ๋กœ๋“œํ•˜์—ฌ ์ƒˆ๋กœ์šด ํ”„๋กœ์„ธ์Šค๋ฅผ ๋ฐ”๋ผ๋ณด๊ฒŒํ•˜๊ณ  ๊ธฐ์กด ํ”„๋กœ์„ธ์Šค๋Š” kill ํ•œ๋‹ค.

# 1
# ์Šคํ”„๋ง ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
# export DB_HOST=localhost

# 2
CURRENT_PORT=$(sudo cat /etc/nginx/conf.d/service-url.inc | grep -Po '[0-9]+' | tail -1)
if [ ${CURRENT_PORT} -eq 8089 ]
  then
    echo "๋ธ”๋ฃจ ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰"
    sudo -E nohup java -jar /home/ubuntu/backend/build/perfume-api-0.0.1.jar --spring.profiles.active=dev --server.port=8088 /> /home/ubuntu/backend/backend.log 2>&1 &
    BEFORE_COLOR="green"
    AFTER_COLOR="blue"
    BEFORE_PORT=8089
    AFTER_PORT=8088
elif [ ${CURRENT_PORT}	-eq 8088 ]
  then
    echo "๊ทธ๋ฆฐ ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰"
    sudo -E nohup java -jar /home/ubuntu/backend/build/perfume-api-0.0.1.jar --spring.profiles.active=dev --server.port=8089 /> /home/ubuntu/backend/backend.log 2>&1 &
    BEFORE_COLOR="blue"
    AFTER_COLOR="green"
    BEFORE_PORT=8088
    AFTER_PORT=8089
else
  echo "๋“ฑ๋ก๋œ ํฌํŠธ ์—†์Œ"
  exit 1
fi

# 3
for cnt in $(seq 10)
  do
    echo "์„œ๋ฒ„ ์‘๋‹ต ํ™•์ธ์ค‘(${cnt}/10)";
    if curl -s http://127.0.0.1:${AFTER_PORT}/api/actuator/health > /dev/null
      then
        UP="up"
        break
      else
        UP="down"
    fi
    if [ "${UP}" != "up" ]
      then
        sleep 10
        continue
      else
        break
    fi
done

if [ $cnt -eq 10 ]
  then
    echo "์„œ๋ฒ„ ๊ตฌ๋™ ์‹คํŒจ"
    exit 1
fi

# 4
sudo sed -i "s/${BEFORE_PORT}/${AFTER_PORT}/" /etc/nginx/conf.d/service-url.inc
sudo nginx -s reload
echo "๋ฐฐํฌ ์™„๋ฃŒ"

echo "$BEFORE_COLOR server down(port:${BEFORE_PORT})"
sudo lsof -t -i:${BEFORE_PORT} | xargs -I {} sudo kill -15 {}

 

๋งˆ์น˜๋ฉฐ

์•„์ฃผ ๊ฐ„๋‹จํ•œ ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ๋ฅผ ์™„๋ฃŒํ–ˆ๋‹ค.

์ฒ˜์Œ์— CD๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ํ™˜๊ฒฝ๋ณ€์ˆ˜, Nginx ์„ค์ • ์‹ค์ˆ˜ ๋“ฑ์œผ๋กœ ์ธํ•ด ์•„์ฃผ ์• ๋ฅผ ๋จน์—ˆ๋Š”๋ฐ ์ด๋ ‡๊ฒŒ ์ž˜ ๋™์ž‘ํ•˜๋Š” ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์ถ•ํ•˜๋‹ˆ ํ–‰๋ณตํ•˜๋‹ค ใ… _ใ… 

์•ž์œผ๋กœ Nginx๋ฅผ ์ข€ ๋” ๊ณต๋ถ€ํ•ด์„œ ์–ด๋– ํ•œ ์„ค์ •๋“ค์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ด๋ณด์•„์•ผ๊ฒ ๋‹ค.

 

์ฐธ๊ณ  ์ž๋ฃŒ

https://gnuoyus.tistory.com/96

https://map-befine-official.github.io/github-actions-ci-cd/

๋Œ“๊ธ€