Technology

Crossplane Composition Developer Experience DX

crossplane composition developer experience dx
Crossplane Composition Developer Experience DX | SiamCafe Blog
2025-11-03· อ. บอม — SiamCafe.net· 1,732 คำ

Crossplane Composition Developer Experience DX คืออะไร

Crossplane เป็น open source Kubernetes add-on ที่ช่วยจัดการ cloud infrastructure ผ่าน Kubernetes API ด้วย Compositions developers สร้าง self-service platform สำหรับ provision resources ข้าม cloud providers Developer Experience (DX) คือการออกแบบ platform ให้ developers ใช้งานง่าย ลด cognitive load และเพิ่ม productivity การรวมสองแนวคิดนี้ช่วยสร้าง Internal Developer Platform (IDP) ที่ abstract ความซับซ้อนของ infrastructure ให้ developers focus ที่ application code โดยขอ resources ผ่าน simple YAML claims

Crossplane Composition Basics

# composition_basics.py — Crossplane Composition fundamentals
import json

class CompositionBasics:
    CONCEPTS = {
        "composite_resource_def": {
            "name": "CompositeResourceDefinition (XRD)",
            "description": "กำหนด schema ของ custom resource ที่ developers จะใช้",
            "analogy": "เหมือน class definition — กำหนดว่า resource มี fields อะไรบ้าง",
        },
        "composition": {
            "name": "Composition",
            "description": "กำหนดว่า XRD จะสร้าง cloud resources อะไรบ้าง (implementation)",
            "analogy": "เหมือน class implementation — map fields ไปเป็น actual resources",
        },
        "claim": {
            "name": "Claim (XRC)",
            "description": "สิ่งที่ developers ใช้ขอ resources — simple YAML",
            "analogy": "เหมือน new ClassName() — สร้าง instance จาก definition",
        },
        "provider": {
            "name": "Provider",
            "description": "Plugin สำหรับ cloud provider (AWS, GCP, Azure)",
            "examples": "provider-aws, provider-gcp, provider-azure, provider-helm",
        },
    }

    XRD_EXAMPLE = """
# xrd-database.yaml — CompositeResourceDefinition
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:
                size:
                  type: string
                  enum: [small, medium, large]
                  description: "Database size"
                engine:
                  type: string
                  enum: [postgres, mysql]
                  default: postgres
              required: [size]
"""

    CLAIM_EXAMPLE = """
# claim-database.yaml — What developers write (simple!)
apiVersion: platform.example.com/v1alpha1
kind: Database
metadata:
  name: my-app-db
  namespace: team-alpha
spec:
  size: medium
  engine: postgres
"""

    def show_concepts(self):
        print("=== Crossplane Concepts ===\n")
        for key, concept in self.CONCEPTS.items():
            print(f"[{concept['name']}]")
            print(f"  {concept['description']}")
            print()

    def show_xrd(self):
        print("=== XRD Example ===")
        print(self.XRD_EXAMPLE[:500])

    def show_claim(self):
        print(f"\n=== Developer Claim (Simple!) ===")
        print(self.CLAIM_EXAMPLE)

basics = CompositionBasics()
basics.show_concepts()
basics.show_claim()

Developer Experience Design

# dx_design.py — Developer Experience design principles
import json

class DXDesign:
    PRINCIPLES = {
        "simplicity": {
            "name": "Simplicity (ง่าย)",
            "description": "Developer ไม่ต้องรู้ implementation details — แค่ระบุ size, engine, region",
            "bad": "Developer ต้องเขียน 200 lines YAML สำหรับ RDS + VPC + Security Group",
            "good": "Developer เขียน 10 lines YAML claim → platform สร้างทุกอย่างให้",
        },
        "guardrails": {
            "name": "Guardrails (ราวกั้น)",
            "description": "จำกัด options ที่ developers เลือกได้ ป้องกันความผิดพลาด",
            "bad": "Developer เลือก instance type อะไรก็ได้ → เลือก x2idn.24xlarge ($10/hr)",
            "good": "enum: [small, medium, large] → platform map ไปเป็น instance type ที่เหมาะสม",
        },
        "self_service": {
            "name": "Self-Service (บริการตัวเอง)",
            "description": "Developer ขอ resources ได้เองทันที ไม่ต้องเปิด ticket รอ ops",
            "bad": "เปิด JIRA ticket → รอ ops 2-3 วัน → ได้ database",
            "good": "kubectl apply -f claim.yaml → ได้ database ใน 5-10 นาที",
        },
        "defaults": {
            "name": "Sensible Defaults (ค่า default ดี)",
            "description": "ทุก field มี default ที่เหมาะสม — developer ระบุแค่สิ่งที่จำเป็น",
            "bad": "ต้องระบุ 30+ fields ทุกตัว",
            "good": "ระบุแค่ name + size → อย่างอื่น platform จัดการให้",
        },
        "discoverability": {
            "name": "Discoverability (ค้นหาง่าย)",
            "description": "Developer รู้ว่ามี resources อะไรใช้ได้ และใช้อย่างไร",
            "tools": "kubectl explain, API docs, Backstage catalog, CLI tools",
        },
    }

    def show_principles(self):
        print("=== DX Design Principles ===\n")
        for key, p in self.PRINCIPLES.items():
            print(f"[{p['name']}]")
            print(f"  {p['description']}")
            if 'good' in p:
                print(f"  Good: {p['good']}")
            print()

    def dx_comparison(self):
        print("=== Before vs After Crossplane ===")
        print(f"\n  Before (Manual):")
        print(f"    1. Developer เปิด JIRA ticket")
        print(f"    2. Ops review + approve (1-2 days)")
        print(f"    3. Ops สร้าง resources manually/Terraform (1 day)")
        print(f"    4. Ops ส่ง credentials กลับ (1 day)")
        print(f"    Total: 2-5 days")
        print(f"\n  After (Crossplane Self-Service):")
        print(f"    1. Developer: kubectl apply -f claim.yaml")
        print(f"    2. Crossplane provisions all resources")
        print(f"    3. Credentials auto-injected as Secret")
        print(f"    Total: 5-15 minutes")

dx = DXDesign()
dx.show_principles()
dx.dx_comparison()

Composition Implementation

# composition_impl.py — Composition implementation
import json

class CompositionImpl:
    COMPOSITION = """
# composition-database.yaml — Full 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. RDS Instance
    - name: rds
      base:
        apiVersion: rds.aws.upbound.io/v1beta1
        kind: Instance
        spec:
          forProvider:
            engine: postgres
            engineVersion: "15"
            allocatedStorage: 20
            instanceClass: db.t3.medium
            publiclyAccessible: false
            skipFinalSnapshot: true
      patches:
        # Map 'size' to instance class
        - type: FromCompositeFieldPath
          fromFieldPath: spec.size
          toFieldPath: spec.forProvider.instanceClass
          transforms:
            - type: map
              map:
                small: db.t3.micro
                medium: db.t3.medium
                large: db.r6g.large
        # Map engine
        - type: FromCompositeFieldPath
          fromFieldPath: spec.engine
          toFieldPath: spec.forProvider.engine
    
    # 2. Security Group
    - name: sg
      base:
        apiVersion: ec2.aws.upbound.io/v1beta1
        kind: SecurityGroup
        spec:
          forProvider:
            description: "Database security group"
    
    # 3. Connection Secret
    - name: connection-secret
      connectionDetails:
        - name: host
          fromFieldPath: status.atProvider.endpoint
        - name: port
          value: "5432"
        - name: username
          fromFieldPath: spec.forProvider.username
"""

    SIZE_MAPPING = {
        "small": {"instance": "db.t3.micro", "storage": "20GB", "cost": "~$15/month"},
        "medium": {"instance": "db.t3.medium", "storage": "50GB", "cost": "~$50/month"},
        "large": {"instance": "db.r6g.large", "storage": "100GB", "cost": "~$150/month"},
    }

    def show_composition(self):
        print("=== Composition YAML ===")
        print(self.COMPOSITION[:600])

    def show_mapping(self):
        print(f"\n=== Size Mapping ===")
        for size, details in self.SIZE_MAPPING.items():
            print(f"  [{size}] → {details['instance']} | {details['storage']} | {details['cost']}")

impl = CompositionImpl()
impl.show_composition()
impl.show_mapping()

Platform Engineering Tools

# platform_tools.py — Platform engineering tooling
import json
import random

class PlatformTools:
    TOOLS = {
        "backstage": {
            "name": "Backstage (Spotify)",
            "description": "Developer portal — catalog, templates, docs, plugins",
            "integration": "Backstage templates สร้าง Crossplane claims อัตโนมัติ",
        },
        "port": {
            "name": "Port (getport.io)",
            "description": "Internal developer portal — self-service UI",
            "integration": "Port actions trigger Crossplane claim creation",
        },
        "argocd": {
            "name": "Argo CD",
            "description": "GitOps deployment — sync claims จาก Git repo",
            "integration": "Developer push claim YAML → Argo CD apply → Crossplane provisions",
        },
        "crossplane_cli": {
            "name": "Crossplane CLI (crank)",
            "description": "CLI สำหรับ build, push, install Crossplane packages",
            "integration": "crank build, crank push สำหรับ composition packages",
        },
    }

    AUTOMATION = """
# platform_automation.py — Platform automation script
import subprocess
import yaml
import json

class PlatformAutomation:
    def __init__(self):
        self.namespace = "platform-system"
    
    def create_claim(self, name, kind, spec):
        claim = {
            "apiVersion": "platform.example.com/v1alpha1",
            "kind": kind,
            "metadata": {"name": name, "namespace": "default"},
            "spec": spec,
        }
        yaml_str = yaml.dump(claim)
        result = subprocess.run(
            ["kubectl", "apply", "-f", "-"],
            input=yaml_str, capture_output=True, text=True,
        )
        return result.returncode == 0
    
    def get_status(self, name, kind):
        result = subprocess.run(
            ["kubectl", "get", kind.lower(), name, "-o", "json"],
            capture_output=True, text=True,
        )
        if result.returncode == 0:
            data = json.loads(result.stdout)
            conditions = data.get("status", {}).get("conditions", [])
            ready = any(c["type"] == "Ready" and c["status"] == "True" for c in conditions)
            return {"name": name, "ready": ready, "conditions": conditions}
        return None
    
    def list_claims(self):
        result = subprocess.run(
            ["kubectl", "get", "databases, caches, buckets", "-A", "-o", "json"],
            capture_output=True, text=True,
        )
        return json.loads(result.stdout) if result.returncode == 0 else None

# Usage
platform = PlatformAutomation()
platform.create_claim("my-db", "Database", {"size": "medium", "engine": "postgres"})
"""

    def show_tools(self):
        print("=== Platform Engineering Tools ===\n")
        for key, tool in self.TOOLS.items():
            print(f"[{tool['name']}]")
            print(f"  {tool['description']}")
            print(f"  Integration: {tool['integration']}")
            print()

    def show_automation(self):
        print("=== Automation Script ===")
        print(self.AUTOMATION[:500])

    def platform_dashboard(self):
        print(f"\n=== Platform Dashboard ===")
        resources = [
            {"name": "app-db", "kind": "Database", "team": "alpha", "status": "Ready", "age": f"{random.randint(1, 30)}d"},
            {"name": "cache-1", "kind": "Cache", "team": "beta", "status": "Ready", "age": f"{random.randint(1, 20)}d"},
            {"name": "logs-bucket", "kind": "Bucket", "team": "platform", "status": "Ready", "age": f"{random.randint(5, 60)}d"},
            {"name": "new-db", "kind": "Database", "team": "gamma", "status": "Provisioning", "age": "2m"},
        ]
        for r in resources:
            icon = "OK" if r["status"] == "Ready" else "..."
            print(f"  [{icon:>3}] {r['name']:<15} {r['kind']:<10} Team: {r['team']:<10} Age: {r['age']}")

tools = PlatformTools()
tools.show_tools()
tools.platform_dashboard()

Testing & Validation

# testing.py — Testing Crossplane compositions
import json

class CompositionTesting:
    STRATEGIES = {
        "unit": {
            "name": "Unit Tests (crossplane-contrib/xrender)",
            "description": "ทดสอบ Composition rendering — input claim → expected resources",
            "tool": "crossplane beta render, kuttl",
        },
        "integration": {
            "name": "Integration Tests",
            "description": "ทดสอบว่า Composition สร้าง actual cloud resources ได้จริง",
            "tool": "Kind cluster + Crossplane + provider",
        },
        "e2e": {
            "name": "E2E Tests",
            "description": "ทดสอบ full flow: claim → resources ready → app connects",
            "tool": "Chainsaw, kuttl, custom scripts",
        },
    }

    RENDER_TEST = """
# test_composition.sh — Test Composition rendering
# Install: crossplane CLI
# crossplane beta render claim.yaml composition.yaml functions.yaml

# Example test with kuttl
# tests/database/00-claim.yaml
apiVersion: platform.example.com/v1alpha1
kind: Database
metadata:
  name: test-db
spec:
  size: small
  engine: postgres

# tests/database/00-assert.yaml
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
metadata:
  labels:
    crossplane.io/claim-name: test-db
spec:
  forProvider:
    instanceClass: db.t3.micro
    engine: postgres
"""

    def show_strategies(self):
        print("=== Testing Strategies ===\n")
        for key, strat in self.STRATEGIES.items():
            print(f"[{strat['name']}]")
            print(f"  {strat['description']}")
            print(f"  Tool: {strat['tool']}")
            print()

    def show_render_test(self):
        print("=== Render Test ===")
        print(self.RENDER_TEST[:400])

test = CompositionTesting()
test.show_strategies()
test.show_render_test()

FAQ - คำถามที่พบบ่อย

Q: Crossplane กับ Terraform อันไหนดี?

A: Crossplane: Kubernetes-native, continuous reconciliation, self-service platform, GitOps Terraform: mature, HCL language, state management, broad provider support ใช้ Crossplane: Kubernetes-first org, self-service platform, continuous drift detection ใช้ Terraform: standalone IaC, non-K8s environments, team คุ้นเคย HCL ใช้ทั้งคู่ได้: Terraform สำหรับ bootstrap + Crossplane สำหรับ day-2 operations

Q: Developer Experience สำคัญอย่างไร?

A: DX ดี = developer productivity สูง = ship faster = competitive advantage DX แย่ = developer frustration = slow delivery = talent retention issues วัด DX: Time to first resource, number of support tickets, developer satisfaction survey Crossplane ช่วย DX: abstract complexity, self-service, guardrails, sensible defaults

Q: Composition ยากไหม?

A: เขียน Composition: ยากพอสมควร (ต้องรู้ Crossplane + cloud resources) ใช้ Claim: ง่ายมาก (แค่ YAML 10 บรรทัด) Platform team เขียน Composition ครั้งเดียว → developers ใช้ Claim ซ้ำได้ตลอด เริ่มต้น: ใช้ Upbound Marketplace มี ready-made compositions

Q: เริ่มใช้ Crossplane อย่างไร?

A: 1. ติดตั้ง Crossplane บน Kubernetes (helm install) 2. ติดตั้ง Provider (e.g., provider-aws) 3. สร้าง XRD + Composition สำหรับ use case แรก (เช่น Database) 4. ให้ developers ทดลองใช้ Claim 5. เพิ่ม Compositions ตาม feedback Docs: docs.crossplane.io | Upbound: upbound.io

📖 บทความที่เกี่ยวข้อง

Crossplane Composition Low Code No Codeอ่านบทความ → Crossplane Composition Freelance IT Careerอ่านบทความ → Crossplane Composition Cache Strategy Redisอ่านบทความ → Go Fiber Developer Experience DXอ่านบทความ →

📚 ดูบทความทั้งหมด →