Skip to main content

Containers

Overview

You can run a full Genesis application in a self-contained Docker container. There are two simple ways:

  • using our helpful plugin
  • by creating the Dockerfile and image yourself

Prerequisites

Building

The Genesis Application Platform provides two different options for building a Docker image.

  • The first option, which is covered in this section, is using the Gradle plugin. This is the easiest and quickest way to get up and running with a Docker image, but it comes with the cost of reduced flexibility. This option is highly recommended for new developers to try Genesis out.

  • The second option is to create your own Dockerfile and build your own image. This provides the most flexibility, but it means you need to manage the Genesis dependencies yourself.

Gradle plugin overview

The Gradle plugin provides the easiest and quickest way to get Genesis running in a Docker container. We provide a Gradle task that generates a Dockerfile for you, with all the necessary dependencies, and then builds it.

There are 3 Gradle tasks that are provided to help you build your Docker image:

  • createDockerfile - generates the Dockerfile dynamically based on user-defined settings and dependencies. It also copies all the files needed for the context into the same folder.
  • buildImage - runs createDockerfile as a sub-task, and then runs docker build on the generated Dockerfile.
  • pushImage - pushes the generated image to a repository defined in gradle.properties (see section on pushing).

Using the plugin

  1. Create or use an existing Genesis project.

  2. Make the necessary changes to the genesis-system-definitions.kts for your dependencies, such as the location of the database.

  3. Add your dependencies to the Deploy plugin in server/build.gradle.kts:

    genesisServer(
group = "global.genesis",
name = "genesis-distribution",
version = properties["genesisVersion"].toString(),
classifier = "bin",
ext = "zip"
)
genesisServer(
group = "global.genesis",
name = "auth-distribution",
version = properties["authVersion"].toString(),
classifier = "bin",
ext = "zip"
)
  1. Run the buildImage Gradle task from the root of the server/jvm/ project. You can also run this task from your IDE if you prefer:
./gradlew buildImage

Once the image has been built, the output should display the name of the image:

Successfully built eaa290495637
Successfully tagged genesis/appname:1.0.0-SNAPSHOT
Created image with ID 'eaa290495637'.

Running

If you haven’t already initialised the database, you can run the Docker container passing the environment variable GENESIS_DB_INSTALL=true. This triggers a remap to create all the tables and will exit on completion.

docker run -e GENESIS_DB_INSTALL=true genesis/appname:1.0.0-SNAPSHOT

You can then start the Docker container with whichever system you choose to use (e.g. docker-compose / Kubernetes etc.) Or simply run the container on its own:

docker run -it -p 443:443 genesis/appname:1.0.0-SNAPSHOT

Note: The -p flag is used in this example, as nginx is bundled in with our image and presents the Genesis app on port 443.

Dockerfile

If you want more control over the image, you can create your own Dockerfile. This gives you complete control over the base image and the versions of the underlying dependencies.

There are quite a few configuration steps and dependencies that are required as part of the image build. Below is an example Dockerfile that is generated by our Gradle task:

Note: This is just an example. You will need to manage the locations of the dependencies for copying to the container. If the dependencies are remote, you might need to use wget or a similar tool.

FROM rockylinux:8
RUN yum install -y unzip ncurses python3 java-17-openjdk-devel lmdb-libs openssl nginx procps
RUN ln -s /usr/lib64/liblmdb.so.0.0.0 /usr/lib64/liblmdb.so
RUN update-alternatives --set python /usr/bin/python3
RUN mkdir -p /app/run/site-specific/cfg /app/web/console /etc/ssl/certs
RUN openssl req -x509 -nodes -days 365 -newkey rsa:4096 -keyout /etc/ssl/certs/certs.key -out /etc/ssl/certs/certs.pem -subj "/C=GB/L=London/O=Genesis Global/CN=localhost"
WORKDIR /app
COPY nginx.conf /etc/nginx/nginx.conf
RUN echo "export GENESIS_HOME=/app/run/" >> ~/.bashrc
RUN echo "source \$GENESIS_HOME/genesis/util/setup.sh" >> ~/.bashrc
COPY genesis-distribution-6.2.0-SNAPSHOT-bin.zip /app
RUN unzip genesis-distribution-6.2.0-SNAPSHOT-bin.zip -d run/
RUN rm -f genesis-distribution-6.2.0-SNAPSHOT-bin.zip
COPY auth-distribution-6.2.0-SNAPSHOT-bin.zip /app
RUN unzip auth-distribution-6.2.0-SNAPSHOT-bin.zip -d run/
RUN rm -f auth-distribution-6.2.0-SNAPSHOT-bin.zip
COPY log4j2-default.xml /app/run/site-specific/cfg/
COPY web-distribution.zip /app
RUN unzip web-distribution.zip -d web/
RUN rm -f web-distribution.zip
COPY position-site-specific-1.0.0-SNAPSHOT-bin.zip /app
RUN unzip position-site-specific-1.0.0-SNAPSHOT-bin.zip -d run/
RUN rm -f position-site-specific-1.0.0-SNAPSHOT-bin.zip
COPY genesisproduct-position-1.0.0-SNAPSHOT-bin.zip /app
RUN unzip genesisproduct-position-1.0.0-SNAPSHOT-bin.zip -d run/
RUN rm -f genesisproduct-position-1.0.0-SNAPSHOT-bin.zip
RUN source ~/.bashrc && genesisInstall --ignoreHooks
RUN source ~/.bashrc && preCompileScripts
SHELL ["bash", "-c"]
EXPOSE 8080
EXPOSE 443
CMD ["/app/run/genesis/util/dockerStartup.sh", "-p"]

Pushing

If you choose to use the Gradle plugin to build the image, the Genesis Application Platform provides a Gradle task that pushes your built image to your chosen repository.

To configure this task, add the following settings to your gradle.properties:

NameDescription
dockerUrlThe URL to the repository you wish to push to
dockerUsernameUsername
dockerPasswordPassword
dockerEmailEmail address

healthchecks

The Genesis Application Platform Docker image provides a health check endpoint, which reports the status of the container.

This endpoint can be used for your liveliness/readiness checks if you are using container orchestration, or you can use it in your own checks if you are managing your containers yourself.

PathPortResponse
/health/statusThis is set in the System Definition with the item DaemonHealthPort and an integer value for the port chosen to serve the health status

Example: item(name = "DaemonHealthPort", value = "4569")
Either 200 for HEALTHY or 503 for UNHEALTHY if ANY of the underlying services are not in a healthy state.

The payload of the response is in JSON format and provides details for each underlying service configured in the container, including their individual health status

Note: You need to ensure the port is accessible with the Docker --port option; alternatively, check the documentation for whichever container orchestration system you use.

Sample response

[
{
"PROCESS_NAME":"GENESIS_AUTH_MANAGER",
"STATUS":"UP",
"MESSAGE":"",
"PORT":8001
},
{
"PROCESS_NAME":"GENESIS_AUTH_DATASERVER",
"STATUS":"UP",
"MESSAGE":";Lmdb currently uses 1% of available space (total size 2GB)",
"PORT":8002
},
{
"PROCESS_NAME":"GENESIS_AUTH_PERMS",
"STATUS":"UP",
"MESSAGE":"",
"PORT":8003
},
{
"PROCESS_NAME":"GENESIS_AUTH_REQUEST_SERVER",
"STATUS":"UP",
"MESSAGE":"",
"PORT":8004
},
{
"PROCESS_NAME":"GENESIS_AUTH_CONSOLIDATOR",
"STATUS":"UP",
"MESSAGE":"",
"PORT":8005
},
{
"PROCESS_NAME":"GENESIS_CLUSTER",
"STATUS":"UP",
"MESSAGE":";Lmdb currently uses 1% of available space (total size 2GB)",
"PORT":9000
},
{
"PROCESS_NAME":"GENESIS_ROUTER",
"STATUS":"UP",
"MESSAGE":"",
"PORT":9017
},
{
"PROCESS_NAME":"GENESIS_NOTIFY",
"STATUS":"UP",
"MESSAGE":";Lmdb currently uses 1% of available space (total size 2GB)",
"PORT":9018
}
]

Configuration options

Docker plugin extension

To customize how the Docker plugin behaves, the Genesis Application Platform provides a Gradle plugin extension.

The table below describes the values that can be changed:

NameDescriptionDefault
compactProcessesWhen set to true, combines compatible services into a single process, which reduces the number of services running within the containerfalse
debugEnvVarsAllows you to provide a map of the environment variables that will be passed to the containers created by the extra tasks enabled with debugMode
This has no effect, unless debugMode is set to true
null
debugModeWhen set to true, this changes the Dockerfile to create an image that has extra tools in it to help debugging.
Extra yum packages include: vim, net-tools
false
networkNameWhen using the extra debug tasks, this setting allows you to specify a custom Docker network for the containers to use.
This has no effect unless debugMode is set to true
null
useGenesisContainerLogConfigDefines whether the container should use a specific log4j configuration file that is designed for our containers.
When set to true, logs are effectively output by the container PID1, allowing most container orchestration systems to capture all the logs with default settings.
true
preCompileScriptsWhen set to true, the build process must include a step that compiles all the .kts scripts and caches the results.
This results in a slower build process, but dramatically decreases the start-up time of the container.
true

These settings should be set in the server/build.gradle.kts file:

dockerImage {
compactProcesses.set(true)
debugMode.set(true)
debugEnvVars.putAll(
mapOf(
"DB_HOST" to "172.17.0.2"
)
)
}

Local plugin configuration

The Genesis Application Platform also supports overrides of the Docker extension configuration, allowing for different values for different environments. This means that you can set your own values without changing any files that are tracked in SCM.

This is done by adding server/gradle.properties and ensuring that the file is ignored by SCM.

Extension ConfigGradle Properties EquivalentNotes
compactProcessesdockerCompactProcessesN/A
debugEnvVarsdockerDebugEnvVarsThis should be a comma-separated list.
e.g DB_HOST=genesis_db,DB_PORT=5432
debugModedockerDebugModeN/A
networkNamedockerNetworkNameN/A
useGenesisContainerLogConfigdockerUseGenesisContainerLogConfigN/A
preCompileScriptsdockerPreCompileScriptsN/A

Environment variables

Below is a list of environment variables that can be passed to the Docker container at runtime.

VariableDescriptionDefault
GENESIS_DB_DRY_RUNAllows you to see the output of a remap without committing it.
This option only has an effect if it’s used with GENESIS_DB_INSTALL or GENESIS_DB_UPGRADE
false
GENESIS_DB_INSTALLRuns a remap and then a genesisInstallHooks --init to mark the migration hooks so they do not run again.
This should only be used during the initial DB setup, as it will ignore the migration hooks.
GENESIS_DB_UPGRADE should be used for any further DB schema changes.
The container will exit on completion and will not run any Genesis processes.
false
GENESIS_DB_UPGRADERuns genesisInstallHooks to run any migration hooks, then runs a remap
The container will exit on completion and will not run any Genesis processes
false

You can also set System Definitions values from environment variables, which allows you to change the location of external dependencies (such as the database) between your production and non-production environments.

Clustering

The Genesis Application Platform is highly resilient and easy to cluster for a High Availability (HA) setup.

The Genesis platform uses Akka to help manage the communication between clusters: specifically for identifying members of the cluster, joins and leaves etc.

This area takes you through how to configure the platform with our clustering options.

Consul

The Consul cluster mode uses Hashicorp's Consul for service discovery.

Combining this with the use of an MQTT broker gives you a dynamically scalable Genesis app with automatic failover.

Prerequisites

Services

To operate the Genesis Application Platform within an HA setup using Hashicorp's Consul for Clustering, the following services must be available:

  • A Database - A database needs to be hosted independently from the platform
  • A Consul Cluster - A Consul cluster with an agent on each of the hosts running a Genesis app
  • An MQTT Broker - Any message queue broker that supports MQTT, such as Mosquitto or RabbitMQ
important

The Genesis Application Platform can run with Consul without MQTT using ZeroMQ.

However, this is not recommended as MQTT is required for dynamic scaling.

Automatic failover

When Consul is used, processes that are set to primaryOnly failover automatically to the secondary node when the primary node becomes unresponsive. (Failover must be performed manually if you are using the standard Genesis Clustering service.)

Networking

When using Consul mode, all Genesis services within an app need to be able to access all the other Genesis services in the cluster on their defined ports.

The default Genesis services and their ports are:

ServicePort
GENESIS_AUTH_CONSOLIDATOR8005
GENESIS_AUTH_MANAGER8001
GENESIS_AUTH_PERMS8003
GENESIS_CLUSTER9000
GENESIS_EVALUATOR9015
GENESIS_ROUTER9017

See service definitions for information on how to set the ports for user-defined services, and how to override the ports dynamically.

The Consul Agents must also be able to access all the services on their defined ports.

Configuring the app

Basic configuration

If a Consul agent is running locally to the app on the default port of 8500, then the only config required is:

genesis-system-definition.kts
systemDefinition {
global {
...
item(name = "ClusterMode", value = "CONSUL")
...
}
}

Extended configuration

ItemDescriptionDefault
ConsulHostAddressIP or hostname of the Consul Agentlocalhost
ConsulHostPortHTTP Port for the Consul Agent8500
ConsulConnectionTimeoutSecondsSet the timeout for connecting to the Consul Agent in seconds10
ConsulReadTimeoutSecondsSet the timeout for reading from the Consul Agent in seconds10
ConsulWriteTimeoutSecondsSet the timeout for writing to the Consul Agent in seconds10
ConsulWatchSecondsHow long to tell the Consul server to wait for new key/value cache values8
ConsulTtlSecondsTimeout for the TTL Consul checks15
ConsulTtlPingSecondsHow often to run the TTL checks and update Consul with the stateConsulTtlSeconds / 2
ConsulDeregisterTimeoutMinsTime to wait after a TTL check goes critical before it deregisters the service in minutes30
ClusterServiceAffinityGroupGroup services for preferred routing based on this value. Useful for multi-region set-ups""
ConsulServiceNamePatternAllows you to template your Consul service names with {{PROCESS_NAME}} eg. my_app_{{PROCESS_NAME}}null
ConsulNamespaceFormatSets Consul namespace (only used in Consul Enterprise)null

Troubleshooting

Request fails with 5xx when trying to reach a process that no longer exists

Scenario: This might happen when a resource is moved from one process to another. For example, a requestReply originally served from REQ_REP_SERVICE_A is now moved to REQ_REP_SERVICE_B, but consul still holds a reference that is being served by the former.

Solution: Check the consul console (or cli) to see if there is still a resourceMap (under KeyValues) for the process that no longer exists. If there is, delete it. Then restart the Genesis services.