GCP 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 GCP.

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 GCP resources. This custom API is a Composite Resource (XR) API.

Prerequisites

  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 GCP Provider.
1cat <<EOF | kubectl apply -f -
2apiVersion: pkg.crossplane.io/v1
3kind: Provider
4metadata:
5  name: upbound-provider-gcp
6spec:
7  package: xpkg.upbound.io/upbound/provider-gcp:v0.28.0
8EOF
  1. Create a file called gcp-credentials.json with your GCP service account JSON file.
Tip
The GCP documentation provides information on how to generate a service account JSON file.
  1. Create a Kubernetes secret from the GCP JSON file
1kubectl create secret \
2generic gcp-secret \
3-n crossplane-system \
4--from-file=creds=./gcp-credentials.json
  1. Create a ProviderConfig Include your GCP project ID in the ProviderConfig settings.
Tip
Find your GCP project ID from the project_id field of the gcp-credentials.json file.
 1cat <<EOF | kubectl apply -f -
 2apiVersion: gcp.upbound.io/v1beta1
 3kind: ProviderConfig
 4metadata:
 5  name: default
 6spec:
 7  projectID: 
 8  credentials:
 9    source: Secret
10    secretRef:
11      namespace: crossplane-system
12      name: gcp-secret
13      key: creds
14EOF

Create a composition

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

This sample composition creates a Pub/Sub instance and associated GCP storage bucket.

Note
This example comes from part of the GCP
Stream messages from Pub/Sub by using Dataflow guide.

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

Create a storage 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: storage.gcp.upbound.io/v1beta1
2kind: Bucket
3metadata:
4  name: crossplane-quickstart-bucket
5spec:
6  forProvider:
7    location: US
8  providerConfigRef:
9    name: default

Create a Pub/Sub topic resource

Next, define a Pub/Sub topic resource.

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

The GCP Provider defines the apiVersion and kind.

A Pub/Sub topic doesn’t have requirements but using messageStoragePolicy.allowedPersistenceRegions can keep messages stored in the same location as the storage bucket.

1apiVersion: pubsub.gcp.upbound.io/v1beta1
2kind: Topic
3metadata:
4  name: crossplane-quickstart-topic
5spec:
6  forProvider:
7    messageStoragePolicy:
8      - allowedPersistenceRegions: 
9        - "us-central1"
Note
Pub/Sub topic specifics are beyond the scope of this guide. Read the GCP Pub/Sub API reference 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: topic-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.

Note
Don’t include resource metadata under the base key.
 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  name: topic-with-bucket
 5spec:
 6  resources:
 7    - name: crossplane-quickstart-bucket
 8      base:
 9        apiVersion: storage.gcp.upbound.io/v1beta1
10        kind: Bucket
11        spec:
12          forProvider:
13            location: US
14    - name: crossplane-quickstart-topic
15      base:
16        apiVersion: pubsub.gcp.upbound.io/v1beta1
17        kind: Topic
18        spec:
19          forProvider:
20            messageStoragePolicy:
21              - allowedPersistenceRegions: 
22                - "us-central1"

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

A composition defines which 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: topic-with-bucket
 5spec:
 6  compositeTypeRef:
 7    apiVersion: custom-api.example.org/v1alpha1
 8    kind: XTopicBucket
 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 allows composite resources from the API group custom-api.example.org that are of kind: XTopicBucket to use this template to create resources. No other API group or kind can use this template.

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: topic-with-bucket
 6spec:
 7  compositeTypeRef:
 8    apiVersion: custom-api.example.org/v1alpha1
 9    kind: XTopicBucket
10  resources:
11    - name: crossplane-quickstart-bucket
12      base:
13        apiVersion: storage.gcp.upbound.io/v1beta1
14        kind: Bucket
15        spec:
16          forProvider:
17            location: US
18    - name: crossplane-quickstart-topic
19      base:
20        apiVersion: pubsub.gcp.upbound.io/v1beta1
21        kind: Topic
22        spec:
23          forProvider:
24            messageStoragePolicy:
25              - allowedPersistenceRegions: 
26                - "us-central1"
27EOF

Confirm the composition exists with kubectl get composition

1kubectl get composition
2NAME                AGE
3topic-with-bucket   8s

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 team.
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: xtopicbuckets.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: xtopicbuckets.custom-api.example.org
5spec:
6  group: custom-api.example.org
7  names:
8    kind: XTopicBucket
9    plural: xtopicbuckets
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: XTopicBucket

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: xtopicbuckets.custom-api.example.org
 5spec:
 6  group: custom-api.example.org
 7  names:
 8    kind: XTopicBucket
 9    plural: xtopicbuckets
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 one setting:

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

Users can’t change any other settings of the storage bucket or Pub/Sub topic.

Thelocation is a string and matches the regular expression that’s oneOf EU or US.

This API requires the setting location.

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

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 Definition (XRD) 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: XTopicBucket
 8    plural: xtopicbuckets
 9  claimNames:
10    kind: TopicBucket
11    plural: topicbuckets
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: xtopicbuckets.custom-api.example.org
 6spec:
 7  group: custom-api.example.org
 8  names:
 9    kind: XTopicBucket
10    plural: xtopicbuckets
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              location:
23                type: string
24                oneOf:
25                  - pattern: '^EU$'
26                  - pattern: '^US$'
27            required:
28              - location
29  claimNames:
30    kind: TopicBucket
31    plural: topicbuckets
32EOF

Verify Kubernetes created the XRD with kubectl get xrd

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

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: XTopicBucket
 8# Removed for brevity
 9      spec:
10        type: object
11        properties:
12          location:
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 location and it can be either EU or US.

This composite resource uses location: 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: XTopicBucket
4metadata:
5  name: my-composite-resource
6spec: 
7  location: "US"
8EOF

Verify the composite resource

Verify Crossplane created the composite resource with kubectl get xdatasetwithbucket

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

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

Now look at the GCP storage bucket and Pub/Sub topic managed resources with kubectl get bucket and kubectl get topic.

1kubectl get bucket
2NAME                          READY   SYNCED   EXTERNAL-NAME                 AGE
3my-composite-resource-m6lbx   True    True     my-composite-resource-m6lbx   4m34s
1kubectl get topics
2NAME                          READY   SYNCED   EXTERNAL-NAME                 AGE
3my-composite-resource-88vzp   True    True     my-composite-resource-88vzp   4m48s

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:                  XTopicBucket
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 storage bucket and Pub/Sub topic.

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

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

1kubectl get XTopicBucket
2NAME                           SYNCED   READY   COMPOSITION         AGE
3my-composite-resource          True     True    topic-with-bucket   8m41s
4my-second-composite-resource   True     True    topic-with-bucket   2m4s

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

1kubectl get bucket
2NAME                                 READY   SYNCED   EXTERNAL-NAME                        AGE
3my-composite-resource-m6lbx          True    True     my-composite-resource-m6lbx          9m18s
4my-second-composite-resource-rkhbd   True    True     my-second-composite-resource-rkhbd   2m41s
1kubectl get topic
2NAME                                 READY   SYNCED   EXTERNAL-NAME                        AGE
3my-composite-resource-88vzp          True    True     my-composite-resource-88vzp          9m31s
4my-second-composite-resource-4wv89   True    True     my-second-composite-resource-4wv89   2m54s

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 XTopicBucket.

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

Delete the second composition

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

Now a single bucket and topic exist.

1kubectl get bucket
2NAME                          READY   SYNCED   EXTERNAL-NAME                 AGE
3my-composite-resource-m6lbx   True    True     my-composite-resource-m6lbx   11m
1kubectl get topic
2NAME                          READY   SYNCED   EXTERNAL-NAME                 AGE
3my-composite-resource-88vzp   True    True     my-composite-resource-88vzp   11m

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

1kubectl delete xtopicbucket 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: TopicBucket
9    plural: topicbuckets

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: TopicBucket
4metadata:
5  name: claimed-topic-with-bucket
6  namespace: test
7spec:
8  location: "US"
9EOF

Verify the claim

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

Tip
View claims with kubectl get <kind> or use kubectl get claim to view all claims.
1kubectl get TopicBucket -n test
2NAME                        SYNCED   READY   CONNECTION-SECRET   AGE
3claimed-topic-with-bucket   True     True                        4m37s

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

1kubectl get xtopicbucket
2NAME                              SYNCED   READY   COMPOSITION         AGE
3claimed-topic-with-bucket-7k2lj   True     True    topic-with-bucket   4m58s

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: TopicBucket
 5metadata:
 6  name: second-claimed-topic-with-bucket
 7  namespace: test2
 8spec:
 9  location: "US"
10EOF

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

1kubectl get topicbucket -A
2NAMESPACE   NAME                               SYNCED   READY   CONNECTION-SECRET   AGE
3test        claimed-topic-with-bucket          True     True                        8m48s
4test2       second-claimed-topic-with-bucket   True     True                        2m24s

Now look at the composite resources at the cluster scope.

1kubectl get xtopicbucket
2NAME                                     SYNCED   READY   COMPOSITION         AGE
3claimed-topic-with-bucket-7k2lj          True     True    topic-with-bucket   9m11s
4second-claimed-topic-with-bucket-d5x58   True     True    topic-with-bucket   2m47s

Crossplane created a second composite resource for the second claim.

Looking at the GCP storage bucket and Pub/Sub topic shows two of each resource, one for each claim.

1kubectl get bucket
2NAME                                           READY   SYNCED   EXTERNAL-NAME                                  AGE
3claimed-topic-with-bucket-7k2lj-qf2m6          True    True     claimed-topic-with-bucket-7k2lj-qf2m6          9m46s
4second-claimed-topic-with-bucket-d5x58-drlxr   True    True     second-claimed-topic-with-bucket-d5x58-drlxr   3m22s
1kubectl get topic
2NAME                                           READY   SYNCED   EXTERNAL-NAME                                  AGE
3claimed-topic-with-bucket-7k2lj-8xn7t          True    True     claimed-topic-with-bucket-7k2lj-8xn7t          9m59s
4second-claimed-topic-with-bucket-d5x58-ctkrp   True    True     second-claimed-topic-with-bucket-d5x58-ctkrp   3m35s

Delete the claims

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

1kubectl delete topicbucket claimed-topic-with-bucket -n test
2kubectl delete topicbucket second-claimed-topic-with-bucket -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