Skip to content

Commit

Permalink
Add kubernetes template for standalone deployment
Browse files Browse the repository at this point in the history
  • Loading branch information
DaSpood committed Jul 24, 2024
1 parent 687f2d9 commit 6ea1c18
Show file tree
Hide file tree
Showing 7 changed files with 497 additions and 1 deletion.
1 change: 0 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
**/target/
**/*.log
**/*.jar
**/*.war
Expand Down
76 changes: 76 additions & 0 deletions kubernetes-dbless/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# JQM Kubernetes DB-less ("Standalone") deployment

This is the official Kubernetes DB-less setup for JQM (Job Queue Manager). It contains a deployment script and related
documentation.

In this document, "node" refers to a replicated instance, containing a JQM Engine, JQM Webservice, and HSQLDB
in-memory database.

## Limitations

JQM is originally designed to work with a central database aggregating jobs for active engines. Removing this central
database is not the intended use for it, and as such, some limitations are required to make it work in this way.

- The Kubernetes deployment is expected to use the `jqm-standalone` docker image, a variant of the Linux image with
standalone-specific parameters enabled.

- Each individual node still requires a local database to function. For now, only in-memory HSQLDB is supported.

- Each individual node requires a unique IPv4 address. This IPv4 will be used by the node to assign job IDs. A node may
only enqueue up to 1 million jobs, after that threshold, behavior is undefined and may lead to malfunctions, so the
node should be restarted to clear its database. The `1789` port of each node needs to be exposed to other nodes.

- Each individual node hosts its own instance of the JQM REST API. When making requests to the JQM API, whether to
enqueue a new job or control an existing job, the request should go through a simple load balancer. There is no
additional logic to pick a "better suited" node for enqueue requests.

- Each individual node is completely unaware of other nodes. When queried for a job that does not belong to it, it
guesses the corresponding IPv4 and forwards the request. No information about the state of other nodes is saved, it
is up to the user to decide when a node is considered offline to avoid making unnecessary requests.

## Testing for developpers

Microk8s is recommended.

This guide will help you install it and properly alias `kubectl` for ease of use:
https://kubernetes.io/blog/2019/11/26/running-kubernetes-locally-on-linux-with-microk8s/

Run the following script at the root of the repository:

```bash
# Stop existing kube configs
kubectl delete service jqm-standalone-lb-service
kubectl delete deployment jqm-standalone-deployment

# Package the application
cd ./jqm-all
/snap/intellij-idea-ultimate/510/plugins/maven/lib/maven3/bin/mvn clean install -DskipTests || exit 1
cd ..

# Build the image
docker build --no-cache -t enioka/jqm-standalone -f ./kubernetes-dbless/docker/Dockerfile . || exit 1

# Import the image into Microk8s, otherwise it will try to pull from dockerio
docker save -o ./jqm-standalone.tar enioka/jqm-standalone || exit 1
microk8s ctr image import jqm-standalone.tar || exit 1
rm jqm-standalone.tar

# Load the deployment configuration
kubectl apply -f ./kubernetes-dbless/deployment.yml

# Load the loadbalancer configuration
kubectl apply -f ./kubernetes-dbless/service.yml

# Wait for boot
sleep 10

# Verify the state of your kubernetes configuration
kubectl get deployments
kubectl get pods

# If any pod is in a "READY: 0/1" state:
# Get an overview with
#kubectl describe pod '<Pod id from the "kubectl get pods" command>'
# Get more detailed logs with
#kubectl logs -p -c jqm-standalone-deployment '<Pod id from the "kubectl get pods" command>'
```
34 changes: 34 additions & 0 deletions kubernetes-dbless/deployment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: jqm-standalone-deployment
labels:
app: jqm-standalone
spec:
replicas: 1
selector:
matchLabels:
app: jqm-standalone
template:
metadata:
labels:
app: jqm-standalone
spec:
containers:
- name: jqm-standalone-deployment
image: enioka/jqm-standalone
imagePullPolicy: IfNotPresent
ports:
- containerPort: 1789
livenessProbe:
httpGet:
path: /ws/simple/localnode/health
port: 1789
initialDelaySeconds: 15
periodSeconds: 15
readinessProbe:
httpGet:
path: /ws/simple/localnode/health
port: 1789
initialDelaySeconds: 5
periodSeconds: 10
58 changes: 58 additions & 0 deletions kubernetes-dbless/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
FROM maven:3-eclipse-temurin-21 as installer

ARG MVN_SETTINGS=" "
ARG SKIP_TESTS=true

WORKDIR /jqm-all

RUN apt update && apt install unzip

#COPY ./docker/windows/nexus/settings.xml ./jqm-all ./ # FIXME: settings needed ?
COPY ./jqm-all ./

RUN --mount=type=cache,target=/root/.m2/repository mvn install -DskipTests=${SKIP_TESTS} ${MVN_SETTINGS}
RUN mkdir /jqm
RUN unzip ./jqm-service/target/jqm*.zip -d /tmp/
RUN mv /tmp/jqm*/ /tmp/jqm/

COPY ./kubernetes-dbless/docker/node.sh /tmp/jqm/bin/
COPY ./kubernetes-dbless/docker/selfConfig.standalone.xml /tmp/jqm/



FROM azul/zulu-openjdk-alpine:21

COPY --from=installer /tmp/jqm/ /jqm/
RUN apk add curl

ENV JAVA_OPTS="-Xms128m -Xmx512m -XX:MaxMetaspaceSize=128m" \
JQM_ROOT="/jqm" \
JQM_NODE_NAME="ContainerNode" \
JQM_NODE_WS_INTERFACE="0.0.0.0" \
#JQM_CREATE_NODE_TEMPLATE=TEMPLATE_WEB \
JQM_INIT_MODE=SINGLE \
JQM_POOL_CONNSTR="jdbc:hsqldb:file:db/jqmdatabase;shutdown=true;hsqldb.write_delay=false" \
JQM_POOL_USER="sa" \
JQM_POOL_PASSWORD="" \
JQM_POOL_DRIVER="org.hsqldb.jdbcDriver" \
JQM_POOL_VALIDATION_QUERY="SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS" \
JQM_POOL_INIT_SQL=\
JQM_POOL_MAX=10 \
JQM_HEALTHCHECK_URL="http://localhost:1789/ws/simple/localnode/health"

EXPOSE 1789 1790 1791
VOLUME /jqm/hotdeploy/ \
/jqm/ext/drivers/

WORKDIR /jqm

# Import initial config
RUN java -jar jqm.jar Update-Schema ; java -jar jqm.jar Import-ClusterConfiguration -f selfConfig.standalone.xml ; java -jar jqm.jar Import-JobDef -f ./jobs/jqm-demo ; rm -f .\logs\* ; chmod 700 /jqm/bin/*.sh ;

# Run node on startup
ENTRYPOINT /jqm/bin/node.sh

# Healthcheck is equivalent to calling Node.AllPollersPolling
# Kubernetes already has probes
#HEALTHCHECK --interval=30s --start-period=91s --retries=2 --timeout=10s CMD curl --connect-timeout 2 -q --http1.1 -4 -s -S ${JQM_HEALTHCHECK_URL}
STOPSIGNAL SIGINT
153 changes: 153 additions & 0 deletions kubernetes-dbless/docker/node.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/bin/sh

###############################################################################
## Startup script for Linux docker deployments.
###############################################################################


# set resource file from env variables
if [ ! "x${JQM_POOL_INIT_SQL}" = "x" ]
then
export JQM_POOL_INIT_SQL="initSQL=\"${JQM_POOL_INIT_SQL}\""
fi

echo "<resource
name='jdbc/jqm'
auth='Container'
type='javax.sql.DataSource'
factory='org.apache.tomcat.jdbc.pool.DataSourceFactory'
testWhileIdle='true'
testOnBorrow='false'
testOnReturn='true'
validationQuery=\"${JQM_POOL_VALIDATION_QUERY}\"
${JQM_POOL_INIT_SQL}
logValidationErrors='true'
validationInterval='1000'
timeBetweenEvictionRunsMillis='60000'
maxActive='${JQM_POOL_MAX}'
minIdle='2'
maxIdle='5'
maxWait='30000'
initialSize='5'
removeAbandonedTimeout='3600'
removeAbandoned='true'
logAbandoned='true'
minEvictableIdleTimeMillis='60000'
jmxEnabled='true'
username='${JQM_POOL_USER}'
password='${JQM_POOL_PASSWORD}'
driverClassName='${JQM_POOL_DRIVER}'
url='${JQM_POOL_CONNSTR}'
connectionProperties='v\$session.program=JQM;'
singleton='true'
/>" > ${JQM_ROOT}/conf/resources.xml

# helper for swarm deployment - use local host name for node name.
if [ "${JQM_NODE_NAME}" = "_localhost_" ]
then
export JQM_NODE_NAME=$(hostname)
fi
if [ "${JQM_NODE_WS_INTERFACE}" = "_localhost_" ]
then
export JQM_NODE_WS_INTERFACE=$(hostname)
fi

if [ "${JQM_INIT_MODE}" = "SINGLE" ]
then
echo "### Checking configuration in database for node ${JQM_NODE_NAME} - Single mode."
if [ ! -f /jqm/db/${JQM_NODE_NAME} ]
then
echo "#### Node does not exist (as seen by the container). Single node mode."

echo "### Updating database schema"
java -jar jqm.jar Update-Schema

echo "### Creating node ${JQM_NODE_NAME}"
java -jar jqm.jar New-Node -n ${JQM_NODE_NAME}

# mark the node as existing
echo 1 > /jqm/db/${JQM_NODE_NAME}

# Apply template
if [ ! "x${JQM_CREATE_NODE_TEMPLATE}" = "x" ]
then
echo "#### Applying template ${JQM_CREATE_NODE_TEMPLATE} to new JQM node"
java -jar jqm.jar Install-NodeTemplate -t ${JQM_CREATE_NODE_TEMPLATE} -n ${JQM_NODE_NAME} -i ${JQM_NODE_WS_INTERFACE}
fi

# Jobs
echo "### Importing local job definitions inside database"
java -jar jqm.jar Import-JobDef -f ./jobs/
else
echo "### Node ${JQM_NODE_NAME} already exists inside database configuration, skipping config"
fi
fi

if [ "${JQM_INIT_MODE}" = "CLUSTER" ]
then
echo "### Checking configuration in database for node ${JQM_NODE_NAME} - Cluster mode."
java -jar jqm.jar Get-NodeCount >/jqm/tmp/nodes.txt
if [ ! $? -eq 0 ]
then
echo "cannot check node status, java failure"
return 1
fi

cat /jqm/tmp/nodes.txt | grep "Already existing: ${JQM_NODE_NAME}"
if [ $? -eq 0 ]
then
echo "### Node ${JQM_NODE_NAME} already exists inside database configuration, skipping config"
else
echo "#### Node does not exist (as seen by the database). Cluster mode."

echo "### Waiting for templates import"
$(exit 0)
while [ $? -eq 0 ]
do
# no templates yet - wait one second and retry
sleep 1
java -jar jqm.jar Get-NodeCount >/jqm/tmp/nodes.txt
grep "Existing nodes: 0" /jqm/tmp/nodes.txt
done

echo "### Creating node ${JQM_NODE_NAME}"
java -jar jqm.jar New-Node -n ${JQM_NODE_NAME}

# mark the node as existing
echo 1 > /jqm/db/${JQM_NODE_NAME}

# Apply template
if [ ! "x${JQM_CREATE_NODE_TEMPLATE}" = "x" ]
then
echo "#### Applying template ${JQM_CREATE_NODE_TEMPLATE} to new JQM node"
java -jar jqm.jar Install-NodeTemplate -t ${JQM_CREATE_NODE_TEMPLATE} -n ${JQM_NODE_NAME} -i ${JQM_NODE_WS_INTERFACE}
fi
fi
fi

if [ "${JQM_INIT_MODE}" = "UPDATER" ]
then
echo "### Updating database schema"
java -jar jqm.jar Update-Schema

echo "### Importing local job definitions inside database"
java -jar jqm.jar Import-JobDef -f ./jobs/

echo "### Listing nodes"
java -jar jqm.jar get-nodecount >/jqm/tmp/nodes.txt
type /jqm/tmp/nodes.txt

# If first node, upload template and initial configuration.
grep "Existing nodes: 0" /jqm/tmp/nodes.txt
if [ $? -eq 0 ]
then
echo "### No nodes yet - uploading templates and initial configuration to database"
java -jar jqm.jar Import-ClusterConfiguration -f selfConfig.swarm.xml
fi

return 0
fi

# Go!
echo "### Starting JQM node ${JQM_NODE_NAME}"
java -D"com.enioka.jqm.interface=0.0.0.0" ${JAVA_OPTS} -jar jqm.jar Start-Node -n ${JQM_NODE_NAME}
Loading

0 comments on commit 6ea1c18

Please sign in to comment.