Documentation

PLEASE NOTE: This document applies to v0.11 version and not to the latest release v0.12

Documentation for other releases can be found by using the version selector in the top right of any doc page.

Publish Infrastructure

Provisioning infrastructure using the Kubernetes API is a powerful capability, but combining primitive infrastructure resources into a single unit and publishing them to be provisioned by developers and consumed by applications greatly enhances this functionality.

As mentioned in the last section, CRDs that represent infrastructure resources on a provider are installed at the cluster scope. However, applications are typically provisioned at the namespace scope using Kubernetes primitives such as Pod or Deployment. To make infrastructure resources available to be provisioned at the namespace scope, they can be published. This consists of creating the following resources:

In addition to making provisioning available at the namespace scope, composition also allows for multiple types of managed resources to satisfy the same namespace-scoped resource. In the examples below, we will define and publish a new PostgreSQLInstance resource that only takes a single storageGB parameter, and specifies that it will create a connection Secret with keys for username, password, and endpoint. We will then create a Composition for each provider that can satisfy and be parameterized by a PostgreSQLInstance. Let’s get started!

Note: Crossplane must be granted RBAC permissions to managed new infrastructure types that we define. This is covered in greater detail in the composition section, but you can run the following command to grant all necessary RBAC permissions for this quick start guide: kubectl apply -f https://raw.githubusercontent.com/crossplane/crossplane/release-0.11/docs/snippets/publish/clusterrole.yaml

Create InfrastructureDefinition

The next step is defining an InfrastructureDefinition that declares the schema for a PostgreSQLInstance. You will notice that this looks very similar to a CRD, and after the InfrastructureDefinition is created, we will in fact have a PostgreSQLInstance CRD present in our cluster.

apiVersion: apiextensions.crossplane.io/v1alpha1
kind: InfrastructureDefinition
metadata:
  name: postgresqlinstances.database.example.org
spec:
  connectionSecretKeys:
    - username
    - password
    - endpoint
    - port
  crdSpecTemplate:
    group: database.example.org
    version: v1alpha1
    names:
      kind: PostgreSQLInstance
      listKind: PostgreSQLInstanceList
      plural: postgresqlinstances
      singular: postgresqlinstance
    validation:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              parameters:
                type: object
                properties:
                  storageGB:
                    type: integer
                required:
                  - storageGB
            required:
              - parameters
kubectl apply -f https://raw.githubusercontent.com/crossplane/crossplane/release-0.11/docs/snippets/publish/definition.yaml

You are now able to create instances of kind PostgreSQLInstance at the cluster scope.

Create InfrastructurePublication

The InfrastructureDefinition will make it possible to create PostgreSQLInstance objects in our Kubernetes cluster at the cluster scope, but we want to make them available at the namespace scope as well. This is done by defining an InfrastructurePublication that references the new PostgreSQLInstance type.

apiVersion: apiextensions.crossplane.io/v1alpha1
kind: InfrastructurePublication
metadata:
  name: postgresqlinstances.database.example.org
spec:
  infrastructureDefinitionRef:
    name: postgresqlinstances.database.example.org
kubectl apply -f https://raw.githubusercontent.com/crossplane/crossplane/release-0.11/docs/snippets/publish/publication.yaml

This will create a new CRD named PostgreSQLInstanceRequirement, which is the namespace-scoped variant of the PostgreSQLInstance CRD. You are now able to create instances of kind PostgreSQLInstanceRequirement at the namespace scope.

Create Compositions

Now it is time to define the resources that represent the primitive infrastructure units that actually get provisioned. For each provider we will define a Composition that satisfies the requirements of the PostgreSQLInstance InfrastructureDefinition. In this case, each will result in the provisioning of a public PostgreSQL instance on the provider.


apiVersion: apiextensions.crossplane.io/v1alpha1
kind: Composition
metadata:
  name: postgresqlinstances.aws.database.example.org
  labels:
    provider: aws
    guide: quickstart
    vpc: default
spec:
  writeConnectionSecretsToNamespace: crossplane-system
  reclaimPolicy: Delete
  from:
    apiVersion: database.example.org/v1alpha1
    kind: PostgreSQLInstance
  to:
    - base:
        apiVersion: database.aws.crossplane.io/v1beta1
        kind: RDSInstance
        spec:
          forProvider:
            dbInstanceClass: db.t2.small
            masterUsername: masteruser
            engine: postgres
            engineVersion: "9.6"
            skipFinalSnapshotBeforeDeletion: true
            publiclyAccessible: true
          writeConnectionSecretToRef:
            namespace: crossplane-system
          providerRef:
            name: aws-provider
          reclaimPolicy: Delete
      patches:
        - fromFieldPath: "metadata.uid"
          toFieldPath: "spec.writeConnectionSecretToRef.name"
          transforms:
            - type: string
              string:
                fmt: "%s-postgresql"
        - fromFieldPath: "spec.parameters.storageGB"
          toFieldPath: "spec.forProvider.allocatedStorage"
      connectionDetails:
        - fromConnectionSecretKey: username
        - fromConnectionSecretKey: password
        - fromConnectionSecretKey: endpoint
        - fromConnectionSecretKey: port
kubectl apply -f https://raw.githubusercontent.com/crossplane/crossplane/release-0.11/docs/snippets/publish/composition-aws.yaml

Note that this Composition will create an RDS instance using your default VPC, which may or may not allow connections from the internet depending on how it is configured. Select the AWS (New VPC) Composition if you wish to create an RDS instance that will allow traffic from the internet.

apiVersion: apiextensions.crossplane.io/v1alpha1
kind: Composition
metadata:
  name: vpcpostgresqlinstances.aws.database.example.org
  labels:
    provider: aws
    guide: quickstart
    vpc: new
spec:
  writeConnectionSecretsToNamespace: crossplane-system
  reclaimPolicy: Delete
  from:
    apiVersion: database.example.org/v1alpha1
    kind: PostgreSQLInstance
  to:
    - base:
        apiVersion: network.aws.crossplane.io/v1alpha3
        kind: VPC
        spec:
          cidrBlock: 192.168.0.0/16
          enableDnsSupport: true
          enableDnsHostNames: true
          providerRef:
            name: aws-provider
          reclaimPolicy: Delete
    - base:
        apiVersion: network.aws.crossplane.io/v1alpha3
        kind: Subnet
        metadata:
          labels:
            zone: us-west-2a
        spec:
          cidrBlock: 192.168.64.0/18
          vpcIdSelector:
            matchControllerRef: true
          availabilityZone: us-west-2a
          providerRef:
            name: aws-provider
          reclaimPolicy: Delete
    - base:
        apiVersion: network.aws.crossplane.io/v1alpha3
        kind: Subnet
        metadata:
          labels:
            zone: us-west-2b
        spec:
          cidrBlock: 192.168.128.0/18
          vpcIdSelector:
            matchControllerRef: true
          availabilityZone: us-west-2b
          providerRef:
            name: aws-provider
          reclaimPolicy: Delete
    - base:
        apiVersion: network.aws.crossplane.io/v1alpha3
        kind: Subnet
        metadata:
          labels:
            zone: us-west-2c
        spec:
          cidrBlock: 192.168.192.0/18
          vpcIdSelector:
            matchControllerRef: true
          availabilityZone: us-west-2c
          providerRef:
            name: aws-provider
          reclaimPolicy: Delete
    - base:
        apiVersion: database.aws.crossplane.io/v1beta1
        kind: DBSubnetGroup
        spec:
          forProvider:
            description: An excellent formation of subnetworks.
            subnetIdSelector:
              matchControllerRef: true
          providerRef:
            name: aws-provider
          reclaimPolicy: Delete
    - base:
        apiVersion: network.aws.crossplane.io/v1alpha3
        kind: InternetGateway
        spec:
          vpcIdSelector:
            matchControllerRef: true
          providerRef:
            name: aws-provider
          reclaimPolicy: Delete
    - base:
        apiVersion: network.aws.crossplane.io/v1alpha3
        kind: RouteTable
        spec:
          vpcIdSelector:
            matchControllerRef: true
          routes:
            - destinationCidrBlock: 0.0.0.0/0
              gatewayIdSelector:
                matchControllerRef: true
          associations:
            - subnetIdSelector:
                matchLabels:
                  zone: us-west-2a
            - subnetIdSelector:
                matchLabels:
                  zone: us-west-2b
            - subnetIdSelector:
                matchLabels:
                  zone: us-west-2c
          providerRef:
            name: aws-provider
          reclaimPolicy: Delete
    - base:
        apiVersion: network.aws.crossplane.io/v1alpha3
        kind: SecurityGroup
        spec:
          vpcIdSelector:
            matchControllerRef: true
          groupName: crossplane-getting-started
          description: Allow access to PostgreSQL
          ingress:
            - fromPort: 5432
              toPort: 5432
              protocol: tcp
              cidrBlocks:
                - cidrIp: 0.0.0.0/0
                  description: Everywhere
          providerRef:
            name: aws-provider
          reclaimPolicy: Delete
    - base:
        apiVersion: database.aws.crossplane.io/v1beta1
        kind: RDSInstance
        spec:
          forProvider:
            dbSubnetGroupNameSelector:
              matchControllerRef: true
            vpcSecurityGroupIDSelector:
              matchControllerRef: true
            dbInstanceClass: db.t2.small
            masterUsername: masteruser
            engine: postgres
            engineVersion: "9.6"
            skipFinalSnapshotBeforeDeletion: true
            publiclyAccessible: true
          writeConnectionSecretToRef:
            namespace: crossplane-system
          providerRef:
            name: aws-provider
          reclaimPolicy: Delete
      patches:
        - fromFieldPath: "metadata.uid"
          toFieldPath: "spec.writeConnectionSecretToRef.name"
          transforms:
            - type: string
              string:
                fmt: "%s-postgresql"
        - fromFieldPath: "spec.parameters.storageGB"
          toFieldPath: "spec.forProvider.allocatedStorage"
      connectionDetails:
        - fromConnectionSecretKey: username
        - fromConnectionSecretKey: password
        - fromConnectionSecretKey: endpoint
        - fromConnectionSecretKey: port

kubectl apply -f https://raw.githubusercontent.com/crossplane/crossplane/release-0.11/docs/snippets/publish/composition-aws-with-vpc.yaml
apiVersion: apiextensions.crossplane.io/v1alpha1
kind: Composition
metadata:
  name: postgresqlinstances.gcp.database.example.org
  labels:
    provider: gcp
    guide: quickstart
spec:
  writeConnectionSecretsToNamespace: crossplane-system
  reclaimPolicy: Delete
  from:
    apiVersion: database.example.org/v1alpha1
    kind: PostgreSQLInstance
  to:
    - base:
        apiVersion: database.gcp.crossplane.io/v1beta1
        kind: CloudSQLInstance
        spec:
          forProvider:
            databaseVersion: POSTGRES_9_6
            region: us-central1
            settings:
              tier: db-custom-1-3840
              dataDiskType: PD_SSD
              ipConfiguration:
                ipv4Enabled: true
                authorizedNetworks:
                  - value: "0.0.0.0/0"
          writeConnectionSecretToRef:
            namespace: crossplane-system
          providerRef:
            name: gcp-provider
          reclaimPolicy: Delete
      patches:
        - fromFieldPath: "metadata.uid"
          toFieldPath: "spec.writeConnectionSecretToRef.name"
          transforms:
            - type: string
              string:
                fmt: "%s-postgresql"
        - fromFieldPath: "spec.parameters.storageGB"
          toFieldPath: "spec.forProvider.settings.dataDiskSizeGb"
      connectionDetails:
        - fromConnectionSecretKey: username
        - fromConnectionSecretKey: password
        - fromConnectionSecretKey: endpoint
        - name: port
          value: "5432"
kubectl apply -f https://raw.githubusercontent.com/crossplane/crossplane/release-0.11/docs/snippets/publish/composition-gcp.yaml

Note: the Composition for Azure also includes a ResourceGroup and PostgreSQLServerFirewallRule that are required to provision a publicly available PostgreSQL instance on Azure. Composition enables scenarios such as this, as well as far more complex ones. See the composition documentation for more information.

apiVersion: apiextensions.crossplane.io/v1alpha1
kind: Composition
metadata:
  name: postgresqlinstances.azure.database.example.org
  labels:
    provider: azure
    guide: quickstart
spec:
  writeConnectionSecretsToNamespace: crossplane-system
  reclaimPolicy: Delete
  from:
    apiVersion: database.example.org/v1alpha1
    kind: PostgreSQLInstance
  to:
    - base:
        apiVersion: azure.crossplane.io/v1alpha3
        kind: ResourceGroup
        spec:
          location: West US 2
          reclaimPolicy: Delete
          providerRef:
            name: azure-provider
    - base:
        apiVersion: database.azure.crossplane.io/v1beta1
        kind: PostgreSQLServer
        spec:
          forProvider:
            administratorLogin: myadmin
            resourceGroupNameSelector:
              matchControllerRef: true
            location: West US 2
            sslEnforcement: Disabled
            version: "9.6"
            sku:
              tier: GeneralPurpose
              capacity: 2
              family: Gen5
          writeConnectionSecretToRef:
            namespace: crossplane-system
          providerRef:
            name: azure-provider
          reclaimPolicy: Delete
      patches:
        - fromFieldPath: "metadata.uid"
          toFieldPath: "spec.writeConnectionSecretToRef.name"
          transforms:
            - type: string
              string:
                fmt: "%s-postgresql"
        - fromFieldPath: "spec.parameters.storageGB"
          toFieldPath: "spec.forProvider.storageProfile.storageMB"
          transforms:
            - type: math
              math:
                multiply: 1024
      connectionDetails:
        - fromConnectionSecretKey: username
        - fromConnectionSecretKey: password
        - fromConnectionSecretKey: endpoint
        - name: port
          value: "5432"
    - base:
        apiVersion: database.azure.crossplane.io/v1alpha3
        kind: PostgreSQLServerFirewallRule
        spec:
          forProvider:
            serverNameSelector:
              matchControllerRef: true
            resourceGroupNameSelector:
              matchControllerRef: true
            properties:
              startIpAddress: 0.0.0.0
              endIpAddress: 255.255.255.254
          reclaimPolicy: Delete
          providerRef:
            name: azure-provider
kubectl apply -f https://raw.githubusercontent.com/crossplane/crossplane/release-0.11/docs/snippets/publish/composition-azure.yaml
apiVersion: apiextensions.crossplane.io/v1alpha1
kind: Composition
metadata:
  name: postgresqlinstances.alibaba.database.example.org
  labels:
    provider: alibaba
    guide: quickstart
spec:
  writeConnectionSecretsToNamespace: crossplane-system
  reclaimPolicy: Delete
  from:
    apiVersion: database.example.org/v1alpha1
    kind: PostgreSQLInstance
  to:
    - base:
        apiVersion: database.alibaba.crossplane.io/v1alpha1
        kind: RDSInstance
        spec:
          forProvider:
            engine: PostgreSQL
            engineVersion: "9.4"
            dbInstanceClass: rds.pg.s1.small
            securityIPList: "0.0.0.0/0"
            masterUsername: "myuser"
          writeConnectionSecretToRef:
            namespace: crossplane-system
          providerRef:
            name: alibaba-provider
          reclaimPolicy: Delete
      patches:
        - fromFieldPath: "metadata.uid"
          toFieldPath: "spec.writeConnectionSecretToRef.name"
          transforms:
            - type: string
              string:
                fmt: "%s-postgresql"
        - fromFieldPath: "spec.parameters.storageGB"
          toFieldPath: "spec.forProvider.dbInstanceStorageInGB"
      connectionDetails:
        - fromConnectionSecretKey: username
        - fromConnectionSecretKey: password
        - fromConnectionSecretKey: endpoint
        - fromConnectionSecretKey: port
kubectl apply -f https://raw.githubusercontent.com/crossplane/crossplane/release-0.11/docs/snippets/publish/composition-alibaba.yaml

Create Requirement

Now that we have defined our new type of infrastructure (PostgreSQLInstance) and created at least one composition that can satisfy it, we can create a PostgreSQLInstanceRequirement in the namespace of our choosing. In each Composition we defined we added a provider: <name-of-provider> label. In the PostgreSQLInstanceRequirement below we can use the compositionSelector to match our Composition of choice.


apiVersion: database.example.org/v1alpha1
kind: PostgreSQLInstanceRequirement
metadata:
  name: my-db
  namespace: default
spec:
  parameters:
    storageGB: 20
  compositionSelector:
    matchLabels:
      provider: aws
      vpc: default
  writeConnectionSecretToRef:
    name: db-conn
kubectl apply -f https://raw.githubusercontent.com/crossplane/crossplane/master/docs/snippets/publish/requirement-aws.yaml
apiVersion: database.example.org/v1alpha1
kind: PostgreSQLInstanceRequirement
metadata:
  name: my-db
  namespace: default
spec:
  parameters:
    storageGB: 20
  compositionSelector:
    matchLabels:
      provider: aws
      vpc: new
  writeConnectionSecretToRef:
    name: db-conn
kubectl apply -f https://raw.githubusercontent.com/crossplane/crossplane/release-0.11/docs/snippets/publish/requirement-aws.yaml
apiVersion: database.example.org/v1alpha1
kind: PostgreSQLInstanceRequirement
metadata:
  name: my-db
  namespace: default
spec:
  parameters:
    storageGB: 20
  compositionSelector:
    matchLabels:
      provider: gcp
  writeConnectionSecretToRef:
    name: db-conn
kubectl apply -f https://raw.githubusercontent.com/crossplane/crossplane/release-0.11/docs/snippets/publish/requirement-gcp.yaml
apiVersion: database.example.org/v1alpha1
kind: PostgreSQLInstanceRequirement
metadata:
  name: my-db
  namespace: default
spec:
  parameters:
    storageGB: 20
  compositionSelector:
    matchLabels:
      provider: azure
  writeConnectionSecretToRef:
    name: db-conn
kubectl apply -f https://raw.githubusercontent.com/crossplane/crossplane/release-0.11/docs/snippets/publish/requirement-azure.yaml
apiVersion: database.example.org/v1alpha1
kind: PostgreSQLInstanceRequirement
metadata:
  name: my-db
  namespace: default
spec:
  parameters:
    storageGB: 20
  compositionSelector:
    matchLabels:
      provider: alibaba
  writeConnectionSecretToRef:
    name: db-conn
kubectl apply -f https://raw.githubusercontent.com/crossplane/crossplane/release-0.11/docs/snippets/publish/requirement-alibaba.yaml

After creating the PostgreSQLInstanceRequirement Crossplane will provision a database instance on your provider of choice. Once provisioning is complete, you should see READY: True in the output when you run:

kubectl get postgresqlinstancerequirement.database.example.org my-db

Note: while waiting for the PostgreSQLInstanceRequirement to become ready, you may want to look at other resources in your cluser. The following commands will allow you to view groups of Crossplane resources:

  • kubectl get managed: get all resources that represent a unit of external infrastructure
  • kubectl get <name-of-provider>: get all resources related to <provider>
  • kubectl get crossplane: get all resources related to Crossplane

You should also see a Secret in the default namespace named db-conn that contains fields for username, password, and endpoint:

kubectl get secrets db-conn

Consume Infrastructure

Because connection secrets are written as a Kubernetes Secret they can easily be consumed by Kubernetes primitives. The most basic building block in Kubernetes is the Pod. Let’s define a Pod that will show that we are able to connect to our newly provisioned database.

apiVersion: v1
kind: Pod
metadata:
  name: see-db
  namespace: default
spec:
  containers:
  - name: see-db
    image: postgres:9.6
    command: ['psql']
    args: ['-c', 'SELECT current_database();']
    env:
    - name: PGDATABASE
      value: postgres
    - name: PGHOST
      valueFrom:
        secretKeyRef:
          name: db-conn
          key: endpoint
    - name: PGUSER
      valueFrom:
        secretKeyRef:
          name: db-conn
          key: username
    - name: PGPASSWORD
      valueFrom:
        secretKeyRef:
          name: db-conn
          key: password
    - name: PGPORT
      valueFrom:
        secretKeyRef:
          name: db-conn
          key: port
kubectl apply -f https://raw.githubusercontent.com/crossplane/crossplane/release-0.11/docs/snippets/publish/pod.yaml

This Pod simply connects to a PostgreSQL database and prints its name, so you should see the following output (or similar) after creating it if you run kubectl logs see-db:

 current_database
------------------
 postgres
(1 row)

Clean Up

To clean up the infrastructure that was provisioned, you can delete the PostgreSQLInstanceRequirement:

kubectl delete postgresqlinstancerequirement.database.example.org my-db

To clean up the Pod, run:

kubectl delete pod see-db

Don’t clean up your InfrastructureDefinition, InfrastructurePublication, or Composition just yet if you plan to continue on to the next section of the guide! We’ll use them again when we deploy an OAM application.

Next Steps

Now you have seen how to provision and publish more complex infrastructure setups. In the next section you will learn how to consume infrastructure alongside your OAM application manifests.