Jarvice Applications Push to Compute tutorial¶
This tutorial should allow end users to build their own applications (apps) for Jarvice clusters through Jarvice PushToCompute interface.
This tutorial assumes user is building applications based on appdefversion
version 2
.
First part of the tutorial is dedicated to general knowledge and how to build and deploy a basic application.
Second part covers most standard use cases users could need to build their application.
It is assumed user have already installed docker on its personal system.
It is recommended to first read all sections from part 1 (Global view) to part 5 (Review application parameters) before proceeding to desired application target examples. Each of these general steps will help understand all features available, and provides key tips for new applications developers.
Table of content:
- 1. Global view
- 2. Hello world
- 3. Important building guidelines
- 4. Basic interactive job
- 5. Review application parameters
- 6. Non interactive application
- 7. Basic shell interactive application
- 8. Basic UI interactive application
- 9. MPI application
- 10. Script based application
- 11. Server application
1. Global view¶
In order to use Jarvice cluster, users need to build their own application container image, then push it to a registry accessible from the cluster, pull it using PushToCompute interface, and then simply submit jobs.
Process global view can be reduced to this simple schema:
In order to explain in details this process, best way is to build a Hello World application, steps by steps.
2. Hello world¶
Objective of this Hello World application is simply to display an Hello World as output message of a Jarvice job.
In order to achieve that, we will need to go through multiple steps. Process is not complex, but need steps to be understood in ordure to avoid basic issues.
2.1. Create Dockerfile¶
First step is to create the Dockerfile that will be used to build application container image.
Create folder hello_world:
mkdir hello_world
cd hello_world
Create then a Dockerfile. A Dockerfile is a multi-steps description of how image should be created, and from what. We are going to start from basic Ubuntu image, as this source image is a widely used starting point.
To get more details on how this Dockerfile can be extended and used, refer to https://docs.docker.com/engine/reference/builder/ .
Create file Dockerfile
with the following content, which should be self-explained:
# Use Ubuntu latest image as starting point.
# This image will be automatically pulled from web at build.
FROM ubuntu:latest
Note: we did not specify any CMD
or ENDPOINT
. This is on purpose, as this will be handled by a separated file later.
Now, generate the hello_world image, tagging it as tutorial:hello_word
(to get more details on how to build images, refer to https://docs.docker.com/engine/reference/commandline/build/):
docker build --tag="tutorial:hello_world" -f Dockerfile .
Once image has been successfully created, it is possible to test it by manually executing it:
:~$ docker run -it --rm tutorial:hello_world /usr/bin/echo "Hello World!"
Hello World!
:~$ docker run -it --rm tutorial:hello_world /usr/bin/cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.2 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.2 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
:~$
We can see here that image contains Ubuntu 20.04 release.
2.2. Create AppDef.json¶
We now need to create the Jarvice application file.
Create folder NAE:
mkdir NAE
cd NAE
And create here AppDef.json
file with the following content:
{
"name": "Hello World App",
"description": "A very basic app thay says hello to world.",
"author": "Me",
"licensed": false,
"appdefversion": 2,
"classifications": [
"Uncategorized"
],
"machines": [
"*"
],
"vault-types": [
"FILE",
"BLOCK",
"BLOCK_ARRAY",
"OBJECT"
],
"commands": {
"Hello": {
"path": "/usr/bin/echo",
"interactive": false,
"name": "Echo with arguments",
"description": "Execute /usr/bin/echo with 'Hello World!' as argument.",
"parameters": {
"message": {
"name": "message",
"description": "hello world message",
"type": "CONST",
"value": "Hello World!",
"positional": true,
"required": true
}
}
}
},
"image": {
"type": "image/png",
"data": ""
}
}
Let’s review key parts of this file (when not detailed, just keep it as it):
{
"name": "Hello World App",
"description": "A very basic app that says hello to world.",
"author": "Me",
"licensed": false,
"appdefversion": 2,
"classifications": [
"Uncategorized"
],
...
}
This first section of the file details general settings, like application name, a description, the author, if application is under license or not, and application classification.
{
...
"machines": [
"*"
],
...
}
Machines allows you to restrict usage of the application to a specific set of machines registered in the targeted Jarvice cluster. For example, if your application is GPU dedicated, it would make no sense to run it on non-GPU nodes, and so only GPU able nodes should be added here. Note that only Jarvice cluster administrator can create machines profiles on its cluster. Please contact your JXE administrator to get a detailed list of available machines.
{
...
"commands": {
"Hello": {
"path": "/usr/bin/echo",
"interactive": false,
"name": "Echo with arguments",
"description": "Execute /usr/bin/echo with 'Hello World!' as argument.",
...
}
},
...
}
Commands section describes possible commands available with the application. An application can have multiple commands available (For this same image, we could have for example a command named hello
with /usr/bin/echo
with argument Hello World!
as entry point, and a second command named os infos
with /usr/bin/cat
with argument /etc/os-release
as entry point). This case will be covered later.
Path is application entry point, and interactive lets you decide if user will be able to interact with running application or if all running process is automated.
Refers to https://jarvice.readthedocs.io/en/latest/appdef/#commands-object-reference for more details.
{
...
"commands": {
"Hello": {
"path": "/usr/bin/echo",
"interactive": false,
"name": "Echo with arguments",
"description": "Execute /usr/bin/echo with 'Hello World!' as argument.",
"parameters": {
"message": {
"name": "message",
"description": "hello world message",
"type": "CONST",
"value": "Hello World!",
"positional": true,
"required": true
}
}
}
},
...
}
Parameters are required and optional parameters passed to entry point as arguments or available in /etc/JARVICE/jobenv.sh
during run (which can be imported by scripts or users).
In this example, we are going to pass a CONST
with value "Hello World!"
as parameter to entry point. This will result in /usr/bin/echo "Hello World!"
being called. Other parameters (positional, etc.) will be described later.
Refer to https://jarvice.readthedocs.io/en/latest/appdef/#parameters-object-reference for more details.
{
...
"image": {
"type": "image/png",
"data": ""
}
}
The last section, image, refer to logo that will be displayed inside Jarvice interface, for our application. It has to be encoded as text. Let's add an image to our application. Download a basic sample from wikimedia:
wget https://upload.wikimedia.org/wikipedia/commons/thumb/2/28/HelloWorld.svg/128px-HelloWorld.svg.png
And encode it with base64:
base64 -w 0 128px-HelloWorld.svg.png
Get the output (which can be very large for big images), and add it into AppDef.json inside images.data
value:
{
...
"image": {
"type": "image/png",
"data": "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"
}
}
Now that our AppDef file is ready, lets inject it into final image.
2.3. Finalize image¶
Edit again Dockerfile and add a step to inject AppDef.json file into image, and another step to validate it:
FROM ubuntu:latest
COPY NAE/AppDef.json /etc/NAE/AppDef.json
RUN curl --fail -X POST -d @/etc/NAE/AppDef.json https://cloud.nimbix.net/api/jarvice/validate
And build again image with this new Dockerfile:
:~$ docker build --tag="tutorial:hello_world" -f Dockerfile .
Sending build context to Docker daemon 16.9kB
Step 1/3 : FROM ubuntu:latest
---> 7e0aa2d69a15
Step 2/3 : COPY NAE/AppDef.json /etc/NAE/AppDef.json
---> 0f8bdf9181e6
Step 3/3 : RUN curl --fail -X POST -d @/etc/NAE/AppDef.json https://cloud.nimbix.net/api/jarvice/validate
---> Running in eb716411eb8c
/bin/sh: 1: curl: not found
The command '/bin/sh -c curl --fail -X POST -d @/etc/NAE/AppDef.json https://cloud.nimbix.net/api/jarvice/validate' returned a non-zero code: 127
:~$
You can see here that we are missing a tool: curl
. This tool is contained in package curl. We have to add a step in Dockerfile to add it.
FROM ubuntu:latest
RUN apt-get update; apt-get install curl -y --no-install-recommends;
COPY NAE/AppDef.json /etc/NAE/AppDef.json
RUN curl --fail -X POST -d @/etc/NAE/AppDef.json https://cloud.nimbix.net/api/jarvice/validate
Please note that we also added the --no-install-recommends
to apt-get command. By default, apt-get or yum/dnf will try to install all recommended packages, generating huge images. 99% of time with app images, we do not need these packages. Setting --no-install-recommends
for apt-get or --nobest
for yum and dnf will sometime significantly reduce images sizes.
And build again:
:~$ docker build --tag="tutorial:hello_world" -f Dockerfile .
Sending build context to Docker daemon 16.9kB
Step 1/4 : FROM ubuntu:latest
---> 7e0aa2d69a15
Step 2/4 : RUN apt-get update; apt-get install curl -y --no-install-recommends;
---> Running in d97271342b81
...
Removing intermediate container d97271342b81
---> 88667cf55ce3
Step 3/4 : COPY NAE/AppDef.json /etc/NAE/AppDef.json
---> e167bfac86e5
Step 4/4 : RUN curl --fail -X POST -d @/etc/NAE/AppDef.json https://cloud.nimbix.net/api/jarvice/validate
---> Running in 3ab8ff302d45
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 4237 100 21 100 4216 567 111k --:--:-- --:--:-- --:--:-- 111k
{
"valid": true
}
Removing intermediate container 3ab8ff302d45
---> a32ee72e76f6
Successfully built a32ee72e76f6
Successfully tagged tutorial:hello_world
:~$
We can see the output of Nimbix application validator:
{
"valid": true
}
Our image is ready.
Next step is to push image to a registry.
2.4. Register to registry (optional)¶
This step is optional.
If you do not own a registry, or do not own an account on a third-party registry, you will need to obtain one.
In this tutorial, we are going to get a free account from https://hub.docker.com/ . There are others free registry available on the market, this is only an example. Note that by default, a free account on https://hub.docker.com/ allows unlimited public repositories, but only a single private repository. If you need to host multiple private images, you will need to consider another solution.
Create an account on https://hub.docker.com/.
Once account is created, sign in, and click on Create Repository.
Fill required information, as bellow:
And click Create.
Our application repository is now created and ready to host our image. Pull command is displayed on repository page:
2.5. Push image¶
Now that image is ready and registry repository is ready to host image, we need to login to registry and upload image into repository.
Check image is ready:
:~$ docker images | grep tutorial
tutorial hello_world a32ee72e76f6 5 hours ago 124MB
:~$
Now login to docker hub registry, using your credentials (or login to your private registry if not using docker, command may vary):
:~$ docker login
...
Login Succeeded
:~$
Now tag and push image. We are going to tag it as v1
version. Do not copy as it the bellow command, replace oxedions
by your docker hub user name to match your repository.
docker tag tutorial:hello_world oxedions/app-hello_world:v1
And push image into registry (again, replace oxedions
by your docker hub user name to match your repository):
:~$ docker push oxedions/app-hello_world:v1
The push refers to repository [docker.io/oxedions/app-hello_world]
42221693cbf0: Pushed
1594cdd4a24c: Pushed
...
v1: digest: sha256:7c37fb5840d4677f5e8b45195b8aa64ef0059ccda9fcefc0df62db49e426d805 size: 1363
:~$
Image is now pushed and can be pulled from Jarvice Push to Compute interface.
2.6. Pull image with Push to Compute¶
We are ready to inject our image into our Jarvice cluster.
Login as usual user, and in the main interface, go to PushToCompute tab on the right:
Then, click on New to create a new application.
Fill only App ID
and Docker repository
. Everything else will be automatically grabbed from the AppDef.json file created with the application. Then click OK.
Now that application is registered into Jarvice, we need to request an image pull, so that Jarvice will retrieve AppDef.json embed into image and update all application data. To do so, click on the small burger on the top left of the application, then Pull, confirm pull by clicking OK, and close Pull response windows. Then wait few seconds for image to be pulled.
Once image has been pulled, you can see that application logo has been updated by our base64 encoded png.
It is possible to check application logs / history to see pull process. To do so, press burger again, then go to History, and you can visualise here the pull process logs. Close the windows once read.
Application is now ready to be run in Jarvice.
2.7. Run application in Jarvice¶
To submit an application job to the cluster, simply click on the application card:
Then on the application window, click on Echo With Arguments (remember, all of this was set in the AppDef.json file when building application image).
In the next window, select the Machine type to be used. For this tutorial hello world, you can use the smallest available.
Then click Submit to submit the application job and run the application.
If all goes well, you are automatically redirected to Dashboard tab. If not, click on it on the right. You can see here that your job has been submitted, and is now queued. After few seconds/minutes, job will start and application will run. Note the job number here: 11776. This will be the reference for job logs later.
Once application has finished to run, you will see it marked as Completed.
2.8. Gather logs¶
Now that our application as run, lets gather few logs about it. Note that if the application failed to run, Jarvice local Administrator have access to advanced logs that might help to debug.
First, to grab job output, simply click on the small arrow on the right of the "completed" job card.
We can see that our "Hello World!" message is here.
Now, click on Jobs tab on the left, and then History. You can see here all your jobs history.
Last part, it is possible to go into Account tab on the right, then Team Log on the left. In the central area, we can see all team's job.
This is the general process to create an application for Jarvice and inject it through PushToCompute. You may need to iterate time to time between image creation and application jobs, to fix application execution issues. This is an expected behavior.
Also, if too much issues, switch for a time to interactive application (seen later in this documentation) to debug application, and then switch back to non-interactive.
We can now proceed to more productive applications and general guidelines. Next examples will not be as detailed as this one as process is always the same.
3. Important building guidelines¶
Before proceeding to more examples, it is important to keep in mind few image building guidelines.
3.1. Repush image¶
When you need to fix image and rebuild it, you need to delete local tag and create it again in order to be able to push again image.
Let’s take an example: we need to fix our hello_world application image, since we made a mistake in it.
First, delete local tag to remote repository:
docker rmi oxedions/app-hello_world:v1
Then build fixed image, it will update local copy:
docker build --tag="tutorial:hello_world" -f Dockerfile .
Note: sometime, you may want to force rebuilding all the steps, so forcing docker not using build cache. To do so, add --no-cache
to docker build
line.
And tag and push again local copy:
docker tag tutorial:hello_world oxedions/hello_world:v1
docker push oxedions/hello_world:v1
Don't forget to request a new pull in Jarvice interface to grab latest image NAEs.
3.2. Multi stages¶
Application images can be really big. Images builder should care about images size, and so optimize build process in order to save disk, memory, and bandwidth.
Docker containers images work with layers. Each step generates a layer that is stacked over others (like a git repository). This means that if user import (ADD/COPY/RUN wget) huge archives or files or installer binaries during process, even if file is deleted in a next step, this file will be stored in image stack, and so will grow image size without usage.
In order to prevent that, it is common and recommended to use multi-stages builds.
Idea is simple: instead of having a single stage in Dockerfile, we create multiple, and import all what we need in the final stage from previous ones, as only this last stage will be in our final image.
Let’s take an example:
We want to install Intel Parallel Studio Compilers. Archive is big (> 3GB).
In stage 0 (counter start at 0), we ADD archive to image, and install Intel product into /opt/intel
folder. Final image layers contains both installer archive, extracted archive content, and final installed product.
Then, in second stage we simply say "now copy all that is in /opt/intel
from stage 0 into current stage, so final image of this stage only contains final installed product in its layers, we don't have archive and extracted archive in final application image layer, which saves a LOT of disks and bandwidth.
As a simple example here, lets create a naive standard image that would download and install ffmpeg, the famous video processing tool:
FROM ubuntu:latest
RUN apt-get update; apt-get install tar xz-utils wget -y;
RUN wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz;
RUN tar xvJf ffmpeg-release-amd64-static.tar.xz;
RUN cd ffmpeg-5.0.1-amd64-static; cp ffmpeg /usr/bin/ffmpeg;
Build it:
docker build --tag="tutorial:ffmpeg" -f Dockerfile .
And check size of image:
:~$ docker images | grep ffmpeg
tutorial ffmpeg f2da746cb648 12 seconds ago 401MB
:~$
401 MB, this is a lot for such a small program. Problem here is: our image has in memory apt cache, few packages installed needed to download and extract archive, archive itself, and archive content that we do not need.
Let’s update it to a multi-stage build now:
# Stage 0, lets give it a name for convenience: download_extract_ffmpeg
FROM ubuntu:latest AS download_extract_ffmpeg
RUN apt-get update; apt-get install tar xz-utils wget -y;
RUN wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz;
RUN tar xvJf ffmpeg-release-amd64-static.tar.xz;
RUN cd ffmpeg-5.0.1-amd64-static; cp ffmpeg /usr/bin/ffmpeg;
# Stage 1, we simply import /usr/bin/ffmpeg from stage download_extract_ffmpeg
FROM ubuntu:latest
COPY --from=download_extract_ffmpeg /usr/bin/ffmpeg /usr/bin/ffmpeg
Note: we could also have COPY --from=0
, stage number are accepted. But using names is more convenient for large dockerfiles.
Let’s build again our image now, and we will need this time to prevent cache usage to be sure image is done again from scratch.
docker build --no-cache --tag="tutorial:ffmpeg" -f Dockerfile .
And now let’s check again image size:
:~$ docker images | grep ffmpeg
tutorial ffmpeg 610696e0d2eb 13 seconds ago 151MB
:~$
If you take a deeper look, you can see the stage 0 download_extract_ffmpeg in images list:
:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tutorial ffmpeg 610696e0d2eb 12 minutes ago 151MB
<none> <none> 8a182a98de1f 12 minutes ago 401MB
:~$
We have our first 401MB image, without name, only ID, and our stage 1 image, as final ffmpeg image. We can safely remove intermediate stage now (or keep it if you wish to benefit from cache if you think you will need to rebuild same image to fix things in stage 1):
docker rmi 8a182a98de1f
We went from 401MB to 151MB. This is a huge size reduction. While with this small example image size does not really matter, with huge applications, this could have a significant impact.
For more details, please visit official documentation on multi stage builds: https://docs.docker.com/develop/develop-images/multistage-build/
3.3. Optimize packages installation¶
Most of the time, final image will need to have few packages installed. In order to reduce size of image, and prevent waste, two actions can be done:
- Prevent recommended/best packages
- Pipe all packages management on the same line, ending with cache clean
As an example, lets create an image with Python 3 and curl installed. A basic Dockerfile would be:
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install python3 curl -y
Build image:
docker build --tag "size" -f Dockerfile .
The result image will be of size:
:~$ docker images | grep size
size latest 0bf03cdd01d2 12 seconds ago 159MB
:~$ ls
Now, simple tune image by adding --no-install-recommends
and having all packages task, including cache clean, as a single RUN task.
FROM ubuntu:latest
RUN apt-get update && apt-get install python3 curl -y --no-install-recommends && apt-get clean
Resulting image size is now:
:~$ docker images | grep size
size latest 79b6275b8518 About a minute ago 150MB
:~$ ls
We saved 9MB. With bigger packages, like desktop applications, size reduction is even larger and can sometime leads to around 30-40% size reduction.
3.4. End with NAE¶
We have seen that container images are multi layers images.
When pulling an image into Jarvice, Jarvice system needs to grab NAE content (AppDef.json file) from layers. To do so, it will search in each layer, starting from last one, for files. This process can take a long time. To speedup pull process, it is recommended to always perform NAE copy at last stage of a build, or to simply add a "touch" line that will simply contains copy of files:
RUN mkdir -p /etc/NAE && touch /etc/NAE/screenshot.png /etc/NAE/screenshot.txt /etc/NAE/license.txt /etc/NAE/AppDef.json
This line has to be adapted to files provided with the application image. If you only provide AppDef.json file, reduce the line to:
RUN mkdir -p /etc/NAE && touch /etc/NAE/AppDef.json
So, for hello world application, this would be:
FROM ubuntu:latest
RUN apt-get update && apt-get install curl -y --no-install-recommends && apt-get clean
COPY NAE/AppDef.json /etc/NAE/AppDef.json
RUN curl --fail -X POST -d @/etc/NAE/AppDef.json https://cloud.nimbix.net/api/jarvice/validate
RUN mkdir -p /etc/NAE && touch /etc/NAE/AppDef.json
For such a small image, it does not worth it, but for very large images, this small tip can have interesting benefit.
4. Basic interactive job¶
Before reviewing available application parameters, next step is to be able to launch interactive jobs.
Interactive jobs are very useful to debug when creating an application, as you can test launch application end point yourself, and debug directly inside the Jarvice cluster context.
4.1. Standard way¶
Create a new application folder aside app-hello_world folder, called app-interactive_shell:
mkdir app-interactive_shell
cd app-interactive_shell
mkdir NAE
Now, create here a Dockerfile with the following content:
FROM ubuntu:latest
RUN apt-get update && apt-get install curl -y --no-install-recommends && apt-get clean
RUN echo "Sarah Connor?" > /knock_knock ;
COPY NAE/AppDef.json /etc/NAE/AppDef.json
RUN curl --fail -X POST -d @/etc/NAE/AppDef.json https://cloud.nimbix.net/api/jarvice/validate
Then create AppDef.json in NAE folder with the following content:
{
"name": "Interactive application",
"description": "Start bash command in a gotty",
"author": "Me",
"licensed": false,
"appdefversion": 2,
"classifications": [
"Uncategorized"
],
"machines": [
"*"
],
"vault-types": [
"FILE",
"BLOCK",
"BLOCK_ARRAY",
"OBJECT"
],
"commands": {
"Gotty": {
"path": "/bin/bash",
"webshell": true,
"name": "Gotty Shell",
"description": "Start bash command in a gotty",
"parameters": {}
}
},
"image": {
"type": "image/png",
"data": ""
}
}
Now build application:
:~$ docker build --tag="tutorial:interactive_shell" -f Dockerfile .
Sending build context to Docker daemon 4.096kB
Step 1/5 : FROM ubuntu:latest
---> 7e0aa2d69a15
Step 2/5 : RUN apt-get update; apt-get install curl -y;
---> Using cache
---> 88667cf55ce3
Step 3/5 : RUN echo "Sarah Connor?" > /knock_knock ;
---> Running in 12a077247095
Removing intermediate container 12a077247095
---> b4218b95fca0
Step 4/5 : COPY NAE/AppDef.json /etc/NAE/AppDef.json
---> 9e360a999e3f
Step 5/5 : RUN curl --fail -X POST -d @/etc/NAE/AppDef.json https://cloud.nimbix.net/api/jarvice/validate
---> Running in 7dd760c1f55e
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 963 100 21 100 942 333 14952 --:--:-- --:--:-- --:--:-- 15285
{
"valid": true
}
Removing intermediate container 7dd760c1f55e
---> 3fbe9926327b
Successfully built 3fbe9926327b
Successfully tagged tutorial:interactive_shell
:~$
Then as for hello world application, create a dedicated repository on docker hub, and tag image and push it:
docker tag tutorial:interactive_shell oxedions/app-interactive_shell:v1
docker push oxedions/app-interactive_shell:v1
Once image is pushed, add application into Jarvice as before. Note that this time, we didn't base64 encoded a png image, so default image is used.
When clicking on application card, you should now have:
Click on Gotty Shell, and in the next window, observe that you can tune the command to be launched if desired (here default value is /bin/bash
as requested in AppDef.json file).
Click on Submit to launch the job.
Once job is started, it is possible to click on its card to open a new tab in web browser.
In the new tab, you now have an interactive bash shell. It is possible from here to check file created via Dockerfile (/knock_knock
):
4.2. On an existing application image¶
Sometime, in order to debug an app image, and launch entry point manually to check what is failing, it is useful to temporary switch it to an interactive shell. This basically allows you to "enter" the image inside the running context, and debug interactively.
There is no need to rebuild the image for that, we can live "hack" the AppDef inside Jarvice interface.
Let’s take our hello world application. Click on its burger, and select Edit.
Now, go into tab APPDEFF and copy all curent AppDef content from the text area. Edit this json into a text editor, and "hack" it this way (explanations follow):
{
"name": "Hello World App",
"description": "A very basic app thay says hello to world.",
"author": "Me",
"licensed": false,
"appdefversion": 2,
"classifications": [
"Uncategorized"
],
"machines": [
"*"
],
"vault-types": [
"FILE",
"BLOCK",
"BLOCK_ARRAY",
"OBJECT"
],
"commands": {
"Hello": {
"path": "/usr/bin/echo",
"interactive": false,
"name": "Echo with arguments",
"description": "Execute /usr/bin/echo with 'Hello World!' as argument.",
"parameters": {
"message": {
"name": "message",
"description": "hello world message",
"type": "CONST",
"value": "Hello World!",
"positional": true,
"required": true
}
}
},
"Gotty": {
"path": "/bin/bash",
"webshell": true,
"name": "Gotty shell",
"description": "Enter an interactive shell",
"parameters": {}
}
},
"image": {
"type": "image/png",
"data": "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"
}
}
Basically, only changes made are the additional command entry added called Gotty.
Then click on LOAD FROM FILE and select the hacked json file. This will update application AppDef inside the Jarvice cluster.
Now, when clicking on application to launch it, you can observe we have a second possible entry point into image (you may need to refresh page):
Using this entry point starts an interactive shell, and allows to debug inside the image: once logged, you can start manually the desired command, here /usr/bin/echo Hello World!
.
Once issues are solved, AppDef will be restored when pulling fixed image into Jarvice.
5. Review application parameters¶
Let’s review now available parameters in AppDef for commands entry. We will not cover all of them in this guide, only the most used ones. Please refer to Appdf commands object reference and Appdf parameters object reference for more details.
Target here is to test most of the possibilities offered.
5.1. Commands¶
Let's focus on commands, which is the level above parameters:
...
"commands": {
"Hello": {
"path": "/usr/bin/echo",
"interactive": false,
"name": "Echo with arguments",
"description": "Execute /usr/bin/echo with 'Hello World!' as argument.",
"parameters": {}
}
}
...
Basic commands can take the following settings: (note: an * means key is mandatory)
- path*: Command entry point which is run when application start.
- name*: Command’s name, which is used as the title of the command in the Jarvice interface.
- description*: Description of the command’s functionality that is used in the Jarvice interface.
- interactive: (default to
false
) defines if application execution should return to user an URL to interact with execution (automatically forced to true if webshell or desktop are set to true). - webshell*: (default to
false
) run command inside a gotty shell. - desktop*: (default to
false
) run command inside Jarvice Xfce desktop (assumes image includes https://github.com/nimbix/jarvice-desktop). - mpirun*: (default to
false
) run command under mpi environment. - verboseinit*: (default to
false
) enable verbose init app execution phase, including parameters passed to command. - cmdscript: if set, will trigger cmdscript execution mode. Value of this key will be written into a file matching path* set above, and executed. This allows injecting scripts directly from AppDef json. User can pass a single line plain text script, or a base64 encoded script (auto detected, allows multilines scripts).
- parameters*: Parameters are used to construct the arguments passed to the command. If no parameters are needed, set it to
{}
When using standalone binary applications, no parameters are needed. However, most of the time, some additional settings are required by applications' binary (input file path, parameters, etc.). You may also wish that user could define some application settings.
This is where parameters are needed.
5.2. Commands parameters¶
Commands parameters allows to:
- Force specific read only values to be passed to application's entry point as arguments (CONST)
- Allows users to tune settings to be passed to application's entry point or scripts (with mandatory or optional values)
Remember the Command parameter to be set for interactive gotty shell example:
...
"commands": {
"echo": {
"path": "/bin/echo",
"interactive": true,
"name": "Say hello",
"description": "Say hello example",
"parameters": {
"command": { <<<<<<<<<<<<<< This
"name": "To who?",
"description": "Say hello to who?",
"type": "STR",
"value": "hello world!",
"positional": true,
"required": true
}
}
}
}
...
It is possible to create a detailed environment for an application with a variety of input format.
There are multiple available parameters types: CONST
, STR
, INT
, FLOAT
, RANGE
, BOOL
, selection
, FILE
, UPLOAD
.
A detailed list of available values/keys is available.
In order to simplify visual understanding, you can find bellow a table with a screenshot of what each type would generate in job user interface:
Type | Screenshot |
---|---|
CONST | Nothing will be displayed in interface |
STR | |
INT | |
RANGE | |
FLOAT | |
BOOL | |
selection | |
FILE | |
UPLOAD |
In order to understand all possible combination, lets create a specific application. This application makes no sense, this is for testing and understanding purposes.
Note: if you want to play with parameters, remember that you can directly edit AppDef Json in Jarvice UI by editing application. For testing purposed, do not bother building and pushing/pulling an image: simply edit directly in Jarvice UI by pushing updated AppDef jsons.
Create a new application called app-reverse_engineer, with the following Dockerfile:
FROM ubuntu:latest
RUN apt-get update; apt-get install curl -y;
COPY launch.sh /launch.sh
RUN chmod +x /launch.sh;
COPY NAE/AppDef.json /etc/NAE/AppDef.json
RUN curl --fail -X POST -d @/etc/NAE/AppDef.json https://cloud.nimbix.net/api/jarvice/validate
Then create in same folder a file called launch.sh
with the following content:
#!/usr/bin/env bash
echo Launch script is starting
echo
echo Checking arguments passed directly to entry point
cat << EOF
Arguments passed: $@
EOF
echo
echo Checking job environment
cat /etc/JARVICE/jobenv.sh
echo
echo Checking job information
cat /etc/JARVICE/jobinfo.sh
echo
echo Checking job cores and nodes
echo - cores
cat /etc/JARVICE/cores
echo - nodes
cat /etc/JARVICE/nodes
echo
echo Checking uploaded file
cat /opt/JARVICE/file.txt
echo
echo Script end
Note: we are using cat
and not echo
to display parameters, to avoid echo evaluating some values.
Create the following AppDef.json
file with all possible types available, sometime combined with different settings.
{
"name": "Reverse engineer application",
"description": "A environment test application",
"author": "Me",
"licensed": false,
"appdefversion": 2,
"classifications": [
"Uncategorized"
],
"machines": [
"*"
],
"vault-types": [
"FILE",
"BLOCK",
"BLOCK_ARRAY",
"OBJECT"
],
"commands": {
"Hello": {
"path": "/launch.sh",
"interactive": false,
"name": "Test script",
"description": "Execute launch.sh",
"parameters": {
"const_1": {
"name": "const_1_name",
"description": "const_1_description",
"type": "CONST",
"value": "const_1_value",
"positional": true,
"required": true
},
"const_2": {
"name": "const_2_name",
"description": "const_2_description",
"type": "CONST",
"value": "const_2_value",
"positional": false,
"required": true
},
"const_3": {
"name": "const_3_name",
"description": "const_3_description",
"type": "CONST",
"value": "const_3_value",
"positional": true,
"required": true,
"variable": true
},
"str_1": {
"name": "str_1_name",
"description": "str_1_description",
"type": "STR",
"value": "str_1_value",
"positional": true,
"required": true,
"variable": false
},
"str_2": {
"name": "str_2_name",
"description": "str_2_description",
"type": "STR",
"value": "str_2_value",
"positional": true,
"required": true,
"variable": true
},
"str_3": {
"name": "str_3_name",
"description": "str_3_description",
"type": "STR",
"value": "str_3_value",
"positional": true,
"required": false,
"variable": false
},
"int_1": {
"name": "int_1_name",
"description": "int_1_description",
"type": "INT",
"value": 2,
"min": 0,
"max": 10,
"positional": true,
"required": true,
"variable": false
},
"float_1": {
"name": "float_1_name",
"description": "float_1_description",
"type": "FLOAT",
"value": "1.2",
"min": "0.0",
"max": "10.0",
"positional": true,
"required": true,
"variable": false
},
"range_1": {
"name": "range_1_name",
"description": "range_1_description",
"type": "RANGE",
"value": 2,
"min": 0,
"max": 10,
"step": 1,
"positional": true,
"required": true,
"variable": false
},
"bool_1": {
"name": "bool_1_name",
"description": "bool_1_description",
"type": "BOOL",
"value": true,
"positional": true,
"required": true,
"variable": false
},
"selection_1": {
"name": "selection_1_name",
"description": "selection_1_description",
"type": "selection",
"values": ["selection_1_val1","selection_1_val2"],
"positional": true,
"required": true,
"variable": false
},
"file_1": {
"name": "file_1_name",
"description": "file_1_description",
"type": "FILE",
"positional": true,
"required": true,
"variable": false
},
"upload_1": {
"name": "upload_1_name",
"description": "upload_1_description",
"type": "UPLOAD",
"target": "file.txt",
"filter": ".txt",
"size": 4096,
"required": true
}
}
}
},
"image": {
"type": "image/png",
"data": ""
}
}
Build application, create a repository to host it, and pull it into Jarvice. Once done, click on Test Script to open application's job settings interface.
In GENERAL, you can observe all parameters set in our AppDef file.
Observe also in OPTIONAL tab the non-required STR
value set in AppDef.
Ensure all fields are set (for this example a txt file with coucou
as content was uploaded in upload_1 parameter), and click on Submit to submit application job.
Once job completed, check job output log, you should have:
INIT[1]: Initializing networking...
INIT[1]: Reading keys...
INIT[1]: Finalizing setup in application environment...
INIT[1]: WARNING: Cross Memory Attach not available for MPI applications
INIT[1]: Platform fabric and MPI libraries successfully deployed
INIT[1]: Detected preferred MPI fabric provider: tcp
INIT[1]: Securing application environment...
INIT[1]: Configuring user: nimbix ...
INIT[1]: /home/nimbix does not exist or is external
INIT[1]: Waiting for job configuration before executing application...
INIT[1]: hostname: jarvice-job-11859-nw2fm
INIT[64]: HOME=/home/nimbix
################################################################################
Launch script is starting
Checking arguments passed directly to entry point
Arguments passed: const_2 const_2_value const_1_value str_1_value str_3_value 2 1.2 0 bool_1 selection_1_val1 /data/jellyfish-3-mbps-hd-h264.mkv
Checking job environment
:
const_3=\c\o\n\s\t\_\3\_\v\a\l\u\e
str_2=\s\t\r\_\2\_\v\a\l\u\e
Checking job information
:
JOB_NAME="20220420201746-IZP8L-bleveugle-reverse_engineer-bleveugle_s1"
JOB_LABEL=
JOB_PRIVATEIP=10.88.4.13
JOB_PUBLICIP=
Checking job cores and nodes
- cores
jarvice-job-11864-mrdvs
- nodes
jarvice-job-11864-mrdvs
Checking files in /opt
coucou
Script end
You can see that:
const_2
value was passed first in arguments, becausepositional
isfalse
, and was a combination of the main entry key and its value. This is useful to define parameters like-c blue
:
"-c": {
"name": "define color",
"description": "-c parameters allows to change color",
"type": "CONST",
"value": "blue",
"positional": false,
"required": true
},
const_3
andstr_2
were not passed as arguments, but instead are available in file/etc/JARVICE/jobenv.sh
, which can be sourced from scripts to be used as variables.- Other values were passed in the same order than defined in the AppDef file as arguments.
- Uploaded file was correctly uploaded as
/opt/file.txt
.
We have seen all possible and existing parameters. You can now use the ones needed to create tunable applications for Jarvice.
5.3. Commands parameters advanced settings¶
It is possible to use conditionals on parameters keys, combined with a boolean value, in order to trigger specific parameters only when needed.
Scenario. We have an application that accepts 2 kind of licences : either a full file path, or a remote server (ip:port).
We can create a boolean, to only pass to application needed value (and prevent possible user miss-usage).
"parameters": {
"license": {
"name": "License argument",
"description": "License argument",
"type": "CONST",
"value": "-i",
"required": true
},
"license_file_path": {
"name": "License file path",
"description": "License file path provided by your beloved administrator",
"type": "STR",
"value": "",
"if": [
"license_is_file"
],
"required": false
},
"license_server": {
"name": "License server",
"description": "License server ip:port to reach",
"type": "STR",
"value": "black",
"variable": true,
"ifnot": [
"license_is_file"
],
"required": false
},
"license_is_file": {
"name": "Use a file based license",
"description": "Use a file based license instead of a remote server?",
"type": "BOOL",
"value": false,
"required": true
}
}
In this specific case, if license_is_file
boolean is true, then command will be: -i license_file_path.value
, else it will be -i license_server.value
.
6. Non interactive application¶
Non-interactive applications are very common.
Users specify some settings via provided application command parameters (input file or folder, solver to use, tunings, etc.) and launch job. Job executes then in background, and user can collect result once job has ended.
Users can also check application logs in their user space on Jarvice interface.
Let’s create a very basic ffmpeg application that will be used to convert uploaded video to h265
codec. User will be able to set crf
(video quality). To simplify this tutorial, we will not consider anything else than video (sound streams, subtitles, etc. will be ignored).
Note that you can easily find video samples here: jell.yfish.us.
6.1. Dockerfile¶
We can already re-use the multi stages example above:
# Stage 0, let’s give it a name for convenience: download_extract_ffmpeg
FROM ubuntu:latest AS download_extract_ffmpeg
RUN apt-get update; apt-get install tar xz-utils wget -y;
RUN wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz;
RUN tar xvJf ffmpeg-release-amd64-static.tar.xz;
RUN cd ffmpeg-5.0.1-amd64-static; cp ffmpeg /usr/bin/ffmpeg;
# Stage 1, we simply import /usr/bin/ffmpeg from stage download_extract_ffmpeg
FROM ubuntu:latest
RUN apt-get update; apt-get install curl -y; apt-get clean;
COPY --from=download_extract_ffmpeg /usr/bin/ffmpeg /usr/bin/ffmpeg
COPY NAE/AppDef.json /etc/NAE/AppDef.json
RUN curl --fail -X POST -d @/etc/NAE/AppDef.json https://cloud.nimbix.net/api/jarvice/validate
RUN mkdir -p /etc/NAE && touch /etc/NAE/AppDef.json
6.2. AppDef¶
Let’s now create a related AppDef.json
file:
{
"name": "Video conversion",
"description": "A basic video conversion app, for a tutorial",
"author": "Me",
"licensed": false,
"appdefversion": 2,
"classifications": [
"Uncategorized"
],
"machines": [
"*"
],
"vault-types": [
"FILE",
"BLOCK",
"BLOCK_ARRAY",
"OBJECT"
],
"commands": {
"Convert": {
"path": "/usr/bin/ffmpeg",
"interactive": false,
"name": "Convert video",
"description": "Convert video to h265 using ffmpeg",
"parameters": {
"-i": {
"name": "Input file parameter",
"description": "File to be processed parameter",
"type": "CONST",
"value": "-i",
"positional": true,
"required": true
},
"input_file": {
"name": "Input file path",
"description": "File to be processed parameter path",
"type": "FILE",
"positional": true,
"required": true
},
"-c_v": {
"name": "Video codec parameter",
"description": "Video codec parameter",
"type": "CONST",
"value": "-c:v",
"positional": true,
"required": true
},
"libx265": {
"name": "Video codec h265",
"description": "Video codec h265",
"type": "CONST",
"value": "libx265",
"positional": true,
"required": true
},
"-crf": {
"name": "crf parameter",
"description": "crf parameter (quality)",
"type": "CONST",
"value": "-crf",
"positional": true,
"required": true
},
"crf_value": {
"name": "crf value",
"description": "crf value, between 0 (quality lossless) and 51 (worse quality). Default is 28.",
"type": "RANGE",
"value": 28,
"min": 0,
"max": 51,
"step": 1,
"positional": true,
"required": true
},
"output_file": {
"name": "Output file",
"description": "Output file path and name. Must be /data/XXX .",
"type": "STR",
"value": "/data/out.mkv",
"positional": true,
"required": true
}
}
}
},
"image": {
"type": "image/png",
"data": ""
}
}
User will be able to set CRF video quality value, and output file path.
6.3. Run application¶
Build and upload to cluster application.
Launch a job, using an input sample. In this test, we used jellyfish-3-mbps-hd-h264.mkv
file.
Set CRF quality. We let 28 here as default.
Then submit. Note that video processing benefits from multi cores, and so you should select a machine with multiple cores. Memory doesn't matter here, 1Gb is enough.
Once processed, you should see the following result:
INIT[1]: Initializing networking...
INIT[1]: Reading keys...
INIT[1]: Finalizing setup in application environment...
INIT[1]: WARNING: Cross Memory Attach not available for MPI applications
INIT[1]: Platform fabric and MPI libraries successfully deployed
INIT[1]: Detected preferred MPI fabric provider: tcp
INIT[1]: Securing application environment...
INIT[1]: Configuring user: nimbix ...
INIT[1]: /home/nimbix does not exist or is external
INIT[1]: Waiting for job configuration before executing application...
INIT[1]: hostname: jarvice-job-12180-z9ldv
INIT[64]: HOME=/home/nimbix
################################################################################
ffmpeg version 5.0.1-static https://johnvansickle.com/ffmpeg/ Copyright (c) 2000-2022 the FFmpeg developers
built with gcc 8 (Debian 8.3.0-6)
configuration: --enable-gpl --enable-version3 --enable-static --disable-debug --disable-ffplay --disable-indev=sndio --disable-outdev=sndio --cc=gcc --enable-fontconfig --enable-frei0r --enable-gnutls --enable-gmp --enable-libgme --enable-gray --enable-libaom --enable-libfribidi --enable-libass --enable-libvmaf --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-librubberband --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libvorbis --enable-libopus --enable-libtheora --enable-libvidstab --enable-libvo-amrwbenc --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libdav1d --enable-libxvid --enable-libzvbi --enable-libzimg
libavutil 57. 17.100 / 57. 17.100
libavcodec 59. 18.100 / 59. 18.100
libavformat 59. 16.100 / 59. 16.100
libavdevice 59. 4.100 / 59. 4.100
libavfilter 8. 24.100 / 8. 24.100
libswscale 6. 4.100 / 6. 4.100
libswresample 4. 3.100 / 4. 3.100
libpostproc 56. 3.100 / 56. 3.100
Input #0, matroska,webm, from '/data/jellyfish-3-mbps-hd-h264.mkv':
Metadata:
encoder : libebml v1.2.0 + libmatroska v1.1.0
creation_time : 2016-02-06T03:58:03.000000Z
Duration: 00:00:30.03, start: 0.000000, bitrate: 2984 kb/s
Stream #0:0(eng): Video: h264 (High), yuv420p(tv, bt709, progressive), 1920x1080 [SAR 1:1 DAR 16:9], 29.97 fps, 29.97 tbr, 1k tbn (default)
Stream mapping:
Stream #0:0 -> #0:0 (h264 (native) -> hevc (libx265))
Press [q] to stop, [?] for help
x265 [info]: HEVC encoder version 3.5+1-f0c1022b6
x265 [info]: build info [Linux][GCC 8.3.0][64 bit] 8bit+10bit+12bit
x265 [info]: using cpu capabilities: MMX2 SSE2Fast LZCNT SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
x265 [info]: Main profile, Level-4 (Main tier)
x265 [info]: Thread pool created using 16 threads
x265 [info]: Slices : 1
x265 [info]: frame threads / pool features : 4 / wpp(17 rows)
x265 [info]: Coding QT: max CU size, min CU size : 64 / 8
x265 [info]: Residual QT: max TU size, max depth : 32 / 1 inter / 1 intra
x265 [info]: ME / range / subpel / merge : hex / 57 / 2 / 3
x265 [info]: Keyframe min / max / scenecut / bias : 25 / 250 / 40 / 5.00
x265 [info]: Lookahead / bframes / badapt : 20 / 4 / 2
x265 [info]: b-pyramid / weightp / weightb : 1 / 1 / 0
x265 [info]: References / ref-limit cu / depth : 3 / off / on
x265 [info]: AQ: mode / str / qg-size / cu-tree : 2 / 1.0 / 32 / 1
x265 [info]: Rate Control / qCompress : CRF-40.0 / 0.60
x265 [info]: tools: rd=3 psy-rd=2.00 early-skip rskip mode=1 signhide tmvp
x265 [info]: tools: b-intra strong-intra-smoothing lslices=6 deblock sao
Output #0, matroska, to '/data/out.mkv':
Metadata:
encoder : Lavf59.16.100
Stream #0:0(eng): Video: hevc, yuv420p(tv, bt709, progressive), 1920x1080 [SAR 1:1 DAR 16:9], q=2-31, 29.97 fps, 1k tbn (default)
Metadata:
encoder : Lavc59.18.100 libx265
Side data:
cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A
frame= 1 fps=0.0 q=0.0 size= 3kB time=00:00:00.00 bitrate=N/A speed= 0x frame= 25 fps=0.0 q=0.0 size= 3kB time=00:00:00.00 bitrate=N/A speed= 0x frame= 28 fps= 11 q=0.0 size= 3kB time=00:00:00.00 bitrate=N/A speed= 0x frame= 30 fps=9.5 q=0.0 size= 3kB time=00:00:00.00 bitrate=N/A speed= 0x frame= 32 fps=7.7 q=41.8 size= 3kB time=-00:00:00.03 bitrate=N/A speed=N/A frame= 35 fps=7.1 q=41.4 size= 3kB time=00:00:00.06 bitrate= 357.3kbits/s speed=0.frame= 900 fps=4.2 q=47.9 Lsize= 1994kB time=00:00:29.93 bitrate= 545.7kbits/s speed=0.138x
video:1984kB audio:0kB subtitle:0kB other streams:0kB global headers:2kB muxing overhead: 0.470304%
x265 [info]: frame I: 4, Avg QP:40.24 kb/s: 2734.29
x265 [info]: frame P: 264, Avg QP:42.01 kb/s: 1366.43
x265 [info]: frame B: 632, Avg QP:46.71 kb/s: 181.45
x265 [info]: Weighted P-Frames: Y:5.7% UV:5.7%
x265 [info]: consecutive B-frames: 1.5% 2.6% 71.3% 7.8% 16.8%
encoded 900 frames in 216.00s (4.17 fps), 540.39 kb/s, Avg QP:45.30
Launch Jarvice files manager, and see the compressed video:
Using h265 instead of h264, we reduced video size. This is however a very basic example, and video quality was also reduced. A real ffmpeg application would need much more settings available to users. This was however enough as an example.
7. Basic shell interactive application¶
It is possible to obtain an interactive shell directly from the browser. This can be useful for applications that requires interactions with users (or have to be manually launched) and that do not require a full GUI desktop.
We are going to create a very basic and naive python-based calculator, as an example.
7.1. Create image¶
Create Dockerfile, that includes python3 and our basic application.
FROM ubuntu:latest
RUN apt-get update \
&& apt-get install -y python3-pip python3-dev curl \
&& cd /usr/local/bin \
&& ln -s /usr/bin/python3 python \
&& pip3 install --upgrade pip
COPY calculator.py /calculator.py
RUN chmod +x /calculator.py;
COPY NAE/AppDef.json /etc/NAE/AppDef.json
RUN curl --fail -X POST -d @/etc/NAE/AppDef.json https://cloud.nimbix.net/api/jarvice/validate
RUN mkdir -p /etc/NAE && touch /etc/NAE/AppDef.json
7.2. Create calculator.py file¶
Create now the basic python-based application, in file calculator.py
:
#!/usr/bin/env python3
print('Calculator example')
while True:
eval_output = eval(input("Enter any operation of your choice: "))
print("Result: " + str(eval_output))
7.3. Create AppDef¶
Create AppDef file, with target path to gotty shell, and command to our application.
{
"name": "Gotty shell command",
"description": "Run a command in a gotty webshell on image",
"author": "Nimbix, Inc.",
"licensed": true,
"appdefversion": 2,
"classifications": [
"Uncategorized"
],
"machines": [
"*"
],
"vault-types": [
"FILE",
"BLOCK",
"BLOCK_ARRAY",
"OBJECT"
],
"commands": {
"Gotty": {
"path": "/calculator.py",
"webshell": true,
"interactive": true,
"name": "Interactive webshell",
"description": "Start a command in a gotty webshell",
"parameters": {}
}
},
"image": {
"type": "image/png",
"data": ""
}
}
7.4. Launch and use¶
Once built and submitted to cluster, it is possible to join session by clicking on "Click here to connect".
It should open a new tab in your web browser, and connect you directly to a shell running the application:
Note also that you can replace application command path (/calculator.py
here) by a full shell instead of an application, like /usr/bin/bash
, to fully manipulate image.
When debugging an application, it can be a real added value to add a second entry to image, with a gotty shell combined to the bash shell to be able to interactively launch scripts and debug.
8. Basic UI interactive application¶
Some applications need a full GUI to be used, with a windows manager.
It is possible to get a full XFCE desktop by adding needed dependencies during docker build step, and using the correct command entry point.
In this example, we are going to create a GIMP (image manipulation software) application. Note that we are using here Ubuntu, but you can also use any RHEL derivate distribution. Refer to https://github.com/nimbix/jarvice-desktop for more details.
8.1. Create image¶
Create Dockerfile, with a specific RUN
that bootstraps Nimbix default desktop. We then install gimp.
FROM ubuntu:latest
# Install jarvice-desktop tools and desktop from Nimbix repository
RUN apt-get -y update && \
DEBIAN_FRONTEND=noninteractive apt-get -y install curl --no-install-recommends && \
curl -H 'Cache-Control: no-cache' \
https://raw.githubusercontent.com/nimbix/jarvice-desktop/master/install-nimbix.sh \
| bash
RUN apt-get -y install gimp;
COPY NAE/AppDef.json /etc/NAE/AppDef.json
RUN curl --fail -X POST -d @/etc/NAE/AppDef.json https://cloud.nimbix.net/api/jarvice/validate
RUN mkdir -p /etc/NAE && touch /etc/NAE/AppDef.json
8.2. Create AppDef¶
Create now AppDef file, with /usr/bin/gimp
as target path:
{
"name": "myapp",
"description": "",
"author": "",
"licensed": true,
"appdefversion": 2,
"classifications": [
"Uncategorized"
],
"machines": [
"*"
],
"vault-types": [
"FILE",
"BLOCK",
"BLOCK_ARRAY",
"OBJECT"
],
"commands": {
"GUI": {
"path": "/usr/bin/gimp",
"desktop": true,
"interactive": true,
"name": "Gimp GUI",
"description": "Run a GIMP in a GUI desktop, and connect interactively directly from your web browser (requires Nimbix Desktop in image).",
"parameters": {}
}
},
"image": {
"data": "",
"type": "image/png"
}
}
8.3. Launch application¶
Once application has been built and uploaded to Jarvice PushToCompute, submit a new job.
Once job is started, simply click on job's "Click here to connect":
This will open a new tab in your browser, in which after few seconds you will be connected to a full GUI desktop, with Gimp opened.
BEWARE! If you close gimp window (so end execution of /usr/bin/gimp
), job will terminate.
9. MPI application¶
Jarvice embed an OpenMPI version of MPI, that can be used to build and run MPI applications. It is also possible for users to use their own MPI libraries and runtime, but this is out of the scope of this tutorial. However, some advices are given bellow.
9.1. Basic benchmark application¶
In this example, we are going to download and build the Intel MPI Benchmark tool, and run it in parallel on the cluster.
Create folder app-mpi and NAE subfolder:
mkdir app-mpi/NAE/ -p
Then create the Dockerfile. We are going to extract Jarvice OpenMPI from jarvice_mpi image, and use it to build our application. There is no need to keep Jarvice OpenMPI in final image, as it is automatically side-loaded (available) during execution on cluster.
# Load jarvice_mpi image as JARVICE_MPI
FROM us-docker.pkg.dev/jarvice/images/jarvice_mpi:4.1 as JARVICE_MPI
# Multistage to optimise, as image does not need to contain jarvice_mpi
# components, these are side loaded during job containers init.
FROM ubuntu:latest as buffer
# Grab jarvice_mpi from JARVICE_MPI
COPY --from=JARVICE_MPI /opt/JARVICE /opt/JARVICE
# Install needed dependencies to download and build Intel MPI Benchmark
RUN apt-get update; apt-get install -y wget curl gcc g++ git make bash; apt-get clean;
# Build IMB-MPI1 which is enough for basic testing
# Note that we are sourcing Jarcice OpenMPI environment using the provided /opt/JARVICE/jarvice_mpi.sh
RUN bash -c 'git clone https://github.com/intel/mpi-benchmarks.git; cd mpi-benchmarks; \
source /opt/JARVICE/jarvice_mpi.sh; sed -i 's/mpiicc/mpicc/' src_cpp/Makefile; \
sed -i 's/mpiicpc/mpicxx/' src_cpp/Makefile; make IMB-MPI1;'
# Create final image from Ubuntu
FROM ubuntu:latest
# Grab MPI benchmarks binaries built before using jarvice-mpi
COPY --from=buffer /mpi-benchmarks/IMB-MPI1 /IMB-MPI1
# Integrate AppDef file
COPY NAE/AppDef.json /etc/NAE/AppDef.json
And create the AppDef.json file with the following content:
{
"name": "mpiapp",
"description": "An mpi application",
"author": "Me",
"licensed": false,
"appdefversion": 2,
"classifications": [
"Uncategorized"
],
"machines": [
"*"
],
"vault-types": [
"FILE",
"BLOCK",
"BLOCK_ARRAY",
"OBJECT"
],
"commands": {
"imb": {
"path": "/IMB-MPI1",
"mpirun": true,
"verboseinit": true,
"interactive": false,
"name": "Intel Mpi Benchmark",
"description": "Run the Intel MPI Benchmark MPI1 over multiple nodes.",
"parameters": {}
}
},
"image": {
"data": "",
"type": "image/png"
}
}
Then simply build the image, push it, and load it into Jarvice Push to Compute.
Note that we set mpirun
to true, to allow native Jarvice MPI parallel execution.
Note also that we set verboseinit
to true. This is optional and allows us to see exactly what is executed at start.
At job submission, use Machine type select box, and Cores range selector to choose more than 1 machine. Then submit the job.
If all goes well, you should see the MPI benchmark running on the cluster. It should not take more than few minutes. If it hangs, you may have network issues to investigate with your cluster administrator.
Note that at execution start, verboseinit allowed to see few interesting steps:
We can see the executed command:
INIT[1]: VERBOSE - argv[3] : PATH=/opt/JARVICE/openmpi/bin/:/opt/JARVICE/bin/:$PATH LD_LIBRARY_PATH=/opt/JARVICE/openmpi/lib/:/opt/JARVICE/lib/:$LD_LIBRARY_PATH /opt/JARVICE/openmpi/bin/mpirun -x PATH -x LD_LIBRARY_PATH -N 4 --hostfile /etc/JARVICE/nodes /IMB-MPI1
And also the ssh connectivity test between nodes, to ensure smooth MPI execution:
INIT[1]: Starting SSHD server...
INIT[1]: Checking all nodes can be reached through ssh...
INIT[1]: VERBOSE - Attempting ssh connection to jarvice-job-101593-fw84g.
INIT[1]: VERBOSE - Success connecting to jarvice-job-101593-fw84g.
INIT[1]: VERBOSE - Attempting ssh connection to jarvice-job-101593-vwzk7.
INIT[1]: VERBOSE - Success connecting to jarvice-job-101593-vwzk7.
INIT[1]: SSH test success!
9.2. Using another MPI implementation¶
If you wish to use your own MPI implementation (Intel MPI, HPCX, etc), you need to uses your own script as path to start your application.
You can rely on the following example, as a starting point, and also refer to the "MPI Application Configuration Guide" of this documentation.
Note that in the current script, as an example, we also added a CASE_FOLDER variable, to be passed by user, to be able to set a working directory. This can be useful with some applications.
#!/usr/bin/env bash
# Source the JARVICE job environment variables
echo "Sourcing JARVICE environment..."
[[ -r /etc/JARVICE/jobenv.sh ]] && source /etc/JARVICE/jobenv.sh
[[ -r /etc/JARVICE/jobinfo.sh ]] && source /etc/JARVICE/jobinfo.sh
# Gather job environment and process input
echo "Processing computational environment..."
CASE_FOLDER=
MPIHOSTS=
CORES=
while [[ -n "$1" ]]; do
case "$1" in
case_folder)
shift
CASE_FOLDER="$1"
;;
*)
echo "Invalid argument: $1" >&2
exit 1
;;
esac
shift
done
CASE_FOLDER=$(dirname "$CASE_FOLDER")
echo " - Using Case directory: $CASE_FOLDER"
CORES=$(cat /etc/JARVICE/cores | wc -l )
NBNODES=$(cat /etc/JARVICE/nodes | wc -l)
if [[ "$NBNODES" -gt 1 ]]; then
MPIHOSTS="/etc/JARVICE/cores"
NBPROCPERNODE=$((CORES/NBNODES))
echo "MPI environment: "
echo " - mpi_hosts list file: $MPIHOSTS"
echo " - number of process per nodes: $NBPROCPERNODE"
echo " - cores: $CORES"
else
echo "MPI environment: "
echo " - cores: $CORES"
fi
# Load mpi environment
echo "Loading Jarvice OpenMPI environment..."
source /opt/JARVICE/jarvice_mpi.sh;
# Enter case directory
echo "Entering case folder $CASE_FOLDER ..."
cd "$CASE_FOLDER"
# Execute command, add verbosity to see exact command executed
# Also use full path for binaries, even mpirun, to avoid issues on slave nodes
echo "Executing application."
echo "First command explicitly shows who is running MPI (help understanding)."
echo "Second command is the real application."
date
set -x
/opt/JARVICE/openmpi/bin/mpirun -x PATH -x LD_LIBRARY_PATH -np $CORES --hostfile /etc/JARVICE/cores hostname
/opt/JARVICE/openmpi/bin/mpirun -x PATH -x LD_LIBRARY_PATH -np $CORES --hostfile /etc/JARVICE/cores /opt/my_parallel_application/bin/mpi_application
set +x
date
And the CASE_FOLDER associated would be a parameter in AppDef.json:
"case_folder": {
"required": true,
"type": "STR",
"value": "",
"name": "Case folder."
}
10. Script based application¶
It is possible to directly inject script to be executed into the AppDef.json file, allowing advanced usage of application images.
Note that by default, you cannot execute any privilege escalation, and so sudo cannot be used to install packages for example in such scripts. It is however possible, if allowed by cluster administrator, to enable privilege execution during apps execution. This feature is however out of the scope of this tutorial.
10.1. Plain text script¶
First possibility is to use plain text script.
In this example, we are not going to create an image. We will only rely on Ubuntu default image, and directly inject our AppDef.json into Push To Compute interface.
Create on your local system file AppDef_script.json
with the following content:
{
"name": "Script test",
"description": "Script test",
"author": "Me",
"licensed": false,
"appdefversion": 2,
"classifications": [
"Uncategorized"
],
"machines": [
"*"
],
"vault-types": [
"FILE",
"BLOCK",
"BLOCK_ARRAY",
"OBJECT"
],
"commands": {
"Script_raw": {
"path": "/myscript.sh",
"interactive": false,
"name": "Script Raw",
"description": "Run a raw script",
"cmdscript": "#!/bin/sh\necho I love pizza\necho I am running on $(hostname)",
"parameters": {}
}
},
"image": {
"data": "",
"type": "image/png"
}
}
Note that we added a key called cmdscript
that contains our script content, and path
will be the path were script content will be written before being executed.
Now, create a new App, and in first tab, GENERAL, set an App ID, and use docker.io/ubuntu:latest as app base image.
Then go to tab APPDEF, and upload AppDef_script.json
file created before.
Then validate. Our application is ready.
Submit a job, and you should see the script executing.
However, since json format do not support multiline strings, this is a one-line script, which can be a pain when dealing with large scripts.
When using complex scripts, you can base64 encode them (see below).
10.2. Base64 encoded script¶
When dealing with complex scripts, it might be simpler to base64 encode them, so that you can pass them as a single line string into a json file.
Jarvice will auto-detect that script provided is encoded, and will decode it on the fly.
Create a file called myscript.sh
with the following content:
#!/bin/bash
for i in 1 2 3 4 5
do
echo "Welcome $i times"
done
Then, encode it:
base64 -w 0 myscript.sh
You should obtain:
IyEvYmluL2Jhc2gKZm9yIGkgaW4gMSAyIDMgNCA1CmRvCiAgIGVjaG8gIldlbGNvbWUgJGkgdGltZXMiCmRvbmUKCg==
Note that to decode it, you can use:
echo "IyEvYmluL2Jhc2gKZm9yIGkgaW4gMSAyIDMgNCA1CmRvCiAgIGVjaG8gIldlbGNvbWUgJGkgdGltZXMiCmRvbmUKCg==" | base64 -d
Now, update file AppDef_script.json
and replace previous text script by the base64 encoded string:
{
"name": "Script test",
"description": "Script test",
"author": "Me",
"licensed": false,
"appdefversion": 2,
"classifications": [
"Uncategorized"
],
"machines": [
"*"
],
"vault-types": [
"FILE",
"BLOCK",
"BLOCK_ARRAY",
"OBJECT"
],
"commands": {
"Script_raw": {
"path": "/myscript.sh",
"interactive": false,
"name": "Script Raw",
"description": "Run a raw script",
"cmdscript": "IyEvYmluL2Jhc2gKZm9yIGkgaW4gMSAyIDMgNCA1CmRvCiAgIGVjaG8gIldlbGNvbWUgJGkgdGltZXMiCmRvbmUKCg==",
"parameters": {}
}
},
"image": {
"data": "",
"type": "image/png"
}
}
Edit script_raw app, and upload this new file to replace previous AppDef, and save.
Then launch a new job. You should see the script execution.
11. Server application¶
JARVICE system allows to submit server applications, that can be associated with a public ip to be reached. This could be any kind of server, as long as ports are correctly defined in the AppDef.json file.
To explain the concept, we are going in this part to deploy 2 kinds of servers. A basic ssh server, and a Minecraft server. First one will be static, and users will be able to submit their ssh public key, while the second one will allow job user to interact with the server (since a Minecraft server allows many live tuning commands), combining public ip and a webshell seen before.
11.1. Basic ssh server¶
A basic ssh server can be very useful, for many purposes:
- Use scp/sftp with tools like Filezilla/WinSCP or simple shell commands to upload complex or very large data to persistent vault.
- Interactively manipulate remote data in vault.
- Build an advanced application that would need more than what current webshell/desktop can offer.
- Etc.
To simplify process, we will allow user to upload via an STR field a public key to be setup during startup to connect to server. Also, since the ssh server is already embed by Jarvice, we can use a very minimal image, like an Alpine, so our application will be ultra small.
Please understand also that while the Jarvice embed ssh server listens on port 2222, Jarvice automaticaly map port 22 of the public ip to port 2222 of the running container. This means we will only need to open port 2222 on the application side, and end user will just have to reach ssh 22 default port on his/her side.
First, create image. Dockerfile is very simple:
FROM alpine:latest
COPY NAE/AppDef.json /etc/NAE/AppDef.json
RUN mkdir -p /etc/NAE && touch /etc/NAE/AppDef.json
Then, our NAE/AppDef.json file will be the following (the long text is a base64 encoded png icon for openssh logo):
{
"name": "SSH server",
"description": "SSH server",
"author": "",
"licensed": true,
"appdefversion": 2,
"classifications": [
"Middle Earth"
],
"machines": [
"*"
],
"vault-types": [
"FILE",
"BLOCK",
"BLOCK_ARRAY",
"OBJECT"
],
"commands": {
"custom": {
"path": "/start.sh",
"interactive": true,
"cmdscript": "#!/bin/sh\necho $pubkey >> $HOME/.ssh/authorized_keys\necho You can now login as $(whoami)...\necho Please kill this job once done using it.\nsleep infinity",
"publicip": true,
"ports": [
"2222/tcp"
],
"name": "Launch ssh server",
"description": "Start a remote ssh server, to reach /data folder.",
"verboseinit": true,
"parameters": {
"pubkey": {
"name": "Public Key ssh key",
"description": "Add here your public ssh key to use to login on ssh server.",
"type": "STR",
"value": "",
"positional": true,
"variable": true,
"required": true
}
}
}
},
"image": {
"data": "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",
"type": "image/png"
}
}
Now, just import application in PushToCompute as usual, and pull it. After few seconds, you should see the application with the openssh icon on it:
And when launching job, user can set his/her ssh public key to be used to authenticate.
Once job is started, user can retrieve public ip to connect to, and remote user to be used (which can change depending of administrator settings):
It is then possible to login via ssh as usual:
$> ssh nimbix@34.132.168.36
The authenticity of host '34.132.168.36 (34.132.168.36)' can't be established.
ED25519 key fingerprint is SHA256:P5DoI5PL4wHUFSe5b3kzhnOsJPdctf7Zf6Ny1UUBZhw.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '34.132.168.36' (ED25519) to the list of known hosts.
Welcome to Alpine!
The Alpine Wiki contains a large amount of how-to guides and general
information about administrating Alpine systems.
See <https://wiki.alpinelinux.org/>.
You can setup the system with the command: setup-alpine
You may change this message by editing /etc/motd.
jarvice-job-123333-bscv5:/tmp/.ephemeral-home$
As expressed in the logs, user have to manually kill the job once done (this is a server app, not a computation app).
11.2. Minecraft server¶
Minecraft is a very popular building, crafting and adventure game. It is now owned by Microsoft. A Minecraft server is an interesting example, since it uses a non-standard port, and also since server administrator needs to be able to interact with the server, to configure it, allow new users, etc. It is also very simple to setup. This is the reason why it was chosen for this tutorial as an example.
The architecture schema is simple: server listen on port 25565/tcp and clients can connect to server to join the world (if allowed by server admin whitelist if any).
For this app, we need to combine the server with public ip seen before, with an interactive webshell, so administrator can join server shell anytime and interact with it.
We will also store server world (data) into /data/minecraft_server
folder for world persistency.
Minecraft server needs a recent Java to run, we will rely on OpenJDK. Also, we will create a small script to start it. Last part is to download the most up to date server file each time application start. We will use a small script from https://gist.github.com/ntoonio/198c14f5915fc9aafe54eba0fc1f4163 to download it at launch.
First, create Dockerfile:
FROM ubuntu:22.04
RUN apt update && apt install -y python3 python3-requests tmux openjdk-19-jre --no-install-recommends
COPY start.sh /start.sh
COPY get_server.py /get_server.py
COPY NAE/AppDef.json /etc/NAE/AppDef.json
COPY ./NAE/screenshot.png /etc/NAE/screenshot.png
RUN mkdir -p /etc/NAE && touch /etc/NAE/AppDef.json
Then create file start.sh with the following content:
#!/bin/sh
cd /data
mkdir -p minecraft_server
cd minecraft_server/
rm -f server.jar
echo 'eula=True' > eula.txt
python3 /get_server.py # --version 1.20.1
java -jar server.jar nogui
Now we need the script get_server.py that will grab latest version of server jar file. Create file get_server.py with the following content:
VERSION_MANIFEST_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json"
import json
import requests
import argparse
import urllib.request
rManifest = requests.get(VERSION_MANIFEST_URL)
manifest = rManifest.json()
latestVersion = manifest["latest"]["release"]
latestSnapshotVersion = manifest["latest"]["snapshot"]
a = argparse.ArgumentParser()
a.add_argument("-v", "--version", help="Version, default to latest", default=latestVersion)
a.add_argument("-s", "--snapshot", help="Use the latest snapshot", const=latestSnapshotVersion, dest="version", action="store_const")
args = a.parse_args()
version = args.version
versionURL = None
for v in manifest["versions"]:
if v["id"] == version:
versionURL = v["url"]
break
if versionURL == None:
print("Can't find version " + version)
else:
rVersionManifest = requests.get(versionURL)
versionManifest = rVersionManifest.json()
serverDownloadURL = versionManifest["downloads"]["server"]["url"]
print("Downloading server version " + version + "...")
urllib.request.urlretrieve(serverDownloadURL, "server.jar")
print("Done!")
Now create NAE folder, and inside create the AppDef.json file. The Minecraft logo is embed as base64 encoded png:
{
"name": "Minecraft Server",
"description": "Minecraft Server",
"author": "",
"licensed": true,
"appdefversion": 2,
"classifications": [
"Middle Earth"
],
"machines": [
"*"
],
"vault-types": [
"FILE",
"BLOCK",
"BLOCK_ARRAY",
"OBJECT"
],
"commands": {
"custom": {
"path": "/start.sh",
"interactive": true,
"webshell": true,
"publicip": true,
"ports": [
"25565/tcp",
"5902/tcp"
],
"name": "Minecraft Server",
"description": "Start a Minecraft Server",
"verboseinit": true,
"parameters": {
}
}
},
"image": {
"data": "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",
"type": "image/png"
}
}
Note that we opened both 25565 and 5902 ports. 25565 is for the Minecraft server, and 5902 is required by webshell when public ip mechanism is used.
Finally, we need a nice screenshot. I propose here the file https://www.minecraft-france.fr/wp-content/uploads/2020/11/minecraft.jpg, but depending of licenses and your taste, you could download something else.
Now convert it as png, and store it as NAE/screenshot.png:
convert minecraft.jpg minecraft.png
mv minecraft.png NAE/screenhost.png
And build your app. Once imported in Push2Compute, you should see the server app like this:
And once opened:
Now, start the application. I strongly recommend at least 4Gb ram and 4 CPU core. Less can work, but may be laggy for few players. If you plan to host more players on the server, you will need more resources and maybe some Java launch tunings. Also, ensure to request a persistent Vault in optional tab! Or your server chunks will not be saved once server shutdown.
Once application is started, you can grab the public ip to be given to users.
Note that for now, the server hasn't started yet. Server administrator now must connect once to the webshell to trigger server start. Click on "Click here to connect" to join the webshell and trigger server start.
Once server is started, you need to establish a whitelist to prevent unwanted users to login, and add your players in it:
/whitelist on
/whitelist add username
/whitelist list
Then users can join the server if invited by server admin (i.e. added to whitelist), using multiplayer section on the Minecraft client.
You can close webshell tab anytime, and rejoin later. Of course, do not give the link to any users!
Once server must be shutdown (upgrade, maintenance, etc.), ensure all users are aware of the shutdown, then use command stop
to ensure server save chunks before stopping gracefully.
Note that to allow advanced server usage, like modding, etc., we could also propose a full remote desktop for this application, etc.