02_user_tutorials:07_additional_interfaces:api:07_grolink-on-kubernetes
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| 02_user_tutorials:07_additional_interfaces:api:07_grolink-on-kubernetes [2025/01/24 16:28] – removed - external edit (Unknown date) 127.0.0.1 | 02_user_tutorials:07_additional_interfaces:api:07_grolink-on-kubernetes [2025/01/24 16:32] (current) – tim2 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | {{howhard> | ||
| + | ====== Deploying GroIMP/ | ||
| + | This tutorial is a first step towards deploying GroIMP on a compute cluster. This at the moment is more of a prove of concept than a established way of using GroIMP. | ||
| + | |||
| + | The idea is to have several multiple pods running the GroIMP API server GroLink and one pod or job that is scheduling tasks to the GroLink pods. This one pod/job could later be a web service or a deployment job, but for this tutorial its going to be just a small python terminal-Pod we can connect to and run our script by hand. This script than uses python multiprocessing to send API calls to the different GroLink pods in parallel and than collecting the results to a csv file. | ||
| + | |||
| + | The Python script will create a small data set for a sensitivity analysis of a very simple model and execute the model. | ||
| + | |||
| + | |||
| + | |||
| + | ====== Preparing the model ====== | ||
| + | |||
| + | To run a Model we need a model, but since this is not about modelling or statistics or analysis this model can be very very simple. It grows a tree with branches of a first order and print the crown radius after every step. The radius is hereby depending on the angle and the length vactor (lenV), which are the values we are going to test in our analysis. | ||
| + | |||
| + | The idea of the sensitivity analysis using GroLink is explained in this [[02_user_tutorials: | ||
| + | |||
| + | |||
| + | <code java> | ||
| + | //model.rgg | ||
| + | import parameters.*; | ||
| + | |||
| + | module Bud(int order,float len) extends Sphere(0.1); | ||
| + | |||
| + | protected void init () | ||
| + | [ | ||
| + | Axiom ==> Bud(0,1); | ||
| + | ] | ||
| + | |||
| + | |||
| + | public void run () | ||
| + | [ | ||
| + | Bud(0,len) ==> F (len) | ||
| + | [RL(parameters.angle) F(len*parameters.lenV) Bud(1, | ||
| + | RH(90)[RL(parameters.angle) F(len*parameters.lenV) Bud(1, | ||
| + | RH(90)[RL(parameters.angle) F(len*parameters.lenV) Bud(1, | ||
| + | RH(90)[RL(parameters.angle) F(len*parameters.lenV) Bud(1, | ||
| + | Bud(0, | ||
| + | Bud(1,len) ==> F (len) Bud(1, | ||
| + | {getRadius(); | ||
| + | ] | ||
| + | |||
| + | |||
| + | public void getRadius(){ | ||
| + | println(max(location((*Bud*)).x)+0.2); | ||
| + | |||
| + | } | ||
| + | |||
| + | |||
| + | </ | ||
| + | |||
| + | |||
| + | <code java > | ||
| + | // param/ | ||
| + | static float lenV=0.9; | ||
| + | static float angle=45; | ||
| + | |||
| + | </ | ||
| + | ===== Setup the cluster ===== | ||
| + | |||
| + | To do the following a Kubernetes cluster | ||
| + | |||
| + | |||
| + | We start by creating the namespace " | ||
| + | |||
| + | <code bash> | ||
| + | kubectl create namespace grolinktutorial | ||
| + | </ | ||
| + | |||
| + | The namespace defines which nodes we want to address or what roles are used. | ||
| + | In our case the only additional role we need is that a pod can see other pods (so our terminal node can find the GroLink nodes). We can just use the role named " | ||
| + | |||
| + | Therefore we need a ClusterRoleBinding that defines who has this role, in our case we just bind it to default because this is the simplest. The following should be stored in rolebinding.yaml | ||
| + | <code yaml> | ||
| + | apiVersion: rbac.authorization.k8s.io/ | ||
| + | kind: ClusterRoleBinding | ||
| + | metadata: | ||
| + | name: podreaderbinding | ||
| + | roleRef: | ||
| + | apiGroup: rbac.authorization.k8s.io | ||
| + | kind: ClusterRole | ||
| + | name: system:node | ||
| + | subjects: | ||
| + | - kind: ServiceAccount | ||
| + | name: default | ||
| + | namespace: grolinktutorial | ||
| + | </ | ||
| + | |||
| + | Now we can apply them using kubectl in a command line: | ||
| + | <code bash> | ||
| + | kubectl apply -f role.yaml | ||
| + | kubectl apply -f rolebinding.yaml | ||
| + | </ | ||
| + | |||
| + | |||
| + | ===== Create GroLink Pods ===== | ||
| + | |||
| + | For the Grolink pods we can create a deployment that uses the GroIMP default docker image from GitLab and executes the GroLINK api on it with -a api. | ||
| + | For the beginning we only create 3 replicas since we are working with a simulated cluster. | ||
| + | An we define a matchLabel so we can later figgure out which pods are actually running the API. | ||
| + | |||
| + | <code yaml> | ||
| + | apiVersion: apps/v1 | ||
| + | kind: Deployment | ||
| + | metadata: | ||
| + | name: groimp-grolink | ||
| + | namespace: grolinktutorial | ||
| + | labels: | ||
| + | app: grolink | ||
| + | spec: | ||
| + | replicas: 3 | ||
| + | selector: | ||
| + | matchLabels: | ||
| + | app: grolink | ||
| + | template: | ||
| + | metadata: | ||
| + | labels: | ||
| + | app: grolink | ||
| + | spec: | ||
| + | containers: | ||
| + | - name: grolink | ||
| + | image: registry.gitlab.com/ | ||
| + | args: [" | ||
| + | </ | ||
| + | |||
| + | |||
| + | This can be executed for a file similar to the role and the Binding: | ||
| + | <code bash> | ||
| + | kubectl apply -f grolinkDeploy.yaml | ||
| + | </ | ||
| + | |||
| + | |||
| + | For our terminal pod we are not very picky we just need a pod that runs for ever and can execute python code. Therefore we can just use a python image with some dependencies installed and let it run the embedded web server(we don't need this server at all, but if the node is not busy is dies). | ||
| + | <code yaml> | ||
| + | apiVersion: apps/v1 | ||
| + | kind: Deployment | ||
| + | metadata: | ||
| + | name: terminal | ||
| + | namespace: grolinktutorial | ||
| + | labels: | ||
| + | app: terminal | ||
| + | spec: | ||
| + | replicas: 1 | ||
| + | selector: | ||
| + | matchLabels: | ||
| + | app: terminal | ||
| + | template: | ||
| + | metadata: | ||
| + | labels: | ||
| + | app: terminal | ||
| + | spec: | ||
| + | containers: | ||
| + | - name: terminal | ||
| + | image: registry.gitlab.com/ | ||
| + | args: [" | ||
| + | </ | ||
| + | |||
| + | We need to also deply this: | ||
| + | <code bash> | ||
| + | kubectl apply -f terminalDeploy.yaml | ||
| + | </ | ||
| + | |||
| + | To test if all pods are running as we want them to we can just list all of them for our name space: | ||
| + | |||
| + | <code bash> | ||
| + | kubectl get pods --namespace grolinktutorial | ||
| + | </ | ||
| + | |||
| + | |||
| + | ===== Get IP-addresses for Python ===== | ||
| + | |||
| + | Now we can start for the first time running code on our little setup. | ||
| + | To find all the Pods we use the python [[https:// | ||
| + | with a selector that checks if the app of this node is GgroLink. Then we can use the IP addresses of the pods with the GroPy library. | ||
| + | |||
| + | <code python> | ||
| + | from GroPy import GroPy | ||
| + | import kr8s | ||
| + | podIPs=[] | ||
| + | selector = {' | ||
| + | for podS in kr8s.get(" | ||
| + | print(podS.status.podIP) | ||
| + | podIPs.append(podS.status.podIP) | ||
| + | |||
| + | for i in podIPs: | ||
| + | link = GroPy.GroLink(" | ||
| + | wb = link.createWB().run().read() | ||
| + | print(wb.listFiles().run().read()) | ||
| + | wb.close() | ||
| + | </ | ||
| + | |||
| + | To run this on the terminal pod we have to first get the name of the terminal pod using the command | ||
| + | <code bash> | ||
| + | kubectl get pods --namespace grolinktutorial | ||
| + | </ | ||
| + | In this list one name should start with " | ||
| + | |||
| + | Now we can use kubectl to copy the python file with the code from above on this pod: | ||
| + | <code bash> | ||
| + | kubectl -n grolinktutorial cp run.py terminal-< | ||
| + | </ | ||
| + | |||
| + | and run it with | ||
| + | |||
| + | <code bash> | ||
| + | kubectl -n grolinktutorial exec terminal-< | ||
| + | </ | ||
| + | |||
| + | ===== Run a simulation ===== | ||
| + | |||
| + | Now with the existing connection we can run our model for the first time. Todo so we first need to copy our model to the terminal pod: | ||
| + | |||
| + | <code bash> | ||
| + | kubectl -n grolinktutorial cp model.gsz terminal-XXX:/ | ||
| + | </ | ||
| + | |||
| + | |||
| + | And then we can open this project using the GroPy library, it is important to open it with the content of the gsz and not with the link to it because the API server runs on another system. | ||
| + | |||
| + | Then we can update and execute our file as we know it from other API examples: | ||
| + | |||
| + | <code python> | ||
| + | |||
| + | from GroPy import GroPy | ||
| + | import kr8s | ||
| + | podIPs=[] | ||
| + | selector = {' | ||
| + | for podS in kr8s.get(" | ||
| + | podIPs.append(podS.status.podIP) | ||
| + | #create link to the first pod | ||
| + | link = GroPy.GroLink(" | ||
| + | #open the workbench with a POST request | ||
| + | wb = link.openWB(content=open(" | ||
| + | # change the parameters fo the simulation | ||
| + | wb.updateFile(" | ||
| + | static float lenV=4; | ||
| + | static float angle=12; | ||
| + | """,' | ||
| + | wb.compile().run() | ||
| + | #execute the run function | ||
| + | data = wb.runRGGFunction(" | ||
| + | print(data) | ||
| + | #close the workbench | ||
| + | wb.close().run() | ||
| + | |||
| + | </ | ||
| + | |||
| + | If we then update our run.py function on the terminal pod and execute it we can get a result of: '' | ||
| + | |||
| + | ===== Running on all pods ===== | ||
| + | |||
| + | To now use the potential of the cluster, we need to send requests to all the pods in parallel. This can be done from our one terminal pod using the python multiprocessing library. | ||
| + | |||
| + | Using this library we can initialize a pool of " | ||
| + | |||
| + | With this pool of workers we can than work through a list of parameter sets and push each set in a simulation and collect the results in a file. | ||
| + | |||
| + | ==== Generate input data with SALib ==== | ||
| + | |||
| + | To generate the input data we can use the saltelli.sample function from [[https:// | ||
| + | |||
| + | <code python> | ||
| + | problem = { | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | } | ||
| + | param_values = saltelli.sample(problem, | ||
| + | |||
| + | |||
| + | </ | ||
| + | |||
| + | '' | ||
| + | |||
| + | |||
| + | ==== Linking processes/" | ||
| + | |||
| + | |||
| + | First we create a list with links to all the API servers: | ||
| + | <code python> | ||
| + | links=[] | ||
| + | selector = {' | ||
| + | for podS in kr8s.get(" | ||
| + | links.append(GroPy.GroLink(" | ||
| + | </ | ||
| + | |||
| + | Then we can use this list to create a queue long enough to " | ||
| + | <code python> | ||
| + | WORKERCOUNT =9 | ||
| + | pods = multiprocessing.Queue() | ||
| + | n = len(links) | ||
| + | for i in range(0, | ||
| + | pods.put(links[i%n]) | ||
| + | </ | ||
| + | |||
| + | This queue is required so that the workers can be initialized in parallel using the following function: | ||
| + | |||
| + | <code python> | ||
| + | #initialize each worker | ||
| + | def init_worker(function, | ||
| + | function.cursor = queue.get().openWB(content=open(" | ||
| + | </ | ||
| + | |||
| + | The function.cursor will then later be defined for each worker, by emptying the given queue. | ||
| + | |||
| + | |||
| + | ==== The actual function ==== | ||
| + | |||
| + | The actual growth function is no much different to the one we used above to test our model for the first time. Only that we can use the variable grow.cursor as a workbench because we know already that it will be initialized in that way. And we only get one tuple as an input parameter from the ASlib function, so we split it in the first line: | ||
| + | |||
| + | <code python> | ||
| + | # the actual execution | ||
| + | def grow(val): | ||
| + | lenV, angle = val | ||
| + | results = [] | ||
| + | #overwrite the parameters in the file | ||
| + | grow.cursor.updateFile(" | ||
| + | static float lenV=""" | ||
| + | static float angle=""" | ||
| + | """,' | ||
| + | grow.cursor.compile().run() | ||
| + | for x in range(0, | ||
| + | data=grow.cursor.runRGGFunction(" | ||
| + | results.append(float(data[' | ||
| + | return results | ||
| + | </ | ||
| + | |||
| + | ==== Running and saving ==== | ||
| + | |||
| + | In the final step we initialize a multiprocessing pool using the init_worker function, with the grow function and pods queue as parameters and map this pool on the generated input values. | ||
| + | |||
| + | Finally we can transfrom and save our result in an csv file. | ||
| + | <code python> | ||
| + | # Multi processing | ||
| + | pool = multiprocessing.Pool(processes=WORKERCOUNT, | ||
| + | results = pool.map(grow, | ||
| + | pool.close() | ||
| + | y = np.array(results) | ||
| + | |||
| + | # save result | ||
| + | np.savetxt(" | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== Running It ==== | ||
| + | |||
| + | After we put this all together and run it as we did above, we can read our csv file through our terminal pod: | ||
| + | <code bash> | ||
| + | kubectl -n grolinktutorial exec terminal-XXXX cat result.csv | ||
| + | </ | ||
| + | |||
| + | For simplicity you can find the last python code here in one file: | ||
| + | <code python> | ||
| + | import numpy as np | ||
| + | from SALib.sample import saltelli | ||
| + | from GroPy import GroPy | ||
| + | import multiprocessing | ||
| + | import kr8s | ||
| + | from kr8s.objects import Pod | ||
| + | |||
| + | WORKERCOUNT =9 | ||
| + | |||
| + | # defining the problem | ||
| + | problem = { | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | } | ||
| + | param_values = saltelli.sample(problem, | ||
| + | |||
| + | #creating a link for each pod | ||
| + | links=[] | ||
| + | selector = {' | ||
| + | for podS in kr8s.get(" | ||
| + | print(" | ||
| + | links.append(GroPy.GroLink(" | ||
| + | |||
| + | # create an queue to assign pods to workers | ||
| + | pods = multiprocessing.Queue() | ||
| + | n = len(links) | ||
| + | for i in range(0, | ||
| + | pods.put(links[i%n]) | ||
| + | |||
| + | #initialize each worker | ||
| + | def init_worker(function, | ||
| + | function.cursor = pods.get().openWB(content=open(" | ||
| + | |||
| + | # the actual execution | ||
| + | def grow(val): | ||
| + | lenV, angle = val | ||
| + | results = [] | ||
| + | #overwrite the parameters in the file | ||
| + | grow.cursor.updateFile(" | ||
| + | static float lenV=""" | ||
| + | static float angle=""" | ||
| + | """,' | ||
| + | grow.cursor.compile().run() | ||
| + | for x in range(0, | ||
| + | data=grow.cursor.runRGGFunction(" | ||
| + | results.append(float(data[' | ||
| + | return results | ||
| + | | ||
| + | # Multi processing | ||
| + | pool = multiprocessing.Pool(processes=WORKERCOUNT, | ||
| + | results = pool.map(grow, | ||
| + | pool.close() | ||
| + | y = np.array(results) | ||
| + | |||
| + | # save result | ||
| + | np.savetxt(" | ||
| + | |||
| + | </ | ||
