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가지를 반드시 지킬 것.
- spec 일치: 새 Application의 name, namespace, source.path, helm.releaseName, destination, valueFiles가 기존 ApplicationSet 렌더 결과와 동일해야 Argo CD가 기존 helm release를 adopt (diff 0)
- orphan 선행: ApplicationSet이 element 삭제 시 자식 Application을 cascade 삭제하지 못하도록 ownerReferences를 먼저 제거
- 원자적 커밋: ApplicationSet elements 제거 + 새 Application yaml 추가를 같은 커밋으로 push (분리하면 reconcile 레이스 발생)
템플릿
원본 ApplicationSet template.spec을 그대로 복사해서 kind: ApplicationSet → kind: 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) → 자식 순.
- root Application sync → ApplicationSet 업데이트 + 새 자식 Application 리소스 생성
- 새 자식 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가 소실될 수 있다.
롤백
- 새
<name>.yaml삭제 - ApplicationSet의 elements에 해당 블록 복원
- 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 + 워크로드 소실 |