Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
andrewgugger
Advisor
Advisor

This blog post is the second part in a series around using SAP S/4HANA with containers. I would encourage you to read Part 1: Containerizing SAP S/4HANA Systems with Docker first, where we discussed what Docker is and how we can use it in conjunction with SAP S/4HANA to solve some problems related to N + M landscape. I have been working on this project alongside my colleagues Maxim Shmyrev and Griffin McMann. Let’s dive right in! 

Overview

Here is an overview of the topics I will cover in this blog post: 

  1. What is Kubernetes? 
  2. Configuring the pod 
  3. Configuring the service 
  4. Configuring the NGINX Reverse Proxy 
  5. Creating additional pods 
  6. Conclusion 

What is Kubernetes? 

Now that we have learned how to build a docker image of a SAP S/4HANA system, we need to deploy containers for our ABAP developers to make use of them! We will use Kubernetes (or K8s) to deploy multiple containers. Kubernetes is an open-source technology used to deploy containerized applications. We can also utilize it to manage the incoming connections from the SAP GUI. We will be using Kubernetes Pods to run the container of our SAP S/4HANA that we built in the previous tutorial. The great thing about Kubernetes is that we can use simple yaml files to define our container orchestration parameters and with one command we can deploy as many pods as we want. 

andrewgugger_0-1711548462549.png

In theory, different ABAP developers and other users will ask for their own SAP S/4HANA system to connect to. The diagram above depicts users connecting to their own SAP S/4HANA systems that were deployed with Kubernetes and are all running on the same server. This infrastructure allows you to deploy many pods on-demand where developers can pull their code into these isolated S/4HANA systems via gCTS or abapGit. The changes made in these S/4HANA systems can then be committed to a centralized repository, merged and then pulled back into a DEV, QA, or production system. This solution can speed up the development process by circumventing object lock and can be used as a solution for N+M landscapes.  

Configuring the Pod 

In production situations, it is best practice to create a yaml file that defines a deployment. A deployment is a Kubernetes object that manages a set of identical pods. For our simple use case, however, we will create yaml file which will define pod A:

 

 

 

apiVersion: v1 
kind: Pod 
metadata: 
  name: s4hana2023a 
  labels: 
    app: s4-2023a 
spec: 
  hostname: veuxci 
  containers: 
  - args: 
    - infinity 
    command: 
    - sleep 
    name: s4hana2023-a 
    image: your_image_repo.com/s4hana:2023 
    imagePullPolicy: IfNotPresent 
    ports: 
      - containerPort: 4237 
        protocol: TCP 
      - containerPort: 3200 
        protocol: TCP 
      - containerPort: 3300 
        protocol: TCP 
      - containerPort: 8000 
        protocol: TCP 
      - containerPort: 50000 
        protocol: TCP 
      - containerPort: 44300 
        protocol: TCP 
      - containerPort: 1129 
        protocol: TCP 
      - containerPort: 1128 
        protocol: TCP 
    resources: 
      limits: 
        cpu: '2' 
        memory: 48Gi 
      requests: 
        cpu: '2' 
        memory: 48Gi 

 

 

 

From this yaml file you can see that we are defining a pod with the name: s4hana2023a, app: s4-2023a, the image we are using (s4hana:2023), exposing some ports necessary for connecting to the system and then finally resource limits and requests. The resource limits parameter defines the maximum amount of a resource that a container can use, in this case the container can only use 2 CPUs and 45Gi of memory. If the container exceeds this amount it will throttle or terminate it to prevent it from impacting the performance of other containers. The resource requests parameter specifies the amount of a resource that the containers guaranteed to receive. 

Save the yaml file as pod_a.yaml and deploy the pod by running the command:  

 

 

 

Kubectl apply -f pod_a.yaml -n <name_of_namespace> 

 

 

 

You can run this command any time you make changes to this yaml file and you want those changes to reflect in the pod currently running. The –n denotes which namespace you want to deploy/apply the changes of the pod to. A namespace is a way to organize the objects in your Kubernetes cluster. For our scenario, all of the objects will exist in the same namespace.

Configuring the Service 

Now that we have deployed our pod we will need to create a Kubernetes service to manage the connection from the outside world to the pod. We can create this service by defining some parameters to the bottom of our existing yaml file. Your pod_a.yaml file should now look like this: 

 

 

 

apiVersion: v1 
kind: Pod 
metadata: 
  name: s4hana2023a 
  labels: 
    app: s4-2023a 
spec: 
  hostname: veuxci 
  containers: 
  - args: 
    - infinity 
    command: 
    - sleep 
    name: s4hana2023-a 
    image: your_image_repo.com/s4hana:2023 
    imagePullPolicy: IfNotPresent 
    ports: 
      - containerPort: 4237 
        protocol: TCP 
      - containerPort: 3200 
        protocol: TCP 
      - containerPort: 3300 
        protocol: TCP 
      - containerPort: 8000 
        protocol: TCP 
      - containerPort: 50000 
        protocol: TCP 
      - containerPort: 44300 
        protocol: TCP 
      - containerPort: 1129 
        protocol: TCP 
      - containerPort: 1128 
        protocol: TCP 
    resources: 
      limits: 
        cpu: '2' 
        memory: 48Gi 
      requests: 
        cpu: '2' 
        memory: 48Gi 
--- 
apiVersion: v1 
kind: Service 
metadata: 
  name: s4hana2023-combined-service-a 
spec: 
  selector: 
    app: s4-2023a 
  ports: 
    - name: servicea-sapgui #Service for SAPGUI 
      protocol: TCP 
      port: 3200 
      targetPort: 3200 
      nodePort: 30000 
    - name: servicea-fiori #Service for Fiori 
      protocol: TCP 
      port: 44300 
      targetPort: 44300 
      nodePort: 31001 
    - name: servicea-eclipse #Service for Eclipse 
      protocol: TCP 
      port: 3300 
      targetPort: 3300 
      nodePort: 30301 
  type: NodePort

 

 

 

At the bottom of our yaml file you can see that we are defining a Kubernetes Service with the name of s4hana2023-combined-serivce-a, and notice that the ‘app’ parameter is the same name as the ‘app’ parameter for our pod: s4-2023a. This tells Kubernetes that the networking rules for this service should be applied to pod a. Within this combined service we have three services for three different connections: SAP GUI, Fiori, and Eclipse. Within each of these services we have defined the protocol (TCP) as well as these other parameters: 

 

 

 

Port: 3200 
targetPort: 3200 
nodePort:30000 
type: nodePort 

 

 

 

The NodePort is what exposes our S/4HANA system running inside a Kubernetes pod to the outside world. From there, the service will route the connection to the targetPort of 3200 of pod A.  

From the diagram below you can see what these different port parameters correspond to in the yaml file.

andrewgugger_0-1711553005666.png

There is only one glaring issue with trying to connect through the NodePort we just created. The NodePort range is 30000 – 32767 and the SAPGUI can only connect via port 3200 (3200-3299). We solve this problem by deploying a NGINX Reverse Proxy to handle the incoming requests coming from port 3200 (or 32XX) and forwarding them to our NodePort. 

andrewgugger_0-1711635722367.png

Configuring the NGINX Reverse Proxy 

It is important to note that instead of NGINX, it could be any other reverse proxy solution, such as Traefik. For our tutorial we will run our NGINX reverse proxy in a docker container. From the command below you can see we are running the latest version of nginx and we are exposing ports 3200, 3201, 3202 and 3203. The last two digits in the port 3200 refer to the instance number, for example instance number 00 will connect to port 3200, instance number 01 will connect to port 3201, instance 02 port 3202. We will be able to load balance our users to their designated pods based on what instance number they chose but when they connect to the Kubernetes service their traffic will be routed back to the correct instance number of 00. Feel free to expose more ports based on the number of pods you would like to run in parallel. If this seems confusing, it will become clearer as you continue reading.  

Running a NGINX docker container: 

 

 

 

sudo docker run -d --name nginx-base -p 3200:3200 -p 3201:3201 -p 3202:3202 -p 3203:3203 nginx:latest

 

 

 

The next step we need to do is to copy the nginx.conf file to our local directory with the following command: 

 

 

 

sudo docker cp [container-id]:/etc/nginx/nginx.conf /your/local/dir/nginx.conf

 

 

 

We then want to add these lines to the bottom of the nginx.conf file: 

 

 

 

stream { 
    server { 
        listen 3200; 
 
        proxy_pass my-server.com:30000; 
        proxy_connect_timeout 300s; 
        proxy_timeout 600s; 
    } 
    server { 
        listen 3201; 
 
        proxy_pass my-server.com:30001; 
        proxy_connect_timeout 300s; 
        proxy_timeout 600s; 
    } 
 
    server { 
        listen 3202; 
 
        proxy_pass my-server.com:30002; 
        proxy_connect_timeout 300s; 
        proxy_timeout 600s; 
    } 
    server { 
        listen 3203; 
 
        proxy_pass my-server.com:30003; 
        proxy_connect_timeout 300s; 
        proxy_timeout 600s; 
    } 
}

 

 

 

Here you can see that NGINX is listening on port 3200 and if it receives a request on this port, it will pass the request off to port 30000, where our Kubernetes NodePort service is exposed. From there it will forward the request to the pod. In this nginx.conf file we are also listening on other ports in case we decide to spin up extra pods: 

3201 to 30001 

3202 to 30002 

3203 to 30003 

Once you have added these lines to the nginx.conf file and added any additional ports that you have also exposed when creating the nginx container, this file can be copied back to the /etc/nginx/ dir in the container with the following command: 

 

 

 

sudo docker cp /hana/data/bootcamp/ngin2/nginx.conf [container-id]:/etc/nginx/

 

 

 

Reload the nginx service so that the changes in the nginx.conf file take place: 

 

 

 

sudo docker exec [container-id] nginx -s reload

 

 

 

Creating additional pods 

Now, all of the infrastructure has been setup and if we want to create a “pod-b” all we need to do is copy our pod-a yaml file and change each “a” to “b” and assign it a different NodePort for our SAPGUI, Eclipse, and Fiori connections, in this case 30001, 31001, and 30301. 

 

 

 

apiVersion: v1 
kind: Pod 
metadata: 
  name: s4hana2023b
  labels: 
    app: s4-2023b
spec: 
  hostname: veuxci 
  containers: 
  - args: 
    - infinity 
    command: 
    - sleep 
    name: s4hana2023-b
    image: your_image_repo.com/s4hana:2023 
    imagePullPolicy: IfNotPresent 
    ports: 
      - containerPort: 4237 
        protocol: TCP 
      - containerPort: 3200 
        protocol: TCP 
      - containerPort: 3300 
        protocol: TCP 
      - containerPort: 8000 
        protocol: TCP 
      - containerPort: 50000 
        protocol: TCP 
      - containerPort: 44300 
        protocol: TCP 
      - containerPort: 1129 
        protocol: TCP 
      - containerPort: 1128 
        protocol: TCP 
    resources: 
      limits: 
        cpu: '2' 
        memory: 48Gi 
      requests: 
        cpu: '2' 
        memory: 48Gi 
--- 
 
apiVersion: v1 
kind: Service 
metadata: 
  name: s4hana2023-combined-service-b
spec: 
  selector: 
    app: s4-2023b
  ports: 
    - name: serviceb-sapgui #Service for SAPGUI 
      protocol: TCP 
      port: 3200 
      targetPort: 3200 
      nodePort: 30001 
    - name: serviceb-fiori #Service for Fiori 
      protocol: TCP 
      port: 44300 
      targetPort: 44300 
      nodePort: 31001 
    - name: serviceb-eclipse #Service for Eclipse 
      protocol: TCP 
      port: 3300 
      targetPort: 3300 
      nodePort: 30301 
  type: NodePort 

 

 

 

Then run the same command to start up pod b: 

 

 

 

Kubectl apply -f pod-b.yaml 

 

 

 

Repeat this step to create as many pods as you would like! 

andrewgugger_0-1711553892541.png

In the image above, you can see the infrastructure that we have now created. If user A wants to connect to pod A they can use instance 00 which connects them to the nginx reverse proxy on port 3200 and this will forward them to port 30000 which is the NodePort for Service A that then passes the request to Pod A. User B will connect via instance 01 which means they will use port 3201 and NGINX will pass the request to the NodePort for service B (30001). The benefit of this architecture is that if you need another S/4HANA system, you don’t need to provision another server or VM. One can simply edit a few lines of code in the yaml file to create a new K8s pod and service eliminating the time and effort for provisioning a new sandbox system. Please note that this diagram only displays the services defined for our SAPGUI connections (NodePorts 30000, 30001, and 30002), and does not show the services we defined for Eclipse and Fiori.

Conclusion 

In this blog post we walked through the steps to deploy containerized SAP S/4HANA systems using Kubernetes. We discussed how this architecture allows for the on-demand provisioning of isolated S/4HANA systems, accelerating the development process by being a solution for N+M landscapes. We demonstrated how to deploy Kubernetes pods and services using an Infrastructure as Code (IaC) approach, utilizing simple yaml files. If you enjoyed this blog post, please stay tuned for other topics my team members and I will be covering next, including how to install ABAPGit and gCTS in these containerized systems S/4HANA systems. This is a project that my colleagues Maxim Shmyrev, Griffin McMann, and I have been working on for a little while now and we are excited about the possibilities this creates for our customers. If you have questions about this blog post or the tutorial, let me know in the comments below!