Skip to content

Commit

Permalink
fix(docker): separate out build and run steps
Browse files Browse the repository at this point in the history
This also updates the README with new relevant instructions.

Signed-off-by: Patrick Avery <patrick.avery@kitware.com>
  • Loading branch information
psavery committed Dec 17, 2022
1 parent 33e050d commit 7393f1d
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 214 deletions.
92 changes: 53 additions & 39 deletions docker/README.md
@@ -1,22 +1,46 @@
# Trame Docker

The Trame Docker images are intended to be used for deploying multi-client ParaviewWeb/trame applications. With these images, multiple clients can connect to the same URL, and each client will be viewing and running their own separate process.
The Trame Docker images are intended to be used for deploying multi-client ParaviewWeb/Trame applications. With these images, multiple clients can connect to the same URL, and each client will be viewing and running their own separate process.

Such image include an Apache front-end that is able to serve static web content and manage WebSocket routing, a launcher for starting new processes and a custom Python environment to handle any ParaviewWeb/trame application runtime.
The images include an Apache front-end that is able to serve static web content and manage WebSocket routing, a launcher for starting new processes, and a Python virtual environment containing the runtime requirements for the ParaviewWeb/Trame application.

A few different flavors of the Trame Docker images exist, including pip, pip with glvnd (for nvidia runtimes), and conda.

An example of its usage can be found [in the trame-cookiecutter package](https://github.com/Kitware/trame-cookiecutter/tree/master/%7B%7Bcookiecutter.package_name%7D%7D/bundles/docker).
An example of its usage can be found [here](https://github.com/Kitware/trame/tree/master/examples/deploy/docker/SingleFile). The `trame-cookiecutter` package [also contains an example](https://github.com/Kitware/trame-cookiecutter/tree/master/%7B%7Bcookiecutter.package_name%7D%7D/bundles/docker).

## Usage

The basic gist imply the creation of a server directory that should contain everything specific to your application from the static website (www) that needs to be serve to the way your application needs to be started (launcher) when a user request access to what kind of Python dependency are needed to run it (venv).
To run a docker image of a trame application (for example, after building the one located [here](https://github.com/Kitware/trame/tree/master/examples/deploy/docker/SingleFile)), a command similar to the following may be invoked:

That server can be generated with the help of a setup directory. The process of creating and configuring that server can be achieve either within the docker image at build time or done directly on a mounted directory.
```bash
docker run -it --rm -p 8080:80 trame-app
```

After the container is running, the application may be accessed at `localhost:8080`. Each time the URL is accessed, a new application process is created and displayed.

### Options

When running the Trame Docker images through the main entrypoint, there
are a few environment variables available that provide some options:

#### TRAME_USE_HOST

The sessionURL by default is defined as: `ws://USE_HOST/proxy?sessionId=${id}&path=ws`

If the `TRAME_USE_HOST` environment variable is set, then `USE_HOST` will be replaced
with the contents of `TRAME_USE_HOST`.

If `TRAME_USE_HOST` contains `://`, however, then it is assumed that it will be
overwriting the `ws://` part at the beginning as well, and the whole
`ws://USE_HOST` section will be replaced by the contents of `TRAME_USE_HOST`.

## Building the Server

For building the server, a `setup` directory is expected to be mounted in `/deploy/setup`. This directory should contain 3 files:
To run your application in a Trame Docker image, a server directory must be present that contains everything required to run the application. This includes the static website (www) that needs to be served, instructions for starting the application (launcher) when a user requests access, and the Python dependencies that are needed to run it (venv).

The server directory may either be built within the Dockerfile itself (see [here](https://github.com/Kitware/trame/tree/master/examples/deploy/docker/SingleFile) for an example), or a pre-existing server directory may be mounted at `/deploy/server` at runtime (see [here](https://github.com/Kitware/trame-cookiecutter/blob/master/%7B%7Bcookiecutter.package_name%7D%7D/bundles/docker/scripts/build_server.sh) for an example of building the server directory outside the container that can be mounted inside later).

To build the server, a `setup` directory is expected to be mounted in `/deploy/setup`. This directory should contain 3 files:

### apps.yml

Expand All @@ -29,11 +53,10 @@ trame: # Default app under /index.html

This indicates that the docker image should run the `trame-app` package when a user connects.

Additional options at the same level as `app` include `www_modules`, if there are custom Vue components that should be included, and `cmd` if a custom command should be used for launching the application (this will replace the `app` key).
Additional options at the same level as `app` include `www_modules` if there are custom Vue components that should be included, and `cmd` if a custom command should be used for launching the application (this will replace the `app` key).

Additional endpoints may also be specified. For instance:


```yaml
hello: # /hello.html
app: trame-app
Expand All @@ -45,52 +68,43 @@ This indicates that the app `trame-app` may also be accessed at the `/hello.html

This file contains requirements that will be installed during setup.
For pip, the file will be installed via `pip install -r requirements.txt`.
For conda, the file will be installed via `conda install -y --file requirements.txt`.
For conda, the file will be installed via `conda install -y --file requirements.txt`.
This file may include the actual trame application itself.

### initialize.sh

This file is optional. If present, it may be used to run additional commands that are necessary during setup. It is executed before the `requirements.txt` is installed. It may include the installation of the actual Trame application itself.

Once the server is built, it may be mounted instead of the `setup` directory in `/deploy/`.
### The Build Command

## Options
The build command is invoked as an argument after the entrypoint. If you are building the server outside of a `Dockerfile`, this can be done like so:

When running the Trame Docker images through the main entrypoint, there
are a few environment variables available that provide some options.
```bash
docker run --rm \
-v "$DEPLOY_DIR:/deploy" \
kitware/trame build
```

### TRAME_BUILD_ONLY
Or if you are building the server within a `Dockerfile`, this can be done like so:

If this is equal to `1`, then the application will exit immediately after
building the server. It will not proceed to run the server.
```Dockerfile
RUN /opt/trame/entrypoint.sh build
```

Once the server is built, it is expected to be found in `/deploy/server` at runtime.

### TRAME_BUILD
### Build Options

There are three parts to the server build: `launcher`, `venv`, and `www`.
By default, each of these will be built if they are missing, and they will
not be re-built if they are already present.
Building the server consists of three parts: `launcher`, `venv`, and `www`. By default, each of these will be built if they do not already exist, and they will not be re-built if they are already present.

Any combination of these strings can be used to indicate that those steps
should be re-built every time (even if they are already present). For
instance:
Any combination of these strings, however, can be passed as arguments to indicate that those steps should be built, even if they are already present. For instance, if building externally:

```bash
export TRAME_BUILD="launcher;venv;www"
docker run --rm \
-v "$DEPLOY_DIR:/deploy" \
kitware/trame build launcher venv www
```

This indicates to re-build all three parts every time, even if they are
present.

The `www` part is the only part that may be optionally skipped entirely.
This can be done by providing `no_www` as one of the strings.

### TRAME_USE_HOST
This indicates to re-build all three parts, even if they are already present.

The sessionURL by default is defined as: `ws://USE_HOST/proxy?sessionId=${id}&path=ws`

If the `TRAME_USE_HOST` environment variable is set, then `USE_HOST` will be replaced
with the contents of `TRAME_USE_HOST`.

If `TRAME_USE_HOST` contains `://`, however, then it is assumed that it will be
overwriting the `ws://` part at the beginning as well, and the whole
`ws://USE_HOST` section will be replaced by the contents of `TRAME_USE_HOST`.
The `www` part is the only part that may be optionally skipped entirely. This can be done by providing `no_www` as one of the arguments.
63 changes: 59 additions & 4 deletions docker/scripts/build.sh
@@ -1,8 +1,63 @@
#!/usr/bin/env bash

if [ ! -d /deploy/setup ]; then
echo "ERROR: The 'setup' directory must be present in the container at '/deploy/setup'"
exit 1
fi

# Put any arguments into the `TRAME_BUILD` variable
# This is so you can do things like `/opt/trame/build.sh no_www venv`, etc.
export TRAME_BUILD="$*"
export TRAME_BUILD_ONLY=1
# This is so you can do things like `/opt/trame/build.sh no_www venv`, etc.,
# or "build no_www venv" via the default entrypoint.
TRAME_BUILD="$*"

LAUNCHER_OUTPUT_PATH=/deploy/server/launcher.json
WWW_PATH=/deploy/server/www

# Convert the apps.yml file to json and put it in the right place.
# This needs PyYAML, which is in the root python environment, so
# we must do this before activating the venv.
# We will do this every time, because it is needed in both the launcher
# step and the www step.
python /opt/trame/yaml_to_json.py /deploy/setup/apps.yml /opt/trame/apps.json

# launcher
# Build if it does not exist, or if "launcher" is in `TRAME_BUILD`
if [[ ! -f $LAUNCHER_OUTPUT_PATH || $TRAME_BUILD == *"launcher"* ]]; then
# Generate the launcher config
python /opt/trame/generate_launcher_config.py
fi

# venv
# Build if it does not exist, or if "venv" is in `TRAME_BUILD`
if [[ ! -d $TRAME_VENV || $TRAME_BUILD == *"venv"* ]]; then
# In case we are doing a force rebuild, make sure the directory is deleted
rm -rf $TRAME_VENV

# Create (and activate) the venv
. /opt/trame/create_venv.sh

# Run the initialize script (if it exists)
if [[ -f /deploy/setup/initialize.sh ]]; then
. /deploy/setup/initialize.sh
fi

# Install any specified requirements
. /opt/trame/install_requirements.sh
else
# Activate it if we skipped building it
. /opt/trame/activate_venv.sh
fi

# www
# This must be done after activating the venv.
# This directory should already exist.
# Build if "no_www" is not in `TRAME_BUILD` and either it is empty or "www" is in `TRAME_BUILD`
if [[ $TRAME_BUILD != *"no_www"* ]] && [[ -z "$(ls -A $WWW_PATH)" || $TRAME_BUILD == *"www"* ]]; then
# Generate the www directory
python /opt/trame/generate_www.py

/opt/trame/entrypoint.sh
# Merge any user-created www directories with the generated one
if [[ -d /deploy/setup/www ]]; then
cp -r /deploy/setup/www/* /deploy/server/www
fi
fi
33 changes: 18 additions & 15 deletions docker/scripts/entrypoint.sh
@@ -1,19 +1,22 @@
#!/usr/bin/env bash

if [ ! -d /deploy/server ] && [ ! -d /deploy/setup ]
then
echo "ERROR: The deploy directory must be mounted into the container at /deploy"
exit 1
fi

# Fix any uid/gid mismatch
/opt/trame/fix_uid_gid.sh
# The entrypoint provides some branching logic as to what we
# are going to do. By default, it runs the server.
# If the first argument is "build", however, it will build the
# server instead, and forward any extra args to the build script.

# Ensure the needed directories exist
gosu trame-user /opt/trame/make_directories.sh
# First, perform initial setup
. /opt/trame/setup.sh

# Restart apache
service apache2 restart

# Start the server
gosu trame-user /opt/trame/server.sh
if [[ "$1" == "build" ]]; then
# Run the build
# Forward all arguments after `build`, so the user can pass things
# like `www venv launcher` etc.
echo "Running build..."
gosu trame-user /opt/trame/build.sh ${@:2}
echo "Build complete"
else
# Start the server
echo "Starting server..."
gosu trame-user /opt/trame/run.sh
fi
36 changes: 36 additions & 0 deletions docker/scripts/run.sh
@@ -0,0 +1,36 @@
#!/usr/bin/env bash

# This script is used to start the trame server.
# If the `TRAME_USE_HOST` environment variable is set, this
# will replace `USE_HOST` in the launcher json file. If it contains
# `://`, it will replace `ws://USE_HOST` instead.

if [ ! -d /deploy/server ]; then
echo "ERROR: The the server directory must be in the container at '/deploy/server'"
exit 1
fi

# First, activate the venv
. /opt/trame/activate_venv.sh

# We will copy the launcher and make any needed edits to it
LAUNCHER_TEMPLATE_PATH=/deploy/server/launcher.json
LAUNCHER_PATH=/opt/trame/config.json

OUTPUT=$(<"${LAUNCHER_TEMPLATE_PATH}")

if [[ -n $TRAME_USE_HOST ]]; then
REPLACEMENT_STRING="USE_HOST"
if [[ $TRAME_USE_HOST == *"://"* ]]; then
# If the string contains "://", then we are replacing the "ws://" at
# the beginning as well
REPLACEMENT_STRING="ws://$REPLACEMENT_STRING"
fi
OUTPUT="${OUTPUT//$REPLACEMENT_STRING/$TRAME_USE_HOST}"
fi

echo -e "$OUTPUT" > "${LAUNCHER_PATH}"

# Run the launcher in the foreground so this script doesn't end
echo "Starting the wslink launcher at"
python -m wslink.launcher ${LAUNCHER_PATH}
69 changes: 0 additions & 69 deletions docker/scripts/server.sh

This file was deleted.

16 changes: 16 additions & 0 deletions docker/scripts/setup.sh
@@ -0,0 +1,16 @@
#!/usr/bin/env bash

if [ ! -d /deploy/server ] && [ ! -d /deploy/setup ]
then
echo "ERROR: The deploy directory must be mounted into the container at /deploy"
exit 1
fi

# Fix any uid/gid mismatch
/opt/trame/fix_uid_gid.sh

# Ensure the needed directories exist
gosu trame-user /opt/trame/make_directories.sh

# Restart apache
service apache2 restart

0 comments on commit 7393f1d

Please sign in to comment.