Site icon cocone engineering

GitHub Actions를 활용한 리포지토리 운영 개선: Reusable workflows와 Composite action 도입 사례

안녕하세요.

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를 자세히 비교하고, 다음과 같이 정리했습니다.

 

공통화할 수 있는 부분과 독자적 부분을 구분한 후, GitHub Actions의 Reusable workflows와 Composite action을 활용한 Workflow 작성을 진행했습니다.

공통 처리를 일원화하는 Reusable workflows의 활용

Reusable workflows를 활용하여 다음과 같은 과제 해결을 목표로 했습니다.

 

Reusable workflows의 구조

Reusable workflows는 각 프로젝트가 공통 처리를 ‘호출하는’ 형태로 사용됩니다. 이 구조를 통해 다음이 가능해집니다.

 

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는 다음과 같은 흐름으로 동작합니다.

 

  1. 호출하는 workflow에서 필요한 입력(inputs)을 받아 환경과 설정을 동적으로 변경
  2. Node.js 환경을 설정하고 의존성을 설치
  3. 빌드 명령어를 실행하여 결과물을 생성
  4. AWS S3의 지정된 버킷에 결과물을 배포

개별 처리를 효율화하는 Composite action의 활용

Reusable workflows의 보완으로서 세부적인 처리나 특정 프로세스를 Composite action으로 모듈화했습니다.

이를 통해 코드의 재사용성을 높이고, 프로젝트별 세부 조정이 가능해졌습니다.

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

이 예시의 핵심 포인트

검증 결과와 향후 방향

도입 초기의 성과와 가능성

Reusable workflows와 Composite action을 도입하기 시작하면서 몇 가지 방향성과 가능성이 보이기 시작했습니다.

 

향후 과제와 개선 방향

도입 단계에서 드러난 과제와 그 해결을 위한 방향성에 대해 언급하겠습니다.

 

자동화와 운영 개선을 위한 노력

Reusable workflows와 Composite action의 도입과 함께 다음과 같은 노력을 진행하고 있습니다.

 

마치며

GitHub Actions의 Reusable workflows와 Composite action을 활용함으로써 효율적인 관리 플로우 구축을 향한 첫걸음을 내딛을 수 있었습니다.

이 글에서 소개한 YAML 예시는 간략화 되어있지만, 실제 운영에서는 더 복잡한 요구사항을 고려하고 있습니다.

현재는 테스트와 정적 분석 도입, 템플릿 리포지토리 활용을 진행하고 있으며 운영을 계속해서 개선하고 있습니다.

앞으로는 더 많은 리포지토리에 적용 범위를 넓혀 더 높은 효율화를 실현해 나갈 예정입니다.

이 글이 비슷한 과제를 안고 있는 분들에게 힌트나 참고가 되길 바랍니다.

Exit mobile version