[CI/CD 구축] GithubAction + Docker + Nginx 무중단 배포 2

Elmo·2024년 8월 21일
1

이제 본격적으로 CI/CD를 구축해 봅시당

스프링부트 health check api 구현

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-actuator'

application.yml

# health-check
management:
  endpoints:
    web:
      exposure:
        include: health

SecurityConfig.java

.requestMatchers("/actuator/health").permitAll() // API

프로젝트에서 스프링 시큐리티를 사용 중이라 해당 path로의 요청을 사용자 검증하지 않고 접근 가능하도록 추가했습니다.

GET http://localhost:8080/actuator/health

다음과 같이 서버 상태를 확인할 수 있습니다.

도커 파일 설정

스프링부트 루트 디렉토리에 다음 파일을 추가해주세요.
DockerFile

FROM openjdk:17

ARG JAR_FILE_PATH=build/libs/*.jar

WORKDIR /apps

COPY $JAR_FILE_PATH app.jar

CMD ["java", "--enable-preview" ,"-jar", "app.jar"]

EC2에 접속해서 /home/ubuntu에 다음 파일들을 추가해주세요.

docker-compose.blue.yml

version: '3.1'

services:
  redis-server:
    image: redis
    container_name: redis-server
    command: redis-server --port 6379
    hostname: redis
    ports:
      - "6379:6379"
  api:
	image: 도커 허브 사용자이름/도커 허브 레포지토리 이름:latest
	container_name: blue
	environment:
	  - LANG=ko_KR.UTF-8
	  - HTTP_PORT=8080
	ports:
	  - '8080:8080'
    depends_on:
      - redis-server

docker-compose.green.yml

version: '3.1'

services:
  redis-server:
    image: redis
    container_name: redis-server
    command: redis-server --port 6379
    hostname: redis
    ports:
      - "6379:6379"
  api:
	  image: 도커 허브 사용자이름/도커 허브 레포지토리 이름:latest
	  container_name: green
	  environment:
		- LANG=ko_KR.UTF-8
		- HTTP_PORT=8081
	  ports:
		- '8081:8080'
      depends_on:
      - redis-server

Redis 서버도 ec2에서 직접 설치하지 않고 도커 컨테이너로 같이 올렸습니다. 여기서 삽질했던게 Redis 서버는 따로 다른 yml을 작성해서 컨테이너를 올렸더니 스프링부트 서버 컨테이너와 네트워크가 달라서 redis 호스트 이름을 인식을 못하더라구용.. 같은 Yml안에서 작성해줘야 동일한 네트워크로 할당됩니다.

이때 꼭 application.yml에서 redis 호스트 이름을 위에 docker-compose.yml에서 적은 호스트 이름으로 바꿔줘야 합니다!

deploy.sh

#!/bin/bash

# 환경 변수 읽기
IS_GREEN=$(docker ps -q -f "name=green")
IS_BLUE=$(docker ps -q -f "name=blue")

# 기존 컨테이너 종료 및 제거
if [ -n "$IS_GREEN" ]; then
    echo "Stopping and removing green container"
    sudo docker-compose -p green -f /home/ubuntu/docker-compose.green.yml down
fi

if [ -n "$IS_BLUE" ]; then
    echo "Stopping and removing blue container"
    sudo docker-compose -p blue -f /home/ubuntu/docker-compose.blue.yml down
fi

# 컨테이너 실행
if [ -z "$IS_BLUE" ]; then
		echo "Running blue container"
    sudo docker-compose -p blue -f /home/ubuntu/docker-compose.blue.yml up -d
    BEFORE_COLOR="green"
    AFTER_COLOR="blue"
    BEFORE_PORT=8081
    AFTER_PORT=8080
else
    echo "Running green container"
    sudo docker-compose -p green -f /home/ubuntu/docker-compose.green.yml up -d
    BEFORE_COLOR="blue"
    AFTER_COLOR="green"
    BEFORE_PORT=8080
    AFTER_PORT=8081
fi

echo "${AFTER_COLOR} server up(port:${AFTER_PORT})"

# 서버 상태 확인
for cnt in {1..10}
do
    echo "Checking response from server(${cnt}/10)"
    UP=$(curl -s http://127.0.0.1:${AFTER_PORT}/actuator/health)
    if [[ "$UP" == *'"status":"UP"'* ]]; then
        sleep 10
    else
        break
    fi
done

if [ $cnt -eq 10 ]; then
    echo "Server Error"
    exit 1
fi

# Nginx 설정 업데이트
sudo sed -i "s/${BEFORE_PORT}/${AFTER_PORT}/" /etc/nginx/conf.d/service-url.inc
sudo nginx -s reload
echo "Deploy Completed!!"

Nginx 무중단 배포 설정

sudo vi /etc/nginx/sites-available/default

server {
    include /etc/nginx/conf.d/service-url.inc;

    listen 80 default_server;
    listen [::]:80 default_server;

    root /var/www/html;

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

    server_name _;

    # Proxy all other requests to Spring Boot
    location / {
        proxy_pass $service_url;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }

    # Actuator health endpoint
    location /actuator/health {
        proxy_pass $service_url;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }
}

sudo vi /etc/nginx/conf.d/service-url.inc

set $service_url http://127.0.0.1:8080;

깃허브 액션 WorkFlow

name: footballGG cicd

on:
  workflow_dispatch:
  push:
    branches: 
      - main

jobs:
  docker-build-and-push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'
          
      - name: Gradle Caching
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Set environment values
        run: |
          cd ./src/main/resources # resources 폴더로 이동
          touch ./env.properties # env.properties 파일 생성
          echo "${{ secrets.ENV_VARS }}" > ./env.properties 
        shell: bash

      - name: Gradlew auth setting
        run: chmod +x ./gradlew
        
      - name: Jar File build
        run: ./gradlew bootJar
        
      - name: Docker Image build & Push to DockerHub
        run: |
          docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_PASSWORD }}
          docker build -f Dockerfile -t ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }} .
          docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}
          
      - name: Deploy to EC2
        uses: appleboy/ssh-action@master
        with:
          key: ${{ secrets.EC2_PRIVATE_KEY }}
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }} 
          script: |
            cd /home/ubuntu
            sudo chmod +x deploy.sh
            sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}
            ./deploy.sh

env.properties 부분 때문에 엄청 고생했습니다..제가 바보같이 깃허브 시크릿에 등록해놓고 자꾸 docker compose를 실행할 때 .env파일과 함께 실행되도록만 설정한 것입니다. 막상 스프링부트를 실행하면 env.properties라는 파일이 없다고 에러가 발생했습니다.

docker compose 실행시 --envfiles ./env 라는 명령어와 함께 실행하는 것은 docker-compose.yml에서 환경변수를 사용했을 때 필요합니다. 저는 보다시피 docker-compose.yml에서 사용한 것이 아니라 스프링부트 내의 application.yml에서 환경변수를 사용했기 때문에 직접 env.properties 파일을 깃허브 시크릿에서 복사하여 붙여넣는 과정이 필요했습니다.

항상 nginx 무중단 배포를 구현할 때마다 밤새면서 삽질하는 것 같네요 ㅠ 물론 무중단 배포를 구현안하면 github Action이 매우 간단하고 편리해서 금방 cicd를 구축할 수 있었습니다.

하지만 다른 프로젝트에서 무중단 배포를 구축해놓으니깐 과정은 힘들어도 추후 프로젝트를 진행할 때 매우 편했어요! 여러분들은 이 글 참고해서 삽질안하고 빠르게 구축하시길 바랍니당

profile
엘모는 즐거워

0개의 댓글

관련 채용 정보