이코에코(Eco²)/Clean Architecture Migration
이코에코(Eco²) Clean Architecture #3: Auth 버전 분리, 점진적 배포
mango_fr
2025. 12. 31. 03:27



배포 전략: Canary
리팩토링된 코드를 바로 배포해 클라이언트로 노출하는 것은 위험. Canary 배포로 일부 트래픽만 새 버전으로 라우팅하여 검증, PR에 부착된 Canary 라벨을 감지해 Canary 태그를 부착한 채로 패키징.
┌─────────────────────────────────────────────────────────┐
│ Istio VirtualService │
├─────────────────────────────────────────────────────────┤
│ X-Canary: true → auth-api-canary (version: v2) │
│ (default) → auth-api (version: v1) │
└─────────────────────────────────────────────────────────┘
Kubernetes Manifest 구조
workloads/domains/auth/
├── base/
│ ├── deployment.yaml # Stable (v1)
│ ├── deployment-canary.yaml # Canary (v2)
│ ├── service.yaml
│ ├── destination-rule.yaml # Istio 서브셋
│ └── kustomization.yaml
├── dev/
│ ├── kustomization.yaml
│ └── patch-deployment.yaml
└── prod/
└── kustomization.yaml
Stable Deployment
# workloads/domains/auth/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-api
labels:
app: auth-api
version: v1
spec:
selector:
matchLabels:
app: auth-api
version: v1
template:
spec:
containers:
- name: auth-api
image: docker.io/mng990/eco2:auth-api-dev-latest
Canary Deployment
# workloads/domains/auth/base/deployment-canary.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-api-canary
labels:
app: auth-api
version: v2
release: canary
spec:
selector:
matchLabels:
app: auth-api
version: v2
template:
spec:
containers:
- name: auth-api
image: docker.io/mng990/eco2:auth-api-dev-canary
env:
- name: RELEASE_TYPE
value: canary
CI/CD 파이프라인
경로 감지 (apps/ + domains/)
domains/auth/(legacy)와 apps/auth/(v2) 모두 지원:
# .github/workflows/ci-services.yml
on:
push:
paths:
- "apps/auth/**" # Clean Architecture
- "domains/auth/**" # Legacy
서비스 감지 로직
# Changed files 분석
for path in files:
parts = path.split("/")
# apps/<service>/ 경로 감지 (Clean Architecture)
if len(parts) >= 2 and parts[0] == "apps":
svc = parts[1]
if svc in API_SERVICES:
services.append(svc)
# domains/<service>/ 경로 감지 (Legacy)
elif len(parts) >= 2 and parts[0] == "domains":
svc = parts[1]
if svc in API_SERVICES:
services.append(svc)
Dockerfile 자동 감지
- name: Determine Dockerfile path
id: dockerfile
run: |
SERVICE="${{ matrix.service }}"
# Clean Architecture 버전 우선
if [ -f "apps/${SERVICE}/Dockerfile" ]; then
echo "path=apps/${SERVICE}/Dockerfile" >> "$GITHUB_OUTPUT"
else
echo "path=domains/${SERVICE}/Dockerfile" >> "$GITHUB_OUTPUT"
fi
- name: Build and push
uses: docker/build-push-action@v5
with:
file: ${{ steps.dockerfile.outputs.path }}
PYTHONPATH 설정
- name: Determine source path
id: source
run: |
SERVICE="${{ matrix.service }}"
if [ -d "apps/${SERVICE}" ]; then
echo "path=apps/${SERVICE}" >> "$GITHUB_OUTPUT"
echo "pythonpath=${{ github.workspace }}" >> "$GITHUB_OUTPUT"
else
echo "path=domains/${SERVICE}" >> "$GITHUB_OUTPUT"
echo "pythonpath=${{ github.workspace }}/domains/${SERVICE}" >> "$GITHUB_OUTPUT"
fi
- name: Pytest
working-directory: ${{ steps.source.outputs.path }}
env:
PYTHONPATH: ${{ steps.source.outputs.pythonpath }}
run: pytest
Canary 빌드 워크플로우
PR에 canary 라벨 추가 시 Canary 이미지 자동 빌드:
# .github/workflows/ci-canary.yml
on:
pull_request:
types: [labeled, synchronize]
jobs:
check-canary-label:
steps:
- name: Check for canary label
run: |
if echo "$PR_LABELS" | jq -e 'contains(["canary"])'; then
echo "is_canary=true" >> "$GITHUB_OUTPUT"
fi
api-canary-build:
if: needs.check-canary-label.outputs.is_canary == 'true'
steps:
- name: Prepare canary tags
run: |
CANARY_TAG="${SERVICE}-api-dev-canary"
echo "tags=${IMAGE_REPO}:${CANARY_TAG}" >> "$GITHUB_OUTPUT"
- name: Build and push canary image
uses: docker/build-push-action@v5
ArgoCD GitOps 연동
ApplicationSet
# clusters/dev/apps/40-apis-appset.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: dev-apis
spec:
generators:
- list:
elements:
- name: auth
phase: '1'
template:
metadata:
name: dev-api-{{name}}
annotations:
# Image Updater - digest 변경 감지
argocd-image-updater.argoproj.io/image-list: stable=docker.io/mng990/eco2:{{name}}-api-dev-latest
argocd-image-updater.argoproj.io/stable.update-strategy: digest
spec:
source:
path: workloads/domains/{{name}}/dev
syncPolicy:
automated:
prune: true
selfHeal: true
배포 플로우
1. apps/auth/** 변경
↓
2. GitHub Actions (ci-services.yml)
↓
3. Docker Build (apps/auth/Dockerfile)
↓
4. Push: mng990/eco2:auth-api-dev-latest
↓
5. ArgoCD Image Updater (digest 감지)
↓
6. workloads/domains/auth/dev/ Sync
↓
7. Kubernetes Deployment 업데이트
Canary 테스트 방법
헤더 기반 라우팅
# Canary 버전 요청
curl -H "X-Canary: true" https://api.growbin.app/api/v1/auth/health
# Stable 버전 요청 (기본)
curl https://api.growbin.app/api/v1/auth/health
Pod 확인
# Stable Pod
kubectl get pods -n auth -l version=v1
# Canary Pod
kubectl get pods -n auth -l version=v2
# 로그 비교
kubectl logs -n auth -l version=v2 --tail=100 -f
롤백 전략
Canary 문제 발생 시
# Canary Deployment 스케일 다운
kubectl scale deployment auth-api-canary -n auth --replicas=0
# 또는 삭제
kubectl delete deployment auth-api-canary -n auth
ArgoCD Rollback
# Application 히스토리 확인
kubectl get applications dev-api-auth -n argocd -o yaml
# 이전 버전으로 롤백
argocd app rollback dev-api-auth
파이프라인 요약
| 워크플로우 | 트리거 | 결과물 |
|---|---|---|
ci-services.yml |
push to develop | auth-api-dev-latest |
ci-canary.yml |
PR + canary label | auth-api-dev-canary |
ci-auth-relay.yml |
workers 변경 | auth-relay-dev-latest |
| 매니페스트 | 이미지 태그 | 버전 |
|---|---|---|
deployment.yaml |
auth-api-dev-latest |
v1 (stable, legacy) |
deployment-canary.yaml |
auth-api-dev-canary |
v2 (canary) |