ksc0204 님의 블로그

AEWS 4기 6주차(3) - EKS CI/CD(SaaS Tier 전략, ArgoCD Worflow) 본문

AWS 4기

AEWS 4기 6주차(3) - EKS CI/CD(SaaS Tier 전략, ArgoCD Worflow)

ksc0204 2026. 4. 23. 11:58

Before Start..

본 글은 공식 문서 및 서종호(가시다)님의 AWS EKS 워크샵 내용을 기반으로 참고하여 학습 목적으로 작성하였습니다.
주관적인 해석이 포함되어 있어 사실과 다르거나 오류가 있을 수 있으니 참고용으로만 읽어주시기 바랍니다.
본 글은 Amazon EKS 기반 확장 가능한 멀티 테넌틑 SaaS 플랫폼 구축에 대한 AWS Workshop 실습을 통해 작성한 내용입니다.

SaaS 티어 전략

1. Tier(티어) 전략

B2B SaaS(Software as a Service) 환경에서 '티어(Tier)'를 나눈다는 것은 단순히 마케팅 부서에서 요금제 표를 예쁘게 꾸미는 작업이 아닙니다. 엔지니어링 관점에서 티어 전략은 "고객이 지불하는 비용에 맞춰 인프라 자원의 격리 수준(Isolation Level)과 성능을 물리적/논리적으로 어떻게 분배할 것인가?"를 결정하는 핵심 생존 전략입니다.

 

초기 스타트업부터 엔터프라이즈 컴플라이언스를 요구하는 금융권 고객까지, 다양한 요구사항을 하나의 플랫폼에서 수용하기 위해 현대 SaaS 기업들이 채택하고 있는 'SaaS 인프라 티어 전략'의 개념과 실제 적용 사례를 살펴봅니다.

 

2. SaaS 티어 전략 - 실습 환경 기준

구성 요소 Basic 티어 Premium 티어
배포 모델 Pool (공유) Silo (전용)
Kubernetes 네임스페이스 pool-1 (공유) 테넌트 전용
Producer 공유 (pool-1) 전용 배포
Consumer 공유 (pool-1) 전용 배포
SQS 큐 공유 전용
DynamoDB 테이블 공유 전용
Ingress 테넌트별 라우팅만 전용
비용 낮음 높음
격리 수준 낮음 높음

 

아래 내용은 Gemini 3 Pro를 참고하여 작성하였습니다.

 

① Basic Tier (다중 임대 / Shared Pool 모델)

  • 구조: 여러 테넌트(고객)가 소수의 동일한 애플리케이션 파드와 단일 데이터베이스 자원을 함께 공유합니다.
  • 특징: 유휴 자원을 극한으로 활용하여 테넌트 당 인프라 단가를 극소화합니다. 대규모 무료 사용자나 초기 진입 고객을 유치하는 데 필수적입니다.
  • 한계: 한 테넌트의 과도한 트래픽이 다른 테넌트의 성능 저하를 유발하는 '시끄러운 이웃(Noisy Neighbor)' 문제가 발생할 수 있습니다.

  Premium Tier (완전 격리 / Silo 모델)

  • 구조: 전용 파드는 물론 데이터베이스, 메시지 큐, 캐시 서버까지 모든 구성 요소를 단독으로 프로비저닝합니다.
  • 특징: 비용은 가장 높지만 다른 회사와 데이터가 섞이는 것을 엄격히 금지하는 엔터프라이즈 보안 및 망 분리 규제(컴플라이언스)를 완벽하게 충족합니다.

 

3. 티어 전략 도입의 비즈니스 및 기술적 이점

  • 비용 효율성의 극대화: 모든 고객에게 전용 자원을 할당할 때 발생하는 기하급수적인 적자 구조를 막고, 수익성을 보장합니다.
  • 안정성과 SLA 보장: 고수익 VIP 고객의 인프라를 논리적/물리적으로 분리하여, 공용 풀에서 발생하는 장애로부터 완벽하게 보호합니다.
  • 보안 규제 대응력 확보: 금융, 공공기관 등 까다로운 보안 요건을 요구하는 대규모 B2B 계약을 수주할 수 있는 기술적 토대가 됩니다.

 

4. 글로벌 SaaS 기업의 적용 사례

  • 개발자 플랫폼 (GitHub): 무료/일반 사용자는 공용 웹 서버와 Shared Runner를 사용하지만, Enterprise 고객에게는 격리된 Dedicated Runner를 제공하거나 아예 고객사 환경에 독립된 클러스터를 구축(GitHub Enterprise Server)합니다.
  • 데이터 웨어하우스 (Snowflake): 기본 티어에서는 컨트롤 플레인과 컴퓨팅 자원을 공유하지만, 최상위 티어(Business Critical)에서는 전용 컴퓨팅 클러스터(Virtual Warehouse)를 즉시 스핀업하고 AWS PrivateLink를 통해 인터넷을 거치지 않는 격리 네트워크를 제공합니다.
  • 금융망 B2B 핀테크: 일반 고객은 논리적 스키마만 분리된 공용 RDS를 사용하지만, 금융권 고객이 유입될 경우 프로비저닝 도구를 통해 별도의 AWS 계정이나 전용 클러스터 기반의 완벽한 Silo 환경을 찍어냅니다.

5. GitOps를 활용한 인프라 구현

이러한 복잡한 티어링 전략을 수동으로 관리하는 것은 불가능에 가깝습니다.(관리 포인트 증가 문제)

최근에는 Flux CD, Terraform(TF-controller), HelmRelease를 결합하여 비즈니스 로직을 인프라 코드(IaC)로 완벽히 동기화합니다.

예를 들어, Basic 고객이 Premium으로 결제를 업그레이드할 경우 엔지니어는 Git 저장소의 테넌트 설정 파일(values.yaml)에서 변수 하나(producer.enabled: true)만 변경하여 커밋합니다. 그러면 GitOps 컨트롤러가 이를 감지하여 무중단으로 전용 파드와 DB를 생성하고, Ingress 라우팅 목적지를 공용 서버에서 전용 서버로 부드럽게 이전시킵니다.

 

Advanced 티어 정의

해당 실습 환경에서는 Basic, Premium에 대한 티어는 정의되어 있지만, Advanced에 대한 티어는 정의되어 있지 않아 직접 정의하는 실습을 진행하였습니다.

 

설계 목표

목표 : 기존 Helm Chart를 변경하지 않은 상태에서 Silo와 Pool 모델의 장점을 사용 패턴에 맞게 조합하여 Advanced 설계

  • Producer: 공유
    - pool-1 Pool 환경 활용(요청 패턴이 비교적 균일한 워크로드)
  • Consumer : 전용
    - 테넌트 전용 네임스페이스(데이터 처리 격리 필요)

 

HelmRelease 생성 - Advanced 템플릿

cat << EOF > /home/ec2-user/environment/gitops-gitea-repo/application-plane/production/tier-templates/advanced_tenant_template.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: {TENANT_ID}
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: {TENANT_ID}-advanced
  namespace: flux-system
spec:
  releaseName: {TENANT_ID}-advanced
  targetNamespace: {TENANT_ID}  # Deploying into the tenant-specific namespace
  interval: 1m0s
  chart:
    spec:
      chart: helm-tenant-chart
      version: "{RELEASE_VERSION}.x"
      sourceRef:
        kind: HelmRepository
        name: helm-tenant-chart
  values: # producer, consumer 부분 수정
    tenantId: {TENANT_ID}
    apps:
      producer:
        envId: pool-1
        enabled: false # Pool deployment -- advanced tier shares resources with other tenants
        ingress:
          enabled: true
      consumer:
        enabled: true  # Silo deployment -- advanced tier has a dedicated deployment for each tenant
        ingress:
          enabled: true
        image:
          tag: "0.1" # {"\$imagepolicy": "flux-system:consumer-image-policy:tag"}
EOF

 

Advanced 테넌트 - 프로비저닝(수동)

export TENANT_ID=tenant-t1d6c
export RELEASE_VERSION=0.0

cd /home/ec2-user/environment/gitops-gitea-repo/application-plane/production/
cp tier-templates/advanced_tenant_template.yaml tenants/advanced/$TENANT_ID.yaml

#TENANT_ID 값 치환
sed -i "s|{TENANT_ID}|$TENANT_ID|g" "tenants/advanced/$TENANT_ID.yaml"
sed -i "s|{RELEASE_VERSION}|$RELEASE_VERSION|g" "tenants/advanced/$TENANT_ID.yaml"

 

Kustomization 생성 및 Git Commit & Push

cat << EOF > tenants/advanced/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - $TENANT_ID.yaml
EOF

cd /home/ec2-user/environment/gitops-gitea-repo/
git pull origin main
git add .
git commit -am "Adding tenant-t1d6c with Advanced Tier"
git push origin main

 

Flux 강제 반영 및 확인

# flux 강제 반영
flux reconcile source git flux-system

# 배포 및 리소스 생성 확인
aws dynamodb list-tables | grep tenant-t1d6c
aws sqs list-queues | grep tenant-t1d6c

 

consumer 전용 SQS 및 DynamoDB 생성 확인

 

Curl 명령어를 통해 Consumer와 Producer 호출 시 환경 확인

APP_LB=http://$(kubectl get ingress -n tenant-t1d6c -o json | jq -r .items[0].status.loadBalancer.ingress[0].hostname)
echo $APP_LB # ALB 접속 도메인 확인

curl -s -H "tenantID: tenant-t1d6c" $APP_LB/producer | jq
curl -s -H "tenantID: tenant-t1d6c" $APP_LB/consumer | jq

위에서처럼 Producer는 공용환경을 가지며, Consumer는 전용 환경을 부여한 것을 확인할 수 있습니다. 즉, 아래 표와 같이 설계되었습니다.

구분 Producer Consumer 인프라 비용 격리 수준
Basic 공유 (pool-1) 공유 (pool-1) 공유 낮음 낮음
Advanced 공유 (pool-1) 전용 Consumer만 전용 중간 중간
Premium 전용 전용 전용 높음 높음

 


ArgoCD Workflow

실습 환경

  • workflowtemplates 확인
    onbording 워크플로우 템플릿 역할 - 새 테넌트 환경 프로비저닝
    offboarding 워크플로우 템플릿 역할 - 테넌트를 환경에서 제거
    deployment 워크플로우 템플릿 역할 - 테넌트 HelmRelease 버전 업데이트

Onbording workflow yaml 분석

[ec2-user@ip-10-0-1-206 gitops-gitea-repo]$ kubectl get workflowtemplate tenant-onboarding-template -n argo-workflows -o yaml
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
  creationTimestamp: "2026-04-20T08:25:07Z"
  generation: 1
  labels:
    kustomize.toolkit.fluxcd.io/name: controlplane
    kustomize.toolkit.fluxcd.io/namespace: flux-system
  name: tenant-onboarding-template
  namespace: argo-workflows
  resourceVersion: "5503"
  uid: 508a01d6-f14e-4716-8e32-dff6063fd0b8
spec:
  serviceAccountName: argoworkflows-sa
  templates:
  - container:
      args:
      - ./00-validate-tenant.sh {{workflow.parameters.TENANT_ID}}
      command:
      - /bin/sh
      - -c
      env:
      - name: GIT_USERNAME
        value: '{{workflow.parameters.GIT_USERNAME}}'
      - name: GIT_TOKEN
        value: '{{workflow.parameters.GIT_TOKEN}}'
      image: xxxxxxxxxxxx.dkr.ecr.us-west-2.amazonaws.com/argoworkflow-container:0.1
      volumeMounts:
      - mountPath: /mnt/vol
        name: workdir
    name: validate-if-tenant-exists
  - container:
      args:
      - ./01-tenant-clone-repo.sh {{workflow.parameters.REPO_URL}} {{workflow.parameters.GIT_BRANCH}}
        {{workflow.parameters.GIT_USERNAME}} {{workflow.parameters.GIT_TOKEN}} &&
        cp -r eks-saas-gitops /mnt/vol/eks-saas-gitops
      command:
      - /bin/sh
      - -c
      env:
      - name: GIT_USERNAME
        value: '{{workflow.parameters.GIT_USERNAME}}'
      - name: GIT_TOKEN
        value: '{{workflow.parameters.GIT_TOKEN}}'
      image: xxxxxxxxxxxx.dkr.ecr.us-west-2.amazonaws.com/argoworkflow-container:0.1
      volumeMounts:
      - mountPath: /mnt/vol
        name: workdir
    name: clone-repository
  - container:
      args:
      - ./02-tenant-onboarding.sh {{workflow.parameters.TENANT_ID}} {{workflow.parameters.RELEASE_VERSION}}
        {{workflow.parameters.TENANT_TIER}} {{workflow.parameters.GIT_USER_EMAIL}}
        {{workflow.parameters.GIT_USERNAME}} {{workflow.parameters.GIT_BRANCH}} {{workflow.parameters.GIT_TOKEN}}
      command:
      - /bin/sh
      - -c
      env:
      - name: GIT_USERNAME
        value: '{{workflow.parameters.GIT_USERNAME}}'
      - name: GIT_TOKEN
        value: '{{workflow.parameters.GIT_TOKEN}}'
      image: xxxxxxxxxxxx.dkr.ecr.us-west-2.amazonaws.com/argoworkflow-container:0.1
      volumeMounts:
      - mountPath: /mnt/vol
        name: workdir
    name: create-tenant-helm-release
  volumeClaimTemplates:
  - metadata:
      name: workdir
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi
      storageClassName: gp2

 

설정 역할
sericeAccountName: argoworkflows-sa 해당 Workflow를 실행할 때 부여 받는 권한
spec.container.name: validate-if-tenant-exists 신규 테넌트 = 기존 테넌트 중복 검사 스크립트 실행(인프라 충돌 방지)
spec.container.name: clone-repository Git Repo → Workflow 내부로 git clone
spec.container.name: create-tenant-helm-release HelmRelease 객체 생성

 

argoworkflows-sa SA 확인

현재는 실습환경으로 administrator 권한이 설정되어 있지만 추후 운영 환경시에는 최소권한으로만 설정하는 것을 권장합니다.

AdminitratorAccess 권핞 ㅘㄱ인

ArgoCD Workflow - Onboarding 동작 방식(실습 환경 기준)

1. SQS 이벤트 및 tenant ID 값 전달 → 2.테넌트 검증(중복 검사) → 3. 신규 테넌트 생성 및 Git Clone → 4. HelmRelease 생성 → Git Commit & Push (ArgoCD Workflow에서 실행) → flux cd 감지(flux-system에서 확인)

 

SQS 이벤트 전달

신규 테넌트를 생성하기 위해서는 필요한 메타데이터 값을 SQS로 전달하여 동작 과정을 확인하였습니다.

필요한 메타데이터 : tenant_id, tenant_tier, release_version

export ARGO_WORKFLOWS_ONBOARDING_QUEUE_SQS_URL=$(kubectl get configmap saas-infra-outputs -n flux-system -o jsonpath='{.data.argoworkflows_onboarding_queue_url}')

aws sqs send-message \
    --queue-url $ARGO_WORKFLOWS_ONBOARDING_QUEUE_SQS_URL \
    --message-body '{
        "tenant_id": "tenant-1",
        "tenant_tier": "premium",
        "release_version": "0.0"
    }'

 

Premium tenant Onboarding

 

 

Onboarding Workflow 실행 확인

 

Gitops 변경사항 검증

- 신규 테넌트가 추가되고 자동으로 이미지가 업데이트 된것을 확인할 수 있습니다.

Adding new tenant tenant-1 in tier premium으로 업데이트된 내역을 확인해보면 kustomization.yaml 파일이 수정되었고, tenant-1.yaml 파일이 신규 생성된 것을 확인할 수 있습니다.

 

offboarding 실행