Skip to Content
Technical Articles
Author's profile photo Uwe Klinger

The new way to consume Service Bindings on Kyma Runtime

With the Kyma Runtime, you can use services offered by the SAP Business Technology Platform (SAP BTP). Previously service bindings were limited to the SAP BTP Cloud Foundry environment. Learn how to consume them in your application running in the SAP BTP Kyma environment.

Introduction

SAP BTP services run outside of your Kyma cluster, just like services you can consume from a Hyperscaler. Today it’s not possible to integrate them seamlessly in your service mesh. Even if it would be possible, other service-specific information is required in the application. Therefore, a mechanism is required to provide your application with the required information — referred to as “service credentials”.

Credentials are stored in a Kubernetes secret and mapped to a bunch of environment variables. However, this is not suitable if frameworks are used to access credentials, such as the SAP Cloud Application Programming Model (CAP) or Spring Boot.

The Cloud Foundry Approach

Cloud Foundry has a thing called “service bindings” that bind service instances to applications. The data of all services bound to an application is provided as a JSON object via an environment variable (VCAP_SERVICES). Applications just need to parse the value of that environment variable.

Service bindings consist of metadata and credentials. Metadata are, for example, the type of the service offering (label), the service plan, a list of tags, and the name of the service instance.

servicebinding.io Service Bindings

Kubernetes doesn’t have such a mechanism. And here comes servicebinding.io into the play: It’s a community lead effort to standardize how services are bound to Kubernetes applications, or more precisely: Kubernetes workloads.

Basically, that means:

A container must have an environment variable SERVICE_BINDING_ROOT that points to the root directory for all service bindings.

For example, you set in your container spec:

...
env:
  - name: SERVICE_BINDING_ROOT
    value: /bindings

Bindings have to be put in a sub directory of that directory. The sub directory has a file for each property of the binding. Usually, it’s mounted to a volume created from a Kubernetes secret.

There are some conventions for binding properties:

  1. There must be a property type that contains the type of the service. You can compare this to the label property in Cloud Foundry.
  2. Optionally, there is a property provider to indicate the provider of this service.
  3. There are rules for a few well-known properties.
  4. Any additional properties can have arbitrary content.

With that you can use type and provider to look up the desired service. For example, if you’re looking for the hana service, you can search for a binding with type hana.

Let’s have a look at an example directory structure for an SAP HANA binding:

/bindings/
  my-hana-binding/
    type: hana
    url: http://hana.ondemand.com
    user: me
    password: secret
  another-binding/
    ...
  ...

This works for SAP BTP services as well. Compared to Cloud Foundry, the label property containing the service type must be renamed to type.

But there are a few shortcomings that I wanted to mention:

The Metadata Extension

SAP BTP services have additional metadata, such as the service plan or tags, that you might want to use as a search criterion. For example, if your application supports different databases you could use the tag db to search for a database service.

Bindings of SAP BTP services do not just contain strings, they can be structured using sub objects and arrays, and they can have boolean or numeric properties. Kubernetes secrets do not support typing and thus you need service-specific knowledge to deserialize the credentials according to its type.

Well, that’s not a problem if you write the code for specific service bindings. But it’s a problem for frameworks and libraries that read the bindings and expose them via an API. If your application was implemented for Cloud Foundry and you want to port it to Kyma, you don’t want to change the code to read the bindings. You expect the libraries to do the job for you.

To support that, SAP has implemented an extension to servicebinding.io. It just adds another property .metadata that contains a JSON string explaining the kind and the formatting of each property. A property either belongs to the metadata, like type or plan, or to the credentials, like user or password. It can be formatted as a string or as a JSON value. String type properties are always stored with the string format. The JSON value formatting allows to store booleans, numbers, sub objects, and arrays in a binding value.

For example:

/bindings/
  my-funny-binding/
    .metadata: /* see below */
    type: funny-service
    tags: ["funny", "somewhat", "useful"]
    user: me
    a_number: 3
    a_boolean: true
    an_object: { "property1": "value 1", "property2": true }

.metadata File Content:

{
    "metaDataProperties": [
        { "name": "type", "format": "text" },
        { "name": "tags", "format": "json" }
    ],
    "credentialProperties": [
        { "name": "user", "format": "text" },
        { "name": "a_number", "format": "json" },
        { "name": "a_boolean", "format": "json" },
        { "name": "an_object", "format": "json" }
    ]
}

This helps libraries like @sap/xsenv and CAP to parse the bindings and provide them through their APIs in a compatible manner.

The good news is that it’s already implemented, and you can use it right now.

In the Kyma Runtime, secrets of SAP BTP service bindings are already created with the .metadata property and contain all metadata you know from Cloud Foundry. This was introduced with the SAP BTP Service Operator version v0.2.3.

Library Support

You can consume bindings in your Node.js, Java, and CAP Node.js applications:

Language Library Min Version
Node.js CAP 6.0.1
Node.js @sap/xsenv 3.3.2
Node.js @sap/hdi-deploy 4.4.1
Node.js @sap/html5-app-deployer 4.1.2
Node.js @sap/sbf 6.5.2
Java BTP Env Variable Access 0.4.1

These libraries allow you to consume servicebinding.io bindings without the .metadata property extension as well. In that case, they treat all properties as strings. The properties type and provider are handled as metadata and all other properties as strings.

End-to-End Example

If you want to see how this works end-to-end, you can follow the next steps to create a service and see how it can be consumed with @sap/xsenv in your Kyma Runtime cluster.

Create a Service Instance

The first step is to create a service instance.

  1. Create a file xsuaa-instance.yaml:
     apiVersion: services.cloud.sap.com/v1
     kind: ServiceInstance
     metadata:
       name: xsuaa-instance
     spec:
       serviceOfferingName: xsuaa
       servicePlanName: application
    
  2. Apply it to your cluster:
     kubectl apply -f xsuaa-instance.yaml
    
  3. Check the state of your service instance:
     kubectl get serviceinstance
    
  4. After short time, it should look like this:
     NAME             OFFERING   PLAN          STATUS    READY   AGE
     xsuaa-instance   xsuaa      application   Created   True    28s
    

Create a Service Binding

Next up, create a service binding for your service instance. This automatically creates a Kubernetes secret with the binding metadata and credentials.

  1. For the binding, create a file xsuaa-binding.yaml:
     apiVersion: services.cloud.sap.com/v1
     kind: ServiceBinding
     metadata:
       name: xsuaa-binding
     spec:
       serviceInstanceName: xsuaa-instance
       secretName: xsuaa-secret
    
  2. Apply it to your cluster:
     kubectl apply -f xsuaa-binding.yaml
    
  3. After short time, it should look like this:
     NAME            INSTANCE         STATUS    READY   AGE
     xsuaa-binding   xsuaa-instance   Created   True    15s
    

Inspecting the Binding’s Secret

Now, you can inspect the Kubernetes secreted that was created by the service binding.

  1. List the created secret:
     kubectl get secret xsuaa-secret
    
     NAME           TYPE     DATA   AGE
     xsuaa-secret   Opaque   22     62s
    
  2. You can view the secret’s base64 encoded values (I hid the sensitive data in my example):
     kubectl get secret xsuaa-secret -o yaml
    
     apiVersion: v1
     kind: Secret
     type: Opaque
     metadata:
       name: xsuaa-secret
       ...
     data:
       .metadata: eyJjc...XX0=
       apiurl: <...hidden...>
       clientid: <...hidden...>
       clientsecret: <...hidden...>
       credential-type: YmluZGluZy1zZWNyZXQ=
       identityzone: <...hidden...>
       identityzoneid: <...hidden...>
       instance_guid: <...hidden...>
       instance_name: eHN1YWEtaW5zdGFuY2U=
       label: eHN1YWE=
       plan: YXBwbGljYXRpb24=
       sburl: <...hidden...>
       subaccountid: <...hidden...>
       tags: WyJ4c3VhYSJd
       tenantid: <...hidden...>
       tenantmode: c2hhcmVk
       type: eHN1YWE=
       uaadomain: <...hidden...>
       url: <...hidden...>
       verificationkey: <...hidden...>
       xsappname: <...hidden...>
       zoneid: <...hidden...>
    
  3. You can check what’s in the .metadata property with this small piece of shell magic:
     kubectl get secret xsuaa-secret -o json | jq -r '.data[".metadata"]' | base64 -d | jq .
    

Try a Service Binding API

Finally, you can create a pod using this binding to explore it, with the required configuration to consume the service binding.

  1. Create a file test-pod.yaml:
     apiVersion: v1
     kind: Pod
     metadata:
       name: test-pod
     spec:
       containers:
       - name: test
         env:
         - name: SERVICE_BINDING_ROOT
           value: /bindings
         image: node:alpine
         volumeMounts:
         - mountPath: /bindings/auth
           name: xsuaa-volume
           readOnly: true
         command: [ 'sleep', "10000" ]
       volumes:
       - name: xsuaa-volume
         secret:
           defaultMode: 420
           secretName: xsuaa-secret
    

    You can find the three things here that are important to access the service binding:

    1. The environment variable SERVICE_BINDING_ROOT is provided.
    2. There is a volume with the service binding secret properties.
    3. The volume is mounted in a sub directory of the directory provided in SERVICE_BINDING_ROOT.
  2. Apply the pod to your cluster:
     kubectl apply -f test-pod.yaml
    
  3. Wait for the pod to turn into status Running:
     kubectl get pods test-pod
    
     NAME       READY   STATUS    RESTARTS   AGE
     test-pod   2/2     Running   0          10s
    

After having the preparation completed, you can use the pod to explore the files created for the binding:

  1. Enter a shell on the pod:
     kubectl exec test-pod -it -- sh
    

    Now, you can execute commands on the pod.

  2. List all the service binding files, for example:
     find /bindings
    

    You will observe an unexpected, yet a bit weird, output: All properties exist two times!

    The files in the expected location (for example: /bindings/auth/clientid) are symbolic links to files in a sub directory starting with ...

You can also try out what the @sap/xsenv library returns:

  1. Install @sap/xsenv on the pod:
     npm i -g @sap/xsenv
    
  2. Read all services:
     node -e 'console.log(require("/usr/local/lib/node_modules/@sap/xsenv").readServices())'
    
  3. The output looks like this:
     {
       auth: {
         instance_name: 'xsuaa-instance',
         instance_guid: '<...hidden...>',
         plan: 'application',
         label: 'xsuaa',
         type: 'xsuaa',
         tags: [ 'xsuaa' ],
         credentials: {
           sburl: '<...hidden...>',
           url: '<...hidden...>',
           verificationkey: '<...hidden...>',
           clientid: '<...hidden...>',
           clientsecret: '<...hidden...>',
           'credential-type': 'binding-secret',
           uaadomain: '<...hidden...>',
           zoneid: '<...hidden...>',
           subaccountid: '<...hidden...>',
           tenantmode: 'shared',
           xsappname: '<...hidden...>',
           apiurl: '<...hidden...>',
           identityzone: '<...hidden...>',
           identityzoneid: '<...hidden...>',
           tenantid: '<...hidden...>'
         },
         name: 'auth'
       }
     }
    

Metadata and credential data are separated, and the tags array is correctly deserialized as expected. The API returns the same binding data like on Cloud Foundry.

Credentials can be also looked up by its metadata properties.

  1. Query the service using its label:
     node -e 'console.log(require("/usr/local/lib/node_modules/@sap/xsenv").getServices({auth: { label: "xsuaa"}}))'
    
  2. You should get the same output as the previous call.

If you like, you can create a second service instance and bind it and continue doing experiments.

Once you’ve finished, enter exit to leave the pod.

You can delete the created Kubernetes resources using:

kubectl delete -f xsuaa-instance.yaml,xsuaa-binding.yaml,test-pod.yaml

Remark: The value of the name Property

Note, that the name property is filled with the directory name (auth in the example above) of your binding instead of the instance name (xsuaa-instance in the example). On Cloud Foundry, you usually get the instance name.

The VCAP_SERVICE doc says:

name → The binding_name, if it exists. Otherwise, the instance_name

Accordingly, we decided to always fill it with the binding’s directory name that you can set depending on your needs. For compatibility with Cloud Foundry, you either use the instance’s name as binding directory name or change your code to use the instance_name property instead of the name property.

Summary

Wrapping it up, the adoption of servicebinding.io increases the interoperability between SAP and non-SAP technology.

Further, it enables usage of things that may be created around that spec. For example, you could use the service binding controller to inject bindings into your pods.

With the .metadata extension, all the metadata of SAP BTP service bindings can be accessed and credentials are accessible with their correct data type. This lets you stay platform independent and migrate applications from Cloud Foundry to the Kyma Runtime without touching the binding related code.

Additionally, any applications that are capable to read servicebinding.io bindings can also read bindings to SAP BTP services (ignoring the .metadata file).

I’d like to recommend the new tutorials showing how to Deploy Your CAP Application on SAP BTP Kyma Runtime as part of the SAP BTP end-to-end tutorial series. In the tutorials you use CAP’s tooling to add a Helm chart to an application and deploy it to a Kyma Runtime cluster. The Helm chart makes use of the new service binding mechanism.

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.