EKS에서 ArgoCD로 GitOps 구축하기: 순환 의존성 해결부터 Single Sync까지

2026. 1. 5. 15:51·Network

시작하며

최근 프로젝트에서 마이크로서비스 아키텍처를 EKS로 이전하면서, GitOps를 도입하기로 결정했습니다. 하지만 막상 ArgoCD를 설치하려고 하니 예상치 못한 문제들이 하나둘 나타나기 시작했어요.

"ArgoCD가 시작하려면 argocd-secret이 필요한데, 이 시크릿은 ExternalSecret이 만들어야 하고, ExternalSecret이 동작하려면 External Secrets Operator가 필요하고, ESO를 ArgoCD로 관리하려면... 어? 순환 의존성이네?"

오늘은 이런 복잡한 의존성 문제를 어떻게 해결했는지, 그리고 Terraform과 ArgoCD의 역할을 어떻게 나누어 Single Sync를 유지했는지 공유해보려고 합니다.

문제 상황: 닭이 먼저냐 달걀이 먼저냐

초기 계획의 함정

처음 계획은 단순했습니다:

  1. Terraform으로 EKS 클러스터 생성
  2. ArgoCD 설치
  3. 모든 애플리케이션을 ArgoCD로 관리

하지만 실제로 구현하려니 순환 의존성 문제가 발생했어요.

graph LR
    A[ArgoCD 시작] --> B[argocd-secret 필요]
    B --> C[ExternalSecret이 생성]
    C --> D[ESO 필요]
    D --> E[ArgoCD로 ESO 설치?]
    E --> A

ArgoCD는 argocd-secret이 있어야 시작되는데, 이 시크릿을 만들려면 이미 ArgoCD가 동작하고 있어야 한다는 모순이었죠.

추가로 고려해야 할 것들

  • 보안: AWS Secrets Manager에 저장된 OAuth 정보를 안전하게 가져오기
  • 자동화: Terraform 재배포 후에도 모든 설정이 자동으로 복구되어야 함
  • Single Sync: 하나의 진실 공급원(Git)에서 모든 것을 관리
  • 순서 제어: 인프라 → 애드온 → 애플리케이션 순으로 배포

해결 방법: 역할 분담과 Bootstrap 전략

1. Terraform vs ArgoCD 역할 분담

문제를 해결하기 위해 최소한의 Bootstrap만 Terraform에서 하고, 나머지는 모두 ArgoCD에 맡기기로 했습니다.

Terraform 관리 범위 (Bootstrap만)

# terraform/prod/compute/helm-releases.tf
# 순환 의존성 해결을 위한 최소한의 설치
1. EKS 클러스터
2. Gateway API CRDs  
3. Istio (Base + Istiod)
4. External Secrets Operator ← 핵심!
5. ClusterSecretStore
6. argocd-secret (ExternalSecret으로)
7. ArgoCD Helm 설치
8. 초기 AppProject + Root Application

ArgoCD 관리 범위 (모든 애플리케이션)

# k8s/argocd/apps/prod/ 디렉토리
- 클러스터 애드온들 (ALB Controller, External DNS 등)
- ArgoCD 자체 설정 (GitOps로!)
- 마이크로서비스들
- Istio 설정
- 모니터링 스택

2. 순환 의존성 해결: ESO 먼저 설치

핵심은 External Secrets Operator를 ArgoCD보다 먼저 Terraform에서 설치하는 것이었습니다.

# terraform/prod/compute/helm-releases.tf
# =============================================================================
# 3. External Secrets Operator (ArgoCD보다 먼저 설치 - Bootstrap 필수)
# =============================================================================
resource "helm_release" "external_secrets" {
  name       = "external-secrets"
  repository = "https://charts.external-secrets.io"
  chart      = "external-secrets"
  version    = "1.2.0"
  namespace  = "external-secrets"

  create_namespace = true

  # CRDs 설치
  set {
    name  = "installCRDs"
    value = "true"
  }

  depends_on = [
    helm_release.istiod,
    module.pod_identity_external_secrets  # Pod Identity 연결
  ]
}

3. argocd-secret 자동 생성

ESO가 설치되면, ArgoCD 시작 전에 필요한 시크릿을 자동으로 생성할 수 있습니다.

# terraform에서 kubectl로 적용
resource "kubectl_manifest" "argocd_external_secret" {
  yaml_body = <<-YAML
    apiVersion: external-secrets.io/v1
    kind: ExternalSecret
    metadata:
      name: argocd-oauth-secret
      namespace: argocd
    spec:
      refreshInterval: 1h
      secretStoreRef:
        name: aws-secrets-manager
        kind: ClusterSecretStore
      target:
        name: argocd-secret  # ArgoCD가 찾는 시크릿 이름
        creationPolicy: Owner
        template:
          data:
            server.secretkey: "{{ .server_secretkey }}"
            dex.google.clientID: "{{ .dex_google_clientID }}"
            dex.google.clientSecret: "{{ .dex_google_clientSecret }}"
      data:
        - secretKey: server_secretkey
          remoteRef:
            key: "wealist/prod/argocd/server"
            property: secretkey
        - secretKey: dex_google_clientID
          remoteRef:
            key: "wealist/prod/oauth/argocd"
            property: client_id
        - secretKey: dex_google_clientSecret
          remoteRef:
            key: "wealist/prod/oauth/argocd"
            property: client_secret
  YAML

  depends_on = [kubernetes_namespace.argocd]
}

4. ArgoCD 설치 시 중요한 설정

# ArgoCD Helm 설치 시 핵심 설정
resource "helm_release" "argocd" {
  name       = "argocd"
  repository = "https://argoproj.github.io/argo-helm"
  chart      = "argo-cd"
  version    = "5.55.0"
  namespace  = "argocd"

  create_namespace = false  # 이미 생성됨

  # ⚠️ 중요: ArgoCD가 자체 시크릿을 만들지 않도록 설정
  set {
    name  = "configs.secret.createSecret"
    value = "false"  # ExternalSecret이 관리하므로
  }

  # argocd-secret이 먼저 생성되어야 ArgoCD가 시작 가능
  depends_on = [time_sleep.wait_for_argocd_secret]
}

App of Apps 패턴으로 모든 것을 GitOps로

Root Application 구조

ArgoCD가 설치되면, App of Apps 패턴으로 모든 애플리케이션을 관리합니다.

# k8s/argocd/root-apps/prod.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: wealist-apps-prod
  namespace: argocd
spec:
  project: wealist-prod
  source:
    repoURL: https://github.com/OrangesCloud/wealist-project-advanced-k8s.git
    targetRevision: k8s-deploy-prod
    path: k8s/argocd/apps/prod  # 이 디렉토리의 모든 Application을 관리
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Sync Wave로 배포 순서 제어

복잡한 의존성을 sync-wave 어노테이션으로 해결했습니다.

# k8s/argocd/apps/prod/argocd-config.yaml
metadata:
  annotations:
    argocd.argoproj.io/sync-wave: "-10"  # 가장 먼저

# k8s/argocd/apps/prod/cluster-addons.yaml  
metadata:
  annotations:
    argocd.argoproj.io/sync-wave: "-6"   # 애드온들

# k8s/argocd/apps/prod/external-secrets.yaml
metadata:
  annotations:
    argocd.argoproj.io/sync-wave: "1"    # ExternalSecret 리소스들

# k8s/argocd/apps/prod/infrastructure.yaml
metadata:
  annotations:
    argocd.argoproj.io/sync-wave: "2"    # ConfigMap, PVC

# k8s/argocd/apps/prod/auth-service.yaml
metadata:
  annotations:
    argocd.argoproj.io/sync-wave: "5"    # 마이크로서비스들

핵심 인사이트: ArgoCD 자체도 GitOps로 관리하기

가장 중요한 깨달음

ArgoCD 자체 설정도 Git으로 관리해야 한다는 것이었습니다. 그렇지 않으면 Terraform을 재배포할 때마다 수동으로 설정을 다시 해야 하거든요.

# k8s/argocd/apps/prod/argocd-config.yaml
# ArgoCD가 자기 자신의 설정을 관리!
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: argocd-config
  namespace: argocd
spec:
  project: wealist-prod
  sources:
    # ArgoCD ConfigMap 설정 (health check, SSO 등)
    - repoURL: https://github.com/OrangesCloud/wealist-project-advanced-k8s.git
      targetRevision: k8s-deploy-prod
      path: k8s/argocd/config
    # Discord 알림 설정
    - repoURL: https://github.com/OrangesCloud/wealist-project-advanced-k8s.git
      targetRevision: k8s-deploy-prod
      path: k8s/argocd/notifications
    # 정책 설정
    - repoURL: https://github.com/OrangesCloud/wealist-project-advanced-k8s.git
      targetRevision: k8s-deploy-prod
      path: k8s/argocd/kyverno/prod

실제 ArgoCD 설정 예시

# k8s/argocd/config/argocd-cm-patch.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
  namespace: argocd
data:
  # Google OAuth SSO 설정
  dex.config: |
    connectors:
    - type: google
      id: google
      name: Google
      config:
        clientID: $dex.google.clientID  # argocd-secret에서 주입
        clientSecret: $dex.google.clientSecret
        redirectURI: https://argocd.wealist.co.kr/api/dex/callback
        
  # 커스텀 Health Check
  resource.customizations.health.external-secrets.io_ExternalSecret: |
    hs = {}
    if obj.status ~= nil then
      if obj.status.conditions ~= nil then
        for i, condition in ipairs(obj.status.conditions) do
          if condition.type == "Ready" and condition.status == "False" then
            hs.status = "Degraded"
            hs.message = condition.message
            return hs
          end
          if condition.type == "Ready" and condition.status == "True" then
            hs.status = "Healthy"
            hs.message = "ExternalSecret is ready"
            return hs
          end
        end
      end
    end
    hs.status = "Progressing"
    hs.message = "Waiting for ExternalSecret"
    return hs

실제 적용 결과

배포 프로세스

이제 전체 배포 프로세스는 다음과 같습니다:

# 1. Terraform으로 EKS + ArgoCD Bootstrap
cd terraform/prod/compute
terraform apply

# 2. ArgoCD가 자동으로 모든 애플리케이션 배포
# (수동 작업 없음!)

# 3. 애플리케이션 업데이트는 Git Push만으로
git add .
git commit -m "Update user-service to v1.2.3"
git push origin k8s-deploy-prod
# ArgoCD가 자동으로 감지하고 배포

장점들

1. 완전한 GitOps

  • 모든 설정이 Git에 있음
  • 변경 이력 추적 가능
  • 롤백 간단

2. 자동 복구

  • Terraform 재배포 후에도 ArgoCD가 모든 설정 자동 복구
  • 수동 작업 최소화

3. 명확한 의존성 관리

  • sync-wave로 배포 순서 보장
  • 각 애플리케이션의 상태를 ArgoCD UI에서 확인 가능

배운 점과 주의사항

핵심 교훈

1. Bootstrap은 최소한으로

  • Terraform에서는 정말 필요한 것만 설치
  • 나머지는 모두 ArgoCD에 맡기기
  • (기업별로 어디에 중점을 더 두냐에 따라서 엔지니어의 역할이 달라질수 있다고 생각합니다.)

2. 순환 의존성은 미리 파악하기

  • 의존성 그래프를 그려보고 시작
  • 어떤 것을 먼저 설치해야 하는지 명확히 하기

3. ArgoCD 자체도 GitOps 대상

  • ArgoCD 설정도 Git으로 관리해야 진정한 GitOps
  • 수동 설정은 언젠가 문제가 됨

주의사항

1. CRD 설치 타이밍

# CRD가 완전히 등록될 때까지 대기 필요
resource "time_sleep" "wait_for_eso_crds" {
  depends_on = [helm_release.external_secrets]
  create_duration = "30s"  # 충분한 대기 시간
}

2. kubectl_manifest vs kubernetes_manifest

# kubernetes_manifest는 plan 시 CRD 검증을 하므로
# CRD가 아직 없으면 실패함
# kubectl_manifest는 apply 시에만 검증하므로 안전
resource "kubectl_manifest" "cluster_secret_store" {
  yaml_body = <<-YAML
    apiVersion: external-secrets.io/v1  # CRD 필요
    kind: ClusterSecretStore
  YAML
}

3. ArgoCD Health Check 설정

  • ExternalSecret, ClusterSecretStore 등의 커스텀 리소스는 기본 Health Check가 없음
  • 직접 정의해야 sync-wave가 제대로 동작함

마치며

EKS에서 ArgoCD를 구축하면서 가장 어려웠던 부분은 **"어디까지 Terraform에서 하고, 어디서부터 ArgoCD에 맡길 것인가"**를 결정하는 것이었습니다.

결국 답은 최소한의 Bootstrap만 Terraform에서 하고, 나머지는 모두 GitOps로 관리하는 것이었어요. 특히 순환 의존성 문제는 External Secrets Operator를 먼저 설치하는 것으로 깔끔하게 해결할 수 있었습니다.

이제는 새로운 서비스를 추가하거나 설정을 변경할 때 Git에 커밋만 하면 되니까, 정말 편해졌습니다. 무엇보다 **"Infrastructure as Code"**가 아니라 **"Infrastructure as Git"**이 된 느낌이에요.

여러분도 비슷한 고민을 하고 계신다면, 이 글이 조금이나마 도움이 되길 바랍니다. 궁금한 점이나 다른 접근 방법이 있으시면 댓글로 공유해주세요!

참고 자료

  • ArgoCD Official Documentation
  • External Secrets Operator
  • Terraform AWS EKS Module
  • App of Apps Pattern

'Network' 카테고리의 다른 글

Kind 클러스터에서 SandboxChanged 에러 해결하기: systemd와 containerd의 갈등 해소  (1) 2026.01.20
Istio로 대규모 트래픽 관리하기: 실전 적용 사례와 확장 전략  (2) 2026.01.10
실전 Kubernetes 보안: 4C 모델을 프로덕션 클러스터에 적용하며 배운 것들  (0) 2026.01.04
GitHub Actions Docker CI 파이프라인 구조 이해하기: 멀티 아키텍처 빌드부터 캐시 전략  (1) 2026.01.03
SealSecret vs AWS Secret Manager  (0) 2026.01.02
'Network' 카테고리의 다른 글
  • Kind 클러스터에서 SandboxChanged 에러 해결하기: systemd와 containerd의 갈등 해소
  • Istio로 대규모 트래픽 관리하기: 실전 적용 사례와 확장 전략
  • 실전 Kubernetes 보안: 4C 모델을 프로덕션 클러스터에 적용하며 배운 것들
  • GitHub Actions Docker CI 파이프라인 구조 이해하기: 멀티 아키텍처 빌드부터 캐시 전략
Ry-
Ry-
  • Ry-
    developer_Ryu
    Ry-
  • 전체
    오늘
    어제
    • 분류 전체보기 (97) N
      • AI (14)
      • DB (3)
      • Book (6)
      • Development Environment (2)
        • Mac (2)
      • Frontend (8)
        • Flutter (6)
        • React (1)
        • React-Native (1)
      • Backend (19)
        • Spring (14)
        • Java (1)
        • Node.js (4)
      • Computer Science (27)
        • PS (20)
        • CS (7)
      • Network (12)
      • 이것저것 (3) N
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      종만북
      aop
      BOGGLE
      최대 힙
      spring
      백준
      Baekjoon
      몽고디비
      node.js
      구글 로그인
      mongodb
      티스토리챌린지
      mysql
      java
      오블완
      chatgpt api
      database
      redis
      스프링
      flutter
      Kakao
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.6
    Ry-
    EKS에서 ArgoCD로 GitOps 구축하기: 순환 의존성 해결부터 Single Sync까지
    상단으로

    티스토리툴바