GitHub Actions를 활용한 리포지토리 운영 개선: Reusable workflows와 Composite action 도입 사례
-
2025년 02월 20일
안녕하세요.
Cocone Engineering에서 웹 프론트엔드를 담당하고 있는 O입니다.
우리 팀은 여러 제품에 걸쳐 작업하기 때문에 100개 이상의 Git 리포지토리를 관리하고 있습니다.
작년에 GitLab에서 GitHub로 이전하면서 GitLab CI/CD를 GitHub Actions로 전환했습니다. 이러한 변화로 인해 기존 운영 프로세스를 어떻게 효율화하고 통일할 것인지가 과제가 되었습니다.
이 글에서는 늘어나는 리포지토리를 효율적으로 관리하기 위해 GitHub Actions의 Reusable workflows(재사용 가능한 워크플로우)와 Composite action(복합 액션)을 도입한 배경과 시행착오 과정, 구체적인 예시를 공유하겠습니다.
시작하며
관리 리포지토리가 100개 이상 존재하고, 일부에는 workflow가 설정되어 있었지만, 대부분 복사/붙여넣기 운영 상태였습니다.
또한, 리포지토리마다 미묘하게 다른 설정이 혼재되어 있어 관리와 변경에 시간이 많이 들고, 변경 실수나 운영의 비효율을 초래할 위험이 있었습니다.
그리고 팀 전체에서 통일된 플로우를 쉽게 적용할 수 있는 구조가 없다는 것도 과제였습니다.
이러한 과제를 해결하기 위해 GitHub Actions의 Reusable workflows와 Composite action을 활용하는 방법을 모색했습니다.
시도한 해결책
Workflow의 현황 파악과 통일 포인트 정리
먼저, 기존 운영 상황을 파악하기 위해 팀에서 관리하는 모든 리포지토리에 설정된 workflow를 리스트업했습니다.
그 결과, 다음과 같은 특징이 있었습니다.
- 리포지토리마다 workflow 설정이 미묘하게 다름
- 복사/붙여넣기로 만들어진 workflow가 많고, 여러 다른 복사 출처가 존재
- 빌드부터 S3 배포까지의 기본 플로우는 거의 같지만, 세부적인 차이가 혼재
이러한 과제에 대응하기 위해 각 workflow를 자세히 비교하고, 다음과 같이 정리했습니다.
- 환경별로 다른 설정이나 특정 프로젝트 고유의 처리는 ‘독자적 부분’으로 분류
- 배포나 빌드 등 공통 처리는 ‘재사용 가능 부분’으로 추출
공통화할 수 있는 부분과 독자적 부분을 구분한 후, GitHub Actions의 Reusable workflows와 Composite action을 활용한 Workflow 작성을 진행했습니다.
공통 처리를 일원화하는 Reusable workflows의 활용
Reusable workflows를 활용하여 다음과 같은 과제 해결을 목표로 했습니다.
- 공통화: 빌드나 배포 등 거의 모든 프로젝트에서 공통적으로 필요한 처리를 Reusable workflows로 통일
- 유연성: 환경이나 프로젝트별로 다른 설정은 inputs나 Secrets를 활용하여 동적으로 변경 가능
Reusable workflows의 구조
Reusable workflows는 각 프로젝트가 공통 처리를 ‘호출하는’ 형태로 사용됩니다. 이 구조를 통해 다음이 가능해집니다.
- 재사용성: 공통 처리를 한 번 정의하는 것만으로 여러 리포지토리나 프로젝트에서 사용 가능
- 유지보수 효율 향상: 공통 처리의 변경이 필요한 경우, Reusable workflows를 수정하는 것만으로 관련 프로젝트 전체에 반영 가능
- 보안성 향상: Secrets를 사용하여 배포 대상 환경이나 인증 정보를 안전하게 관리
YAML 예시: 배포용 Reusable workflow
다음은 실제로 사용하고 있는 배포용 workflow의 간략화된 버전입니다. 이 workflow에서는 Node.js 환경 설정, 프로젝트 빌드, AWS S3로의 배포를 수행합니다.
name: Deploy
on:
workflow_call:
inputs:
environment:
description: |
배포 대상 환경(예: production, staging)
type: string
required: true
node-version:
description: |
사용할 Node.js 버전
type: string
required: false
build-command:
description: |
프로젝트 빌드에 사용할 스크립트
`package.json`에 정의된 스크립트에 맞춰 조정
예: `build`, `generate`
type: string
required: false
default: "generate"
s3-sync-command:
description: |
AWS S3에 배포하기 위한 명령어
예: `'./dist s3://my-bucket --delete --exclude "logs/*'`
type: string
required: true
secrets:
aws-role-arn:
description: |
AWS 리소스 접근을 허용하기 위한 역할 ARN(GitHub Secrets로 관리)
required: true
jobs:
deploy:
name: Deploy to ${{ inputs.environment }}
runs-on: ubuntu-22.04
timeout-minutes: 20
permissions:
contents: write
id-token: write
steps:
# Node.js 설정과 의존성 설치
- name: Setup Node.js and Install dependencies
uses: my-org/composite-actions/.github/actions/node-setup-and-dependencies@v1
with:
node-version: ${{ inputs.node-version }}
# 프로젝트 빌드
- name: Build ${{ inputs.environment }}
run: |
set -xeu
npm run ${{ inputs.build-command }}
# AWS S3로 배포
- name: Deploy to ${{ inputs.environment }} [${{ inputs.s3-sync-command }}]
uses: my-org/composite-actions/.github/actions/aws-s3-deploy-conditionally@v1
with:
environment: ${{ inputs.environment }}
aws-role-arn: ${{ secrets.aws-role-arn }}
s3-sync-command: ${{ inputs.s3-sync-command }}
이 Reusable workflow는 다음과 같은 흐름으로 동작합니다.
- 호출하는 workflow에서 필요한 입력(inputs)을 받아 환경과 설정을 동적으로 변경
- Node.js 환경을 설정하고 의존성을 설치
- 빌드 명령어를 실행하여 결과물을 생성
- AWS S3의 지정된 버킷에 결과물을 배포
개별 처리를 효율화하는 Composite action의 활용
Reusable workflows의 보완으로서 세부적인 처리나 특정 프로세스를 Composite action으로 모듈화했습니다.
이를 통해 코드의 재사용성을 높이고, 프로젝트별 세부 조정이 가능해졌습니다.
Composite action의 역할과 이점
- 재사용성 향상: 공통 처리를 하나의 액션으로 묶어 여러 Workflow에서 쉽게 사용 가능
- 유지보수성 향상: 수정이 필요한 경우에도 하나의 Composite action을 수정하는 것만으로 전체에 반영 가능
- 유연성: 프로젝트나 환경에 따른 설정이나 입력을 받아 특정 요구사항에 대응
YAML 예시: Node.js 설정용 composite action
Node.js 설정과 의존성 설치를 수행하는 Composite action의 예시입니다. 이 액션은 Reusable workflows 내의 공통 처리로 사용됩니다.
name: Node.js Setup and Dependency Installation
description: |
지정된 버전과 옵션에 따라 Node.js와 npm을 설정하고 의존성을 설치합니다.
주요 기능:
1. 지정된 Node.js 버전 설치
2. npm 의존성 설치
inputs:
skip-checkout:
description: |
체크아웃 단계를 건너뛸지 여부
required: false
default: "false"
node-version:
description: |
Node.js 버전 지정
required: false
npm-install-command:
description: |
npm 설치 명령어
required: false
default: "ci"
npm-install-options:
description: |
npm 명령어 추가 옵션
예: `--prefer-offline --no-audit`
required: false
runs:
using: composite
steps:
- name: Checkout code
if: ${{ inputs.skip-checkout == 'false' }}
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Install dependencies
run: |
set -xeu
npm ${{ inputs.npm-install-command}} ${{ inputs.npm-install-options }}
shell: bash
YAML 예시: AWS S3 조건부 배포 composite action
AWS S3 버킷에 조건부 배포를 수행하는 Composite action의 예시입니다. 이 액션은 환경 변수와 입력값을 기반으로 배포 처리를 실행하고, 결과를 Summary에 출력합니다.
name: AWS S3 Deploy Conditionally
description: |
조건에 따라 AWS S3 버킷에 파일을 배포하기 위한 Composite action입니다.
inputs:
aws-region:
description: |
AWS 리전 (예: us-east-1, eu-central-1)
required: false
default: "ap-northeast-1"
aws-role-arn:
description: |
AWS 리소스 접근용 역할 ARN (GitHub Secrets 사용)
required: true
environment:
description: |
배포 대상 환경
required: true
s3-sync-command:
description: |
실행할 aws s3 sync 명령어
예: `./dist s3://your-bucket-name/path --delete --exclude "tmp/*"`
required: true
runs:
using: composite
steps:
- name: Setup AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ inputs.aws-role-arn }}
aws-region: ${{ inputs.aws-region }}
- name: Deploy to ${{ inputs.environment }} [aws s3 sync ${{ inputs.s3-sync-command }}] and check return code
run: |
set -xeu
# AWS S3 Sync 명령어를 실행하고, 명령어의 표준 출력과 표준 에러 출력을 모두 캡처
if output=$(aws s3 sync ${{ inputs.s3-sync-command }} 2>&1); then
{
echo "## AWS S3 Sync 성공 🚀"
echo "실행 명령어 \\`aws s3 sync ${{ inputs.s3-sync-command }}\\`"
} >> "$GITHUB_STEP_SUMMARY"
else
ret_code=$?
error_message=$(echo "$output" | tr '\\n' ' ' | sed 's/"/\\\\"/g')
echo "::error title=AWS S3 Sync Failure::Failed with return code $ret_code. Error: $error_message"
{
echo "## AWS S3 Sync 실패 ❌"
echo "실행 명령어 \\`aws s3 sync ${{ inputs.s3-sync-command }}\\`"
echo "에러 출력:"
echo '```'
echo "$output"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
exit $ret_code
fi
shell: bash
Reusable workflows를 사용한 구체적인 예시
Reusable workflows를 활용한 구체적인 예시를 아래에 보여드립니다.
이 예시에서는 수동으로 트리거되는 배포 워크플로우를 정의하고 있습니다.
이 워크플로우는 Reusable workflows를 호출하여 실행하며, 환경별 설정에 따른 유연한 배포를 가능하게 합니다.
YAML 예시: Reusable workflows를 사용한 배포
name: CI/CD
run-name: '${{ github.workflow }} for ${{ github.ref_name }} / ${{ inputs.environment }} [${{ github.event_name }}] [${{ github.sha }}]'
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy'
type: environment
required: true
default: 'prod'
s3-dryrun:
description: 'Dryrun for S3 sync'
type: boolean
required: false
default: false
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.environment }}
cancel-in-progress: true
jobs:
deploy:
uses: my-org/github-reusable-workflows/.github/workflows/deploy.yml@v1
with:
environment: ${{ inputs.environment }}
node-version: 20
build-command: build
s3-sync-command: >-
./dist s3://your-bucket-name/path --delete
${{ inputs.s3-dryrun && '--dryrun' || '' }}
secrets:
aws-role-arn: ${{ secrets.YOUR_AWS_S3_DEPLOY_ROLE_ARN }}
permissions:
contents: write
id-token: write
이 예시의 핵심 포인트
- workflow_dispatch 트리거: 수동 워크플로우 실행을 가능하게 하는 트리거를 사용합니다. 필요한 입력값(
environment나s3-dryrun)을 지정할 수 있습니다. - concurrency: 동일 환경에서의 배포가 중복 실행되지 않도록 설정되어 있습니다.
- Reusable workflows 호출:
jobs.deploy에서 정의한 Reusable workflows(deploy.yml)를 호출하고, 필요한 파라미터를 입력합니다. - s3-sync-command의 동적 설정:
inputs.s3-dryrun에 따라-dryrun옵션을 추가합니다. 이를 통해 안전하게 배포 내용을 확인할 수 있습니다.
검증 결과와 향후 방향
도입 초기의 성과와 가능성
Reusable workflows와 Composite action을 도입하기 시작하면서 몇 가지 방향성과 가능성이 보이기 시작했습니다.
- 통일성 향상: 배포와 빌드 같은 공통 처리를 Reusable workflows로 통일하므로써 각 리포지토리에 산재해 있던 복사/붙여넣기 코드를 줄일 수 있는 가능성 확인
- 모듈화를 통한 유연성 강화: Composite action을 활용하여 Node.js 설정과 AWS 배포 같은 개별 처리를 간결하게 하면서도 프로젝트별 요구사항에 대응 가능
- 적용 범위 확대 가능성: 템플릿 리포지토리를 준비하여 도입을 더욱 간소화할 수 있다는 가능성 확인
향후 과제와 개선 방향
도입 단계에서 드러난 과제와 그 해결을 위한 방향성에 대해 언급하겠습니다.
- 디버깅 작업의 복잡성: Reusable workflows나 Composite action 내부에서 발생하는 에러 조사에 시간과 지식이 필요. 로그 가시화와 에러 정보 수집의 효율화가 과제
- 커스텀 요구사항 대응의 어려움: 특수한 리포지토리의 경우 Reusable workflows나 Composite action을 그대로 적용할 수 없는 경우가 있어, 유연한 커스터마이징 방식이 필요
- 팀 전체로의 확산: 팀 전원이 새로운 플로우를 활용할 수 있도록 교육과 문서화 정비가 계속해서 중요
자동화와 운영 개선을 위한 노력
Reusable workflows와 Composite action의 도입과 함께 다음과 같은 노력을 진행하고 있습니다.
- GitHub 템플릿 리포지토리 활용: 자주 사용하는 workflow를 포함한 템플릿을 준비하여 신규 프로젝트 도입을 효율화
- 사내용 문서 공개: Reusable workflows와 Composite action의 사양과 사용 예시를 GitHub Pages로 공개 중. 이를 통해 개발자가 언제든 참조 가능한 상태 유지
- 릴리스 플로우 자동화: Reusable workflows와 Composite action의 업데이트를 일원화하고 자동 릴리스 프로세스 정비
- 정적 분석 도입: YAML 파일의 작성 실수를 방지하기 위해 yaml-lint와 action-lint 도입
- 테스트 도입 확충: 각 workflow와 action의 동작 확인용 테스트를 추가하여 운영의 안정성 향상
마치며
GitHub Actions의 Reusable workflows와 Composite action을 활용함으로써 효율적인 관리 플로우 구축을 향한 첫걸음을 내딛을 수 있었습니다.
이 글에서 소개한 YAML 예시는 간략화 되어있지만, 실제 운영에서는 더 복잡한 요구사항을 고려하고 있습니다.
현재는 테스트와 정적 분석 도입, 템플릿 리포지토리 활용을 진행하고 있으며 운영을 계속해서 개선하고 있습니다.
앞으로는 더 많은 리포지토리에 적용 범위를 넓혀 더 높은 효율화를 실현해 나갈 예정입니다.
이 글이 비슷한 과제를 안고 있는 분들에게 힌트나 참고가 되길 바랍니다.