목차
- Crossplane GitOps가 멀티 클라우드에서 유리한 이유, 그리고 아닌 경우
- Crossplane GitOps 설계 전에 먼저 못 박아야 하는 결정
- 실전 구현 1: 저장소 분리와 기본 설치는 이렇게 가져가면 덜 꼬입니다
- 실전 구현 2: XR 하나로 클라우드별 구현 갈라타기
- 실전에서 자주 터지는 문제와 근본 원인
- 1. CRD와 요청 리소스를 같은 배포 단위로 넣었더니 가끔만 성공
- 2. ProviderConfig 인증 문제인데 요청 리소스만 들여다봄
- 3. 요청 API를 너무 두껍게 만들어서 장애 반경이 커짐
- 4. Composition 변경이 기존 XR 전체에 바로 전파됨
- 5. Git에서 지웠더니 실제 리소스 삭제까지 이어져 버림
- 검증 포인트: 무엇을 봐야 진짜 성공인지
- 운영 권장안: 이런 팀이면 이렇게 가는 편이 낫습니다
- 자주 묻는 질문과 마지막 판단 기준
- Crossplane GitOps가 Terraform을 완전히 대체하나요?
- 멀티 클라우드 운영을 위해 꼭 복잡한 추상화가 필요한가요?
- 처음 도입하는 팀이라면 어디까지를 1차 목표로 잡는 게 좋을까요?
Crossplane GitOps로 멀티 클라우드 인프라 운영 전략 잡기
Crossplane GitOps를 붙이면 처음엔 다 비슷한 착각을 하더라고요. Git에 YAML만 넣으면 멀티 클라우드 운영이 정리될 거라고요. 그런데 실제 운영에선 YAML 문법보다 인터페이스를 어디까지 추상화할지, 어느 계정과 어느 클러스터에 누가 적용 권한을 가질지, 실패했을 때 어느 계층에서 멈췄는지 어떻게 판별할지가 훨씬 더 중요합니다.
특히 AWS, GCP, Azure가 섞여 있고 환경마다 네트워크, 계정, 규정이 다르면 더 그렇습니다. 이때 Crossplane GitOps의 진짜 가치는 단순히 인프라를 Kubernetes 안으로 가져오는 데 있지 않습니다. 제가 현업에서 더 크게 느낀 건 개발팀이 보는 요청 인터페이스와 플랫폼 팀이 유지하는 구현 세부를 분리할 수 있다는 점이었어요. 멀티 클라우드 운영이 어려운 이유는 클라우드가 여러 개라서가 아니라, 요청 인터페이스와 실행 책임이 뒤엉키기 때문이거든요.
그래서 이 글은 단순 설치 가이드로 가지 않겠습니다. Crossplane GitOps를 언제 쓰면 이득이 커지고, 언제는 오히려 운영 복잡도만 늘어나는지, 그리고 실제 설계에서 먼저 봐야 할 기준을 중심으로 정리해보겠습니다.

Git 저장소, Argo CD, Crossplane, 여러 클라우드 또는 클러스터가 어떻게 연결되는지 한눈에 보여주는 아키텍처 이미지 위치입니다.
Crossplane GitOps가 멀티 클라우드에서 유리한 이유, 그리고 아닌 경우
제가 이 조합을 높게 보는 이유는 하나입니다. 요청은 공통 API로 받고, 실행은 환경별 구현으로 분기할 수 있기 때문입니다. 개발팀은 비슷한 형식의 리소스를 요청하고, 플랫폼 팀은 뒤에서 AWS용 Composition을 태울지 GCP용 Composition을 태울지 고릅니다. 이 구조가 잡히면 클라우드 차이가 사용자 경험으로 바로 새지 않아요.
- 적합한 경우: 개발팀 셀프서비스가 필요하고, 플랫폼 팀이 공통 API를 설계할 역량이 있을 때
- 적합한 경우: 이미 Argo CD나 Flux 같은 GitOps 흐름이 있고, 인프라도 같은 승인 체계로 묶고 싶을 때
- 보류할 경우: 팀마다 요구사항 차이가 커서 공통 인터페이스가 거의 안 나올 때
- 보류할 경우: 단일 클라우드 비중이 높고 Terraform 워크스페이스 수준으로도 운영이 충분히 정리될 때
여기서 핵심 판단 기준은 반복되는 요청이 실제로 존재하느냐입니다. Namespace, Object Storage, 기본 권한 연결처럼 반복 요청이 많고 정책을 통일해야 하는 자원은 Crossplane GitOps와 잘 맞습니다. 반대로 일회성 예외 구성, 팀별 편차가 큰 네트워크 토폴로지, 사람 승인 없이는 못 건드리는 보안 자원은 억지로 추상화하면 디버깅 비용만 올라갑니다.
Crossplane GitOps 설계 전에 먼저 못 박아야 하는 결정
Crossplane GitOps가 꼬이는 팀은 대개 YAML을 늦게 배워서가 아니라 결정 순서가 뒤집혀서 그렇습니다. 이걸 정하지 않고 XRD부터 만들면 나중에 스키마를 몇 번씩 뜯어고치게 됩니다. 이거 생각보다 꽤 아픕니다.
| 의사결정 항목 | 선택지 | 이럴 때 권장 | 트레이드오프 |
|---|---|---|---|
| 제어면 토폴로지 | 중앙 Crossplane 1개 | 플랫폼 팀이 강하고 공통 정책을 한곳에서 관리해야 할 때 | 장애 반경이 커지고 ProviderConfig 권한 경계 설계가 더 중요해집니다. |
| 제어면 토폴로지 | 환경별 Crossplane 분리 | 계정·망 분리가 엄격하고 운영 책임이 환경별로 나뉠 때 | 중복 배포와 업그레이드 비용이 늘지만 장애 격리가 쉽습니다. |
| API 노출 방식 | Namespaced XR | Crossplane v2 신규 설계를 시작할 때 | 최신 권장 방식이지만 기존 Claim 중심 운영 모델과는 설계 습관이 달라집니다. |
| API 노출 방식 | Claim | v1 스타일 API를 이미 쓰고 있거나 호환 운영이 필요할 때 | Crossplane v2 신규 기본 모델은 아니므로 장기적으로는 XR 중심 전환을 검토해야 합니다. |
| 추상화 단위 | 얇은 API | 셀프서비스를 빨리 열고 장애 분석 시간을 줄여야 할 때 | 리소스 수는 늘지만 실패 지점이 선명합니다. |
| 클라우드 분기 방식 | Composition 분리 | AWS/GCP/Azure 구현 차이가 큰 경우 | 구현은 길어지지만 디버깅과 변경 영향도 파악이 쉽습니다. |
| 자격 증명 모델 | ProviderConfig 환경별 분리 | 계정, 프로젝트, 구독이 다르고 감사 경계가 중요할 때 | Secret 회전과 참조 관계를 별도 운영해야 합니다. |
제가 실제로는 이렇게 봅니다. 멀티 클라우드라고 해서 처음부터 공통 API를 넓게 잡지 마세요. 공통분모가 작으면 추상화도 작아야 합니다. Namespace, Bucket, 기본 IAM 연동처럼 공통성이 높은 것부터 묶고, 네트워크처럼 클라우드별 의미가 달라지는 영역은 늦게 가져오는 편이 훨씬 덜 아픕니다.
실전 구현 1: 저장소 분리와 기본 설치는 이렇게 가져가면 덜 꼬입니다
실무에서는 저장소 구조가 운영 모델을 거의 결정합니다. 저는 보통 platform, providers, claims-or-xrs를 분리합니다. 이유는 단순합니다. 변경 주기와 승인 권한이 다르기 때문입니다. 플랫폼 팀이 XRD와 Composition을 바꾸는 행위와, 서비스 팀이 요청 리소스 하나 추가하는 행위를 같은 PR 정책으로 묶으면 금방 병목이 생깁니다.
- platform: XRD, Composition, Function
- providers: Provider, ProviderConfig, Secret 참조 정책
- claims-or-xrs: 팀별 요청 리소스
최신 Crossplane v2 기준으로는 Namespaced XR이 기본이고, Claim은 신규 기본 모델이 아닙니다. 다만 기존 v1 스타일 API를 유지하는 환경이라면 Claim 패턴을 계속 쓸 수는 있습니다. 그래서 신규 도입 팀이라면 XR 중심으로 시작하고, 기존 팀이라면 Claim을 호환 운영 대상으로 보는 편이 더 안전합니다.
Crossplane 자체 설치는 현재 문서 기준으로 Helm chart가 가장 깔끔합니다. 그리고 Patch & Transform을 쓸 거라면 Composition Function도 같이 올려두는 편이 좋습니다. 나중에 예제를 확장할 때 진짜 편하더라고요.
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
helm install crossplane \
--namespace crossplane-system \
--create-namespace \
crossplane-stable/crossplane
kubectl get pods -n crossplane-system
kubectl apply -f - <<'EOF'
apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
name: function-patch-and-transform
spec:
package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2
EOF
kubectl get functions
GitOps 도구는 Crossplane를 애플리케이션 하나로 보지 않는 편이 좋습니다. 제가 권하는 최소 단위는 core, platform, workloads 세 층입니다. 이유는 순서와 장애 반경이 다르기 때문입니다. XRD가 아직 준비되지 않았는데 XR이나 Claim이 먼저 들어오면, YAML은 정상인데 동기화가 실패하는 황당한 상황이 바로 나옵니다.
Argo CD를 쓰면 sync wave 또는 앱 분리로 순서를 고정하는 편이 안전합니다. 또 Crossplane 공식 가이드처럼 annotation 기반 리소스 추적을 쓰고, ProviderConfigUsage는 UI에서 제외하는 구성이 운영 노이즈를 줄이는 데 도움이 됩니다.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: crossplane-platform
namespace: argocd
spec:
project: default
source:
repoURL: https://git.example.com/platform/infra.git
targetRevision: main
path: crossplane/platform
destination:
server: https://kubernetes.default.svc
namespace: crossplane-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
- SkipDryRunOnMissingResource=true
여기서 포인트는 세 가지입니다. CreateNamespace=true는 기본 편의고, SkipDryRunOnMissingResource=true는 CRD가 먼저 생기는 구조에서 불필요한 실패를 줄여줍니다. 그리고 앱이 커지기 시작하면 ApplyOutOfSyncOnly=true를 고려할 만합니다. Claim이나 XR 수가 많아질수록 매 sync마다 전체 리소스를 다시 건드리는 비용이 꽤 느껴지거든요.

플랫폼 정의, Provider 설정, Claim 또는 XR 배포가 어떤 순서로 Git에서 흘러가는지 보여주는 구성도 위치입니다.
실전 구현 2: XR 하나로 클라우드별 구현 갈라타기
최신 Crossplane v2 신규 설계라면 예시는 Claim보다 Namespaced XR로 잡는 편이 맞습니다. 여러 클러스터에 걸쳐 Namespace를 만드는 패턴은 작지만 실무적인 예제예요. 추상화가 맞는지 틀린지는 거대한 데이터베이스보다 이런 작은 자원에서 먼저 드러나는 경우가 많습니다.
먼저 provider-kubernetes와 ProviderConfig를 준비합니다. 서로 다른 대상 클러스터에 apply하는 흐름이므로 kubeconfig Secret 경계를 분명히 두는 게 핵심입니다. 운영에서 제일 흔한 사고는 권한이 부족해서 실패하는 경우보다, 잘못된 대상 클러스터에 정상 적용되는 경우입니다. 그래서 ProviderConfig 이름에 환경과 대상 의미를 같이 넣는 걸 권합니다.
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-kubernetes
spec:
package: xpkg.crossplane.io/crossplane-contrib/provider-kubernetes:v1.3.0
---
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: ProviderConfig
metadata:
name: aws-dev-cluster
namespace: crossplane-system
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: aws-dev-cluster-kubeconfig
key: kubeconfig
---
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: ProviderConfig
metadata:
name: gcp-prod-cluster
namespace: crossplane-system
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: gcp-prod-cluster-kubeconfig
key: kubeconfig
그다음 XRD와 Composition입니다. 아래 예시는 사용자에게는 동일한 PlatformNamespace만 보이고, 실제 구현은 AWS용과 GCP용 Composition으로 갈라집니다. 만약 기존 운영이 Claim 중심이라면 이 패턴을 v1 스타일 API와 함께 유지할 수는 있지만, 신규 도입이라면 XR로 시작하는 편이 덜 헷갈립니다.
apiVersion: apiextensions.crossplane.io/v2
kind: CompositeResourceDefinition
metadata:
name: platformnamespaces.platform.example.org
spec:
scope: Namespaced
group: platform.example.org
names:
kind: PlatformNamespace
plural: platformnamespaces
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
parameters:
type: object
properties:
name:
type: string
provider:
type: string
enum:
- aws
- gcp
required:
- name
- provider
required:
- parameters
---
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: platformnamespace-aws
labels:
provider: aws
spec:
compositeTypeRef:
apiVersion: platform.example.org/v1alpha1
kind: PlatformNamespace
mode: Pipeline
pipeline:
- step: patch-and-transform
functionRef:
name: function-patch-and-transform
input:
apiVersion: pt.fn.crossplane.io/v1beta1
kind: Resources
resources:
- name: namespace
base:
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
metadata:
namespace: crossplane-system
spec:
forProvider:
manifest:
apiVersion: v1
kind: Namespace
metadata:
name: placeholder
providerConfigRef:
name: aws-dev-cluster
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.name
toFieldPath: spec.forProvider.manifest.metadata.name
---
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: platformnamespace-gcp
labels:
provider: gcp
spec:
compositeTypeRef:
apiVersion: platform.example.org/v1alpha1
kind: PlatformNamespace
mode: Pipeline
pipeline:
- step: patch-and-transform
functionRef:
name: function-patch-and-transform
input:
apiVersion: pt.fn.crossplane.io/v1beta1
kind: Resources
resources:
- name: namespace
base:
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
metadata:
namespace: crossplane-system
spec:
forProvider:
manifest:
apiVersion: v1
kind: Namespace
metadata:
name: placeholder
providerConfigRef:
name: gcp-prod-cluster
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.name
toFieldPath: spec.forProvider.manifest.metadata.name
---
apiVersion: platform.example.org/v1alpha1
kind: PlatformNamespace
metadata:
namespace: platform-team
name: observability
spec:
crossplane:
compositionSelector:
matchLabels:
provider: aws
parameters:
name: observability
provider: aws
여기서 제가 꼭 보는 판단 기준이 있습니다. 분기 기준을 사용자에게 직접 노출할지, 운영 정책으로 숨길지입니다. 팀이 아직 멀티 클라우드 배치 정책을 이해하고 선택할 단계가 아니면 provider 같은 필드를 API에 열어주지 않는 편이 낫습니다. 반대로 플랫폼 팀이 모든 배치 결정을 대신하면서 병목이 심하면, 제한된 선택지를 노출하는 게 더 현실적일 때도 있어요.

AWS용 Composition과 GCP용 Composition이 같은 요청 인터페이스를 받아 서로 다른 대상 클러스터로 배포하는 흐름을 설명하는 이미지 위치입니다.
실전에서 자주 터지는 문제와 근본 원인
운영에서 힘든 지점은 YAML 자체가 아닙니다. 대부분의 장애는 Composition 선택, ProviderConfig 인증, 외부 API 응답, GitOps 적용 순서에서 생깁니다. 저도 초기에 이 구간에서 제일 오래 헤맸습니다.
1. CRD와 요청 리소스를 같은 배포 단위로 넣었더니 가끔만 성공
증상: 어떤 날은 되고 어떤 날은 안 됩니다. Argo CD에서는 리소스가 다 보이는데, 실제 sync에서는 XR이나 Claim이 먼저 들어가며 the server could not find the requested resource 비슷한 오류가 납니다.
근본 원인: 선언은 동시에 넣었지만, 컨트롤 플레인 입장에서는 XRD 등록과 이를 참조하는 리소스 생성이 완전히 다른 단계이기 때문입니다.
실무 처방: 앱을 분리하거나 sync wave를 명시하세요. 이 문제는 설치 순서라기보다 배포 계약 문제에 가깝습니다.
2. ProviderConfig 인증 문제인데 요청 리소스만 들여다봄
증상: XR은 생성됐는데 Ready가 끝까지 올라오지 않습니다.
근본 원인: kubeconfig Secret 참조 오류, 잘못된 컨텍스트, 대상 클러스터 RBAC 부족, 만료된 토큰 중 하나인 경우가 많습니다.
제가 보는 순서는 위에서 아래가 아니라 아래에서 위입니다. 요청 리소스에서 시작해 결국 Managed Resource와 Provider 로그까지 내려가야 원인이 보입니다.
kubectl get platformnamespaces.platform.example.org -A
kubectl get objects.kubernetes.m.crossplane.io -A
kubectl describe object.kubernetes.m.crossplane.io <object-name> -n crossplane-system
kubectl describe providerconfig.kubernetes.m.crossplane.io aws-dev-cluster -n crossplane-system
kubectl -n crossplane-system logs -l pkg.crossplane.io/provider=provider-kubernetes --tail=200
XR는 존재하는데Object가 없으면 Composition 선택이나 함수 입력을 먼저 봅니다.Object는 있는데Ready=False면 외부 클러스터 적용 실패 가능성이 큽니다.cannot get credentials,secret not found류 메시지는 거의 ProviderConfig 또는 Secret 참조 문제입니다.forbidden가 보이면 kubeconfig는 읽었지만 대상 클러스터 RBAC가 부족한 경우가 많습니다.
3. 요청 API를 너무 두껍게 만들어서 장애 반경이 커짐
이건 구조적 문제입니다. Namespace, RoleBinding, NetworkPolicy, ExternalSecret, StorageClass 참조까지 한 API에 다 넣으면 처음엔 멋있어 보여요. 그런데 하나만 실패해도 전체 요청 실패로 뭉개집니다. 멀티 클라우드에서는 실패 원인이 클라우드별로 달라서, 한 API는 한 책임 원칙을 거의 고정으로 가져가는 편이 훨씬 낫습니다.
- 네임스페이스 생성은 네임스페이스 생성
- 권한 바인딩은 별도 API
- 스토리지는 스토리지
- 데이터 서비스는 데이터 서비스
4. Composition 변경이 기존 XR 전체에 바로 전파됨
증상: 플랫폼 팀이 Composition을 수정했는데, 의도하지 않게 기존 요청들도 동작이 바뀝니다.
근본 원인: Crossplane는 Composition 변경을 revision으로 추적하고, XR은 기본적으로 최신 revision을 자동 추종할 수 있기 때문입니다.
실무 처방: 변경 위험이 큰 리소스는 compositionUpdatePolicy: Manual을 검토하세요. 데이터 서비스, 네트워크, 권한 모델처럼 기존 인스턴스가 한꺼번에 바뀌면 안 되는 자원은 자동 추종보다 수동 승격이 훨씬 안전합니다.
apiVersion: platform.example.org/v1alpha1
kind: PlatformNamespace
metadata:
namespace: platform-team
name: observability
spec:
crossplane:
compositionUpdatePolicy: Manual
compositionRef:
name: platformnamespace-aws
parameters:
name: observability
provider: aws
5. Git에서 지웠더니 실제 리소스 삭제까지 이어져 버림
GitOps를 인프라에 붙일 때 가장 민감한 건 생성보다 삭제입니다. Namespace나 버킷처럼 파급효과가 큰 자원은 PR 하나의 merge가 곧바로 파괴 작업으로 이어지지 않게 설계해야 합니다.
실무 처방: 삭제 위험이 큰 자원은 저장소를 분리하거나, PR 승인 정책을 강화하거나, Argo CD의 삭제 확인 옵션을 검토하세요. Crossplane GitOps는 생성 자동화보다 삭제 통제를 먼저 설계한 팀이 훨씬 안정적이었습니다.
검증 포인트: 무엇을 봐야 진짜 성공인지
배포가 끝났다고 끝이 아닙니다. 저는 항상 세 층을 분리해서 봅니다. Git 상태, Crossplane 상태, 실제 대상 상태입니다. 이 셋 중 하나라도 빠지면 동기화는 됐는데 서비스는 안 되는 애매한 상태가 생깁니다.
- GitOps 도구에서 애플리케이션 sync와 health가 정상인지 확인
- XR, Composition, Managed Resource가 모두 생성됐는지 확인
- 조건 값에서
Synced와Ready를 구분해 읽기 - 대상 클러스터 또는 클라우드에 실제 자원이 생겼는지 직접 검증
kubectl get platformnamespace observability -n platform-team -o yaml
kubectl get platformnamespaces.platform.example.org -A -o custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[?(@.type=="Synced")].status,READY:.status.conditions[?(@.type=="Ready")].status
kubectl get objects.kubernetes.m.crossplane.io -A -o custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[?(@.type=="Synced")].status,READY:.status.conditions[?(@.type=="Ready")].status
kubectl --kubeconfig ./aws-dev-cluster.kubeconfig get ns observability
Argo CD가 Healthy여도 외부 클러스터 리소스 생성까지 보장하지는 않습니다. 반대로 Crossplane Object가 Synced라고 해도 대상 클러스터에서 admission webhook이나 정책 엔진이 막고 있을 수 있습니다. 그래서 대시보드도 정상 개수보다 Ready=False 목록과 최근 이벤트 위주로 보는 편이 훨씬 실용적입니다.

검증 순서, 상태값 판독 기준, 상황별 추천 선택을 정리한 요약 인포그래픽 위치입니다.
운영 권장안: 이런 팀이면 이렇게 가는 편이 낫습니다
- 클라우드가 1~2개이고 플랫폼 팀이 작다: API는 얇게 두고, ProviderConfig만 환경별로 분리하세요. 초반에는 추상화의 아름다움보다 장애 분석 속도가 더 중요합니다.
- 플랫폼 팀과 개발팀이 분리돼 있다: XRD 스키마부터 먼저 고정하세요. 요청 저장소와 platform 저장소 권한을 분리하는 게 운영 비용을 줄입니다.
- 클라우드마다 자원 모델 차이가 크다: 억지 공통분모를 만들지 말고 Composition을 클라우드별로 나누세요. 구현 중복보다 잘못된 추상화가 더 비쌉니다.
- 변경 승인과 감사 추적이 중요하다: 사용자 요청 merge와 Composition 변경을 같은 승인 흐름에 두지 마세요. 위험도가 다릅니다.
- 삭제 사고가 치명적이다: GitOps 자동화 범위에서 삭제를 별도로 취급하세요. 생성 자동화보다 삭제 통제가 먼저입니다.
반대로 요청 패턴이 아직 안정되지 않았거나, 예외가 표준보다 많거나, 운영팀이 Kubernetes CRD 디버깅 경험이 부족하다면 조금 멈추는 게 맞습니다. Crossplane가 만능 해답은 아니거든요.
제가 실제로는 보통 이렇게 시작합니다. Namespace → Object Storage → 기본 권한 연결 → 데이터 서비스 순서입니다. 공통성이 높은 자원부터 열고, 실패 비용이 큰 자원은 뒤로 미루는 편이 운영 충격이 적었습니다.
자주 묻는 질문과 마지막 판단 기준
Crossplane GitOps가 Terraform을 완전히 대체하나요?
항상 그렇지는 않습니다. 다만 플랫폼 API를 Kubernetes 안에 두고, 서비스 팀 요청을 PR 기반으로 받으며, 멀티 클라우드 구현을 뒤로 숨겨야 한다면 Crossplane GitOps가 훨씬 자연스럽습니다. 반대로 공통 API보다 프로젝트별 세밀한 조정이 더 중요하면 Terraform이 더 단순할 때도 많습니다.
멀티 클라우드 운영을 위해 꼭 복잡한 추상화가 필요한가요?
아닙니다. 오히려 복잡한 추상화를 기본값으로 두지 않는 편이 낫습니다. 반복되는 요청이 명확할 때만 추상화하고, 반복되지 않으면 직결 구성이 더 낫습니다.
처음 도입하는 팀이라면 어디까지를 1차 목표로 잡는 게 좋을까요?
제가 추천하는 1차 목표는 간단합니다. 사용자가 같은 형식의 XR 또는 Claim을 제출하고, 플랫폼 팀이 ProviderConfig와 Composition으로 대상 환경을 통제할 수 있는 상태까지입니다. 거기서 이미 운영 가치가 나옵니다. 그다음에 Composition Revision, Secret 회전, 환경 승격, 정책 검증을 붙이는 순서가 훨씬 안정적입니다.
관련 글도 함께 보시면 흐름이 더 잘 잡힙니다. Argo CD sync wave 설계 가이드, Kubernetes GitOps 운영 체크리스트도 이어서 확인해보세요.
마지막 판단 기준을 한 줄로 줄이면 이겁니다. Crossplane GitOps는 인프라 자동화 도구라기보다 플랫폼 인터페이스 설계 도구에 가깝습니다. AWS와 GCP를 함께 다루는 팀이라면 API는 좁고 명확하게, Composition은 클라우드별로 분리해서 시작하세요. 반대로 단일 클라우드에 가깝거나 반복 요청이 적다면 굳이 복잡한 추상화를 도입하지 않는 편이 더 낫습니다.
![[Cloud] Crossplane GitOps로 멀티 클라우드 인프라 운영 전략](https://blog.pswq.net/wp-content/uploads/2026/08/crossplane-multi-cloud-gitops-implementation-strategy-thumbnail.jpg)
![[인프라] Crossplane 장애 사례로 배우는 멀티 클라우드 인프라 관리](https://blog.pswq.net/wp-content/uploads/2026/07/crossplane-multi-cloud-infrastructure-management-failure-recovery-thumbnail.jpg)



