Consume MongoDB via Crossplane
This guide describes using the Services Toolkit to allow Tanzu Application Platform workloads to consume Azure MongoDB (via CosmosDB).
This particular topic makes use of Universal Crossplane (UXP) by UpBound to manage resources in Azure.
Prerequisites¶
- UpBound's Universal Crossplane installed
- Tanzu Application Platform (TAP) 1.3.0 or higher installed
- Tanzu CLI with TAP plugins
Note
To test the automated binding of external services to applications, you need TAP installed. In addition, you need the Services Toolkit(STK) resources (ClusterInstanceClass, Claim), created.
If you want to test the Crossplane bindings without TAP, skip the sections creating STK resources. Instead of creating a TAP workload, we have an example Deployment (the Kubernetes CR), The expected secret is hard coded in the section Test Claim Without TAP.
Create service instances that are compatible with Tanzu Application Platform¶
For the sake of creating a MongoDB instance, we need to construct the following resources:
- Account
- MongoDatabase
- MongoCollection
- Secret containing the connection details in a supported format (Service Bindings)
We also recommend, especially while testing or doing a PoC, creating a specific Resource Group for these resources.
Success
To instantiate the resources, we can either directly create the Crossplane instances, or we can use a Crossplane package that uses Crossplane's Compositions.
Once your path of choice has reached the point the resources are created, it will redirect you back here.
Once Resources Are Ready¶
Once we have the MongoDB instance and the appropriate secret (adhering to Service Bindings), we can create a (STK) Claim.
We create the following:
- ClusterInstanceClass
- Reader ClusterRole for Services Toolkit
- ResourceClaim
- TAP workload to test the database instance
Create a Cluster Instance Class for Azure MongoDB¶
We will create a generic class of bindable resources. So applications can be made aware of the specific name of the resources, or their bindable secret, to claim them.
echo "apiVersion: services.apps.tanzu.vmware.com/v1alpha1
kind: ClusterInstanceClass
metadata:
name: azure-mongodb
spec:
description:
short: azure mongodb
pool:
kind: Secret
group: \"\"
labelSelector:
matchLabels:
services.apps.tanzu.vmware.com/class: azure-mongodb
" > cluster-instance-class.yml
kubectl apply -f cluster-instance-class.yml
Services Toolkit Reader Cluster Role¶
We make sure the STK controller can read the secrets anywhere. Feel free to limit this role to specific named resources via the resourceNames
property.
echo "apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: stk-secret-reader
labels:
servicebinding.io/controller: \"true\"
rules:
- apiGroups:
- \"\"
resources:
- secrets
verbs:
- get
- list
- watch
" > reader-cluster-role.yml
kubectl apply -f reader-cluster-role.yml
Discover, Claim, and Bind to an Azure MongoDB instance¶
First, confirm we have claimable class instances.
export RESOURCE_NAMESPACE=${UNIQUE_NAME:-"default"}
export RESOURCE_NAME=
Hint
If you use the instance
path to create the resources, the RESOURCE_NAMESPACE
will be $UNIQUE_NAME
. Else, it will be default
.
If you used the instance
path, RESOURCE_NAME
is also $UNIQUE_NAME
, else if you use the package path, it is $INSTANCE_NAME
.
tanzu services claimable list --class azure-mongodb \
-n ${RESOURCE_NAMESPACE}
Which should yield:
NAME NAMESPACE KIND APIVERSION
trp-mongodb-docs-test-bindable trp-mongodb-docs-test Secret v1
We have to create the claim in the namespace of the consuming application. So set this variable to the namespace where you create TAP workloads.
export TAP_DEV_NAMESPACE=default
We then create the associated resource claim:
tanzu service claim create azure-mongodb-claim-01 \
--namespace ${TAP_DEV_NAMESPACE} \
--resource-name ${RESOURCE_NAME}-bindable \
--resource-namespace ${RESOURCE_NAMESPACE} \
--resource-kind Secret \
--resource-api-version v1
echo "apiVersion: services.apps.tanzu.vmware.com/v1alpha1
kind: ResourceClaim
metadata:
name: azure-mongodb-claim-01
namespace: ${TAP_DEV_NAMESPACE}
spec:
ref:
apiVersion: v1
kind: Secret
name: ${RESOURCE_NAME}-bindable
namespace: ${RESOURCE_NAMESPACE}
" > resource-claim.yml
kubectl apply -f resource-claim.yml
To verify your claim is ready, you run this command:
tanzu service claim list -o wide
Which should yield the following:
NAME READY REASON CLAIM REF
azure-mongodb-claim-01 True Ready services.apps.tanzu.vmware.com/v1alpha1:ResourceClaim:azure-mongodb-claim-01
You can now claim it with a TAP workload.
Test Claim With TAP Workload¶
We can now create a TAP workload that uses our resource claim.
The runtime will fail once or twice as it takes time for the secret with the connection details to be mounted into the container. So wait for deployment-0002
or 0003
.
tanzu apps workload create spring-boot-mongo-01 \
--namespace ${TAP_DEV_NAMESPACE} \
--git-repo https://github.com/joostvdg/spring-boot-mongo.git \
--git-branch main \
--type web \
--label app.kubernetes.io/part-of=spring-boot-mongo-01 \
--annotation autoscaling.knative.dev/minScale=1 \
--build-env BP_JVM_VERSION=17 \
--service-ref db=services.apps.tanzu.vmware.com/v1alpha1:ResourceClaim:azure-mongodb-claim-01 \
--yes
echo "apiVersion: carto.run/v1alpha1
kind: Workload
metadata:
labels:
app.kubernetes.io/part-of: spring-boot-mongo-01
apps.tanzu.vmware.com/workload-type: web
name: spring-boot-mongo-01
namespace: ${TAP_DEV_NAMESPACE}
spec:
build:
env:
- name: BP_JVM_VERSION
value: \"17\"
params:
- name: annotations
value:
autoscaling.knative.dev/minScale: \"1\"
serviceClaims:
- name: db
ref:
apiVersion: services.apps.tanzu.vmware.com/v1alpha1
kind: ResourceClaim
name: azure-mongodb-claim-01
source:
git:
ref:
branch: main
url: https://github.com/joostvdg/spring-boot-mongo.git
" > workload.yml
kubectl apply -f workload.yml
To see the logs:
tanzu apps workload tail spring-boot-mongo-01
To get the status:
tanzu apps workload get spring-boot-mongo-01
Tap creates the deployment when the build and config writer workflows are complete.
To see their pods:
kubectl get pod -l app.kubernetes.io/part-of=spring-boot-mongo-01
Then you can wait for the application to be ready via the kubectl
CLI.
kubectl wait --for=condition=ready \
pod -l app.kubernetes.io/component=run,app.kubernetes.io/part-of=spring-boot-mongo-01 \
--timeout=180s \
--namespace ${TAP_DEV_NAMESPACE}
Ensure there is only one deployment active:
kubectl get pod --namespace ${TAP_DEV_NAMESPACE} \
-l app.kubernetes.io/component=run,app.kubernetes.io/part-of=spring-boot-mongo-01
Which should list a single deployment with Ready 2/2:
NAME READY STATUS RESTARTS AGE
spring-boot-mongo-01-00002-deployment-8cd56bdc8-gb44n 2/2 Running 0 6m11s
We then collect the name of the **Pod**or copy it yourself from the command-line output.
POD_NAME=$(kubectl get pod \
--namespace ${TAP_DEV_NAMESPACE} \
-l app.kubernetes.io/component=run,app.kubernetes.io/part-of=spring-boot-mongo-01 \
-o jsonpath="{.items[0].metadata.name}")
Continue with testing the application
Test Claim Without TAP¶
Here's the same application we use as TAP workload, but with a hardcoded Deployment.
kubectl apply -f https://raw.githubusercontent.com/joostvdg/spring-boot-mongo/main/kubernetes/raw/deployment.yaml
As the Deployment is hard coded, it doesn't know the name of your bindable secret. So let's patch that; first, create a patch file.
echo "
---
spec:
template:
spec:
volumes:
- name: secret-volume
projected:
defaultMode: 420
sources:
- secret:
name: "${RESOURCE_NAME}-bindable"
" > spring-boot-mongo-deployment-patch.yml
And second, patch the deployment to update the secret name:
kubectl patch --type merge deploy spring-boot-mongo\
--patch-file='spring-boot-mongo-deployment-patch.yml' \
--namespace ${TAP_DEV_NAMESPACE}
We wait for the Pod to become Ready.
kubectl wait --for=condition=ready pod \
--namespace ${TAP_DEV_NAMESPACE} \
-l app.kubernetes.io/name=spring-boot-mongo \
--timeout=60s
Once that happens, we copy the Pod's name to test the application.
POD_NAME=$(kubectl get pod \
--namespace ${TAP_DEV_NAMESPACE} \
-l app.kubernetes.io/name=spring-boot-mongo \
-o jsonpath="{.items[0].metadata.name}")
Test Application¶
Use the kubectl
CLI to create a port forward.
kubectl port-forward pod/${POD_NAME} --namespace ${TAP_DEV_NAMESPACE} 8080
And then open another terminal to test the application:
curl -s "http://localhost:8080"
Which should return an empty list []
. Add a value that gets stored in the MongoDB instance.
curl --header "Content-Type: application/json" \
--request POST --data '{"name":"Alice"}' \
http://localhost:8080/create
Making another GET request should return the stored entry:
curl -s "http://localhost:8080"
Success
We have gone through all the steps. You can now clean up all the resources.
Cleanup¶
kubectl delete ResourceClaim azure-mongodb-claim-01 \
--namespace ${TAP_DEV_NAMESPACE}
kubectl delete workload spring-boot-mongo-01
kubectl delete deploy spring-boot-mongo || true
Once you have cleaned up the resources related to this guide, visit the specific sub-guides for their cleanup commands.
- Via Crossplane instances cleanup commands
- Via Crossplane package cleanup commands