본문 바로가기
Cloud Computing & MSA

쿠버네티스에 Argo CD로 GitOps 파이프라인 깔기 - Application, 자동 sync, self-heal로 kubectl apply 손절하기

냉장고를사다줘·2026년 9월 5일·조회 1

클러스터를 몇 명이서 나눠 쓰다 보면 어느 순간 아무도 지금 떠 있는 게 Git의 어느 커밋인지 자신 있게 말하지 못하는 상태가 된다. 누군가 급해서 kubectl edit로 replicas를 바꿔놨고, 다른 누군가는 로컬 매니페스트를 kubectl apply로 밀어넣었다. 나도 예전에 그렇게 굴리다가 롤백 한 번에 한참을 헤맨 적이 있어서, 새 클러스터를 잡을 때는 처음부터 Argo CD를 깐다.

결론부터 말하자면, Argo CD는 Git 저장소를 단일 진실원천(SSOT)으로 두고 클러스터 상태를 그 저장소에 맞춰 계속 수렴시키는 컨트롤러다. 매니페스트를 Git에 커밋하면 Argo CD가 알아서 sync하고, 누가 클러스터를 손으로 건드리면 self-heal이 원상복구한다. 아래에서는 설치, Application 정의, 자동 sync와 self-heal 설정, 그리고 삭제 동작까지 차례로 살펴본다.

1. GitOps와 Argo CD가 뭘 하는 물건인가

GitOps는 배포의 기준을 클러스터가 아니라 Git 저장소에 두는 방식이다. "지금 떠 있어야 하는 상태"를 YAML로 적어 Git에 커밋하면, 그 커밋이 곧 배포 명령이 된다. 사람이 클러스터에 직접 명령을 쏘는 게 아니라, 저장소와 클러스터의 차이를 좁히는 에이전트가 대신 일한다.

Argo CD가 바로 그 에이전트다. 쿠버네티스 안에서 도는 컨트롤러로, Git 저장소를 주기적으로 들여다보며 원하는 상태(desired state)와 실제 클러스터 상태(live state)를 비교한다. 둘이 다르면 OutOfSync로 표시하고, 설정에 따라 자동으로 맞춰준다. 사람이 하던 kubectl apply를 컨트롤러가 대신하는 셈이라고 보면 된다.

Application은 Argo CD의 핵심 커스텀 리소스(CRD)다. "이 Git 저장소의 이 경로를, 이 클러스터의 이 네임스페이스에 배포하라"를 한 덩어리로 묶은 정의다. 하나의 Application이 하나의 배포 단위가 된다.

2. 설치

전용 네임스페이스를 만들고 공식 install 매니페스트를 적용한다. CRD 일부가 어노테이션 크기 한도(262KB)를 넘어서 --server-side가 필요하다. 이걸 빼면 metadata.annotations: Too long 에러가 나면서 설치가 깨진다. 처음에 이걸로 한 번 걸렸다.

kubectl create namespace argocd
kubectl apply -n argocd --server-side --force-conflicts \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

파드가 다 뜰 때까지 기다린다. argocd-server, argocd-repo-server, argocd-application-controller, argocd-redis, argocd-dex-server 등이 올라온다.

kubectl get pods -n argocd

NAME                                  READY   STATUS    RESTARTS   AGE
argocd-application-controller-0       1/1     Running   0          90s
argocd-repo-server-6b7f8c9d4-xk2lp    1/1     Running   0          90s
argocd-server-7d9f5c6b8-mn4qr         1/1     Running   0          90s
argocd-redis-5c8b9f7d6-p2wxz          1/1     Running   0          90s
argocd-dex-server-6f4c8b9d7-t8jkl     1/1     Running   0          90s

CLI도 깔아둔다. macOS면 brew install argocd, 리눅스면 릴리스 바이너리를 받는다.

웹 UI와 CLI 접속

argocd-server 서비스는 기본이 ClusterIP라 외부에서 안 보인다. 일단 port-forward로 붙는다.

kubectl port-forward svc/argocd-server -n argocd 8080:443

브라우저에서 https://localhost:8080으로 접속한다. 자체 서명 인증서라 경고가 뜨는데 그냥 넘어간다. 초기 admin 비밀번호는 argocd-initial-admin-secret 시크릿에 들어있다.

argocd admin initial-password -n argocd

이 값으로 CLI 로그인한다. 로그인 후 비밀번호를 바꾸고, 안내대로 초기 시크릿은 지우는 게 좋다.

argocd login localhost:8080 --username admin --password <위에서_나온_값> --insecure
argocd account update-password
kubectl -n argocd delete secret argocd-initial-admin-secret

3. Application 만들기 - 명령형과 선언형

Application은 두 가지 방법으로 만든다. 급하게 하나 띄워볼 때는 CLI가 편하다.

argocd app create guestbook \
  --repo https://github.com/argoproj/argocd-example-apps.git \
  --path guestbook \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace default

다만 이 방식은 그 자체가 다시 "사람이 손으로 친 명령"이라, GitOps의 취지와 어긋난다. Application 정의부터 Git에 두는 게 맞다. 선언형 매니페스트로 쓴다.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: guestbook
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/argoproj/argocd-example-apps.git
    targetRevision: HEAD
    path: guestbook
  destination:
    server: https://kubernetes.default.svc
    namespace: guestbook
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

필드를 하나씩 본다.

  • metadata.namespace: Application 리소스 자체는 argocd 네임스페이스에 산다. 배포 대상 네임스페이스와 헷갈리지 말 것.
  • spec.source: 저장소 URL, 참조할 리비전(HEAD나 태그, 브랜치), 저장소 안 경로. 이 셋이 "원하는 상태"의 출처다.
  • spec.destination: 배포할 클러스터 API 주소와 네임스페이스. https://kubernetes.default.svc는 Argo CD가 도는 그 클러스터 자기 자신을 가리킨다.
  • syncOptions: CreateNamespace=true: 대상 네임스페이스가 없으면 만들어준다. 이게 없고 네임스페이스도 없으면 sync가 namespaces "guestbook" not found로 실패한다.
  • finalizers: 뒤에서 다시 다룬다. Application을 지울 때 배포한 리소스까지 같이 지울지를 정한다.

이 파일을 kubectl apply로 클러스터에 넣는다. Application 매니페스트를 관리하는 Git 저장소(흔히 app-of-apps 패턴)를 따로 두고 그걸 다시 Argo CD가 보게 만드는 구성도 있지만, 시작은 이 정도로 충분하다.

kubectl apply -f guestbook-app.yaml

application.argoproj.io/guestbook created

4. 자동 sync와 self-heal 설정

syncPolicy.automated가 GitOps의 핵심이다. 이걸 켜야 Git 커밋이 곧 배포가 된다. 안 켜면 Application이 OutOfSync로 감지만 하고 실제 반영은 사람이 argocd app sync를 눌러야 한다.

기본값은 셋 다 꺼져 있다는 점을 기억해야 한다. Argo CD는 안전을 우선해서, 아무 옵션 없이 automated: {}만 켜면 Git에 있는 것만 반영하고 지우지는 않는다.

prune - Git에서 지운 리소스를 클러스터에서도 지운다

prune은 기본이 꺼져 있다. Git에서 어떤 리소스 정의를 삭제해도, prune이 꺼져 있으면 클러스터에는 그대로 남는다. 이런 리소스를 Argo CD는 곧바로 지우지 않고 OutOfSync에 "pruned 대상"으로만 표시한다. 실수로 매니페스트를 지웠을 때 서비스가 통째로 날아가는 걸 막는 안전장치다. Git을 진짜 SSOT로 쓰려면 켜는 게 맞다.

selfHeal - 클러스터를 손으로 건드리면 되돌린다

selfHeal도 기본이 꺼져 있다. 켜면 Git은 그대로인데 클러스터 쪽이 바뀐 경우, 즉 누가 kubectl editkubectl scale로 라이브 상태를 건드린 경우에도 Argo CD가 Git 기준으로 다시 sync한다. 꺼져 있으면 Argo CD는 자동 sync를 대상이 OutOfSync일 때만 수행하는데, 이 판단은 Git 변경을 기준으로 삼기 때문에 클러스터만 바뀐 drift는 자동으로 복구되지 않는다.

CLI로 켜고 끄기

매니페스트를 안 고치고 즉석에서 바꾸려면 CLI로도 된다.

argocd app set guestbook --sync-policy automated
argocd app set guestbook --auto-prune
argocd app set guestbook --self-heal

다만 CLI로 바꾼 설정 역시 Git에 없는 변경이다. 운영에서는 매니페스트에 박아두고 커밋하는 쪽을 권한다.

5. 동작 확인

Application 상태부터 본다. Sync StatusSynced, Health StatusHealthy면 정상이다.

argocd app get guestbook

Name:               argocd/guestbook
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          guestbook
URL:                https://localhost:8080/applications/guestbook
Repo:               https://github.com/argoproj/argocd-example-apps.git
Target:             HEAD
Path:               guestbook
Sync Policy:        Automated (Prune)
Sync Status:        Synced to HEAD (53e28ff)
Health Status:      Healthy

self-heal이 정말 도는지 눈으로 보려면 클러스터를 일부러 흐트러뜨려 본다. Deployment의 replicas를 손으로 바꾼다.

kubectl -n guestbook scale deploy guestbook-ui --replicas=5

deployment.apps/guestbook-ui scaled

잠시 뒤 다시 조회하면 Argo CD가 Git 기준(예: replicas 1)으로 되돌려놓은 걸 확인할 수 있다. drift가 감지되면 OutOfSync로 잠깐 떴다가 self-heal이 sync를 걸어 다시 Synced로 돌아온다.

kubectl -n guestbook get deploy guestbook-ui

NAME           READY   UP-TO-DATE   AVAILABLE   AGE
guestbook-ui   1/1     1            1           6m

여기서 한 가지 함정. self-heal이 즉시 반응하지는 않는다. 컨트롤러의 조정 주기와 drift 감지 사이에 짧은 지연이 있어서, 환경에 따라 다르지만 대략 수 초에서 수십 초 정도 뒤에 복구된다. "바로 안 돌아오는데?" 하고 조급해할 필요는 없다.

Git 쪽 변경도 확인해 본다. 저장소에서 이미지 태그를 바꿔 커밋하고 push하면, 자동 sync가 켜져 있는 한 사람이 아무것도 안 해도 새 버전이 롤아웃된다. 반영 이력은 argocd app history guestbook로 본다.

6. Application 삭제와 finalizer

Application을 지울 때 배포한 리소스까지 같이 지울지가 finalizers로 갈린다. 이걸 모르고 지웠다가 Deployment와 Service가 클러스터에 그대로 남아 유령처럼 도는 경우가 생긴다.

resources-finalizer.argocd.argoproj.io finalizer가 매니페스트에 있으면 Application을 지울 때 그 Application이 관리하던 리소스까지 연쇄(cascade) 삭제한다. finalizer가 없으면 Application 오브젝트만 사라지고 배포된 워크로드는 클러스터에 남는다.

# 연쇄 삭제 (기본)
argocd app delete guestbook --cascade

# Application만 지우고 배포 리소스는 남기기
argocd app delete guestbook --cascade=false

연쇄 삭제의 전파 정책은 두 가지다. 기본인 foreground는 관리 리소스를 먼저 다 지운 뒤 Application을 제거하고, background는 Application을 먼저 지우고 리소스는 비동기로 정리한다. 매니페스트에서 background를 원하면 finalizer를 resources-finalizer.argocd.argoproj.io/background로 쓴다.

7. 정리

Argo CD를 깔고 Application에 syncPolicy.automatedpruneselfHeal을 켜면, Git 저장소가 배포의 유일한 기준이 된다. 사람은 kubectl apply를 치는 대신 Git에 커밋하고, 클러스터를 손으로 건드린 흔적은 self-heal이 되돌린다. 롤백은 Git revert로 끝나고, 지금 떠 있는 게 어느 커밋인지는 argocd app get 한 줄로 답이 나온다. prune과 self-heal의 기본값이 꺼져 있다는 점, 그리고 삭제 동작이 finalizer에 달려 있다는 점만 기억하면 초기 세팅에서 크게 헤맬 일은 없다.

자주 묻는 질문

자동 sync를 켰는데 배포가 반영되지 않는다.

Application이 OutOfSync 상태인지 먼저 확인한다. 자동 sync는 대상이 OutOfSync일 때만 수행되며, 이 판단은 기본적으로 Git 변경을 기준으로 한다. 클러스터만 손으로 바뀐 drift를 자동 복구하려면 selfHeal을 켜야 한다. 또 컨트롤러 조정 주기 때문에 즉시가 아니라 수 초에서 수십 초 지연이 있을 수 있다. argocd app get으로 Sync Status와 마지막 sync 결과를 확인한다.

prune과 selfHeal의 차이가 뭔가?

prune은 Git에서 삭제된 리소스를 클러스터에서도 지우는 옵션이다. selfHeal은 Git은 그대로인데 클러스터 쪽이 바뀐 경우(kubectl edit 등) Git 기준으로 되돌리는 옵션이다. 방향이 다르다. 둘 다 기본값은 꺼져 있으며, Git을 완전한 SSOT로 쓰려면 함께 켠다.

install 시 --server-side 없이 apply하면 왜 실패하나?

Argo CD의 일부 CRD가 어노테이션 크기 한도인 262KB를 넘어서, 클라이언트 사이드 apply로는 metadata.annotations Too long 에러가 난다. 그래서 공식 설치 명령이 --server-side --force-conflicts를 쓴다. --force-conflicts는 다른 도구가 잡고 있던 필드 소유권을 넘겨받게 해준다.

Application을 지웠더니 배포한 파드가 클러스터에 그대로 남았다.

metadata.finalizers에 resources-finalizer.argocd.argoproj.io가 없으면 Application 오브젝트만 삭제되고 관리 리소스는 남는다. 연쇄 삭제를 원하면 이 finalizer를 넣거나 argocd app delete APPNAME --cascade로 지운다. 반대로 리소스를 보존하려면 --cascade=false를 쓴다.

CLI로 argocd app create 하는 것과 매니페스트로 만드는 것 중 뭘 써야 하나?

운영에서는 선언형 매니페스트를 Git에 두고 kubectl apply하는 쪽을 권한다. CLI create나 argocd app set은 그 자체가 Git에 남지 않는 수동 변경이라 GitOps의 취지와 어긋난다. CLI는 빠르게 하나 띄워 검증할 때만 쓰고, 정착시킬 때는 매니페스트로 옮긴다.

관련 글

댓글 0

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.