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.
For additional information on more deployment options you can also check out the guide for JavaScript.
Prerequisites
To follow this guide you will need:
- A Gardener managed cluster
- The SAP BTP Service Operator installed in the Cluster (minimum version
0.2.3
) - Docker and a publicly reachable Docker repository
- A Spring Boot Application using the SAP Cloud SDK
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.
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.
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:
- Docker Hub
- Artifactory DMZ (for SAP internal developers)
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
.
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:17-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.
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>
In case you are facing authorization issues when pushing to your repository refer to the dedicated section under Prerequisites.
Create a Kubernetes Deployment
-
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 -
Install the configuration via
kubectl apply -f deployment.yml
. -
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
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.
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.
- Create a new YAML file containing the following Ingress configuration:
---
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
-
Install the configuration via
kubectl apply -f ingress.yml
. -
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
.
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.
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.
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:
- Destination Service
- Identity and Authentication Service (IAS)
Bind the Destination Service
- Create a new YAML file:
---
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
-
Install the configuration via
kubectl apply -f destination-service.yml
. -
Monitor the status via
kubectl describe ServiceInstance destination-service
. Eventually this should automatically create a Kubernetes secret namedmy-destination-service-secret
. This secret will contain the actual service binding information. -
Mount the
my-destination-service-secret
secret into the file system of the application as follows:-
Find the empty list of
volumes
at the end of yourdeployment.yml
. Add a new volume, referencing the secret:volumes:- name: my-destination-service-binding-volume
secret:
secretName: my-destination-service-secret -
Mount this volume into the file system of your application. Add it to the empty list of
volumeMounts
in thecontainer
section of yourdeployment.yml
:volumeMounts:- name: my-destination-service-binding-volume
mountPath: '/etc/secrets/sapbtp/my-destination-service'
readOnly: true
-
-
Update the configuration via
kubectl apply -f deployment.yml
.
Bind the Identity and Authentication Service
- Create a new
identity-service.yaml
file:
---
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
- Repeat the same steps 2-5 from the previous section, always replacing
destination
withidentity
.
On-Premise Connectivity
On Kubernetes there are generally two approaches to connect to On-Premise destinations:
- Using Transparent Proxy and Connectivity Proxy
- 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
- Private Link not yet supported
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
We generally recommend to go with approach (1) unless the given constraints apply to your use case.
1. Using Transparent and Connectivity Proxies
Using the SAP Cloud SDK with the Transparent Proxy on Gardener is identical to how it is used on Kyma. Please refer to this documentation.
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
andbinding_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 astoken_service_url
- For
- 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
:
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.
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.
-
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
andargs
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.
-
Make sure that the adjusted image is actually running in your Kubernetes Cluster.
-
Identify the pod you want to debug against, for example using the
kubectl get pods
command. -
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
-
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.