Crossplane
Operator Pattern을 Infra 전체에 적용하면 IaC는 apply가 아니라 control plane이 된다
Crossplane은 Cloud infra를 Kubernetes API object로 다루고, Controller가 그 선언을 계속 현실로 수렴시키는 control plane이다.
Intro
지난 글에서 CRD와 Operator를 다뤘다.
Crossplane은 그 패턴을 Infra 전체에 적용한다.
RDS, S3 Bucket, VPC, IAM Role, GKE Cluster 등을 Crossplane은 Kubernetes object로 선언하게 하고, Controller가 그 선언과 실제 Cloud 상태의 차이를 끊임없이 맞추려고 한다.
즉 Crossplane은 도구라기보다 Operator의 집합이다. Cloud API 하나하나에 붙는 Controller들과, 그것들을 조합해 나만의 Infra API를 만드는 장치로 이뤄져 있다.
Why a Control Plane
Terraform을 떠올려 보자.
terraform apply를 실행하면 계획을 세우고, Cloud API를 호출해 Resource를 만들고, 결과를 state file에 적고, 프로세스가 끝난다.
다음에 누군가 Console에서 그 Resource를 손으로 바꿔도, 다시 apply를 돌리기 전까지 아무도 눈치채지 못한다.
IaC는 이 모델을 오래 써 왔다. 사람이 명령을 내릴 때만 원하는 상태로 맞춰진다.
순간형 실행 모델이라고 하더이다.
Crossplane은 Infra를 관리하는 주체를 CLI가 아니라 Cluster 안에서 상시 도는 Controller로 둔다.
공식문서 발췌: The control plane configures your software, then monitors it throughout its lifecycle. If your software ever drifts from your desired state, the control plane automatically corrects the drift.
Managed Resources
Crossplane의 가장 작은 단위는 Managed Resource(MR)다. 외부 서비스 하나에 대응하는 Kubernetes object다.
A managed resource (MR) represents an external service in a Provider. Every external service managed by Crossplane maps to a managed resource.
S3 Bucket 하나가 Bucket object 하나, RDS Instance 하나가 Instance object 하나다.
Cluster 안의 이 object를 managed resource, Cloud 쪽의 실제 Bucket을 external resource라 부르고, 둘은 1:1로 묶인다.
MR은 저마다 고유한 API endpoint를 갖는다. kind: Instance, apiVersion: ec2.aws.m.upbound.io/v1beta1 같은 식으로, group과 kind와 version이 붙는다.
Crossplane의 provider를 설치하면 이런 MR type 수백 개가 CRD로 Cluster에 등록된다.
MR의 spec.forProvider가 외부 Resource의 parameter로 매핑된다. 그리고 Crossplane은 forProvider를 SSOT로 본다.
Crossplane considers the forProvider field of a managed resource the “source of truth” for external resources. If a user makes a change inside a Provider’s web console, Crossplane reverts that change back to what’s configured in the forProvider setting.
누가 AWS Console에 들어가 Bucket 설정을 손으로 바꾸면 Crossplane이 그것을 되돌린다. Terraform이 다음 apply 때까지 모르는 그 Drift를 Crossplane은 상시 감지하고 즉시 교정한다.
immutable field를 다루는 방식도 Terraform과 다르다.
Terraform deletes and recreates a resource to change an immutable field. Crossplane only deletes an external resource if you delete the resource object from Kubernetes.
바꿀 수 없는 field를 건드리면 Terraform은 Resource를 지웠다 다시 만든다. Crossplane은 Kubernetes에서 그 object를 지우지 않는 한 external resource를 함부로 삭제하지 않는다.
Cloud Resource에는 생성 후에 수정이 불가능한 속성이 있다. EC2 의 AZ 나 S3 Bucket 의 이름이 이에 해당한다.
AWS API 자체가 이 값의 수정을 지원하지 않아서, 바꾸려면 지우고 새로 만드는 방법밖에 없다.
Terraform 은 AZ 를 바꾸고 Apply 하면 “아 수정이 안되니까 교체해야지” 하고 기존 Resource 를 삭제하고 새로 만든다.
실로 패도적이다.
반면 Crossplane은 spec.forProvider 에서 같은 값을 바꿔도 기존 resource 를 삭제하지 않고, MR이 Synced=False 같은 상태로 남는다.
Providers
MR type과 그 뒤의 Controller를 Cluster에 심는 것이 Provider다.
Provider를 설치하면 두 가지가 생긴다. 그 provider의 API를 나타내는 CRD들과, 그 API를 실제 Cloud로 reconcile하는 pod다.
Installing a provider also creates a Provider pod that’s responsible for reconciling the Provider’s APIs into the Kubernetes cluster. Providers constantly watch the state of the desired managed resources and create any external resources that are missing.
provider-aws, provider-gcp, provider-azure가 대표적이고, Helm이나 GitHub, 심지어 Terraform을 감싼 provider도 있다. 설치는 object 하나로 끝난다.
실제 Provider 가 없더라도 Terraform provider 가 있다면 provider 자체를 구현하는건 그리 어렵지는 않다.
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws-s3
spec:
package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v2.0.0
provider가 외부와 통신하는 방법, 특히 인증은 ProviderConfig로 분리한다. MR은 자신이 어떤 ProviderConfig로 인증할지 참조하고, 하나의 provider가 여러 계정이나 여러 자격으로 동시에 일할 수 있다.
잠시 정리하면:
Provider가 Cloud API로 연동, ProviderConfig가 인증을 담당, MR로 개별 Resource 하나를 선언한다.
Composite Resources
MR을 직접 다루는 건 결국 Cloud API를 YAML로 옮겨 적는 일이다.
Bucket 하나, 롤 하나, 정책 하나를 낱개로 적는건데 이건 사실 Terraform 도 똑같긴 하다.
근데 “우리 회사의 표준 Database 한 벌”처럼, 여러 Resource를 묶은 개념을 선언하고 싶을 때도 있다.
- CompositeResourceDefinition(XRD): 내가 만들 새 API의 schema를 정의한다.
Database라는 type에engine,size,region같은 field가 있다고 선언하는 부분이다. XRD를 apply하면 그 type의 CRD가 Cluster에 생긴다. - Composite Resource(XR): 그 API의 실제 Instance다. 사용자가
Databaseobject를 하나 만드는 순간이 XR이다. - Composition: XR 하나가 어떤 MR들로 구성될지를 정의하는 template이다.
Database하나가 RDS Instance + Subnet Group + Security Group + Parameter Group으로 구성되게 만들 수 있다.
A composite resource or XR is a custom API. You use two Crossplane types to create a new custom API: A Composite Resource Definition (XRD) defines the XR’s schema. A Composition configures how the XR creates other resources.
Platform 팀이 XRD와 Composition으로 “우리 조직의 Database란 이런 것”이라는 API를 한 번 정의해 두면, Application 팀은 그 뒤의 RDS 옵션 수백 개를 몰라도 engine과 size만 적어 Database를 self-service로 받는다.
이런 점들이 Crossplane을 단순한 IaC가 아니라 Platform Engineering Framework로 만든다.
Composition Functions
그러면 Composition은 XR 하나를 MR 여러 개로 어떻게 펼치는지 보자.
Crossplane은 Composition Function을 이용해서 이 문제를 풀려고 했다.
Composition을 함수들의 Pipeline으로 정의하고, 각 단계가 코드로 Resource를 만들어 낸다.
A Composition is a pipeline of composition functions. Crossplane passes each Function in the pipeline the result of the previous Function.
함수는 관측된 상태(observed state)를 입력으로 받아, 원하는 Resource 집합(desired state)을 출력으로 돌려주는 프로그램이다.
Composition functions are like configuration language plugins. Functions allow you to write your configuration in multiple languages, including YAML, KCL, Python, and Go.
즉 “복제본 개수가 3 이상이면 read replica를 붙인다” 같은 로직을 Python이나 Go로 그냥 짠다.
그러면서도 Controller를 직접 만들 필요는 없다. 함수는 선언형 결과만 돌려주고, 그것을 실제로 적용하고 reconcile하는 일은 Crossplane이 맡는다.
With Crossplane you don’t have to write a controller. Instead you configure a pipeline of functions.
Reconciliation and Drift
- 사용자가 XR을 apply한다.
- Composition 함수 Pipeline이 그 XR을 MR 집합으로 펼친다.
- 각 MR의 provider Controller가 자신이 맡은 external resource를 Cloud에 만든다.
이 전체가 멈추지 않는다. Controller들은 계속 실제 상태를 관찰하고, forProvider와 어긋나면 되돌린다.
Terraform은 상태를 별도 state file에 두고, Drift를 plan이나 refresh 시점에만 감지한 뒤 다시 apply해야 교정된다.
Crossplane은 별도 state file이 없다. 상태는 Cluster의 etcd 안, 각 object의 status에 있고, Drift 감지와 교정이 상시 루프의 일부다.
| 축 | Terraform | Crossplane |
|---|---|---|
| 실행 모델 | 사람이 apply할 때 정렬 | Controller가 상시 정렬 |
| 상태 저장 | 별도 state file + 원격 backend | etcd 안 object status, 별도 파일 없음 |
| Drift | plan/refresh 시점에만 감지, 재apply 필요 | 상시 감지, 자동 교정 |
| 작성 | HCL | YAML + Composition Function(Python, Go, KCL 등) |
이 점이 Crossplane의 최대 장점이자 최대 부담인듯 하다.
사람이 잊어도 Infra가 스스로 정렬을 유지하지만, 그러려면 control plane 자체가 항상 살아 있어야 한다.
Where It Bites
다 좋은데 약간 애매한 점은 Bootstrap이다.
Crossplane은 Cloud Infra를 만드는 도구인데, 정작 그 자신이 이미 떠 있는 Kubernetes Cluster를 요구한다.
IaC로 가장 먼저 만들고 싶은 것이 Cluster인데, 그 도구가 Cluster를 전제로 한다.
그래서 보통 Cluster와 Crossplane 자신은 작은 Terraform으로 bootstrap하고, 그 위에서 나머지 99%의 Resource를 Crossplane으로 관리한다.
GitOps with ArgoCD
Crossplane은 Kubernetes object를 다루므로 GitOps와 궁합이 좋다.
XR과 Composition을 Git에 두고 ArgoCD가 Cluster에 sync하면, Infra의 desired state가 Git에서 관리된다.
Backstage + ArgoCD + Crossplane(Provisioning)을 묶는 구성이 Platform Engineering에서 상당히 유용하다.
다만 기본 설정으로는 잘 맞물리지 않고, 몇 가지를 손으로 맞춰 줘야 한다.
- Health check customize: ArgoCD의 기본 health 평가로는 Crossplane Resource가 계속
Progressing에 걸린다.*.crossplane.io/*등에 대해 Lua script로 Ready/Synced condition을 읽어 Healthy를 판정하도록 가르쳐야 한다. - Resource tracking을 annotation으로: label 기반 기본 추적 방식을 annotation 기반으로 바꿔야 한다.
- 대량 object 제외와 QPS 상향: Crossplane이 CRD와 부수 object를 대량 생성하므로,
ProviderConfigUsage같은 것을 추적에서 빼고 ArgoCD의 rate limit을 올려야 한다.
특히 자주 부딪히는 지점은 sync 순서다.
The fact that Argo doesn’t sync CRDs and force them to exist before future sync waves is a huge pain point. You find yourself having to manually sync CRDs — even if you’ve specified sync waves.
XR/Composition의 health를 ArgoCD가 스스로 이해하지 못하고, CRD가 하위 Resource보다 먼저 존재하도록 순서를 강제하기 어렵다는 것.
References
- Crossplane — What’s Crossplane?
https://docs.crossplane.io/latest/whats-crossplane/ - Crossplane — Managed Resources
https://docs.crossplane.io/latest/managed-resources/managed-resources/ - Crossplane — Providers
https://docs.crossplane.io/latest/packages/providers/ - Crossplane — Compositions
https://docs.crossplane.io/latest/composition/compositions/ - Crossplane — Composite Resource Definitions
https://docs.crossplane.io/latest/composition/composite-resource-definitions/ - Crossplane — What’s New in v2?
https://docs.crossplane.io/v2.3/whats-new/ - Crossplane — Using Crossplane with Argo CD
https://docs.crossplane.io/latest/guides/crossplane-with-argo-cd/ - Crossplane v2.0.0 Release
https://github.com/crossplane/crossplane/releases/tag/v2.0.0 - CNCF — Crossplane Graduation Announcement
https://www.cncf.io/announcements/2025/11/06/cloud-native-computing-foundation-announces-graduation-of-crossplane/ - Spacelift — Crossplane vs Terraform
https://spacelift.io/blog/crossplane-vs-terraform - InfoQ — Crossplane 2.0 Adds Application Support
https://www.infoq.com/news/2025/08/crossplane-applications-v2/