For example, see runner.
I've been working on re-architecting cyber-dojo so the web-server (written in Rails) runs in a Docker image...
- the server receives its source files from the browser
- it saves them to a temporary folder
- it back-ticks a shell file which
- ...puts the source files into a docker container
- ...runs the source files (by executing cyber-dojo.sh) as user=nobody
- ...limits execution to 10 seconds
My shell file started like this:
#!/bin/sh
SRC_DIR=$1 # where source files are
IMAGE=$2 # the image to run them in
MAX_SECS=$3 # how long they've got to complete
TAR_FILE=`mktemp`.tgz # source files are tarred into this
SANDBOX=/sandbox # where tar is untarred to inside container
# - - - - - - - - - - - - - - - - - - -
# 1. Create the tar file
cd ${SRC_DIR}
tar -zcf ${TAR_FILE} .
# - - - - - - - - - - - - - - - - - - -
# 2. Start the container
CID=$(sudo docker run --detach \
--interactive \
--net=none \
--user=nobody \
${IMAGE} sh)
# - - - - - - - - - - - - - - - - - - -
# 3. Pipe the source files into the container
cat ${TAR_FILE} \
| sudo docker exec --interactive \
--user=root \
${CID} \
sh -c "mkdir ${SANDBOX} \
&& tar zxf - -C ${SANDBOX} \
&& chown -R nobody ${SANDBOX}"
# - - - - - - - - - - - - - - - - - - -
# 4. After max_seconds, remove the container
(sleep ${MAX_SECS} && sudo docker rm --force ${CID}) &
# - - - - - - - - - - - - - - - - - - -
# 5. Run cyber-dojo.sh in the container
sudo docker exec --user=nobody \
${CID} \
sh -c "cd ${SANDBOX} && ./cyber-dojo.sh 2>&1"
# - - - - - - - - - - - - - - - - - - -
# 6. If the container isn't running, the sleep woke and removed it
RUNNING=$(sudo docker inspect --format="{{ .State.Running }}" ${CID})
if [ "${RUNNING}" != "true" ]; then
exit 137 # (128=timed-out) + (9=killed)
else
exit 0
fi
Things to note:
- The container is started in detached mode. This is so I can get its CID and setup the backgrounded sleep task (4) before running cyber-dojo.sh (5)
- I use [sudo docker] because I do not put the current user into the docker group. Instead I sudo the current user to run the docker binary without a password.
- The first [docker exec] user is root but this is root inside the CID container not root where the shell file is being run.
- I can pipe STDIN from the shell into the container
- The sleep task (4) kills the container if it runs out of time and step (6) detects this.
I realized I could avoid creating the (physical) tar file completely by using a 'proper' tar pipe:
#!/bin/sh
...
(cd ${SRC_DIR} && tar -zcf - .) \
| sudo docker exec --interactive \
--user=root \
$CID \
sh -c "mkdir ${SANDBOX} \
&& tar -zxf - -C ${SANDBOX} \
&& chown -R nobody ${SANDBOX}"
...
- [tar -zcf] means create a compressed tar file
- [-] means don't write to a named file but to STDOUT
- [.] means tar the current directory
- which is why there's a preceding cd
- [tar -zxf] means extract files from the compressed tar file
- [-] means don't read from a named file but from STDIN
- [-C ${SANDBOX}] means save the extracted files to the ${SANDBOX} directory
Then I realized I could combine the two [docker exec]s into one and drop the chown...
...
# - - - - - - - - - - - - - - - - - - -
# 1. Start the container
CID=$(sudo docker run \
--detach \
--interactive \
--net=none \
--user=nobody \
${IMAGE} sh)
# - - - - - - - - - - - - - - - - - - -
# 2. After max_seconds, remove the container
(sleep ${MAX_SECS} && sudo docker rm --force ${CID}) &
# - - - - - - - - - - - - - - - - - - -
# 3. Tar pipe the source files into the container and run
(cd ${SRC_DIR} && tar -zcf - .) \
| sudo docker exec --interactive \
--user=nobody \
${CID} \
sh -c "mkdir ${SANDBOX} \
&& cd ${SANDBOX} \
&& tar -zxf - -C . \
&& ./cyber-dojo.sh"
# - - - - - - - - - - - - - - - - - - -
# 4. If the container isn't running, the sleep woke and removed it
RUNNING=$(sudo docker inspect --format="{{ .State.Running }}" ${CID})
if [ "${RUNNING}" != "true" ]; then
exit 137 # (128=timed-out) + (9=killed)
else
exit 0
fi
This worked but the backgrounded sleep created a zombie process.
That's for another blog.