27 KiB
title | weight | tocHidden |
---|---|---|
AWS Quickstart Part 2 | 120 | true |
{{< hint "important" >}} This guide is part 2 of a series. Follow [part 1]({{<ref "provider-aws" >}}) to install Crossplane and connect your Kubernetes cluster to AWS.
[Part 3]({{<ref "provider-aws-part-3">}}) covers patching composite resources and using Crossplane packages. {{< /hint >}}
This section creates a Composition, Custom Resource Definition and a Claim to create a custom Kubernetes API to create AWS resources.
Prerequisites
- Complete [quickstart part 1]({{<ref "provider-aws" >}}) connecting Kubernetes to AWS.
- an AWS account with permissions to create an AWS S3 storage bucket and a DynamoDB instance
{{<expand "Skip part 1 and just get started" >}}
- 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
- When the Crossplane pods finish installing and are ready, apply the AWS Provider
cat <<EOF | kubectl apply -f -
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: upbound-provider-aws
spec:
package: xpkg.upbound.io/upbound/provider-aws:v0.27.0
EOF
- Create a file with your AWS keys
[default]
aws_access_key_id = <aws_access_key>
aws_secret_access_key = <aws_secret_key>
- Create a Kubernetes secret from the AWS keys
kubectl create secret \
generic aws-secret \
-n crossplane-system \
--from-file=creds=./aws-credentials.txt
- Create a ProviderConfig
cat <<EOF | kubectl apply -f -
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: aws-secret
key: creds
EOF
{{}}
Create a composition
[Part 1]({{<ref "provider-aws" >}}) created a single managed resource. A Composition is a template to create multiple managed resources at the same time.
This sample composition creates an DynamoDB instance and associated S3 storage bucket.
{{< hint "note" >}} This example comes from the AWS recommendation for storing large DynamoDB attributes in S3. {{< /hint >}}
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:
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
name: crossplane-quickstart-bucket
spec:
forProvider:
region: "us-east-2"
providerConfigRef:
name: default
Create a DynamoDB table resource
Next, define a DynamoDB table
resource.
{{< hint "tip" >}}
The Upbound Marketplace provides
schema documentation for a Table
resource.
{{< /hint >}}
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"
apiVersion: dynamodb.aws.upbound.io/v1beta1
kind: Table
metadata:
name: crossplane-quickstart-database
spec:
forProvider:
region: "us-east-2"
writeCapacity: 1
readCapacity: 1
attribute:
- name: S3ID
type: S
hashKey: S3ID
{{< hint "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{{</ hover>}} comes from the {{}}Crossplane{{</ hover>}} API resources.
Create any {{}}name{{</ hover>}} for this composition.
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: dynamoDBWithS3
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.
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: dynamoDBWithS3
spec:
resources:
- name: s3Bucket
base:
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
name: crossplane-quickstart-bucket
spec:
forProvider:
region: "us-east-2"
providerConfigRef:
name: default
- name: dynamoDB
base:
apiVersion: dynamodb.aws.upbound.io/v1beta1
kind: Table
metadata:
name: crossplane-quickstart-database
spec:
forProvider:
region: "us-east-2"
writeCapacity: 1
readCapacity: 1
attribute:
- name: S3ID
type: S
hashKey: S3ID
Put the entire resource definition including the {{}}apiVersion{{</ hover>}} and resource settings under the {{}}base{{</ hover>}}.
Compositions are only 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{{</ hover>}} definition.
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: dynamodb-with-bucket
spec:
compositeTypeRef:
apiVersion: custom-api.example.org/v1alpha1
kind: database
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 only allows composite resources from the API group {{}}custom-api.example.org{{</ hover>}} that are of {{}}kind: database{{</ hover>}} to use this template to create resources.
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: dynamo-with-bucket
spec:
compositeTypeRef:
apiVersion: custom-api.example.org/v1alpha1
kind: database
resources:
- name: s3Bucket
base:
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
name: crossplane-quickstart-bucket
spec:
forProvider:
region: us-east-2
providerConfigRef:
name: default
- name: dynamoDB
base:
apiVersion: dynamodb.aws.upbound.io/v1beta1
kind: Table
metadata:
name: crossplane-quickstart-database
spec:
forProvider:
region: "us-east-2"
writeCapacity: 1
readCapacity: 1
attribute:
- name: S3ID
type: S
hashKey: S3ID
EOF
Confirm the composition exists with kubectl get composition
kubectl get composition
NAME AGE
dynamo-with-bucket 22s
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.
{{< 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.
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: databases.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: databases.custom-api.example.org
spec:
group: custom-api.example.org
names:
kind: database
plural: databases
{{<hint "note" >}} The XRD {{}}group{{}} matches the composition {{}}apiVersion{{}} and the XRD {{}}kind{{}} matches the composition {{}}kind{{}} under the {{}}compositeTypeRef{{}}.
kind: Composition
# Removed for brevity
spec:
compositeTypeRef:
apiVersion: custom-api.example.org/v1alpha1
kind: database
{{< /hint >}}
Set the API version
In Kubernetes, all API endpoints have a version to tell the stability of the API and track revisions.
Apply a version to the XRD with a {{}}versions.name{{}}. This matches the {{}}apiVersion{{}} used in the composition's {{}}compositeTypeRef{{}}.
XRDs require both {{}}versions.served{{}} and {{}}versions.referenceable{{}}.
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: custom-api-definition
spec:
group: custom-api.example.org
names:
kind: database
plural: databases
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" >}}
An XRD is a Kubernetes custom resource definition.
For more information on the values allowed in the XRD view the XRD object with
kubectl describe crd compositeresourcedefinitions
{{< /hint >}}
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.
The{{}}region{{}} is a {{}}string{{}} and can match the regular expression that's {{}}oneOf{{}} {{}}EU{{}} or {{}}US{{}}.
This API requires the setting {{}}region{{}}.
# Removed for brevity
# schema.openAPIV3Schema.type.properties.spec
properties:
region:
type: string
oneOf:
- pattern: '^EU$'
- pattern: '^US$'
required:
- region
Enable claims to the API
Allow a claim to use this XRD by defining the claim API endpoint under the XRD {{}}spec{{< /hover >}}.
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
# Removed for brevity
spec:
# Removed for brevity
claimNames:
kind: customDatabase
plural: customDatabases
{{<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: databases.custom-api.example.org
spec:
group: custom-api.example.org
names:
kind: database
plural: databases
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
region:
type: string
oneOf:
- pattern: '^EU$'
- pattern: '^US$'
required:
- region
claimNames:
kind: custom-database
plural: custom-databases
EOF
Verify Kubernetes created the XRD with kubectl get xrd
kubectl get xrd
NAME ESTABLISHED OFFERED AGE
databases.custom-api.example.org True True 9s
Create a composite resource
Creating an XRD allows the creation composite resources.
Composite resources are a convenient way to create multiple resources with a standard template.
A composite resource uses the custom API created in the XRD.
Looking at part of the XRD:
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
# Removed for brevity
spec:
group: custom-api.example.org
names:
kind: database
# Removed for brevity
spec:
type: object
properties:
region:
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 {{}}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.
cat <<EOF | kubectl apply -f -
apiVersion: custom-api.example.org/v1alpha1
kind: database
metadata:
name: my-composite-resource
spec:
region: "US"
EOF
Verify the composite resource
Verify Crossplane created the composite resource with kubectl get composite
kubectl get composite
NAME SYNCED READY COMPOSITION AGE
my-composite-resource True True dynamo-with-bucket 31s
The output mentions the composite template that the composite resource used.
Now look at the S3 bucket
and DynmoDB table
managed resources with
kubectl get bucket
and kubectl get table
.
kubectl get bucket
NAME READY SYNCED EXTERNAL-NAME AGE
my-composite-resource-8b6tx True True my-composite-resource-8b6tx 56s
kubectl get table
NAME READY SYNCED EXTERNAL-NAME AGE
my-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.
kubectl describe bucket | grep "Owner References" -A5
Owner References:
API Version: custom-api.example.org/v1alpha1
Block Owner Deletion: true
Controller: true
Kind: database
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
.
cat <<EOF | kubectl apply -f -
apiVersion: custom-api.example.org/v1alpha1
kind: database
metadata:
name: my-second-composite-resource
spec:
region: "US"
EOF
Again, use kubectl get composite
to view both composite resources.
kubectl get composite
NAME SYNCED READY COMPOSITION AGE
my-composite-resource True True dynamo-with-bucket 2m21s
my-second-composite-resource True True dynamo-with-bucket 42s
And see there are two bucket
and two table
managed resources.
kubectl get bucket
NAME READY SYNCED EXTERNAL-NAME AGE
my-composite-resource-8b6tx True True my-composite-resource-8b6tx 2m57s
my-second-composite-resource-z22lc True True my-second-composite-resource-z22lc 78s
kubectl get table
NAME READY SYNCED EXTERNAL-NAME AGE
my-composite-resource-m6vk6 True True my-composite-resource-m6vk6 3m
my-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 composite
.
kubectl delete composite my-second-composite-resource
{{<hint "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.
kubectl get bucket
NAME READY SYNCED EXTERNAL-NAME AGE
my-composite-resource-8b6tx True True my-composite-resource-8b6tx 7m34s
kubectl get table
NAME READY SYNCED EXTERNAL-NAME AGE
my-composite-resource-m6vk6 True True my-composite-resource-m6vk6 7m37s
Delete the second composite resource to remove the last bucket
and table
managed resources.
kubectl delete composite my-composite-resource
Composite resources are great for creating multiple 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
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: custom-database
plural: custom-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.
cat <<EOF | kubectl apply -f -
apiVersion: custom-api.example.org/v1alpha1
kind: custom-database
metadata:
name: claimed-database
namespace: test
spec:
region: "US"
EOF
Verify the claim
Verify Crossplane created the claim with kubectl get claim
in the test
namespace.
kubectl get claim -n test
NAME SYNCED READY CONNECTION-SECRET AGE
claimed-database True True 35s
When Crossplane creates a claim a unique composite resource is automatically
created too. View the new composite resource with kubectl get composite
.
kubectl get composite
NAME SYNCED READY COMPOSITION AGE
claimed-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.
kubectl create namespace test2
cat <<EOF | kubectl apply -f -
apiVersion: custom-api.example.org/v1alpha1
kind: custom-database
metadata:
name: claimed-database
namespace: test2
spec:
region: "US"
EOF
View the claims in all namespaces with kubectl get claim -A
kubectl get claim -A
NAMESPACE NAME SYNCED READY CONNECTION-SECRET AGE
test claimed-database True True 4m32s
test2 claimed-database True True 43s
Now look at the composite resources at the cluster scope.
kubectl get composite
NAME SYNCED READY COMPOSITION AGE
claimed-database-6xsgq True True dynamo-with-bucket 8m37s
claimed-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.
kubectl get bucket
NAME READY SYNCED EXTERNAL-NAME AGE
claimed-database-6xsgq-l9d8z True True claimed-database-6xsgq-l9d8z 9m18s
claimed-database-f54qv-9542v True True claimed-database-f54qv-9542v 5m28s
kubectl get table
NAME READY SYNCED EXTERNAL-NAME AGE
claimed-database-6xsgq-nmxhs True True claimed-database-6xsgq-nmxhs 11m
claimed-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.
kubectl delete claim claimed-database -n test
kubectl delete claim claimed-database -n test2
Verify Crossplane removed all the managed resources.
kubectl get bucket
kubectl get table
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-aws-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-aws-part-3">}}) to create a learn about patching resources and creating Crossplane packages.
- Explore AWS resources that Crossplane can configure in the Provider CRD reference.
- Join the Crossplane Slack and connect with Crossplane users and contributors.