K8s Crossplane 인프라 자동화

Crossplane이란?

Crossplane은 Kubernetes 위에서 클라우드 인프라를 YAML로 관리하는 도구다. Terraform이 HCL 파일과 CLI로 인프라를 프로비저닝한다면, Crossplane은 Kubernetes CRD(Custom Resource Definition)로 AWS RDS, S3, GCP CloudSQL 등을 선언한다. kubectl로 인프라를 생성하고, Kubernetes의 Reconciliation Loop가 상태를 지속적으로 동기화한다.

핵심 차이는 이렇다: Terraform은 terraform apply를 실행하는 순간에만 동작하는 명령형 워크플로우다. 누군가 콘솔에서 설정을 바꾸면 드리프트가 발생한다. Crossplane은 Kubernetes처럼 지속적으로 desired state를 유지한다. 콘솔에서 변경해도 자동으로 원래 상태로 복구된다.

설치

# Crossplane 설치 (Helm)
helm repo add crossplane-stable https://charts.crossplane.io/stable
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

핵심 개념: 4계층 아키텍처

계층 역할 담당자
Provider 클라우드 API 연결 (AWS, GCP, Azure) 플랫폼 팀
Managed Resource 개별 클라우드 리소스 (RDS, S3, VPC) 플랫폼 팀
Composition 리소스 조합 템플릿 플랫폼 팀
Claim (XR) 추상화된 인프라 요청 개발팀

Provider 설정: AWS 연결

# AWS Provider 설치
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-aws-s3
spec:
  package: xpkg.upbound.io/upbound/provider-aws-s3:v1.7.0

---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-aws-rds
spec:
  package: xpkg.upbound.io/upbound/provider-aws-rds:v1.7.0

---
# AWS 자격증명 Secret
apiVersion: v1
kind: Secret
metadata:
  name: aws-credentials
  namespace: crossplane-system
type: Opaque
stringData:
  credentials: |
    [default]
    aws_access_key_id = AKIAIOSFODNN7EXAMPLE
    aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

---
# ProviderConfig: 자격증명 연결
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
  name: default
spec:
  credentials:
    source: Secret
    secretRef:
      namespace: crossplane-system
      name: aws-credentials
      key: credentials

Managed Resource: 개별 리소스 생성

# S3 버킷 생성 — kubectl apply만으로 AWS에 버킷 생성
apiVersion: s3.aws.upbound.io/v1beta2
kind: Bucket
metadata:
  name: my-app-uploads
spec:
  forProvider:
    region: ap-northeast-2
    tags:
      Environment: production
      Team: backend
  providerConfigRef:
    name: default

---
# RDS 인스턴스 생성
apiVersion: rds.aws.upbound.io/v1beta2
kind: Instance
metadata:
  name: my-app-db
spec:
  forProvider:
    region: ap-northeast-2
    engine: mysql
    engineVersion: "8.0"
    instanceClass: db.t3.medium
    allocatedStorage: 50
    dbName: myapp
    masterUsername: admin
    masterPasswordSecretRef:
      name: db-password
      namespace: crossplane-system
      key: password
    publiclyAccessible: false
    vpcSecurityGroupIdRefs:
      - name: db-security-group
    dbSubnetGroupNameRef:
      name: db-subnet-group
    backupRetentionPeriod: 7
    multiAz: true
    storageEncrypted: true
    tags:
      Environment: production
  providerConfigRef:
    name: default
  writeConnectionSecretToRef:
    name: db-connection
    namespace: default

writeConnectionSecretToRef가 핵심이다. RDS가 생성되면 엔드포인트, 포트, 사용자명, 비밀번호를 Kubernetes Secret에 자동 저장한다. 애플리케이션 Pod에서 이 Secret을 마운트하면 연결 정보를 수동으로 관리할 필요가 없다.

Composition: 리소스 조합 템플릿

실전에서는 DB 하나를 만들 때 서브넷 그룹, 보안 그룹, 파라미터 그룹, 모니터링까지 함께 생성해야 한다. Composition으로 이 모든 것을 하나의 템플릿으로 묶는다.

# CompositeResourceDefinition (XRD) — 추상 인터페이스 정의
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]
                      description: "DB 인스턴스 크기"
                    engine:
                      type: string
                      enum: [mysql, postgresql]
                      default: postgresql
                    region:
                      type: string
                      default: ap-northeast-2
                  required: [size]
              required: [parameters]
# Composition — 실제 리소스 조합 정의
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: database-aws
  labels:
    provider: aws
spec:
  compositeTypeRef:
    apiVersion: platform.example.com/v1alpha1
    kind: XDatabase
  resources:
    # 1. 서브넷 그룹
    - name: subnet-group
      base:
        apiVersion: rds.aws.upbound.io/v1beta1
        kind: SubnetGroup
        spec:
          forProvider:
            region: ap-northeast-2
            subnetIds:
              - subnet-abc123
              - subnet-def456
            description: "Managed by Crossplane"

    # 2. 보안 그룹
    - name: security-group
      base:
        apiVersion: ec2.aws.upbound.io/v1beta1
        kind: SecurityGroup
        spec:
          forProvider:
            region: ap-northeast-2
            vpcId: vpc-xyz789
            description: "DB Security Group"

    # 3. 보안 그룹 규칙
    - name: sg-rule-ingress
      base:
        apiVersion: ec2.aws.upbound.io/v1beta1
        kind: SecurityGroupIngressRule
        spec:
          forProvider:
            region: ap-northeast-2
            cidrIpv4: "10.0.0.0/16"
            fromPort: 5432
            toPort: 5432
            ipProtocol: tcp
            securityGroupIdSelector:
              matchControllerRef: true

    # 4. RDS 인스턴스
    - name: rds-instance
      base:
        apiVersion: rds.aws.upbound.io/v1beta2
        kind: Instance
        spec:
          forProvider:
            region: ap-northeast-2
            engine: postgresql
            engineVersion: "16"
            publiclyAccessible: false
            backupRetentionPeriod: 7
            storageEncrypted: true
            dbSubnetGroupNameSelector:
              matchControllerRef: true
            vpcSecurityGroupIdSelector:
              matchControllerRef: true
      patches:
        # size 파라미터에 따라 인스턴스 클래스 변경
        - 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에 따라 스토리지 크기 변경
        - 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
      connectionDetails:
        - type: FromConnectionSecretKey
          fromConnectionSecretKey: endpoint
          name: host
        - type: FromConnectionSecretKey
          fromConnectionSecretKey: port
          name: port
        - type: FromConnectionSecretKey
          fromConnectionSecretKey: username
          name: username
        - type: FromConnectionSecretKey
          fromConnectionSecretKey: password
          name: password

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

개발자는 Composition의 내부 구현을 몰라도 된다. Claim만 제출하면 플랫폼 팀이 설계한 모범 사례에 맞게 인프라가 생성된다.

# 개발자가 작성하는 YAML — 이것만으로 RDS + 서브넷 + SG 전부 생성
apiVersion: platform.example.com/v1alpha1
kind: Database
metadata:
  name: order-db
  namespace: order-service
spec:
  parameters:
    size: medium      # small/medium/large 중 선택
    engine: postgresql
  compositionSelector:
    matchLabels:
      provider: aws
  writeConnectionSecretToRef:
    name: order-db-connection

---
# 애플리케이션에서 자동 생성된 Secret 사용
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  template:
    spec:
      containers:
        - name: app
          env:
            - name: DB_HOST
              valueFrom:
                secretKeyRef:
                  name: order-db-connection
                  key: host
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: order-db-connection
                  key: password

Terraform vs Crossplane 비교

항목 Terraform Crossplane
상태 관리 tfstate 파일 (S3 등) Kubernetes etcd
드리프트 감지 plan 실행 시만 지속적 자동 감지·복구
셀프 서비스 PR 기반 워크플로우 Claim YAML 직접 적용
학습 곡선 HCL 학습 필요 K8s YAML (이미 친숙)
권한 제어 별도 정책 도구 필요 K8s RBAC 그대로 사용
적합한 환경 다양한 인프라, 초기 구축 K8s 중심, 개발자 셀프서비스

GitOps 연동: ArgoCD + Crossplane

# Git 저장소 구조
# infrastructure/
# ├── crossplane/
# │   ├── providers/
# │   │   └── aws-providers.yaml
# │   ├── compositions/
# │   │   ├── database.yaml
# │   │   └── cache.yaml
# │   └── claims/
# │       ├── production/
# │       │   ├── order-db.yaml
# │       │   └── user-db.yaml
# │       └── staging/
# │           └── order-db.yaml

# ArgoCD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: infrastructure-claims
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/org/infrastructure
    path: crossplane/claims/production
    targetRevision: main
  destination:
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      prune: false    # 인프라 리소스는 자동 삭제 금지
      selfHeal: true

상태 확인 및 트러블슈팅

# Claim 상태 확인
kubectl get database order-db -o yaml
# status.conditions에서 Ready, Synced 확인

# 하위 Managed Resource 상태
kubectl get managed
# NAME                                    READY   SYNCED   AGE
# instance.rds.aws/order-db-xxxxx         True    True     5m
# subnetgroup.rds.aws/order-db-xxxxx      True    True     5m
# securitygroup.ec2.aws/order-db-xxxxx    True    True     5m

# 이벤트 확인 (에러 디버깅)
kubectl describe database order-db
# Events:
#   Type    Reason                   Message
#   Normal  CompositionRevisionReady Composition revision is ready
#   Normal  SelectComposition        Successfully selected composition

# Provider 로그 확인
kubectl logs -n crossplane-system 
  -l pkg.crossplane.io/revision=provider-aws-rds 
  --tail=50

Usage 정책: 삭제 보호

# 실수로 DB Claim을 삭제하면 실제 RDS도 삭제됨
# Usage로 보호
apiVersion: apiextensions.crossplane.io/v1alpha1
kind: Usage
metadata:
  name: protect-order-db
spec:
  of:
    apiVersion: platform.example.com/v1alpha1
    kind: XDatabase
    resourceRef:
      name: order-db
  reason: "프로덕션 데이터베이스 — 삭제 전 승인 필요"

Crossplane은 “Kubernetes가 곧 컨트롤 플레인이다”라는 철학의 결정체다. 애플리케이션과 인프라를 동일한 GitOps 워크플로우로 관리하고, 개발자에게 Claim 기반 셀프 서비스를 제공할 수 있다. Kubernetes 중심 조직이라면, Terraform에서 Crossplane으로의 전환을 검토할 시점이다.

관련 글: Terraform State 관리 심화 | K8s ArgoCD GitOps 배포

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux