선언적 상태
명령 순서가 아니라 최종적으로 도달해야 할 상태를 YAML, Helm values, Kustomize overlay 같은 파일로 표현한다.
실행 로그보다 리뷰 가능한 목표 상태가 더 중요할 때.분류: Layer 5 - 플랫폼 엔지니어링 & 자동화 | 선수지식: CI/CD Basics, Git Flow, Kubernetes Basics, Terraform/IaC
GitOps는 Git 저장소에 선언한 원하는 상태(desired state)를 기준으로, 클러스터 내부의 컨트롤러가 실제 상태(actual state)를 계속 비교하고 맞추는 운영 방식이다.
핵심은 “CI가 배포 명령을 밀어 넣는다”가 아니라 “Git에 승인된 목표 상태를 남기고, 클러스터가 그 상태를 당겨와 수렴한다”는 점이다. 그래서 GitOps는 단순 배포 도구가 아니라 권한 경계, 감사 이력, 상태 복구 방식을 함께 바꾸는 패턴이다.
일반적인 push CI/CD에서는 CI 서버가 빌드, 테스트, 배포 스크립트 실행까지 맡는다. 이 구조 자체가 잘못은 아니다. 작은 팀이나 단일 서비스에서는 빠르고 단순하다. 문제는 CI 서버가 운영 클러스터의 kubeconfig, 토큰, 배포 스크립트까지 쥐는 순간부터 커진다.
첫 번째 한계는 권한 집중이다. CI 서버가 침해되면 공격자는 빌드 결과물을 바꾸는 데서 멈추지 않고 운영 클러스터에 직접 쓰기 작업을 할 수 있다. 배포 권한이 넓을수록 “빌드 시스템 장애”가 “운영 환경 침해”로 번진다.
두 번째 한계는 상태 드리프트(drift)다.
Drift는 Git에 기록된 상태와 클러스터 실제 상태가 달라진 상태를 뜻한다.
예를 들어 Git에는 replicas: 3이 남아 있는데 장애 대응 중 누군가 kubectl scale로 클러스터를 replicas: 5로 바꿨다면,
Git 이력만 보고는 현재 운영 상태를 설명할 수 없다.
세 번째 한계는 복구 재현성이다. 장애 후 “마지막으로 정상 동작하던 상태”로 돌아가려면 배포 스크립트, CI 로그, 수동 수정 내역을 함께 뒤져야 한다. 반대로 GitOps에서는 Git revision이 복구 기준점이 된다. 롤백도 “어떤 명령을 다시 실행할까”보다 “어떤 Git 상태로 수렴시킬까”에 가깝다.
GitOps는 이 문제를 명령 로그가 아니라 목표 상태 모델로 푼다. Git은 승인된 desired state의 단일 진실 공급원(single source of truth)이 되고, 클러스터 내부의 reconciliation loop가 실제 상태를 그 방향으로 맞춘다.
처음 읽을 때는 도구 이름보다 아래 단어를 먼저 잡아야 한다. 도구가 Argo CD든 Flux든 사고 모델은 거의 같다.
| 용어 | 첫 정의 | 왜 중요한가 |
|---|---|---|
| GitOps | Git을 desired state와 승인 이력의 기준점으로 삼고 자동 수렴시키는 운영 방식 | 배포 자동화뿐 아니라 감사, 복구, 권한 분리를 함께 다룬다 |
| Desired state | Git에 선언한 목표 상태 | 컨트롤러가 맞추려는 기준이다 |
| Actual state | Kubernetes API에서 관찰한 현재 상태 | 장애 대응, 수동 수정, 다른 컨트롤러 때문에 desired state와 달라질 수 있다 |
| Single source of truth | 여러 장소가 아니라 Git 하나를 기준 상태로 삼는 원칙 | 사람이 콘솔과 CLI에서 따로 바꾸기 시작하면 추적성이 깨진다 |
| Reconciliation | 현재 상태를 관찰하고 차이를 계산한 뒤 목표 상태로 수렴시키는 반복 루프 | GitOps, Kubernetes Controller, Terraform 모두 같은 제어 패턴을 쓴다 |
| Drift | Git과 실제 클러스터 상태가 어긋난 상태 | 수동 핫픽스가 누적되면 장애 복구가 재현 불가능해진다 |
| Push model | CI/CD가 클러스터에 직접 배포 명령을 보내는 방식 | 빠르지만 CI가 운영 쓰기 권한을 가진다 |
| Pull model | 클러스터 내부 에이전트가 Git을 읽고 상태를 적용하는 방식 | 외부 CI가 운영 클러스터 토큰을 갖지 않아도 된다 |
| Sync | Git 상태를 클러스터에 적용하는 작업 | 수동 승인 또는 자동 동기화로 실행된다 |
| Prune | Git에서 사라진 리소스를 클러스터에서도 삭제하는 동작 | 유용하지만 경로 실수와 ApplicationSet 변경에서 삭제 반경이 커진다 |
| Self-heal | 클러스터가 Git과 달라지면 자동으로 Git 상태로 되돌리는 동작 | drift를 줄이지만 긴급 수동 조치까지 되돌릴 수 있다 |
용어를 한 문장으로 줄이면 이렇다. GitOps는 Git을 기준 상태로 삼고, reconciliation으로 drift를 줄이는 pull 기반 운영 방식이다.
OpenGitOps는 GitOps를 네 가지 원칙으로 설명한다. 이 원칙은 특정 도구의 기능 목록이 아니라 “운영 상태를 어떻게 다룰 것인가”에 대한 기준이다.
명령 순서가 아니라 최종적으로 도달해야 할 상태를 YAML, Helm values, Kustomize overlay 같은 파일로 표현한다.
실행 로그보다 리뷰 가능한 목표 상태가 더 중요할 때.desired state와 변경 승인 이력을 Git에 남긴다. 누가, 언제, 무엇을 바꿨는지 Git history로 추적한다.
감사 추적과 롤백 기준점이 필요할 때.머지된 상태를 클러스터 내부 에이전트가 감지하고 적용한다. 사람의 수동 kubectl 실행은 기본 경로가 아니다.
반복 배포를 플랫폼 워크플로로 만들 때.실제 상태가 Git과 달라지면 차이를 감지하고 다시 목표 상태로 수렴시킨다.
드리프트를 빠르게 찾고 복구 기준을 단순화해야 할 때.다만 “Git이 단일 진실 공급원”이라는 말은 모든 값을 Git에 평문으로 넣으라는 뜻이 아니다. Git은 desired state와 승인 이력의 기준점이다. 비밀번호, API key, 단기 토큰은 Git에 직접 저장하지 않는다. 그 값들은 Sealed Secrets나 External Secrets Operator처럼 별도 보안 경계를 가진 방식으로 연결한다.
또 GitOps는 모든 운영 변경을 초 단위로 처리하는 도구도 아니다. feature flag, 실험 설정, 트래픽 라우팅처럼 런타임에 빠르게 바뀌어야 하는 값은 전용 설정 플랫폼이나 progressive delivery 도구가 더 적합할 수 있다.
Reconciliation loop는 세 단계로 읽으면 된다.
flowchart TD
Git["Git repo: desired state"] --> Render["Render: Helm template / Kustomize build"]
Render --> Observe["Observe: Kubernetes API에서 actual state 읽기"]
Observe --> Diff{"desired state와 actual state가 다른가?"}
Diff -->|no| Synced["Synced: 목표 상태와 일치"]
Diff -->|yes| Out["OutOfSync: 차이 발견"]
Out --> Policy{"자동 sync 정책인가?"}
Policy -->|yes| Apply["Apply: Kubernetes API로 상태 변경"]
Policy -->|no| Wait["대기: 사람이 diff 확인 후 승인"]
Apply --> Health["Health check: Ready / Healthy 확인"]
Health --> Synced
Synced --> Loop["다음 reconcile 주기 또는 이벤트"]
Loop --> Git 정량 감각도 필요하다. 컨트롤러가 3분마다 300개의 Application을 확인한다면 평균적으로 분당 100개 Application을 비교하는 셈이다. 모든 Application이 같은 시각에 Git과 Kubernetes API를 조회하면 저장소와 API 서버에 부하가 몰린다. 그래서 실제 도구는 interval, jitter, queue, worker 수 같은 설정을 둔다. 정확한 기본값은 도구와 버전에 따라 바뀔 수 있으므로 운영 튜닝 시 공식 문서와 릴리스 노트를 확인해야 한다.
처음에는 성능 파라미터보다 더 중요한 질문이 있다. “이 리소스의 desired state는 어디에 있는가?” “실제 상태를 바꿀 권한은 누가 갖는가?” “차이가 발견되면 자동으로 고칠 것인가, 사람이 승인할 것인가?” 이 세 질문에 답하지 못하면 reconciliation loop는 있어도 GitOps 운영 모델은 약하다.
GitOps를 이해하면 Kubernetes, Terraform, CDC 같은 시스템을 같은 렌즈로 읽을 수 있다. 도구 이름은 달라도 “현재 상태를 보고, 목표 상태와 비교하고, 차이를 줄인다”는 구조가 반복된다.
| 도메인 | Observe | Diff | Act | 실패 신호 |
|---|---|---|---|---|
| GitOps | Kubernetes API에서 live resource를 읽는다 | Git manifest와 live resource를 비교한다 | sync, prune, self-heal을 수행한다 | OutOfSync가 반복되거나 sync는 되는데 health가 나쁘다 |
| Kubernetes Controller | etcd watch로 object 변화를 본다 | spec과 status를 비교한다 | Pod, Service, Endpoint 같은 하위 리소스를 만든다 | controller retry, event warning, status condition 악화 |
| Terraform | state와 cloud API를 읽는다 | HCL 선언과 실제 cloud resource를 비교한다 | plan/apply로 resource를 생성/수정/삭제한다 | plan에 예상 밖 변경이 계속 나온다 |
| CDC | source DB log position을 읽는다 | 마지막 처리 offset과 현재 log position을 비교한다 | event를 consumer나 broker로 전달한다 | lag가 누적되거나 중복 처리된다 |
이 표는 암기용이 아니다.
새 도구를 볼 때 “이 시스템의 desired state는 어디에 저장되는가?”를 먼저 찾기 위한 지도다.
GitOps에서는 Git repo가 desired state이고, Terraform에서는 .tf와 state 조합이 기준이며,
Kubernetes controller에서는 spec이 목표 상태다.
코드 리뷰에서도 같은 질문을 쓸 수 있다.
| 리뷰 질문 | 좋은 신호 | 위험 신호 |
|---|---|---|
| 이 변경의 desired state는 어디에 남는가? | Git manifest, values, overlay에 남는다 | 배포 스크립트 내부 변수나 수동 명령에만 남는다 |
| 같은 작업을 두 번 실행해도 안전한가? | apply, migration, hook이 멱등적이다 | hook이 매번 새 row를 만들거나 migration을 중복 실행한다 |
| 소유자가 하나인가? | Terraform과 GitOps가 리소스 층을 나눴다 | Terraform과 Argo CD가 같은 Deployment를 관리한다 |
| 실패하면 관측 가능한 상태가 있는가? | OutOfSync, Degraded, condition, event가 남는다 | CI 로그 외에는 현재 상태를 설명할 방법이 없다 |
Worked example로 보면, DB migration Job을 PreSync hook으로 넣는 PR은 “배포 순서 제어”처럼 보인다. 하지만 migration이 이미 적용된 상태에서 다시 실행되면 실패하거나 데이터를 중복 변경한다면 reconciliation과 맞지 않는다. GitOps 컨트롤러는 네트워크 오류, timeout, controller restart 후 같은 sync를 재시도할 수 있다. 따라서 Hook Job은 “이미 적용됨”을 성공으로 처리하거나, migration tool의 version table을 기준으로 멱등성을 가져야 한다.
반대로 모든 일을 GitOps hook에 넣는 것도 좋은 설계가 아니다. 장시간 데이터 백필, 위험한 schema rewrite, 외부 결제 시스템 one-off 정정처럼 사람이 단계별로 관찰해야 하는 작업은 GitOps Application sync의 부수 효과로 숨기면 안 된다. GitOps는 반복 가능한 목표 상태 수렴에 강하고, 비가역적인 일회성 운영 작업에는 별도 runbook과 승인 절차가 필요하다.
아래 상황을 손으로 따라가면 GitOps가 줄이는 문제가 선명해진다.
# Git에 있는 desired stateapiVersion: apps/v1kind: Deploymentmetadata: name: apispec: replicas: 3장애 대응 중 누군가 운영 클러스터에서 임시로 replica를 늘렸다고 하자.
actual state in clusterDeployment/api replicas = 5이제 세 가지 모델이 달라진다.
| 모델 | Git 상태 | 클러스터 상태 | 다음에 벌어지는 일 |
|---|---|---|---|
| 수동 운영 | 3 | 5 | 누가 왜 5로 바꿨는지 별도 기록이 없으면 추적이 어렵다 |
| push CI/CD | 3 | 5 | 다음 배포 스크립트가 3으로 덮을 수도 있고, 5를 유지할 수도 있다 |
| GitOps | 3 | 5 | 컨트롤러가 OutOfSync를 표시하고 정책에 따라 3으로 sync하거나 승인 대기한다 |
여기서 GitOps의 장점은 “항상 3으로 되돌린다”가 아니다. 장점은 차이가 관측 가능한 상태로 드러난다는 점이다. Auto-sync와 self-heal이 켜져 있으면 자동으로 3으로 돌아갈 수 있고, 꺼져 있으면 사람이 diff를 보고 “장애 중이라 5를 Git에 반영할지” 또는 “임시 변경을 되돌릴지” 결정한다.
반례도 중요하다. 트래픽 급증 중 사람이 긴급하게 5로 늘렸는데 self-heal이 즉시 3으로 되돌리면 복구를 방해한다. 따라서 프로덕션에서 self-heal은 “모든 것을 자동 복원”이 아니라 “어떤 리소스는 자동 복원하고, 어떤 변경은 승인 후 복원할지”를 정한 뒤 켜야 한다.
Push 모델은 CI 서버가 클러스터에 명령을 보낸다. Pull 모델은 클러스터 내부 에이전트가 Git을 읽고 클러스터에 적용한다. 두 모델 모두 자동 배포를 만들 수 있지만, 권한 경계가 다르다.
sequenceDiagram participant Dev as Developer participant Git participant CI as CI Server participant Agent as GitOps Agent participant K8s as Kubernetes API rect rgb(245, 245, 245) Dev->>Git: code push / merge Git->>CI: workflow trigger CI->>K8s: kubectl apply 또는 helm upgrade Note over CI,K8s: Push 모델은 CI가 클러스터 쓰기 권한을 가진다 end rect rgb(235, 248, 255) Dev->>Git: desired state merge CI->>Git: image tag 또는 manifest PR 생성 Agent->>Git: desired state pull Agent->>K8s: diff 발견 시 apply Note over Agent,K8s: Pull 모델은 클러스터 내부 에이전트가 적용한다 end
Pull 모델에서 CI가 사라지는 것은 아니다. CI는 여전히 테스트, 빌드, 이미지 push, SBOM 생성, 취약점 스캔, manifest 업데이트 PR 생성 같은 일을 한다. 다만 CI가 운영 클러스터의 쓰기 토큰을 직접 갖지 않는다. 운영 쓰기 권한은 클러스터 내부의 GitOps 컨트롤러와 Kubernetes RBAC로 좁힌다.
보안 관점에서 이 차이는 크다. CI 토큰이 유출되어도 공격자가 곧바로 Kubernetes API에 apply할 수 없고, Git PR, 서명, branch protection, 리뷰 정책 같은 승인 경계를 통과해야 한다. 물론 GitOps 에이전트 자체가 강한 권한을 가지므로, 에이전트의 RBAC와 프로젝트 경계도 함께 설계해야 한다.
GitOps 컨트롤러는 Git 파일을 그대로 Kubernetes API에 던지는 것만 하지 않는다. 대부분 먼저 렌더링(rendering)을 수행한다. 렌더링은 사람이 관리하기 쉬운 입력을 Kubernetes가 이해하는 최종 YAML로 바꾸는 단계다.
Kustomize는 템플릿 언어 없이 base와 overlay를 조합하는 방식이다.
공통 리소스는 base에 두고, dev/staging/prod 차이는 overlay에 둔다.
k8s/├── base/│ ├── deployment.yaml│ ├── service.yaml│ └── kustomization.yaml└── overlays/ ├── dev/ │ ├── kustomization.yaml │ └── replica-patch.yaml └── prod/ ├── kustomization.yaml └── replica-patch.yaml이 구조에서는 prod overlay가 replicas: 3, dev overlay가 replicas: 1만 다르게 가질 수 있다.
환경별 YAML 전체를 복사하지 않으므로 중복이 줄지만,
overlay가 많아지면 “최종 YAML이 무엇인가”를 눈으로 추적하기 어려워진다.
그래서 GitOps 리뷰에서는 원본 파일뿐 아니라 렌더링 결과도 볼 수 있어야 한다.
Helm은 chart와 values를 조합해 Kubernetes YAML을 만드는 패키징 방식이다.
Argo CD가 Helm chart를 처리할 때 주의할 점은 helm install이나 helm upgrade를 그대로 실행하는 것이 아니라
helm template으로 YAML을 만든 뒤 Kubernetes API에 적용한다는 점이다.
따라서 Helm release history가 GitOps 컨트롤러 안에 자동으로 남는다고 기대하면 안 된다.
배포 이력의 기준은 Helm history가 아니라 Git history와 Application 상태다.
Helm template의 대표 반례는 랜덤 값이다. Chart가 렌더링할 때마다 다른 값을 만드는 함수를 쓰면 Git 파일은 바뀌지 않았는데 최종 YAML이 매번 달라진다. 이 경우 Application은 계속 OutOfSync처럼 보일 수 있다. 해결 방향은 랜덤 값을 values에 고정하거나, Secret 값을 외부 시크릿 관리로 분리하는 것이다.
GitOps의 품질은 도구보다 Git repo 구조에서 많이 결정된다. repo 구조가 불명확하면 컨트롤러는 잘 동작해도 사람은 무엇을 리뷰해야 하는지 모른다.
대표적인 선택지는 세 가지다.
| 구조 | 설명 | 장점 | 위험 |
|---|---|---|---|
| 앱 repo 안에 manifest 포함 | 애플리케이션 코드와 Kubernetes manifest를 같은 repo에 둔다 | 작은 팀에서 변경 맥락을 한 PR로 보기 쉽다 | 앱팀이 운영 정책을 과하게 직접 만질 수 있다 |
| 별도 environment repo | k8s-manifests, platform-config 같은 repo에 환경별 상태를 둔다 | 운영 승인, RBAC, 감사 경계를 나누기 쉽다 | 앱 코드 변경과 배포 상태 변경 PR이 분리된다 |
| platform repo + app overlay | 플랫폼 공통 base와 앱별 overlay를 나눠 둔다 | 표준 정책과 앱 차이를 함께 관리한다 | base 변경이 많은 앱에 동시에 영향을 줄 수 있다 |
처음에는 별도 environment repo가 학습 모델로 가장 선명하다. 소스 코드 repo는 “무엇을 빌드할지”를 담고, manifest repo는 “어느 환경에 어떤 이미지와 설정을 배포할지”를 담는다. CI는 이미지를 빌드한 뒤 manifest repo에 image tag 변경 PR을 만들 수 있다. GitOps 컨트롤러는 그 PR이 머지된 뒤 클러스터를 맞춘다.
작은 흐름은 아래처럼 읽는다.
1. app repo main에 코드가 merge된다.2. CI가 테스트 후 image registry에 api:1.8.4를 push한다.3. CI가 manifest repo의 overlays/prod image tag를 api:1.8.4로 바꾸는 PR을 만든다.4. 사람이 prod PR에서 diff, policy, rollout 조건을 리뷰한다.5. PR이 merge되면 GitOps 컨트롤러가 prod 클러스터를 sync한다.이 흐름에서 GitOps가 책임지는 것은 5번이다. 1번부터 3번까지는 여전히 CI/CD와 supply chain의 책임이다. GitOps를 도입해도 테스트가 약하거나 image tag가 추적되지 않으면 안전한 배포가 되지 않는다.
환경 분리도 중요한 설계 지점이다.
브랜치로 환경을 나누면 dev, staging, prod 브랜치가 서로 달라지기 쉽다.
폴더로 환경을 나누면 한 PR에서 환경별 차이를 비교하기 쉽지만, prod 변경 권한을 path 기반으로 잘 나눠야 한다.
| 환경 분리 방식 | 좋은 상황 | 주의 |
|---|---|---|
| branch per env | 기존 release branch 정책이 강하고 환경 승격을 branch merge로 표현한다 | 브랜치 drift가 생기면 어느 환경이 정본인지 흐려진다 |
| folder per env | overlays/dev, overlays/staging, overlays/prod 차이를 한 repo에서 리뷰한다 | prod path 보호와 CODEOWNERS가 필요하다 |
| repo per env | 강한 권한 분리와 감사 경계가 필요하다 | repo 간 중복과 승격 자동화 비용이 커진다 |
정량 감각으로 보면, 서비스 30개와 환경 3개가 있으면 최소 90개의 배포 상태 조합이 생긴다. 각 조합마다 image tag, replica, resource limit, secret reference가 다르면 사람이 수동으로 추적하기 어렵다. GitOps repo 설계는 이 90개 조합을 “복사 붙여넣기 YAML”로 둘 것인지, base/overlay, ApplicationSet, policy로 압축할 것인지 결정하는 일이다.
좋은 repo 구조의 실패 신호도 있다. 첫째, prod와 staging의 차이가 무엇인지 PR diff만 보고 알 수 없다. 둘째, 같은 ServiceAccount나 NetworkPolicy가 여러 앱 디렉터리에 복사되어 서로 다르게 진화한다. 셋째, image tag 업데이트 PR과 설정 변경 PR이 한 덩어리로 섞여 리뷰 초점이 흐려진다. 넷째, ApplicationSet generator 변경이 일반 앱 변경과 같은 리뷰 강도로 처리된다.
GitOps에서는 “파일이 Git에 있다”만으로 충분하지 않다. 사람이 어떤 파일을 리뷰해야 하고, 컨트롤러가 어떤 파일을 적용하며, 삭제나 self-heal이 어느 경로까지 영향을 주는지 repo 구조로 드러나야 한다.
Argo CD는 Kubernetes 안에서 동작하는 GitOps 도구다.
사용자는 보통 Application 리소스를 통해 “어떤 Git 저장소의 어떤 경로를 어느 클러스터/namespace에 배포할지”를 선언한다.
Application은 Argo CD에서 GitOps 배포 단위의 핵심 추상화다.
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: api namespace: argocdspec: project: default source: repoURL: https://github.com/my-team/k8s-manifests targetRevision: main path: apps/api/prod destination: server: https://kubernetes.default.svc namespace: production syncPolicy: automated: prune: true selfHeal: true이 예시에서 읽어야 할 것은 YAML 문법이 아니라 소유권이다.
source는 desired state의 위치다.
destination은 actual state를 만들 클러스터와 namespace다.
prune은 Git에서 사라진 리소스를 삭제하겠다는 뜻이다.
selfHeal은 클러스터에서 직접 바뀐 값을 Git 기준으로 되돌리겠다는 뜻이다.
Argo CD 내부 컴포넌트는 아래처럼 역할을 나눠 읽으면 충분하다.
UI, CLI, 외부 요청의 진입점이다. 사용자 인증과 RBAC가 여기서 중요해진다.
누가 어떤 Application을 볼 수 있고 sync할 수 있는지 나눌 때.Git 저장소를 가져오고 Helm template, Kustomize build 같은 렌더링을 수행한다.
렌더링이 느리거나 큰 chart 때문에 메모리 문제가 날 때.Git 상태와 클러스터 상태를 비교하고 sync, prune, self-heal을 수행하는 핵심 루프다.
OutOfSync, sync queue, drift 복구를 이해할 때.반복 조회와 상태 계산 비용을 줄이는 캐시 계층이다.
대규모 Application에서 상태 계산 부하를 줄일 때.Argo CD의 Sync Wave와 Hook은 순서 제어 도구다. Sync wave는 리소스에 숫자 어노테이션을 붙여 작은 숫자부터 적용하는 방식이다. Hook은 sync 전후 특정 시점에 실행되는 리소스, 보통 Job이다.
# wave -1: 설정 먼저 적용metadata: annotations: argocd.argoproj.io/sync-wave: "-1"---# PreSync hook: 앱 배포 전 마이그레이션metadata: annotations: argocd.argoproj.io/hook: PreSync argocd.argoproj.io/hook-delete-policy: HookSucceededDB 마이그레이션을 Hook으로 실행한다면 반드시 멱등적이어야 한다. 같은 sync가 재시도되어도 이미 적용된 마이그레이션을 다시 깨뜨리지 않아야 한다. Hook이 “한 번만 실행될 것”이라고 가정하는 순간 reconciliation과 충돌한다.
Argo CD 화면이나 CLI에서 가장 많이 보는 단어는 Synced, OutOfSync, Healthy, Degraded다.
이 단어들은 같은 층이 아니다.
처음에는 네 축으로 나눠 읽는 편이 좋다.
| 축 | 질문 | 좋은 상태 | 나쁜 상태가 뜻하는 것 |
|---|---|---|---|
| Sync status | desired state가 actual state에 적용되었나? | Synced | Git과 클러스터가 다르거나 apply가 필요하다 |
| Health status | 적용된 리소스가 실제로 정상인가? | Healthy | Pod, Service, Ingress, hook Job 등이 준비되지 않았다 |
| Operation state | 현재 sync 작업이 진행 중인가? | Succeeded 또는 Idle | sync가 running, failed, error 상태일 수 있다 |
| History/revision | 어떤 Git revision이 적용되었나? | 기대한 commit SHA | 예상하지 못한 revision이나 rollback 상태일 수 있다 |
예를 들어 Synced이면서 Degraded인 상태가 가능하다.
Git의 YAML은 클러스터에 정확히 적용되었지만,
Deployment Pod가 ImagePullBackOff이거나 readiness probe에 실패하면 health는 나쁘다.
이때 GitOps 문제라고만 보면 안 된다.
image registry 권한, container port, probe path, resource limit 같은 Kubernetes 실행 문제로 내려가야 한다.
반대로 OutOfSync이면서 서비스는 정상일 수도 있다.
클러스터의 actual state가 Git과 다르지만, 그 차이가 replica 수나 자동 주입 annotation처럼 즉시 장애를 만들지 않을 수 있다.
그래도 방치하면 “현재 상태를 Git으로 설명할 수 없다”는 문제가 남는다.
따라서 OutOfSync는 장애 여부와 별개로 운영 부채 신호다.
상태 해석에서 흔한 실수는 sync 버튼을 복구 버튼으로 생각하는 것이다. Sync는 desired state를 적용하는 버튼이지, 애플리케이션을 정상화하는 만능 버튼이 아니다. 잘못된 desired state를 sync하면 장애 상태도 정확하게 재현된다. GitOps에서는 “sync할 것인가”보다 먼저 “Git의 desired state가 맞는가”를 확인해야 한다.
Flux도 Kubernetes 안에서 Git 상태를 당겨와 적용하는 GitOps 도구다. Argo CD가 Application 중심으로 읽히는 경우가 많다면, Flux는 여러 Kubernetes-native controller와 CRD의 조합으로 읽는 편이 자연스럽다.
대표적으로 GitRepository는 Git 소스를 정의하고,
Kustomization은 그 소스의 어느 경로를 어떤 주기로 적용할지 정의한다.
apiVersion: source.toolkit.fluxcd.io/v1kind: GitRepositorymetadata: name: platform-config namespace: flux-systemspec: interval: 1m url: https://github.com/my-team/k8s-manifests ref: branch: main---apiVersion: kustomize.toolkit.fluxcd.io/v1kind: Kustomizationmetadata: name: api-prod namespace: flux-systemspec: interval: 10m path: ./apps/api/prod prune: true sourceRef: kind: GitRepository name: platform-config여기서 GitRepository.interval은 소스 변경을 확인하는 리듬이고,
Kustomization.interval은 적용 대상의 상태를 다시 맞추는 리듬이다.
두 interval이 다르면 “Git 변경을 빨리 감지하지만 drift 검사는 더 느리게 한다” 같은 운영 모델이 가능하다.
정확한 동작과 기본값은 Flux 공식 문서 기준으로 확인한다.
Flux의 장점은 Kubernetes API와 CRD 모델에 잘 녹아든다는 점이다. 반대로 내장 UI를 중심으로 여러 팀에 배포 상태를 보여주는 경험은 Argo CD가 더 직관적일 수 있다.
두 도구 모두 GitOps를 구현한다. 선택은 “어느 쪽이 최신인가”보다 팀 구조와 운영 모델에 맞춰야 한다. 버전, 관리 주체, 클라우드 벤더 통합은 바뀔 수 있으므로 최신 판단은 공식 릴리스 노트와 프로젝트 문서를 확인한다.
| 기준 | Argo CD가 맞는 경우 | Flux가 맞는 경우 |
|---|---|---|
| 운영 인터페이스 | 플랫폼팀이 UI로 여러 팀의 배포 상태를 보여줘야 한다 | CLI와 Kubernetes CRD 중심 운영이 자연스럽다 |
| 팀 구조 | 중앙 플랫폼팀이 여러 앱팀을 지원한다 | 팀이 클러스터별로 자율 운영한다 |
| 멀티테넌시 | 프로젝트, RBAC, 대시보드 구분이 중요하다 | Git repo와 namespace 단위로 작게 나누는 편이 좋다 |
| 도구 확장 | Argo Rollouts, Workflows 등 Argo 생태계를 함께 고려한다 | Source, Kustomize, Helm, Image automation 컨트롤러를 조합한다 |
| 학습 진입 | UI에서 desired/actual diff를 눈으로 보는 것이 도움이 된다 | Kubernetes resource 모델로 모든 것을 보는 훈련이 목적이다 |
작은 팀에서 처음 시작한다면 UI 때문에 Argo CD가 이해하기 쉬울 수 있다. 이미 Kubernetes CRD와 controller 운영에 익숙하고, 클러스터마다 독립적으로 GitOps를 넣고 싶다면 Flux가 더 단순하게 느껴질 수 있다. 중요한 것은 도구명이 아니라 Git, 권한, 리소스 소유권, 삭제 정책을 팀이 설명할 수 있는가다.
Terraform/IaC는 클라우드 인프라를 선언하고 프로비저닝하는 데 강하다. GitOps는 Kubernetes 안의 애플리케이션과 클러스터 리소스를 지속적으로 맞추는 데 강하다. 둘 다 desired state를 쓰지만, 소유해야 하는 리소스 층이 다르다.
| 리소스 | 보통 Terraform이 소유 | 보통 GitOps가 소유 |
|---|---|---|
| VPC, Subnet, IAM, RDS | 예 | 아니오 |
| EKS 클러스터 생성 | 예 | 아니오 |
| Argo CD 설치 자체 | Terraform 또는 bootstrap 스크립트 | 설치 후 자기 자신을 관리할 수도 있지만 신중해야 한다 |
| Deployment, Service, Ingress | 가능은 하지만 일반적으로 비권장 | 예 |
| ConfigMap, NetworkPolicy, HPA | 경우에 따라 다름 | 애플리케이션 운영 단위라면 예 |
가장 위험한 반례는 동일 리소스를 두 시스템이 함께 소유하는 것이다.
Terraform이 Deployment/api를 관리하고 Argo CD도 같은 Deployment/api를 관리하면,
Terraform apply 후 Argo CD가 되돌리고, Argo CD sync 후 Terraform plan이 다시 변경을 감지하는 루프가 생긴다.
경계 원칙은 간단하다. 클라우드 기반 자원과 장기 수명 인프라는 Terraform이 소유한다. 클러스터 안에서 자주 배포되고 앱팀이 PR로 바꾸는 리소스는 GitOps가 소유한다. 경계가 애매하면 “장애 시 어느 팀이 어떤 Git repo를 revert해야 하는가”로 소유자를 정한다.
실제 플랫폼에서는 한 리소스가 어느 도구의 책임인지 애매해지는 순간이 많다. 예를 들어 EKS 클러스터 위에 Ingress Controller를 설치한다고 하자.
cloud layer VPC, subnet, IAM, EKS cluster
cluster platform layer ingress-nginx controller, cert-manager, external-dns
application layer api Deployment, Service, Ingress이 셋을 모두 Terraform으로 만들 수도 있고, 모두 GitOps로 밀어 넣을 수도 있다. 하지만 학습 모델로는 층을 나눠 소유하는 편이 안전하다.
| 층 | 권장 소유자 | 이유 |
|---|---|---|
| cloud layer | Terraform | cloud API resource와 IAM 경계는 클러스터 밖에 있다 |
| cluster platform layer | Terraform bootstrap 또는 GitOps 중 하나 | 설치 후 지속 업데이트가 많으면 GitOps가 편하고, 초기 클러스터 생성과 강하게 묶이면 Terraform이 편하다 |
| application layer | GitOps | 앱팀 PR, 환경별 overlay, 빠른 rollback과 잘 맞는다 |
Ingress Controller 자체를 Terraform Helm provider로 설치하면서, Argo CD에도 같은 chart를 Application으로 등록하면 문제가 생긴다. Terraform은 chart version 4.10.0을 유지하려 하고, Argo CD는 Git에 있는 chart version 4.11.0으로 바꾸려 한다. 둘 다 “desired state를 맞추는 도구”라서 사람이 보기에 한쪽은 항상 drift처럼 보인다.
좋은 기준은 장애 시 행동이다. Ingress Controller 장애가 나면 어느 repo에서 rollback할 것인가? Terraform module을 revert할 것인가, Argo CD Application revision을 되돌릴 것인가? 둘 다 가능하다는 답은 운영 기준으로는 위험하다. 하나만 정하고, 다른 도구는 그 리소스를 읽거나 참조만 해야 한다.
경계가 바뀌는 전환기도 있다. 초기에는 Terraform으로 Argo CD와 필수 add-on을 설치하고, 운영이 안정되면 add-on 관리를 GitOps repo로 옮길 수 있다. 이때는 동시에 관리하지 말고 소유권 이전 PR을 분리한다. 먼저 Terraform state에서 해당 리소스를 제거하거나 관리 중지를 명시하고, 그다음 GitOps Application을 만든다. 전환 순서가 없으면 “마이그레이션”이 아니라 “두 reconciler의 경합”이 된다.
Argo CD에서 Application이 하나의 배포 단위라면, 많은 Application을 어떻게 생성하고 묶을지가 다음 문제다.
App of Apps는 부모 Application이 자식 Application YAML들을 가리키는 패턴이다. 부모가 목차이고 자식 Application이 각 챕터라고 생각하면 된다. 단일 클러스터에서 여러 앱을 묶거나, 환경별 Application 목록을 Git에 명시적으로 보관할 때 이해하기 쉽다.
ApplicationSet은 generator 규칙으로 여러 Application을 자동 생성하는 리소스다. 예를 들어 앱 20개와 클러스터 5개를 조합하면 100개의 Application이 생긴다. 이 규모에서 Application YAML을 하나씩 손으로 관리하면 중복과 누락이 빠르게 늘어난다.
| 패턴 | 잘 맞는 상황 | 주의할 점 |
|---|---|---|
| App of Apps | Application 목록을 명시적으로 리뷰하고 싶다 | 부모 Application의 prune이 자식 Application 삭제로 이어질 수 있다 |
| ApplicationSet | 클러스터 목록, 디렉터리 목록, 앱 목록의 조합이 많다 | generator 입력 실수가 대량 삭제로 확대될 수 있다 |
ApplicationSet의 가장 중요한 실패 모드는 삭제 반경이다.
generator에서 prod 클러스터 항목이 사라지고, 생성된 Application에 prune이 켜져 있으면,
Application만 사라지는 것이 아니라 실제 배포 리소스 삭제까지 이어질 수 있다.
처음에는 preserveResourcesOnDeletion 같은 보호 옵션과 수동 승인 흐름을 검토하고,
프로덕션에서는 generator 변경 PR을 별도로 엄격하게 리뷰한다.
Prune은 Git에서 삭제된 리소스를 클러스터에서도 삭제한다. Self-heal은 클러스터에서 직접 바뀐 리소스를 Git 상태로 되돌린다. 둘 다 GitOps의 강력한 기능이지만, 운영에서는 각각 다른 위험을 가진다.
| 기능 | 줄이는 문제 | 새로 생기는 위험 | 첫 적용 기준 |
|---|---|---|---|
| prune | Git에서 제거한 리소스가 클러스터에 남아 떠도는 문제 | 경로 변경, generator 실수, namespace 오타가 실제 삭제로 번진다 | dev/staging에서 삭제 diff와 보호 옵션을 먼저 검증 |
| self-heal | kubectl edit 같은 수동 변경이 계속 남는 drift | 긴급 수동 조치가 자동으로 되돌아가 복구를 방해한다 | stateless 앱부터 켜고, 상태 저장 리소스는 승인형으로 시작 |
정량 예시로 보면 위험이 더 잘 보인다. ApplicationSet 하나가 앱 20개와 클러스터 5개를 생성하면 100개의 Application을 만든다. generator 파일 한 줄 삭제가 1개 Application 삭제가 아니라 20개 또는 100개 삭제 후보로 확장될 수 있다. GitOps 리뷰에서 generator 변경이 일반 앱 manifest 변경보다 더 엄격해야 하는 이유다.
또 self-heal은 “사람 실수를 자동 복구”하는 기능이지만, 장애 대응 중 임시 스케일 아웃이나 feature kill switch를 클러스터에서 직접 적용하는 팀에게는 역효과가 날 수 있다. 운영 정책은 “self-heal을 켠다/끈다”가 아니라 “어떤 리소스는 자동 복구하고 어떤 리소스는 diff 표시 후 승인한다”로 나눠야 한다.
GitOps에서 가장 흔한 오해는 “모든 것을 Git에 둔다”를 “비밀번호도 Git에 둔다”로 받아들이는 것이다. Git에는 시크릿의 평문이 아니라, 시크릿을 어디서 어떻게 가져올지에 대한 선언만 두는 것이 원칙이다.
Sealed Secrets는 Secret을 클러스터 공개키로 암호화해 SealedSecret 형태로 Git에 저장한다.
클러스터 안의 controller만 복호화할 수 있으므로 Git에는 암호문이 남는다.
장점은 GitOps 흐름과 잘 맞는다는 점이고,
주의할 점은 클러스터 키 관리와 복구 절차가 중요하다는 점이다.
External Secrets Operator는 AWS Secrets Manager, GCP Secret Manager, Vault 같은 외부 저장소에서 값을 가져와 Kubernetes Secret으로 동기화한다.
Git에는 ExternalSecret 리소스와 참조 이름만 남는다.
장점은 시크릿 rotation과 중앙 보안 정책을 외부 secret manager에 맡길 수 있다는 점이다.
주의할 점은 refresh interval 때문에 값이 즉시 바뀌지 않을 수 있다는 점이다.
| 선택지 | Git에 남는 것 | 좋은 상황 | 실패 신호 |
|---|---|---|---|
| Sealed Secrets | 암호화된 Secret YAML | 작은 팀, Git 기반 리뷰가 중요, 외부 secret manager가 없다 | 클러스터 키 분실 시 복호화/복구가 어렵다 |
| External Secrets | 외부 secret key 참조 | 클라우드 secret manager, Vault, rotation 정책이 이미 있다 | 외부 권한 오류나 refresh 지연으로 Pod가 오래된 값을 쓴다 |
예를 들어 ExternalSecret의 refresh interval이 1시간이면, 비밀번호를 외부 저장소에서 바꿔도 클러스터 Secret 반영은 최대 1시간 늦어질 수 있다. 장애 대응 시 “Git은 맞는데 앱이 새 비밀번호를 못 읽는다”면 GitOps diff만 보지 말고 external secret 동기화 상태와 마지막 refresh 시간을 같이 봐야 한다.
Progressive delivery는 새 버전을 한 번에 모두 노출하지 않고 일부 트래픽이나 일부 Pod부터 점진적으로 노출하는 배포 전략이다. Argo Rollouts 같은 도구가 Canary, Blue-Green, metric 기반 promotion을 제공한다.
GitOps는 “어떤 상태를 적용할 것인가”를 다룬다. Progressive delivery는 “그 상태를 사용자 트래픽에 어떻게 노출할 것인가”를 다룬다. 둘은 함께 쓰일 수 있지만 같은 개념은 아니다.
수치로 보면 차이가 분명하다. replica 10개인 서비스에서 canary 10%를 시작하면 새 버전 Pod 1개만 트래픽을 받는다. 5분 동안 5xx 비율이나 latency가 기준을 넘지 않으면 30%, 60%, 100%로 늘릴 수 있다. 반대로 기본 RollingUpdate는 Pod를 점진적으로 바꾸더라도 트래픽 분석과 자동 중단 기준이 약할 수 있다.
GitOps로 Rollout 리소스를 관리할 수는 있다. 하지만 “Git에 머지되었다”가 곧 “전체 사용자에게 노출되었다”는 뜻은 아니다. GitOps Application은 Synced여도 Rollout은 pause 상태일 수 있고, metric 분석 실패로 promotion이 멈출 수 있다.
GitOps는 팀과 시스템이 어느 정도 복잡해졌을 때 효과가 크다. 단순한 자동 배포만 필요하다면 기존 CI/CD가 더 빠르고 충분할 수 있다.
| 기준 | GitOps 권장 | 주의 | 부적합 가능성 |
|---|---|---|---|
| Kubernetes 사용 | 이미 운영 중 | 도입 예정 | Kubernetes를 쓰지 않는다 |
| 환경 수 | dev/staging/prod 이상 | dev/prod 정도 | 환경이 하나이고 배포도 드물다 |
| 권한 분리 | CI에서 운영 토큰을 제거해야 한다 | 토큰 범위 축소만 필요하다 | CI에 운영 권한이 있어도 위험이 작다 |
| 감사 요구 | PR, 승인, Git 이력 기반 추적이 필요하다 | 팀 내부 규칙 수준 | 빠른 수동 핫픽스가 더 중요하다 |
| 팀 규모 | 여러 앱팀과 플랫폼팀이 있다 | 3~5명 팀 | 1~2명 팀에서 PR 오버헤드가 더 크다 |
| 변경 성격 | 배포 단위 상태 변경이 많다 | 일부 설정은 런타임 변경 | 초 단위 feature flag가 핵심이다 |
실무 판단은 조합으로 한다. “Kubernetes 운영 중 + 환경 3개 이상 + 운영 클러스터 토큰을 CI에서 제거해야 함 + PR 기반 감사가 필요함”이면 GitOps가 잘 맞는다. 반대로 “단일 VM + 팀 2명 + 분기 1회 배포 + 수동 핫픽스가 중심”이면 GitOps 컨트롤러 운영 비용이 더 클 수 있다.
GitOps를 공부할 때는 적용하면 좋은 상황뿐 아니라 피해야 할 상황도 같이 봐야 한다. 반례를 알아야 “모든 것을 Git에 넣자”라는 과한 결론을 피할 수 있다.
첫 번째 반례는 초 단위 런타임 설정이다. 예를 들어 결제 장애 중 특정 payment provider를 30초 안에 끄고 켜야 한다면, Git PR, 리뷰, merge, reconciliation 주기를 기다리는 모델은 너무 느릴 수 있다. 이 경우 feature flag나 runtime config service가 더 적합하다. GitOps repo에는 “이 서비스가 어떤 config service를 참조한다”는 선언을 남기고, 실제 토글 값은 전용 시스템에서 관리하는 편이 낫다.
두 번째 반례는 비가역적 데이터 작업이다. 대량 데이터 삭제, 계정 정정, 장시간 백필 같은 작업은 desired state를 apply한다고 안전해지지 않는다. GitOps hook으로 숨기면 Application sync가 데이터 작업의 실행 버튼이 되어 버린다. 이런 작업은 별도 승인, dry-run, 샘플 검증, 중단 기준, 롤백 가능성 검토가 필요하다.
세 번째 반례는 소유권이 둘로 나뉜 리소스다. 이미 Terraform module이 특정 Kubernetes add-on을 설치하고 있고, Argo CD도 같은 add-on manifest를 sync한다면 둘 중 하나를 제거해야 한다. GitOps가 좋다고 해서 기존 IaC 소유권 위에 같은 리소스를 덮어쓰면 drift 복구가 아니라 drift 생성기가 된다.
네 번째 반례는 높은 빈도의 자동 이미지 업데이트다. Flux image automation이나 Argo CD Image Updater 같은 도구는 새 image tag를 Git에 반영할 수 있다. 하지만 하루 수십 번 tag PR이 올라오면 Git history가 “승인된 운영 의사결정”보다 “자동 봇 로그”에 가까워질 수 있다. 이때는 staging까지 자동, prod는 promotion PR처럼 단계와 권한을 나누는 편이 낫다.
다섯 번째 반례는 Git repo 접근 자체가 운영 의존성을 과하게 키우는 경우다. 모든 환경이 한 외부 Git provider에만 의존하고, 장애 시 cached manifest나 break-glass 절차가 없다면, Git provider 장애가 배포와 복구 절차를 막을 수 있다. GitOps는 Git을 기준으로 삼지만, 운영에는 read-only mirror, controller cache, 수동 승인형 emergency path 같은 보완도 필요하다.
아래 표는 빠른 판단을 위한 요약이다.
| 상황 | GitOps로 밀어붙이면 생기는 문제 | 더 나은 방향 |
|---|---|---|
| 초 단위 feature flag | PR/reconcile 주기가 너무 느리다 | feature flag 플랫폼, runtime config |
| 대량 데이터 정정 | sync가 위험한 데이터 작업의 실행 버튼이 된다 | 별도 runbook, dry-run, 승인 절차 |
| Terraform과 같은 리소스 중복 소유 | 두 reconciler가 서로 되돌린다 | 소유 층 분리 |
| 자동 tag PR 폭주 | Git history가 운영 판단보다 봇 로그가 된다 | 환경별 자동화 수준 분리 |
| Git provider 장애에 취약 | 복구 기준점에 접근하지 못한다 | mirror, cache, emergency path |
좋은 GitOps 설계는 “GitOps를 쓰지 않을 것”까지 명시한다. 그 경계가 있어야 팀이 GitOps를 신뢰하고, 예외 상황에서도 어디서 무엇을 바꿔야 하는지 헷갈리지 않는다.
GitOps 문서에는 도구 버전, 클라우드 관리형 서비스, CNCF 상태, 특정 벤더 기능 같은 정보가 자주 들어간다. 이 정보는 학습에 도움이 될 수 있지만 빠르게 낡는다. 따라서 본문에서는 버전 자체보다 변하지 않는 판단 구조를 먼저 둔다.
예를 들어 “어떤 Argo CD 버전이 표준인가”보다 중요한 질문은 다음과 같다.
| 질문 | 왜 오래 가는가 |
|---|---|
| 현재 운영 버전의 support window는 남아 있는가? | 버전 숫자는 바뀌어도 지원 종료 확인은 항상 필요하다 |
| upgrade가 CRD, RBAC, ApplicationSet 동작을 바꾸는가? | GitOps 컨트롤러는 삭제와 sync 권한을 가지므로 upgrade 영향이 크다 |
| 관리형 서비스가 클러스터 안/밖 어느 경계에서 동작하는가? | 권한 경계와 장애 책임이 달라진다 |
| 공식 release note에 breaking change가 있는가? | 블로그 요약보다 운영 판단에 직접적이다 |
클라우드 벤더가 관리형 GitOps 기능을 제공할 수 있고, 프로젝트가 새 major version을 낼 수 있으며, Flux나 Argo CD 주변 생태계도 계속 바뀐다. 이 문서에서는 그런 소식을 “현재 결론”으로 강하게 고정하지 않는다. 실제 도입이나 업그레이드 시점에는 공식 문서, release note, 보안 공지를 확인한다.
학습 단계에서는 아래처럼 정리하면 충분하다. 도구 생태계 정보는 “선택에 영향을 주는 후보 신호”다. GitOps의 핵심 개념은 desired state, pull 권한 경계, reconciliation, drift, 소유권, 삭제 반경이다. 시간에 민감한 정보가 이 핵심 개념을 덮기 시작하면 문서가 운영 뉴스 모음으로 흐른다.
문제 해결을 긴 명령어 목록으로 외우면 금방 잊힌다. 먼저 어떤 개념이 깨졌는지 연결해야 한다.
| 증상 | 깨진 개념 | 왜 그런가 | 첫 확인 |
|---|---|---|---|
| Application이 계속 OutOfSync로 돌아온다 | desired/actual diff | 렌더링 결과가 매번 달라지거나 다른 controller가 같은 리소스를 바꾼다 | diff에서 변하는 필드와 소유 controller를 본다 |
| Sync는 성공했는데 앱이 Ready가 아니다 | sync와 health의 차이 | YAML 적용은 됐지만 Pod readiness, probe, image pull, dependency가 실패할 수 있다 | Application health와 Deployment events를 함께 본다 |
| Git repo 접근 오류가 난다 | source 접근 권한 | GitOps 에이전트가 desired state를 읽지 못하면 reconciliation이 시작되지 않는다 | repo credential 만료, SSH/HTTPS 권한, source controller 상태를 본다 |
| Helm hook이 매번 실행된다 | Helm install과 template의 차이 | Argo CD는 Helm release lifecycle이 아니라 sync lifecycle로 본다 | Helm hook을 Argo CD hook 또는 멱등 Job으로 바꿀지 판단한다 |
| DB 마이그레이션이 중복 실행된다 | hook 멱등성 | sync 재시도와 reconciliation에서는 같은 Job이 다시 실행될 수 있다 | migration table, hook delete policy, wave/hook 위치를 본다 |
| ApplicationSet 변경 후 앱이 사라진다 | generator와 prune 반경 | 입력 목록에서 사라진 항목이 Application 삭제와 리소스 prune으로 이어진다 | generator diff, preserve 옵션, 자동 prune 여부를 본다 |
| Repo Server가 자주 죽거나 느리다 | 렌더링 비용 | 큰 Helm chart와 많은 values가 동시에 렌더링되면 메모리와 CPU가 튄다 | 렌더링 대상 수, chart 크기, 동시성 limit, Pod resource를 본다 |
| 특정 클러스터만 sync 실패한다 | 환경별 actual state 차이 | CRD, Kubernetes 버전, namespace, RBAC, 네트워크가 클러스터마다 다를 수 있다 | 실패 클러스터의 CRD/RBAC/API 연결 상태를 먼저 본다 |
| Terraform apply와 Argo CD sync가 서로 되돌린다 | 단방향 소유권 위반 | 두 reconciler가 같은 리소스를 동시에 소유한다 | 해당 resource의 owner를 하나로 정한다 |
| Secret 값 변경 후 앱이 오래된 값을 쓴다 | secret 동기화 지연 | ExternalSecret refresh, Pod reload, app config reload가 별도 단계다 | ExternalSecret status, Secret timestamp, Pod 재시작 정책을 본다 |
이 표에서 중요한 것은 “첫 확인”이 곧 전체 해결 절차는 아니라는 점이다. 첫 확인은 문제를 어느 개념으로 분류할지 정하는 단계다. 그다음에야 도구별 명령과 운영 런북이 의미를 가진다.
| 개념 A | 개념 B | 구분 |
|---|---|---|
| GitOps | CI/CD | CI/CD는 빌드와 검증 자동화까지 포함한다. GitOps는 배포 상태를 Git desired state와 reconciliation으로 관리하는 방식이다 |
| Sync | Health | Sync는 YAML 적용 여부이고, Health는 애플리케이션이 실제로 정상인지다 |
| Drift | 배포 실패 | Drift는 Git과 actual state의 차이다. 배포 실패는 적용 또는 readiness가 실패한 상태다 |
| Prune | Self-heal | Prune은 Git에서 사라진 리소스를 삭제한다. Self-heal은 Git과 달라진 리소스를 되돌린다 |
| App of Apps | ApplicationSet | App of Apps는 Application 목록을 명시적으로 묶는다. ApplicationSet은 generator로 Application을 만든다 |
| Kustomize | Helm | Kustomize는 YAML patch/overlay 중심이다. Helm은 chart와 values로 template을 렌더링한다 |
| Terraform | GitOps | Terraform은 클라우드 인프라 프로비저닝에 강하다. GitOps는 클러스터 내부 상태의 지속 조정에 강하다 |
| RollingUpdate | Progressive delivery | RollingUpdate는 Pod 교체 전략이다. Progressive delivery는 트래픽과 metric 기반으로 노출을 단계화한다 |
프론트엔드나 백오피스 서비스를 배포할 때 GitOps는 “코드를 잘 빌드했다” 이후의 문제를 다룬다. 이미지 태그가 무엇인지, 어느 환경에 들어갔는지, 누가 prod 반영을 승인했는지, 클러스터에서 누가 임의로 바꿨는지를 추적하게 해준다.
플랫폼 엔지니어링 관점에서는 GitOps가 IDP의 배포 백엔드가 될 수 있다. 개발자는 포털이나 템플릿으로 서비스 manifest PR을 만들고, 플랫폼은 GitOps 컨트롤러로 표준 정책, namespace, secret 참조, progressive delivery를 적용한다.
다만 GitOps를 도입한다고 플랫폼이 자동으로 좋아지는 것은 아니다. Git repo 구조, 리뷰 규칙, 리소스 소유권, 삭제 보호, 시크릿 경계가 같이 설계되어야 한다. 이 설계가 없으면 “kubectl 수동 운영”이 “YAML 수동 운영”으로만 바뀐다.
이 부록은 설치 런북이 아니라 개념 관찰용이다. 도구 버전과 설치 방식은 공식 getting started 문서를 따른다.
관찰할 최소 명령은 아래 정도면 충분하다.
argocd app get apiargocd app diff apikubectl get deploy api -n productionFlux를 본다면 같은 관찰을 GitRepository, Kustomization, 실제 Deployment 상태로 나눠 본다.
flux get sources gitflux get kustomizationskubectl get deploy api -n production실습의 목적은 명령을 외우는 것이 아니다. Git desired state, actual state, diff, sync, health가 서로 다른 층이라는 것을 눈으로 확인하는 것이다.
퀴즈
힌트: 누가 Kubernetes API에 apply 권한을 갖는지 비교한다.
클러스터 내부의 Argo CD나 Flux 같은 에이전트가 Git의 desired state를 읽고 Kubernetes API에 적용하기 때문이다. CI는 테스트, 이미지 빌드, manifest 변경 PR 생성까지 맡고, 운영 쓰기 권한은 클러스터 내부 컨트롤러와 RBAC로 제한할 수 있다.
퀴즈
힌트: 앱 20개와 클러스터 5개를 조합하면 몇 개 Application이 생기는지 생각한다.
generator 항목 삭제가 생성된 Application 삭제로 이어지고, 그 Application에 prune이 켜져 있으면 실제 클러스터 리소스 삭제까지 연쇄될 수 있기 때문이다. 앱 수와 클러스터 수가 곱해지면 삭제 반경이 커진다.
Argo CD Application, AppProject, ApplicationSet, App of Apps, Flux GitRepository, Flux Kustomization, Helm template, Kustomize overlay, Sync Wave, Sync Hook, prune, self-heal, sealed secrets, External Secrets Operator, progressive delivery, Argo Rollouts, canary, blue-green, image automation, drift detection, desired state, actual state, reconciliation loop
helm template 방식으로 다루는 차이를 확인한다.