Github Action CI/CD 개발/배포 분리 전략 (feat: 브랜치 분리 이유)

2024. 7. 2. 21:58개발 고민

 

목차

  1. branch 분리 구현 이유 및 방식
  2. gradle.yml 파일 전략 변경점

 

branch 분리 구현 이유 및 방식

 

자동화 배포로 인해 원치 않은 자료까지 배포되던 문제 해결을 위한 방법으로 개발 branch와 배포 branch를 분리해 사용하기로 하였다.

 

기존 프로젝트에서 저장 및 배포는 master 브랜치에서 이뤄졌다. 이론상으로 많은 사람들이 배포와 개발을 분리해서 진행하는 것이 옳다는 것을 알았지만 이번 자동화 배포를 왜 그렇게 하는지 절실하게 느끼게 되었다. 

 

1. 문제점 단일 브랜치를 이용 문제

 

단일 브랜치(master)에서 개발/배포를 동시에 진행하면 별도의 pull request 없이 push하는 순간 테스트 후 자동 배포가 된다. 별도 작업 없이 push만하면 배포가되니 편하기는 했지만 하나의 컴퓨터를 이용할 때는 문제가 없었지만 개발이 완료되지 않은 기능을 push해야될 때 문제가 발생하였다. 외부에서 개발을 해야되는 경우 완료되지 않은 기능을 다른 컴퓨터에도 동기화해야되기 때문에 어쩔 수 없이 push를 해야되지만 단일 브랜치의 경우 ci/cd 구현을 해놨기 때문에 실제로 사용하는 프로그램이 망가지는 문제가 발생하였다. 

 

2. 문제점 테스트를 위한 파일 수정 후 재변경

 

단일 브랜치에서 사용할 때 기능 구현 확인을 위해 프로젝트를 실행할 때 yml 파일을 testDB에 연결해 사용하였다. 하지만 재배포과정에서 변경된 환경을 다시 롤백하는데 실수를 해 실제 DB를 초기화를 하는 등의 문제가 발생하였다.

 

gradle.yml 파일 전략 변경점

 

두개의 브랜치를 이용해 ci/cd 배포 전략을 수정했다.

master의 경우 develop에서 수행한 테스트가 성공했을 때 pull request를 통해 병합을 한 경우 해당 파일을 재배포하는 방식 

develop의 경우 로컬환경에서 push된 코드를 테스트하고 성공한 경우 pull reqeust를 대기한다. 

 

name: CI/CD using GitHub Actions & Docker

on:
  push:
    branches:
      - develop
  pull_request:
    types: [closed]

jobs:
  build:
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Grant execute permission for Gradle
        run: chmod +x ./gradlew

      - name: Create application-api.properties
        run: |
          echo "naver-cloud-sms.accessKey=${{ secrets.ACCESS_KEY }}" >> src/main/resources/application-api.properties
          echo "naver-cloud-sms.secretKey=${{ secrets.SECRET_KEY }}" >> src/main/resources/application-api.properties
          echo "naver-cloud-sms.serviceId=${{ secrets.SERVICE_ID }}" >> src/main/resources/application-api.properties
          echo "naver-cloud-sms.senderPhone=${{ secrets.PHONE }}" >> src/main/resources/application-api.properties
          echo "SERVER_URL=${{ secrets.DB_URL }}" >> src/main/resources/application-api.properties
          echo "DB_PASSWORD=${{ secrets.DB_PASSWORD }}" >> src/main/resources/application-api.properties

      - name: Display application-api.properties content for verification
        run: cat src/main/resources/application-api.properties

      - name: Run tests
        run: ./gradlew test

      - name: Build JAR
        run: ./gradlew clean build -x test

      - name: Set artifact
        run: echo "artifact=$(ls ./build/libs)" >> $GITHUB_ENV

      - name: Build Docker image
        run: |
          docker build -t 이미지 이름/버전 .
          echo "DOCKER_IMAGE=이미지 이름/버전" >> $GITHUB_ENV

      - name: Log in to Docker Hub
        run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin

      - name: Push Docker image to Docker Hub
        run: docker push ${{ env.DOCKER_IMAGE }}

  deploy:
    if: ${{ github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master' }}
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Log in to Docker Hub
        run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin

      - name: Set up SSH connection
        uses: webfactory/ssh-agent@v0.5.3
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

      - name: SSH into Synology NAS and deploy
        env:
          NAS_USERNAME: ${{ secrets.NAS_USERNAME }}
          NAS_PASSWORD: ${{ secrets.NAS_PASSWORD }}
          NAS_HOST: ${{ secrets.NAS_HOST }}
          SSH_PORT: ${{ secrets.NAS_SSH_PORT }}
          DOCKER_IMAGE: "이미지 이름/버전"
        run: |
          sshpass -p "$NAS_PASSWORD" ssh -o StrictHostKeyChecking=no -p $SSH_PORT $NAS_USERNAME@$NAS_HOST << EOF
            docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
            docker pull $DOCKER_IMAGE
            docker stop 실행 중인 컨테이너 이름 || true
            docker rm 컨테이너 이미지 이름 || true
            docker run -d --name 컨테이너 이름 \
              -p 8002:8002 ${{ env.DOCKER_IMAGE }}
          EOF