- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 골든패스란 무엇이고 왜 필요한가
- GitHub Actions Reusable Workflow로 골든패스 구축
- CD 골든패스: 배포 안전장치 설계
- Branch Protection과 품질 게이트 강제
- 골든패스 버전 관리 전략
- 배포 지표 측정: DORA Metrics 자동 수집
- 트러블슈팅
- 퀴즈
- 참고 자료

골든패스란 무엇이고 왜 필요한가
골든패스(Golden Path)는 조직이 권장하는 소프트웨어 배포 경로다. "반드시 이렇게 해라"는 강제가 아니라 "이렇게 하면 가장 빠르고 안전하다"는 포장도로를 제공하는 개념이다. Spotify가 Backstage와 함께 대중화한 이 용어는 2026년 현재 Internal Developer Platform(IDP)의 핵심 설계 원칙이 되었다.
골든패스가 없으면 일어나는 일:
- 팀마다 CI/CD 파이프라인이 다르고, 장애 시 원인 파악에 시간이 걸린다
- 신규 서비스 생성에 2-3주가 소요된다 (인프라 요청, 권한 설정, 모니터링 연동)
- 배포 실패 시 롤백 절차가 팀마다 다르거나 아예 없다
- 보안 취약점 패치가 각 팀의 자체 판단에 의존한다
골든패스가 잘 구축되면:
- 신규 서비스 생성이 30분 내에 완료된다
- 모든 서비스가 동일한 품질 게이트를 통과한다
- 장애 시 롤백이 자동화되어 MTTR이 5분 이내로 줄어든다
- 보안 패치가 중앙에서 일괄 적용된다
GitHub Actions Reusable Workflow로 골든패스 구축
2025년 11월부터 GitHub Actions는 중첩 reusable workflow를 최대 10단계까지, 총 50개 workflow까지 지원한다. 이 확장 덕분에 대규모 조직에서도 reusable workflow 기반의 골든패스가 실용적이 되었다.
조직 공용 워크플로우 저장소 구조
.github-workflows/ # 조직 공용 저장소
├── .github/
│ └── workflows/
│ ├── golden-ci.yml # CI 골든패스
│ ├── golden-cd.yml # CD 골든패스
│ ├── golden-security.yml # 보안 스캔
│ └── golden-release.yml # 릴리스 자동화
├── actions/
│ ├── setup-node/
│ │ └── action.yml
│ ├── docker-build/
│ │ └── action.yml
│ └── deploy-k8s/
│ └── action.yml
└── CHANGELOG.md # 버전별 변경 이력
CI 골든패스 워크플로우
# .github/workflows/golden-ci.yml
name: Golden Path CI
on:
workflow_call:
inputs:
node-version:
description: 'Node.js 버전'
type: string
default: '22'
enable-sonar:
description: 'SonarQube 분석 활성화'
type: boolean
default: true
docker-registry:
description: '컨테이너 레지스트리'
type: string
default: 'ghcr.io'
secrets:
SONAR_TOKEN:
required: false
REGISTRY_TOKEN:
required: true
jobs:
lint-and-test:
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # SonarQube에 전체 히스토리 필요
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
- name: 의존성 설치 및 감사
run: |
npm ci --ignore-scripts
npm audit --audit-level=high || echo "::warning::npm audit에서 high 이상 취약점 발견"
- name: 린트
run: npm run lint
- name: 단위 테스트 + 커버리지
run: npm run test -- --coverage --ci
env:
CI: true
- name: 커버리지 게이트 (80% 미만 실패)
run: |
COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
echo "Line coverage: ${COVERAGE}%"
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "::error::커버리지가 80% 미만입니다: ${COVERAGE}%"
exit 1
fi
- name: SonarQube 분석
if: inputs.enable-sonar && secrets.SONAR_TOKEN != ''
uses: SonarSource/sonarqube-scan-action@v3
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
build-and-push:
needs: lint-and-test
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
packages: write
contents: read
outputs:
image-digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v4
- name: Docker 이미지 빌드 및 푸시
id: build
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ inputs.docker-registry }}/${{ github.repository }}:${{ github.sha }}
${{ inputs.docker-registry }}/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: true
sbom: true
- name: 이미지 취약점 스캔
uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ inputs.docker-registry }}/${{ github.repository }}:${{ github.sha }}'
severity: 'CRITICAL,HIGH'
exit-code: '1'
format: 'sarif'
output: 'trivy-results.sarif'
- name: 스캔 결과 업로드
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
제품 팀에서의 사용 방법
각 제품 팀은 한 줄로 골든패스를 호출한다.
# 제품 팀의 .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
golden-ci:
uses: my-org/.github-workflows/.github/workflows/golden-ci.yml@v2.3.1
with:
node-version: '22'
enable-sonar: true
secrets:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
핵심은 @v2.3.1처럼 골든패스 워크플로우를 태그로 버전 고정하는 것이다. @main을 사용하면 예고 없이 변경되어 팀의 파이프라인이 깨질 수 있다.
CD 골든패스: 배포 안전장치 설계
배포 단계별 안전장치
# .github/workflows/golden-cd.yml
name: Golden Path CD
on:
workflow_call:
inputs:
environment:
type: string
required: true
image-tag:
type: string
required: true
canary-weight:
type: number
default: 10
analysis-duration:
type: string
default: '5m'
secrets:
KUBE_CONFIG:
required: true
DATADOG_API_KEY:
required: false
jobs:
deploy-canary:
runs-on: ubuntu-24.04
environment:
name: ${{ inputs.environment }}-canary
url: https://${{ inputs.environment }}.internal.example.com
steps:
- uses: actions/checkout@v4
- name: kubeconfig 설정
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > ~/.kube/config
- name: 카나리 배포 (트래픽 ${{ inputs.canary-weight }}%)
run: |
kubectl argo rollouts set image my-app \
my-app=${{ inputs.image-tag }}
kubectl argo rollouts promote my-app --full=false
- name: 지표 기반 자동 분석 (${{ inputs.analysis-duration }})
id: analysis
run: |
echo "분석 시작: $(date)"
sleep 30 # 메트릭 수집 대기
# Datadog에서 에러율 조회
ERROR_RATE=$(curl -s \
"https://api.datadoghq.com/api/v1/query?from=$(date -d '-5min' +%s)&to=$(date +%s)&query=sum:http.errors{service:my-app,version:canary}.as_rate()" \
-H "DD-API-KEY: ${{ secrets.DATADOG_API_KEY }}" | jq '.series[0].pointlist[-1][1]')
P95_LATENCY=$(curl -s \
"https://api.datadoghq.com/api/v1/query?from=$(date -d '-5min' +%s)&to=$(date +%s)&query=p95:http.latency{service:my-app,version:canary}" \
-H "DD-API-KEY: ${{ secrets.DATADOG_API_KEY }}" | jq '.series[0].pointlist[-1][1]')
echo "에러율: ${ERROR_RATE}, P95 지연: ${P95_LATENCY}ms"
# 중단 조건: 에러율 1% 초과 또는 P95 500ms 초과
if (( $(echo "$ERROR_RATE > 0.01" | bc -l) )) || \
(( $(echo "$P95_LATENCY > 500" | bc -l) )); then
echo "::error::카나리 분석 실패 - 자동 롤백 실행"
echo "result=fail" >> $GITHUB_OUTPUT
else
echo "카나리 분석 통과"
echo "result=pass" >> $GITHUB_OUTPUT
fi
- name: 자동 롤백 (분석 실패 시)
if: steps.analysis.outputs.result == 'fail'
run: |
kubectl argo rollouts abort my-app
echo "::error::카나리 롤백 완료. 에러율 또는 지연 시간이 기준을 초과했습니다."
exit 1
promote-full:
needs: deploy-canary
runs-on: ubuntu-24.04
environment:
name: ${{ inputs.environment }}-production
steps:
- name: 전체 트래픽 전환
run: |
kubectl argo rollouts promote my-app --full
- name: 배포 완료 확인
run: |
kubectl argo rollouts status my-app --timeout 300s
echo "배포 완료: $(date)"
Branch Protection과 품질 게이트 강제
골든패스는 워크플로우만으로 완성되지 않는다. GitHub의 Branch Protection Rules와 결합해야 우회를 방지할 수 있다.
# GitHub CLI로 branch protection 설정
gh api repos/{owner}/{repo}/branches/main/protection \
--method PUT \
--input - <<'EOF'
{
"required_status_checks": {
"strict": true,
"contexts": [
"golden-ci / lint-and-test",
"golden-ci / build-and-push",
"security / trivy-scan"
]
},
"enforce_admins": true,
"required_pull_request_reviews": {
"required_approving_review_count": 1,
"dismiss_stale_reviews": true,
"require_code_owner_reviews": true
},
"restrictions": null,
"allow_force_pushes": false,
"allow_deletions": false
}
EOF
핵심 설정:
strict: true: PR 브랜치가 base branch 최신 상태여야 merge 가능enforce_admins: true: 관리자도 예외 없이 규칙 적용dismiss_stale_reviews: 코드 변경 시 기존 승인 무효화
골든패스 버전 관리 전략
골든패스 워크플로우는 조직 전체에 영향을 미치므로, 버전 관리가 제품 코드만큼 중요하다.
Semantic Versioning 적용
# CHANGELOG.md 예시
## [2.3.1] - 2026-03-01
### Fixed
- Trivy 스캔에서 SBOM 생성 시 OCI 레이어 참조 오류 수정
## [2.3.0] - 2026-02-15
### Added
- SonarQube 분석 결과를 PR 코멘트로 자동 게시
## [2.2.0] - 2026-02-01
### Changed
- ubuntu-22.04 -> ubuntu-24.04 러너 업그레이드
- Node.js 기본 버전 20 -> 22
### Breaking
- enable-sonar 입력이 기본 true로 변경 (기존 false)
팀에 버전 업데이트 알림
# 골든패스 저장소의 릴리스 자동 알림
name: Notify Teams on Release
on:
release:
types: [published]
jobs:
notify:
runs-on: ubuntu-24.04
steps:
- name: Slack 알림
uses: slackapi/slack-github-action@v2
with:
webhook: ${{ secrets.SLACK_PLATFORM_WEBHOOK }}
payload: |
{
"text": "골든패스 ${{ github.event.release.tag_name }} 릴리스",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*골든패스 ${{ github.event.release.tag_name }}* 가 릴리스되었습니다.\n${{ github.event.release.body }}\n\n각 팀의 workflow 파일에서 버전을 업데이트해 주세요."
}
}
]
}
배포 지표 측정: DORA Metrics 자동 수집
골든패스의 효과를 측정하려면 DORA 4대 지표를 추적해야 한다.
# 배포 빈도와 리드타임 자동 수집
name: DORA Metrics
on:
workflow_run:
workflows: ['Golden Path CD']
types: [completed]
jobs:
collect-metrics:
runs-on: ubuntu-24.04
steps:
- name: 리드타임 계산 (첫 커밋 -> 프로덕션 배포)
run: |
# 이 배포에 포함된 커밋의 가장 이른 시간
FIRST_COMMIT=$(git log --format=%aI origin/main..HEAD | tail -1)
DEPLOY_TIME=$(date -Iseconds)
# 리드타임 (초)
LEAD_TIME=$(( $(date -d "$DEPLOY_TIME" +%s) - $(date -d "$FIRST_COMMIT" +%s) ))
echo "Lead time: ${LEAD_TIME}s ($(( LEAD_TIME / 3600 ))h)"
# Prometheus Pushgateway로 지표 전송
cat <<METRIC | curl --data-binary @- http://pushgateway.internal:9091/metrics/job/dora
deployment_lead_time_seconds{service="my-app"} ${LEAD_TIME}
deployment_count_total{service="my-app"} 1
METRIC
- name: 변경 실패율 계산
if: github.event.workflow_run.conclusion == 'failure'
run: |
cat <<METRIC | curl --data-binary @- http://pushgateway.internal:9091/metrics/job/dora
deployment_failure_total{service="my-app"} 1
METRIC
DORA 지표 기준:
| 지표 | Elite | High | Medium | Low |
|---|---|---|---|---|
| 배포 빈도 | 하루 여러 번 | 주 1회-일 1회 | 월 1회-주 1회 | 월 1회 미만 |
| 리드타임 | 1시간 미만 | 1일-1주 | 1주-1개월 | 1개월 초과 |
| 변경 실패율 | 0-5% | 5-10% | 10-15% | 15% 초과 |
| MTTR | 1시간 미만 | 1일 미만 | 1일-1주 | 1주 초과 |
트러블슈팅
문제 1: Reusable workflow에서 secrets가 전달되지 않음
Error: Input required and not supplied: REGISTRY_TOKEN
원인: secrets: inherit를 사용하지 않고, 호출 측에서 secret을 명시적으로 전달하지 않았다.
# 해결 방법 1: 명시적 전달
jobs:
ci:
uses: org/.github-workflows/.github/workflows/golden-ci.yml@v2
secrets:
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 해결 방법 2: 전체 상속 (보안상 비권장)
jobs:
ci:
uses: org/.github-workflows/.github/workflows/golden-ci.yml@v2
secrets: inherit
문제 2: 카나리 배포에서 지표 수집이 지연되어 오판 발생
Error: 카나리 분석 실패 - 에러율 0.08 (기준 0.01)
# 실제로는 이전 버전의 에러가 포함된 윈도우
원인: 메트릭 파이프라인의 수집 주기(보통 15-60초)와 분석 시작 시간이 맞지 않는다.
# 해결: 분석 시작 전 대기 시간 + 윈도우 조정
- name: 메트릭 안정화 대기
run: sleep 120 # 2분 대기 후 분석 시작
- name: 분석 윈도우를 3분으로 설정
run: |
# from: 2분 전, to: 현재 (안정화 대기 후의 순수 카나리 구간)
FROM=$(date -d '-3min' +%s)
TO=$(date +%s)
문제 3: 러너 캐시 미스로 빌드 시간 급증
Cache not found for input keys: npm-Linux-abc123...
원인: package-lock.json 변경 시 캐시 키가 바뀐다. 또는 GitHub Actions 캐시의 기본 만료(7일)로 제거되었다.
# 해결: 폴백 캐시 키 사용
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-
퀴즈
Q1. 골든패스와 강제 표준의 차이점은?
정답: ||골든패스는 "이렇게 하면 가장 빠르고 안전하다"는 권장 경로이며, 팀이 선택적으로 따른다.
강제 표준은 예외 없이 모든 팀에 적용된다. 골든패스에서 배포 중단 조건과 보안 스캔 같은 핵심 규칙만
강제하고, 나머지는 팀 자율에 맡기는 것이 일반적이다.||
Q2. Reusable workflow를 @main 대신 태그로 버전 고정하는 이유는?
정답: ||@main은 골든패스 워크플로우 변경이 즉시 모든 팀에 반영되어 예고 없이 파이프라인이 깨질 수
있다. 태그(@v2.3.1)로 고정하면 팀이 준비된 시점에 업그레이드할 수 있고, 문제 발생 시 이전 버전으로
롤백도 용이하다.||
Q3. enforce_admins: true 설정의 의미와 중요성은?
정답: ||GitHub 저장소 관리자도 branch protection 규칙을 우회할 수 없게 한다. 이 설정이 없으면
관리자가 직접 main에 push하거나 PR 없이 merge할 수 있어 품질 게이트가 무력화된다.||
Q4. 카나리 분석에서 메트릭 수집 전에 대기 시간을 두는 이유는?
정답: ||메트릭 파이프라인에는 수집-전송-집계 지연이 있다(보통 30초-2분). 카나리 배포 직후 바로
분석하면 이전 버전의 메트릭이 포함되어 오판할 수 있다. 충분한 대기 후 순수 카나리 트래픽 구간만
분석해야 정확한 판단이 가능하다.||
Q5. DORA 지표 중 MTTR(Mean Time To Recovery)을 5분 이내로 줄이기 위한 핵심 요소는?
정답: ||자동 롤백이 핵심이다. 카나리 분석 실패 시 kubectl argo rollouts abort가 자동 실행되어야
하고, 롤백 대상 이미지가 항상 레지스트리에 존재해야 한다. 수동 개입이 필요한 순간 MTTR은 분
단위에서 시간 단위로 늘어난다.||
Q6. GitHub Actions 캐시가 7일 후 만료되는 것에 대한 대응 방안은?
정답: ||restore-keys에 부분 일치 키를 설정하여 정확한 캐시가 없어도 가장 최근의 유사한 캐시를
복원한다. 또한 주기적으로 캐시를 미리 워밍하는 scheduled workflow를 운영하거나, npm ci 대신 npm
install --prefer-offline을 사용하는 방법도 있다.||