From cc6f7364315df1f60f6250edff6330cc646644ce Mon Sep 17 00:00:00 2001 From: Robert Dickinson Date: Wed, 17 Aug 2016 00:02:45 -0700 Subject: [PATCH] Initial checkin. --- .gitignore | 2 + Makefile | 5 ++ README.md | 14 +++++ docker-compose.yml | 64 ++++++++++++++++++++ nginx.env | 2 + nginx/Dockerfile | 38 ++++++++++++ nginx/README.md | 28 +++++++++ nginx/containerpilot.json | 25 ++++++++ nginx/nginx.conf | 38 ++++++++++++ nginx/nginx.conf.ctmpl | 117 ++++++++++++++++++++++++++++++++++++ nginx/reload-nginx.sh | 85 ++++++++++++++++++++++++++ stashbox.env | 2 + synchro.env | 2 + synchro/.dockerignore | 4 ++ synchro/Dockerfile | 37 ++++++++++++ synchro/README.md | 19 ++++++ synchro/containerpilot.json | 13 ++++ synchro/prestart-synchro.sh | 14 +++++ 18 files changed, 509 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 nginx.env create mode 100644 nginx/Dockerfile create mode 100644 nginx/README.md create mode 100644 nginx/containerpilot.json create mode 100644 nginx/nginx.conf create mode 100644 nginx/nginx.conf.ctmpl create mode 100644 nginx/reload-nginx.sh create mode 100644 stashbox.env create mode 100644 synchro.env create mode 100644 synchro/.dockerignore create mode 100644 synchro/Dockerfile create mode 100644 synchro/README.md create mode 100644 synchro/containerpilot.json create mode 100644 synchro/prestart-synchro.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..309234e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +synchro/init +tmp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9117b57 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +# Build the docker images +# +build: + docker build -t synchro/synchro_nginx_ap nginx + docker build -t synchro/synchro_ap synchro diff --git a/README.md b/README.md new file mode 100644 index 0000000..c55e39c --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# Synchro AutoPilot + +This project is an implementation of the AutoPilot Pattern using ContainerPilot in support of the Synchro Server application. + +This project includes the container definition and support files for custom nginx and synchro deployments that are self-orchestrating using ContainerPilot. For more information on the AutoPilot Pattern using ContainerPilot, see: https://www.joyent.com/containerpilot + +The project also contains a Docker composition for running those containers, along with the other containers that they require. + +The beauty of self-orchestrating containers is that they can be run from any orchestration solution without need to take any special action other than to run the number of each type of container that is needed. In this applicaton, new instances of nginx will find the set of Synchro servers to route to, and when Synchro instances appear or dissapear, all nginx servers will automatically update, without your app orchestration system needing to be involved at all. + +While this project contains container definitions and support files, that would allow you to build your own images if desired, it should be noted that Synchro can be deployed using only the published containers from the Docker registry (as referenced from the docker-compose.yml file). Those images are: + + synchro/synchro_ap + synchro/synchro_nginx_ap diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a706e19 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,64 @@ +# Nginx as a load-balancing tier and reverse proxy, with caching and optional SSL termination +nginx: + image: synchro/synchro_nginx_ap:1.0.0 + mem_limit: 128m + ports: + - 80 + - 443 + links: + - consul + - stashbox + restart: always + command: > + /bin/containerpilot + -config file:///etc/containerpilot/containerpilot.json + nginx -g "daemon off;" + env_file: ./nginx.env + labels: + # Joyent: Setting the CNS service name (not needed unless running on Joyent and using CNS) + - triton.cns.services=synchro + +# The Synchro microservice +synchro: + image: synchro/synchro_ap:1.5.4 + links: + - redis + - consul + - stashbox + mem_limit: 512m + expose: + - 80 + environment: + - SYNCHRO__PORT=80 + - SYNCHRO__SESSIONSTORE_PACKAGE=synchro-api + - SYNCHRO__SESSIONSTORE_SERVICE=RedisSessionStore + - SYNCHRO__SESSIONSTORE__host=redis + - SYNCHRO__SESSIONSTORE__port=6379 + env_file: ./synchro.env + restart: always + +# StashBox +stashbox: + image: synchro/stashbox + mem_limit: 128m + expose: + - 80 + env_file: ./stashbox.env + +# redis +redis: + image: redis + mem_limit: 128m + expose: + - 6379 + +# service discovery tier +consul: + image: progrium/consul:latest + command: -server -bootstrap -ui-dir /ui + restart: always + mem_limit: 128m + expose: + - 8500 + dns: + - 127.0.0.1 diff --git a/nginx.env b/nginx.env new file mode 100644 index 0000000..e3019b5 --- /dev/null +++ b/nginx.env @@ -0,0 +1,2 @@ +# docker-compose env vars for Nginx containers +# diff --git a/nginx/Dockerfile b/nginx/Dockerfile new file mode 100644 index 0000000..7fd9292 --- /dev/null +++ b/nginx/Dockerfile @@ -0,0 +1,38 @@ +# The Synchro Nginx container including ContainerPilot +FROM gliderlabs/alpine:3.3 + +# install nginx and tooling we need +RUN apk update && apk add \ + nginx \ + curl \ + unzip \ + && rm -rf /var/cache/apk/* + +# we use consul-template to re-write our Nginx virtualhost config +RUN curl -Lo /tmp/consul_template_0.14.0_linux_amd64.zip https://releases.hashicorp.com/consul-template/0.14.0/consul-template_0.14.0_linux_amd64.zip && \ + unzip /tmp/consul_template_0.14.0_linux_amd64.zip && \ + mv consul-template /bin + +# get ContainerPilot release +ENV CONTAINERPILOT_VERSION 2.0.1 +RUN export CP_SHA1=a4dd6bc001c82210b5c33ec2aa82d7ce83245154 \ + && curl -Lso /tmp/containerpilot.tar.gz \ + "https://github.com/joyent/containerpilot/releases/download/${CONTAINERPILOT_VERSION}/containerpilot-${CONTAINERPILOT_VERSION}.tar.gz" \ + && echo "${CP_SHA1} /tmp/containerpilot.tar.gz" | sha1sum -c \ + && tar zxf /tmp/containerpilot.tar.gz -C /bin \ + && rm /tmp/containerpilot.tar.gz + +# add ContainerPilot configuration and onChange handler +COPY containerpilot.json /etc/containerpilot/ +COPY reload-nginx.sh /bin + +# Make the reload script executable... +RUN chmod +x /bin/reload-nginx.sh + +# add Nginx virtualhost configuration +COPY nginx.conf /etc/nginx/nginx.conf + +# add Nginx virtualhost template that we'll overwrite +COPY nginx.conf.ctmpl /etc/containerpilot/ + +EXPOSE 80 443 diff --git a/nginx/README.md b/nginx/README.md new file mode 100644 index 0000000..cdbf1a3 --- /dev/null +++ b/nginx/README.md @@ -0,0 +1,28 @@ +# Synchro Nginx AP + +Synchro Nginx AP is a Docker image of nginx that implements the AutoPilot Pattern using ContainerPilot, specifically for use in a Synchro application/composition. The published image is located at synchro/synchro_nginx_ap. In most cases, you should be able to use that image for your deployment. + +Following is a description of the environment variables supported: + +To replace Nginx configuration template (nginx.conf.ctmpl) from URL (Stashbox or other) + + NGINX_CTMPL_URL + +To Enable SSL + + SSL=1 + +To specify SSL cert/key locations + + SSL_CERTS_PATH // Defaults to /etc/ssl/certs/ssl.crt + SSL_KEY_PATH // Defaults to /ect/ssl/private/ssl.key + +To populate SSL cert/key files from base64 encoded env vars + + SSL_CERTS_BASE64 + SSL_KEY_BASE64 + +To populate SSL cert/key files from URL (Stashbox or other) + + SSL_CERTS_URL + SSL_KEY_URL diff --git a/nginx/containerpilot.json b/nginx/containerpilot.json new file mode 100644 index 0000000..2636fa3 --- /dev/null +++ b/nginx/containerpilot.json @@ -0,0 +1,25 @@ +{ + "consul": "consul:8500", + "preStart": "/bin/reload-nginx.sh preStart", + "logging": { + "level": "DEBUG", + "format": "text" + }, + "services": [ + { + "name": "nginx", + "port": 80, + "interfaces": ["eth1", "eth0"], + "health": "/usr/bin/curl -o /dev/null --fail -s http://localhost/health", + "poll": 10, + "ttl": 25 + } + ], + "backends": [ + { + "name": "synchro", + "poll": 3, + "onChange": "/bin/reload-nginx.sh onChange" + } + ] +} diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..e0b39a1 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,38 @@ +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + keepalive_timeout 65; + + server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + + location /health { + # requires http_stub_status_module + stub_status; + allow 127.0.0.1; + deny all; + } + } +} diff --git a/nginx/nginx.conf.ctmpl b/nginx/nginx.conf.ctmpl new file mode 100644 index 0000000..d7dd854 --- /dev/null +++ b/nginx/nginx.conf.ctmpl @@ -0,0 +1,117 @@ +user nginx; +worker_processes {{ if env "SSL" }}auto{{ else }}1{{ end }}; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=synchro_cache:10m inactive=60m use_temp_path=off; + + sendfile on; + keepalive_timeout 65; + + {{ if service "synchro" }} + upstream synchro { + # write the address:port pairs for each healthy Synchro node + {{range service "synchro"}} + server {{.Address}}:{{.Port}}; + {{end}} + ip_hash; # Poor man's session affinity (for Synchro async ops) + # least_conn; + }{{ end }} + + server { + + {{ if env "SSL" }} + listen 443 ssl; + + # https://www.ssllabs.com/ssltest/index.html + + ssl_certificate {{ or (env "SSL_CERTS_PATH") "/etc/ssl/certs/ssl.crt" }}; + ssl_certificate_key {{ or (env "SSL_KEY_PATH") "/etc/ssl/private/ssl.key" }}; + + ssl_session_cache shared:SSL:20m; + ssl_session_timeout 180m; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + + ssl_prefer_server_ciphers on; + ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5; + {{ else }} + listen 80; + {{ end }} + + server_name _; + + root /usr/share/nginx/html; + + location /health { + # requires http_stub_status_module + stub_status; + allow 127.0.0.1; + deny all; + } + + {{ if service "synchro" }} + location ^~ / { + proxy_cache synchro_cache; + proxy_cache_bypass $http_cache_control; + add_header X-Proxy-Cache $upstream_cache_status; + + # websocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + proxy_pass http://synchro; + proxy_set_header Host $http_host; # Pass the http Host header as received (instead of rewriting to upstream host) + {{ if env "SSL" }} + proxy_set_header x-arr-ssl "yes"; # Add SSL indicator for upstream + {{ else }} + proxy_set_header x-arr-ssl ""; # Turn off any downstream SSL indicator + {{ end }} + # add_header X-Upstream $upstream_addr; # For test - to verify upstream from client testing (turn off ip_hash) + proxy_redirect off; + }{{end}} + + } + + {{ if env "SSL" }} + server { + + listen 80; + + server_name _; + + location /health { + # requires http_stub_status_module + stub_status; + allow 127.0.0.1; + deny all; + } + + location ^~ / { + return 301 https://$host$request_uri; + } + } + {{ end }} + +} diff --git a/nginx/reload-nginx.sh b/nginx/reload-nginx.sh new file mode 100644 index 0000000..6477d8d --- /dev/null +++ b/nginx/reload-nginx.sh @@ -0,0 +1,85 @@ +#!/bin/sh + +# ContainerPilot preStart +# +preStart() { + echo "preStart" + + # Get SSL certs from env or remote URL, if so specified + # + : ${SSL_CERTS_PATH:="/etc/ssl/certs/ssl.crt"} + if [ -n "$SSL_CERTS_BASE64" ]; then + echo "Decode SSL_CERTS_BASE64 to $SSL_CERTS_PATH" + echo $SSL_CERTS_BASE64 | base64 -d > $SSL_CERTS_PATH + elif [ -n "$SSL_CERTS_URL" ]; then + echo "Download SSL certs from url to $SSL_CERTS_PATH" + curl $SSL_CERTS_URL -s -S -f -o $SSL_CERTS_PATH + if [ "$?" = "7" ]; then + echo "Connection refused, retrying in 5 seconds" + sleep 5 + curl $SSL_CERTS_URL -s -S -f -o $SSL_CERTS_PATH + fi; + fi; + + # Get SSL key from env or remote URL, if so specified + # + : ${SSL_KEY_PATH:="/etc/ssl/private/ssl.key"} + if [ -n "$SSL_KEY_BASE64" ]; then + echo "Decode SSL_KEY_BASE64 to $SSL_KEY_PATH" + echo $SSL_KEY_BASE64 | base64 -d > $SSL_KEY_PATH + elif [ -n "$SSL_CERTS_URL" ]; then + echo "Download SSL key from url to $SSL_KEY_PATH" + curl $SSL_KEY_URL -s -S -f -o $SSL_KEY_PATH + if [ "$?" = "7" ]; then + echo "Connection refused, retrying in 5 seconds" + sleep 5 + curl $SSL_KEY_URL -s -S -f -o $SSL_KEY_PATH + fi; + fi; + + # Get Nginx config template from remote URL, if so specified + # + if [ -n "$NGINX_CTMPL_URL" ]; then + echo "Download Nginx config template from url" + curl $NGINX_CTMPL_URL -s -S -f -o /etc/containerpilot/nginx.conf.ctmpl + if [ "$?" = "7" ]; then + echo "Connection refused, retrying in 5 seconds" + sleep 5 + curl $NGINX_CTMPL_URL -s -S -f -o /etc/containerpilot/nginx.conf.ctmpl + fi; + fi; + + # Render Nginx configuration template using values from Consul, + # but do not reload because Nginx has't started yet + # + consul-template \ + -once \ + -consul consul:8500 \ + -template "/etc/containerpilot/nginx.conf.ctmpl:/etc/nginx/nginx.conf" +} + +# ContainerPilot onChange +# +onChange() { + echo "onChange" + # Render Nginx configuration template using values from Consul, + # then gracefully reload Nginx + # + consul-template \ + -once \ + -consul consul:8500 \ + -template "/etc/containerpilot/nginx.conf.ctmpl:/etc/nginx/nginx.conf:nginx -s reload" +} + +until + cmd=$1 + if [ -z "$cmd" ]; then + onChange + fi + shift 1 + $cmd "$@" + [ "$?" -ne 127 ] +do + onChange + exit +done diff --git a/stashbox.env b/stashbox.env new file mode 100644 index 0000000..4b42212 --- /dev/null +++ b/stashbox.env @@ -0,0 +1,2 @@ +# docker-compose env vars for StashBox containers +# diff --git a/synchro.env b/synchro.env new file mode 100644 index 0000000..4bad301 --- /dev/null +++ b/synchro.env @@ -0,0 +1,2 @@ +# docker-compose env vars for Synchro containers +# diff --git a/synchro/.dockerignore b/synchro/.dockerignore new file mode 100644 index 0000000..c9f5660 --- /dev/null +++ b/synchro/.dockerignore @@ -0,0 +1,4 @@ +Dockerfile +init/Dockerfile +init/node_modules/ +init/synchro-apps/node_modules/ diff --git a/synchro/Dockerfile b/synchro/Dockerfile new file mode 100644 index 0000000..7329fe0 --- /dev/null +++ b/synchro/Dockerfile @@ -0,0 +1,37 @@ +# Latest Node.js 4.x LTS +FROM node:argon + +# Get ContainerPilot release +ENV CONTAINERPILOT_VERSION 2.0.1 +RUN export CP_SHA1=a4dd6bc001c82210b5c33ec2aa82d7ce83245154 \ + && curl -Lso /tmp/containerpilot.tar.gz \ + "https://github.com/joyent/containerpilot/releases/download/${CONTAINERPILOT_VERSION}/containerpilot-${CONTAINERPILOT_VERSION}.tar.gz" \ + && echo "${CP_SHA1} /tmp/containerpilot.tar.gz" | sha1sum -c \ + && tar zxf /tmp/containerpilot.tar.gz -C /bin \ + && rm /tmp/containerpilot.tar.gz + +# add ContainerPilot configuration and scripts +COPY containerpilot.json /etc/containerpilot.json +ENV CONTAINERPILOT=file:///etc/containerpilot.json + +# Copy the prestart script and make it executable... +COPY prestart-synchro.sh /bin +RUN chmod +x /bin/prestart-synchro.sh + +# Create app directory +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +# Install Synchro +# +RUN npm install -g synchro +RUN synchro init -v 1.5.4 # to install specific version + +# OPTIONAL - Copy app config/source from Synchro installed in init/ +# +# COPY init/config.json /usr/src/app +# COPY init/synchro-apps /usr/src/app/synchro-apps + +EXPOSE 80 + +CMD [ "/bin/containerpilot", "node", "app.js" ] diff --git a/synchro/README.md b/synchro/README.md new file mode 100644 index 0000000..8f9d0bd --- /dev/null +++ b/synchro/README.md @@ -0,0 +1,19 @@ +# Synchro AP + +Synchro AP is a Docker image of synchro that implements the AutoPilot Pattern using ContainerPilot, specifically for use in a Synchro application/composition. + +In addition to the standard Synchro environment variables supported, you may also specifiy that the Synchro config file (typically config.json) be retreived from a remote URL (using StashBox or other) by specifying: + + SYNCHRO_CONFIG_URL + +It is possible to deploy Synchro using the published image: synchro/synchro_ap, without bundling in any deployment-specific files, by specifying a SYNCHRO_CONFIG_URL that points to a config file, which itself configures a remote module store, or by doing the equivalent via environment variables. + +That being said, if you wish to bundle your Synchro configuration and/or you Synchro app code, we also support that mode. Create a directory here (under synchro/) called "init", and in that directory, install Synchro and add any configuration or apps. Then uncomment the appropriate lines in the Dockerfile so that your Synchro files will be copied into the image. Then you may build that image. + + mkdir init + cd init + npm install -g synchro # if not already installed + synchro init + synchro install foo # Your app here + cd ../.. + docker build -t your_image_name synchro diff --git a/synchro/containerpilot.json b/synchro/containerpilot.json new file mode 100644 index 0000000..05b89a4 --- /dev/null +++ b/synchro/containerpilot.json @@ -0,0 +1,13 @@ +{ + "consul": "consul:8500", + "preStart": "/bin/prestart-synchro.sh", + "services": [ + { + "name": "synchro", + "port": 80, + "health": "/usr/bin/curl -o /dev/null --fail -s http://localhost:80/health", + "poll": 10, + "ttl": 25 + } + ] +} diff --git a/synchro/prestart-synchro.sh b/synchro/prestart-synchro.sh new file mode 100644 index 0000000..e3e86e7 --- /dev/null +++ b/synchro/prestart-synchro.sh @@ -0,0 +1,14 @@ +#!/bin/sh +echo Prestart of Synchro + +# Get Synchro config.json from remote URL, if so specified +# +if [ -n "$SYNCHRO_CONFIG_URL" ]; then + echo "Download Synchro config.json template from url" + curl $SYNCHRO_CONFIG_URL -s -S -f -o /usr/src/app/config.json + if [ "$?" = "7" ]; then + echo "Connection refused, retrying in 5 seconds" + sleep 5 + curl $SYNCHRO_CONFIG_URL -s -S -f -o /usr/src/app/config.json + fi; +fi;