Skip to main content

Develop your App for Kubernetes with SAP Gardener and Java SDK

SAP Gardener is a managed Kubernetes service by SAP developed as an Open Source project. It helps create and manage multiple Kubernetes clusters with less effort by abstracting environment specifics to deliver the same homogeneous Kubernetes-native DevOps experience everywhere.

The SAP Cloud SDK for Java supports SAP Gardener-based Kubernetes clusters out of the box.

SAP Cloud SDK Features Supported on SAP Gardener

Find below the list of features we currently support: Legend: ✅ - supported, ❗- partially supported, ❌ - not supported

  • ✅ Consume SAP BTP services like Destination, Connectivity, IAS, XSUAA, and others
  • ✅ Multitenancy
  • ✅ Resilience & Caching
  • ✅ Connect to and consume services from SAP S/4HANA Cloud
  • ❗ Connect to and consume services from SAP S/4HANA On-Premise
  • ✅ Seamless use of typed clients provided by the SAP Cloud SDK

Getting Started with the SAP Cloud SDK on Gardener

This detailed guide will help get your SAP Cloud SDK Java application up and running in the SAP Gardener-based Kubernetes cluster. You can also use this guide to migrate your existing application to Kubernetes.

tip

For additional information on more deployment options you can also check out the guide for JavaScript.

Prerequisites

To follow this guide you will need:

Check out the details below in case you are uncertain about any of the prerequisites.

Gardener Cluster

This guide assumes you have created a cluster via Gardener dashboard, have Kubernetes CLI installed on your local machine and have it set up for cluster access.

tip

Running kubectl cluster-info should print out your cluster endpoints.

In case you haven't set this up you can do so by downloading a kubeconfig from your Gardener dashboard. You can read more about accessing clusters using kubeconfig on the Kubernetes documentation

We also recommend to have an Ingress set up that exposes the application to the internet. You can read more about configuring an Ingress on the Gardener documentation.

SAP BTP Service Operator

This guide assumes you have the SAP BTP Service Operator installed in your cluster. The operator is used to create and bind service instances. The same can also be achieved via the Service Catalog. However, this guide will focus on the Service Operator usage.

tip

In case you don't have it installed please follow the documentation to install it.

Docker

This guide assumes you have Docker installed on your local machine.

Furthermore, you need a Docker repository where you can store images. The repository needs to be publicly accessible in order for the cluster to access and download the Docker image we are going to create.

In case you don't have such a repository yet we recommend either:

Access to images in a repository may be limited to authenticated and/or authorized users, depending on your configuration.

Make sure you are logged in to your repository on your local machine by running:

docker login (your-repo) --username=(your-username)

And check your configuration which is usually located under (your-home-directory)/.docker/config.json.

tip

In case AuthN/AuthZ is required to download images make sure you have a secret configured in your cluster:

kubectl create secret docker-registry (name-of-the-secret) --docker-username=(username) --docker-password=(API-token) --docker-server=(your-repo)
Application using the SAP Cloud SDK

If you don't have an application already you can comfortably create one from our archetypes.

Containerize the Application

To run on Kubernetes the application needs to be shipped in a container. For this guide we will be using Docker.

Create a Dockerfile in the project root directory:

FROM openjdk:8-jdk-alpine
ARG JAR_FILE=application/target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
EXPOSE 8080

If needed, update the JAR_FILE to point to your JAR file.

tip

You can find more information on how to containerize Spring Boot applications in this guide (in particular, check the Containerize It section).

Compile and push the image by running:

docker build -t <your-repo>/<your-image-name> .
docker push <your-repo>/<your-image-name>
tip

In case you are facing authorization issues when pushing to your repository refer to the dedicated section under Prerequisites.

Create a Kubernetes Deployment

  1. Create a new YAML file describing the Kubernetes deployment:

    deployment.yml
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: my-app-deployment
    spec:
    replicas: 1
    selector:
    matchLabels:
    app: my-app
    template:
    metadata:
    labels:
    app: my-app
    spec:
    containers:
    # Configure the docker image you just pushed to your repository here
    - image: <your-repo>/<your-image>:latest
    name: my-app
    imagePullPolicy: Always
    resources:
    requests:
    memory: '1Gi'
    cpu: '500m'
    limits:
    memory: '1.5Gi'
    cpu: '750m'
    # Volume mounts needed for injecting BTP service credentials
    volumeMounts:
    imagePullSecrets:
    # In case your repository requires a login, reference your secret here
    - name: <your-secret-for-docker-login>
    # Volumes containing BTP serice credentials from secrets
    volumes:
    ---
    apiVersion: v1
    kind: Service
    metadata:
    labels:
    app: my-app
    name: my-app
    namespace: default
    spec:
    type: NodePort
    ports:
    - port: 8080
    selector:
    app: my-app
  2. Install the configuration via kubectl apply -f deployment.yml.

  3. Monitor the status of the deployment by running: kubectl get deployment my-app-deployment.

Eventually, you should see an output similar to:

$ kubectl get deployment my-app-deployment

NAME READY UP-TO-DATE AVAILABLE AGE
my-app-deployment 1/1 1 1 15s
tip

In case something went wrong use kubectl describe together with deployment or pod to get more information about the status of your application.

Create an Ingress

To make your application available from outside the cluster we will create an Ingress.

note

In case you already have an Ingress configured in your cluster only add the new rule for your new applications.

You can read more about configuring an Ingress on the Gardener documentation.

  1. Create a new YAML file containing the following Ingress configuration:
ingress.yml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-router
namespace: default
annotations:
# cert.gardener.cloud/purpose: managed
spec:
tls:
- hosts:
# - "<your-cluster-host>"
# - "*.ingress.<your-cluster-host>"
# secretName: secret-tls
rules:
- host: 'my-app.ingress.<your-cluster-host>'
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 8080
  1. Install the configuration via kubectl apply -f ingress.yml.

  2. Verify the Ingress is up and running: kubectl describe ingress ingress-router

You should see an entry with the path / pointing to the backend my-app.

note

In case something went wrong and you are struggling to configure the Ingress you can also come back and set it up later. The Ingress is a convenient way to access your application. It is not strictly required for the rest of this guide.

Recommended: Configure TLS for your Ingress

Enable the NGINX Ingress add-on in your Gardener dashboard. The process may take a few minutes. Afterwards, you should see a domain in the dashboard as well as a Kubernetes secret secret-tls.

Un-comment the 4 lines in the YAML above using the generated domain and secret. Then re-deploy the configuration as usual. Your cluster endpoint should now be trusted by your browser.

tip

We highly recommended enabling TLS for your cluster endpoints. It ensures your client (e.g. browser) can verify the cluster's identity.

Access Your Application

At this point take a moment to verify you can access your application. Use the host you have defined in your Ingress rule in a browser or other tool of your choice (e.g. Postman). In case you started with an SAP Cloud SDK Archetype your should be greeted with a welcome page under the root path.

note

In case you skipped setting up an Ingress before you can use port forwarding to access your application.

Identify the pod name of your application with kubectl get pods. Then enable port forwarding to it by running: kubectl port-forward (your-pod-name) 8080:8080. With that you should be able to access the application on your http://localhost:8080

Bind SAP BTP Services to the Application

The SAP Business Technology Platform offers various services that can be used by applications. To access services from a Kubernetes environment instances have to be created and bound to the application.

For this guide we'll assume we want to use two services:

  1. Destination Service
  2. Identity and Authentication Service (IAS)

Bind the Destination Service

  1. Create a new YAML file:
destination-service.yaml
---
apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
name: destination-service
spec:
serviceOfferingName: destination
servicePlanName: lite
externalName: default-destination-service
---
apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
name: my-destination-service-binding
spec:
serviceInstanceName: destination-service
secretName: my-destination-service-secret
  1. Install the configuration via kubectl apply -f destination-service.yml.

  2. Monitor the status via kubectl describe ServiceInstance destination-service. Eventually this should automatically create a Kubernetes secret named my-destination-service-secret. This secret will contain the actual service binding information.

  3. Mount the my-destination-service-secret secret into the file system of the application as follows:

    1. Find the empty list of volumes at the end of your deployment.yml. Add a new volume, referencing the secret:

      volumes:
      - name: my-destination-service-binding-volume
      secret:
      secretName: my-destination-service-secret
    2. Mount this volume into the file system of your application. Add it to the empty list of volumeMounts in the container section of your deployment.yml:

      volumeMounts:
      - name: my-destination-service-binding-volume
      mountPath: '/etc/secrets/sapbtp/my-destination-service'
      readOnly: true
  4. Update the configuration via kubectl apply -f deployment.yml.

Bind the Identity and Authentication Service

  1. Create a new identity-service.yaml file:
identity-service.yaml
---
apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
name: my-identity-service
spec:
serviceOfferingName: identity
servicePlanName: application
parameters:
# Allowed redirect URIs in case you want to use an approuter behind an ingress for user login
# oauth2-configuration:
# redirect-uris:
# - https://*.ingress.<your-cluster-host>/login/callback
consumed-services: []
xsuaa-cross-consumption: true
multi-tenant: true
---
apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
name: my-identity-service-binding
spec:
serviceInstanceName: my-identity-service
secretName: my-identity-service-secret
  1. Repeat the same steps 2-5 from the previous section, always replacing destination with identity.

On-Premise Connectivity

On Kubernetes there are generally two approaches to connect to On-Premise destinations:

  1. Using Transparent Proxy and Connectivity Proxy
  2. Using Connectivity Proxy only

Approach (1) simplifies the application code but does not cover all features at the time of writing.

Constraints when using the Transparent Proxy

Approach (2) does not come with the above constraints, but imposes limitations on the operation mode.

Constraints when using the Connectivity Proxy
  • Connectivity Proxy must be operated in untrusted mode
tip

We generally recommend to go with approach (1) unless the given constraints apply to your use case.

1. Using Transparent and Connectivity Proxies

Prerequisites

This guide assumes you have both the Transparent Proxy (version >= 1.3.0) and Connectivity Proxy (version >= 2.9.2) installed in your cluster. Please refer to this and this documentation on how to install it if you haven't already.

Background Information

When using the Transparent Proxy your app performs requests against the Transparent Proxy without explicit authentication, assuming the network in your cluster can be trusted. The Transparent Proxy will obtain the relevant destination from the destination service and use it to forward the request via the Connectivity Proxy to the On-Premise system. Consequently, your app itself does not interact with destination or connectivity service at all and thus your application pods do not require bindings to these two services.

tip

This approach is conceptually different from what you may be used to from a CloufdFoundry environment. The official documentation of the Transparent Proxy gives more information on the architecture.

Create a Kubernetes Resource

Create the following YAML file:

apiVersion: destination.connectivity.api.sap/v1
kind: Destination
metadata:
name: example-dest
spec:
destinationRef:
name: 'example-dest-onprem' # name from BTP Cockpit
destinationServiceInstanceName: dest-service-instance-example # can be ommited if config.destinationService.defaultInstanceName was provided to the helm chart of the transparent proxy upon installation
  • Adjust the name example-dest-onprem to the On-Premise destination you defined in your SAP BTP Cockpit.

It will create a Kubernetes resource in your cluster, pointing to the destination you created in the cockpit. Apply the YAML with kubectl apply to take effect.

note

This step is required for each destination that should be reachable via the Transparent Proxy. If you do not create a custom Kubernetes resource of type destinations.destination.connectivity.api.sap for a given destination, the destination will not be handled by the Transparent Proxy.

Executing Requests

In your application you can now configure a destination to execute requests:

DefaultHttpDestination destination = DefaultHttpDestination
.builder("http://example-dest.namespace/")
// for a subscriber tenant make sure to send the tenant header:
// .header(new Header("X-Tenant-Id", TenantAccessor.getCurrentTenant().getTenantId()))
// for principal propagation make sure to set the auth type to "TOKEN_FORWARDING":
// .authenticationType(AuthenticationType.TOKEN_FORWARDING)
.build();

List<SalesArea> execute = new DefaultSalesAreaService().getAllSalesArea() // example OData request
.execute(destination);
  • Replace namespace in the URL with the namespace you installed the Transparent Proxy into

This will target the Kubernetes resource example-dest we created above which will be resolved to the destination we configured in that resource. The code above shows an example how you can use it to perform an OData request.

note

Repeat this process for all destination names you want to use in your application.

Connecting to Cloud systems

The above approach is not limited to destinations of proxy type ON_PREMISE. INTERNET destinations are equally supported.

2. Using the Connectivity Proxy

Prerequisites

This guide assumes you have the Connectivity Proxy installed in the untrusted mode in your cluster. In case you used the Helm chart to install the proxy, the untrusted mode is enabled if the property config.servers.proxy.http.enableProxyAuthorization is set to true. Please refer to this documentation on how to install the Connectivity Proxy if you haven't already.

The untrusted mode requires a service binding or service key for an XSUAA service instance. Make sure you have this binding/key at hand as you will need it in the steps below.

Create a Kubernetes Secret

Copy the following JSON template:

{
"label": "connectivity",
"provider": null,
"plan": "lite",
"name": "connectivity",
"tags": ["connectivity", "conn", "connsvc"],
"instance_guid": "",
"instance_name": "connectivity",
"binding_guid": "",
"binding_name": null,
"credentials": {
"tenantmode": "",
"clientid": "",
"token_service_domain": "",
"token_service_url": "",
"xsappname": "",
"clientsecret": "",
"url": "",
"uaadomain": "",
"verificationkey": "",
"identityzone": "",
"tenantid": "",
"onpremise_proxy_port": "20003",
"onpremise_proxy_http_port": "20003",
"onpremise_socks5_proxy_port": "20004",
"onpremise_proxy_ldap_port": "20001",
"onpremise_proxy_rfc_port": "20001",
"onpremise_proxy_host": ""
}
}

Perform the following adjustments:

  • Generate two random GUIDs and fill them into the fields instance_guid and binding_guid
  • Fill the fields from line 16-26 with the corresponding values from the XSUAA credentials you created to run the Connectivity Proxy in untrusted mode
    • For url use the same value as token_service_url
  • Update onpremise_proxy_host to contain the URL under which the Connectivity Proxy is reachable within your cluster
  • Update the ports in case you configured custom values for them on the Connectivity Proxy

Next, create a new YAML file and insert the prepared JSON object into connectivity-service-key:

apiVersion: v1
kind: Secret
metadata:
name: connectivity-service-secret
type: Opaque
stringData:
connectivity-service-key: '{
"label": "connectivity",
"provider": null,
....
}'

Finally, adjust your deployment YAML to contain these two entries under volumes and volumteMounts:

deployment.yaml:
  volumes:
- name: conectivity-service-binding-volume
secret:
secretName: connectivity-service-secret
volumeMounts:
- name: conectivity-service-binding-volume
mountPath: '/etc/secrets/sapbtp/connectivity/connectivity-service'
readOnly: true

Apply the YAML files for the changes to take effect. Your app should now be ready to connect to On-Premise systems via the Connectivity Proxy.

Troubleshooting

Issues about "No connectivity service biding found" usually indicate a misconfiguration with the created secret. To fix them, please double check the JSON, the secret, and the volume mounts

Excursion: Debug Kubernetes App From Your Local IDE

To understand some problems with an application it might be helpful to debug the application from within your IDE. Then you can go through the code step by step and see, where your expectations are not fulfilled anymore.

This excursion will guide you through the necessary steps to get your application running on your Kubernetes cluster connected to your local IDE.

  1. Add the following parameter to your invocation of the JVM:

    -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

    As an example, let's assume that your Dockerfile has the following entrypoint:

    ENTRYPOINT ["java","-jar","/app.jar"]

    Then you can update your deployment by adding the command and args properties to your image spec in your deployment.yml:

    spec: # pod spec
    containers:
    - image: <your-image-spec>
    name: <your-container-name>
    command: ['java']
    args:
    [
    '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005',
    '-jar',
    '/app.jar'
    ]

    This will replace the entrypoint with the given command and arguments, as described in the Kubernetes documentation.

  2. Make sure that the adjusted image is actually running in your Kubernetes Cluster.

  3. Identify the pod you want to debug against, for example using the kubectl get pods command.

  4. Forward the port used in the debug String above via the following command to your local machine:

    kubectl port-forward <name-of-your-pod> 5005:5005
  5. Let your IDE connect against localhost:5005. The specifics of this step depend heavily on your choice of IDE, so we cannot give a fits-all solution.