Important

This guide is part 2 of a series.

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

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

Prerequisites

  • Complete quickstart part 1 connecting Kubernetes to AWS.
  • an AWS account with permissions to create an AWS S3 storage bucket and a DynamoDB instance

  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 AWS Provider
1cat <<EOF | kubectl apply -f -
2apiVersion: pkg.crossplane.io/v1
3kind: Provider
4metadata:
5  name: provider-aws-s3
6spec:
7  package: xpkg.upbound.io/upbound/provider-aws-s3:v1.1.0
8EOF
  1. Create a file with your AWS keys
1[default]
2aws_access_key_id = <aws_access_key>
3aws_secret_access_key = <aws_secret_key>
  1. Create a Kubernetes secret from the AWS keys
1kubectl create secret \
2generic aws-secret \
3-n crossplane-system \
4--from-file=creds=./aws-credentials.txt
  1. Create a ProviderConfig
 1cat <<EOF | kubectl apply -f -
 2apiVersion: aws.upbound.io/v1beta1
 3kind: ProviderConfig
 4metadata:
 5  name: default
 6spec:
 7  credentials:
 8    source: Secret
 9    secretRef:
10      namespace: crossplane-system
11      name: aws-secret
12      key: creds
13EOF

Install the DynamoDB Provider

Part 1 only installed the AWS S3 Provider. This section deploys an S3 bucket along with a DynamoDB Table.
Deploying a DynamoDB Table requires the DynamoDB Provider as well.

Add the new Provider to the cluster.

1cat <<EOF | kubectl apply -f -
2apiVersion: pkg.crossplane.io/v1
3kind: Provider
4metadata:
5  name: provider-aws-dynamodb
6spec:
7  package: xpkg.upbound.io/upbound/provider-aws-dynamodb:v1.1.0
8EOF

View the new DynamoDB provider with kubectl get providers.

1kubectl get providers
2NAME                          INSTALLED   HEALTHY   PACKAGE                                                 AGE
3provider-aws-dynamodb         True        True      xpkg.upbound.io/upbound/provider-aws-dynamodb:v1.1.0     3m55s
4provider-aws-s3               True        True      xpkg.upbound.io/upbound/provider-aws-s3:v1.1.0           13m
5upbound-provider-family-aws   True        True      xpkg.upbound.io/upbound/provider-family-aws:v1.1.0       13m

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: database.example.com/v1alpha1
2kind: NoSQL
3metadata:
4  name: my-nosql-database
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 database.example.com.

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

1apiVersion: database.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 database group may have a Relational and NoSQL kinds.

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

This API’s kind is NoSQL

1apiVersion: database.example.com/v1alpha1
2kind: NoSQL

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: database.example.com/v1alpha1
2kind: NoSQL
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.

 1cat <<EOF | kubectl apply -f -
 2apiVersion: apiextensions.crossplane.io/v1
 3kind: CompositeResourceDefinition
 4metadata:
 5  name: nosqls.database.example.com
 6spec:
 7  group: database.example.com
 8  names:
 9    kind: NoSQL
10    plural: nosqls
11  versions:
12  - name: v1alpha1
13    schema:
14      openAPIV3Schema:
15        type: object
16        properties:
17          spec:
18            type: object
19            properties:
20              location:
21                type: string
22                oneOf:
23                  - pattern: '^EU$'
24                  - pattern: '^US$'
25            required:
26              - location
27    served: true
28    referenceable: true
29  claimNames:
30    kind: NoSQLClaim
31    plural: nosqlclaim
32EOF

Adding the claimNames allows users to access this API either at the cluster level with the nosql endpoint or in a namespace with the nosqlclaim 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
3nosqls.database.example.com   True          True      2s

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

1kubectl api-resources | grep nosql
2nosqlclaim                                     database.example.com/v1alpha1          true         NoSQLClaim
3nosqls                                         database.example.com/v1alpha1          false        NoSQL

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 AWS S3 Bucket and a DynamoDB Table.

Crossplane uses patches to apply the user’s input to the resource template.
This Composition takes the user’s location input and uses it as the region used in the individual resource.

Apply this Composition to your cluster.

 1cat <<EOF | kubectl apply -f -
 2apiVersion: apiextensions.crossplane.io/v1
 3kind: Composition
 4metadata:
 5  name: dynamo-with-bucket
 6spec:
 7  resources:
 8    - name: s3Bucket
 9      base:
10        apiVersion: s3.aws.upbound.io/v1beta1
11        kind: Bucket
12        metadata:
13          name: crossplane-quickstart-bucket
14        spec:
15          forProvider:
16            region: us-east-2
17          providerConfigRef:
18            name: default
19      patches:
20        - type: FromCompositeFieldPath
21          fromFieldPath: "spec.location"
22          toFieldPath: "spec.forProvider.region"
23          transforms:
24            - type: map
25              map: 
26                EU: "eu-north-1"
27                US: "us-east-2"
28    - name: dynamoDB
29      base:
30        apiVersion: dynamodb.aws.upbound.io/v1beta1
31        kind: Table
32        metadata:
33          name: crossplane-quickstart-database
34        spec:
35          forProvider:
36            region: "us-east-2"
37            writeCapacity: 1
38            readCapacity: 1
39            attribute:
40              - name: S3ID
41                type: S
42            hashKey: S3ID
43      patches:
44        - type: FromCompositeFieldPath
45          fromFieldPath: "spec.location"
46          toFieldPath: "spec.forProvider.region"
47          transforms:
48            - type: map
49              map: 
50                EU: "eu-north-1"
51                US: "us-east-2"
52  compositeTypeRef:
53    apiVersion: database.example.com/v1alpha1
54    kind: NoSQL
55EOF

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

Tip

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

Read the Patch and Transform documentation for more information on how Crossplane 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
3dynamo-with-bucket   NoSQL     database.example.com/v1alpha1   3s

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 NoSQL object to create the cloud resources.

1cat <<EOF | kubectl apply -f -
2apiVersion: database.example.com/v1alpha1
3kind: NoSQL
4metadata:
5  name: my-nosql-database
6spec: 
7  location: "US"
8EOF

View the resource with kubectl get nosql.

1kubectl get nosql
2NAME                SYNCED   READY   COMPOSITION          AGE
3my-nosql-database   True     True    dynamo-with-bucket   14s

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
3table.dynamodb.aws.upbound.io/my-nosql-database-t5wtx   True    True     my-nosql-database-t5wtx   33s
4
5NAME                                               READY   SYNCED   EXTERNAL-NAME             AGE
6bucket.s3.aws.upbound.io/my-nosql-database-xtzph   True    True     my-nosql-database-xtzph   33s

Delete the resources with kubectl delete nosql.

1kubectl delete nosql my-nosql-database
2nosql.database.example.com "my-nosql-database" 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 nosql 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.

1cat <<EOF | kubectl apply -f -
2apiVersion: database.example.com/v1alpha1
3kind: NoSQLClaim
4metadata:
5  name: my-nosql-database
6  namespace: crossplane-test
7spec: 
8  location: "US"
9EOF

View the Claim with kubectl get claim -n crossplane-test.

1kubectl get claim -n crossplane-test
2NAME                SYNCED   READY   CONNECTION-SECRET   AGE
3my-nosql-database   True     True                        17s

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-nosql-database-t9qrw   True     True    dynamo-with-bucket   77s

Again, view the managed resources with kubectl get managed.

1kubectl get managed
2NAME                                                          READY   SYNCED   EXTERNAL-NAME                   AGE
3table.dynamodb.aws.upbound.io/my-nosql-database-t9qrw-dcpwv   True    True     my-nosql-database-t9qrw-dcpwv   116s
4
5NAME                                                     READY   SYNCED   EXTERNAL-NAME                   AGE
6bucket.s3.aws.upbound.io/my-nosql-database-t9qrw-g98lv   True    True     my-nosql-database-t9qrw-g98lv   117s

Deleting the Claim deletes all the Crossplane generated resources.

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

1kubectl delete claim -n crossplane-test my-nosql-database
2nosqlclaim.database.example.com "my-nosql-database" 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