This document is for an older version of Crossplane.

This document applies to Crossplane v1.20 and not to the latest release v2.2.

Important

This guide is part 2 of a series.

Part 1 covers to installing Crossplane and connect your Kubernetes cluster to Azure.

This guide walks you through building and accessing a custom API with Crossplane.

Prerequisites

  • Complete quickstart part 1 connecting Kubernetes to Azure.
  • an Azure account with permissions to create an Azure Virtual Machine, Resource Group and Virtual Networking.

  1. Add the Crossplane Helm repository and install Crossplane
1helm repo add \
2crossplane-stable https://charts.crossplane.io/stable
3helm repo update
4&&
5helm install crossplane \
6crossplane-stable/crossplane \
7--namespace crossplane-system \
8--create-namespace
  1. When the Crossplane pods finish installing and are ready, apply the Azure Provider
1apiVersion: pkg.crossplane.io/v1
2kind: Provider
3metadata:
4  name: provider-azure-network
5spec:
6  package: xpkg.crossplane.io/crossplane-contrib/provider-azure-network:v1.11.2
1kubectl apply -f https://docs.crossplane.io/v1.20/manifests/getting-started/azure-part-2/provider-azure-network.yaml
  1. Use the Azure CLI to create a service principal and save the JSON output as azure-crednetials.json
1az ad sp create-for-rbac \
2--sdk-auth \
3--role Owner \
4--scopes /subscriptions/
  1. Create a Kubernetes secret from the Azure JSON file.
1kubectl create secret \
2generic azure-secret \
3-n crossplane-system \
4--from-file=creds=./azure-credentials.json
  1. Create a ProviderConfig
 1apiVersion: azure.upbound.io/v1beta1
 2metadata:
 3  name: default
 4kind: ProviderConfig
 5spec:
 6  credentials:
 7    source: Secret
 8    secretRef:
 9      namespace: crossplane-system
10      name: azure-secret
11      key: creds
1kubectl apply -f https://docs.crossplane.io/v1.20/manifests/getting-started/azure-part-2/providerconfig.yaml

Create a custom API

Crossplane allows you to build your own custom APIs for your users, abstracting away details about the cloud provider and their resources. You can make your API as complex or simple as you wish.

The custom API is a Kubernetes object. Here is an example custom API.

1apiVersion: compute.example.com/v1alpha1
2kind: VirtualMachine
3metadata:
4  name: my-vm
5spec:
6  location: "US"

Like any Kubernetes object the API has a version, kind and spec.

Define a group and version

To create your own API start by defining an API group and version.

The group can be any value, but common convention is to map to a fully qualified domain name.

The version shows how mature or stable the API is and increments when changing, adding or removing fields in the API.

Crossplane doesn’t require specific versions or a specific version naming convention, but following Kubernetes API versioning guidelines is strongly recommended.

  • v1alpha1 - A new API that may change at any time.
  • v1beta1 - An existing API that’s considered stable. Breaking changes are strongly discouraged.
  • v1 - A stable API that doesn’t have breaking changes.

This guide uses the group compute.example.com.

Because this is the first version of the API, this guide uses the version v1alpha1.

1apiVersion: compute.example.com/v1alpha1

Define a kind

The API group is a logical collection of related APIs. In a group are individual kinds representing different resources.

For example a compute group may have a VirtualMachine and BareMetal kinds.

The kind can be anything, but it must be UpperCamelCased.

This API’s kind is VirtualMachine

1apiVersion: compute.example.com/v1alpha1
2kind: VirtualMachine

Define a spec

The most important part of an API is the schema. The schema defines the inputs accepted from users.

This API allows users to provide a location of where to run their cloud resources.

All other resource settings can’t be configurable by the users. This allows Crossplane to enforce any policies and standards without worrying about user errors.

1apiVersion: compute.example.com/v1alpha1
2kind: VirtualMachine
3spec:
4  location: "US"

Apply the API

Crossplane uses Composite Resource Definitions (also called an XRD) to install your custom API in Kubernetes.

The XRD spec contains all the information about the API including the group, version, kind and schema.

The XRD’s name must be the combination of the plural and group.

The schema uses the OpenAPIv3 specification to define the API spec.

The API defines a location that must be oneOf either EU or US.

Apply this XRD to create the custom API in your Kubernetes cluster.

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: CompositeResourceDefinition
 3metadata:
 4  name: virtualmachines.compute.example.com
 5spec:
 6  group: compute.example.com
 7  names:
 8    kind: VirtualMachine
 9    plural: virtualmachines
10  versions:
11  - name: v1alpha1
12    schema:
13      openAPIV3Schema:
14        type: object
15        properties:
16          spec:
17            type: object
18            properties:
19              location:
20                type: string
21                oneOf:
22                  - pattern: '^EU$'
23                  - pattern: '^US$'
24            required:
25              - location
26    served: true
27    referenceable: true
28  claimNames:
29    kind: VirtualMachineClaim
30    plural: virtualmachineclaims
1kubectl apply -f https://docs.crossplane.io/v1.20/manifests/getting-started/azure-part-2/virtualmachines.compute.example.com.yaml

Adding the claimNames allows users to access this API either at the cluster level with the VirtualMachine endpoint or in a namespace with the VirtualMachineClaim endpoint.

The namespace scoped API is a Crossplane Claim.

Tip
For more details on the fields and options of Composite Resource Definitions read the XRD documentation.

View the installed XRD with kubectl get xrd.

1kubectl get xrd
2NAME                                  ESTABLISHED   OFFERED   AGE
3virtualmachines.compute.example.com   True          True      43s

View the new custom API endpoints with kubectl api-resources | grep VirtualMachine

1kubectl api-resources | grep VirtualMachine
2virtualmachineclaims              compute.example.com/v1alpha1           true         VirtualMachineClaim
3virtualmachines                   compute.example.com/v1alpha1           false        VirtualMachine

Create a deployment template

When users access the custom API Crossplane takes their inputs and combines them with a template describing what infrastructure to deploy. Crossplane calls this template a Composition.

The Composition defines all the cloud resources to deploy. Each entry in the template is a full resource definitions, defining all the resource settings and metadata like labels and annotations.

This template creates an Azure LinuxVirtualMachine NetworkInterface, Subnet VirtualNetwork and ResourceGroup.

This Composition takes the user’s location input and uses it as the location used in the individual resource.

Important

This Composition uses an array of resource templates. You can patch each template with data copied from the custom API. Crossplane calls this a Patch and Transform Composition.

You don’t have to use Patch and Transform. Crossplane supports a variety of alternatives, including Go Templating and CUE. You can also write a function in Go or Python to template your resources.

Read the Composition documentation for more information on configuring Compositions and all the available options.

Apply this Composition to your cluster.

  1apiVersion: apiextensions.crossplane.io/v1
  2kind: Composition
  3metadata:
  4  name: crossplane-quickstart-vm-with-network
  5spec:
  6  mode: Pipeline
  7  pipeline:
  8  - step: patch-and-transform
  9    functionRef:
 10      name: function-patch-and-transform
 11    input:
 12      apiVersion: pt.fn.crossplane.io/v1beta1
 13      kind: Resources
 14      resources:
 15        - name: quickstart-vm
 16          base:
 17            apiVersion: compute.azure.upbound.io/v1beta1
 18            kind: LinuxVirtualMachine
 19            spec:
 20              forProvider:
 21                adminUsername: adminuser
 22                adminSshKey:
 23                  - publicKey: ssh-rsa
 24                      AAAAB3NzaC1yc2EAAAADAQABAAABAQC+wWK73dCr+jgQOAxNsHAnNNNMEMWOHYEccp6wJm2gotpr9katuF/ZAdou5AaW1C61slRkHRkpRRX9FA9CYBiitZgvCCz+3nWNN7l/Up54Zps/pHWGZLHNJZRYyAB6j5yVLMVHIHriY49d/GZTZVNB8GoJv9Gakwc/fuEZYYl4YDFiGMBP///TzlI4jhiJzjKnEvqPFki5p2ZRJqcbCiF4pJrxUQR/RXqVFQdbRLZgYfJ8xGB878RENq3yQ39d8dVOkq4edbkzwcUmwwwkYVPIoDGsYLaRHnG+To7FvMeyO7xDVQkMKzopTQV8AuKpyvpqu0a9pWOMaiCyDytO7GGN
 25                      example@docs.crossplane.io
 26                    username: adminuser
 27                location: "Central US"
 28                osDisk:
 29                  - caching: ReadWrite
 30                    storageAccountType: Standard_LRS
 31                resourceGroupNameSelector:
 32                  matchControllerRef: true
 33                size: Standard_B1ms
 34                sourceImageReference:
 35                  - offer: debian-11
 36                    publisher: Debian
 37                    sku: 11-backports-gen2
 38                    version: latest
 39                networkInterfaceIdsSelector:
 40                  matchControllerRef: true
 41          patches:
 42            - type: FromCompositeFieldPath
 43              fromFieldPath: "spec.location"
 44              toFieldPath: "spec.forProvider.location"
 45              transforms:
 46                - type: map
 47                  map:
 48                    EU: "Sweden Central"
 49                    US: "Central US"
 50        - name: quickstart-nic
 51          base:
 52            apiVersion: network.azure.upbound.io/v1beta1
 53            kind: NetworkInterface
 54            spec:
 55              forProvider:
 56                ipConfiguration:
 57                  - name: crossplane-quickstart-configuration
 58                    privateIpAddressAllocation: Dynamic
 59                    subnetIdSelector:
 60                      matchControllerRef: true
 61                location: "Central US"
 62                resourceGroupNameSelector:
 63                  matchControllerRef: true
 64          patches:
 65            - type: FromCompositeFieldPath
 66              fromFieldPath: "spec.location"
 67              toFieldPath: "spec.forProvider.location"
 68              transforms:
 69                - type: map
 70                  map:
 71                    EU: "Sweden Central"
 72                    US: "Central US"
 73        - name: quickstart-subnet
 74          base:
 75            apiVersion: network.azure.upbound.io/v1beta1
 76            kind: Subnet
 77            spec:
 78              forProvider:
 79                addressPrefixes:
 80                  - 10.0.1.0/24
 81                virtualNetworkNameSelector:
 82                  matchControllerRef: true
 83                resourceGroupNameSelector:
 84                  matchControllerRef: true
 85        - name: quickstart-network
 86          base:
 87            apiVersion: network.azure.upbound.io/v1beta1
 88            kind: VirtualNetwork
 89            spec:
 90              forProvider:
 91                addressSpace:
 92                  - 10.0.0.0/16
 93                location: "Central US"
 94                resourceGroupNameSelector:
 95                  matchControllerRef: true
 96          patches:
 97            - type: FromCompositeFieldPath
 98              fromFieldPath: "spec.location"
 99              toFieldPath: "spec.forProvider.location"
100              transforms:
101                - type: map
102                  map:
103                    EU: "Sweden Central"
104                    US: "Central US"
105        - name: crossplane-resourcegroup
106          base:
107            apiVersion: azure.upbound.io/v1beta1
108            kind: ResourceGroup
109            spec:
110              forProvider:
111                location: Central US
112          patches:
113            - type: FromCompositeFieldPath
114              fromFieldPath: "spec.location"
115              toFieldPath: "spec.forProvider.location"
116              transforms:
117                - type: map
118                  map:
119                    EU: "Sweden Central"
120                    US: "Central US"
121  compositeTypeRef:
122    apiVersion: compute.example.com/v1alpha1
123    kind: VirtualMachine
1kubectl apply -f https://docs.crossplane.io/v1.20/manifests/getting-started/azure-part-2/crossplane-quickstart-vm-with-network.yaml

The compositeTypeRef defines which custom APIs can use this template to create resources.

A Composition uses a pipeline of composition functions to define the cloud resources to deploy. This template uses function-patch-and-transform. You must install the function before you can use it in a Composition.

Apply this Function to install function-patch-and-transform:

1apiVersion: pkg.crossplane.io/v1
2kind: Function
3metadata:
4  name: function-patch-and-transform
5spec:
6  package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2
1kubectl apply -f https://docs.crossplane.io/v1.20/manifests/getting-started/azure-part-2/function-patch-and-transform.yaml
Tip

Read the Composition documentation for more information on configuring Compositions and all the available options.

Read the Patch and Transform function documentation for more information on how it uses patches to map user inputs to Composition resource templates.

View the Composition with kubectl get composition

1kubectl get composition
2NAME                                    XR-KIND           XR-APIVERSION                     AGE
3crossplane-quickstart-vm-with-network   XVirtualMachine   custom-api.example.org/v1alpha1   77s

Install the Azure virtual machine provider

Part 1 only installed the Azure Virtual Network Provider. To deploying virtual machines requires the Azure Compute provider as well.

Add the new Provider to the cluster.

1apiVersion: pkg.crossplane.io/v1
2kind: Provider
3metadata:
4  name: provider-azure-compute
5spec:
6  package: xpkg.crossplane.io/crossplane-contrib/provider-azure-compute:v1.11.2
1kubectl apply -f https://docs.crossplane.io/v1.20/manifests/getting-started/azure-part-2/provider-azure-compute.yaml

View the new Compute provider with kubectl get providers.

1kubectl get providers
2NAME                                       INSTALLED   HEALTHY   PACKAGE                                                                AGE
3crossplane-contrib-provider-family-azure   True        True      xpkg.crossplane.io/crossplane-contrib/provider-family-azure:v1.11.2    23m
4provider-azure-compute                     True        True      xpkg.crossplane.io/crossplane-contrib/provider-azure-compute:v1.11.2   2m54s
5provider-azure-network                     True        True      xpkg.crossplane.io/crossplane-contrib/provider-azure-network:v1.11.2   23m

Access the custom API

With the custom API (XRD) installed and associated to a resource template (Composition) users can access the API to create resources.

Create a VirtualMachine object to create the cloud resources.

1apiVersion: compute.example.com/v1alpha1
2kind: VirtualMachine
3metadata:
4  name: my-vm
5spec:
6  location: "EU"
1kubectl apply -f https://docs.crossplane.io/v1.20/manifests/getting-started/azure-part-2/virtualmachine-my-vm.yaml

View the resource with kubectl get VirtualMachine.

Note
It may take up to five minutes for the resources to provision.
1kubectl get VirtualMachine
2NAME    SYNCED   READY   COMPOSITION                             AGE
3my-vm   True     True    crossplane-quickstart-vm-with-network   3m3s

This object is a Crossplane composite resource (also called an XR). It’s a single object representing the collection of resources created from the Composition template.

View the individual resources with kubectl get managed

 1kubectl get managed
 2NAME                                         READY   SYNCED   EXTERNAL-NAME   AGE
 3resourcegroup.azure.upbound.io/my-vm-7jb4n   True    True     my-vm-7jb4n     3m43s
 4
 5NAME                                                       READY   SYNCED   EXTERNAL-NAME   AGE
 6linuxvirtualmachine.compute.azure.upbound.io/my-vm-5h7p4   True    True     my-vm-5h7p4     3m43s
 7
 8NAME                                                    READY   SYNCED   EXTERNAL-NAME   AGE
 9networkinterface.network.azure.upbound.io/my-vm-j7fpx   True    True     my-vm-j7fpx     3m43s
10
11NAME                                          READY   SYNCED   EXTERNAL-NAME   AGE
12subnet.network.azure.upbound.io/my-vm-b2dqt   True    True     my-vm-b2dqt     3m43s
13
14NAME                                                  READY   SYNCED   EXTERNAL-NAME   AGE
15virtualnetwork.network.azure.upbound.io/my-vm-pd2sw   True    True     my-vm-pd2sw     3m43s

Accessing the API created all five resources defined in the template and linked them together.

Look at a specific resource to see it’s created in the location used in the API.

1kubectl describe linuxvirtualmachine | grep Location
2    Location:                         Sweden Central
3    Location:                         swedencentral

Delete the resources with kubectl delete VirtualMachine.

1kubectl delete VirtualMachine my-vm
2virtualmachine.compute.example.com "my-vm" deleted

Verify Crossplane deleted the resources with kubectl get managed

Note
It may take up to 5 minutes to delete the resources.
1kubectl get managed
2No resources found

Using the API with namespaces

Accessing the API VirtualMachine happens at the cluster scope. Most organizations isolate their users into namespaces.

A Crossplane Claim is the custom API in a namespace.

Creating a Claim is just like accessing the custom API endpoint, but with the kind from the custom API’s claimNames.

Create a new namespace to test create a Claim in.

1kubectl create namespace crossplane-test

Then create a Claim in the crossplane-test namespace.

1apiVersion: compute.example.com/v1alpha1
2kind: VirtualMachineClaim
3metadata:
4  name: my-namespaced-vm
5  namespace: crossplane-test
6spec:
7  location: "EU"
1kubectl apply -f https://docs.crossplane.io/v1.20/manifests/getting-started/azure-part-2/virtualmachineclaim-my-namespaced-vm.yaml
View the Claim with kubectl get claim -n crossplane-test.

1kubectl get claim -n crossplane-test
2NAME               SYNCED   READY   CONNECTION-SECRET   AGE
3my-namespaced-vm   True     True                        5m11s

The Claim automatically creates a composite resource, which creates the managed resources.

View the Crossplane created composite resource with kubectl get composite.

1kubectl get composite
2NAME                     SYNCED   READY   COMPOSITION                             AGE
3my-namespaced-vm-r7gdr   True     True    crossplane-quickstart-vm-with-network   5m33s

Again, view the managed resources with kubectl get managed.

 1NAME                                                          READY   SYNCED   EXTERNAL-NAME                  AGE
 2resourcegroup.azure.upbound.io/my-namespaced-vm-r7gdr-cvzw6   True    True     my-namespaced-vm-r7gdr-cvzw6   5m51s
 3
 4NAME                                                                        READY   SYNCED   EXTERNAL-NAME                  AGE
 5linuxvirtualmachine.compute.azure.upbound.io/my-namespaced-vm-r7gdr-vrbgb   True    True     my-namespaced-vm-r7gdr-vrbgb   5m51s
 6
 7NAME                                                                     READY   SYNCED   EXTERNAL-NAME                  AGE
 8networkinterface.network.azure.upbound.io/my-namespaced-vm-r7gdr-hwrb8   True    True     my-namespaced-vm-r7gdr-hwrb8   5m51s
 9
10NAME                                                           READY   SYNCED   EXTERNAL-NAME                  AGE
11subnet.network.azure.upbound.io/my-namespaced-vm-r7gdr-gh468   True    True     my-namespaced-vm-r7gdr-gh468   5m51s
12
13NAME                                                                   READY   SYNCED   EXTERNAL-NAME                  AGE
14virtualnetwork.network.azure.upbound.io/my-namespaced-vm-r7gdr-5qhz7   True    True     my-namespaced-vm-r7gdr-5qhz7   5m51s

Deleting the Claim deletes all the Crossplane generated resources.

kubectl delete claim -n crossplane-test my-VirtualMachine-database

1kubectl delete claim -n crossplane-test my-namespaced-vm
2virtualmachineclaim.compute.example.com "my-namespaced-vm" deleted
Note
It may take up to 5 minutes to delete the resources.

Verify Crossplane deleted the composite resource with kubectl get composite.

1kubectl get composite
2No resources found

Verify Crossplane deleted the managed resources with kubectl get managed.

1kubectl get managed
2No resources found

Next steps