Argo CD ApplicationSet을 App-of-Apps로 안전하게 전환하기

Table of Contents

list generator 기반 Argo CD ApplicationSet을 파일 단위 Application(App-of-Apps)으로 안전하게 전환하는 일반 절차.

언제?

  • ApplicationSet 템플릿으로 생성되던 자식 Application들을 개별 git 파일로 관리하고 싶을 때
  • StatefulSet/PVC 등 데이터 보존이 필요한 워크로드를 무중단으로 옮겨야 할 때

원칙

[!CAUTION] StatefulSet/PVC를 쓰는 차트는 잘못 전환하면 cascade prune으로 볼륨까지 삭제될 수 있다. 아래 3가지를 반드시 지킬 것.

  1. spec 일치: 새 Application의 name, namespace, source.path, helm.releaseName, destination, valueFiles가 기존 ApplicationSet 렌더 결과와 동일해야 Argo CD가 기존 helm release를 adopt (diff 0)
  2. orphan 선행: ApplicationSet이 element 삭제 시 자식 Application을 cascade 삭제하지 못하도록 ownerReferences를 먼저 제거
  3. 원자적 커밋: ApplicationSet elements 제거 + 새 Application yaml 추가를 같은 커밋으로 push (분리하면 reconcile 레이스 발생)

템플릿

원본 ApplicationSet template.spec을 그대로 복사해서 kind: ApplicationSetkind: Application으로 바꾸고 제너레이터 변수({{.app}}, {{.namespace}} 등)를 실제 값으로 치환한다.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: <generated-name>          # ApplicationSet 템플릿이 렌더했던 이름과 동일
  namespace: argocd
  labels: { ... }                 # ApplicationSet template.metadata에 있던 것 복사
  annotations: { ... }            # notifications 등 포함해서 그대로 복사
spec:
  project: <project>
  source:
    repoURL: <repo-url>
    targetRevision: <rev>
    path: <chart-path>            # 템플릿 렌더 결과와 동일
    helm:
      releaseName: <release>      # 템플릿 렌더 결과와 동일
      valueFiles: [ ... ]         # 템플릿 렌더 결과와 동일 (중복 항목도 그대로)
  destination: { ... }
  syncPolicy: { ... }

[!TIP] 변환 전에 기존 자식 Application의 live spec을 덤프해서 그대로 yaml로 쓰는 게 가장 안전하다.

kubectl -n argocd get application <name> -o yaml \
  | yq 'del(.status, .metadata.creationTimestamp, .metadata.generation, .metadata.managedFields, .metadata.resourceVersion, .metadata.uid, .metadata.ownerReferences)' \
  > apps/.../<name>.yaml

전환 절차 (차트 1개 기준 — 반복 적용)

1. 대상 Application의 현재 상태 확인

kubectl -n argocd get application <name>
kubectl -n argocd get application <name> -o jsonpath='{.metadata.ownerReferences}{"\n"}'

Synced/Healthy인지, ownerReferences가 ApplicationSet을 가리키는지 확인.

2. orphan 패치 — ApplicationSet 소유권 끊기

kubectl -n argocd patch application <name> --type=json \
  -p='[{"op":"remove","path":"/metadata/ownerReferences"}]'

[!NOTE] 이미 비어있으면 The request is invalid 에러가 나오지만 무시해도 된다.

# 확인
kubectl -n argocd get application <name> -o jsonpath='{.metadata.ownerReferences}{"\n"}'

3. git 변경 (같은 커밋)

  • ApplicationSet yaml의 generators.list.elements에서 대상 블록 제거
  • <name>.yaml 파일 추가 (위 템플릿)
git add <appset-file> <new-app-file>
git commit -m "chore: migrate <name> from ApplicationSet to Application"
git push

[!WARNING] 따로 push하면 ApplicationSet reconcile 타이밍에 따라 ownerReferences가 재부착되거나 cascade 삭제가 트리거될 수 있다.

4. sync 순서

root (app-of-apps) → 자식 순.

  1. root Application sync → ApplicationSet 업데이트 + 새 자식 Application 리소스 생성
  2. 새 자식 Application이 automated sync로 알아서 돌거나, 수동이면 그다음에 sync

5. 검증

argocd app get <name> | grep -E 'Health Status|Sync Status'
kubectl -n <ns> get sts,pvc      # StatefulSet/PVC 건재 확인

Synced/Healthy + PVC 보존되면 성공.

전부 옮긴 후 ApplicationSet 제거

모든 element를 옮겨 ApplicationSet이 비었을 때:

# git
rm <appset-file>
git add <appset-file>
git commit -m "chore: remove empty ApplicationSet"
git push

# 클러스터 (cascade=orphan 필수)
kubectl -n argocd delete applicationset <appset-name> --cascade=orphan

[!CAUTION] --cascade=orphan을 빼면 자식 Application들이 같이 삭제되고 → prune: true 설정에 따라 워크로드/PVC가 소실될 수 있다.

롤백

  1. <name>.yaml 삭제
  2. ApplicationSet의 elements에 해당 블록 복원
  3. push

[!NOTE] ownerReferences가 비어있어도 이름이 일치하면 ApplicationSet 컨트롤러가 재흡수한다.

자주 하는 실수

실수 결과
orphan 없이 ApplicationSet element 제거 cascade 삭제 가능성
element 제거와 새 파일 push를 다른 커밋으로 분리 reconcile 레이스
releaseName / namespace / path 중 하나라도 기존과 다름 adoption 실패 → 새 helm release 생성 → 충돌
valueFiles 중복 나열 정리를 동시에 진행 spec diff 발생 → 재싱크 1회 (adoption 후 별도 커밋에서 정리)
ApplicationSet 삭제 시 --cascade=orphan 누락 자식 Application + 워크로드 소실