Consuming Azure Flexible Server for PostgreSQL on Tanzu Application Platform with Crossplane

Introduction

This topic demonstrates how to use Services Toolkit to allow Tanzu Application Platform workloads to consume Azure Flexible Server for PostgreSQL. This particular topic makes use of Crossplane to manage those Flexible Server for PostgreSQL instances. As such, it can be thought of as an alternative approach to Consuming Azure Flexible Server for PostgreSQL on Tanzu Application Platform with Azure Service Operator (ASO) to achieve the similar outcomes.

Note This usecase is not currently compatible with TAP air-gapped installations.

Prerequisites

Meet these prerequisites:

Note

: In this example we use an AKS Cluster to deploy Crossplane and Tanzu Application Platform too. However, any other cluster which supports running those two systems should suffice.

Install Crossplane

Note

: For the latest steps for installing Crossplane, see these instructions. For the instructions in this topic, it is important to enable support for external secret stores in Crossplane. This is currently an Alpha feature. As such, you will have to explicitly set command line flag --enable-external-secret-stores when starting the Crossplane controller.

Run the following commands to install Crossplane to your existing Kubernetes cluster:

kubectl create namespace crossplane-system

helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update

helm install crossplane --namespace crossplane-system crossplane-stable/crossplane \
  --set 'args={--enable-external-secret-stores}'

For this topic, you do not need to install the Crossplane CLI or any additional configuration package.

Install the Azure Provider for Crossplane

To install the Azure Provider for Crossplane, run:

kubectl apply -f - <<'EOF'
apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
  name: jet-azure-config
spec:
  image: crossplane/provider-jet-azure-controller:v0.12.0
  args: ["-d"]
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-jet-azure
spec:
  package: crossplane/provider-jet-azure:v0.12.0
  controllerConfigRef:
    name: jet-azure-config
EOF

After you have installed the provider, you see a new flexibleservers.dbforpostgresql.azure.jet.crossplane.io API resource available in your Kubernetes cluster. You can wait for the provider to become healthy by running:

kubectl -n crossplane-system wait provider/provider-jet-azure \
  --for=condition=Healthy=True --timeout=3m

Install the Kubernetes Provider for Crossplane

To install the Kubernetes Provider for Crossplane, run:

kubectl apply -f - <<'EOF'
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-kubernetes
spec:
  package: "crossplane/provider-kubernetes:main"
EOF

Configure the Azure Provider

This section creates a new Service Principal to be used by the Crossplane system to allow it to manage PostgreSQL Servers.

  1. Setup some configuration in the current shell session

    # Set the name of the Service Principal to be created
    AZURE_SP_NAME='sql-crossplane-demo'
    
    # Get the subscription ID
    AZURE_SUBSCRIPTION_ID="$( az account show -o json | jq -r '.id' )"
    
  2. Create a new Service Principal and set up the kubernetes secret

    kubectl create secret generic jet-azure-creds -o yaml --dry-run=client --from-literal=creds="$(
     az ad sp create-for-rbac -n "${AZURE_SP_NAME}" \
       --sdk-auth \
       --role "Contributor" \
       --scopes "/subscriptions/${AZURE_SUBSCRIPTION_ID}" \
       -o json
    )" | kubectl apply -n crossplane-system -f -
    
    Note

    : You’ll see the following warning

    Note

    WARNING: Option ‘–sdk-auth’ has been deprecated and will be removed in a future release.

    which you can ignore for now. There is some context about that in this issue for the Azure CLI and this issue for the Crossplane Azure Provider.

  3. Deploy a ProviderConfig which uses the previously created secret for the Azure crossplane provider

    kubectl apply -f - <<'EOF'
    apiVersion: azure.jet.crossplane.io/v1alpha1
    kind: ProviderConfig
    metadata:
     name: default
    spec:
     credentials:
       source: Secret
       secretRef:
         namespace: crossplane-system
         name: jet-azure-creds
         key: creds
    EOF
    

Configure the Kubernetes Provider

SA=$(kubectl -n crossplane-system get sa -o name | grep provider-kubernetes | sed -e 's|serviceaccount\/|crossplane-system:|g')
kubectl create role -n crossplane-system password-manager --resource=passwords.secretgen.k14s.io --verb=create,get,update,delete
kubectl create rolebinding -n crossplane-system provider-kubernetes-password-manager --role password-manager --serviceaccount="${SA}"

kubectl apply -f - <<'EOF'
apiVersion: kubernetes.crossplane.io/v1alpha1
kind: ProviderConfig
metadata:
  name: default
spec:
  credentials:
    source: InjectedIdentity
EOF

Define Composite Resource Types

Now that the Azure Provider for Crossplane has been installed and configured, create a new CompositeResourceDefinition (XRD) and corresponding Composition representing individual instances of Azure PostgreSQL Server. For more information about these concepts see the Crossplane Composition documentation.

<!– Removing this note, as there are not jet XRD examples on the upbound marketplace

Note

: Instead of creating your own custom XRD and Composition as shown below, you can also install an existing Crossplane configuration package for Azure that includes pre-configured XRDs and compositions for Azure PostgreSQL Servers. The primary reason for creating a new XRD and composition from scratch is to make sure the connection secrets for newly provisioned PostgreSQL Server instances support the Service Binding Specification for Kubernetes and automatic Spring Boot configuration using Spring Cloud Bindings.

–>

  1. Create a new XRD by running:

    kubectl apply -f - <<'EOF'
    apiVersion: apiextensions.crossplane.io/v1
    kind: CompositeResourceDefinition
    metadata:
     name: xpostgresqlinstances.bindable.database.example.org
    spec:
     claimNames:
       kind: PostgreSQLInstance
       plural: postgresqlinstances
     connectionSecretKeys:
     - type
     - provider
     - host
     - port
     - database
     - username
     - password
     group: bindable.database.example.org
     names:
       kind: XPostgreSQLInstance
       plural: xpostgresqlinstances
     versions:
     - name: v1alpha1
       referenceable: true
       schema:
         openAPIV3Schema:
           properties:
             spec:
               properties:
                 parameters:
                   properties:
                     storageGB:
                       type: integer
                   required:
                   - storageGB
                   type: object
               required:
               - parameters
               type: object
           type: object
       served: true
    EOF
    

    After the newly created XRD has been successfully reconciled, there are two new API resources available in your Kubernetes cluster, xpostgresqlinstances.bindable.database.example.org and postgresqlinstances.bindable.database.example.org.

  2. Create a corresponding composition (not in a production environment) by running:

    kubectl apply -f - <<'EOF'
    apiVersion: apiextensions.crossplane.io/v1
    kind: Composition
    metadata:
      labels:
        provider: azure
      name: xpostgresqlinstances.bindable.gcp.database.example.org
    spec:
      compositeTypeRef:
        apiVersion: bindable.database.example.org/v1alpha1
        kind: XPostgreSQLInstance
      publishConnectionDetailsWithStoreConfigRef:
        name: default
      resources:
      - name: dbinstance
        base:
          apiVersion: dbforpostgresql.azure.jet.crossplane.io/v1alpha2
          kind: FlexibleServer
          spec:
            forProvider:
              administratorLogin: myPgAdmin
              administratorPasswordSecretRef:
                name: ""
                namespace: crossplane-system
                key: password
              location: westeurope
              skuName: GP_Standard_D2s_v3
              version: "12" #! 11,12 and 13 are supported
              resourceGroupName: tap-psql-demo
            writeConnectionSecretToRef:
              namespace: crossplane-system
        connectionDetails:
        - name: type
          value: postgresql
        - name: provider
          value: azure
        - name: database
          value: postgres
        - name: username
          fromFieldPath: spec.forProvider.administratorLogin
        - name: password
          fromConnectionSecretKey: "attribute.administrator_password"
        - name: host
          fromFieldPath: status.atProvider.fqdn
        - name: port
          type: FromValue
          value: "5432"
        patches:
        - fromFieldPath: metadata.uid
          toFieldPath: spec.writeConnectionSecretToRef.name
          transforms:
          - string:
              fmt: '%s-postgresql'
              type: Format
            type: string
          type: FromCompositeFieldPath
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: spec.forProvider.administratorPasswordSecretRef.name
        - fromFieldPath: spec.parameters.storageGB
          toFieldPath: spec.forProvider.storageMb
          type: FromCompositeFieldPath
          transforms:
          - type: math
            math:
              multiply: 1024
      - name: dbfwrule
        base:
          apiVersion: dbforpostgresql.azure.jet.crossplane.io/v1alpha2
          kind: FlexibleServerFirewallRule
          spec:
            forProvider:
              serverIdSelector:
                matchControllerRef: true
              #! not recommended for production deployments!
              startIpAddress: 0.0.0.0
              endIpAddress: 255.255.255.255
      - name: password
        base:
          apiVersion: kubernetes.crossplane.io/v1alpha1
          kind: Object
          spec:
            forProvider:
              manifest:
                apiVersion: secretgen.k14s.io/v1alpha1
                kind: Password
                metadata:
                  name: ""
                  namespace: crossplane-system
                spec:
                  length: 64
                  secretTemplate:
                    type: Opaque
                    stringData:
                      password: $(value)
        patches:
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: spec.forProvider.manifest.metadata.name
    EOF
    

    The composition defined above makes sure that all FlexibleServers are placed in the westeurope region and under the resource group tap-psql-demo. This composition fulfils the XRD previously created.

    Warning: Setting the FlexibleServerFirewallRule to start at 0.0.0.0 and end at 255.255.255.255 will allow access to the PostgreSQL Server from any IP and is not recommended in a production environment.

Create an Instance Class

In order to make instances of a service easily discoverable and claimable by Application Operators, the role of the Service Operator creates a ClusterInstanceClass. In this particular example, the class states that claimable instances of PostgreSQL instances are represented by Secret objects of type connection.crossplane.io/v1alpha1 with label services.apps.tanzu.vmware.com/class set to azure-postgres:

kubectl apply -f - <<'EOF'
apiVersion: services.apps.tanzu.vmware.com/v1alpha1
kind: ClusterInstanceClass
metadata:
  name: azure-postgres
spec:
  description:
    short: Azure Postgresql database instances
  pool:
    kind: Secret
    labelSelector:
      matchLabels:
        services.apps.tanzu.vmware.com/class: azure-postgres
    fieldSelector: type=connection.crossplane.io/v1alpha1
EOF

In addition, you need to grant sufficient RBAC permissions to Services Toolkit to be able to read the secrets specified by the class.

kubectl apply -f - <<'EOF'
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
EOF

Provision Azure Flexible Server for PostgreSQL instances

Playing the role of the Service Operator, you now provision an instance of an Azure Flexible Server for PostgreSQL using the postgresqlinstances.bindable.database.example.org API managed by the XRD you previously created. Note that .spec.publishConnectionDetailsTo provides Crossplane with the name and a label for the secret that is being used to store the connection details for the newly created database. You can see that the label specified here matches the label selector defined on the ClusterInstanceClass you created in the previous step.

The PostgreSQLInstance has a dependency on a Secret where the Service Operator needs to specify the password for the admin user. Here we use Carvel’s Password API to create this Secret for us.

Run the following command:

kubectl apply -f - <<'EOF'
apiVersion: bindable.database.example.org/v1alpha1
kind: PostgreSQLInstance
metadata:
  name: postgresql-server
  namespace: default
spec:
  parameters:
    #! supported storage sizes: 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
    storageGB: 32
  compositionSelector:
    matchLabels:
      provider: azure
  publishConnectionDetailsTo:
    name: postgresql-server
    metadata:
      labels:
        services.apps.tanzu.vmware.com/class: azure-postgres
EOF

Running this command will cause the creation of a Azure Flexible Server for PostgreSQL instance in your Azure account. You can use the Azure CLI to verify this:

az postgres flexible-server list -o table

After the instance has been successfully created, the status of the newly created PostgreSQLInstance resource should show READY=True. This might take a few minutes. You can wait for this by running:

kubectl wait postgresqlinstances.bindable.database.example.org/postgresql-server \
    --for=condition=Ready=true --timeout=10m

As soon as the Azure Flexible Server for PostgreSQL instance is ready, it is claimable by the role of the Application Operator as shown in the next section.

Claim the Azure Flexible Server for PostgreSQL Server instance and connect to it from the Tanzu Application Platform Workload

Thanks to the previously created ClusterInstanceClass, Secrets representing PostgreSQL Server instances can now be discovered and claimed by Application Operators through the Tanzu CLI as shown below.

  1. Show available classes of service instances by running:

    tanzu service classes list
    
      NAME              DESCRIPTION
      azure-postgres    Azure Postgresql database instances
    
  2. Show claimable instances belonging to the PostgreSQL Server instance class by running:

    tanzu services claimable list --class azure-postgres
    
      NAME                 NAMESPACE  API KIND  API GROUP/VERSION
      postgresql-server    default    Secret    v1
    
  3. Create a claim for the discovered instance by running:

    tanzu service resource-claim create postgresql-server-claim \
      --resource-name postgresql-server\
      --resource-kind Secret \
      --resource-api-version v1
    
  4. Obtain the claim reference by running:

    tanzu service resource-claim list -o wide
    

    Expect to see the following output:

    NAME                        READY  REASON  CLAIM REF
    postgresql-server-claim     True           services.apps.tanzu.vmware.com/v1alpha1:ResourceClaim:postgresql-server-claim
    
  5. Create an application workload that consumes the claimed PostgreSQL Server instance by running:

    Example:

    tanzu apps workload create my-workload \
      --git-repo https://github.com/sample-accelerators/spring-petclinic \
      --git-branch main \
      --git-tag tap-1.2 \
      --type web \
      --label app.kubernetes.io/part-of=spring-petclinic \
      --annotation autoscaling.knative.dev/minScale=1 \
      --env SPRING_PROFILES_ACTIVE=postgres \
      --service-ref db=services.apps.tanzu.vmware.com/v1alpha1:ResourceClaim:postgresql-server-claim
    

    Note that --service-ref is being set to the claim reference obtained previously.

check-circle-line exclamation-circle-line close-line
Scroll to top icon