Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
victor_ionescu
Explorer
2,953

Background


A while ago I wrote a blog on the importance of using test environments which are reliable to set up and centrally managed. Building on the concepts laid out in that post, I set out to find a toolbox which would enable me to run an entire landscape of CloudFoundry apps on any infrastructure. My initial choice of technology (using vagrant to provide a VM with all required assets) turned out to be rather rigid and difficult to work with. So I decided to not reinvent the wheel and instead build on Docker, which is nowadays more mainstream & flexible at the same time.

In conclusion this blog will provide a walkthrough for using Docker to run a set of CloudFoundry apps in a test environment outside of CloudFoundry. In other words we will go from here:



to here:



The apps in this sample use a postgres DB as a backing service, while security & the Fiori Launchpad are provided by the "trio" appRouter/xsuaa/portal. This architectural setup should be rather common on the SAP Cloud Platform, being derived from the well-known HANA XSA Programming Model.

Disclaimer: Before we move on, I want to emphasize that this is a test environment. It is designed to be used for functional tests during development, not as a replacement for your productive environment.

In order to be able to run apps outside of CloudFoundry, it is important to first understand how they run inside of it



Understanding CloudFoundry


Containerization


Whenever you push some code to CloudFoundry, one of its buildpacks will package the code in a so called droplet, i.e. a ready-to-be-executed archive containing all necessary dependencies, libraries etc.

Platform Services & the App Environment


The CloudFoundry platform interacts with an app mainly through its environment: whenever you attach a backing service(database, xsuaa etc.) to an app, CloudFoundry will simply provide information for accessing this service in the environment of the app.

Networking


On upload, apps on CloudFoundry get a unique (public) hostname through which they can be addressed. When running multiple instances of an app, the platform will automatically load balance requests across these instances.

Moving outside of CloudFoundry


Getting an app to run outside of CloudFoundry requires addressing all of the above 3 aspects:

Containerization


To have a running test environment, we need to containerize and run:

  • our own applications

  • the SAP Fiori Launchpad

  • any services that we decide to host ourself (in this case the postgres DB)


e.g. a Spring Boot app can be dockerized with a dockerfile like the following:
FROM openjdk:8-jdk-alpine
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

If you're new to Docker, here are some more resources to get you started:

This should be good enough for most cases. However, if you are looking for more dev-prod parity, you can use a plugin like cf local to create a docker image using the exact same buildpack that CloudFoundry would use. This is particularly important if you rely on the buildpack to autoconfigure your app (that's not in the scope of this blog though)

Platform Services & the App Environment


Enabling your apps to run in a CF-like environment basically means providing environment variables which point to valid backing services.

Note: environment variables contain confidential information. Keep them safe and do not check them into version control.

Depending on the type of service that you are trying to consume, you will run into one of 2 scenarios. For some services its easiest to just spin up your own service instance and run your apps against it. In our sample project this is what we'll do with the postgres DB. In other cases you might not be able to run you own instance, so you'll have to consume the instance running in CloudFoundry. In our example we will use this approach with the authentication(xsuaa) and Portal service.

To run an app against a service instance running in CloudFoundry you need to retrieve the credentials and provide them to the app environment. Doing this manually for an xsuaa instance would require the following:

  • create and retrieve a service key


cf create-service-key xsuaa_instance local_test
cf service-key xsuaa_instance local_test > xsuaa.credentials.json


  • prepare the VCAP_SERVICES variable using the xsuaa credentials


  "VCAP_SERVICES": {
"xsuaa": [
{
"binding_name": null,
"credentials": {
// .. xsuaa.crendetials.json content goes here
},
"instance_name": "myuaa",
"label": "xsuaa",
"name": "myuaa",
"plan": "application",
"provider": null,
"syslog_drain_url": null,
"tags": [
"xsuaa"
],
"volume_mounts": []
}
]
}

When instead running against your own service instance you have to manually provide the service url & credentials.
"credentials": {
"dbname": "mydb",
"end_points": [
{
"host": "mypostgres",
"network_id": "SF",
"port": "5432"
}
],
"hostname": "localhost",
"password": "pwd",
"port": "5432",
"ports": {
"5432/tcp": "5432"
},
"uri": "postgres://postgres:pwd@mypostgres:5432/mydb",
"username": "postgres"
}

In this example the postgres instance will be accessible using the alias "mypostgres". How exactly your app & services communicate with each other depends on your networking setup (see below).

These are fundamentally the steps you would have to do if you were doing things manually. However, managing multiple apps manually this way can be very tedious and error prone. The last chapter describes a more efficient approach to handling this situation. Keep reading.

Networking


Apps on CloudFoundry are publicly accessible through their hostname. The same is not true by default when running an app in a Docker container. Docker provides several networking options, but whenever possible it's easiest to stick with the basics.

Our architecture imposes the following requirements on our network setup:

1. Apps and self-hosted services must be able to communicate with each other

As a result we attach all containers to the same virtual network. This way they can address each other using the container name as hostname

2. Some containers require internet access

Works out of the box, but remember to set your proxy configuration when your corporate network requires it.

3. The Fiori Launchpad must be "externally" accessible

We must enable port-forwarding from the docker host to the Fiori Launchpad container, to make the Launchpad "externally" accessible (i.e. from outside of the Docker network)

Putting the pieces together


The puzzle now consists of multiple pieces that need to be put together:

  • several docker images

  • environment variables for every container

  • a Docker network and port forwarding for the application router


A good way to define all these centrally is by using docker-compose. Everything discussed in this blog would be summarized in a docker-compose file like this:
version: '3'
services:
launchpad:
build: ../src/launchpad
container_name: launchpad
ports:
- "8080:8080"
networks:
- NET1
environment:
- PORT=8080
- VCAP_SERVICES={"portal-services":[{"binding_name":null,"credentials":{...
- DESTINATIONS=[{"name":"app1","url":"http://spring-app1","forwardAuthToken":true},...
- sapui5url=https://sapui5.hana.ondemand.com/1.56.10
app1:
build: ../src/app
container_name: spring-app1
networks:
- NET1
environment:
- SPRING_PROFILES_ACTIVE=cloud
- VCAP_APPLICATION={}
- VCAP_SERVICES={"postgresql":[{..."host":"mypostgres"..
app2:
build: ../src/app2
container_name: spring-app2
networks:
- NET1
environment:
- SPRING_PROFILES_ACTIVE=cloud
- VCAP_APPLICATION={}
- VCAP_SERVICES={"postgresql":[{..."host":"mypostgres"..
postgres:
image: "postgres"
container_name: mypostgres
networks:
- NET1
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres

A simple "docker-compose up" is then sufficient to start up your entire landscape, and you're ready to go.

However, I find maintaining this kind of yaml file manually rather painful. Moreover, since the file contains confidential information, it's important to keep it out of version control and thus be able to generate it on-demand.

Luckily, with a bit of glue code everything can be set up automatically for you. Want to know how? Check out the SAP CP CF Starter Project: https://github.com/ionescuv/sap-cp-cf-starter

The starter project provides a toolkit which:

  • Sets up the necessary services in your SAP CP Cloud Foundry account

  • Retrieves services credentials and generates the docker-compose.yml for you

  • Builds & runs docker images for all apps and dependencies


Give it a try and let me know what you think!
1 Comment
Labels in this area