개발자로 후회없는 삶 살기

[운영] 개발, 운영, QA 환경에 배포 방법 본문

[Infra]/[GitHub Actions]

[운영] 개발, 운영, QA 환경에 배포 방법

몽이장쥰 2025. 2. 6. 22:30

서론

※ 아래 내용을 다룹니다.

  • 개발 환경 CICD
  • 개발 + 운영 환경 CICD
  • 개발 + 운영 + QA 환경 CICD

 

본론

- 다양한 배포 시나리오

1) 로컬 작업 내용을 개발 환경에 배포
2) 로컬 작업 내용을 개발 환경에 배포하고 릴리즈 브랜치를 간접적으로 사용하여 운영 환경에 배포
3) 로컬 작업 내용을 개발, QA, 운영 환경에 배포
4) 로컬 작업 내용을 개발, QA, 스테이징 환경에 배포하고 승인 시 운영 환경에 배포

다수의 인원이 하나의 프로젝트에 참여하여 개발을 진행할 때, git flow 방식을 사용하면 위와 같은 시나리오로 개발과 테스트, 배포를 하게 된다. 2번 내용에서 릴리즈 브랜치를 두는 이유는 아래에서 알아보자.

 

-> CICD 프로세스

1) CI : 변경된 코드의 테스트 코드가 성공하는지 확인하고 빌드 테스트를 수행
2) CD : CI 성공 시 이미지를 빌드하고 배포 환경에 배포

cicd를 통해 빌드와 배포를 자동화할 수 있으며 보통 위와 같은 방식으로 진행하고 배포 환경이나 os 개발 언어에 따라서 달라질 수 있다.

 

+ CICD를 하는 이유

1) CI

변경된 코드를 테스트 없이 메인 코드에 병합하면 오류를 유발할 수 있다. 테스트 코드는 잘 작성되었는지, 빌드는 정상적으로 되는지 확인하여 병합 전에 변경된 코드에 문제가 없는지, 병합해도 되는지 확인한다. pre commit 같은 컨벤션과 시큐어 코딩 여부도 확인할 수 있다.

 

2) CD

검증이 완료된 코드를 빠르게 배포하여 사용자가 불편함을 느끼지 않도록 한다. CD를 수동으로 하면 서비스를 업데이트하는데 오랜 시간이 소요된다.

 

-> CICD 요소

1) 도커 : 이미지 빌드 도구
2) AWS ECR : AWS에서 도커 이미지를 저장하는 레포지토리로 앱을 이미지 화 한 후 푸시하면 ECR에서 이를 관리한다.
3) AWS EKS : 쿠버네티스 관리 도구로, ECR에 있는 이미지를 로드하여 쿠버네티스 POD 내부에서 실행한다.

배포는 앱을 이미지로 빌드하고 ECR에 저장한 후 EKS에서 이미지를 가져와서 POD 내부에서 실행시키는 형태로 동작한다. 워크플로우를 실행하면 모든 것이 자동화되기 때문에 개발자는 개발과 배포만 신경 쓰면 쿠버네티스 관리는 EKS가 알아서 해준다.

 

- 시나리오 1

어플을 개발 환경의 쿠버네티스에 배포하는 cicd를 구성한다. 로컬이 아닌 모두가 공유하는 develop 환경이다. 피쳐 브랜치에서 dev 브랜치로 pr을 생성, 동기화하면 ci를 하고 머지되면 dev 환경에 배포한다.

 

-> 요구사항

1) my-app 경로의 변경 사항만을 액션 실행으로 본다.
2) dev 브랜치로 pr이 생성되거나 동기화 될 때 CI를 실행
3) PR이 머지되면 이미지 빌드 후 성공하면 개발 환경에 배포하는 CD 실행
4) 배포 성공 여부를 슬랙으로 전송

 

-> 트리거 구성

1) PR에서만 액션이 트리거 되도록 하기 위해서 PR 이벤트
2) 특정 디렉토리의 변경만 감지하기 위해서 Path 필터링
3) 특정 브랜치에 PR만 감지하기 위해서 브랜치 필터링

필터링을 통해서 원하는 파일, 원하는 브랜치에서의 변경, PR만 감지하고 트리거할 수 있다.

 

-> 잡 구성

CICD를 위한 잡은 어떻게 구성되어야 할까? 새로운 변경 사항의 빌드 성공 여부를 테스트하고 개발 환경에 변경 사항을 배포해야 한다.

 

1) 테스트 : 메인 코드와 변경 사항을 통합한 코드가 정상적으로 빌드되는지 빌드 테스트
2) 이미지 빌드 : CI 성공 이후 CD를 시작하는 부분으로, 앱을 도커로 이미지 빌드하고 ECR에 푸시
3) 배포 : 이미지 빌드가 성공되면 순차적으로 실행되기 위해서 needs 키워드를 사용하고 ECR의 이미지를 로드하여 쿠버네티스에 배포

 

워크플로우에 작성된 민감 정보들은 시크릿과 환경 변수를 사용하여 관리하면 된다. AWS는 ECR을 제공하여 도커 허브에 푸시되지 않는다. 만약 도커 허브에 푸시를 하게 되면 레포지토리를 private로 해야 깃허브 시크릿을 적용할 수 있고 public으로 한다면 따로 도커 시크릿을 적용해야 한다.

 

- 워크플로우 실행 결과

-> 예상 결과

이미지 빌드 : ECR에 커밋 ID를 이름으로 이미지가 푸시된다.
배포 : 푸시된 이미지를 사용하여 쿠버네티스 클러스터를 생성하고 파드에 배포하여 앱을 실행

 

실제 실행 결과를 보면 AWS ECR에 이미지가 빌드된 것을 확인할 수 있다.

 

배포 내역은 EKS가 과금이 발생하기 때문에 확인하지 않았다. AWS에 EKS와 EC2에 실행 중인 클러스터와 서버 개수를 확인하면 배포 성공 여부를 확인할 수 있다.

 

 

- 시나리오 2

dev 브랜치를 개발 환경, 마스터 브랜치를 운영 환경으로 사용할 때 개발 환경과 운영 환경의 cicd를 구성하고 싶다.

 

-> 요구사항

dev 브랜치에 머지가 발생하면 동일한 스토리를 가진 릴리즈 브랜치를 생성하고 릴리즈 브랜치에서 마스터 브랜치로 PR을 생성하면 CI가 발생하고 머지하면 운영 환경에 배포된다. 릴리즈 브랜치는 중복되지 않는 고유 ID 이름을 가져야 한다.

 

🚨 왜 릴리즈 브랜치를 사용하지?

dev 브랜치에서 마스터 브랜치로 PR를 쏘면 될 것 같은데, 릴리즈를 브랜치를 따로 사용하는 이유를 알아보자

 

피쳐 1이 dev로 머지를 하고 dev에서 마스터로 pr을 생성했다. 이때 피쳐 2도 dev에 머지를 하면 마스터로 생성된 PR에 피쳐 2의 커밋이 동기화된다. 

 

피쳐 1 -> 운영 환경에 배포 예정
피쳐 2 -> 개발 환경에 배포 예정, 운영 환경에 배포하면 안됨

이때 위와 같은 상황이었다면 운영 환경에 오류가 발생할 수 있다.

 

따라서 릴리즈 브랜치를 사용하여 dev와 마스터는 간접적으로 연결하는 것이 일반적이다. 피쳐 1의 머지는 릴리즈 1 브랜치에 등록되고, 피쳐 2의 머지는 릴리즈 2 브랜치에서 처리된다. 피쳐 1의 기능만 머지하면 운영 환경에는 피쳐 2의 기능이 머지되지 않는다. 이렇게 하여, dev와 마스터를 직접적으로 연결하면 안 된다.

 

- 트리거 구성

시나리오 1에서 설정한 트리거에 master만 추가하면 된다. 그러면 마스터에 PR이 발생해도 액션이 실행된다는 것인데 if 조건문으로 실행할 잡과 스킵할 잡을 정하면 된다.

 

- 잡 구성

개발과 운영 환경을 다루는 잡을 구성 해보자

 

🚨 개발 환경과 운영 배포에 사용되는 잡이 다른 잡이어야 할까?

환경 자체가 다르고 아예 분리되어야 하니 다른 잡을 구성 해도 괜찮을 것 같다. 하지만 그러면 환경이 추가될 때마다 잡이 추가되어 관리가 힘들어질 것이다.

 

사실, 각 환경마다 잡 구성은 거의 비슷하다. 변수와 시크릿만 다르기 때문에 Environment를 다르게 만들고 변수와 시크릿을 다르게 지정하면 하나의 잡에서 여러 개의 환경을 다룰 수 있다. 개발과 운영에서의 envi를 만들고 변수와 시크릿을 설정해서 잡을 재사용하자.

이전에 이슈 알림처럼 아웃풋을 통해 환경을 받아와서 개발 환경, 운영 환경을 설정하고 그뿐만 아니라 환경이 추가돼도 단일 잡을 재사용할 수 있다.

 

1) set - env

현재 워크플로우가 트리거 되는 환경을 정하는 잡이다. set env 잡에서 설정하는 환경을 따라서 dev면 dev에 맞는 하위 잡이 실행되고 prod면 prod로 설정한 변수와 시크릿으로 잡이 실행될 것이다. 마스터도 동일하다.

 

https://hsb422.tistory.com/entry/%EC%B5%9C%EC%A0%81%ED%99%94-%EC%9C%A0%EC%97%B0%ED%95%98%EA%B3%A0-%ED%99%95%EC%9E%A5-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%9B%8C%ED%81%AC%ED%94%8C%EB%A1%9C%EC%9A%B0-%ED%99%9C%EC%9A%A9%EB%B2%95

 

[최적화] 유연하고 확장 가능한 워크플로우 활용법

🚨 서론 (문제 상황)이슈를 생성했을 때, 특정 키워드 [critical, normal]이 포함되어 있는 경우에만 해당 키워드가 포함된 슬랙 채널에 훅을 날리고 싶은 상황이다. 워크플로우는 위와 같이 작성할

hsb422.tistory.com

위 내용은 블로그에서 찾아볼 수 있다.

 

2) 이미지 빌드, 배포

이 2개의 잡은 시나리오 1의 잡과 유사하고, set env에서 설정한 환경을 environment 변수로 지정하는 것만 하면 된다. 이제는 set env에서 지정한 아웃풋을 사용할 것이므로 needs에 set env를 해야 할 것이다.

 

3) create-pr

개발 환경일 경우 배포가 완료되면 릴리즈 브랜치를 만들고 마스터 브랜치로 pr을 날리는 잡이다. 간접적인 pr을 연결하는 잡이다.

 

1] 체크아웃 : 개발 환경일 경우에만 개발 환경 배포에 성공한 커뮤니티 스토리를 받아올 수 있다.
2] 깃헙 권한 받아오기 : pr 생성을 위한 깃헙 권한 받아오기
3] 릴리즈 브랜치 생성
4] 릴리즈 브랜치에서 마스터 브랜치로 gh cli를 사용해서 pr을 생성

생성된 pr을 머지하면 운영 환경으로 배포된다.

 

✅ 정리

1. 브랜치 필터에 마스터를 추가하여 트리거 구성은 끝!
2. 이미지 빌드, 디플로이 잡이 dev와 prod 같은 아웃풋을 받아올 수 있도록 업데이트
3. 아웃풋을 설정하는 set envi 잡을 만듦
4. 개발환경 배포에 성공하면 릴리즈 브랜치를 만들고 마스터로 pr을 생성

 

- 워크플로우 구성

트리거는 마스터만 추가하면 된다.

 

1. set - envi

  set-environment:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    outputs:
      environment: ${{ steps.set-env.outputs.environment }}
    steps:
    - name: set env
      id: set-env
      run: |
        echo ${{ github.base_ref }}
        echo "environment=dev" >> $GITHUB_OUTPUT

        if [[ ${{ github.base_ref }} == "master" ]]; then
          echo "environment=prod" >> $GITHUB_OUTPUT 
        fi
    - name: check env
      run: echo ${{ steps.set-env.outputs.environment }}

테스트는 PR이 생성되면 항상 실행되지만, 환경 설정은 배포될 때만 이루어지면 된다. 따라서 if 문을 머지로 한다. base_ref는 PR의 대상 브랜치를 의미하며 dev면 현재 목적 병합 환경이 dev로 아웃풋에 저장된다.

 

2. 이미지 빌드와 배포

이미지 빌드와 배포 잡이 envi 아웃풋을 받아오도록 하자. 매트릭스로 아웃풋을 시각화하고 environment에 전달한다. 이렇게 하여 dev면 envi가 dev인 변수와 시크릿을 사용하게 된다. 디플로이 잡도 set envi의 아웃풋을 받아오기 위해서 needs에 포함시킨다. 이렇게 하면 디플로이잡에서도 envi를 사용할 수 있다.

 

✅ 꿀팁

${{ secrets.AWS_ROLE_TO_ASSUME }}

서비스를 운영하면 개발, 운영, 테스트 등 다양한 환경을 사용하게 되고, 각 환경은 다른 변수와 시크릿을 사용한다. 그때 environment를 사용하면 잡 구성은 동일하게 유지하고 다른 변수와 시크릿을 사용할 수 있다. 레포 레벨을 사용하면 동일 변수와 시크릿이 사용된다. 환경마다 다른 것은 env의 변수와 시크릿을 만들고 환경마다 동일한 것은 레포 레벨을 사용하면 된다. env와 레포 레벨에서 중복될 경우 env가 우선순위를 가지지만 env에 없고 레포에만 있는 경우 레포의 변수가 사용된다.

 

3. create-pr

create-pr:
    if: needs.set-environment.outputs.environment == 'dev'
    runs-on: ubuntu-latest
    needs: [set-environment, deploy]
    steps:
    - name: checkout
      uses: actions/checkout@v4
    - name: gh auth login
      run: |
        echo ${{ secrets.PERSONAL_ACCESS_TOKEN }} | gh auth login --with-token 
    - name: create branch
      run: |
        git checkout -b release/${{ github.run_id }}
        git push origin release/${{ github.run_id }}
    - name: create pr
      run: |
        gh pr create --base master --head release/${{ github.run_id }} --title "release/${{ github.run_id }} -> master" --body "release pr"

릴리즈 브랜치를 만들고 마스터 브랜치로 PR을 날리는 잡은 무조건 개발 환경에 배포가 완료된 이후에 실행되어야 하므로 needs에 deploy를 추가한다. 운영 환경에 배포가 성공하면 pr을 생성할 필요가 없다. 그래서 이 잡에서는 현재 환경을 알아야 한다. 또한, 실행 환경이 dev일때만 마스터로 pr을 날리면 되므로 if문을 dev로 한다. pr의 제목은 release/ID 이고 내용은 release pr이다.

 

- 워크플로우 실행 결과

피쳐 -> dev : ci + cd + pr 생성
dev -> master : ci + cd + pr 미생성

피쳐에서 dev로 pr을 생성하고 머지하면 이렇게 2가지가 모두 일어나야 한다.

 

그 결과가 총 4번으로 나타날 것이다.

 

먼저 피쳐에서 dev로 PR을 생성하면 CI가 발동된다.

 

머지하면 CD가 발동되고

 

자동으로 릴리즈 브랜치를 생성한 후 master 브랜치로 PR을 생성한다.

 

PR이 생성되니 자동으로 CI가 실행되고

 

머지하면 CD가 실행되며 배포 이후 PR을 생성되는 것은 DEV 환경에서만 실행되므로 스킵된다.

 

- 시나리오 3

시나리오 2에서 추가로 qa 환경에서 cicd를 구성한다.

 

2와 비슷한데, dev 에서 릴리즈 브랜치를 만드는 것이 아니라 필요한 경우 qa 환경에 배포를 하고 qa 환경에서 운영환경에 배포해야하며, 따라서 qa 환경에서는 CD 프로세스만 진행한다.

 

1) dev 환경에 배포
2) 필요 시 태그를 찍어서 qa 환경에 배포
3) 그 시점과 동일한 커밋을 가지는 릴리즈 브랜치를 생성하고 릴리즈에서 운영 환경으로 pr 생성

개발 환경에 배포를 한 후 v1.0.0 태그를 찍으면 qa 환경에 배포가 되고 qa에 배포를 성공하면 release/v1.0.0 브랜치가 만들어지고 pr을 생성하며, 머지하면 v1.0.0 코드가 운영 환경에 반영된다. 태그를 사용하면 필요 시점에 태그를 생성하여 유연한 배포 관리가 가능하다. 또한 태그는 원하는 특정 커밋에 라벨을 붙이는 방식으로 동작해서 명확하고 구체적인 배포 포인트로 인식될 수 있다.

 

✅ 어떻게 QA 환경에서는 CD만 하도록 하지?

jobs:
  test:
  # 테스트 CI는 PR 액션에서만 발생
    if: github.event.action == 'opened' || github.event.action == 'synchronize'

# CD에 push 요소가 tag인지 확인하는 코드
set-environment:
    if: github.event.pull_request.merged == true || github.ref_type == 'tag'

CI는 PR이 열리거나 동기화 될 때만 발생하는데 태그는 push로 할 수 있다. 따라서 push 이벤트를 만들고 push를 일으킨 요소가 태그인지 확인하는 if 문을 넣으면 태그는 푸시를 발생시켰을 때 CD만 일어나도록 할 수 있다. push 이벤트는 ref_type으로 push를 발생시킨 요소를 찾는데 push는 태그 외에도 브랜치에서 발생할 수 있다.

 

🚨 추가로, 가능한 CICD 프로세스

만약 QA 환경까지는 제외해도 괜찮다고 판단될 경우 dev까지는 동일하고 태그를 찍을 경우 qa 환경에 배포 없이 릴리즈 브랜치를 만들면 될 것이다. CI, 이미지 빌드, 배포 프로세스에서 태그를 제거하고 태그는 create-pr만 수행하면 된다.

 

-> 트리거 구성

시나리오 2의 트리거에서 태그 필터를 추가하고 깃헙 이벤트로 푸시를 추가한다. 태그 이벤트로 액션을 트리거 하려면 푸시를 해야하기 때문이다.

 

-> 잡 구성

set env와 create pr 잡만 수정하면 되고 다른 잡은 동일하다.

 

1. set env

태그를 푸시하면 아웃풋이 qa 환경으로 저장되도록 바꾼다. 

 

  set-environment:
    if: github.event.pull_request.merged == true || github.ref_type == 'tag'
    runs-on: ubuntu-latest
    outputs:
      environment: ${{ steps.set-env.outputs.environment }}
    steps:
    - name: set env
      id: set-env
      run: |
        if [[ ${{ github.ref_type }} == "tag" ]]; then
          echo "environment=qa" >> $GITHUB_OUTPUT
          exit 0
        fi

        if [[ ${{ github.ref_type }} == "branch" ]]; then
          echo "environment=dev" >> $GITHUB_OUTPUT

          if [[ ${{ github.base_ref }} == "master" ]]; then
            echo "environment=prod" >> $GITHUB_OUTPUT 
          fi
        fi
    - name: check env
      run: echo ${{ steps.set-env.outputs.environment }}

기존에는 환경 셋팅을 머지될 때만 하면 됐는데 이제는 태그가 푸시될 때 QA 환경에 배포해야하므로 태그 조건도 추가한다. ref type은 태그인지 브랜치인지 판단한다. 

 

2. create pr

원래는 dev와 마스터 사이에 릴리즈를 추가하는 것이었는데 이제는 검증된 qa 환경의 코드를 운영 환경에 pr하는 역할이다. qa 환경에 성공적으로 배포되면 릴리즈 브랜치가 생기는데 이젠 랜덤값이 아니라 태그 값을 사용하고 중복된 이름의 태그를 만들 수 없어서 중복되지 않는다. 개발 환경 배포에 성공한 다음 qa 배포를 원할 때 태그를 생성하고 qa 배포에 성공했다면 릴리즈 브랜치를 생성하고 릴리즈와 마스터 사이에 pr을 생성하는 것이다.

 

  create-pr:
    if: needs.set-environment.outputs.environment == 'qa'
    runs-on: ubuntu-latest
    needs: [set-environment, deploy]
    steps:
    - name: checkout
      uses: actions/checkout@v4
    - name: gh auth login
      run: |
        echo ${{ secrets.PERSONAL_ACCESS_TOKEN }} | gh auth login --with-token 
    - name: create branch
      run: |
        git checkout -b release/${{ github.ref_name }}
        git push origin release/${{ github.ref_name }}
    - name: create pr
      run: |
        gh pr create --base master --head release/${{ github.ref_name }} --title "release/${{ github.ref_name }} -> master" --body "release pr"

릴리즈 브랜치 이름은 ref_name으로 하면 태그 이름 값이다. v1.0.0로 태그를 찍으면 v1.0.0 브랜치가 만들어진다.

 

- 실행 테스트

피쳐를 dev에 PR을 생성하면 CI 가 발동한다.

 

시나리오 2에선 피쳐를 dev에 머지하면 릴리즈를 만들고 pr을 만들었는데 이젠 dev는 개발 환경에 배포만 하고 멈춘다.

 

git pull origin dev
git tag v1.0.0
git push origin v1.0.0

qa 환경에 배포하기 위해 태그를 찍자.

 

태그 이벤트로 인해서 액션이 트리거되고 set env의 아웃풋이 qa로 설정되어 QA 환경에 이미지 빌드와 배포를 하고 pr을 만든다.

 

마스터로 pr이 생성되어서 자동으로 ci가 되고

 

마스터에서 머지하면 마스터 브랜치에서 운영 환경으로 cd가 된다.

Comments