docs/content/v1.11/getting-started/provider-gcp-part-2.md

29 KiB

title weight tocHidden
GCP Quickstart Part 2 120 true

{{< hint "important" >}} This guide is part 2 of a series. Follow [part 1]({{<ref "provider-gcp" >}}) to install Crossplane and connect your Kubernetes cluster to GCP.

[Part 3]({{<ref "provider-gcp-part-3">}}) covers patching composite resources and using Crossplane packages. {{< /hint >}}

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

  • Complete [quickstart part 1]({{<ref "provider-gcp" >}}) connecting Kubernetes to GCP.
  • a GCP account with permissions to create a GCP storage bucket and a Pub/Sub topic.

{{<expand "Skip part 1 and just get started" >}}

  1. Add the Crossplane Helm repository and install Crossplane.
helm repo add \
crossplane-stable https://charts.crossplane.io/stable
helm repo update
&&
helm install crossplane \
crossplane-stable/crossplane \
--namespace crossplane-system \
--create-namespace
  1. When the Crossplane pods finish installing and are ready, apply the GCP Provider.
cat <<EOF | kubectl apply -f -
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: upbound-provider-gcp
spec:
  package: xpkg.upbound.io/upbound/provider-gcp:v0.28.0
EOF
  1. Create a file called gcp-credentials.json with your GCP service account JSON file.

{{< hint type="tip" >}} The GCP documentation provides information on how to generate a service account JSON file. {{< /hint >}}

  1. Create a Kubernetes secret from the GCP JSON file
kubectl create secret \
generic gcp-secret \
-n crossplane-system \
--from-file=creds=./gcp-credentials.json
  1. Create a ProviderConfig Include your {{< hover label="providerconfig" line="7" >}}GCP project ID{{< /hover >}} in the ProviderConfig settings.

{{< hint type="warning" >}} Find your GCP project ID from the project_id field of the gcp-credentials.json file. {{< /hint >}}

{{< editCode >}}

cat <<EOF | kubectl apply -f -
apiVersion: gcp.upbound.io/v1beta1
kind: ProviderConfig
metadata:
  name: default
spec:
  projectID: $@<PROJECT_ID>$@
  credentials:
    source: Secret
    secretRef:
      namespace: crossplane-system
      name: gcp-secret
      key: creds
EOF

{{< /editCode >}}

{{}}

Create a composition

[Part 1]({{<ref "provider-gcp" >}}) 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.

{{< hint "note" >}} This example comes from part of the GCP
Stream messages from Pub/Sub by using Dataflow guide. {{< /hint >}}

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:

{{< hint "note" >}} Don't apply this configuration. This YAML is part of a larger definition. {{< /hint >}}

apiVersion: storage.gcp.upbound.io/v1beta1
kind: Bucket
metadata:
  name: crossplane-quickstart-bucket
spec:
  forProvider:
    location: US
  providerConfigRef:
    name: default

Create a Pub/Sub topic resource

Next, define a Pub/Sub topic resource.

{{< hint "tip" >}} The Upbound Marketplace provides schema documentation for a topic resource. {{< /hint >}}

The GCP Provider defines the {{}}apiVersion{{}} and {{}}kind{{}}.

A Pub/Sub topic doesn't have requirements but using {{}}messageStoragePolicy.allowedPersistenceRegions{{< /hover >}} can keep messages stored in the same location as the storage bucket.

apiVersion: pubsub.gcp.upbound.io/v1beta1
kind: Topic
metadata:
  name: crossplane-quickstart-topic
spec:
  forProvider:
    messageStoragePolicy:
      - allowedPersistenceRegions: 
        - "us-central1"

{{< hint "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{{</ hover>}} comes from the {{}}Crossplane{{</ hover>}} API resources.

Create any {{}}name{{</ hover>}} for this composition.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: topic-with-bucket

Add the resources to the {{}}spec.resources{{</ hover>}} section of the composition.

Give each resource a {{}}name{{</ hover>}} and put the resource definition under the {{}}base{{</ hover>}} key.

{{< hint "note" >}} Don't include resource metadata under the {{}}base{{</ hover>}} key. {{< /hint >}}

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: topic-with-bucket
spec:
  resources:
    - name: crossplane-quickstart-bucket
      base:
        apiVersion: storage.gcp.upbound.io/v1beta1
        kind: Bucket
        spec:
          forProvider:
            location: US
    - name: crossplane-quickstart-topic
      base:
        apiVersion: pubsub.gcp.upbound.io/v1beta1
        kind: Topic
        spec:
          forProvider:
            messageStoragePolicy:
              - allowedPersistenceRegions: 
                - "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{{</ hover>}} definition.

{{< hint "tip" >}} Crossplane recommends prefacing the kind with an X to show it's a Composition. {{< /hint >}}

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: topic-with-bucket
spec:
  compositeTypeRef:
    apiVersion: custom-api.example.org/v1alpha1
    kind: XTopicBucket
  resources:
    # 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{{</ hover>}} Crossplane allows composite resources from the API group {{}}custom-api.example.org{{</ hover>}} that are of {{}}kind: XTopicBucket{{</ hover>}} 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.

cat <<EOF | kubectl apply -f -
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: topic-with-bucket
spec:
  compositeTypeRef:
    apiVersion: custom-api.example.org/v1alpha1
    kind: XTopicBucket
  resources:
    - name: crossplane-quickstart-bucket
      base:
        apiVersion: storage.gcp.upbound.io/v1beta1
        kind: Bucket
        spec:
          forProvider:
            location: US
    - name: crossplane-quickstart-topic
      base:
        apiVersion: pubsub.gcp.upbound.io/v1beta1
        kind: Topic
        spec:
          forProvider:
            messageStoragePolicy:
              - allowedPersistenceRegions: 
                - "us-central1"
EOF

Confirm the composition exists with kubectl get composition

kubectl get composition
NAME                AGE
topic-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.

{{< hint "tip" >}} Composite resource definitions are also called XRDs for short. {{< /hint >}}

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

The XRD {{}}name{{}} is the new API endpoint.

{{< hint "tip" >}} Crossplane recommends using a plural name for the XRD {{}}name{{}}. {{< /hint >}}

apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  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{{}}.

apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: xtopicbuckets.custom-api.example.org
spec:
  group: custom-api.example.org
  names:
    kind: XTopicBucket
    plural: xtopicbuckets

{{<hint "note" >}} The XRD {{}}group{{}} matches the composition {{}}apiVersion{{}} and the XRD {{}}kind{{}} matches the composition {{}}compositeTypeRef.kind{{}}.

kind: Composition
# Removed for brevity
spec:
  compositeTypeRef:
    apiVersion: custom-api.example.org/v1alpha1
    kind: XTopicBucket

{{< /hint >}}

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 {{<hover label="noteComp"line="5">}}compositeTypeRef.apiVersion{{}}

XRDs require both {{}}versions.served{{}} and {{}}versions.referenceable{{}}.

apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: xtopicbuckets.custom-api.example.org
spec:
  group: custom-api.example.org
  names:
    kind: XTopicBucket
    plural: xtopicbuckets
  versions:
  - name: v1alpha1
    served: true
    referenceable: true

{{<hint "note" >}} For more information on defining versions in Kubernetes read the API versioning section of the Kubernetes documentation. {{< /hint >}}

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.

{{< hint "note" >}} XRDs follow the Kubernetes custom resource definition rules for schemas. {{}}

Place the API {{< hover label="xrdSchema" line="8" >}}schema{{}} under the {{< hover label="xrdSchema" line="7" >}}version.name{{}}

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

{{< hover label="xrdSchema" line="9" >}}openAPIV3Schema{{}} specifies how the schema gets validated.

Next, the entire API is an {{< hover label="xrdSchema" line="10" >}}object{{}} with a {{< hover label="xrdSchema" line="11" >}}property{{}} of {{< hover label="xrdSchema" line="12" >}}spec{{}}.

The {{< hover label="xrdSchema" line="12" >}}spec{{}} is also an {{< hover label="xrdSchema" line="13" >}}object{{}} with {{< hover label="xrdSchema" line="14" >}}properties{{}}.

apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
# Removed for brevity
spec:
  # Removed for brevity
  versions:
  - name: v1alpha1
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:

{{< hint "tip" >}} For more information on the values allowed in a composite resource definition view its schema with kubectl explain xrd {{< /hint >}}

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.

The{{}}location{{}} is a {{}}string{{}} and matches the regular expression that's {{}}oneOf{{}} {{}}EU{{}} or {{}}US{{}}.

This API requires the setting {{}}location{{}}.

# Removed for brevity
# schema.openAPIV3Schema.type.properties.spec
properties:
  location:
    type: string
    oneOf:
      - pattern: '^EU$'
      - pattern: '^US$'
required:
  - location

Enable claims to the API

Tell this XRD to offer a claim by defining the claim API endpoint under the XRD {{}}spec{{< /hover >}}.

{{< hint "tip" >}} Crossplane recommends a Claim {{}}kind{{}} match the Composite Resource Definition (XRD) {{}}kind{{</ hover>}}, without the preceding X. {{< /hint >}}

apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
# Removed for brevity
spec:
# Removed for brevity
  names:
    kind: XTopicBucket
    plural: xtopicbuckets
  claimNames:
    kind: TopicBucket
    plural: topicbuckets

{{<hint "note" >}} The Claims section later in this guide discusses claims. {{< /hint >}}

Apply the composite resource definition

Apply the complete XRD to your Kubernetes cluster.

cat <<EOF | kubectl apply -f -
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: xtopicbuckets.custom-api.example.org
spec:
  group: custom-api.example.org
  names:
    kind: XTopicBucket
    plural: xtopicbuckets
  versions:
  - name: v1alpha1
    served: true
    referenceable: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              location:
                type: string
                oneOf:
                  - pattern: '^EU$'
                  - pattern: '^US$'
            required:
              - location
  claimNames:
    kind: TopicBucket
    plural: topicbuckets
EOF

Verify Kubernetes created the XRD with kubectl get xrd

kubectl get xrd
NAME                                   ESTABLISHED   OFFERED   AGE
xtopicbuckets.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:

apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
# Removed for brevity
spec:
  group: custom-api.example.org
  names:
    kind: XTopicBucket
# Removed for brevity
      spec:
        type: object
        properties:
          location:
            type: string
            oneOf:
              - pattern: '^EU$'
              - 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.

cat <<EOF | kubectl apply -f -
apiVersion: custom-api.example.org/v1alpha1
kind: XTopicBucket
metadata:
  name: my-composite-resource
spec: 
  location: "US"
EOF

Verify the composite resource

Verify Crossplane created the composite resource with kubectl get xdatasetwithbucket

{{<hint "tip" >}} Use kubectl get <composite resource kind> to view a specific kind of composite resource.
View all composite resources with kubectl get composite. {{< /hint >}}

kubectl get XTopicBucket
NAME                    SYNCED   READY   COMPOSITION         AGE
my-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.

kubectl get bucket
NAME                          READY   SYNCED   EXTERNAL-NAME                 AGE
my-composite-resource-m6lbx   True    True     my-composite-resource-m6lbx   4m34s
kubectl get topics
NAME                          READY   SYNCED   EXTERNAL-NAME                 AGE
my-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.

kubectl describe bucket | grep "Owner References" -A5
  Owner References:
    API Version:           custom-api.example.org/v1alpha1
    Block Owner Deletion:  true
    Controller:            true
    Kind:                  XTopicBucket
    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.

cat <<EOF | kubectl apply -f -
apiVersion: custom-api.example.org/v1alpha1
kind: XTopicBucket
metadata:
  name: my-second-composite-resource
spec: 
  location: "US"
EOF

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

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

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

kubectl get bucket
NAME                                 READY   SYNCED   EXTERNAL-NAME                        AGE
my-composite-resource-m6lbx          True    True     my-composite-resource-m6lbx          9m18s
my-second-composite-resource-rkhbd   True    True     my-second-composite-resource-rkhbd   2m41s
kubectl get topic
NAME                                 READY   SYNCED   EXTERNAL-NAME                        AGE
my-composite-resource-88vzp          True    True     my-composite-resource-88vzp          9m31s
my-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.

{{<hint "tip" >}} Delete a specific composite resource with kubectl delete <composite kind> <name> or kubectl delete composite <name>. {{< /hint >}}

Delete the second composition

kubectl delete XTopicBucket my-second-composite-resource

{{<hint "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.

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

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

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

kubectl 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{{}}.

apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
# Removed for brevity
spec:
# Removed for brevity
  group: custom-api.example.org
  claimNames:
    kind: TopicBucket
    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.

cat <<EOF | kubectl apply -f -
apiVersion: custom-api.example.org/v1alpha1
kind: TopicBucket
metadata:
  name: claimed-topic-with-bucket
  namespace: test
spec:
  location: "US"
EOF

Verify the claim

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

{{<hint "tip" >}} View claims with kubectl get <kind> or use kubectl get claim to view all claims. {{}}

kubectl get TopicBucket -n test
NAME                        SYNCED   READY   CONNECTION-SECRET   AGE
claimed-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.

kubectl get xtopicbucket
NAME                              SYNCED   READY   COMPOSITION         AGE
claimed-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.

kubectl create namespace test2
cat <<EOF | kubectl apply -f -
apiVersion: custom-api.example.org/v1alpha1
kind: TopicBucket
metadata:
  name: second-claimed-topic-with-bucket
  namespace: test2
spec:
  location: "US"
EOF

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

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

Now look at the composite resources at the cluster scope.

kubectl get xtopicbucket
NAME                                     SYNCED   READY   COMPOSITION         AGE
claimed-topic-with-bucket-7k2lj          True     True    topic-with-bucket   9m11s
second-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.

kubectl get bucket
NAME                                           READY   SYNCED   EXTERNAL-NAME                                  AGE
claimed-topic-with-bucket-7k2lj-qf2m6          True    True     claimed-topic-with-bucket-7k2lj-qf2m6          9m46s
second-claimed-topic-with-bucket-d5x58-drlxr   True    True     second-claimed-topic-with-bucket-d5x58-drlxr   3m22s
kubectl get topic
NAME                                           READY   SYNCED   EXTERNAL-NAME                                  AGE
claimed-topic-with-bucket-7k2lj-8xn7t          True    True     claimed-topic-with-bucket-7k2lj-8xn7t          9m59s
second-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.

kubectl delete topicbucket claimed-topic-with-bucket -n test
kubectl delete topicbucket second-claimed-topic-with-bucket -n test2

Verify Crossplane removed all the managed resources.

kubectl get bucket
No resources found
kubectl get table
No 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]({{< ref "provider-gcp-part-3">}}) of this guide covers composition patches and making all this configuration portable in Crossplane packages.

Next steps

  • [Continue to part 3]({{< ref "provider-gcp-part-3">}}) to create a learn about patching resources and creating Crossplane packages.
  • Explore GCP resources that Crossplane can configure in the Provider CRD reference.
  • Join the Crossplane Slack and connect with Crossplane users and contributors.