Table of contents
- 싱크, 그냥 누르면 되는 거 아닌가?
- Auto Sync — 자동 동기화
- Prune — 삭제까지 자동으로 할 것인가
- Self Heal — 드리프트 자동 복구
- Sync Wave — 배포 순서 제어
- Sync Hook — 싱크 전후 작업 실행
- Retry 정책 — 실패 시 재시도
- 싱크 옵션 조합 — 환경별 전략
싱크, 그냥 누르면 되는 거 아닌가?
ArgoCD를 처음 쓰면 Sync 버튼 하나로 모든 게 해결되는 것처럼 보인다. Git에 매니페스트를 푸시하고, UI에서 Sync를 누르면 클러스터에 반영된다. 깔끔하고 단순하다.
그런데 실제 운영 환경으로 들어가면 질문이 쏟아진다. 매번 수동으로 싱크해야 하나? 누군가 kubectl로 직접 수정하면 어떻게 되지? 더 이상 필요 없는 리소스는 자동으로 지워지나? ConfigMap보다 Deployment가 먼저 배포되어야 하는데 순서를 어떻게 잡을까?
이 질문들에 대한 답이 ArgoCD의 싱크 전략이다. 단순한 “적용” 버튼 이상으로, 배포의 자동화 수준과 안전성을 세밀하게 조절할 수 있는 체계가 갖춰져 있다.
Auto Sync — 자동 동기화
기본적으로 ArgoCD는 Git 변경을 감지만 할 뿐, 자동으로 싱크하지 않는다. 수동 싱크가 기본값인 건 의도적인 설계인데, 프로덕션에 자동 배포가 걸려 있으면 실수 한 번에 장애로 이어질 수 있기 때문이다.
하지만 개발 환경이나 스테이징에서는 이야기가 다르다. 커밋할 때마다 자동으로 반영되길 원하는 경우가 대부분이고, 이때 Auto Sync를 켠다.
Application 매니페스트에서 syncPolicy.automated를 추가하면 된다.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/my-org/my-app.git
targetRevision: main
path: k8s/overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: my-app
syncPolicy:
automated:
prune: false
selfHeal: false
이 설정만으로 ArgoCD는 Git과 클러스터 상태가 달라질 때 자동으로 싱크를 시작한다. 폴링 주기는 기본 3분이지만, Git Webhook을 연결하면 거의 실시간으로 반응하게 만들 수 있다.
Auto Sync에는 두 가지 하위 옵션이 붙는다. prune과 selfHeal이다. 이 둘은 Auto Sync를 켤 때 함께 고민해야 할 핵심 옵션이므로 바로 이어서 살펴보자.
Prune — 삭제까지 자동으로 할 것인가
Git에서 매니페스트 파일을 삭제했다고 가정해보자. 클러스터에는 해당 리소스가 여전히 살아 있다. ArgoCD는 이걸 OutOfSync로 표시하지만, 기본적으로 삭제하지는 않는다.
prune: true를 설정하면 Git에서 사라진 리소스를 클러스터에서도 자동으로 제거한다.
syncPolicy:
automated:
prune: true
편리하지만 위험할 수 있다. 누군가 실수로 매니페스트 파일을 지우면 프로덕션 리소스가 사라진다는 뜻이기도 하니까. 그래서 보통 개발/스테이징에서는 prune: true, 프로덕션에서는 prune: false로 두고 수동 정리하는 전략을 택한다.
특정 리소스만 prune 대상에서 제외하고 싶다면 어노테이션을 활용할 수 있다.
metadata:
annotations:
argocd.argoproj.io/sync-options: Prune=false
이 어노테이션이 붙은 리소스는 Git에서 사라져도 클러스터에 남아 있게 된다. PersistentVolumeClaim처럼 함부로 지우면 안 되는 리소스에 붙여두면 안전장치가 하나 더 생기는 셈이다.
Self Heal — 드리프트 자동 복구
클러스터 운영을 하다 보면 누군가 kubectl edit으로 직접 리소스를 수정하는 일이 종종 벌어진다. 급한 장애 대응이든 실수든, Git에 반영되지 않은 변경이 생기면 Git과 클러스터 사이에 드리프트가 발생한다.
selfHeal: true는 이 드리프트를 감지해서 자동으로 Git 상태로 되돌린다.
syncPolicy:
automated:
prune: true
selfHeal: true
Self Heal이 켜져 있으면 누군가 kubectl로 replica 수를 바꿔도 ArgoCD가 몇 초 안에 원래 값으로 되돌려 놓는다. GitOps의 핵심 원칙인 “Git이 유일한 진실의 원천”을 강제하는 기능이라 볼 수 있다.
다만 주의할 점이 있다. HPA(Horizontal Pod Autoscaler)와 함께 쓸 때는 충돌이 생길 수 있다. HPA가 replica를 늘렸는데 Self Heal이 다시 줄이는 상황이 반복될 수 있기 때문이다. 이 경우 Deployment의 spec.replicas 필드를 Git 매니페스트에서 아예 빼거나, 아래처럼 특정 필드를 무시하도록 설정한다.
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
이렇게 하면 ArgoCD가 replicas 필드의 차이는 무시하므로 HPA와 평화롭게 공존할 수 있다.
Sync Wave — 배포 순서 제어
Kubernetes 리소스 간에는 의존 관계가 있다. Namespace가 먼저 있어야 그 안에 리소스를 만들 수 있고, ConfigMap이나 Secret이 준비된 뒤에 Deployment가 올라와야 정상 동작한다.
ArgoCD는 Sync Wave라는 메커니즘으로 이 순서를 제어한다. 각 리소스에 wave 번호를 지정하면, 낮은 번호부터 순차적으로 싱크된다.
Wave별 실행 순서와 각 단계에서 어떤 리소스가 배포되는지 흐름을 보자.
flowchart LR
W1["Wave -1\nCRD 정의"]
W0["Wave 0\nNamespace"]
W1a["Wave 1\nConfigMap, Secret"]
W2["Wave 2\nDeployment, StatefulSet"]
W3["Wave 3\nService, Ingress"]
W1 -->|"Healthy 확인 후"| W0
W0 -->|"Healthy 확인 후"| W1a
W1a -->|"Healthy 확인 후"| W2
W2 -->|"Healthy 확인 후"| W3
각 wave가 완료되고 모든 리소스가 Healthy 상태가 되어야 다음 wave로 넘어간다. 아래 예시 매니페스트를 보면 이 순서가 어노테이션으로 어떻게 표현되는지 알 수 있다.
# 0번 wave: Namespace 먼저
apiVersion: v1
kind: Namespace
metadata:
name: my-app
annotations:
argocd.argoproj.io/sync-wave: "0"
---
# 1번 wave: ConfigMap과 Secret
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: my-app
annotations:
argocd.argoproj.io/sync-wave: "1"
data:
DATABASE_HOST: "db.example.com"
LOG_LEVEL: "info"
---
# 2번 wave: Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: my-app
annotations:
argocd.argoproj.io/sync-wave: "2"
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-org/my-app:latest
envFrom:
- configMapRef:
name: app-config
---
# 3번 wave: Service
apiVersion: v1
kind: Service
metadata:
name: my-app
namespace: my-app
annotations:
argocd.argoproj.io/sync-wave: "3"
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
wave 번호가 같은 리소스들은 동시에 싱크되고, 해당 wave의 모든 리소스가 정상(Healthy) 상태가 되어야 다음 wave로 넘어간다. 어노테이션을 안 붙이면 기본값은 0이다.
wave에 음수를 쓸 수도 있다. 다른 모든 것보다 먼저 처리해야 하는 CRD 같은 리소스에 -1을 달아두면 유용하다.
Sync Hook — 싱크 전후 작업 실행
배포 전에 DB 마이그레이션을 돌려야 하거나, 배포 후에 스모크 테스트를 실행해야 하는 경우가 있다. ArgoCD는 Sync Hook을 통해 싱크 라이프사이클의 특정 시점에 Job이나 Pod를 실행할 수 있게 해준다.
지원하는 Hook 종류는 다음과 같다.
| Hook | 실행 시점 |
|---|---|
PreSync | 싱크 시작 전. DB 마이그레이션, 스키마 변경에 적합 |
Sync | 메인 리소스와 함께 적용 |
PostSync | 모든 리소스가 Healthy 상태가 된 후. 스모크 테스트, 알림 전송 |
SyncFail | 싱크가 실패했을 때. 롤백 알림이나 정리 작업 |
DB 마이그레이션을 PreSync Hook으로 거는 예시를 보자.
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
namespace: my-app
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
containers:
- name: migrate
image: my-org/db-migrate:latest
command: ["./migrate", "up"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
restartPolicy: Never
backoffLimit: 3
hook-delete-policy는 Hook 리소스의 정리 정책을 결정한다. HookSucceeded로 설정하면 Job이 성공한 뒤 자동으로 삭제되므로 완료된 Job이 쌓이지 않는다. BeforeHookCreation을 쓰면 다음 싱크 때 이전 Hook을 먼저 지우고 새로 생성하는 방식으로 동작한다.
PostSync로 배포 후 알림을 보내는 것도 흔한 패턴이다.
apiVersion: batch/v1
kind: Job
metadata:
name: notify-deploy
namespace: my-app
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
containers:
- name: notify
image: curlimages/curl:latest
command:
- curl
- -X
- POST
- -H
- "Content-Type: application/json"
- -d
- '{"text":"my-app 배포가 완료되었습니다."}'
- https://hooks.slack.com/services/T00/B00/xxxx
restartPolicy: Never
backoffLimit: 1
Hook과 Sync Wave를 결합할 수도 있다. PreSync Hook에 wave 번호를 달면 여러 PreSync 작업 사이의 순서까지 제어할 수 있다.
Retry 정책 — 실패 시 재시도
네트워크 순단, API 서버 일시 장애 같은 일시적 문제로 싱크가 실패하는 건 드문 일이 아니다. 매번 수동으로 다시 싱크하는 건 비효율적이므로, Retry 정책을 설정해두면 자동으로 재시도한다.
syncPolicy:
automated:
prune: true
selfHeal: true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
이 설정은 싱크 실패 시 최대 5회까지 재시도하되, 첫 번째 재시도는 5초 뒤, 두 번째는 10초 뒤, 세 번째는 20초 뒤… 이런 식으로 간격을 두 배씩 늘린다. 최대 대기 시간은 3분으로 제한된다.
exponential backoff 전략을 쓰는 이유는 단순하다. API 서버가 과부하 상태일 때 짧은 간격으로 계속 재시도하면 상황을 악화시키기만 할 뿐이기 때문이다. 간격을 점점 넓혀가며 서버가 회복할 시간을 주는 것이다.
싱크 옵션 조합 — 환경별 전략
지금까지 살펴본 옵션들을 환경별로 어떻게 조합하면 좋을지 정리해보자.
개발 환경에서는 빠른 피드백이 우선이다. 커밋하면 바로 반영되고, 불필요한 리소스도 자동 정리되길 원한다.
syncPolicy:
automated:
prune: true
selfHeal: true
retry:
limit: 3
backoff:
duration: 5s
factor: 2
maxDuration: 1m
스테이징은 프로덕션과 비슷한 흐름을 유지하되, 자동 싱크 정도는 켜두는 게 일반적이다. Prune은 상황에 따라 판단하면 된다.
syncPolicy:
automated:
prune: false
selfHeal: true
retry:
limit: 5
backoff:
duration: 10s
factor: 2
maxDuration: 3m
프로덕션은 신중해야 한다. 자동 싱크를 끄고, 수동 검토 후 싱크하는 방식이 안전하다. Self Heal만 켜두면 드리프트는 방지하면서도 의도하지 않은 배포는 막을 수 있다.
syncPolicy:
automated:
prune: false
selfHeal: true
retry:
limit: 5
backoff:
duration: 30s
factor: 2
maxDuration: 5m
물론 이건 하나의 가이드라인이지 정답은 아니다. 팀의 성숙도와 CI/CD 파이프라인의 구성에 따라 프로덕션에서도 Auto Sync를 쓰는 조직이 있고, 개발 환경에서도 수동 싱크를 선호하는 팀이 있다.
싱크 전략은 ArgoCD 운영의 근간이다. 어떤 옵션을 켜고 끄느냐에 따라 배포의 자동화 수준과 안전성이 결정된다. 다음 편에서는 여러 클러스터를 하나의 ArgoCD로 관리하는 방법과, ApplicationSet으로 반복적인 Application 정의를 자동화하는 방법을 살펴본다.
Loading comments...