Orchestration

Build Reusable Infra with Crossplane Compositions

Crossplane is revolutionizing how we think about infrastructure management in a Kubernetes-native way. While its core promise lies in extending the Kubernetes API to manage external cloud resources, its true power for platform teams emerges with Crossplane Compositions. Imagine a world where your developers can provision a complete application environmentβ€”database, cache, message queue, and networkingβ€”with a single kubectl apply, without needing to understand the underlying cloud provider specifics. This isn’t just about abstracting away the cloud; it’s about codifying your organization’s best practices, security policies, and cost optimizations into reusable, self-service infrastructure templates.

Without Compositions, each developer or team would have to define every individual cloud resource (e.g., an AWS RDS instance, an S3 bucket, an EC2 security group) from scratch. This leads to configuration drift, security vulnerabilities, and an explosion of boilerplate YAML. Compositions solve this by allowing platform engineers to define opinionated, high-level APIs that internally compose multiple lower-level managed resources. This elevates the developer experience, standardizes infrastructure provisioning, and enshrines operational excellence directly into your infrastructure-as-code. It’s like creating your own custom cloud service catalog, all powered by Kubernetes.

TL;DR: Crossplane Compositions

Crossplane Compositions enable platform teams to build reusable, opinionated infrastructure templates that abstract away cloud complexity for developers. They allow you to define a single, high-level API (a Composite Resource Definition or XRD) which, when requested, provisions multiple underlying cloud resources (Managed Resources) according to predefined best practices. This streamlines self-service provisioning, enforces standards, and reduces operational overhead.

Key Commands:

# Install Crossplane (if not already installed)
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane --create-namespace

# Install a cloud provider package (e.g., AWS)
kubectl apply -f - <<EOF
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-aws
spec:
  package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.40.0
EOF

# Define a Composite Resource Definition (XRD)
kubectl apply -f my-xrd.yaml

# Define a Composition for your XRD
kubectl apply -f my-composition.yaml

# Provision your composite resource
kubectl apply -f my-composite-resource.yaml

# View the status of your composite resource
kubectl get mycompositeresource

# View the underlying managed resources
kubectl get managedresources

Prerequisites

Before diving into Crossplane Compositions, ensure you have the following:

  • Kubernetes Cluster: A running Kubernetes cluster (v1.20+ recommended). You can use Minikube, Kind, or a cloud-managed cluster (EKS, GKE, AKS).
  • kubectl: The Kubernetes command-line tool, configured to connect to your cluster. Refer to the official Kubernetes documentation for installation.
  • Helm: The Kubernetes package manager (v3.0+). Instructions can be found on the Helm website.
  • Crossplane Installed: Crossplane and at least one cloud provider package (e.g., provider-aws, provider-gcp, provider-azure) must be installed and configured in your cluster. This tutorial will primarily use AWS examples.
    # Add Crossplane Helm repository
    helm repo add crossplane-stable https://charts.crossplane.io/stable
    helm repo update
    
    # Install Crossplane into its own namespace
    helm install crossplane --namespace crossplane-system crossplane-stable/crossplane --create-namespace
    
    # Verify Crossplane pods are running
    kubectl get pods -n crossplane-system
    
    # Install Crossplane Provider for AWS (adjust version as needed)
    kubectl apply -f - <<EOF
    apiVersion: pkg.crossplane.io/v1
    kind: Provider
    metadata:
      name: provider-aws
    spec:
      package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.40.0
    EOF
    
    # Wait for the Provider to be healthy
    kubectl wait --for=condition=Healthy provider.pkg.crossplane.io/provider-aws --timeout=5m
    
    # Configure AWS credentials for Crossplane
    # Replace with your AWS Access Key ID and Secret Access Key
    # For production, use a more secure method like IAM Roles for Service Accounts (IRSA)
    kubectl create secret generic aws-creds -n crossplane-system --from-literal=credentials='[default]
    aws_access_key_id = YOUR_AWS_ACCESS_KEY_ID
    aws_secret_access_key = YOUR_AWS_SECRET_ACCESS_KEY'
    
    kubectl apply -f - <<EOF
    apiVersion: aws.crossplane.io/v1beta1
    kind: ProviderConfig
    metadata:
      name: default
    spec:
      credentials:
        source: Secret
        secretRef:
          namespace: crossplane-system
          name: aws-creds
          key: credentials
    EOF
    
  • Basic Understanding of Crossplane: Familiarity with Crossplane’s core concepts like Managed Resources (MRs), Providers, and ProviderConfigs.
  • YAML Proficiency: Comfort with writing and understanding Kubernetes YAML manifest files.

Step-by-Step Guide: Building Reusable Infrastructure with Crossplane Compositions

This guide will walk you through creating a simple, opinionated PostgreSQL database instance in AWS using Crossplane Compositions. Our composite resource will provision an AWS RDS instance along with a security group.

Step 1: Define Your Composite Resource Definition (XRD)

The first step in creating a Composition is to define your desired high-level API. This is done using a Composite Resource Definition (XRD). An XRD is a custom resource definition (CRD) for your composite resource, allowing you to define its schema, validation rules, and export secrets.

For our example, we’ll create an XPostgreSQLInstance that allows users to specify an engine version and storage size. We’ll also define connection details that should be exposed as a Kubernetes secret.

# xpostgresqlinstance.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: xpostgresqlinstances.database.example.org
spec:
  group: database.example.org
  names:
    kind: XPostgreSQLInstance
    plural: xpostgresqlinstances
  claimNames:
    kind: PostgreSQLInstance
    plural: postgresqlinstances
  versions:
    - name: v1alpha1
      served: true
      referenceable: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                parameters:
                  type: object
                  description: "Parameters for the PostgreSQL instance."
                  properties:
                    storageGB:
                      type: integer
                      description: "Storage capacity in GB."
                      minimum: 20
                      maximum: 1000
                    engineVersion:
                      type: string
                      description: "PostgreSQL engine version."
                      enum: ["13.7", "14.6", "15.2"]
                      default: "14.6"
                    vpcId:
                      type: string
                      description: "The VPC ID where the RDS instance should be provisioned."
                  required:
                    - storageGB
                    - vpcId
                writeConnectionSecretToRef:
                  type: object
                  description: "References to a Secret to which connection details will be written."
                  properties:
                    name:
                      type: string
                      description: "Name of the secret."
                    namespace:
                      type: string
                      description: "Namespace of the secret."
                  required:
                    - name
                    - namespace
              required:
                - parameters
      # Define what connection details should be exposed from the underlying resources
      # and how they map to the secret.
      connectionSecretKeys:
        - username
        - password
        - endpoint
        - port
        - host
        - database

Explanation:

This YAML defines our XPostgreSQLInstance. Key fields include:

  • group and names: Define the API group and names for our custom resource. We also define claimNames, which creates a namespaced version of our composite resource (PostgreSQLInstance) that developers can use. This provides multi-tenancy within the same cluster.
  • versions: Specifies the API version (v1alpha1 in this case) and its schema.
  • spec.parameters: This is where we define the input parameters that a developer can provide when requesting an XPostgreSQLInstance, such as storageGB, engineVersion, and vpcId.
  • writeConnectionSecretToRef: This standard Crossplane field allows the user to specify where the connection details (endpoint, username, password) for the provisioned resource should be written as a Kubernetes Secret.
  • connectionSecretKeys: This crucial section tells Crossplane which fields from the underlying managed resources’ connection secrets should be aggregated and exposed in the composite resource’s connection secret.

By defining this XRD, we’re essentially creating a new API endpoint in our Kubernetes cluster that represents a “PostgreSQL Instance” from the developer’s perspective.

kubectl apply -f xpostgresqlinstance.yaml

Verify:

You should see the Composite Resource Definition created. You can also check for the new CRDs:

kubectl get crd | grep postgresqlinstance
xpostgresqlinstances.database.example.org             2023-10-27T10:00:00Z
postgresqlinstances.database.example.org              2023-10-27T10:00:00Z

Step 2: Create the Composition

Now that we have our high-level API (the XRD), we need to tell Crossplane how to fulfill requests for it. This is where the Composition comes in. A Composition defines the set of managed resources (e.g., AWS RDS Instance, AWS Security Group) that should be provisioned when an XPostgreSQLInstance is requested.

This Composition will provision an AWS RDS DBInstance and an AWS EC2 SecurityGroup. It will also define how parameters from the XPostgreSQLInstance are mapped to the underlying managed resources, and how connection details are aggregated.

# composition.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: xpostgresqlinstances.aws
  labels:
    provider: aws
    db: postgresql
spec:
  compositeTypeRef:
    apiVersion: database.example.org/v1alpha1
    kind: XPostgreSQLInstance
  resources:
    # Resource 1: AWS RDS DBInstance
    - name: rdsinstance
      base:
        apiVersion: rds.aws.crossplane.io/v1beta1
        kind: DBInstance
        spec:
          forProvider:
            region: us-east-1 # Hardcoded region for simplicity, can be dynamic
            dbInstanceClass: db.t3.micro
            masterUsername: masteruser
            engine: postgres
            skipFinalSnapshotBeforeDeletion: true
            publiclyAccessible: false # Always provision private instances
            tags:
              - key: managed-by
                value: crossplane
              - key: environment
                value: dev # Example tag
          writeConnectionSecretToRef:
            name: rds-connection-secret
            namespace: crossplane-system # Internal secret, will be copied later
      patches:
        # Patch to set StorageGB from XPostgreSQLInstance parameters
        - fromFieldPath: "spec.parameters.storageGB"
          toFieldPath: "spec.forProvider.allocatedStorage"
          type: FromCompositeFieldPath
        # Patch to set EngineVersion from XPostgreSQLInstance parameters
        - fromFieldPath: "spec.parameters.engineVersion"
          toFieldPath: "spec.forProvider.engineVersion"
          type: FromCompositeFieldPath
        # Patch to set SecurityGroupRef from the SecurityGroup resource's name
        - fromFieldPath: "metadata.name" # Refers to the composite resource's name
          toFieldPath: "spec.forProvider.vpcSecurityGroupIDs[0]"
          type: CombineFromComposite
          combine:
            strategy: string
            string:
              fmt: "%s-sg" # Expects the security group to be named after the composite resource + "-sg"
        # Patch to set SubnetGroupRef (requires existing DB Subnet Group)
        - fromFieldPath: "spec.parameters.dbSubnetGroupName" # Assuming this parameter exists in XPostgreSQLInstance
          toFieldPath: "spec.forProvider.dbSubnetGroupName"
          type: FromCompositeFieldPath
          # If dbSubnetGroupName is not provided, we can use a default or omit the patch.
          # For this example, we'll assume it's provided or a default is set in the XPostgreSQLInstance.
          # For a more robust solution, you might use a transform to provide a default if missing.
        # Patch to set VPC ID (needed for DB Subnet Group creation or lookup)
        - fromFieldPath: "spec.parameters.vpcId"
          toFieldPath: "spec.forProvider.vpcSecurityGroupIDs[0]" # This is incorrect, VPC ID is not directly used here for DBInstance
          # Correction: VPC ID is used by the SecurityGroup. We will implicitly link via security group.
          # The VPC ID is primarily used by the SecurityGroup.
          # For DBInstance, the security group IDs and subnet group name are sufficient.

    # Resource 2: AWS EC2 SecurityGroup
    - name: securitygroup
      base:
        apiVersion: ec2.aws.crossplane.io/v1beta1
        kind: SecurityGroup
        spec:
          forProvider:
            region: us-east-1
            groupName: crossplane-rds-sg # This will be patched
            description: "Security group for Crossplane managed PostgreSQL instance."
            ingress:
              - fromPort: 5432
                toPort: 5432
                ipProtocol: tcp
                ipRanges:
                  - cidrBlock: 0.0.0.0/0 # WARNING: This is too permissive for production.
                                        # Use a more restrictive CIDR or `securityGroupRefs` in production.
            tags:
              - key: managed-by
                value: crossplane
          writeConnectionSecretToRef:
            name: sg-connection-secret
            namespace: crossplane-system # Internal secret, will be copied later
      patches:
        # Patch to set the SecurityGroup name dynamically
        - fromFieldPath: "metadata.name"
          toFieldPath: "spec.forProvider.groupName"
          type: CombineFromComposite
          combine:
            strategy: string
            string:
              fmt: "%s-sg"
        # Patch to set the VPC ID from XPostgreSQLInstance parameters
        - fromFieldPath: "spec.parameters.vpcId"
          toFieldPath: "spec.forProvider.vpcId"
          type: FromCompositeFieldPath

    # Connection Secret Publisher (Copies the internal RDS secret to the user-defined secret)
    - name: connection-secret-publisher
      base:
        apiVersion: v1
        kind: Secret
        metadata:
          annotations:
            crossplane.io/composition-resource-name: connection-secret-publisher
      patches:
        # Destination secret name and namespace from XPostgreSQLInstance
        - fromFieldPath: "spec.writeConnectionSecretToRef.name"
          toFieldPath: "metadata.name"
          type: FromCompositeFieldPath
        - fromFieldPath: "spec.writeConnectionSecretToRef.namespace"
          toFieldPath: "metadata.namespace"
          type: FromCompositeFieldPath
        # Data from the RDS instance's connection secret
        - fromFieldPath: "status.atProvider.connectionDetails.data.username"
          toFieldPath: "data.username"
          type: FromManagedResource
          fromFieldPath: "status.connectionDetails.data.username"
          # Reference the 'rdsinstance' resource defined above
          resourceSelector:
            matchControllerRef: true
            apiVersion: rds.aws.crossplane.io/v1beta1
            kind: DBInstance
        - fromFieldPath: "status.atProvider.connectionDetails.data.password"
          toFieldPath: "data.password"
          type: FromManagedResource
          fromFieldPath: "status.connectionDetails.data.password"
          resourceSelector:
            matchControllerRef: true
            apiVersion: rds.aws.crossplane.io/v1beta1
            kind: DBInstance
        - fromFieldPath: "status.atProvider.connectionDetails.data.endpoint"
          toFieldPath: "data.endpoint"
          type: FromManagedResource
          fromFieldPath: "status.connectionDetails.data.endpoint"
          resourceSelector:
            matchControllerRef: true
            apiVersion: rds.aws.crossplane.io/v1beta1
            kind: DBInstance
        - fromFieldPath: "status.atProvider.connectionDetails.data.port"
          toFieldPath: "data.port"
          type: FromManagedResource
          fromFieldPath: "status.connectionDetails.data.port"
          resourceSelector:
            matchControllerRef: true
            apiVersion: rds.aws.crossplane.io/v1beta1
            kind: DBInstance
        - fromFieldPath: "status.atProvider.connectionDetails.data.host"
          toFieldPath: "data.host"
          type: FromManagedResource
          fromFieldPath: "status.connectionDetails.data.host"
          resourceSelector:
            matchControllerRef: true
            apiVersion: rds.aws.crossplane.io/v1beta1
            kind: DBInstance
        - fromFieldPath: "status.atProvider.connectionDetails.data.database"
          toFieldPath: "data.database"
          type: FromManagedResource
          fromFieldPath: "status.connectionDetails.data.database"
          resourceSelector:
            matchControllerRef: true
            apiVersion: rds.aws.crossplane.io/v1beta1
            kind: DBInstance

Explanation:

This Composition is the heart of our reusable template.

  • compositeTypeRef: Links this Composition to our XPostgreSQLInstance XRD.
  • resources: An array defining the managed resources to be provisioned.
    • rdsinstance: Defines an AWS RDS DBInstance.
      • base: Contains the static configuration for the RDS instance. Notice the hardcoded region, dbInstanceClass, and other defaults.
      • patches: These are critical for dynamically configuring the managed resource based on the XPostgreSQLInstance‘s parameters.
        • FromCompositeFieldPath: Takes a value from the composite resource (e.g., spec.parameters.storageGB) and applies it to a field in the managed resource (e.g., spec.forProvider.allocatedStorage).
        • CombineFromComposite: Allows constructing a field value by combining strings, often using the composite resource’s name. Here, we use it to construct the security group ID.
      • writeConnectionSecretToRef: This is an internal secret where the RDS instance will write its connection details. We’ll later copy these to a user-facing secret.
    • securitygroup: Defines an AWS EC2 SecurityGroup.
      • It includes a default ingress rule for PostgreSQL port 5432. Warning: The 0.0.0.0/0 CIDR block is highly insecure for production. In a real-world scenario, you would use a more restrictive CIDR block, perhaps dynamically derived from network policies or other composite resource parameters. For enhancing network security, consider exploring resources like our Kubernetes Network Policies: Complete Security Hardening Guide.
      • Patches are used to dynamically name the security group and link it to the correct VPC using spec.parameters.vpcId from the composite resource.
    • connection-secret-publisher: This resource is a standard Kubernetes Secret. Its purpose is to take the connection details from the internally managed rdsinstance secret and publish them to the secret specified by the user in the XPostgreSQLInstance‘s writeConnectionSecretToRef. This is a common pattern for exposing secrets securely.
kubectl apply -f composition.yaml

Verify:

You should see the Composition created.

kubectl get composition
NAME                      AGE
xpostgresqlinstances.aws  1m

Step 3: Create a Composite Resource Claim (Optional but Recommended)

While you can directly create an XPostgreSQLInstance, it’s generally recommended for developers to use a “Claim” (a namespaced resource) version of your composite resource. The XRD we defined earlier automatically created a PostgreSQLInstance claim kind. Claims provide multi-tenancy and better RBAC isolation for developers.

Before creating the claim, you need a VPC ID. You can get one from your AWS account or create one. For this example, let’s assume you have a VPC ID. Replace YOUR_VPC_ID with an actual VPC ID from your AWS account.

# postgresql-claim.yaml
apiVersion: database.example.org/v1alpha1
kind: PostgreSQLInstance
metadata:
  name: my-app-db
  namespace: default # Developers would deploy this in their own namespace
spec:
  id: my-application-db # Unique identifier for the composite resource
  parameters:
    storageGB: 50
    engineVersion: "14.6"
    vpcId: "vpc-0abcdef1234567890" # Replace with your actual VPC ID
  writeConnectionSecretToRef:
    name: my-app-db-connection
    namespace: default # The secret will be created in the same namespace as the claim

Explanation:

This is what a developer would apply. They specify the desired storageGB, engineVersion, and the vpcId. They also indicate where the connection details should be written. They don’t need to know about RDS, Security Groups, or any AWS specifics – just that they want a PostgreSQL instance with certain characteristics.

Important: The id field in spec is automatically propagated to the underlying XPostgreSQLInstance and can be used to generate unique names for managed resources, preventing naming collisions. The writeConnectionSecretToRef specifies where the database connection information will be stored as a Kubernetes Secret.

kubectl apply -f postgresql-claim.yaml

Verify:

You should see the PostgreSQLInstance claim and the corresponding XPostgreSQLInstance composite resource created.

kubectl get postgresqlinstance -n default
NAME        SYNCED   READY   COMPOSITION              AGE
my-app-db   True     False   xpostgresqlinstances.aws   10s
kubectl get xpostgresqlinstance
NAME               SYNCED   READY   AGE
my-app-db-5zk7w    False    False   15s # The name is derived from the claim name + a hash

Initially, both will be False for READY because Crossplane is still provisioning the underlying AWS resources.

Step 4: Observe Resource Provisioning

Crossplane will now start provisioning the AWS RDS instance and Security Group based on your Composition. This can take several minutes. You can monitor the status of the underlying managed resources.

# Get the XPostgreSQLInstance to find the name of the underlying resources
kubectl get xpostgresqlinstance my-app-db-5zk7w -o yaml | grep "resourceRef" -A 5
      - apiVersion: rds.aws.crossplane.io/v1beta1
        kind: DBInstance
        name: my-app-db-5zk7w-rdsinstance
      - apiVersion: ec2.aws.crossplane.io/v1beta1
        kind: SecurityGroup
        name: my-app-db-5zk7w-securitygroup

Now, check the status of these managed resources:

kubectl get dbinstance my-app-db-5zk7w-rdsinstance
kubectl get securitygroup my-app-db-5zk7w-securitygroup
NAME                          READY   SYNCED   STATE       AGE
my-app-db-5zk7w-rdsinstance   False   True     creating    2m

NAME                            READY   SYNCED   AGE
my-app-db-5zk7w-securitygroup   True    True     2m

The SecurityGroup should become ready quickly. The DBInstance will take longer as AWS provisions it. Once the DBInstance is READY, your composite resource will also become ready.

kubectl get postgresqlinstance -n default
NAME        SYNCED   READY   COMPOSITION              AGE
my-app-db   True     True    xpostgresqlinstances.aws   10m

Step 5: Access Connection Details

Once the PostgreSQLInstance claim is READY: True, the connection secret will be available in the specified namespace.

kubectl get secret my-app-db-connection -n default -o yaml
apiVersion: v1
kind: Secret
metadata:
  name: my-app-db-connection
  namespace: default
type: Opaque
data:
  database: cG9zdGdyZXM= # base64 encoded 'postgres'
  endpoint: bXktYXBwLWRiLTV6azd3LXJkc2luc3RhbmNlLmFic2NkZWZnMTIzNDUuZXUtd2VzdC0xLnJkcy5hbWF6b25hd3MuY29t # Base64 encoded endpoint
  host: bXktYXBwLWRiLTV6azd3LXJkc2luc3RhbmNlLmFic2NkZWZnMTIzNDUuZXUtd2VzdC0xLnJkcy5hbWF6b25hd3MuY29t
  password: YOUR_BASE64_ENCODED_PASSWORD
  port: NQ00MzI= # base64 encoded '5432'
  username: bWFzdGVydXNlcg== # base64 encoded 'masteruser'

You can then decode these values to connect to your database:

kubectl get secret my-app-db-connection -n default -o jsonpath='{.data.endpoint}' | base64 -d
kubectl get secret my-app-db-connection -n default -o jsonpath='{.data.username}' | base64 -d
kubectl get secret my-app-db-connection -n default -o jsonpath='{.data.password}' | base64 -d
kubectl get secret my-app-db-connection -n default -o jsonpath='{.data.port}' | base64 -d

This secret can then be mounted into your application pods, allowing them to connect to the provisioned database without hardcoding any credentials or endpoints. This pattern is fundamental for secure application deployment on Kubernetes, and for further reading on enhancing security, consider our Securing Container Supply Chains with Sigstore and Kyverno guide.

Production Considerations

Deploying Crossplane Compositions in production requires careful planning and adherence to best practices:

  1. RBAC: Implement strict Kubernetes RBAC for your XRDs and Compositions. Developers should only have permissions to create/manage their namespaced claims (PostgreSQLInstance in our example), not the cluster-scoped XPostgreSQLInstance or the underlying managed resources. Platform teams manage the Compositions and XRDs.
  2. Security Groups/Network Policies: The example uses a permissive 0.0.0.0/0 ingress rule for simplicity. In production, this must be restricted to specific CIDR blocks, other security groups, or even integrated with Kubernetes Network Policies for pod-to-database communication. Ensure your cloud provider’s network settings align with your security posture. For advanced network encryption between pods, explore Cilium WireGuard Encryption.
  3. VPC and Subnet Management: For real-world use cases, you’ll likely want to create and manage VPCs, subnets, and database subnet groups via Crossplane as well, making them part of a larger composition or managed by another team’s composition.
  4. ProviderConfig Security: Storing AWS credentials directly in a Kubernetes Secret is acceptable for development but less secure for production. Use IAM Roles for Service Accounts (IRSA) for EKS, Workload Identity for GKE, or Managed Identities for AKS to grant Crossplane the necessary cloud permissions without exposing long-lived credentials.
  5. Monitoring and Observability: Monitor Crossplane’s health, reconciliation loops, and the status of managed resources. Integrate with your existing observability stack. Crossplane emits standard Kubernetes events, and its metrics can be scraped by Prometheus. For eBPF-based observability, tools like Hubble can provide deep insights, as discussed in eBPF Observability: Building Custom Metrics with Hubble.
  6. Versioning and Rollbacks: Treat your XRDs and Compositions as critical infrastructure code. Store them in Git, implement GitOps principles, and use proper versioning. Plan for rollback strategies in case of issues.
  7. Cost Optimization: Compositions can enforce cost-effective defaults (e.g., specific instance types, storage tiers). Combine Crossplane with tools like Karpenter for Kubernetes node autoscaling to further optimize cloud spending.
  8. Idempotency and Drift Detection: Crossplane ensures idempotency by continuously reconciling the desired state with the actual state. It will detect and attempt to correct any drift in your cloud resources.
  9. Managed Resource Defaults: Carefully choose default values in your Compositions (e.g., dbInstanceClass, allocatedStorage). These defaults establish your organization’s baseline for performance and cost.
  10. Extending to Other Resources: Consider extending your Compositions to include other resources like S3 buckets, SQS queues, or even Kubernetes resources like Deployments and Services using the provider-kubernetes. This allows for provisioning entire application stacks. For complex networking setups, the Kubernetes Gateway API can be composed with cloud load balancers.
  11. Service Mesh Integration: For applications that utilize the provisioned database, consider integrating with a service mesh like Istio for advanced traffic management, observability, and security. Our Istio Ambient Mesh Production Guide offers insights into modern service mesh deployments.

Troubleshooting

Here are some common issues you might encounter with Crossplane Compositions and their solutions:

  1. Issue: XPostgreSQLInstance or PostgreSQLInstance stuck in READY: False.

    Solution:
    This usually means the

Leave a Reply

Your email address will not be published. Required fields are marked *