· 57분 읽기
GitOps 심화 (상): 철학과 원칙, ArgoCD와 Flux 아키텍처
Git 저장소에 선언된 상태가 곧 클러스터의 실제 상태가 되도록 만드는 것, 이것이 GitOps의 핵심입니다. 이 글에서는 먼저 GitOps의 4가지 원칙과 Push/Pull 배포 모델의 차이를 압축해 정리합니다. 이어서 대표 구현체인 ArgoCD와 Flux의 내부 아키텍처를 나란히 깊게 살펴보고, 마지막에 두 도구의 선택 기준을 비교표로 제시합니다.
전통적 CI/CD의 한계
Jenkins나 GitHub Actions가 코드를 빌드하고, 테스트를 통과하면 kubectl apply나 helm upgrade 명령으로 클러스터에 직접 배포하는 방식을 떠올려 봅시다.
sequenceDiagram
participant Dev as 개발자
participant Git as Git Repository
participant CI as CI Server
participant K8s as Kubernetes
Dev->>Git: git push
Git->>CI: Webhook 트리거
CI->>CI: Build & Test
CI->>K8s: kubectl apply / helm upgrade
Note over K8s: 배포 완료
이 방식은 동작하지만 몇 가지 근본적인 문제가 있습니다.
| 문제 | 설명 |
|---|---|
| Credential 분산 | CI 서버가 프로덕션 클러스터에 접근하는 강력한 권한 보유 |
| Drift 감지 불가 | 누군가 kubectl edit으로 직접 수정하면 Git과 실제 상태 불일치 |
| Audit Trail 부재 | 누가, 언제, 왜 변경했는지 추적 어려움 |
| 롤백의 복잡성 | 이전 상태로 돌아가려면 "어떤 버전이 배포되어 있었는지"부터 찾아야 함 |
주의: CI 서버가 해킹당하면 공격자가 프로덕션 클러스터에 임의 코드를 배포할 수 있습니다. 2021년 Codecov 사태에서는 CI 파이프라인이 공격 벡터가 되어 수천 개 기업의 credential이 유출되었습니다.
GitOps의 정의와 4가지 원칙
GitOps는 Git을 Single Source of Truth(SSOT)로 삼아 원하는 시스템 상태를 선언적으로 정의하고, 자동화된 프로세스가 실제 상태를 Git에 정의된 상태와 지속적으로 일치시키는 운영 방식입니다. 2021년 CNCF 산하 OpenGitOps 프로젝트가 공식화한 4가지 원칙을 차례로 살펴봅니다.
1. Declarative (선언적)
시스템의 원하는 상태(Desired State)를 선언적으로 기술합니다.
# "3개의 nginx Pod가 실행되어야 한다"는 선언
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
- 명령형(Imperative) 접근: "nginx Pod를 3개 만들어라" (
kubectl scale) - 선언형(Declarative) 접근: "nginx Pod가 3개인 상태가 되어야 한다" (YAML manifest)
Kubernetes 자체가 선언적 시스템이며, GitOps는 이 철학을 Git과 결합한 것입니다.
2. Versioned and Immutable (버전 관리와 불변성)
Git의 각 커밋은 불변이며 전체 히스토리가 보존됩니다.
# 모든 변경 이력 추적 가능
git log --oneline
# a1b2c3d (HEAD) feat: scale nginx to 5 replicas
# d4e5f6g fix: update nginx image to 1.25.3
# g7h8i9j initial: deploy nginx with 3 replicas
# 특정 시점으로 롤백
git revert a1b2c3d
이 성질이 세 가지 이점을 만듭니다.
- Audit Trail: 누가, 언제, 왜 변경했는지 모든 기록이 남습니다.
- Rollback:
git revert로 이전 상태로 즉시 복구할 수 있습니다. - Compliance: 금융, 헬스케어 등 규제 산업의 감사 요구사항을 충족합니다.
인프라 변경도 Pull Request로 흐르게 되므로, 코드 리뷰와 승인 절차가 배포 파이프라인의 일부가 됩니다.
3. Pulled Automatically (자동 Pull)
원하는 상태의 변경이 Git에 푸시되면 클러스터 내부의 에이전트가 자동으로 감지하고 적용합니다. CI 서버가 클러스터에 접근하는 것이 아니라 에이전트가 Git을 주기적으로 확인하는 Pull 모델이 핵심입니다.
flowchart LR
subgraph GitRepo [Git Repository]
Manifest[Kubernetes Manifests]
end
subgraph Cluster [Kubernetes Cluster]
Agent[GitOps Agent\nArgoCD / Flux]
Workload[Running Workloads]
end
Agent -->|1. Pull & Watch| GitRepo
Agent -->|2. Compare| Workload
Agent -->|3. Apply changes| Workload
4. Continuously Reconciled (지속적 조정)
에이전트는 변경을 한 번 적용하고 끝내지 않습니다. Reconciliation Loop를 통해 실제 상태와 원하는 상태를 지속적으로 비교하고, 차이가 발생하면 자동으로 수정합니다.
flowchart TB
subgraph ReconciliationLoop [Reconciliation Loop]
direction TB
Observe[1. 현재 상태 관찰\nCurrent State]
Compare[2. 원하는 상태와 비교\nDesired State in Git]
Drift{Drift\n발견?}
Apply[3. 차이 조정\nReconcile]
Wait[4. 대기\nInterval]
end
Observe --> Compare --> Drift
Drift -->|Yes| Apply --> Wait
Drift -->|No| Wait
Wait --> Observe
중요: 누군가
kubectl edit으로 replicas를 5개로 바꾸면 에이전트가 이를 감지하고 Git에 정의된 3개로 자동 복구합니다. 이것이 Drift Detection과 Self-Healing입니다.
Push vs Pull 배포 모델
GitOps를 이해하는 핵심은 Push 모델과 Pull 모델의 차이입니다.
Push 모델 (전통적 CI/CD)
flowchart LR
subgraph External [클러스터 외부]
CI[CI Server]
end
subgraph Cluster [Kubernetes Cluster]
API[API Server]
Pod[Pods]
end
CI -->|1. kubectl apply\n강력한 권한 필요| API
API --> Pod
CI 서버가 클러스터에 직접 접근하므로 admin 수준의 kubeconfig가 필요하고, CI 서버가 침해되면 클러스터도 위험해집니다.
Pull 모델 (GitOps)
flowchart LR
subgraph External [클러스터 외부]
Git[Git Repository]
end
subgraph Cluster [Kubernetes Cluster]
Agent[GitOps Agent]
API[API Server]
Pod[Pods]
end
Agent -->|1. Git clone/pull\n읽기 권한만| Git
Agent -->|2. kubectl apply\n내부 통신| API
API --> Pod
클러스터 내부의 에이전트가 Git을 Pull하므로 Git에 대한 읽기 전용 권한만 필요하며, 외부에서 클러스터로 향하는 인바운드 연결이 사라집니다.
보안 관점 비교
| 관점 | Push 모델 | Pull 모델 |
|---|---|---|
| 네트워크 방향 | 외부 → 클러스터 | 클러스터 → 외부 (Git) |
| 필요 권한 | CI에 cluster-admin | Agent에 내부 권한 |
| 공격 표면 | CI 서버 침해 시 클러스터 위험 | Git 저장소 보호에 집중 |
| 방화벽 | 인바운드 허용 필요 | 아웃바운드만 허용 |
참고: ArgoCD나 Flux도 클러스터 내부에서는 강력한 권한을 가집니다. 다만 공격 벡터가 Git 저장소로 단일화되어 보안 관리가 단순해집니다.
Kubernetes Controller 패턴의 확장
GitOps가 Kubernetes에서 특히 잘 작동하는 이유는 Controller 패턴 때문입니다. 모든 Kubernetes 컨트롤러는 Reconciliation Loop로 동작합니다.
// Kubernetes Controller의 핵심 로직
func (c *Controller) Reconcile(ctx context.Context, req Request) (Result, error) {
// 1. 현재 상태 조회
current, err := c.Get(ctx, req.NamespacedName)
if err != nil {
return Result{}, err
}
// 2. 원하는 상태와 비교
desired := c.calculateDesiredState(current)
// 3. 차이가 있으면 조정
if !reflect.DeepEqual(current.Spec, desired) {
current.Spec = desired
return Result{}, c.Update(ctx, current)
}
// 4. Requeue for next reconciliation
return Result{RequeueAfter: 30 * time.Second}, nil
}
GitOps 에이전트는 이 패턴의 상위 레벨 구현입니다. Desired State의 위치가 API Server에서 Git 저장소로 확장되었을 뿐입니다.
| 개념 | Kubernetes Controller | GitOps Agent |
|---|---|---|
| Desired State | Spec in YAML | Git Repository |
| Current State | Status in API Server | Live Kubernetes Objects |
| Reconciliation | Controller Manager | ArgoCD/Flux Controller |
| Watch mechanism | Informer (etcd watch) | Git polling / Webhook |
GitOps는 Kubernetes의 선언적 모델을 Git까지 확장한 것이며, Infrastructure as Code의 자연스러운 진화라고 할 수 있습니다.
ArgoCD 심화: 중앙 집중형 GitOps
ArgoCD는 Kubernetes를 위한 선언적 GitOps 지속 배포(Continuous Delivery) 도구입니다. 2022년 CNCF Graduated 프로젝트가 되었고, 다음과 같은 특징을 가집니다.
- Plain YAML, Helm, Kustomize, Jsonnet 등 다양한 매니페스트 지원
- React 기반의 강력한 Web UI로 실시간 애플리케이션 상태 시각화
- 단일 인스턴스로 여러 클러스터 관리 가능
- OIDC, SAML, LDAP, GitHub, GitLab 등 SSO 통합
3개의 핵심 컴포넌트
flowchart TB
subgraph External [외부]
User[사용자]
Git[Git Repository]
Webhook[Webhook]
end
subgraph ArgoCD [ArgoCD Namespace]
subgraph API [API Server]
REST[REST API]
GRPC[gRPC API]
WebUI[Web UI]
end
subgraph Repo [Repo Server]
Clone[Git Clone]
Render[Manifest Rendering]
Cache[Cache]
end
subgraph Controller [Application Controller]
Reconciler[Reconciliation Loop]
Diff[Diff Engine]
Sync[Sync Engine]
end
Redis[(Redis)]
end
subgraph Cluster [Kubernetes Cluster]
APIServer[K8s API Server]
Resources[Workloads]
end
User --> WebUI & REST
Git --> Clone
Webhook --> API
API <--> Repo
API <--> Controller
API <--> Redis
Controller <--> APIServer
APIServer --> Resources
Repo --> Clone
Clone --> Render
Render --> Cache
API Server는 ArgoCD의 프론트엔드입니다. REST/gRPC API로 CLI와 Web UI, 외부 시스템과 통신하고, RBAC과 SSO 인증을 처리하며, Git 변경 시 즉시 Sync를 트리거하는 Webhook을 받습니다.
# ArgoCD CLI는 gRPC API 사용
argocd app list
argocd app sync my-app
Repo Server는 Git 저장소 클론과 매니페스트 렌더링을 담당합니다.
sequenceDiagram
participant AC as Application Controller
participant RS as Repo Server
participant Git as Git Repository
participant Cache as Cache
AC->>RS: GetManifests(app, revision)
RS->>Cache: 캐시 확인
alt 캐시 Hit
Cache-->>RS: 캐시된 매니페스트
else 캐시 Miss
RS->>Git: git clone/fetch
Git-->>RS: Repository
RS->>RS: Helm/Kustomize 렌더링
RS->>Cache: 캐시 저장
end
RS-->>AC: Rendered Manifests
팁: Repo Server는 stateless입니다. 렌더링 결과를 Redis에 캐싱하므로 스케일 아웃이 용이합니다.
Application Controller는 ArgoCD의 핵심 엔진으로, 앞서 본 Kubernetes Controller 패턴 그대로 동작합니다.
// 간략화된 Application Controller 로직
func (c *ApplicationController) Reconcile(app *Application) error {
// 1. Git에서 Desired State 조회 (Repo Server 통해)
desired, err := c.repoServer.GetManifests(app)
if err != nil {
return err
}
// 2. 클러스터에서 Current State 조회
current, err := c.kubectl.GetResources(app.Destination)
if err != nil {
return err
}
// 3. Diff 계산
diff := c.diffEngine.Compare(desired, current)
// 4. 상태 업데이트
app.Status.Sync.Status = calculateSyncStatus(diff)
app.Status.Health = calculateHealth(current)
// 5. Auto Sync 활성화 시 동기화
if app.Spec.SyncPolicy.Automated != nil && diff.HasChanges() {
return c.syncEngine.Sync(app, desired)
}
return nil
}
Application CRD
ArgoCD가 관리하는 모든 애플리케이션은 Application Custom Resource로 정의됩니다.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd # ArgoCD가 설치된 네임스페이스
finalizers:
- resources-finalizer.argocd.argoproj.io # 삭제 시 리소스 정리
spec:
# 프로젝트 (RBAC 단위)
project: default
# 소스: 어디서 가져올 것인가
source:
repoURL: https://github.com/myorg/myrepo.git
targetRevision: HEAD # 브랜치, 태그, 커밋 SHA
path: k8s/overlays/prod # 매니페스트 경로
# 목적지: 어디에 배포할 것인가
destination:
server: https://kubernetes.default.svc # 클러스터 API Server
namespace: production # 타겟 네임스페이스
# 동기화 정책
syncPolicy:
automated:
prune: true # Git에서 삭제된 리소스 클러스터에서도 삭제
selfHeal: true # 수동 변경 시 Git 상태로 복구
syncOptions:
- CreateNamespace=true # 네임스페이스 자동 생성
status:
sync:
status: Synced # Synced, OutOfSync, Unknown
revision: abc123
health:
status: Healthy # Healthy, Progressing, Degraded, Missing
resources:
- kind: Deployment
name: my-app
status: Synced
health: Healthy
Helm 차트를 소스로 쓸 때는 values와 parameters를 함께 선언합니다.
spec:
source:
repoURL: https://charts.bitnami.com/bitnami
chart: nginx
targetRevision: 15.0.0
helm:
releaseName: my-nginx
values: |
replicaCount: 3
service:
type: ClusterIP
parameters:
- name: image.tag
value: "1.25"
valueFiles:
- values-prod.yaml
Kustomize 소스도 마찬가지로 세부 설정을 오버라이드할 수 있습니다.
spec:
source:
repoURL: https://github.com/myorg/myrepo.git
path: k8s/overlays/prod
kustomize:
namePrefix: prod-
nameSuffix: -v1
images:
- name: my-app
newTag: v1.2.3
commonLabels:
environment: production
Sync 상태와 Health 상태
ArgoCD는 두 가지 축으로 상태를 추적합니다. Sync Status는 Git과 클러스터의 일치 여부이고, Health Status는 배포된 리소스의 실제 동작 상태입니다.
| Sync Status | 의미 |
|---|---|
| Synced | Git과 클러스터 상태 일치 |
| OutOfSync | 차이 존재 (Git 변경 또는 수동 수정) |
| Unknown | 상태 확인 불가 |
| Health Status | 의미 | 예시 |
|---|---|---|
| Healthy | 정상 동작 중 | Deployment replicas 충족 |
| Progressing | 진행 중 | 롤아웃 중 |
| Degraded | 문제 발생 | Pod CrashLoopBackOff |
| Suspended | 일시 중지 | HPA 일시 중지 |
| Missing | 리소스 없음 | 아직 생성 안 됨 |
Sync 전략
Manual Sync는 명시적 트리거가 필요하고, Automated Sync는 Git 변경 시 자동으로 동기화합니다.
# Manual Sync: 명시적 트리거 필요
spec:
syncPolicy: {} # 또는 생략
# Automated Sync: Git 변경 시 자동 동기화
spec:
syncPolicy:
automated:
prune: true # Git에서 삭제된 리소스 정리
selfHeal: true # Drift 발생 시 자동 복구
allowEmpty: false # 빈 매니페스트 허용 여부
주의:
prune: true는 Git에서 리소스 정의를 삭제하면 클러스터에서도 즉시 삭제합니다. 실수로 파일을 지우면 프로덕션 리소스가 사라질 수 있습니다.
Self-Heal의 동작은 다음과 같습니다.
sequenceDiagram
participant Ops as 운영자
participant K8s as Kubernetes
participant ArgoCD as ArgoCD Controller
participant Git as Git Repository
Ops->>K8s: kubectl scale deployment --replicas=5
Note over K8s: 수동 변경 (Drift)
ArgoCD->>K8s: 현재 상태 확인 (replicas: 5)
ArgoCD->>Git: Git 상태 확인 (replicas: 3)
ArgoCD->>ArgoCD: Drift 감지!
alt selfHeal: true
ArgoCD->>K8s: kubectl apply (replicas: 3)
Note over K8s: Git 상태로 복구
else selfHeal: false
Note over ArgoCD: OutOfSync 상태 유지
Note over K8s: 변경 유지
end
세밀한 동기화 제어에는 Sync Options를 사용합니다.
spec:
syncPolicy:
syncOptions:
- CreateNamespace=true # 네임스페이스 자동 생성
- PrunePropagationPolicy=foreground # 삭제 전파 정책
- PruneLast=true # 다른 리소스 먼저 적용 후 Prune
- Replace=true # apply 대신 replace 사용
- ServerSideApply=true # Server-Side Apply 사용
- FailOnSharedResource=true # 다른 앱과 리소스 공유 금지
- RespectIgnoreDifferences=true # ignoreDifferences 존중
Sync Waves와 Hooks
복잡한 배포 시나리오에서 리소스 적용 순서를 제어할 때 사용합니다. Sync Waves는 argocd.argoproj.io/sync-wave 어노테이션으로 순서를 지정합니다.
# Wave 0: 먼저 실행 (기본값)
apiVersion: v1
kind: Namespace
metadata:
name: my-app
annotations:
argocd.argoproj.io/sync-wave: "0"
---
# Wave 1: Namespace 생성 후 실행
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
annotations:
argocd.argoproj.io/sync-wave: "1"
---
# Wave 2: ConfigMap 이후 실행
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
annotations:
argocd.argoproj.io/sync-wave: "2"
Sync Hooks는 특정 시점에 Job을 실행합니다.
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
annotations:
argocd.argoproj.io/hook: PreSync # 언제 실행?
argocd.argoproj.io/hook-delete-policy: HookSucceeded # 언제 삭제?
spec:
template:
spec:
containers:
- name: migrate
image: my-app:latest
command: ["./migrate.sh"]
restartPolicy: Never
| Hook Phase | 시점 | 사용 사례 |
|---|---|---|
PreSync |
Sync 이전 | DB 마이그레이션, 백업 |
Sync |
일반 리소스와 함께 | 특수 처리 필요 리소스 |
PostSync |
Sync 완료 후 | 알림 전송, 테스트 실행 |
SyncFail |
Sync 실패 시 | 롤백, 알림 |
| Delete Policy | 설명 |
|---|---|
HookSucceeded |
성공 시 삭제 |
HookFailed |
실패 시 삭제 |
BeforeHookCreation |
다음 Hook 생성 전 삭제 |
두 기능을 조합하면 Blue-Green 배포 같은 시나리오를 선언적으로 구성할 수 있습니다.
# PreSync: 새 버전 배포 전 준비
---
apiVersion: batch/v1
kind: Job
metadata:
name: pre-deploy-check
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/sync-wave: "-1"
spec:
template:
spec:
containers:
- name: check
image: curlimages/curl
command: ["curl", "-f", "http://health-check-endpoint"]
restartPolicy: Never
---
# Sync: 새 버전 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-green
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
replicas: 3
# ...
---
# PostSync: 트래픽 전환
apiVersion: batch/v1
kind: Job
metadata:
name: switch-traffic
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/sync-wave: "1"
spec:
template:
spec:
containers:
- name: switch
image: my-tool:latest
command: ["./switch-service.sh", "my-app-green"]
restartPolicy: Never
ApplicationSet: 멀티 클러스터의 답
ApplicationSet은 단일 템플릿에서 여러 Application을 자동 생성합니다. 멀티 클러스터, 멀티 테넌트 환경에서 필수입니다.
flowchart TB
subgraph Generators [ApplicationSet Generators]
List[List Generator]
Cluster[Cluster Generator]
Git[Git Generator]
Matrix[Matrix Generator]
Merge[Merge Generator]
end
List --> |정적 목록| Apps1[Applications]
Cluster --> |등록된 클러스터| Apps2[Applications]
Git --> |디렉토리/파일 구조| Apps3[Applications]
Matrix --> |Generator 조합| Apps4[Applications]
List Generator는 정적 목록에서 Application을 만듭니다.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-apps
namespace: argocd
spec:
goTemplate: true
generators:
- list:
elements:
- cluster: dev
url: https://dev.k8s.example.com
namespace: dev
- cluster: staging
url: https://staging.k8s.example.com
namespace: staging
- cluster: prod
url: https://prod.k8s.example.com
namespace: prod
template:
metadata:
name: 'my-app-{{.cluster}}'
spec:
project: default
source:
repoURL: https://github.com/myorg/myrepo.git
targetRevision: HEAD
path: 'k8s/overlays/{{.cluster}}'
destination:
server: '{{.url}}'
namespace: '{{.namespace}}'
Cluster Generator는 ArgoCD에 등록된 클러스터를 기준으로 자동 생성합니다.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: cluster-addons
namespace: argocd
spec:
goTemplate: true
generators:
- clusters:
selector:
matchLabels:
environment: production
template:
metadata:
name: '{{.name}}-monitoring'
spec:
project: default
source:
repoURL: https://github.com/myorg/cluster-addons.git
path: monitoring
destination:
server: '{{.server}}'
namespace: monitoring
syncPolicy:
automated:
prune: true
selfHeal: true
Git Directory Generator는 Git 디렉토리 구조 자체를 소스로 삼습니다.
apps/
├── frontend/
│ └── kustomization.yaml
├── backend/
│ └── kustomization.yaml
└── database/
└── kustomization.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: apps
namespace: argocd
spec:
goTemplate: true
generators:
- git:
repoURL: https://github.com/myorg/gitops.git
revision: HEAD
directories:
- path: apps/*
template:
metadata:
name: '{{.path.basename}}'
spec:
project: default
source:
repoURL: https://github.com/myorg/gitops.git
path: '{{.path.path}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{.path.basename}}'
팁: Git Directory Generator를 사용하면 디렉토리만 추가해도 자동으로 Application이 생성됩니다. 코드 변경 없이 새 앱을 추가할 수 있습니다.
AppProject와 RBAC
AppProject는 Application을 그룹화하고 접근 제어를 적용합니다.
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: production
namespace: argocd
spec:
description: Production applications
# 허용된 소스 저장소
sourceRepos:
- https://github.com/myorg/*
# 허용된 목적지
destinations:
- namespace: 'prod-*'
server: https://prod.k8s.example.com
# 허용된 클러스터 리소스
clusterResourceWhitelist:
- group: ''
kind: Namespace
# 거부된 네임스페이스 리소스
namespaceResourceBlacklist:
- group: ''
kind: ResourceQuota
# RBAC 역할
roles:
- name: developer
description: Developer access
policies:
- p, proj:production:developer, applications, get, production/*, allow
- p, proj:production:developer, applications, sync, production/*, allow
groups:
- developers
RBAC 정책은 CSV 형식으로 세밀하게 정의합니다.
# p, <role>, <resource>, <action>, <object>, <effect>
# 개발자: 읽기 + sync만 가능
p, role:developer, applications, get, */*, allow
p, role:developer, applications, sync, */*, allow
# 운영자: 모든 권한
p, role:operator, applications, *, */*, allow
p, role:operator, clusters, *, *, allow
# 특정 프로젝트만 접근
p, role:team-a, applications, *, team-a/*, allow
자주 만나는 문제
OutOfSync가 해소되지 않을 때는 HPA처럼 다른 컨트롤러가 관리하는 필드를 의심해야 합니다. ignoreDifferences로 해당 필드를 비교 대상에서 제외합니다.
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas # HPA가 관리하는 필드 무시
- group: ""
kind: Service
jqPathExpressions:
- .spec.clusterIP # 자동 할당 필드 무시
Sync 실패 시에는 상태와 이벤트를 확인하고 필요하면 강제 재동기화합니다.
# 동기화 상태 확인
argocd app get my-app
# 이벤트 확인
kubectl describe application my-app -n argocd
# 강제 재동기화
argocd app sync my-app --force
Application 삭제 시 리소스가 남는다면 resources-finalizer.argocd.argoproj.io finalizer가 설정되어 있는지 확인합니다. 리소스를 남긴 채 Application만 지우려면 argocd app delete my-app --cascade=false를 사용합니다.
Flux 심화: GitOps Toolkit
Flux는 Kubernetes를 위한 GitOps 도구 집합입니다. 2019년 Weaveworks에서 시작해 2022년 CNCF Graduated 프로젝트가 되었습니다. ArgoCD가 단일 애플리케이션이라면, Flux는 GitOps Toolkit이라는 독립적인 컨트롤러들의 집합입니다.
- 필요한 컨트롤러만 선택적으로 설치할 수 있는 모듈러 아키텍처
- 모든 설정이 CRD로 관리되는 Kubernetes Native 접근
- 컨테이너 이미지 자동 업데이트(Image Automation) 내장
- 네임스페이스 기반의 Multi-tenancy 지원
참고: Weaveworks가 2024년 폐업했지만, Flux는 CNCF Graduated 프로젝트로서 커뮤니티와 CNCF의 지원 아래 활발히 개발되고 있습니다.
두 도구의 설계 철학 차이는 아키텍처 그림에서 바로 드러납니다.
flowchart TB
subgraph ArgoCD [ArgoCD - 모놀리식]
A_ALL[단일 애플리케이션\nAPI Server + Repo Server + Controller]
A_UI[강력한 Web UI]
A_APP[Application CRD]
end
subgraph Flux [Flux - 마이크로서비스]
F_SOURCE[Source Controller]
F_KUSTOMIZE[Kustomize Controller]
F_HELM[Helm Controller]
F_NOTIFY[Notification Controller]
F_IMAGE[Image Automation]
end
A_ALL --> A_UI
A_ALL --> A_APP
F_SOURCE --> F_KUSTOMIZE
F_SOURCE --> F_HELM
F_KUSTOMIZE --> F_NOTIFY
F_HELM --> F_NOTIFY
GitOps Toolkit 컨트롤러 구성
flowchart TB
subgraph Sources [Source Controllers]
Git[GitRepository]
Helm[HelmRepository]
OCI[OCIRepository]
Bucket[Bucket]
end
subgraph Reconcilers [Reconciler Controllers]
Kustomize[Kustomization]
HelmRelease[HelmRelease]
end
subgraph Automation [Automation Controllers]
ImageRepo[ImageRepository]
ImagePolicy[ImagePolicy]
ImageUpdate[ImageUpdateAutomation]
end
subgraph Notifications [Notification Controller]
Alert[Alert]
Provider[Provider]
Receiver[Receiver]
end
Git --> Kustomize
Git --> HelmRelease
Helm --> HelmRelease
OCI --> Kustomize
OCI --> HelmRelease
ImageRepo --> ImagePolicy
ImagePolicy --> ImageUpdate
ImageUpdate --> Git
Kustomize --> Alert
HelmRelease --> Alert
Alert --> Provider
| 컨트롤러 | 역할 | CRD |
|---|---|---|
| Source Controller | Git, Helm, OCI에서 아티팩트 가져오기 | GitRepository, HelmRepository, OCIRepository, Bucket |
| Kustomize Controller | Kustomize 매니페스트 적용 | Kustomization |
| Helm Controller | Helm 릴리스 관리 | HelmRelease |
| Notification Controller | 이벤트 알림 | Alert, Provider, Receiver |
| Image Automation | 컨테이너 이미지 업데이트 자동화 | ImageRepository, ImagePolicy, ImageUpdateAutomation |
Source Controller
외부 소스에서 아티팩트를 가져와 클러스터 내에서 사용 가능하게 만듭니다. 가장 기본이 되는 GitRepository부터 봅니다.
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: my-app
namespace: flux-system
spec:
# 동기화 주기
interval: 1m
# Git 저장소 URL
url: https://github.com/myorg/my-app.git
# 브랜치/태그/커밋
ref:
branch: main
# 또는
# tag: v1.0.0
# commit: abc123
# 인증 (필요시)
secretRef:
name: git-credentials
# 특정 경로만 가져오기
ignore: |
# 전체 제외
/*
# 특정 디렉토리만 포함
!/deploy/
인증은 Secret으로 연결합니다. HTTPS는 사용자명과 토큰, SSH는 개인키와 known_hosts를 담습니다.
# HTTPS 인증
apiVersion: v1
kind: Secret
metadata:
name: git-credentials
namespace: flux-system
type: Opaque
stringData:
username: git
password: <GitHub PAT 등 액세스 토큰>
HelmRepository는 Helm Chart Repository를, OCIRepository는 OCI Registry의 아티팩트를 소스로 씁니다.
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: bitnami
namespace: flux-system
spec:
interval: 1h
url: https://charts.bitnami.com/bitnami
# OCI Registry도 지원
# type: oci
# url: oci://ghcr.io/myorg/charts
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
name: podinfo
namespace: flux-system
spec:
interval: 5m
url: oci://ghcr.io/stefanprodan/manifests/podinfo
ref:
tag: latest
팁: OCI 아티팩트는 컨테이너 레지스트리에 Kubernetes 매니페스트를 저장하는 방식입니다. Git 없이도 GitOps가 가능해집니다.
Kustomize Controller
GitRepository에서 가져온 매니페스트를 클러스터에 적용합니다. ArgoCD의 Application에 대응하는 것이 Kustomization CRD입니다.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
namespace: flux-system
spec:
# 동기화 주기
interval: 10m
# 재시도 주기 (실패 시)
retryInterval: 2m
# 소스 참조
sourceRef:
kind: GitRepository
name: my-app
# 매니페스트 경로
path: ./deploy/production
# 타겟 네임스페이스 (모든 리소스에 적용)
targetNamespace: production
# Git에서 삭제된 리소스 정리
prune: true
# 타임아웃
timeout: 5m
# Health Check 대기
wait: true
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: my-app
namespace: production
Flux의 강점 중 하나는 Kustomization 간 의존성 관리입니다. ArgoCD의 Sync Waves가 리소스 단위 순서 제어라면, Flux의 dependsOn은 배포 단위 간 의존성을 선언합니다.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: infrastructure
namespace: flux-system
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: infra
path: ./infrastructure
prune: true
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 10m
# infrastructure가 Ready 상태가 된 후에만 적용
dependsOn:
- name: infrastructure
sourceRef:
kind: GitRepository
name: apps
path: ./apps
prune: true
Kustomize 빌드 후 변수를 치환하는 Post-build Substitution도 지원합니다.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
namespace: flux-system
spec:
# ...
postBuild:
substitute:
ENVIRONMENT: production
REPLICAS: "3"
substituteFrom:
- kind: ConfigMap
name: cluster-config
- kind: Secret
name: cluster-secrets
# deployment.yaml (치환 대상)
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: ${REPLICAS}
template:
spec:
containers:
- name: app
env:
- name: ENVIRONMENT
value: ${ENVIRONMENT}
Helm Controller
Helm 릴리스를 선언적으로 관리합니다. 업그레이드 실패 시 재시도와 롤백 정책까지 CRD로 선언할 수 있습니다.
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: nginx
namespace: default
spec:
# 동기화 주기
interval: 10m
# Helm Chart 소스
chart:
spec:
chart: nginx
version: "15.x" # SemVer 범위 지원
sourceRef:
kind: HelmRepository
name: bitnami
namespace: flux-system
# Values 설정
values:
replicaCount: 3
service:
type: ClusterIP
# Values 파일 참조
valuesFrom:
- kind: ConfigMap
name: nginx-values
valuesKey: values.yaml
- kind: Secret
name: nginx-secrets
valuesKey: credentials
# Upgrade 설정
upgrade:
remediation:
retries: 3
remediateLastFailure: true
# Rollback 설정
rollback:
recreate: true
cleanupOnFail: true
HelmRepository 대신 GitRepository에서 직접 차트를 가져올 수도 있습니다. chart.spec.chart에 Git 내 경로(예: ./charts/my-app)를 지정하고 sourceRef를 GitRepository로 연결하면 됩니다.
Image Automation: Flux의 차별점
Flux는 컨테이너 이미지 버전 자동 업데이트를 핵심 기능으로 내장합니다. 레지스트리 스캔부터 Git 커밋까지 전체가 자동화됩니다.
sequenceDiagram
participant Registry as Container Registry
participant Image as ImageRepository
participant Policy as ImagePolicy
participant Update as ImageUpdateAutomation
participant Git as Git Repository
participant Flux as Flux Controllers
participant K8s as Kubernetes
Image->>Registry: 새 이미지 태그 스캔
Registry-->>Image: v1.2.3 발견
Image->>Policy: 정책과 매칭
Policy->>Policy: SemVer 필터링
Policy->>Update: 업데이트 트리거
Update->>Git: manifest 수정 & commit
Git->>Flux: 변경 감지
Flux->>K8s: 배포
ImageRepository가 레지스트리에서 태그를 스캔하고, ImagePolicy가 어떤 태그를 선택할지 결정합니다.
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: my-app
namespace: flux-system
spec:
# 스캔할 이미지
image: ghcr.io/myorg/my-app
# 스캔 주기
interval: 5m
# 인증 (필요시)
secretRef:
name: ghcr-auth
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: my-app
namespace: flux-system
spec:
imageRepositoryRef:
name: my-app
# 정책: SemVer 범위
policy:
semver:
range: ">=1.0.0"
# 또는: 알파벳순 최신
# policy:
# alphabetical:
# order: asc
# 또는: 숫자순 최신
# policy:
# numerical:
# order: asc
ImageUpdateAutomation이 Git 저장소의 매니페스트를 실제로 수정하고 커밋합니다.
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
name: my-app
namespace: flux-system
spec:
interval: 5m
# 업데이트할 Git 저장소
sourceRef:
kind: GitRepository
name: my-app
# Git 설정
git:
checkout:
ref:
branch: main
commit:
author:
name: Flux
email: flux@myorg.com
messageTemplate: |
Update images
{{range .Changed.Changes -}}
- {{.OldValue}} -> {{.NewValue}}
{{end}}
push:
branch: main
# 업데이트 대상 파일
update:
path: ./deploy
strategy: Setters # 마커 기반 업데이트
업데이트 위치는 매니페스트에 마커 주석으로 표시합니다.
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: app
image: ghcr.io/myorg/my-app:v1.0.0 # {"$imagepolicy": "flux-system:my-app"}
중요: ArgoCD는 Image Updater가 별도 프로젝트인 반면, Flux는 Image Automation이 핵심 기능으로 내장되어 있습니다.
Notification Controller
배포 이벤트를 외부 시스템으로 알립니다. Alert가 어떤 이벤트를 보낼지, Provider가 어디로 보낼지 정의합니다.
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: on-call-alerts
namespace: flux-system
spec:
# 심각도 필터
eventSeverity: error
# 모니터링 대상
eventSources:
- kind: Kustomization
name: '*'
- kind: HelmRelease
name: '*'
# 알림 대상
providerRef:
name: slack
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
name: slack
namespace: flux-system
spec:
type: slack
channel: devops-alerts
secretRef:
name: slack-webhook
Slack, Discord, Microsoft Teams, GitHub, GitLab, PagerDuty, Datadog, Sentry 등 다양한 Provider를 지원합니다. 반대로 외부에서 Flux로 Webhook을 받는 Receiver도 있어, GitHub push 이벤트로 즉시 reconcile을 트리거할 수 있습니다.
Bootstrap: Flux가 자기 자신을 관리한다
Flux 설치의 권장 방법은 Bootstrap입니다. Flux 컴포넌트 자체를 Git에 커밋하고, Flux가 자기 자신을 GitOps로 관리하게 만듭니다.
# GitHub 저장소로 Bootstrap
flux bootstrap github \
--owner=myorg \
--repository=fleet-infra \
--branch=main \
--path=clusters/production \
--personal
# 생성되는 구조
fleet-infra/
└── clusters/
└── production/
└── flux-system/
├── gotk-components.yaml # Flux 컴포넌트
├── gotk-sync.yaml # 자기 자신을 관리하는 Kustomization
└── kustomization.yaml
Multi-tenancy 패턴
Flux는 네임스페이스 기반 격리를 지원합니다. 팀별 네임스페이스에 GitRepository와 Kustomization을 두고, 제한된 권한의 ServiceAccount로 reconcile을 수행하게 합니다.
# 팀 A 전용 소스와 Kustomization
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: team-a-apps
namespace: team-a # 팀 네임스페이스
spec:
url: https://github.com/myorg/team-a-apps.git
# ...
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: team-a-apps
namespace: team-a
spec:
# 해당 팀 네임스페이스로 제한
targetNamespace: team-a
serviceAccountName: team-a-reconciler # 제한된 권한의 SA
# ...
실무 도입 시 고려사항
도구 선택과 별개로 저장소 구조와 Secrets 전략을 먼저 정해야 합니다.
레포지토리는 Monorepo와 Polyrepo 중에서 선택합니다.
# Monorepo 방식
gitops-repo/
├── apps/
│ ├── frontend/
│ ├── backend/
│ └── database/
├── infrastructure/
│ ├── monitoring/
│ └── ingress/
└── clusters/
├── dev/
├── staging/
└── prod/
# Polyrepo 방식
frontend-gitops/ # 프론트엔드 팀 관리
backend-gitops/ # 백엔드 팀 관리
platform-gitops/ # 플랫폼 팀 관리
환경별 설정은 Kustomize의 base/overlays 패턴이나 Helm values로 분리합니다.
base/
├── deployment.yaml
├── service.yaml
└── kustomization.yaml
overlays/
├── dev/
│ ├── kustomization.yaml
│ └── replica-patch.yaml
├── staging/
│ └── kustomization.yaml
└── prod/
├── kustomization.yaml
└── resource-patch.yaml
Secrets는 Git에 평문으로 저장하면 안 됩니다. 다음 중 하나를 선택합니다.
- Sealed Secrets: 암호화된 형태로 Git에 저장
- External Secrets Operator: AWS Secrets Manager 등 외부 저장소 참조
- SOPS: 파일 레벨 암호화 (Flux가 네이티브 지원)
ArgoCD vs Flux: 무엇을 선택할까
| 기능 | ArgoCD | Flux |
|---|---|---|
| CNCF 단계 | Graduated | Graduated |
| 아키텍처 | 모놀리식 (단일 애플리케이션) | 마이크로서비스 (독립 컨트롤러) |
| 설치 | helm install 한 줄 | flux bootstrap |
| 리소스 사용 | 약 400MB 메모리 | 약 100MB 메모리 |
| Web UI | 강력한 내장 UI | Weave GitOps (별도) |
| 멀티 클러스터 | 중앙 ArgoCD가 관리 (Hub-Spoke) | 각 클러스터에 Flux 설치 (분산) |
| 이미지 자동화 | Image Updater (별도) | 내장 |
| Helm 지원 | Application에서 처리 | HelmRelease CRD |
| OCI 지원 | 지원 | 네이티브 지원 |
| Health Check | 내장 (강력) | wait/healthChecks |
| SOPS 지원 | 제한적 | 네이티브 |
| 학습 곡선 | 낮음 (UI 친화적) | 중간 (CRD 이해 필요) |
ArgoCD가 맞는 경우는 다음과 같습니다.
- 운영팀이 시각적 모니터링을 선호해 UI가 중요할 때
- 한 곳에서 모든 클러스터를 관리하는 중앙 집중 구조가 필요할 때
- Sync Waves, Hooks 같은 복잡한 Sync 전략이 필요할 때
- 팀의 학습 곡선을 줄이고 싶을 때
Flux가 맞는 경우는 다음과 같습니다.
- Edge 등 리소스 제한 환경에서 경량화가 중요할 때
- 이미지 자동 업데이트가 핵심 요구사항일 때
- SOPS로 Secrets를 관리할 때
- 각 클러스터의 독립성이 중요할 때
- CRD 중심의 Kubernetes Native 접근을 선호할 때
정리
| 개념 | 설명 |
|---|---|
| GitOps | Git을 Single Source of Truth로 삼는 운영 방식 |
| 4가지 원칙 | Declarative, Versioned, Pulled, Reconciled |
| Pull 모델 | 클러스터 내부 에이전트가 Git을 감시, 인바운드 연결 제거 |
| ArgoCD | API Server + Repo Server + Application Controller, Application CRD와 ApplicationSet 중심 |
| Flux | GitOps Toolkit 컨트롤러 집합, Kustomization/HelmRelease CRD와 Image Automation 중심 |
| 선택 기준 | UI와 중앙 관리는 ArgoCD, 경량화와 모듈화는 Flux |
두 도구 모두 CNCF Graduated 프로젝트로 성숙도는 충분하므로, 조직의 운영 모델에 맞는 쪽을 고르면 됩니다. 하편에서는 Kustomize vs Helm 환경별 설정 관리, Secrets Management, CI/CD 파이프라인 통합을 다룹니다.