최근 새로운 프로젝트를 시작하면서, 유지보수가 필요한 사내 B2C 프론트엔드 레포지토리들을 모노레포 구조로 통합했습니다. 통합 범위에서 제외된 레포지토리도 있었지만, 신규 프로젝트를 포함해 총 3개의 프론트엔드 패키지를 서비스 모노레포에 포함시켰습니다. 장기적으로 사내 프론트엔드 코드의 공유 수준을 높이고 이를 통해 디자인 시스템 등의 플레이트를 수월하게 구축하겠다는 도전적인 목표를 설정하고 작업해봤어요.
모노레포 구축 과정은 크게 도구 선정, 개발 환경 설정, 배포 환경 설정으로 나누어 진행했습니다.
우선, 도구 선정에서는 의존성 관리 도구와 모노레포 관리 도구를 고민했습니다. 여러 선택지를 고려한 끝에, 의존성 관리 도구로는 pnpm
, 모노레포 관리 도구로는 turborepo
를 사용하기로 결정했습니다.
개발 환경 설정 단계에서는 코드 마이그레이션
과 Node 스크립트
, 테스트 코드 동작
확인을 간략하게 진행한 후, 모노레포 구축이 완료된 후 각 패키지별로 보다 면밀히 점검할 계획을 세웠습니다.
가장 고민이 컸던 부분은 배포 환경 설정과 이를 뒷받침할 레포지토리 관리 전략 수립이었는데요. 모노레포에 포함된 각 프로젝트의 개발 및 배포 환경이 일관되지 않아, 개별 배포 환경을 건드리지 않으면서 모노레포의 이점을 살릴 수 있는 구조를 설계하는 것이 목표였습니다.
모노레포에 포함된 각 프로젝트의 개발 및 배포 환경은 다음과 같았습니다. 다행히도 배포 파이프라인들은 모두 Github Actions를 통해 작성된 형태였어요.
No | 개발환경 | 배포환경 |
---|---|---|
1 | React(CRA) | AWS EC2 |
2 | React(CRA) | 사내 로컬서버 |
3 | Next.js | AWS Amplify |
이번 포스트에서는 이러한 개별 배포 환경을 가진 프로젝트들을 모노레포로 통합한 과정을 다루려 합니다. 특히 배포 파이프라인 트리거링과 모노레포 브랜치 관리 전략을 수립하는 과정에서 직면한 문제와 해결책을 중점적으로 소개해보려 합니다.
CI/CD
파이프라인 트리거 설계하기첫 번째 목표는 기존에 작성되어 있던 Workflow
를 수정하지 않은 상태로, 필요할 때만 원하는 패키지를 배포하도록 하는 것이었습니다. 모노레포를 구축한답시고 기존에 있던 각 프로젝트들의 CI, CD 파이프라인을 수정하는 건 과한 작업이라는 생각이 들었어요. 그리고 실제로 그 부분을 기준으로 모노레포 관리와 프로젝트 관리의 관심사가 분리되어야 하기도 하니까요.
각 패키지의 파이프라인을 유지한 채로 모노레포에서도 이를 활용하려면, 당연하게도 패키지별로 배포 트리거가 달라야됩니다. 모두 같은 트리거를 통해 모든 파이프라인이 동작하게 된다면, 매일 신규 코드가 업데이트될 패키지 때문에 업데이트도 되지 않는 패키지가 계속해서 빌드되고 배포되는 엄청난 낭비가 발생할 거라 생각했어요.
저희 회사에서 사용되는 가장 일괄적인 CI/CD
파이프라인의 트리거는 PR의 생성과 병합이에요. 일반적으로 PR이 생성되었을 때 CI가 동작하고, PR이 main에 병합되었을 때 CD가 동작하는 형식입니다.
아무튼 저는 패키지별 트리거를 다르게 구성하되, PR생성 = CI
, PR병합 = CD
라는 공식을 깨고 싶지 않았고, 그걸 위한 전략을 고민해봤습니다.
정리하자면 PR이 생성되고 병합되는 동안, 이 PR이 어떤 패키지를 대상으로 작업한 내용인지 식별하여 그 패키지에 의존하고 있는 파이프라인만 동작하도록 만들어야 합니다. 파이프라인의 타겟 패키지를 식별하는 일이 필요한 것이죠.
타겟 패키지를 식별하는 데에서 두 가지 방법을 생각했어요.
PR 내의 변경사항이 발생한 모든 파일에 대해, 의존하는 패키지들의 파이프라인을 동작
시키는 방식
PR 정보에 식별자를 개발자가 직접 포함시켜 파이프라인 스크립트를 트리거링
하는 방식
저는 첫 번째 방식은 테스트 코드가 충분히 작성되어 있는 형태여야만 원하는 효과를 만들어낼 수 있다고 생각했고, 아직 프로젝트의 사이즈가 크지 않기 때문에 파이프라인의 안정성이 보장되기 위해 들여야 하는 공수가 크지 않기를 원했습니다. 그래서 두 번째 방법을 채택
했어요.
PR 정보로 작업 패키지를 식별하는 방법으로 두 가지를 고려해봤는데, 첫 번째는 "PR의 브랜치 이름을 통해 패키지를 식별"
하는 것이었고, 두 번째는 "PR 이름을 통해 패키지를 식별"
하는 것이었어요.
예상되는 범위 내에서는 하나의 브랜치 작업에서 두 개 이상의 패키지를 업데이트할 일이 없었고, 브랜치 생성 시점인 작업 시작 시점에서 실행시킬 파이프라인을 결정시키고 가야 한다는 점이 엄격함으로 작용할 수 있을 것 같아서 좋다고 느꼈기 때문에 첫 번째 방법을 채택
했습니다.
결론적으로 작업을 시작할 때 작업 대상이 되는 패키지 이름을 브랜치 이름에 포함시키고, 이 브랜치에서 main에 대한 PR이 생성되거나 병합되면, 타겟 패키지에 대한 파이프라인이 동작하도록 만드는 것
을 전략으로 가지게 되었습니다. 이를 위해서 일단 브랜치 이름에 타겟 패키지 명이 포함되어야 했는데, 예를 들면 pacakgeA/add-modal
과 같은 형태인거죠. 그리고 브랜치 명에 브랜치의 목적을 포함시키는 prefix
를 포함시킴으로써, dev/packageA/add-modal
과 같은 형태로 브랜치 명명 규칙을 작성해보았어요.
그리고 PR의 병합 규칙으로, 모든 CI
workflow
가 pass
되고, review
를 통해 approved
인 상태임을 필요하도록 설정해줬어요. 다만 급한 수정 배포 건에 대해서는 hotfix
브랜치를 두어서 빠른 수정이 가능하도록 했습니다. 깃허브 레포지토리 설정에서 Ruleset
을 활용하였어요.
위에서 이야기한 것처럼 메인 브랜치에 대해서 CI
pass
와 review
approved
상태를 필수로 하기 위한 Ruleset
을 지정해준 뒤, 또 하나의 Ruleset
으로 브랜치의 이름에 prefix
로 dev, hotfix, prd
를 포함하지 않으면 원격 저장소에 브랜치를 생성할 수 없도록 설정해주었어요.
이제 우리가 정한 네이밍 규칙을 따르지 않는 브랜치는 생성이 불가능하고, 메인에 대해 PR이 만들어졌을 때 병합을 위해 리뷰를 필요로 하도록 설정되었습니다.
테스트로 아무런 이름의 브랜치를 생성해보면, 브랜치 생성이 불가능하다는 메시지를 확인할 수 있어요. 그리고 dev나 hotfix와 같은 정해진 prefix
를 포함하면 생성이 가능합니다.
CI/CD
전략 구체화하기앞서 설명했던 내용처럼, 브랜치 이름에 파이프라인 타겟 패키지명을 명시하도록 설정했습니다. 이후, 해당 브랜치를 main
에 병합하는 PR이 생성되면 CI
파이프라인이 실행되고, PR이 병합되면 해당 패키지에 대한 CD
파이프라인이 동작하도록 스크립트를 작성했어요. CI
는 병렬적으로 작동하므로, 의존성 문제가 없도록 모든 패키지에 대해 실행되게 했습니다.
또한 저희는 내부 테스트용 페이지를 dev stage
, 엔드유저 배포용 페이지를 prd stage
라고 부르는데, PR 머지를 통해 배포되는 곳은 dev stage
로 제한했어요. 완전 자동화 파이프라인을 통한 배포는 dev
로 충분하기도 하고, 여러 개의 PR을 통한 업데이트가 prd
에는 동시에 반영되어야 할 때가 있으니까요.
prd
배포는 레포지토리에 태그를 생성하고 이를 릴리즈하면 동작하도록 하였어요. dev
배포와 같은 이유로 태그에 신규 배포하려는 패키지의 이름을 prefix
로 명시하도록 설정하는 방향이었습니다. @packageA/v0.0.1
과 같은 형태로요.
마지막으로 hotfix
브랜치의 main
으로의 병합은, dev
배포와 prd
배포를 동시에 하도록 dev
배포 스크립트의 동작과 최신 버전의 태그를 생성하는 동작을 하는 걸 기획했습니다. CI/CD
파이프라인 트리거링 전략을 정리하자면 아래와 같아요.
이제 이걸 토대로 스크립트를 작성할 일만 남았습니다. 일단 Github Actions
를 활용해서 파이프라인 스크립트를 작성했고, .github/workflows
디렉토리 이외의 디렉토리에서는 Workflow
가 제대로 동작하지 않기 때문에 모든 파일들을 .github/workflows
내의 yml파일로 작성하게 되었어요. 각 분류에 따라서 파일들이 정렬되었으면 좋겠어서, 아래 세 가지 형식으로 스크립트 파일 이름을 정해줬습니다.
ci.package_name.yml
: 각 패키지별 ci Workflow
스크립트cd.dev.package_name.yml
: 각 패키지별 dev
cd Workflow
스크립트, 개별 파이프라인 트리거의 경우 cd.dev.trigger.yml
cd.prd.package_name.yml
: 각 패키지별 prd
cd Workflow
스크립트, 개별 파이프라인 트리거의 경우 cd.prd.trigger.yml
CI Workflow
작성CI workflow
는 일반적인 PR의 타겟 패키지 식별자를 굳이 사용하지 않고, 모두 아무런 PR이 생성되었을 때 실행되도록 했어요. 그리고 각 패키지 내에서의 테스트코드와 빌드 스크립트를 기본적으로 포함하도록 작성했습니다. 따라서 지금 당장에는 빌드할 필요가 없는 패키지까지 모두 PR 생성 시에 빌드되기 때문에 PR의 크기와 관계 없이 빌드 타임이 소요되는데요, 이후에 turborepo
의 remote cache
를 활용하여 CI
환경에서의 빌드 캐시를 구현할 예정이에요.
# .github/workflows/ci.integration.yml
name: CI_packageA
# 이 `Workflow`의 이름을 "CI_packageA"로 지정함. `GitHub Actions` 탭에서 이 이름으로 `Workflow`를 확인 가능.
on:
pull_request:
types:
- opened
- synchronize
# 이 `Workflow`는 PR(pull request) 이벤트에서 트리거됨.
# 'opened'는 새로운 PR이 생성되었을 때, 'synchronize'는 PR에 새로운 커밋이 추가될 때 해당 `Workflow`가 실행됨.
jobs:
build:
runs-on: ubuntu-latest
# 'build' 작업(job)을 정의하며, 이 작업은 'ubuntu-latest' 환경에서 실행됨.
steps:
- name: Checkout Main
uses: actions/checkout@v4
# 첫 번째 단계로, 'actions/checkout' 액션을 사용하여 리포지토리의 코드를 체크아웃함.
# 주로 PR의 기본 브랜치인 'main' 브랜치에서 코드를 가져옴.
- name: Setup Pnpm Action
uses: pnpm/action-setup@v4
with:
version: 8.15.6
# 'pnpm/action-setup' 액션을 사용하여 지정된 버전의 PNPM을 설치하고 설정함. 여기서는 PNPM 버전 8.15.6을 사용.
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
# 'actions/setup-node' 액션을 사용하여 Node.js 버전 20을 설정함.
# 'cache: "pnpm"'은 PNPM의 캐시를 활성화하여 종속성 설치 시간을 단축하기 위함.
- name: Install Dependencies
run: |
pnpm install -F packageA
# 'packageA'의 종속성을 설치함. `-F packageA`는 monorepo에서 'packageA' 패키지의 종속성만 설치하는 플래그.
- name: Run Build
run: |
pnpm build -F packageA
# 'packageA'의 빌드를 실행함. monorepo 환경에서 'packageA'만 빌드.
run-vitest:
runs-on: ubuntu-latest
# 'run-vitest' 작업(job)을 정의하며, 이 작업도 'ubuntu-latest' 환경에서 실행됨.
steps:
- name: Checkout Main
uses: actions/checkout@v4
# 'run-vitest' 작업에서 다시 리포지토리의 코드를 체크아웃함.
- name: Setup Pnpm Action
uses: pnpm/action-setup@v4
with:
version: 8.15.6
# 'pnpm/action-setup' 액션을 사용하여 PNPM 설정. 'build' 작업과 동일하게 PNPM 버전 8.15.6을 사용함.
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
# 'actions/setup-node' 액션을 사용하여 Node.js 버전 20 설정. PNPM 캐시도 활성화됨.
- name: Install Dependencies
run: |
pnpm install -F packageA
# 'packageA'의 종속성 설치.
- name: Run Vitest
run: |
pnpm test -F packageA
# 'packageA'의 테스트를 Vitest로 실행함.
이런 식으로 각 패키지들의 CI Workflow
를 작성해준 뒤, 새로운 PR을 생성하면 아래와 같이 Workflow
가 잘 동작하는 것을 볼 수 있습니다.
이제 특정 작업에 대한 브랜치를 생성하고, 그 작업 내용을 배포 버전에 반영하기 위한 PR을 생성했을 때 작업 내용에 문제가 없는지를 확인하는 CI 과정에 대한 설정이 완료되었습니다. 다음으로는 실제로 병합된 작업 내용이 어떤 식으로 배포되는지 설정할 차례예요.
dev CD Workflow
작성dev CD Workflow
는, 유저 대상 배포가 아닌 내부 테스트용 배포이기 때문에 패키지에 대한 업데이트가 main에 병합되었을 때 동작해도 크게 상관 없을 것이라 생각했습니다. 그리고 각 패키지의 배포 자체에 대한 스크립트에는 영향을 주지 않으면서, main에 병합된 브랜치의 이름을 가져와서 그 패키지의 배포 스크립트를 동작하도록 하는 방식으로 작성했어요. dev CD Workflow
는 다음 과정을 거쳐 동작하게 됩니다.
dev/packageA/add-modal
)에서 패키지 이름을 추출한다.dev CD Workflow
를 실행한다. (파일 이름 양식은 dev.cd.packageName.yml
)# .github/workflows/cd.dev.trigger.yml
name: CD:DEV-release-trigger
# 이 `Workflow`의 이름을 "CD:DEV-release-trigger"로 지정함. 주로 개발 환경의 패키지 배포 트리거용.
on:
push:
branches:
- main
# 'main' 브랜치로 커밋이 푸시될 때 이 `Workflow`가 실행됨. `main` 브랜치는 direct push가 불가하므로 PR 병합 후의 동작을 의미.
jobs:
dev-release-trigger:
runs-on: ubuntu-latest
# 'dev-release-trigger'라는 이름의 작업(job) 정의. 이 작업은 'ubuntu-latest' 환경에서 실행됨.
steps:
- name: Checkout code
uses: actions/checkout@v4
# 첫 번째 단계로, 'actions/checkout' 액션을 사용하여 리포지토리의 코드를 체크아웃함.
- name: Extract package name from merged branch
env:
MERGED_BRANCH: ${{ github.event.head_commit.message }}
run: |
MERGED_BRANCH=$(echo "${MERGED_BRANCH}" | grep -oP '(?<=from )[^\s]+' | head -n 1)
# 병합된 브랜치의 이름을 PR 커밋 메시지에서 추출.
# 주로 "from branch_name" 형식의 메시지에서 브랜치 이름을 가져옴.
PACKAGE_NAME=$(echo "${MERGED_BRANCH}" | awk -F'/' '{print $3}')
# 추출한 브랜치 이름에서 슬래시(/)로 구분된 세 번째 필드를 패키지 이름으로 사용.
# 예를 들어 'dev/packageA/add-modal' 라는 브랜치 이름에서 'packageA'를 추출.
echo "PACKAGE_NAME=$PACKAGE_NAME" >> $GITHUB_ENV
# 추출한 패키지 이름을 `GitHub Actions`의 환경 변수로 저장하여 이후 단계에서 사용.
- name: Trigger package-specific deployment
uses: actions/github-script@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
script: |
const packageName = process.env.PACKAGE_NAME;
# 이전 단계에서 설정한 환경 변수 PACKAGE_NAME을 가져옴.
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: `cd.dev.${packageName}.yml`,
ref: context.ref,
inputs: {
package_name: packageName,
}
});
# GitHub API를 통해 해당 패키지 이름에 맞는 `Workflow`를 트리거함.
# 'cd.dev.packageName.yml' 이라는 파일을 찾아서 배포 프로세스를 실행.
prd CD Workflow
작성prd cd Workflow
는 새로운 태그가 릴리즈되었을 때 실행됩니다. 지금까지의 관행상 단순히 작업 브랜치를 main에 병합했을 때 실행하도록 하면, 테스트 하지 못한 안정되지 않은 피쳐들이 유저에게 배포될 수 있다고 생각했어요. 따라서 main에 작업 브랜치를 병합하면 dev CD Workflow
가 동작되도록 하여 테스트 환경에 배포하도록 한 뒤, 내부 테스트를 거쳐 정식으로 패키지명과 버전넘버를 명시하여 태그를 릴리즈하면 이를 통해 트리거링되어 개별 패키지의 prd CD Workflow
가 동작하도록 하였습니다. prd CD Workflow
는 다음 과정을 거쳐 동작하게 됩니다.
Workflow
가 실행된다.@packageName/vX.X.X
, e.g. @velogApp/v1.0.33
)에서 패키지 이름과 버전 정보를 추출한다.prd CD Workflow
를 실행한다. (파일 이름 양식은 prd.cd.packageName.yml
)# .github/workflows/cd.prd.trigger.yml
name: CD:PRD-release-trigger
# 이 `Workflow`의 이름을 "CD:`PRD`-release-trigger"로 지정함. 이는 프로덕션 배포 트리거를 의미함.
on:
push:
tags:
- "@*/v*.*.*"
# 특정 형식의 태그가 푸시될 때 이 `Workflow`가 실행됨.
# 태그 형식은 '@패키지명/vX.Y.Z'로 되어 있어야 하며, 주로 '@packageName/v1.0.0'과 같은 형식.
jobs:
prd-release-trigger:
runs-on: ubuntu-latest
# 'prd-release-trigger' 작업(job)을 정의하며, 이 작업은 'ubuntu-latest' 환경에서 실행됨.
steps:
- name: Checkout code
uses: actions/checkout@v3
# 첫 번째 단계로, 'actions/checkout' 액션을 사용하여 리포지토리의 코드를 체크아웃함.
- name: Log tag information
run: |
TAG_NAME=${GITHUB_REF#refs/tags/}
# 푸시된 태그의 전체 경로에서 'refs/tags/' 부분을 제거하여 순수한 태그 이름만 추출.
PACKAGE_NAME=${TAG_NAME%%/*}
PACKAGE_NAME=${PACKAGE_NAME#@}
# 태그에서 패키지 이름을 추출. 태그는 '@패키지명/버전' 형식이므로 '@' 뒤에 오는 패키지 이름을 추출.
# 예: '@packageA/v1.0.0' 태그에서 'packageA'를 추출.
echo "PACKAGE_NAME=$PACKAGE_NAME" >> $GITHUB_ENV
echo "VERSION=$VERSION" >> $GITHUB_ENV
# 추출한 패키지 이름과 버전을 `GitHub Actions`의 환경 변수로 저장하여 이후 단계에서 사용.
- name: Trigger package-specific deployment
uses: actions/github-script@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
script: |
const packageName = process.env.PACKAGE_NAME;
const version = process.env.VERSION;
# 이전 단계에서 설정한 환경 변수 PACKAGE_NAME과 VERSION을 가져옴.
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: `cd.prd.${packageName}.yml`,
ref: context.ref,
inputs: {
package_name: packageName,
version: version
}
});
# GitHub API를 통해 해당 패키지 이름에 맞는 배포 `Workflow`(`cd.`prd`.packageName.yml`)를 트리거함.
# 예: 'cd.`prd`.packageA.yml'이라는 `Workflow` 파일을 찾아서 배포를 실행.
Workflow
작성위에서 작성된 트리거를 통해서 각 패키지가 배포되는 형태이기 때문에, 기존의 패키지별로 작성되어 있던 Workflow
내의 트리거 부분을 수정해줄 필요가 있습니다. 개별 패키지의 Workflow
를 일부 수정해야한다는 점은 슬프지만, PR을 아무렇게나 날려도 전부 동작되도록 내버려두는 것보단 나을테지요.
# .github/workflows/cd.dev.packageName.yml
name: CD:DEV:packageName
on:
workflow_dispatch:
inputs:
package_name:
required: true
type: string
jobs:
...
위와 같이 workflow dispatch
로 트리거링되어서 동작하는 Workflow
라는 것을 파일 내에 명시하고, 이후에는 각 패키지별 배포 환경에 맞는 Workflow
를 작성해주면 됩니다.
글 초반부에서 말씀드렸던 것처럼 제가 통합하게 된 3개의 프론트엔드 레포지토리는 모두 다른 환경에서 배포되고 있었기 때문에, 각 배포 환경에 맞춰서 패키지별로 Workflow
를 작성해줘야 했는데요. 위에서 언급한 트리거 부분만 제외하고는 그대로 기존에 사용하던 Workflow
파일을 그대로 가져다 썼습니다.
VM
배포 환경의 경우AWS EC2
를 사용하는 프로젝트나, 사내 내부망에 배포하고 있는 프로젝트에서는 환경변수 주입과 빌드까지 전부 Github Actions
환경에서 마무리하고, Github Actions
이 자동으로 업로드한 빌드 산출물을 VM
에서 다운로드하여 배포하는 방식으로 구축했습니다. 이를 위해서 Github Artifacts
를 활용했어요.
# .github/workflows/cd.dev.packageName.yml
name: CD:DEV:packageName
on:
workflow_dispatch:
inputs:
package_name:
required: true
type: string
jobs:
deploy:
...
- name: Upload Build to Artifact
uses: actions/upload-artifact@v3
with:
name: DEV_build_${{ github.event.inputs.package_name }}
path: apps/packageName/build
dev/packageName/add-feature
브랜치를 통한 작업내용이 main에 병합되면, 앞서 작성했던 트리거 Workflow
를 통해 위의 Workflow
가 동작합니다. 그리고 artifact
로 빌드 산출물을 업로드합니다. 이제 VM의 배포 환경에서 아래와 같이 artifact
다운로드 스크립트를 작성해주었어요.
#!/bin/bash
GITHUB_TOKEN=
ORGANIZATION=
REPOSITORY=
ARTIFACT_NAME=
BUILD_DIR=
RESPONSE=$(curl -L -H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/$ORGANIZATION/$REPOSITORY/actions/artifacts)
ARTIFACT_ID=$(echo "$RESPONSE" | jq -r '.artifacts[0].id')
echo "artifact id: $ARTIFACT_ID"
mkdir -p $BUILD_DIR
echo "Downloading artifact..."
curl -L -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$ORGANIZATION/$REPOSITORY/actions/artifacts/$ARTIFACT_ID/zip" \
-o $ARTIFACT_NAME.zip
echo "Extracting artifact..."
rm -rf $BUILD_DIR
unzip $ARTIFACT_NAME.zip -d $BUILD_DIR
rm $ARTIFACT_NAME.zip
...
빌드 산출물이 업로드되어있는 artifact
를 다운로드 한 뒤에, 압축을 해제하고 배포하는 과정을 포함하고 있는 shell
스크립트인데요, 실제 배포 부분은 제외한 artifact
다운로드를 위한 스크립트입니다.
위의 스크립트들을 통해서 배포 트리거 동작 후 배포 산출물의 artifact
업로드와, VM 환경에서의 artifact
다운로드가 가능해졌습니다. 남아있는 숙제는 Github Actions
에서 빌드 산출물을 업로드 완료하고 나면, 자동으로 VM SSH
에 접근하여 artifact
를 다운로드하고 그 산출물에 대한 배포를 구성하도록 하는 것입니다.
저 같은 경우 이를 위해서는 사내 배포를 위한 스크립트였기 때문에, SSH 접근을 Github Actions
에서 가능하도록 네트워크 환경을 설정해야했고 이 설정은 제 권한 밖의 일이라고 판단하여 아티팩트 다운로드 스크립트를 수동으로 직접 작동시키기로 하였습니다.
AWS Amplify
배포 환경의 경우그리고 이번에 새로 시작하게 된 프로젝트는 AWS Amplify
로 배포를 하기로 했었는데요. AWS Amplify
는 기본적으로 특정 브랜치를 subscribe
하는 방식인데, subscribe
한 브랜치에 새로운 push
가 발생할 경우 자동으로 새로 빌드와 배포를 하도록 설정되어있습니다.
이러한 AWS Amplify
의 설정을 최대한 건드리지 않고 CD Workflow
를 작성해주었는데요,
# .github/workflows/cd.prd.packageName.yml
name: CD:PRD:packageName
on:
workflow_dispatch:
inputs:
package_name:
required: true
type: string
version:
required: true
type: string
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@main
with:
fetch-depth: 0
ref: main
- name: Fetch all branches
run: git fetch --all
- name: Merge main into prd/packageName
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git checkout prd/packageName || git checkout -b prd/packageName origin/main
git merge main
- name: Push changes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: git push origin prd/packageName
저는 AWS Amplify
가 main 브랜치를 dev
도메인으로 자동배포하도록 subscribe
시키고, prd 배포용 브랜치를 따로 만들어서 저희 prd
배포 트리거가 동작하면 main을 pull
하도록 Workflow
를 작성해준 뒤 prd
도메인으로 prd
브랜치를 subscribe
하도록 구성해주었어요.
이런 식으로 구성할 경우 특히 dev
도메인에 대한 배포 Workflow
를 작성하지 않아도 되었습니다. 다만 문제는 배포 성공 여부를 Github Actions
에서 직접 확인할 수 없다는 점이었어요. 자동 배포를 disable하고 직접 배포 트리거를 Github Actions
에서 걸어주는 방식을 통해서 이를 해결할 수 있을 것으로 예상되지만, 당장에는 그 정도의 설정이 필요하지 않을 것으로 생각되었어요. 단순 트리거를 걸어주는 게 아니라 배포 결과 자체를 Github Actions
에서 response
로 받아올 수 있어야 하기 때문에, 이를 위한 스크립트 작성 자체에 난이도가 꽤 있을 것으로 예상했습니다.
지금은 무사히 모노레포로의 통합을 마쳐서, 신규 프로젝트 작업에서도 개발과 배포에 전혀 불편함이 없도록 설정이 완료된 상태입니다. 앞으로도 이런 환경 설정과 관련된 불편한 내용들이 있다면, 개발 과정에서 모아놨다가 여유 있을 때 일괄적으로 설정하는 식으로 점차 헤처 나가면 좋을 것 같네요.
어쩌면 제 욕심에서 시작한 작업이라, 회사에서 백로그로 가져가면서 작업에 무리가 가지 않을 정도의 환경을 구축해봤어요. 글 중에서 언급한 것만 하더라도 두 세 가지는 되는 여러 개선점들이 남아있는 것 같습니다. 이렇게 기록하는 것만으로도 그 때 어떻게 했으면 더 좋았을텐데...하는 생각이 드네요.
모노레포 환경에서 작업해본 경험이 없는 상태에서 혼자 구축해본 내용이라 부족한 점이 많습니다.
글 내용에 대한 피드백, 형식에 대한 피드백 모두 환경합니다. 더 고도화된 환경을 구축해보신 분이 있다면 팁을 주셔도 너무 감사할 것 같아요. 댓글에 편하게 남겨주세요!
다음에는 turborepo
의 remote cache
를 활용한 모노레포 CI/CD
고도화를 작업해보고, 그 내용을 적어보려고 합니다! 무사히 잘 마치고 글에 담을 수 있게 되면 좋겠어요 🫠... 긴 글 읽어주셔서 감사합니다.