Deploy Serverless SAP Fiori Apps from the Kyma Runtime

This post shows how to deploy SAP Fiori apps via the Kyma runtime to the HTML5 Application Repository. The deployment flow in this runtime follows the same philosophy as the flow of the Cloud Foundry runtime and achieves the same result.

The SAP HTML5 Application Repository Service for SAP BTP provides secure and performant storage space for all your web applications. In the previous posts, I explained how this service is used in the Cloud Foundry environment of SAP BTP. This makes much sense when the other components of your project run in the Cloud Foundry runtime or when there are no other components, and you don’t want to consume any runtime quota. But the situation changes when you want to run different project components in another runtime, such as the Kyma runtime. It doesn’t make much sense to use an MTA project for a “pseudo Cloud Foundry deployment” that will only create the service instances, and the “rest of the party” is going on in a different runtime. In this post, I’ll show you how to deploy any web application via the Kyma runtime. This web application can then be added to the SAP Launchpad service.


Uploading Web Apps via the Kubernetes Environment

This approach works analogously to the deployment in the Cloud Foundry environment:

  • The SAP HTML5 Application Repository Service stores the web application and exposes them to the managed application router.
  • We leverage the SAP Launchpad Service as a managed application router to lower the project’s memory footprint (and the total cost of ownership).
  • The SAP Destination Service and the UAA Service are required by the managed application router to “find” the web app in the HTML5 application repository. Besides this aspect, the services can also guard the web app against unauthorized access and build connections to registered backend systems.

What do we need in the Kubernetes environment to deploy web apps? Let’s find out by having a look at the MTA project descriptor to see what exactly happens in the Cloud Foundry environment:

ID: managed-fiori
_schema-version: 3.2.0
version: 1.0.0
parameters: enable-parallel-deployments: true
modules: - name: HTML5Module type: html5 path: HTML5Module build-parameters: builder: custom commands: - npm run build supported-platforms: [] - name: webapp-deployer type: path: . requires: - name: managed-fiori-html5-repo-host parameters: content-target: true build-parameters: build-result: resources requires: - artifacts: - name: HTML5Module target-path: resources/ - name: managed-fiori-destination-content type: build-parameters: no-source: true requires: - name: managed-fiori-uaa parameters: service-key: name: managed-fiori-uaa-key - name: managed-fiori-html5-repo-host parameters: service-key: name: managed-fiori-html5-repo-host-key - name: managed-fiori-destination parameters: content-target: true parameters: content: instance: existing_destinations_policy: update destinations: - Name: managed-fiori-destination-html5 ServiceInstanceName: managed-fiori-html5-repo-host ServiceKeyName: managed-fiori-html5-repo-host-key cloud.service - Name: managed-fiori-destination-uaa Authentication: OAuth2UserTokenExchange ServiceInstanceName: managed-fiori-uaa ServiceKeyName: managed-fiori-uaa-key cloud.service resources: - name: managed-fiori-destination type: org.cloudfoundry.managed-service parameters: service-plan: lite service: destination path: ./destination.json - name: managed-fiori-html5-repo-host type: org.cloudfoundry.managed-service parameters: service-plan: app-host service: html5-apps-repo config: sizeLimit: 2 - name: managed-fiori-uaa type: org.cloudfoundry.managed-service parameters: path: ./xs-security.json service-plan: application service: xsuaa

Sample project descriptor for the Cloud Foundry environment

We noticed the following things here:

  1. The resources section defines the UAA, HTML5 application repository host, and the destination service instance referenced in the modules.
  2. The module “HTML5Module” contains the static resources of the web application. It won’t be part of the built archive, and it is only here to be referenced by the deployer module.
  3. The module “webapp-deployer” depends on the module above to copy the built resources in this module. The deployer uploads these resources to the HTML5 application repository when during the project deployment. This also explains why there is a service binding to the HTML5 application repository service. This module does not require any runtime memory as it leverages a built-in feature of the Cloud Foundry environment (“”).
  4. The module “managed-fiori-destination-content” creates new service keys for the required UAA and HTML5 application repository host service instances. It also uses information from these service keys to creating new instance-level destinations in the bound destination service. All of this happens during deploy-time and doesn’t consume any memory at all.

Defining the service instances

Let’s start with the first item of the list – the service instance definitions: Kubernetes-based environments have a similar concept for services as Cloud Foundry environments; Both environments have the notion of service instances and bindings because they implement the Open Service Broker API). Kubernetes projects declare these objects as YAML documents in the project manifest, just like deployment and pods. I wrote a blog post about this mechanism a few weeks ago; Here’s a short recap of that post:

kind: ServiceInstance
metadata: name: kyma-destination-instance
spec: clusterServiceClassExternalName: destination clusterServicePlanExternalName: lite parameters: HTML5Runtime_enabled: true version: "1.0.0" ---
kind: ServiceBinding
metadata: name: kyma-destination-binding
spec: instanceRef: name: kyma-destination-instance

An example of the service instance and service binding definition for a Kyma project

The two YAML documents from the sample above define the needed destination service instance and the service binding. This scheme can be repeated for the XSUAA and HTML5 application repository services as well. Service bindings can then be mounted as volumes to attach them to pods:

containers: - image: <docker id>/kyma-html5-app-deployer imagePullPolicy: Always name: html5appdeployer volumeMounts: - name: destination-volume mountPath: /etc/secrets/sapcp/destination/kyma-destination-instance readOnly: true ...
volumes: - name: destination-volume secret: secretName: kyma-destination-binding

Mounting the service binding of the previous snippet to a pod

Web App Deployer

The Cloud Foundry project uses three modules to upload the web app to the HTML5 application repository and to create the destinations for the managed application router. Due to this convenient separation, SAP BTP, Cloud Foundry Environment does everything during deploy-time and doesn’t require any runtime artifacts that consume runtime memory. As the Kubernetes Environment is not customized (in the sense of no built-in application content deployer) by SAP, we must go a different path. But there’s also an advantage to this path: We only need one pod, the “Kubernetes-equivalent” of a CF module, that takes care of the deployment and the creation of the destinations.

Build the deployer app

+-- resources
| +-- ui5-app
| | +-- index.html
| | +-- manifest.json
| | +-- xs-app.json
+-- package.json
+-- Dockerfile

The directory structure of the Kyma web app deployer application

Your deployer application’s directory structure should contain a resources directory that contains one sub-folder per web app that needs to be uploaded. As usual, each web app needs to include at least the manifest.json and the xs-app.jsonfiles. On the project root, you need the well-known package.jsonfile, which has a dependency on the html5-app-deployer package:

{ "name": "kyma-html5-app-deployer", "version": "1.0.0", "dependencies": { "@sap/html5-app-deployer": "2.3.1" }, "scripts": { "start": "node node_modules/@sap/html5-app-deployer/index.js" }

Do you recall how we used to upload web apps in the Cloud Foundry environment about 1-2 years ago? If so, this approach might be quite familiar to you. In addition to the mentioned files, you need a Dockerfile in the application root to describe how the Docker image is built.

FROM node:14-alpine RUN mkdir -p /app && \ chown node.node /app # Create app directory
WORKDIR /app # Bundle app source
COPY . .
RUN npm install --production CMD [ "npm", "start" ]

Build and upload the image

As with every Kubernetes application, we first need to dockerize the application before it can be deployed. To do so, run the following commands to build the image and upload it to your registry.  I’ll use DockerHub, but you can also use your preferred registry for this.

docker build -t <docker id>/kyma-html5-app-deployer docker push <docker id>/kyma-html5-app-deployer

Reference the image in the pod

Now it’s time to connect all the loose ends. Add a Kubernetes object deployment to the project manifest. This pod is based on the Docker image of the previous step. It not only uploads the web app to the HTML5 application repository but also creates all the needed instance-level destinations. For this, we need to mount the existing service binding via volumes. The final deployment.yaml should look as follows:

apiVersion: apps/v1
kind: Deployment
metadata: name: html5appdeployer labels: app: html5appdeployer
spec: replicas: 1 selector: matchLabels: app: html5appdeployer template: metadata: labels: app: html5appdeployer spec: containers: - image: <docker id>/kyma-html5-app-deployer imagePullPolicy: Always name: html5appdeployer volumeMounts: - name: html5-repo-app-host-volume mountPath: /etc/secrets/sapcp/html5-apps-repo/kyma-app-host-instance readOnly: true - name: xsuaa-volume mountPath: /etc/secrets/sapcp/xsuaa/kyma-xsuaa-instance readOnly: true - name: destination-volume mountPath: /etc/secrets/sapcp/destination/kyma-destination-instance readOnly: true env: - name: SAP_CLOUD_SERVICE value: "business.service" volumes: - name: html5-repo-app-host-volume secret: secretName: kyma-app-host-binding - name: xsuaa-volume secret: secretName: kyma-xsuaa-binding - name: destination-volume secret: secretName: kyma-destination-binding

You can also add this YAML document to the same project manifest that contains the previously mentioned documents (that define the service instances and bindings).

Tip: I found it useful to set the imagePullPolicy toAlways. This makes it easier to replace the current web app in the development process as you just need to upload the new image to the registry. In the next step, you can then remove the old pod from the cluster. Kyma will automatically start a new pod based on the most recent image. There’s no need to modify the deployment itself.

Deploy to SAP BTP, Kyma runtime

Have you already enabled your Kyma runtime and connected to it the kubectl CLI? If so, run the following command to trigger the deployment:

kubectl apply -f deployment.yaml

This command will invoke all the needed actions to get your SAP Fiori app up and running via the Kyma runtime 🚀.

Access the SAP Fiori App

Access the web app via the SAP BTP cockpit or assemble the URL according to the following pattern:

https://<subaccount id><destination service instance ID>.businessservice.tokendisplay/index.html

You can find your subaccount id in SAP BTP Cockpit and the destination instance ID in the Kyma console when you inspect the secret of the destination service instance:


Find the destination instance ID in the Kyma Console.

The final URL will then look like this one:

A full end-to-end sample of this technique is available in the GitHub Repo “Examples of HTML5 Applications for SAP Business Technology Platform Multi-Cloud Environments“. This sample also contains a backend component that decodes passed-in JWT token value to create a rich sample scenario. The Fiori application that is baked in the Docker image iobert/kyma-html5-app-deployercontains the following web app:


SAP Fiori app of the end-to-end sample

In this post, you have learned:

  • how to use the managed application router from the Kyma runtime
  • how to mimic service bindings in the Kubernetes deployment descriptor
  • my personal tips for the deployer pod configuration
  • how to build a docker image that contains the web app resources
  • where to find HTML5 application samples for any SAP BTP runtime