AWS Quickstart Part 2

This document is for an older version of Crossplane.

This document applies to Crossplane version v1.11 and not to the latest release v1.13.

Important

This guide is part 2 of a series. Follow part 1 to install Crossplane and connect your Kubernetes cluster to AWS.

Part 3 covers patching composite resources and using Crossplane packages.

This section creates a Composition, Composite Resource Definition and a Claim to create a custom Kubernetes API to create AWS resources. This custom API is a Composite Resource (XR) API.

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: upbound-provider-aws
6spec:
7  package: xpkg.upbound.io/upbound/provider-aws:v0.27.0
8EOF
  1. Create a file called aws-credentials.txt with your AWS keys
1[default]
2aws_access_key_id = 
3aws_secret_access_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

Create a composition

Part 1 created a single managed resource. A Composition is a template to create one or more managed resource at the same time.

This sample composition creates an DynamoDB instance and associated S3 storage bucket.

Note
This example comes from the AWS recommendation for storing large DynamoDB attributes in S3.

To create a composition, first define each individual managed resource.

Create an S3 bucket object

Define a bucket resource using the configuration from the previous section:

Note
Don’t apply this configuration. This YAML is part of a larger definition.
1apiVersion: s3.aws.upbound.io/v1beta1
2kind: Bucket
3metadata:
4  name: crossplane-quickstart-bucket
5spec:
6  forProvider:
7    region: "us-east-2"
8  providerConfigRef:
9    name: default

Create a DynamoDB table resource

Next, define a DynamoDB table resource.

Tip
The Upbound Marketplace provides schema documentation for a Table resource.

The AWS Provider defines the apiVersion and kind.

DynamoDB instances require a region, writeCapacity and readCapacity parameters.

The attribute section creates the database “Partition key” and “Hash key.”

This example creates a single key named S3ID of type S for “string”

 1apiVersion: dynamodb.aws.upbound.io/v1beta1
 2kind: Table
 3metadata:
 4  name: crossplane-quickstart-database
 5spec:
 6  forProvider:
 7    region: "us-east-2"
 8    writeCapacity: 1
 9    readCapacity: 1
10    attribute:
11      - name: S3ID
12        type: S
13    hashKey: S3ID
Note
DynamoDB specifics are beyond the scope of this guide. Read the DynamoDB Developer Guide for more information.

Create the composition object

The composition combines the two resource definitions.

A Composition comes from the Crossplane API resources.

Create any name for this composition.

1apiVersion: apiextensions.crossplane.io/v1
2kind: Composition
3metadata:
4  name: dynamodb-with-bucket

Add the resources to the spec.resources section of the composition.

Give each resource a name and put the resource definition under the base key.

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  name: dynamodb-with-bucket
 5spec:
 6  resources:
 7    - name: s3-bucket
 8      base:
 9        apiVersion: s3.aws.upbound.io/v1beta1
10        kind: Bucket
11        spec:
12          forProvider:
13            region: "us-east-2"
14          providerConfigRef:
15            name: default
16    - name: dynamodb
17      base:
18        apiVersion: dynamodb.aws.upbound.io/v1beta1
19        kind: Table
20        spec:
21          forProvider:
22            region: "us-east-2"
23            writeCapacity: 1
24            readCapacity: 1
25            attribute:
26              - name: S3ID
27                type: S
28            hashKey: S3ID

Compositions are a template for generating resources. A composite resource actually creates the resources.

A composition defines what composite resources can use this template.

Compositions do this with the spec.compositeTypeRef definition.

Tip
Crossplane recommends prefacing the kind with an X to show it’s a Composition.
 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  name: dynamodb-with-bucket
 5spec:
 6  compositeTypeRef:
 7    apiVersion: custom-api.example.org/v1alpha1
 8    kind: XDatabase
 9  resources:
10    # Removed for Brevity    

A composite resource is actually a custom Kubernetes API type you define. The platform team controls the kind, API endpoint and version.

With this spec.compositeTypeRef Crossplane only allows composite resources from the API group custom-api.example.org that are of kind: XDatabase to use this template to create resources.

Apply the composition

Apply the full Composition to your Kubernetes cluster.

 1cat <<EOF | kubectl apply -f -
 2apiVersion: apiextensions.crossplane.io/v1
 3kind: Composition
 4metadata:
 5  name: dynamodb-with-bucket
 6spec:
 7  compositeTypeRef:
 8    apiVersion: custom-api.example.org/v1alpha1
 9    kind: XDatabase
10  resources:
11    - name: s3-bucket
12      base:
13        apiVersion: s3.aws.upbound.io/v1beta1
14        kind: Bucket
15        spec:
16          forProvider:
17            region: "us-east-2"
18    - name: dynamodb
19      base:
20        apiVersion: dynamodb.aws.upbound.io/v1beta1
21        kind: Table
22        spec:
23          forProvider:
24            region: "us-east-2"
25            writeCapacity: 1
26            readCapacity: 1
27            attribute:
28              - name: S3ID
29                type: S
30            hashKey: S3ID
31EOF

Confirm the composition exists with kubectl get composition

1kubectl get composition
2NAME                   AGE
3dynamodb-with-bucket   28s

Define a composite resource

The composition that was just created limited which composite resources can use that template.

A composite resource is a custom API defined by the platform teams.
A composite resource definition defines the schema for a composite resource.

A composite resource definition installs the custom API type into Kubernetes and defines what spec keys and values are valid when calling this new custom API.

Before creating a composite resource Crossplane requires a composite resource definition.

Tip
Composite resource definitions are also called XRDs for short.

Just like a composition the composite resource definition is part of the Crossplane API group.

The XRD name is the new API endpoint.

Tip
Crossplane recommends using a plural name for the XRD name.
1apiVersion: apiextensions.crossplane.io/v1
2kind: CompositeResourceDefinition
3metadata:
4  name: xdatabases.custom-api.example.org

The XRD’s spec defines the new custom API.

Define the API endpoint and kind

First, define the new API group.
Next, create the API kind and plural.

1apiVersion: apiextensions.crossplane.io/v1
2kind: CompositeResourceDefinition
3metadata:
4  name: xdatabases.custom-api.example.org
5spec:
6  group: custom-api.example.org
7  names:
8    kind: XDatabase
9    plural: xdatabases
Note

The XRD group matches the composition apiVersion and the XRD kind matches the composition compositeTypeRef.kind.

1kind: Composition
2# Removed for brevity
3spec:
4  compositeTypeRef:
5    apiVersion: custom-api.example.org/v1alpha1
6    kind: XDatabase

Set the API version

In Kubernetes, all API endpoints have a version to show the stability of the API and track revisions.

Apply a version to the XRD with a versions.name. This matches the compositeTypeRef.apiVersion

XRDs require both versions.served and versions.referenceable.

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: CompositeResourceDefinition
 3metadata:
 4  name: xdatabases.custom-api.example.org
 5spec:
 6  group: custom-api.example.org
 7  names:
 8    kind: XDatabase
 9    plural: xdatabases
10  versions:
11  - name: v1alpha1
12    served: true
13    referenceable: true
Note
For more information on defining versions in Kubernetes read the API versioning section of the Kubernetes documentation.

Create the API schema

With an API endpoint named, now define the API schema, or what’s allowed inside the spec of the new Kubernetes object.

Note

Place the API schema under the version.name

The XRD type defines the next lines. They’re always the same.

openAPIV3Schema specifies how the schema gets validated.

Next, the entire API is an object with a property of spec.

The spec is also an object with properties.

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: CompositeResourceDefinition
 3# Removed for brevity
 4spec:
 5  # Removed for brevity
 6  versions:
 7  - name: v1alpha1
 8    schema:
 9      openAPIV3Schema:
10        type: object
11        properties:
12          spec:
13            type: object
14            properties:
Tip
For more information on the values allowed in a composite resource definition view its schema with kubectl explain xrd

Now, define the custom API. Your custom API continues under the last properties definition in the previous example.

This custom API has only one setting:

  • region - where to deploy the resources, a choice of “EU” or “US”

Users can’t change any other settings of the S3 bucket or DynamoDB instance.

Theregion is a string and can match the regular expression that’s oneOf EU or US.

This API requires the setting region.

 1# Removed for brevity
 2# schema.openAPIV3Schema.type.properties.spec
 3properties:
 4  region:
 5    type: string
 6    oneOf:
 7      - pattern: '^EU$'
 8      - pattern: '^US$'
 9required:
10  - region

Enable claims to the API

Tell this XRD to offer a claim by defining the claim API endpoint under the XRD spec.

Tip
Crossplane recommends a Claim kind match the Composite Resource (XR) kind, without the preceding X.
 1apiVersion: apiextensions.crossplane.io/v1
 2kind: CompositeResourceDefinition
 3# Removed for brevity
 4spec:
 5# Removed for brevity
 6  names:
 7    kind: XDatabase
 8    plural: xdatabases
 9  claimNames:
10    kind: Database
11    plural: databases
Note
The Claims section later in this guide discusses claims.

Apply the composite resource definition

Apply the complete XRD to your Kubernetes cluster.

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

Verify Kubernetes created the XRD with kubectl get xrd

1kubectl get xrd
2NAME                                ESTABLISHED   OFFERED   AGE
3xdatabases.custom-api.example.org   True          True      10s

Create a composite resource

Creating an XRD allows the creation composite resources.

A composite resource uses the custom API created in the XRD.

The XRD maps the composite resource values to the composition template and creates new managed resources.

Looking at part of the XRD:

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: CompositeResourceDefinition
 3# Removed for brevity
 4spec:
 5  group: custom-api.example.org
 6  names:
 7    kind: XDatabase
 8# Removed for brevity
 9      spec:
10        type: object
11        properties:
12          region:
13            type: string
14            oneOf:
15              - pattern: '^EU$'
16              - pattern: '^US$'

The XRD group becomes the composite resource apiVersion.

The XRD kind is the composite resource kind

The XRD API spec defines the composite resource spec.

The XRD properties section defines the options for the composite resource spec.

The one option is region and it can be either EU or US.

This composite resource uses region: US.

Apply the composite resource

Apply the composite resource to the Kubernetes cluster.

1cat <<EOF | kubectl apply -f -
2apiVersion: custom-api.example.org/v1alpha1
3kind: XDatabase
4metadata:
5  name: my-composite-resource
6spec: 
7  region: "US"
8EOF

Verify the composite resource

Verify Crossplane created the composite resource with kubectl get xdatabase

Tip
Use kubectl get <kind> to view a specific kind of composite resource.
View all composite resources with kubectl get composite.
1kubectl get xdatabase
2NAME                    SYNCED   READY   COMPOSITION          AGE
3my-composite-resource   True     True    dynamo-with-bucket   31s

Both SYNCED and READY are True when Crossplane created the AWS resources.

Now look at the S3 bucket and DynmoDB table managed resources with kubectl get bucket and kubectl get table.

Important
This guide uses Upbound AWS provider v0.27.0. AWS Provider v0.30.0 and later requires the full CRD name bucket.s3.aws.upbound.io instead of buckets.
1kubectl get bucket
2NAME                          READY   SYNCED   EXTERNAL-NAME                 AGE
3my-composite-resource-8b6tx   True    True     my-composite-resource-8b6tx   56s
1kubectl get table
2NAME                          READY   SYNCED   EXTERNAL-NAME                 AGE
3my-composite-resource-m6vk6   True    True     my-composite-resource-m6vk6   59s

The composite resource automatically generated both managed resources.

Using kubectl describe on a managed resource shows the Owner References is the composite resource.

1kubectl describe bucket | grep "Owner References" -A5
2  Owner References:
3    API Version:           custom-api.example.org/v1alpha1
4    Block Owner Deletion:  true
5    Controller:            true
6    Kind:                  XDatabase
7    Name:                  my-composite-resource

Each composite resource creates and owns a unique set of managed resources. If you create a second composite resource Crossplane creates a new S3 bucket and DynamoDB table.

1cat <<EOF | kubectl apply -f -
2apiVersion: custom-api.example.org/v1alpha1
3kind: XDatabase
4metadata:
5  name: my-second-composite-resource
6spec: 
7  region: "US"
8EOF

Again, use kubectl get xdatabase to view both composite resources.

1kubectl get xdatabase
2NAME                           SYNCED   READY   COMPOSITION          AGE
3my-composite-resource          True     True    dynamo-with-bucket   2m21s
4my-second-composite-resource   True     True    dynamo-with-bucket   42s

And see there are two bucket and two table managed resources.

1kubectl get bucket
2NAME                                 READY   SYNCED   EXTERNAL-NAME                        AGE
3my-composite-resource-8b6tx          True    True     my-composite-resource-8b6tx          2m57s
4my-second-composite-resource-z22lc   True    True     my-second-composite-resource-z22lc   78s
1kubectl get table
2NAME                                 READY   SYNCED   EXTERNAL-NAME                        AGE
3my-composite-resource-m6vk6          True    True     my-composite-resource-m6vk6          3m
4my-second-composite-resource-nsz6j   True    True     my-second-composite-resource-nsz6j   81s

Delete the composite resources

Because the composite resource is the Owner of the managed resources, when Crossplane deletes the composite resource, it also deletes the managed resources automatically.

Delete the new composite resource with kubectl delete xdatabase.

Tip
Delete a specific composite resource with kubectl delete <kind> or kubectl delete composite.

Delete the second composition

1kubectl delete xdatabase my-second-composite-resource
Note
There may a delay in deleting the managed resources. Crossplane is making API calls to AWS and waits for AWS to confirm they deleted the resources before updating the state in Kubernetes.

Now only one bucket and table exist.

1kubectl get bucket
2NAME                                 READY   SYNCED   EXTERNAL-NAME                        AGE
3my-composite-resource-8b6tx   True    True     my-composite-resource-8b6tx   7m34s
1kubectl get table
2NAME                                 READY   SYNCED   EXTERNAL-NAME                        AGE
3my-composite-resource-m6vk6   True    True     my-composite-resource-m6vk6   7m37s

Delete the other composite resource to remove the last bucket and table managed resources.

1kubectl delete xdatabase my-composite-resource

Composite resources are great for creating one or more related resources against a template, but all composite resources exist at the Kubernetes “cluster level.” There’s no isolation between composite resources. Crossplane uses claims to create resources with namespace isolation.

Create a claim

Claims, just like composite resources use the custom API defined in the XRD. Unlike a composite resource, Crossplane can create claims in a namespace.

Create a new Kubernetes namespace

Create a new namespace with kubectl create namespace.

1kubectl create namespace test

Look at the XRD to see the parameters for the claim. A claim uses the same group a composite resource uses but a different kind.

1apiVersion: apiextensions.crossplane.io/v1
2kind: CompositeResourceDefinition
3# Removed for brevity
4spec:
5# Removed for brevity
6  group: custom-api.example.org
7  claimNames:
8    kind: Database
9    plural: databases

Like the composite resource, create a new object with the custom-api.example.org API endpoint.

The XRD claimNames.kind defines the kind.

The spec uses the same API options as the composite resource.

Apply the claim

Apply the claim to your Kubernetes cluster.

1cat <<EOF | kubectl apply -f -
2apiVersion: custom-api.example.org/v1alpha1
3kind: Database
4metadata:
5  name: claimed-database
6  namespace: test
7spec:
8  region: "US"
9EOF

Verify the claim

Verify Crossplane created the claim with kubectl get database in the test namespace.

Tip
View claims with kubectl get <kind> or use kubectl get claim to view all claims.
1kubectl get database -n test
2NAME               SYNCED   READY   CONNECTION-SECRET   AGE
3claimed-database   True     True                        35s

When Crossplane creates a claim, a unique composite resource is also created. View the new composite resource with kubectl get xdatabase.

1kubectl get xdatabase
2NAME                     SYNCED   READY   COMPOSITION          AGE
3claimed-database-6xsgq   True     True    dynamo-with-bucket   46s

The composite resource exists at the “cluster scope” while the claim exists at the “namespace scope.”

Create a second namespace and a second claim.

 1kubectl create namespace test2
 2cat <<EOF | kubectl apply -f -
 3apiVersion: custom-api.example.org/v1alpha1
 4kind: Database
 5metadata:
 6  name: claimed-database
 7  namespace: test2
 8spec:
 9  region: "US"
10EOF

View the claims in all namespaces with kubectl get database -A

1kubectl get database -A
2NAMESPACE   NAME               SYNCED   READY   CONNECTION-SECRET   AGE
3test        claimed-database   True     True                        4m32s
4test2       claimed-database   True     True                        43s

Now look at the composite resources at the cluster scope.

1kubectl get xdatabase
2NAME                     SYNCED   READY   COMPOSITION          AGE
3claimed-database-6xsgq   True     True    dynamo-with-bucket   8m37s
4claimed-database-f54qv   True     True    dynamo-with-bucket   4m47s

Crossplane created a second composite resource for the second claim.

Looking at the S3 bucket and DynamoDB table shows two of each resource, one for each claim.

1kubectl get bucket
2NAME                           READY   SYNCED   EXTERNAL-NAME                  AGE
3claimed-database-6xsgq-l9d8z   True    True     claimed-database-6xsgq-l9d8z   9m18s
4claimed-database-f54qv-9542v   True    True     claimed-database-f54qv-9542v   5m28s
1kubectl get table
2NAME                           READY   SYNCED   EXTERNAL-NAME                  AGE
3claimed-database-6xsgq-nmxhs   True    True     claimed-database-6xsgq-nmxhs   11m
4claimed-database-f54qv-qrsdj   True    True     claimed-database-f54qv-qrsdj   7m24s

Delete the claims

Removing the claims removes the composite resources and the associated managed resources.

1kubectl delete database claimed-database -n test
2kubectl delete database claimed-database -n test2

Verify Crossplane removed all the managed resources.

1kubectl get bucket
2No resources found
1kubectl get table
2No resources found

Claims are powerful tools to give users resources in their own isolated namespace. But these examples haven’t shown how the custom API can change the settings defined in the composition. This composition patching applies the API settings when creating resources. Part 3 of this guide covers composition patches and making all this configuration portable in Crossplane packages.

Next steps