Skip to content
Published on

Crossplane으로 Kubernetes-Native IaC 구현하기: Terraform을 대체하는 클라우드 제어 플레인

Authors
  • Name
    Twitter

들어가며

Terraform은 IaC의 사실상 표준이었지만, Kubernetes 중심 환경에서는 한계가 있습니다. 상태 파일 관리, Plan/Apply 수동 실행, 드리프트 감지의 어려움 등이 대표적입니다. Crossplane은 이 문제를 Kubernetes의 선언적 모델과 컨트롤러 패턴으로 해결합니다.

2025년 10월 CNCF Graduated 프로젝트로 승격된 Crossplane은 이제 프로덕션 환경에서 충분히 검증되었습니다.

Crossplane vs Terraform

항목TerraformCrossplane
실행 모델CLI (Plan/Apply)Kubernetes 컨트롤러 (지속적 조정)
상태 관리tfstate 파일 (외부 백엔드 필요)etcd (Kubernetes 자체)
드리프트 감지수동 plan 필요자동 (reconciliation loop)
추상화ModuleComposition
권한 관리별도 IAM 설정RBAC 통합
GitOps별도 파이프라인 필요ArgoCD/Flux 네이티브 연동

Crossplane 설치

# Helm으로 설치
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update

helm install crossplane crossplane-stable/crossplane \
  --namespace crossplane-system \
  --create-namespace \
  --set args='{"--enable-usages"}'

# 설치 확인
kubectl get pods -n crossplane-system
kubectl api-resources | grep crossplane

AWS Provider 설정

# AWS Provider 설치
cat <<EOF | kubectl apply -f -
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-aws-s3
spec:
  package: xpkg.upbound.io/upbound/provider-aws-s3:v1.18.0
EOF

# Provider 설치 확인
kubectl get providers

인증 설정

# AWS 자격 증명 Secret 생성
kubectl create secret generic aws-creds \
  -n crossplane-system \
  --from-file=credentials=$HOME/.aws/credentials

# ProviderConfig 생성
cat <<EOF | kubectl apply -f -
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
  name: default
spec:
  credentials:
    source: Secret
    secretRef:
      namespace: crossplane-system
      name: aws-creds
      key: credentials
EOF

Managed Resources — 클라우드 리소스 직접 관리

# s3-bucket.yaml
apiVersion: s3.aws.upbound.io/v1beta2
kind: Bucket
metadata:
  name: my-crossplane-bucket
spec:
  forProvider:
    region: ap-northeast-2
    tags:
      Environment: production
      ManagedBy: crossplane
  providerConfigRef:
    name: default
kubectl apply -f s3-bucket.yaml

# 상태 확인
kubectl get bucket my-crossplane-bucket
# NAME                    READY   SYNCED   EXTERNAL-NAME           AGE
# my-crossplane-bucket    True    True     my-crossplane-bucket    2m

# 상세 상태
kubectl describe bucket my-crossplane-bucket

RDS 인스턴스 생성

# rds-instance.yaml
apiVersion: rds.aws.upbound.io/v1beta2
kind: Instance
metadata:
  name: my-postgres
spec:
  forProvider:
    region: ap-northeast-2
    allocatedStorage: 20
    engine: postgres
    engineVersion: '16.4'
    instanceClass: db.t3.micro
    dbName: myapp
    masterUsername: admin
    masterPasswordSecretRef:
      name: rds-password
      namespace: default
      key: password
    skipFinalSnapshot: true
    publiclyAccessible: false
    vpcSecurityGroupIdSelector:
      matchLabels:
        app: my-rds
  providerConfigRef:
    name: default
  writeConnectionSecretToRef:
    name: rds-connection
    namespace: default
kubectl apply -f rds-instance.yaml

# 연결 정보가 자동으로 Secret에 저장됨
kubectl get secret rds-connection -o yaml

Composition — 추상화의 핵심

Composition은 여러 Managed Resource를 하나의 고수준 API로 묶습니다. Terraform의 Module과 비슷하지만 Kubernetes 네이티브입니다.

CompositeResourceDefinition (XRD)

# xrd-database.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: xdatabases.platform.example.com
spec:
  group: platform.example.com
  names:
    kind: XDatabase
    plural: xdatabases
  claimNames:
    kind: Database
    plural: databases
  versions:
    - name: v1alpha1
      served: true
      referenceable: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                parameters:
                  type: object
                  properties:
                    size:
                      type: string
                      enum: ['small', 'medium', 'large']
                      default: 'small'
                    engine:
                      type: string
                      enum: ['postgres', 'mysql']
                      default: 'postgres'
                    region:
                      type: string
                      default: 'ap-northeast-2'
                  required:
                    - size

Composition

# composition-database.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: aws-database
  labels:
    provider: aws
spec:
  compositeTypeRef:
    apiVersion: platform.example.com/v1alpha1
    kind: XDatabase
  resources:
    - name: rds-instance
      base:
        apiVersion: rds.aws.upbound.io/v1beta2
        kind: Instance
        spec:
          forProvider:
            region: ap-northeast-2
            engine: postgres
            engineVersion: '16.4'
            skipFinalSnapshot: true
            publiclyAccessible: false
          providerConfigRef:
            name: default
      patches:
        # size -> instanceClass 매핑
        - type: FromCompositeFieldPath
          fromFieldPath: spec.parameters.size
          toFieldPath: spec.forProvider.instanceClass
          transforms:
            - type: map
              map:
                small: db.t3.micro
                medium: db.t3.medium
                large: db.r6g.large
        # size -> allocatedStorage 매핑
        - type: FromCompositeFieldPath
          fromFieldPath: spec.parameters.size
          toFieldPath: spec.forProvider.allocatedStorage
          transforms:
            - type: map
              map:
                small: '20'
                medium: '100'
                large: '500'
        - type: FromCompositeFieldPath
          fromFieldPath: spec.parameters.engine
          toFieldPath: spec.forProvider.engine
        - type: FromCompositeFieldPath
          fromFieldPath: spec.parameters.region
          toFieldPath: spec.forProvider.region
    - name: security-group
      base:
        apiVersion: ec2.aws.upbound.io/v1beta1
        kind: SecurityGroup
        spec:
          forProvider:
            region: ap-northeast-2
            description: 'Managed by Crossplane'
            ingress:
              - fromPort: 5432
                toPort: 5432
                protocol: tcp
                cidrBlocks:
                  - '10.0.0.0/8'

Claim — 개발자가 사용하는 인터페이스

# claim-database.yaml
apiVersion: platform.example.com/v1alpha1
kind: Database
metadata:
  name: my-app-db
  namespace: team-alpha
spec:
  parameters:
    size: medium
    engine: postgres
    region: ap-northeast-2
  writeConnectionSecretToRef:
    name: db-credentials
kubectl apply -f claim-database.yaml

# 개발자는 이것만 알면 됨
kubectl get database -n team-alpha
# NAME        READY   CONNECTION-SECRET   AGE
# my-app-db   True    db-credentials      5m

# 플랫폼 엔지니어가 확인
kubectl get composite
kubectl get managed

Functions — 고급 Composition 로직

Crossplane Functions를 사용하면 Go/Python 등으로 복잡한 패치 로직을 구현할 수 있습니다.

# function-go-templating 사용 예
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: dynamic-database
spec:
  compositeTypeRef:
    apiVersion: platform.example.com/v1alpha1
    kind: XDatabase
  mode: Pipeline
  pipeline:
    - step: render-resources
      functionRef:
        name: function-go-templating
      input:
        apiVersion: gotemplating.fn.crossplane.io/v1beta1
        kind: GoTemplate
        source: Inline
        inline:
          template: |
            apiVersion: rds.aws.upbound.io/v1beta2
            kind: Instance
            metadata:
              annotations:
                gotemplating.fn.crossplane.io/composition-resource-name: rds
            spec:
              forProvider:
                region: {{ .observed.composite.resource.spec.parameters.region }}
                engine: {{ .observed.composite.resource.spec.parameters.engine }}
                instanceClass: {{ if eq .observed.composite.resource.spec.parameters.size "small" }}db.t3.micro{{ else if eq .observed.composite.resource.spec.parameters.size "medium" }}db.t3.medium{{ else }}db.r6g.large{{ end }}
    - step: auto-ready
      functionRef:
        name: function-auto-detect-ready

ArgoCD 연동 GitOps

Crossplane 리소스는 일반 Kubernetes 매니페스트이므로 ArgoCD와 자연스럽게 통합됩니다.

# argocd-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: infrastructure
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/infra-repo.git
    targetRevision: main
    path: crossplane/claims
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
# Git에 Claim을 push하면 자동으로 인프라가 프로비저닝됨
git add claim-database.yaml
git commit -m "feat: provision medium postgres for team-alpha"
git push origin main

# ArgoCD가 자동으로 sync
argocd app get infrastructure

Usages — 리소스 의존성 보호

삭제 순서를 보호하는 Usage 리소스입니다.

apiVersion: apiextensions.crossplane.io/v1alpha1
kind: Usage
metadata:
  name: db-used-by-app
spec:
  of:
    apiVersion: platform.example.com/v1alpha1
    kind: Database
    resourceRef:
      name: my-app-db
  by:
    apiVersion: apps/v1
    kind: Deployment
    resourceRef:
      name: my-app
      namespace: team-alpha
  reason: 'Database is used by the application'

트러블슈팅

# Provider 상태 확인
kubectl get providers
kubectl describe provider provider-aws-s3

# Managed Resource 이벤트 확인
kubectl describe bucket my-crossplane-bucket

# Composition 디버깅
kubectl get composite -o wide
kubectl describe xdatabase my-app-db-xxxxx

# Crossplane 로그
kubectl logs -n crossplane-system deploy/crossplane -f

# 특정 Provider 로그
kubectl logs -n crossplane-system \
  $(kubectl get pods -n crossplane-system -l pkg.crossplane.io/revision -o name | head -1) -f

정리

Crossplane은 Terraform과 경쟁하는 것이 아니라, Kubernetes 네이티브 환경에서 IaC를 한 단계 진화시킵니다:

  • 지속적 조정: Plan/Apply 없이 선언만 하면 컨트롤러가 알아서 관리
  • 셀프서비스 플랫폼: Composition으로 복잡한 인프라를 추상화하여 개발자에게 제공
  • GitOps 네이티브: ArgoCD/Flux와 자연스러운 통합
  • RBAC 통합: Kubernetes의 기존 권한 체계 그대로 활용
  • 드리프트 자동 복구: 누군가 콘솔에서 수동 변경해도 자동으로 원복

✅ 퀴즈: Crossplane 이해도 점검 (7문제)

Q1. Crossplane이 Terraform 대비 드리프트 감지에 강한 이유는?

Kubernetes 컨트롤러의 reconciliation loop가 지속적으로 실제 상태와 선언 상태를 비교하여 자동으로 복구합니다.

Q2. Managed Resource와 Composite Resource의 차이는?

Managed Resource는 실제 클라우드 리소스(S3, RDS 등)를 1:1로 매핑하고, Composite Resource는 여러 Managed Resource를 하나의 고수준 API로 묶은 것입니다.

Q3. Claim의 역할은 무엇인가요?

Claim은 네임스페이스 수준에서 개발자가 인프라를 요청하는 인터페이스입니다. Composite Resource를 직접 다루지 않고 추상화된 API를 사용합니다.

Q4. Crossplane의 상태(state)는 어디에 저장되나요?

Kubernetes의 etcd에 저장됩니다. Terraform처럼 별도의 상태 파일 관리가 필요 없습니다.

Q5. Composition의 mode: Pipeline은 어떤 장점이 있나요?

Go Templating, Python 등 다양한 Function을 체인으로 연결하여 복잡한 리소스 생성 로직을 구현할 수 있습니다.

Q6. Usage 리소스의 역할은?

리소스 간 의존성을 선언하여 사용 중인 리소스가 실수로 삭제되는 것을 방지합니다.

Q7. Crossplane을 ArgoCD와 연동할 때 특별한 설정이 필요한가요?

아닙니다. Crossplane 리소스는 일반 Kubernetes 매니페스트이므로 ArgoCD가 그대로 sync할 수 있습니다.