diff --git a/22-minimal/Dockerfile.c10s b/22-minimal/Dockerfile.c10s index 80ccecbf..b558a0ba 100644 --- a/22-minimal/Dockerfile.c10s +++ b/22-minimal/Dockerfile.c10s @@ -56,6 +56,7 @@ RUN INSTALL_PKGS="nodejs$NODEJS_VERSION nodejs-nodemon nodejs$NODEJS_VERSION-ful rm -rf /mnt/rootfs/var/cache/* /mnt/rootfs/var/log/dnf* /mnt/rootfs/var/log/yum.* COPY ./s2i/bin/ /usr/libexec/s2i +RUN chmod +x /usr/libexec/s2i/init-wrapper # Copy extra files to the image. COPY ./root/ / diff --git a/22-minimal/Dockerfile.c8s b/22-minimal/Dockerfile.c8s index ac809767..d7829589 100644 --- a/22-minimal/Dockerfile.c8s +++ b/22-minimal/Dockerfile.c8s @@ -58,6 +58,7 @@ RUN INSTALL_PKGS="nodejs nodejs-nodemon nodejs-full-i18n npm findutils tar which rm -rf /mnt/rootfs/var/cache/* /mnt/rootfs/var/log/dnf* /mnt/rootfs/var/log/yum.* COPY ./s2i/bin/ /usr/libexec/s2i +RUN chmod +x /usr/libexec/s2i/init-wrapper # Copy extra files to the image. COPY ./root/ / diff --git a/22-minimal/Dockerfile.c9s b/22-minimal/Dockerfile.c9s index c9f1c534..92275fbe 100644 --- a/22-minimal/Dockerfile.c9s +++ b/22-minimal/Dockerfile.c9s @@ -58,6 +58,7 @@ RUN INSTALL_PKGS="nodejs nodejs-nodemon nodejs-full-i18n npm findutils tar which rm -rf /mnt/rootfs/var/cache/* /mnt/rootfs/var/log/dnf* /mnt/rootfs/var/log/yum.* COPY ./s2i/bin/ /usr/libexec/s2i +RUN chmod +x /usr/libexec/s2i/init-wrapper # Copy extra files to the image. COPY ./root/ / diff --git a/22-minimal/Dockerfile.fedora b/22-minimal/Dockerfile.fedora index 73cbe75d..2fefa30d 100644 --- a/22-minimal/Dockerfile.fedora +++ b/22-minimal/Dockerfile.fedora @@ -60,6 +60,7 @@ RUN INSTALL_PKGS="nodejs$NODEJS_VERSION nodejs-nodemon nodejs$NODEJS_VERSION-ful # COPY ./s2i/bin/ /usr/libexec/s2i +RUN chmod +x /usr/libexec/s2i/init-wrapper # Copy extra files to the image. COPY ./root/ / diff --git a/22-minimal/Dockerfile.rhel8 b/22-minimal/Dockerfile.rhel8 index 34d5daa2..5f6d0bc3 100644 --- a/22-minimal/Dockerfile.rhel8 +++ b/22-minimal/Dockerfile.rhel8 @@ -59,6 +59,7 @@ RUN INSTALL_PKGS="nodejs nodejs-nodemon nodejs-full-i18n npm findutils tar which rm -rf /mnt/rootfs/var/cache/* /mnt/rootfs/var/log/dnf* /mnt/rootfs/var/log/yum.* COPY ./s2i/bin/ /usr/libexec/s2i +RUN chmod +x /usr/libexec/s2i/init-wrapper # Copy extra files to the image. COPY ./root/ / diff --git a/22-minimal/Dockerfile.rhel9 b/22-minimal/Dockerfile.rhel9 index 61259460..9a18e3ec 100644 --- a/22-minimal/Dockerfile.rhel9 +++ b/22-minimal/Dockerfile.rhel9 @@ -59,6 +59,7 @@ RUN INSTALL_PKGS="nodejs nodejs-nodemon nodejs-full-i18n npm findutils tar which rm -rf /mnt/rootfs/var/cache/* /mnt/rootfs/var/log/dnf* /mnt/rootfs/var/log/yum.* COPY ./s2i/bin/ /usr/libexec/s2i +RUN chmod +x /usr/libexec/s2i/init-wrapper # Copy extra files to the image. COPY ./root/ / diff --git a/22-minimal/README.md b/22-minimal/README.md index 21b64b01..ae1beedb 100644 --- a/22-minimal/README.md +++ b/22-minimal/README.md @@ -215,6 +215,12 @@ Application developers can use the following environment variables to configure **`NPM_RUN`** Select an alternate / custom runtime mode, defined in your `package.json` file's [`scripts`](https://docs.npmjs.com/misc/scripts) section (default: npm run "start"). These user-defined run-scripts are unavailable while `DEV_MODE` is in use. +**`NODE_CMD`** + When specified (e.g.Specify `NODE_CMD="node server.js"`) the value of `NODE_CMD` is used to start the application instead of `npm start`. + +**`INIT_WRAPPER`** + When set to "true", the application is started via the `init-wrapper` script instead of using `npm start`, by looking for the presence of the files `server.js`, `index.js` or `main.js` in the order in which they are listed. In case of `NODE_CMD` environemnt variale is specified, then `init-wrapper` script will use the value of `NODE_CMD` to start your application. + #### Additional variables used in the full-sized image **`HTTP_PROXY`** @@ -316,7 +322,47 @@ Below is an example _package.json_ file with the _main_ attribute and _start_ sc #### Note: `oc rsync` is only available in versions 3.1+ of OpenShift. +## init-wrapper + +init-wrapper script is located on `/usr/libexec/s2i/init-wrapper` and is used to handle: + +- Proper signal handling and propagation, as Node.js was not designed to run as PID 1. +- Reaping zombie child processes +Avoiding use of npm, there is more information on why you want to avoid that in the [Node.js reference architecture](https://github.com/nodeshift/nodejs-reference-architecture/blob/e4c4dc1fd20c2cac392e862859aaad27f85d504f/docs/development/building-good-containers.md#avoiding-using-npm-to-start-application). When the INIT_WRAPPER is set to true the application is started via the init script instead of using npm start. + +A detailed explanation on how the init-wrapper script works is avalable in +[this url](http://veithen.io/2014/11/16/sigterm-propagation.html). + +Example of using init-wrapper: + +**During image build** +``` +s2i -e INIT_WRAPPER=true build . buildImage node-app +docker run node-app +``` +**During container start** +``` +s2i build . buildImage node-app +docker run -e INIT_WRAPPER=true node-app +``` + +`init-wrapper` script can be disabled by setting the `INIT_WRAPPER` env variable to `false`. + +``` +docker run -e INIT_WRAPPER=false node-app +``` +`NODE_CMD` can be used during the build process or container start, in order to have more control on the command that `init-wrapper` script will wrap. + +For example: +**during container build** +``` +s2i -e INIT_WRAPPER=true -e NODE_CMD="node index.js" build . buildImage node-app +``` +**during container start** +``` +docker run -e INIT_WRAPPER=false -e NODE_CMD="node index.js" node-app +``` See also -------- Dockerfile and other sources are available on https://github.com/sclorg/s2i-nodejs-container. diff --git a/22-minimal/s2i/bin/init-wrapper b/22-minimal/s2i/bin/init-wrapper new file mode 100644 index 00000000..24e0d078 --- /dev/null +++ b/22-minimal/s2i/bin/init-wrapper @@ -0,0 +1,18 @@ +#!/bin/bash + +# Overview of how this script works: http://veithen.io/2014/11/16/sigterm-propagation.html +# Set a trap to kill the main app process when this +# init script receives SIGTERM or SIGINT +trap 'kill -s TERM $PID' TERM INT +# Execute the main application in the background +"$@" & +PID=$! +# wait command always terminates when trap is caught, even if the process hasn't finished yet +wait $PID +# Remove the trap and wait till the app process finishes completely +trap - TERM INT +# We wait again, since the first wait terminates when trap is caught +wait $PID +# Exit with the exit code of the app process +STATUS=$? +exit $STATUS \ No newline at end of file diff --git a/22-minimal/s2i/bin/run b/22-minimal/s2i/bin/run index 7894f47f..10150f34 100755 --- a/22-minimal/s2i/bin/run +++ b/22-minimal/s2i/bin/run @@ -16,6 +16,29 @@ run_node() { if [ "$DEV_MODE" == true ]; then echo "Launching via nodemon..." exec nodemon --inspect="$DEBUG_PORT" + elif [ -n "$NODE_CMD" ] && [ "$INIT_WRAPPER" == true ]; then + echo "launching via init wrapper..." + exec ${STI_SCRIPTS_PATH}/init-wrapper $NODE_CMD + elif [ -n "$NODE_CMD" ] && [ "$INIT_WRAPPER" == false ]; then + echo "Launching via ${NODE_CMD}" + exec $NODE_CMD + elif [ ! -n "$NODE_CMD" ] && [ "$INIT_WRAPPER" == true ]; then + + package_json_start=$(sed -n 's/\s*"start"\s*:\s*"\(.*\)".*/\1/p' package.json) + package_json_main=$(sed -n 's/\s*"main"\s*:\s*"\(.*\)".*/\1/p' package.json) + + if [ -n "$package_json_start" ]; then + start_command=$package_json_start + elif [ -n $package_json_main ]; then + start_command="node ." + elif [ -f "server.js" ]; then + start_command="node server.js" + else + echo "Failed to find file for starting the Node.js application" + exit 1 + fi + echo "launching via init wrapper..." + exec ${STI_SCRIPTS_PATH}/init-wrapper $start_command else echo "Launching via npm..." exec npm run -d $NPM_RUN diff --git a/22/Dockerfile.c10s b/22/Dockerfile.c10s index 226938c4..0cc8da62 100644 --- a/22/Dockerfile.c10s +++ b/22/Dockerfile.c10s @@ -65,6 +65,7 @@ RUN INSTALL_PKGS="make gcc gcc-c++ git openssl-devel nodejs nodejs-nodemon nodej # Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH COPY ./s2i/bin/ $STI_SCRIPTS_PATH +RUN chmod +x /usr/libexec/s2i/init-wrapper # Copy extra files to the image. COPY ./root/ / diff --git a/22/Dockerfile.c8s b/22/Dockerfile.c8s index 7e9fdc2b..6a7e0501 100644 --- a/22/Dockerfile.c8s +++ b/22/Dockerfile.c8s @@ -65,6 +65,7 @@ RUN yum -y module enable nodejs:$NODEJS_VERSION && \ # Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH COPY ./s2i/bin/ $STI_SCRIPTS_PATH +RUN chmod +x /usr/libexec/s2i/init-wrapper # Copy extra files to the image. COPY ./root/ / diff --git a/22/Dockerfile.c9s b/22/Dockerfile.c9s index efb59bf5..0b47eefa 100644 --- a/22/Dockerfile.c9s +++ b/22/Dockerfile.c9s @@ -65,6 +65,7 @@ RUN dnf -y module enable nodejs:$NODEJS_VERSION && \ # Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH COPY ./s2i/bin/ $STI_SCRIPTS_PATH +RUN chmod +x /usr/libexec/s2i/init-wrapper # Copy extra files to the image. COPY ./root/ / diff --git a/22/Dockerfile.fedora b/22/Dockerfile.fedora index 89f588a5..81708749 100644 --- a/22/Dockerfile.fedora +++ b/22/Dockerfile.fedora @@ -59,6 +59,7 @@ RUN INSTALL_PKGS="make gcc gcc-c++ libatomic_ops git openssl-devel nodejs$NODEJS # Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH COPY ./s2i/bin/ $STI_SCRIPTS_PATH +RUN chmod +x /usr/libexec/s2i/init-wrapper # Copy extra files to the image, including help file. COPY ./root/ / diff --git a/22/Dockerfile.rhel8 b/22/Dockerfile.rhel8 index c2f5028b..15d4896e 100644 --- a/22/Dockerfile.rhel8 +++ b/22/Dockerfile.rhel8 @@ -65,6 +65,7 @@ RUN dnf -y module enable nodejs:$NODEJS_VERSION && \ # Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH COPY ./s2i/bin/ $STI_SCRIPTS_PATH +RUN chmod +x /usr/libexec/s2i/init-wrapper # Copy extra files to the image. COPY ./root/ / diff --git a/22/Dockerfile.rhel9 b/22/Dockerfile.rhel9 index 0dbe3cb7..c6c4a1e6 100644 --- a/22/Dockerfile.rhel9 +++ b/22/Dockerfile.rhel9 @@ -64,6 +64,7 @@ RUN dnf -y module enable nodejs:$NODEJS_VERSION && \ # Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH COPY ./s2i/bin/ $STI_SCRIPTS_PATH +RUN chmod +x /usr/libexec/s2i/init-wrapper # Copy extra files to the image. COPY ./root/ / diff --git a/22/README.md b/22/README.md index 19880178..3d814e81 100644 --- a/22/README.md +++ b/22/README.md @@ -142,6 +142,12 @@ Application developers can use the following environment variables to configure **`NPM_BUILD`** Select an alternate / custom build command, defined in your `package.json` file's [`scripts`](https://docs.npmjs.com/misc/scripts) section (default: npm run "build"). These user-defined run-scripts are unavailable while `DEV_MODE` is in use. +**`NODE_CMD`** + When specified (e.g.Specify `NODE_CMD="node server.js"`) the value of `NODE_CMD` is used to start the application instead of `npm start`. + +**`INIT_WRAPPER`** + When set to "true", the application is started via the `init-wrapper` script instead of using `npm start`, by looking for the presence of the files `server.js`, `index.js` or `main.js` in the order in which they are listed. In case of `NODE_CMD` environemnt variale is specified, then `init-wrapper` script will use the value of `NODE_CMD` to start your application. + **`NPM_RUN`** Select an alternate / custom runtime mode, defined in your `package.json` file's [`scripts`](https://docs.npmjs.com/misc/scripts) section (default: npm run "start"). These user-defined run-scripts are unavailable while `DEV_MODE` is in use. @@ -244,6 +250,47 @@ Below is an example _package.json_ file with the _main_ attribute and _start_ sc #### Note: `oc rsync` is only available in versions 3.1+ of OpenShift. +## init-wrapper + +init-wrapper script is located on `/usr/libexec/s2i/init-wrapper` and is used to handle: + +- Proper signal handling and propagation, as Node.js was not designed to run as PID 1. +- Reaping zombie child processes +Avoiding use of npm, there is more information on why you want to avoid that in the [Node.js reference architecture](https://github.com/nodeshift/nodejs-reference-architecture/blob/e4c4dc1fd20c2cac392e862859aaad27f85d504f/docs/development/building-good-containers.md#avoiding-using-npm-to-start-application). When the INIT_WRAPPER is set to true the application is started via the init script instead of using npm start. + +A detailed explanation on how the init-wrapper script works is avalable in +[this url](http://veithen.io/2014/11/16/sigterm-propagation.html). + +Example of using init-wrapper: + +**During image build** +``` +s2i -e INIT_WRAPPER=true build . buildImage node-app +docker run node-app +``` +**During container start** +``` +s2i build . buildImage node-app +docker run -e INIT_WRAPPER=true node-app +``` + +`init-wrapper` script can be disabled by setting the `INIT_WRAPPER` env variable to `false`. + +``` +docker run -e INIT_WRAPPER=false node-app +``` +`NODE_CMD` can be used during the build process or container start, in order to have more control on the command that `init-wrapper` script will wrap. + +For example: + +**during container build** +``` +s2i -e INIT_WRAPPER=true -e NODE_CMD="node index.js" build . buildImage node-app +``` +**during container start** +``` +docker run -e INIT_WRAPPER=false -e NODE_CMD="node index.js" node-app +``` See also -------- diff --git a/22/s2i/bin/init-wrapper b/22/s2i/bin/init-wrapper new file mode 100644 index 00000000..24e0d078 --- /dev/null +++ b/22/s2i/bin/init-wrapper @@ -0,0 +1,18 @@ +#!/bin/bash + +# Overview of how this script works: http://veithen.io/2014/11/16/sigterm-propagation.html +# Set a trap to kill the main app process when this +# init script receives SIGTERM or SIGINT +trap 'kill -s TERM $PID' TERM INT +# Execute the main application in the background +"$@" & +PID=$! +# wait command always terminates when trap is caught, even if the process hasn't finished yet +wait $PID +# Remove the trap and wait till the app process finishes completely +trap - TERM INT +# We wait again, since the first wait terminates when trap is caught +wait $PID +# Exit with the exit code of the app process +STATUS=$? +exit $STATUS \ No newline at end of file diff --git a/22/s2i/bin/run b/22/s2i/bin/run index ff566f55..9a207bce 100755 --- a/22/s2i/bin/run +++ b/22/s2i/bin/run @@ -20,6 +20,29 @@ run_node() { if [ "$DEV_MODE" == true ]; then echo "Launching via nodemon..." exec nodemon --inspect="$DEBUG_PORT" + elif [ -n "$NODE_CMD" ] && [ "$INIT_WRAPPER" == true ]; then + echo "launching via init wrapper..." + exec ${STI_SCRIPTS_PATH}/init-wrapper $NODE_CMD + elif [ -n "$NODE_CMD" ] && [ "$INIT_WRAPPER" == false ]; then + echo "Launching via ${NODE_CMD}" + exec $NODE_CMD + elif [ ! -n "$NODE_CMD" ] && [ "$INIT_WRAPPER" == true ]; then + + package_json_start=$(sed -n 's/\s*"start"\s*:\s*"\(.*\)".*/\1/p' package.json) + package_json_main=$(sed -n 's/\s*"main"\s*:\s*"\(.*\)".*/\1/p' package.json) + + if [ -n "$package_json_start" ]; then + start_command=$package_json_start + elif [ -n $package_json_main ]; then + start_command="node ." + elif [ -f "server.js" ]; then + start_command="node server.js" + else + echo "Failed to find file for starting the Node.js application" + exit 1 + fi + echo "launching via init wrapper..." + exec ${STI_SCRIPTS_PATH}/init-wrapper $start_command else echo "Launching via npm..." exec npm run -d $NPM_RUN