Kargo
GitOps 배포 위에 환경 간 promotion을 얹는 컨트롤러
GitOps 배포 위에 “검증된 변경을 dev에서 stage로, stage에서 prod로 밀어 올리는” promotion 과정을 얹는 컨트롤러
Intro
Argo CD는 Git에 적힌 desired state를 클러스터의 actual state로 맞춘다.
Git이 Source of Truth이고, 에이전트는 그 둘의 차이를 계속 지워 나가는 reconciler다.
보통 develop 환경과 release 환경은 별도의 클러스터로 구성되기 마련이다.
develop에서 검증된 이미지 태그를 stage 매니페스트에 반영하고, stage에서 문제가 없으면 다시 production으로 넘긴다.
이 승격 과정을 하나의 리소스 모델로 끌어올린 것이 Kargo다.
어떤 변경을 한 단계의 desired state에서 다음 단계의 desired state로 전파하는 절차 전부를 promotion으로 보고, 그 promotion을 선언적으로 정의하고 실행한다.
여기서 App of Apps나 ApplicationSet과는 층위가 다르다.
ApplicationSet은 “여러 Application을 어떻게 한꺼번에 생성하느냐”를 generator로 푼다.
Kargo는 “검증된 변경이 Environment Chain을 어떻게 통과하느냐”를 푼다.
하나는 앱의 생성을, 하나는 변경의 이동을 다루므로 서로를 대체하지 않고 대개 함께 쓰인다.
Stage, Freight, Warehouse
Kargo의 모델은 세 개의 CRD로 이뤄진다.
Warehouse는 아티팩트 소스를 감시한다. 컨테이너 이미지 레지스트리, Helm 차트 저장소, Git 저장소를 구독해 새 리비전이 올라오는지 폴링하고, 발견하면 그것을 하나의 Freight로 묶는다.
apiVersion: kargo.akuity.io/v1alpha1
kind: Warehouse
metadata:
name: kargo-demo
namespace: kargo-demo
spec:
subscriptions:
- image:
repoURL: public.ecr.aws/nginx/nginx
semverConstraint: ^1.29.0
discoveryLimit: 5
Freight는 이렇게 묶인 결과물이다. 특정 이미지 태그, 특정 Git 커밋, 특정 차트 버전을 함께 가리키는 불변의 meta-artifact다.
한 번 만들어진 Freight는 바뀌지 않고, 여러 아티팩트가 한 덩어리로 이동한다.
dev에서 검증한 것과 prod에 올라가는 것이 같음을 이 Layer 에서 보장한다.
위 Warehouse는 nginx:1.31.2를 발견하면 그 태그를 담은 Freight를 하나 만든다.
Stage는 promotion으로 바뀌는 desired state 하나다.
Stage를 chaining하면 Freight가 흐르는 파이프라인이 된다.
각 Stage는 requestedFreight로 자기가 받을 Freight의 출처를 선언한다.
첫 단계는 Warehouse에서 곧장(direct: true) 받고, 그다음부터는 상류 Stage에서(stages: [...]) 받는다.
apiVersion: kargo.akuity.io/v1alpha1
kind: Stage
metadata:
name: stage # dev 다음 단계
namespace: kargo-demo
spec:
requestedFreight:
- origin:
kind: Warehouse
name: kargo-demo
sources:
stages:
- dev # dev에서 검증된 Freight만 이 단계로 온다
promotionTemplate:
spec:
steps: [] # 아래 Promotion Steps 참고
이렇게 dev ← Warehouse, stage ← dev, prod ← stage로 연결하면 chain이 완성된다.
Freight는 이 chain을 한 칸씩만 건너뛴다.
prod는 stage를 통과한 Freight만 받으므로, 검증되지 않은 변경이 prod로 새치기하는 경로가 원천적으로 없다.
Promotion Steps
그러면 promotion은 실제로 무엇을 하는가. Stage의 promotionTemplate.spec.steps에 적힌 단계들이 순서대로 실행된다.
아래는 이미지 태그를 갈아 끼워 배포까지 잇는 전형적인 흐름이다.
steps:
- uses: git-clone # 소스(main)와 대상 브랜치(stage/<stage>)를 체크아웃
config:
repoURL: http://gitea:3000/kargo/kargo-demo.git
checkout:
- { branch: main, path: ./src }
- { branch: stage/${{ ctx.stage }}, create: true, path: ./out }
- uses: git-clear # 대상 브랜치 작업 트리를 비운다
config: { path: ./out }
- uses: kustomize-set-image # Freight의 이미지 태그를 base에 반영
as: update
config:
path: ./src/base
images:
- image: public.ecr.aws/nginx/nginx
tag: ${{ imageFrom("public.ecr.aws/nginx/nginx").Tag }}
- uses: kustomize-build # 해당 stage 오버레이를 렌더
config: { path: ./src/stages/${{ ctx.stage }}, outPath: ./out }
- uses: git-commit # 렌더된 매니페스트를 대상 브랜치에 커밋
as: commit
config: { path: ./out, message: ${{ outputs.update.commitMessage }} }
- uses: git-push
config: { path: ./out }
- uses: argocd-update # Argo CD Application을 방금 커밋으로 sync
config:
apps:
- name: kargo-demo-${{ ctx.stage }}
sources:
- repoURL: http://gitea:3000/kargo/kargo-demo.git
desiredRevision: ${{ outputs.commit.commit }}
Kargo는 클러스터에 직접 kubectl apply를 하지 않는다.
대신 Render된 Manifest를 Git의 stage 전용 브랜치에 커밋하고, 마지막에 Argo CD에게 그 커밋으로 맞추라고 알린다.
실제 동기화는 Argo CD가 한다. 그래서 모든 promotion이 Git 커밋으로 남고, Git이 곧 “무엇이 언제 어느 단계로 올라갔는지”의 Audit Log가 된다.
배포가 끝나면 Stage는 health check를 돌린다.
Freight가 무사히 배포되고 상태가 healthy로 확인되면 그 Freight는 해당 Stage에서 verified로 표시되고, 그제서야 하류 Stage로의 promotion이 열린다.
실제로 dev 승격이 끝나면 Freight의 상태에 검증 시각이 찍힌다.
{ "dev": { "verifiedAt": "..." }, "stage": { "verifiedAt": "..." } }
이 검증 게이트가 Chain의 안전장치다. dev에서 healthy를 못 받은 Freight는 stage 승격 버튼 자체가 잠긴다.
Multi-Cluster with Argo CD
promotion이 Git 커밋과 Argo CD sync로 이뤄진다는 사실은 멀티클러스터로 자연스럽게 확장된다.
환경마다 클러스터가 다르다면, 환경마다 Argo CD Application의 목적지 클러스터를 다르게 두기만 하면 된다.
전형적인 구성은 이렇다. 컨트롤 플레인 클러스터 하나에 Kargo와 Argo CD를 올리고, Argo CD에 dev, stage, prod 클러스터를 외부 클러스터로 등록한다.
ApplicationSet이 환경별로 Application을 하나씩 만드는데, 각 Application의 destination은 그 환경의 클러스터를, source는 그 환경의 Git 브랜치를 가리킨다.
flowchart LR
W[Warehouse<br/>image poll] -->|Freight| K
subgraph CP[control plane cluster]
K[Kargo]
A[Argo CD]
end
K -->|git commit + sync| A
A -->|app: dev branch| DEV[(dev cluster)]
A -->|app: stage branch| STG[(stage cluster)]
A -->|app: prod branch| PRD[(prod cluster)]
promotion이 한 단계 진행될 때 벌어지는 일을 다시 따라가 보면, dev 승격은 stage/dev 브랜치에 커밋하고 kargo-demo-dev Application을 그 커밋으로 sync한다.
이 Application의 목적지가 dev 클러스터이므로 dev에만 배포된다.
stage 승격은 stage/stage 브랜치와 kargo-demo-stage Application을 건드리고, 그 목적지는 stage 클러스터다.
같은 Freight가 같은 절차로, 그러나 매번 다른 브랜치와 다른 클러스터를 향해 흐른다.
Kargo가 배포를 Argo CD에 위임한 덕에 멀티클러스터가 특별한 기능이 아니라 Argo CD의 클러스터 등록과 ApplicationSet의 목적지 설정만으로 성립한다.
References
- Kargo — Core Concepts
https://docs.kargo.io/user-guide/core-concepts/ - Kargo — Quickstart
https://docs.kargo.io/quickstart - Kargo — Promotion Steps Reference
https://docs.kargo.io/user-guide/reference-docs/promotion-steps/ - Argo CD — ApplicationSet
https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/ - Argo CD — Declarative Cluster Registration
https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/#clusters