Skip to Content
Technical Articles

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%20Web%20Apps%20via%20the%20Kyma%20Runtime

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.

Revisiting the deployment artifacts for the Cloud Foundry runtime

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: com.sap.application.content
    path: .
    requires:
      - name: managed-fiori-html5-repo-host
        parameters:
          content-target: true
    build-parameters:
      build-result: resources
      requires:
        - artifacts:
            - HTML5Module-content.zip
          name: HTML5Module
          target-path: resources/
  - name: managed-fiori-destination-content
    type: com.sap.application.content
    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
              sap.cloud.service: cloud.service
            - Name: managed-fiori-destination-uaa
              Authentication: OAuth2UserTokenExchange
              ServiceInstanceName: managed-fiori-uaa
              ServiceKeyName: managed-fiori-uaa-key
              sap.cloud.service: 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 (“com.sap.application.content”).
  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.

Deployment artifacts for the Kyma runtime

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:

---
apiVersion: servicecatalog.k8s.io/v1beta1
kind: ServiceInstance
metadata:
  name: kyma-destination-instance
spec:
  clusterServiceClassExternalName: destination
  clusterServicePlanExternalName: lite
  parameters:
    HTML5Runtime_enabled: true
    version: "1.0.0"

---
apiVersion: servicecatalog.k8s.io/v1beta1
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

deployer
+-- 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>.launchpad.cfapps.eu10.hana.ondemand.com/<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%20the%20destination%20instance%20ID%20in%20the%20Kyma%20Console

Find the destination instance ID in the Kyma Console.

The final URL will then look like this one:

https://43de072btrial.launchpad.cfapps.eu10.hana.ondemand.com/8aebc2e1-2234-4bd1-8da4-e15231138dbf.businessservice.tokendisplay/index.html

Full end-to-end sample

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%20Fiori%20app%20of%20the%20end-to-end%20sampl

SAP Fiori app of the end-to-end sample

Summary

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
Be the first to leave a comment
You must be Logged on to comment or reply to a post.