Technology

Crossplane Composition Clean Architecture ออกแบบ Infrastructure Platform ทด

crossplane composition clean architecture
Crossplane Composition Clean Architecture | SiamCafe Blog
2025-11-22· อ. บอม — SiamCafe.net· 1,313 คำ

Clean Architecture ????????? Crossplane ?????????????????????

Clean Architecture ???????????????????????????????????????????????? software ????????? Robert C. Martin (Uncle Bob) ?????????????????? concerns ????????????????????? layers ?????????????????? Inner layers ?????????????????????????????? outer layers ??????????????? code maintainable, testable ????????? flexible ????????????????????????????????????????????? Crossplane Compositions ?????????????????????????????????????????????????????????????????? infrastructure-as-code ?????????????????? abstraction layers ??????????????????

Crossplane Composition ???????????????????????????????????? Clean Architecture ???????????????????????? XRD Layer (API Contract) ??????????????? interface ????????? application teams ????????? ??????????????????????????? cloud provider, Composition Layer (Business Logic) ??????????????????????????? provision resources ????????? provider, Managed Resource Layer (Infrastructure) resources ?????????????????? cloud provider

???????????????????????? Clean Architecture ?????????????????? Crossplane Portability ????????????????????? cloud provider ???????????????????????? application teams, Testability ?????????????????????????????? layer ???????????????????????????, Maintainability ????????????????????? implementation ???????????????????????? API, Reusability ????????? XRD ????????????????????? compositions ?????????

??????????????????????????? Composition ????????? Clean Architecture

???????????????????????????????????? repository

# === Clean Architecture Repository Structure ===

cat > repo-structure.txt << 'EOF'
crossplane-platform/
  # Layer 1: API Definitions (XRDs)
  apis/
    database/
      v1alpha1/
        definition.yaml      # XRD: XDatabase
        README.md
    cache/
      v1alpha1/
        definition.yaml      # XRD: XCache
    messaging/
      v1alpha1/
        definition.yaml      # XRD: XMessaging
    network/
      v1alpha1/
        definition.yaml      # XRD: XNetwork

  # Layer 2: Compositions (Implementation per provider)
  compositions/
    aws/
      database/
        composition.yaml     # AWS RDS implementation
        patches.yaml
      cache/
        composition.yaml     # AWS ElastiCache
      messaging/
        composition.yaml     # AWS SNS + SQS
    gcp/
      database/
        composition.yaml     # GCP Cloud SQL
      cache/
        composition.yaml     # GCP Memorystore
      messaging/
        composition.yaml     # GCP Pub/Sub
    azure/
      database/
        composition.yaml     # Azure Database

  # Layer 3: Configuration
  config/
    providers/
      aws.yaml
      gcp.yaml
    provider-configs/
      aws-default.yaml
      gcp-default.yaml

  # Layer 4: Claims (Consumer examples)
  examples/
    dev/
      database.yaml
      cache.yaml
    staging/
      database.yaml
    production/
      database.yaml

  # Testing
  tests/
    unit/
      test_xrds.py
      test_compositions.py
    integration/
      test_aws_database.py
    e2e/
      test_full_stack.py

  # CI/CD
  .github/
    workflows/
      validate.yml
      deploy.yml
EOF

# Create directory structure
mkdir -p apis/{database,cache,messaging,network}/v1alpha1
mkdir -p compositions/{aws,gcp,azure}/{database,cache,messaging}
mkdir -p config/{providers,provider-configs}
mkdir -p examples/{dev,staging,production}
mkdir -p tests/{unit,integration,e2e}

echo "Clean Architecture structure created"

??????????????? Layered Compositions

Implementation ????????? Clean Architecture layers

# === Layer 1: XRD (API Contract) ===

# apis/database/v1alpha1/definition.yaml
cat > apis/database/v1alpha1/definition.yaml << 'EOF'
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: xdatabases.platform.example.io
spec:
  group: platform.example.io
  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:
                # Clean API: no cloud-specific params
                engine:
                  type: string
                  enum: ["postgres", "mysql", "mariadb"]
                version:
                  type: string
                size:
                  type: string
                  enum: ["small", "medium", "large", "xlarge"]
                  description: "T-shirt size abstraction"
                highAvailability:
                  type: boolean
                  default: false
                backup:
                  type: object
                  properties:
                    enabled:
                      type: boolean
                      default: true
                    retentionDays:
                      type: integer
                      default: 7
                environment:
                  type: string
                  enum: ["dev", "staging", "production"]
              required:
                - engine
                - size
                - environment
            status:
              type: object
              properties:
                endpoint:
                  type: string
                port:
                  type: integer
                status:
                  type: string
EOF

# === Layer 2: Composition (AWS Implementation) ===

# compositions/aws/database/composition.yaml
cat > compositions/aws/database/composition.yaml << 'EOF'
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: xdatabase-aws
  labels:
    provider: aws
    crossplane.io/xrd: xdatabases.platform.example.io
spec:
  compositeTypeRef:
    apiVersion: platform.example.io/v1alpha1
    kind: XDatabase
  
  patchSets:
    - name: common-tags
      patches:
        - type: FromCompositeFieldPath
          fromFieldPath: spec.environment
          toFieldPath: spec.forProvider.tags.environment
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: spec.forProvider.tags.managed-by
          transforms:
            - type: string
              string:
                fmt: "crossplane-%s"
    
    - name: size-mapping
      patches:
        - 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
                xlarge: db.r6g.xlarge
  
  resources:
    - name: rds-instance
      base:
        apiVersion: rds.aws.upbound.io/v1beta1
        kind: Instance
        spec:
          forProvider:
            region: ap-southeast-1
            allocatedStorage: 20
            autoMinorVersionUpgrade: true
            publiclyAccessible: false
            skipFinalSnapshot: false
      patches:
        - type: PatchSet
          patchSetName: common-tags
        - type: PatchSet
          patchSetName: size-mapping
        - fromFieldPath: spec.engine
          toFieldPath: spec.forProvider.engine
        - fromFieldPath: spec.version
          toFieldPath: spec.forProvider.engineVersion
        - fromFieldPath: spec.highAvailability
          toFieldPath: spec.forProvider.multiAz
        - fromFieldPath: spec.backup.retentionDays
          toFieldPath: spec.forProvider.backupRetentionPeriod
        - type: ToCompositeFieldPath
          fromFieldPath: status.atProvider.endpoint
          toFieldPath: status.endpoint
        - type: ToCompositeFieldPath
          fromFieldPath: status.atProvider.port
          toFieldPath: status.port
EOF

echo "Layered compositions created"

Testing ????????? Validation

??????????????? Crossplane Compositions

#!/usr/bin/env python3
# tests/unit/test_compositions.py ??? Composition Tests
import json
import logging
import subprocess
from typing import Dict, List

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("test")

class CompositionTester:
    """Test Crossplane Compositions"""
    
    def __init__(self):
        self.results = []
    
    def validate_xrd(self, xrd_path):
        """Validate XRD schema"""
        checks = {
            "has_claim_names": False,
            "has_versions": False,
            "has_required_fields": False,
            "has_status_fields": False,
        }
        
        # Simulate reading YAML
        # In production: use pyyaml to parse
        checks["has_claim_names"] = True  # Check claimNames exists
        checks["has_versions"] = True     # Check versions array
        checks["has_required_fields"] = True  # Check required spec fields
        checks["has_status_fields"] = True    # Check status subresource
        
        passed = all(checks.values())
        self.results.append({
            "test": f"validate_xrd:{xrd_path}",
            "passed": passed,
            "checks": checks,
        })
        return passed
    
    def validate_composition(self, comp_path):
        """Validate Composition patches and resources"""
        checks = {
            "has_composite_type_ref": True,
            "has_resources": True,
            "patches_valid": True,
            "no_hardcoded_values": True,
            "has_status_patches": True,
        }
        
        passed = all(checks.values())
        self.results.append({
            "test": f"validate_composition:{comp_path}",
            "passed": passed,
            "checks": checks,
        })
        return passed
    
    def test_size_mapping(self):
        """Test T-shirt size to instance class mapping"""
        mappings = {
            "small": "db.t3.micro",
            "medium": "db.t3.medium",
            "large": "db.r6g.large",
            "xlarge": "db.r6g.xlarge",
        }
        
        for size, expected_class in mappings.items():
            # Simulate patch transform
            result = mappings.get(size)
            passed = result == expected_class
            self.results.append({
                "test": f"size_mapping:{size}",
                "passed": passed,
                "expected": expected_class,
                "got": result,
            })
        
        return all(r["passed"] for r in self.results if "size_mapping" in r["test"])
    
    def test_environment_isolation(self):
        """Test that environments are properly isolated"""
        envs = ["dev", "staging", "production"]
        checks = []
        
        for env in envs:
            check = {
                "env": env,
                "has_network_isolation": env in ["staging", "production"],
                "has_encryption": env == "production",
                "has_backup": env in ["staging", "production"],
                "ha_enabled": env == "production",
            }
            checks.append(check)
        
        self.results.append({
            "test": "environment_isolation",
            "passed": True,
            "environments": checks,
        })
        return True
    
    def summary(self):
        total = len(self.results)
        passed = sum(1 for r in self.results if r["passed"])
        failed = total - passed
        return {
            "total": total,
            "passed": passed,
            "failed": failed,
            "pass_rate": round(passed / total * 100, 1) if total > 0 else 0,
        }

tester = CompositionTester()
tester.validate_xrd("apis/database/v1alpha1/definition.yaml")
tester.validate_composition("compositions/aws/database/composition.yaml")
tester.test_size_mapping()
tester.test_environment_isolation()

summary = tester.summary()
print(f"Test Results: {summary['passed']}/{summary['total']} passed ({summary['pass_rate']}%)")
for r in tester.results:
    status = "PASS" if r["passed"] else "FAIL"
    print(f"  [{status}] {r['test']}")

GitOps ????????? Environment Management

?????????????????? environments ???????????? GitOps

# === GitOps Environment Management ===

# 1. Kustomize Overlays per Environment
cat > kustomization-base.yaml << 'EOF'
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../apis/database/v1alpha1/definition.yaml
  - ../apis/cache/v1alpha1/definition.yaml
  - ../apis/messaging/v1alpha1/definition.yaml
EOF

cat > kustomization-aws.yaml << 'EOF'
# overlays/aws/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
  - ../../compositions/aws/database/composition.yaml
  - ../../compositions/aws/cache/composition.yaml
  - ../../compositions/aws/messaging/composition.yaml
  - ../../config/providers/aws.yaml
  - ../../config/provider-configs/aws-default.yaml
EOF

# 2. ArgoCD ApplicationSet (multi-cluster)
cat > appset-crossplane.yaml << 'EOF'
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: crossplane-platform
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - cluster: management
            url: https://mgmt.k8s.example.com
            provider: aws
            env: production
          - cluster: dev
            url: https://dev.k8s.example.com
            provider: aws
            env: dev
  template:
    metadata:
      name: "crossplane-{{cluster}}"
    spec:
      project: infrastructure
      source:
        repoURL: https://github.com/org/crossplane-platform.git
        targetRevision: main
        path: "overlays/{{provider}}"
      destination:
        server: "{{url}}"
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
EOF

# 3. GitHub Actions CI/CD
cat > .github/workflows/validate.yml << 'EOF'
name: Validate Crossplane

on:
  pull_request:
    paths: ['apis/**', 'compositions/**']

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Validate YAML
        run: |
          pip install pyyaml
          python -c "
          import yaml, glob, sys
          errors = 0
          for f in glob.glob('**/*.yaml', recursive=True):
              try:
                  with open(f) as fh:
                      list(yaml.safe_load_all(fh))
              except Exception as e:
                  print(f'ERROR: {f}: {e}')
                  errors += 1
          print(f'Validated {len(glob.glob(\"**/*.yaml\", recursive=True))} files, {errors} errors')
          sys.exit(1 if errors > 0 else 0)
          "
      
      - name: Run Tests
        run: |
          pip install pytest pyyaml
          pytest tests/ -v
      
      - name: Kustomize Build Test
        run: |
          kustomize build overlays/aws/ > /dev/null
          echo "AWS overlay: OK"
          kustomize build overlays/gcp/ > /dev/null
          echo "GCP overlay: OK"
EOF

echo "GitOps configured"

Best Practices ????????? Anti-Patterns

????????????????????????????????????????????????????????????????????????????????????????????????????????????

#!/usr/bin/env python3
# best_practices.py ??? Crossplane Clean Architecture Best Practices
import json
import logging
from typing import Dict, List

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("practices")

class BestPractices:
    def __init__(self):
        pass
    
    def practices(self):
        return {
            "do": {
                "abstract_cloud_specifics": {
                    "description": "XRD ???????????????????????? cloud-specific parameters",
                    "example": "????????? size: small/medium/large ????????? instanceClass: db.t3.micro",
                    "benefit": "????????????????????? cloud provider ???????????????????????? consumers",
                },
                "use_patchsets": {
                    "description": "????????? PatchSets ?????????????????? patches ???????????????????????????",
                    "benefit": "?????? duplication, maintain ????????????????????????",
                },
                "version_xrds": {
                    "description": "????????? versioning ?????????????????? XRDs (v1alpha1, v1beta1, v1)",
                    "benefit": "Breaking changes ???????????????????????? existing consumers",
                },
                "test_compositions": {
                    "description": "???????????????????????? composition ???????????? deploy",
                    "tools": ["crossplane beta render", "kuttl", "pytest"],
                },
                "use_composition_revisions": {
                    "description": "Enable composition revisions ?????????????????? safe updates",
                    "benefit": "Rollback ?????????????????? composition update ?????????????????????",
                },
                "separate_concerns": {
                    "description": "????????? XRD, Composition, Claims ?????? directories ?????????????????????",
                    "benefit": "Clear ownership, easy to navigate",
                },
            },
            "dont": {
                "expose_cloud_params_in_xrd": {
                    "description": "??????????????????????????? instanceClass, region, ARN ?????? XRD spec",
                    "reason": "????????? consumers ????????? cloud provider",
                    "fix": "????????? transforms (map) ?????? composition ?????????",
                },
                "monolithic_composition": {
                    "description": "????????????????????????????????????????????????????????? composition ???????????????",
                    "reason": "????????? maintain, ????????? test",
                    "fix": "????????? composition ????????? concern (network, database, cache)",
                },
                "skip_status_patches": {
                    "description": "??????????????????????????? ToCompositeFieldPath ?????????????????? status",
                    "reason": "Consumers ?????????????????? endpoint, connection details",
                },
                "hardcode_credentials": {
                    "description": "?????????????????? hardcode credentials ?????? compositions",
                    "fix": "????????? connectionDetails, writeConnectionSecretToRef",
                },
            },
        }

bp = BestPractices()
practices = bp.practices()
print("Best Practices (DO):")
for name, info in practices["do"].items():
    print(f"  {name}: {info['description']}")

print("\nAnti-Patterns (DON'T):")
for name, info in practices["dont"].items():
    print(f"  {name}: {info['description']}")
    print(f"    Fix: {info.get('fix', info.get('reason', ''))}")

FAQ ??????????????????????????????????????????

Q: Clean Architecture ???????????????????????????????????? Crossplane ??????????

A: ????????????????????????????????????????????? project ??????????????? ?????????????????????????????????????????????????????????????????? ?????? 2+ cloud providers ??????????????????????????? compositions ????????????????????????????????? provider, ?????? 5+ compositions ????????????????????? maintain, ?????????????????? teams ????????? platform (platform team vs application teams), ????????????????????? portability ???????????? cloud ????????? ?????????????????? project ???????????? (1 provider, 2-3 compositions) ??????????????????????????????????????????????????? ????????????????????? ???????????????????????????????????????????????????????????? refactor ???????????? Clean Architecture ??????????????? complexity ???????????????????????????

Q: T-shirt sizing (small/medium/large) ?????????????????? exact specs ??????????????????????

A: ????????????????????? audience ????????? consumers ???????????? application developers ??????????????????????????? infrastructure details ????????? T-shirt sizing ??????????????????????????? ??????????????????????????????????????? db.t3.micro ????????????????????? ???????????????????????? small ????????? consumers ???????????? infra engineers ?????????????????????????????? control ????????????????????? ????????? option ????????? exact specs ????????? (override) ?????????????????? ????????? T-shirt size ???????????? default ??????????????? optional override fields ?????????????????? power users ???????????? size: medium (default) + optional instanceClassOverride: db.r6g.xlarge

Q: Composition Functions ????????????????????? ????????????????????????????

A: Composition Functions ?????????????????????????????????????????????????????? Crossplane ????????????????????????????????? logic ???????????? code (Go, Python, KCL) ????????? patches ??????????????? Logic ?????????????????????????????? patches ???????????????????????? (conditional resources, loops), ????????? programming language ??????????????????????????????, Unit test ???????????????????????? patches ????????????????????? ??????????????? complexity ???????????? maintain code + container image, Debugging ????????????????????? patches, ???????????? build ????????? deploy function image ??????????????? ????????? patches ?????????????????? simple mappings, ????????? Functions ?????????????????? complex logic (conditional provisioning, dynamic resource count) ???????????????????????? patches ???????????? ????????????????????????????????????????????????????????????????????? Functions

Q: ?????????????????? secrets ????????? connection details ??????????????????????

A: Crossplane ?????? built-in mechanism ?????????????????? secrets ?????? Composition ????????? connectionDetails block ??????????????? fields ????????????????????????????????? consumer (endpoint, username, password), ?????? Claim ????????? writeConnectionSecretToRef ??????????????? Secret name ???????????????????????????????????? namespace ????????? consumer Crossplane ??????????????? Kubernetes Secret ??????????????????????????? application ???????????? Secret ?????????????????? ?????????????????? Clean Architecture ????????? abstract connection details ????????????????????? standard format ????????? provider ???????????? endpoint, port, username, password, database ???????????????????????????????????? AWS RDS ???????????? GCP Cloud SQL consumer ??????????????? Secret format ???????????????????????? ????????????????????? External Secrets Operator ?????????????????? sync ????????? Vault/AWS Secrets Manager ?????????

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

Crossplane Composition Capacity Planningอ่านบทความ → Crossplane Composition Site Reliability SREอ่านบทความ → Crossplane Composition DNS Managementอ่านบทความ → Crossplane Composition Compliance Automationอ่านบทความ → Crossplane Composition GreenOps Sustainabilityอ่านบทความ →

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