Pingu
영차영차! Backend

GitHub Actions Reusable Workflows로 GitOps 패턴 구현하기

2026년 1월 22일
6개 태그
GitHub Actions
GitOps
Reusable Workflows
CI/CD
Cloud Run
GCP

GitHub Actions Reusable Workflows로 GitOps 패턴 구현하기

들어가며

마이크로서비스 아키텍처를 운영하다 보면 각 서비스마다 비슷한 배포 파이프라인을 관리하게 됩니다. 처음에는 각 서비스 레포지토리에 개별적으로 workflow 파일을 만들어 관리했는데, 서비스가 늘어날수록 문제가 생기기 시작했습니다.

  • 배포 로직을 수정하려면 모든 서비스 레포지토리를 수정해야 함
  • 각 서비스마다 약간씩 다른 설정으로 인해 일관성 유지가 어려움
  • 새로운 서비스 추가 시 기존 workflow를 복사-붙여넣기해야 함

이런 문제를 해결하기 위해 GitHub Actions의 Reusable Workflows 기능을 활용한 GitOps 패턴을 구축했습니다. 이번 글에서는 실제로 적용한 과정과 구체적인 구현 방법을 공유하려고 합니다.

GitHub Actions 소개

GitHub Actions 소개

현재 Dev 환경 배포 플로우

현재 각 서비스는 독립적인 GitHub 레포지토리로 관리되며, develop 또는 main 브랜치에 push하면 자동으로 GCP Cloud Run의 dev 환경에 배포됩니다.

다이어그램 로딩 중...

각 서비스의 배포 workflow는 다음과 같은 단계로 구성되어 있습니다:

  1. 코드 체크아웃: GitHub 레포지토리에서 소스 코드 가져오기
  2. GCP 인증: Service Account를 사용한 Google Cloud 인증 및 Docker 인증 설정
  3. Docker 이미지 빌드: linux/amd64 플랫폼용 이미지 빌드 (Cloud Run 호환)
  4. 이미지 푸시: Artifact Registry의 sclass-repo에 이미지 업로드
  5. Cloud Run 배포: GCP Cloud Run에 서비스 배포 (dev 프로파일)

문제 상황: 중복된 배포 파이프라인

각 서비스마다 거의 동일한 배포 workflow가 중복되어 있었습니다.

다이어그램 로딩 중...

서비스가 5개, 10개로 늘어날수록 각 서비스마다 동일한 workflow를 유지보수하는 것이 점점 어려워졌습니다.

중복되는 부분:

  • GCP 인증 및 Docker 설정
  • Docker 이미지 빌드 및 푸시
  • Cloud Run 배포 명령어
  • 환경 변수 및 리소스 설정

변경이 필요한 부분:

  • 서비스 이름
  • 이미지 태그 (일부는 latest, 일부는 github.sha)
  • 포트, 메모리, CPU 등 리소스 설정 (서비스별로 다를 수 있음)

해결 방안: Reusable Workflows

GitHub Actions의 Reusable Workflows 기능을 사용하면 중앙에서 배포 로직을 관리하고, 각 서비스에서는 간단한 설정만으로 배포를 트리거할 수 있습니다.

GitHub Actions Workflow 구성 요소

GitHub Actions Workflow 구성 요소

아키텍처 설계

다이어그램 로딩 중...

전체 구조는 다음과 같이 설계했습니다:

  1. 중앙 레포지토리 (또는 .github 레포): Reusable Workflow 정의
  2. 각 서비스 레포지토리: 간단한 workflow로 중앙 workflow 호출
  3. 배포 설정: 각 서비스별로 필요한 설정만 파라미터로 전달

구현 단계

1단계: Reusable Workflow 생성

먼저 중앙 레포지토리(또는 .github 레포)에 Reusable Workflow를 생성합니다.

.github/workflows/deploy-service.yml:

name: Deploy Service

on:
  workflow_call:
    inputs:
      service_name:
        required: true
        type: string
        description: '서비스 이름 (예: lms-service, account-service)'
      source_repo:
        required: true
        type: string
        description: '소스 레포지토리 (예: s-class/lms-service)'
      image_tag:
        required: false
        type: string
        default: ${{ github.sha }}
        description: 'Docker 이미지 태그 (기본값: commit SHA)'
      region:
        required: false
        type: string
        default: asia-northeast3
        description: 'GCP 리전'
      port:
        required: false
        type: string
        default: '8080'
        description: '서비스 포트'
      memory:
        required: false
        type: string
        default: '512Mi'
        description: '메모리 할당량'
      cpu:
        required: false
        type: string
        default: '1'
        description: 'CPU 할당량'
      min_instances:
        required: false
        type: string
        default: '1'
        description: '최소 인스턴스 수'
      max_instances:
        required: false
        type: string
        default: '10'
        description: '최대 인스턴스 수'
      timeout:
        required: false
        type: string
        default: '300'
        description: '타임아웃 (초)'
    secrets:
      GCP_PROJECT_ID:
        required: true
      GCP_SA_KEY:
        required: true

env:
  GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
  GCP_REGION: ${{ inputs.region }}
  SERVICE_NAME: ${{ inputs.service_name }}
  IMAGE_NAME: asia-northeast3-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/sclass-repo/${{ inputs.service_name }}:${{ inputs.image_tag }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout source repository
        uses: actions/checkout@v4
        with:
          repository: ${{ inputs.source_repo }}
          ref: ${{ github.sha }}
          path: service-repo

      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v2
        with:
          project_id: ${{ env.GCP_PROJECT_ID }}

      - name: Authenticate to Google Cloud
        run: |
          echo '${{ secrets.GCP_SA_KEY }}' > $HOME/gcp-key.json
          gcloud auth activate-service-account --key-file=$HOME/gcp-key.json
          gcloud auth configure-docker asia-northeast3-docker.pkg.dev --quiet

      - name: Build and push image (linux/amd64)
        working-directory: service-repo
        run: |
          docker buildx create --name cloud-run-builder --use || docker buildx use cloud-run-builder
          docker buildx build \
            --platform=linux/amd64 \
            -t $IMAGE_NAME \
            --push \
            .

      - name: Deploy to Cloud Run
        run: |
          gcloud run deploy $SERVICE_NAME \
            --image=$IMAGE_NAME \
            --region=$GCP_REGION \
            --platform=managed \
            --allow-unauthenticated \
            --port=${{ inputs.port }} \
            --set-env-vars SPRING_PROFILES_ACTIVE=dev \
            --min-instances=${{ inputs.min_instances }} \
            --max-instances=${{ inputs.max_instances }} \
            --memory=${{ inputs.memory }} \
            --cpu=${{ inputs.cpu }} \
            --timeout=${{ inputs.timeout }}

2단계: 각 서비스에서 Reusable Workflow 호출

이제 각 서비스 레포지토리에서는 간단한 workflow만 작성하면 됩니다.

lms-service/.github/workflows/deploy.yml:

name: Deploy

on:
  push:
    branches: [main, develop]

jobs:
  deploy:
    uses: s-class/.github/workflows/deploy-service.yml@main
    with:
      service_name: lms-service
      source_repo: s-class/lms-service
      image_tag: ${{ github.sha }}
      region: asia-northeast3
      port: '8080'
      memory: '512Mi'
      cpu: '1'
      min_instances: '1'
      max_instances: '10'
      timeout: '300'
    secrets:
      GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
      GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}

account-service/.github/workflows/deploy.yml:

name: Deploy

on:
  push:
    branches: [develop]

jobs:
  deploy:
    uses: s-class/.github/workflows/deploy-service.yml@main
    with:
      service_name: account-service
      source_repo: s-class/account-service
      image_tag: latest  # account-service는 latest 태그 사용
      region: asia-northeast3
      port: '8080'
      memory: '512Mi'
      cpu: '1'
      min_instances: '1'
      max_instances: '10'
      timeout: '300'
    secrets:
      GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
      GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}

배포 프로세스 흐름

배포 프로세스 시퀀스 다이어그램

배포 프로세스 시퀀스 다이어그램

다이어그램 로딩 중...

주요 장점

1. 중앙 집중식 관리

다이어그램 로딩 중...

배포 로직을 한 곳에서 관리할 수 있어 유지보수가 훨씬 쉬워졌습니다. 예를 들어, Docker 빌드 방식을 변경하거나 Cloud Run 배포 옵션을 수정할 때 중앙 workflow만 수정하면 모든 서비스에 자동으로 적용됩니다.

2. 일관성 보장

모든 서비스가 동일한 배포 프로세스를 따르게 되어, 배포 실패나 설정 오류를 줄일 수 있습니다.

3. 간단한 서비스 추가

새로운 서비스를 추가할 때는 간단한 workflow 파일만 작성하면 됩니다. 복잡한 배포 로직을 다시 작성할 필요가 없습니다.

4. 유연한 설정

각 서비스의 특성에 맞게 메모리, CPU, 인스턴스 수 등을 파라미터로 전달할 수 있어 유연하게 대응할 수 있습니다.

주의사항 및 팁

1. Secret 관리

각 서비스 레포지토리에서 GCP_PROJECT_IDGCP_SA_KEY secret을 설정해야 합니다. 조직 레벨에서 secret을 관리하면 더 효율적입니다.

2. Workflow 참조 경로

Reusable Workflow를 참조할 때는 다음 형식을 사용합니다:

uses: owner/repo/.github/workflows/workflow-file.yml@branch

3. 디버깅

Reusable Workflow에서 문제가 발생하면, 각 서비스의 workflow 실행 로그에서 상세한 정보를 확인할 수 있습니다. GitHub Actions UI에서 "Reusable workflow" 섹션을 확인하면 됩니다.

4. 버전 관리

Reusable Workflow를 수정할 때는 브랜치 전략을 명확히 해야 합니다. 프로덕션 서비스는 main 브랜치를 참조하고, 테스트는 develop 브랜치를 참조하는 방식을 권장합니다.

실제 적용 결과

이 패턴을 적용한 후 다음과 같은 개선 효과를 얻을 수 있었습니다:

  • 배포 로직 수정 시간: 모든 서비스 수정 시 약 2시간 → 중앙 workflow 수정 시 약 10분
  • 새 서비스 추가 시간: 약 30분 → 약 5분
  • 배포 실패율: 약 5% → 약 1% (일관된 프로세스로 인해)

마무리

GitHub Actions의 Reusable Workflows를 활용하면 마이크로서비스 환경에서도 효율적으로 배포 파이프라인을 관리할 수 있습니다. 중앙에서 배포 로직을 관리하면서도 각 서비스의 특성에 맞게 유연하게 설정할 수 있어, 확장 가능한 CI/CD 인프라를 구축할 수 있습니다.

GitHub Actions의 Reusable Workflows는 간단하면서도 효과적인 GitOps 패턴을 구현할 수 있는 좋은 방법입니다. 더 복잡한 요구사항이 있다면 ArgoCD나 Flux 같은 전문 GitOps 도구를 고려해볼 수 있습니다.

참고 자료

댓글

?