최근 진행 중인 프로젝트의 CI/CD 파이프라인을 개선하면서, 기존의 단순 배포 방식에서 개발(Dev)과 운영(Prod) 환경을 분리하는 방식으로 변경했다.
이 과정에서 GitHub Actions를 활용하여 자동화된 배포를 구현했다.
이번 글에서는 초기 배포 방식부터 개발·운영 환경을 분리하게 된 과정과 최종적으로 설정한 GitHub Actions 배포 파이프라인을 정리해보려 한다.
처음에는 GitHub Actions에서 간단한 deploy.yml을 작성하여 배포를 자동화했다.
기존 deploy.yml 설정
name: deploy
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'zulu'
- name: make application.yml
run: |
mkdir -p ./src/main/resources
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.APPLICATION }}" >> ./application.yml
shell: bash
- name: Build with Gradle
run: ./gradlew bootJar
- name: web docker build and push
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t ${{ secrets.DOCKER_REPO }}/backend .
docker push ${{ secrets.DOCKER_REPO }}/backend
deploy:
needs:
- build
runs-on: ubuntu-latest
steps:
- name: deploy
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.KEY }}
port: 22
script: |
sudo docker pull ${{ secrets.DOCKER_REPO }}/backend
sudo docker compose down
sudo docker compose up -d
기존 배포 방식의 문제점
- 개발과 운영 환경이 분리되지 않음
- application.yml이 하나뿐이라 운영 환경과 동일한 설정으로 배포됨
- 개발과 운영을 동일한 서버에서 관리하기 어려움
- 배포 브랜치 구분이 없음
- main 브랜치와 개발 브랜치(dev 등)가 동일한 배포 프로세스를 사용
- dev 브랜치에서 테스트하려면 별도의 설정이 필요함
- 운영 서버에서 직접 실행
- 운영 서버에 곧바로 docker pull을 실행하여 위험 부담이 존재
- 여러 환경을 지원하기 어려운 구조
개선된 배포 방식 : 개발/운영 환경 분리
개선 목표
- main 브랜치와 개발 브랜치를 구분하여 서로 다른 서버로 배포
- 환경에 맞는 application.yml을 자동으로 생성
- 운영 서버(api.ABC.com)와 개발 서버(ABC.test)를 분리하여 배포 안정성 강화
개선된 deploy.yml 설정
name: Deploy to Development and Production
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'zulu'
- name: make application.yml
run: |
mkdir -p ./src/main/resources
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.APPLICATION_DEV }}" >> ./application.yml
shell: bash
if: github.ref != 'refs/heads/main'
- name: make application.yml
run: |
mkdir -p ./src/main/resources
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.APPLICATION_PROD }}" >> ./application.yml
shell: bash
if: github.ref == 'refs/heads/main'
- name: Build with Gradle
run: ./gradlew bootJar
- name: Docker Build & Push for Development
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t ${{ secrets.DOCKER_REPO }}/dev_backend .
docker push ${{ secrets.DOCKER_REPO }}/dev_backend
if: github.ref != 'refs/heads/main'
- name: Docker Build & Push for Production
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t ${{ secrets.DOCKER_REPO }}/prod_backend .
docker push ${{ secrets.DOCKER_REPO }}/prod_backend
if: github.ref == 'refs/heads/main'
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy to Development Server
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.HOST_DEV }}
username: ubuntu
key: ${{ secrets.KEY_DEV }}
port: 22
script: |
sudo docker pull ${{ secrets.DOCKER_REPO }}/dev_backend
sudo docker compose down
sudo docker compose up -d
if: github.ref != 'refs/heads/main'
- name: Deploy to Production Server
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.HOST_PROD }}
username: ubuntu
key: ${{ secrets.KEY_PROD }}
port: 22
script: |
sudo docker pull ${{ secrets.DOCKER_REPO }}/prod_backend
sudo docker compose down
sudo docker compose up -d
if: github.ref == 'refs/heads/main'
반복되는 코드를 줄이기 위한 리팩토링
위에서 작성한 deploy.yml을 보면 Deploy to Development Server와 Deploy to Production Server가 거의 동일한 구조로 반복되고 있다.
이렇게 반복되는 코드는 유지보수하기 어렵고 실수할 가능성도 높아진다고 생각했다.
그래서 GitHub Actions의 matrix 전략을 활용하여 코드 중복을 줄이는 방식으로 리팩토링을 했다.
리팩토링 전 문제점
- 개발 서버와 운영 서버 배포 코드가 거의 동일한데, 중복이 많음.
- 같은 로직을 두 번 작성해야 하므로 유지보수 시 실수할 가능성이 큼.
- 불필요한 if 문을 줄일 방법이 필요함.
리팩토링 후 개선된 deploy.yml
name: Deploy to Development and Production
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'zulu'
- name: Make application.yml
run: |
mkdir -p ./src/main/resources
cd ./src/main/resources
touch ./application.yml
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "${{ secrets.APPLICATION_PROD }}" >> ./application.yml
else
echo "${{ secrets.APPLICATION_DEV }}" >> ./application.yml
fi
shell: bash
- name: Build with Gradle
run: ./gradlew bootJar
- name: Docker Build & Push
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
IMAGE_TAG="prod_backend"
else
IMAGE_TAG="dev_backend"
fi
docker build -t ${{ secrets.DOCKER_REPO }}/$IMAGE_TAG .
docker push ${{ secrets.DOCKER_REPO }}/$IMAGE_TAG
deploy:
needs: build
runs-on: ubuntu-latest
strategy:
matrix:
environment: [dev, prod]
steps:
- name: Deploy
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ matrix.environment == 'prod' && secrets.HOST_PROD || secrets.HOST_DEV }}
username: ubuntu
key: ${{ matrix.environment == 'prod' && secrets.KEY_PROD || secrets.KEY_DEV }}
port: 22
script: |
if [ "${{ matrix.environment }}" == "prod" ]; then
IMAGE_TAG="prod_backend"
else
IMAGE_TAG="dev_backend"
fi
sudo docker pull ${{ secrets.DOCKER_REPO }}/$IMAGE_TAG
sudo docker compose down
sudo docker compose up -d
if: |
(matrix.environment == 'prod' && github.ref == 'refs/heads/main') ||
(matrix.environment == 'dev' && github.ref != 'refs/heads/main')
리팩토링 후 개선점
- matrix 전략 사용 → environment: [dev, prod]로 설정하여 하나의 Job에서 개발/운영 배포를 모두 처리할 수 있음.
- host와 key 참조 방식 개선 → && || 연산자로 환경에 따라 secrets 값을 자동 선택.
- 중복 코드 제거 → script 내부에서 환경에 따라 이미지 태그를 선택하도록 변경하여 가독성 향상.
- 유지보수 용이 → 새로운 환경 추가 시 matrix.environment에 값만 추가하면 됨.
이렇게 리팩토링을 마무리하면서, 더 간결하고 유지보수하기 쉬운 CI/CD 파이프라인을 구축할 수 있었다.
이제 개발/운영 환경을 쉽게 배포할 수 있고, 코드 수정 시 실수할 가능성도 줄어들었다!
Matrix란?
matrix는 GitHub Actions에서 여러 환경을 동시에 실행할 수 있도록 도와주는 기능이다.
즉, 같은 작업을 여러 번 실행해야 할 때, 중복을 줄이고 코드의 가독성을 높일 수 있다.
예를 들어, 내 상황처럼 deploy.yml에서 개발(dev)과 운영(prod) 환경에 배포해야 했다면,
기존 방식이라면 같은 steps를 두 번 작성해야 했지만, matrix 전략을 활용하면 한 번만 작성하면 된다.
기본 사용법
strategy:
matrix:
환경변수: [값1, 값2, 값3]
위처럼 정의하면, 해당 환경변수에 대해 각각의 값으로 Job이 실행된다.
예제 (배포 환경을 dev와 prod로 구분)
strategy:
matrix:
environment: [dev, prod]
이렇게 설정하면, GitHub Actions는 environment가 dev일 때 한 번, prod일 때 한 번 총 두 번 실행하게 된다.
즉, 하나의 deploy Job이 두 번 실행되면서 dev와 prod에 맞춰 동작하는 것이다.
'대충 넘어가지 않는 습관을 위한 기록' 카테고리의 다른 글
AWS 해킹 기록.. (0) | 2025.02.12 |
---|---|
CloudFront 적용기 [AWS - S3] (0) | 2025.02.06 |
이미지 업로드 최적화 : 자바 - 코틀린 변환 기록 (0) | 2025.01.23 |
S3 최적화 - 이미지 리사이징 (0) | 2025.01.22 |
S3 최적화 - WebP 변환 및 이미지 압축 적용기 (1) | 2025.01.21 |