-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add kubernetes template for standalone deployment
- Loading branch information
Showing
7 changed files
with
497 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
**/target/ | ||
**/*.log | ||
**/*.jar | ||
**/*.war | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>' | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} |
Oops, something went wrong.