diff --git a/Dockerfile b/Dockerfile index 1b1f229..4e37778 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,30 +7,49 @@ ENV SIGNAL_BUILD_STOP=99 \ CONTAINER_ROLE=web \ CONF_NGINX_SITE="/etc/nginx/sites-available/default" \ CONF_NGINX_SERVER="/etc/nginx/nginx.conf" \ - NOT_ROOT_USER=docker + NOT_ROOT_USER=www-data \ + S6_BEHAVIOUR_IF_STAGE2_FAILS=2 \ + S6_KILL_FINISH_MAXTIME=5000 \ + S6_KILL_GRACETIME=3000 -# Create an unprivileged user -RUN useradd -r -s /bin/false $NOT_ROOT_USER +# Ensure base system is up to date RUN apt-get update && \ - apt-get install -yq \ - openssl \ - ca-certificates \ + apt-get upgrade -yqq && \ + # Install pre-reqs \ + apt-get install -yqq \ software-properties-common \ - supervisor \ - nano \ && \ - rm -rf /var/lib/apt/lists/* - -# Install latest nginx (development PPA is actually mainline development) -RUN add-apt-repository ppa:nginx/development -y && \ - apt-get update -yq && \ - apt-get install -yq nginx \ + # Install latest nginx (development PPA is actually mainline development) \ + add-apt-repository ppa:nginx/development -y && \ + apt-get update -yqq && \ + apt-get install -yqq nginx \ && \ - rm -rf /var/lib/apt/lists/* + # Perform cleanup, ensure unnecessary packages are removed \ + apt-get remove --purge -yq \ + manpages \ + manpages-dev \ + man-db \ + patch \ + make \ + unattended-upgrades \ + python* \ + && \ + apt-get autoclean -y && \ + apt-get autoremove -y && \ + rm -rf /var/lib/{cache,log}/ && \ + rm -rf /var/lib/apt/lists/ && \ + rm -rf /tmp/* /var/tmp/* -# # Overlay the root filesystem from this repo +# Overlay the root filesystem from this repo COPY ./container/root / +# Add S6 overlay build, to avoid having to build from source +RUN tar xzf /tmp/s6-overlay-amd64.tar.gz -C / && \ + rm /tmp/s6-overlay-amd64.tar.gz + EXPOSE 80 + +# NOTE: intentionally NOT using s6 init as the entrypoint +# This would prevent container debugging if any of those service crash CMD ["/bin/bash", "/run.sh"] diff --git a/README.md b/README.md index f47cec4..23307a5 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,60 @@ # docker-nginx -Provides base OS, patches and stable nginx for quick and easy spinup +Provides base OS, patches and stable nginx for quick and easy spinup. +Integrates S6 process supervisor for zombie reaping (as PID 1) and boot coordination. +@see https://github.com/just-containers/s6-overlay +### Expectations + +Applications using this as a container parent must copy their html/app into the `/var/www/html` folder + + + +### Environment Variables Variable | Example | Description --- | --- | --- `SERVER_MAX_BODY_SIZE` | `SERVER_MAX_BODY_SIZE=4M` | Allows the downstream application to specify a non-default `client_max_body_size` configuration for the `server`-level directive in `/etc/nginx/sites-available/default` `SERVER_INDEX` | `SERVER_INDEX index.html index.html index.php` | Changes the default pages to hit for folder and web roots -`SERVER_APP_NAME` | `SERVER_APP_NAME='view'` | Sets a kv pair to be consumed by logging service for easy parsing and searching +`SERVER_APP_NAME` | `SERVER_APP_NAME='view'` | Gets appended to the default logging format `SERVER_GZIP_OPTIONS` | `SERVER_GZIP_OPTIONS=1` | Allows default set of static content to be served gzipped `SERVER_SENDFILE` | `SERVER_SENDFILE=off` | Allows runtime to specify value of nginx's `sendfile` (default, on) `SERVER_KEEPALIVE` | `SERVER_KEEPALIVE=30` | Define HTTP 1.1's keepalive timeout `SERVER_WORKER_CONNECTIONS` | `SERVER_WORKER_CONNECTIONS=2048` | Sets up the number of connections for worker processes `SERVER_LOG_MINIMAL` | `SERVER_LOG_MINIMAL=1` | Minimize the logging format, appropriate for development environments +`S6_KILL_FINISH_MAXTIME` | `S6_KILL_FINISH_MAXTIME=1000` | Wait time (in ms) for zombie reaping before sending a kill signal +`S6_KILL_GRACETIME` | `S6_KILL_GRACETIME=500` | Wait time (in ms) for S6 finish scripts before sending kill signal + + +### Startup/Runtime Modification +To inject changes just before runtime, shell scripts (ending in .sh) may be placed into the +`/etc/cont-init.d` folder. For example, the above environment variables are used to drive nginx configuration at runtime. +As part of the process manager, these scripts are run in advance of the supervised processes. @see https://github.com/just-containers/s6-overlay#executing-initialization-andor-finalization-tasks -### Runtime Commands -To inject things into the runtime process, add shell scripts (ending in .sh) into the -`/run.d` folder. These will be executed during container start. +### Advanced Modification -- If script terminates with a non-zero exit code, container will stop, terminating with the script's exit code, unless... -- If script terminates with exit code of $SIGNAL_BUILD_STOP (99), this will signal the container to stop cleanly. This can be used for multi-stage builds that can be committed +More advanced changes can take effect using the run.d system. Similar to the `/etc/cont-init.d/` script system, any scripts (ending in .sh) in the `/run.d/` folder will be executed ahead of the S6 initialization. +- If run.d script terminates with a non-zero exit code, container will stop, terminating with the script's exit code, unless... +- If script terminates with exit code of $SIGNAL_BUILD_STOP (99), this will signal the container to stop cleanly. This can be used for multi-stage builds -### Long-running processes (workers) + +### Long-running processes (workers + crons) + +This container image can be shared between web and non-web processes. An example use case would be +a web service and codebase that also has a few crons and background workers. To reuse this container for +those types of workloads: `docker run {image_id} /worker.sh 3 /bin/binary -parameters -that -binary -receives` -Runs 3 copies of `/bin/binary` that receives any arguments as parameters +Runs `3` copies of `/bin/binary` that receives the parameters `-parameters -that -binary -receives` + + +### Container Organization + +Besides the instructions contained in the Dockerfile, the majority of this +container's use is in configuration and process. The `./container/root` repo directory is overlayed into a container during build. Adding additional files +to the folders in there will be present in the final image. + +Nginx is currently set up as an S6 service in `/etc/services-available/nginx`, during default environment conditions, it will symlink itself to be supervised under `/etc/services.d/nginx`. When running under worker entrypoint (`worker.sh`), it will not be S6's `service.d` folder to be supervised. diff --git a/container/root/run.d/10-nginx.sh b/container/root/etc/cont-init.d/10-nginx.sh similarity index 98% rename from container/root/run.d/10-nginx.sh rename to container/root/etc/cont-init.d/10-nginx.sh index 299251b..0265ef7 100755 --- a/container/root/run.d/10-nginx.sh +++ b/container/root/etc/cont-init.d/10-nginx.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/with-contenv bash if [[ $SERVER_APP_NAME ]] then diff --git a/container/root/etc/fix-attrs.d/01-stdout b/container/root/etc/fix-attrs.d/01-stdout new file mode 100644 index 0000000..64485c7 --- /dev/null +++ b/container/root/etc/fix-attrs.d/01-stdout @@ -0,0 +1 @@ +/dev/stdout false www-data 0644 2700 diff --git a/container/root/etc/fix-attrs.d/02-tmp b/container/root/etc/fix-attrs.d/02-tmp new file mode 100644 index 0000000..1bffe57 --- /dev/null +++ b/container/root/etc/fix-attrs.d/02-tmp @@ -0,0 +1,4 @@ +# Shouldn't need to be two separate statements, something is requiring it +/tmp/.nginx true www-data 0644 2700 +/tmp true www-data 0644 2700 + diff --git a/container/root/etc/nginx/nginx.conf b/container/root/etc/nginx/nginx.conf index 741838d..ff810bb 100644 --- a/container/root/etc/nginx/nginx.conf +++ b/container/root/etc/nginx/nginx.conf @@ -7,10 +7,12 @@ # add to the run.d/nginx script ############################################################# -user www-data; +# Only set when running with superuser permissions, otherwise causes a warning +# user www-data; + worker_processes auto; -pid /tmp/nginx.pid; +pid /tmp/.nginx/nginx.pid; events { # @see http://serverfault.com/questions/209014/how-can-i-observe-what-nginx-is-doing-to-solve-1024-worker-connections-are-n @@ -28,7 +30,7 @@ http { log_format minimal '$request_method $request_uri $status'; - error_log /dev/stdout info; + error_log /dev/stdout; access_log /dev/stdout main; sendfile on; @@ -38,11 +40,11 @@ http { #gzip on; - client_body_temp_path /tmp/client_body; - fastcgi_temp_path /tmp/fastcgi_temp; - proxy_temp_path /tmp/proxy_temp; - scgi_temp_path /tmp/scgi_temp; - uwsgi_temp_path /tmp/uwsgi_temp; + client_body_temp_path /tmp/.nginx/client_body; + fastcgi_temp_path /tmp/.nginx/fastcgi_temp; + proxy_temp_path /tmp/.nginx/proxy_temp; + scgi_temp_path /tmp/.nginx/scgi_temp; + uwsgi_temp_path /tmp/.nginx/uwsgi_temp; # Everything available is enabled in the container include /etc/nginx/sites-available/*; diff --git a/container/root/etc/services-available/nginx/finish b/container/root/etc/services-available/nginx/finish new file mode 100644 index 0000000..16d4dc8 --- /dev/null +++ b/container/root/etc/services-available/nginx/finish @@ -0,0 +1,7 @@ +#!/usr/bin/execlineb -S1 + +# @see https://github.com/just-containers/s6-overlay/issues/101 +if { s6-test ${1} -ne 0 } +if { s6-test ${1} -ne 256 } + +s6-svscanctl -t /var/run/s6/services diff --git a/container/root/etc/services-available/nginx/run b/container/root/etc/services-available/nginx/run new file mode 100644 index 0000000..01ef178 --- /dev/null +++ b/container/root/etc/services-available/nginx/run @@ -0,0 +1,4 @@ +#!/usr/bin/execlineb -P +s6-setuidgid www-data + +nginx -g "daemon off;" diff --git a/container/root/etc/services.d/.gitkeep b/container/root/etc/services.d/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/container/root/init.sh b/container/root/init.sh index 739acc8..b9f10a4 100755 --- a/container/root/init.sh +++ b/container/root/init.sh @@ -8,25 +8,30 @@ STATUS=0 # When .sh run scripts fail (exit non-zero), container run will fail # NOTE: if a .sh script exits with 99, this is a stop signal, container must exit cleanly -for file in $RUN_SCRIPTS/*.sh; do +if ls ${RUN_SCRIPTS}/*.sh &>/dev/null +then + for file in $RUN_SCRIPTS/*.sh; do - echo "[init] executing ${file}" + echo "[init] executing ${file}" - # Note: -e will enforce that any subcommand that fails will fail the entire script run - /bin/bash -e $file + # Note: -e will enforce that any subcommand that fails will fail the entire script run + /bin/bash -e $file - STATUS=$? # Captures exit code from script that was run + STATUS=$? # Captures exit code from script that was run - if [[ $STATUS == $SIGNAL_BUILD_STOP ]] - then - echo "[init] exit signalled - ${file}" - exit $STATUS - fi + if [[ $STATUS == $SIGNAL_BUILD_STOP ]] + then + echo "[init] exit signalled - ${file}" + exit $STATUS + fi - if [[ $STATUS != 0 ]] - then - echo "[init] failed executing - ${file}" - exit $STATUS - fi + if [[ $STATUS != 0 ]] + then + echo "[init] failed executing - ${file}" + exit $STATUS + fi -done + done +else + echo "[init] no run.d scripts" +fi diff --git a/container/root/run.d/99-nginx.sh b/container/root/run.d/99-nginx.sh new file mode 100644 index 0000000..df2af7d --- /dev/null +++ b/container/root/run.d/99-nginx.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Ensure that worker entrypoint does not also run nginx processes +if [ $CONTAINER_ROLE == 'web' ] +then + echo '[run] enabling web server' + + # Unfortunately, until Dockerhub supports this operation...it has to be done here + setcap cap_net_bind_service=+ep /usr/sbin/nginx + + # Enable nginx as a supervised service + ln -s /etc/services-available/nginx /etc/services.d/nginx +fi diff --git a/container/root/run.d/README b/container/root/run.d/README deleted file mode 100644 index 9175dbc..0000000 --- a/container/root/run.d/README +++ /dev/null @@ -1,7 +0,0 @@ -### Runtime commands ---- - -Allows children to inject things into the run process. - -Drop bash scripts in here to be executed by entrypoint during startup. - diff --git a/container/root/run.sh b/container/root/run.sh index 013fdb0..3865711 100755 --- a/container/root/run.sh +++ b/container/root/run.sh @@ -18,6 +18,6 @@ then exit $STATUS fi -# Primary command - starting webserver -echo "[nginx] start (foreground)" -exec /usr/sbin/nginx -g "daemon off;" +# Start process manager +echo "[run] starting process manager" +exec /init diff --git a/container/root/tmp/.nginx/.gitkeep b/container/root/tmp/.nginx/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/container/root/tmp/s6-overlay-amd64.tar.gz b/container/root/tmp/s6-overlay-amd64.tar.gz new file mode 100644 index 0000000..f6996c9 Binary files /dev/null and b/container/root/tmp/s6-overlay-amd64.tar.gz differ diff --git a/container/root/worker.sh b/container/root/worker.sh index 0a85260..b7e7629 100755 --- a/container/root/worker.sh +++ b/container/root/worker.sh @@ -4,6 +4,7 @@ # Based on configuration, can run multiple instances of a single worker process SUPERVISOR_CONF=/etc/supervisor/conf.d/worker.conf +SERVICES_D=/etc/services.d # Signal to init processes to avoid any webserver startup export CONTAINER_ROLE='worker' @@ -42,17 +43,19 @@ fi echo "[worker] command: '${WORKER_COMMAND}' quantity: ${WORKER_QUANTITY}" -echo "\ -[program:worker] -command=${WORKER_COMMAND} -process_name=%(program_name)s%(process_num)s -numprocs=${WORKER_QUANTITY} -autorestart=true -redirect_stderr=true -stdout_logfile_maxbytes=0 -stdout_logfile=/dev/stdout" > $SUPERVISOR_CONF +for i in `seq 1 $WORKER_QUANTITY`; +do + SERVICE_FOLDER="${SERVICES_D}/worker-${i}" + mkdir $SERVICE_FOLDER + echo "\ +#!/usr/bin/execlineb -P -echo "[worker] entering supervisor" +with-contenv +s6-setuidgid ${NOT_ROOT_USER} +${WORKER_COMMAND}" > "${SERVICE_FOLDER}/run" +done + +# Start process manager +echo "[run] starting process manager" +exec /init -# Primary command - starting supervisor after writing with worker program config file -exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf --nodaemon