diff --git a/README.md b/README.md index 0b4d5e06..075766fc 100644 --- a/README.md +++ b/README.md @@ -270,53 +270,15 @@ Docker will automatically deliver a `SIGTERM` with `docker stop`, not when using Please report any issues you encounter with Containerbuddy or its documentation by [opening a Github issue](https://github.com/joyent/containerbuddy/issues). Roadmap items will be maintained as [enhancements](https://github.com/joyent/containerbuddy/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement). PRs are welcome on any issue. -### Running the example - -In the `examples` directory is a simple application demonstrating how Containerbuddy works. In this application, an Nginx node acts as a reverse proxy for any number of upstream application nodes. The application nodes register themselves with Consul as they come online, and the Nginx application is configured with an `onChange` handler that uses `consul-template` to write out a new virtualhost configuration file and then fires an `nginx -s reload` signal to Nginx, which causes it to gracefully reload its configuration. - -To try this example on your own: - -1. [Get a Joyent account](https://my.joyent.com/landing/signup/) and [add your SSH key](https://docs.joyent.com/public-cloud/getting-started). -1. Install the [Docker Toolbox](https://docs.docker.com/installation/mac/) (including `docker` and `docker-compose`) on your laptop or other environment, as well as the [Joyent CloudAPI CLI tools](https://apidocs.joyent.com/cloudapi/#getting-started) (including the `smartdc` and `json` tools) -1. [Configure Docker and Docker Compose for use with Joyent](https://docs.joyent.com/public-cloud/api-access/docker): - -```bash -curl -O https://raw.githubusercontent.com/joyent/sdc-docker/master/tools/sdc-docker-setup.sh && chmod +x sdc-docker-setup.sh -./sdc-docker-setup.sh -k us-east-1.api.joyent.com ~/.ssh/ -``` - -At this point you can run the example on Triton: - -```bash -$ env | grep -E '(SDC_URL|DOCKER_HOST)' -SDC_URL=https://us-east-1.api.joyentcloud.com -DOCKER_HOST=tcp://us-east-1.docker.joyent.com:2376 -$ cd ./examples -$ ./run.sh consul -p example - -``` - -or in your local Docker environment: - -```bash -$ env | grep DOCKER_HOST -DOCKER_HOST=tcp://192.168.99.100:2376 -$ cd ./examples -$ curl -Lo containerbuddy-0.0.4.tar.gz \ - https://github.com/joyent/containerbuddy/releases/download/0.0.4/containerbuddy-0.0.4.tar.gz -$ tar -xf containerbuddy-0.0.4.tar.gz -$ cp ./containerbuddy ./consul/nginx/opt/containerbuddy/ -$ cp ./containerbuddy ./consul/app/opt/containerbuddy/ -./run.sh consul -p example -f docker-compose-local.yml - -``` - -Let's scale up the number of `app` nodes: - -```bash -docker-compose -p example scale app=3 -``` - -(Note that if we scale up app nodes locally we don't have an IP-per-container and this will result in port conflicts.) - -As the nodes launch and register themselves with Consul, you'll see them appear in the Consul UI. The web page that the start script opens refreshes itself every 5 seconds, so once you've added new application containers you'll start seeing the "This page served by app server: " change in a round-robin fashion. +### Examples + +We've published a number of example applications demonstrating how Containerbuddy works. + +- [CloudFlare DNS and CDN with dynamic origins](https://github.com/tgross/triton-cloudflare) +- [Consul, running as an HA raft](https://github.com/misterbisson/triton-consul) +- [Couchbase](https://www.joyent.com/blog/couchbase-in-docker-containers) +- [ELK stack](https://github.com/tgross/triton-elk) +- [Mesos on Joyent Triton](https://www.joyent.com/blog/mesos-by-the-pound) +- [Nginx with dynamic upstreams](https://www.joyent.com/blog/dynamic-nginx-upstreams-with-containerbuddy) +- [MySQL (Percona Server) with auto scaling and fail-over](https://www.joyent.com/blog/dbaas-simplicity-no-lock-in) +- [Node.js + Nginx + Couchbase](https://www.joyent.com/blog/how-to-dockerize-a-complete-application) diff --git a/containerbuddy/config_test.go b/containerbuddy/config_test.go index f1374155..7f8577fc 100644 --- a/containerbuddy/config_test.go +++ b/containerbuddy/config_test.go @@ -56,7 +56,7 @@ func TestValidConfigParse(t *testing.T) { defer argTestCleanup(argTestSetup()) os.Setenv("TEST", "HELLO") - os.Args = []string{"this", "-config", testJSON, "/test.sh", "valid1", "--debug"} + os.Args = []string{"this", "-config", testJSON, "/testdata/test.sh", "valid1", "--debug"} config, _ := loadConfig() if !reflect.DeepEqual(config, getConfig()) { t.Errorf("Global config was not written after load") @@ -66,7 +66,7 @@ func TestValidConfigParse(t *testing.T) { t.Errorf("Expected 2 backends and 2 services but got: %v", config) } args := flag.Args() - if len(args) != 3 || args[0] != "/test.sh" { + if len(args) != 3 || args[0] != "/testdata/test.sh" { t.Errorf("Expected 3 args but got unexpected results: %v", args) } @@ -155,7 +155,7 @@ func validateParseError(t *testing.T, matchStrings []string, config *Config) { } func TestOnChangeCmd(t *testing.T) { - cmd1 := strToCmd("/root/examples/test/test.sh doStuff --debug") + cmd1 := strToCmd("./testdata/test.sh doStuff --debug") backend := &BackendConfig{ onChangeCmd: cmd1, } @@ -169,7 +169,7 @@ func TestOnChangeCmd(t *testing.T) { } func TestHealthCheck(t *testing.T) { - cmd1 := strToCmd("/root/examples/test/test.sh doStuff --debug") + cmd1 := strToCmd("./testdata/test.sh doStuff --debug") service := &ServiceConfig{ healthCheckCmd: cmd1, } @@ -189,15 +189,15 @@ func TestParseCommandArgs(t *testing.T) { t.Errorf("Unexpected parse error: %s", err.Error()) } - expected := []string{"/test.sh", "arg1"} - json1 := json.RawMessage(`"/test.sh arg1"`) + expected := []string{"/testdata/test.sh", "arg1"} + json1 := json.RawMessage(`"/testdata/test.sh arg1"`) if cmd, err := parseCommandArgs(json1); err == nil { validateCommandParsed(t, "json1", cmd, expected) } else { t.Errorf("Unexpected parse error json1: %s", err.Error()) } - json2 := json.RawMessage(`["/test.sh","arg1"]`) + json2 := json.RawMessage(`["/testdata/test.sh","arg1"]`) if cmd, err := parseCommandArgs(json2); err == nil { validateCommandParsed(t, "json2", cmd, expected) } else { @@ -231,7 +231,7 @@ func validateCommandParsed(t *testing.T, name string, parsed *exec.Cmd, expected func TestInvalidConfigNoConfigFlag(t *testing.T) { defer argTestCleanup(argTestSetup()) - os.Args = []string{"this", "/test.sh", "invalid1", "--debug"} + os.Args = []string{"this", "/testdata/test.sh", "invalid1", "--debug"} if _, err := loadConfig(); err != nil && err.Error() != "-config flag is required" { t.Errorf("Expected error but got %s", err) } @@ -290,7 +290,7 @@ func argTestCleanup(oldArgs []string) { } func testParseExpectError(t *testing.T, testJSON string, expected string) { - os.Args = []string{"this", "-config", testJSON, "/test.sh", "test", "--debug"} + os.Args = []string{"this", "-config", testJSON, "/testdata/test.sh", "test", "--debug"} if _, err := loadConfig(); err != nil && !strings.Contains(err.Error(), expected) { t.Errorf("Expected %s but got %s", expected, err) } diff --git a/containerbuddy/main_test.go b/containerbuddy/main_test.go index b6f7474b..a189ded1 100644 --- a/containerbuddy/main_test.go +++ b/containerbuddy/main_test.go @@ -18,11 +18,11 @@ func TestPoll(t *testing.T) { } func TestRunSuccess(t *testing.T) { - cmd1 := strToCmd("/root/examples/test/test.sh doStuff --debug") + cmd1 := strToCmd("./testdata/test.sh doStuff --debug") if exitCode, _ := run(cmd1); exitCode != 0 { t.Errorf("Expected exit code 0 but got %d", exitCode) } - cmd2 := argsToCmd([]string{"/root/examples/test/test.sh", "doStuff", "--debug"}) + cmd2 := argsToCmd([]string{"./testdata/test.sh", "doStuff", "--debug"}) if exitCode, _ := run(cmd2); exitCode != 0 { t.Errorf("Expected exit code 0 but got %d", exitCode) } @@ -34,7 +34,7 @@ func TestRunFailed(t *testing.T) { t.Errorf("Expected panic but did not.") } }() - cmd := strToCmd("/root/examples/test/test.sh failStuff --debug") + cmd := strToCmd("./testdata/test.sh failStuff --debug") if exitCode, _ := run(cmd); exitCode != 255 { t.Errorf("Expected exit code 255 but got %d", exitCode) } diff --git a/containerbuddy/signals_test.go b/containerbuddy/signals_test.go index 06b987e6..4df9db30 100644 --- a/containerbuddy/signals_test.go +++ b/containerbuddy/signals_test.go @@ -27,7 +27,7 @@ func (c *NoopDiscoveryService) Deregister(service *ServiceConfig) {} func getSignalTestConfig() *Config { config := &Config{ Command: argsToCmd([]string{ - "/root/examples/test/test.sh", + "./testdata/test.sh", "interruptSleep"}), StopTimeout: 5, Services: []*ServiceConfig{ diff --git a/examples/test/test.sh b/containerbuddy/testdata/test.sh similarity index 100% rename from examples/test/test.sh rename to containerbuddy/testdata/test.sh diff --git a/examples/consul/app/Dockerfile b/examples/consul/app/Dockerfile deleted file mode 100644 index a3f9715f..00000000 --- a/examples/consul/app/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -# a minimal Node.js container including containerbuddy -FROM node:slim - -# install curl -RUN apt-get update && \ - apt-get install -y \ - curl && \ - rm -rf /var/lib/apt/lists/* - -# install a simple http server -RUN npm install -g json http-server - -# add containerbuddy and all our configuration -ADD opt/containerbuddy /opt/containerbuddy/ diff --git a/examples/consul/docker-compose-local.yml b/examples/consul/docker-compose-local.yml deleted file mode 100644 index e04cad1f..00000000 --- a/examples/consul/docker-compose-local.yml +++ /dev/null @@ -1,51 +0,0 @@ -# This demonstration of containerbuddy has port mappings so we can use it -# on docker-machine locally. - -consul: - image: progrium/consul:latest - command: > - -server - -bootstrap-expect 1 - -ui-dir /ui - mem_limit: 256m - expose: - - 53 - - 8300 - - 8301 - - 8302 - - 8400 - - 8500 - - 8600 - ports: - - 8500:8500 - restart: always - -nginx: - build: nginx/ - mem_limit: 512m - expose: - - 80 - links: - - consul:consul - ports: - - 8080:80 - restart: always - command: > - /opt/containerbuddy/containerbuddy - -config file:///opt/containerbuddy/nginx.json - nginx -g "daemon off;" - -app: - build: app/ - mem_limit: 512m - expose: - - 8000 - links: - - consul:consul - ports: - - 8000 - restart: always - command: > - /opt/containerbuddy/containerbuddy - -config file:///opt/containerbuddy/app.json - /usr/local/bin/node /usr/local/bin/http-server /srv -p 8000 diff --git a/examples/consul/docker-compose.yml b/examples/consul/docker-compose.yml deleted file mode 100644 index 21966cc2..00000000 --- a/examples/consul/docker-compose.yml +++ /dev/null @@ -1,43 +0,0 @@ -consul: - image: progrium/consul:latest - command: > - -server - -bootstrap-expect 1 - -ui-dir /ui - mem_limit: 256m - ports: - - 53 - - 8300 - - 8301 - - 8302 - - 8400 - - 8500 - - 8600 - restart: always - -nginx: - image: 0x74696d/containerbuddy-demo-nginx - mem_limit: 512m - ports: - - 80 - links: - - consul:consul - restart: always - environment: - - CONTAINERBUDDY=file:///opt/containerbuddy/nginx.json - command: > - /opt/containerbuddy/containerbuddy - nginx -g "daemon off;" - -app: - image: 0x74696d/containerbuddy-demo-app - mem_limit: 512m - ports: - - 8000 - links: - - consul:consul - restart: always - command: > - /opt/containerbuddy/containerbuddy - -config file:///opt/containerbuddy/app.json - /usr/local/bin/node /usr/local/bin/http-server /srv -p 8000 diff --git a/examples/consul/nginx/Dockerfile b/examples/consul/nginx/Dockerfile deleted file mode 100644 index 00361fc7..00000000 --- a/examples/consul/nginx/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -# a minimal Nginx container including containerbuddy and a simple virtulhost config -FROM nginx:latest - -# install curl and jq -RUN apt-get update && \ - apt-get install -y \ - curl \ - unzip && \ - rm -rf /var/lib/apt/lists/* - -RUN curl -Lo /tmp/consul_template_0.11.0_linux_amd64.zip https://github.com/hashicorp/consul-template/releases/download/v0.11.0/consul_template_0.11.0_linux_amd64.zip && \ - unzip /tmp/consul_template_0.11.0_linux_amd64.zip && \ - mv consul-template /bin - -# add containerbuddy and all our configuration -ADD opt/containerbuddy /opt/containerbuddy/ -ADD etc/nginx/conf.d /etc/nginx/conf.d/ diff --git a/examples/consul/nginx/default.ctmpl b/examples/consul/nginx/default.ctmpl deleted file mode 100644 index 2536608a..00000000 --- a/examples/consul/nginx/default.ctmpl +++ /dev/null @@ -1,32 +0,0 @@ -# for each service, create a backend -{{range services}} -upstream {{.Name}} { - # write the health service address:port pairs for this backend - {{range service .Name}} - server {{.Address}}:{{.Port}}; - {{end}} -} -{{end}} - -server { - listen 80; - server_name _; - - # if you have http_stub_status_module compiled-in, then - # this would be a good place to use /nginx_status - location /health.txt { - add_header content-type "text/html"; - alias /usr/share/nginx/html/index.html; - } - - {{range services}} - location = /{{.Name}} { - return 302 /{{.Name}}/; - } - - location /{{.Name}} { - proxy_pass http://{{.Name}}/; - proxy_redirect off; - } - {{end}} -} diff --git a/examples/consul/nginx/etc/nginx/conf.d/default.conf b/examples/consul/nginx/etc/nginx/conf.d/default.conf deleted file mode 100644 index e2d7537a..00000000 --- a/examples/consul/nginx/etc/nginx/conf.d/default.conf +++ /dev/null @@ -1,13 +0,0 @@ -# this is a localhost-only virtualhost configuration that lets us start Nginx -# so we can get its real virtualhost config from consul-template -server { - listen 80; - server_name localhost 127.0.0.1; - - # if you have http_stub_status_module compiled-in, then - # this would be a good place to use /nginx_status - location /health.txt { - add_header content-type "text/html"; - alias /usr/share/nginx/html/index.html; - } -} diff --git a/examples/consul/nginx/opt/containerbuddy/nginx.json b/examples/consul/nginx/opt/containerbuddy/nginx.json deleted file mode 100644 index 89c83fa8..00000000 --- a/examples/consul/nginx/opt/containerbuddy/nginx.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "consul": "consul:8500", - "services": [ - { - "name": "nginx", - "port": 80, - "interfaces": ["eth0"], - "health": "/usr/bin/curl --fail -s -o /dev/null http://localhost/health.txt", - "poll": 10, - "ttl": 25 - } - ], - "backends": [ - { - "name": "app", - "poll": 7, - "onChange": "/opt/containerbuddy/reload-nginx.sh" - } - ] -} diff --git a/examples/consul/nginx/opt/containerbuddy/reload-nginx.sh b/examples/consul/nginx/opt/containerbuddy/reload-nginx.sh deleted file mode 100755 index 00022dba..00000000 --- a/examples/consul/nginx/opt/containerbuddy/reload-nginx.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -if [ -z "$VIRTUALHOST" ]; then - # fetch latest virtualhost template from Consul k/v - curl -s --fail consul:8500/v1/kv/nginx/template?raw > /tmp/virtualhost.ctmpl -else - # dump the $VIRTUALHOST environment variable as a file - echo $VIRTUALHOST > /tmp/virtualhost.ctmpl -fi - -# render virtualhost template using values from Consul and reload Nginx -consul-template \ - -once \ - -consul consul:8500 \ - -template "/tmp/virtualhost.ctmpl:/etc/nginx/conf.d/default.conf:nginx -s reload" diff --git a/examples/consul/start.sh b/examples/consul/start.sh deleted file mode 100755 index 951e26eb..00000000 --- a/examples/consul/start.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash - -COMPOSE_CFG= -PREFIX=exconsul - -while getopts "f:p:" optchar; do - case "${optchar}" in - f) COMPOSE_CFG=" -f ${OPTARG}" ;; - p) PREFIX=${OPTARG} ;; - esac -done -shift $(expr $OPTIND - 1 ) - -COMPOSE="docker-compose -p ${PREFIX}${COMPOSE_CFG:-}" -CONFIG_FILE=${COMPOSE_CFG:-docker-compose.yml} - -echo "Starting example application" -echo "project prefix: $PREFIX" -echo "docker-compose file: $CONFIG_FILE" - -echo 'Pulling latest container versions' -${COMPOSE} pull - -echo 'Starting Consul.' -${COMPOSE} up -d consul - -# get network info from consul and poll it for liveness -if [ -z "${COMPOSE_CFG}" ]; then - CONSUL_IP=$(sdc-listmachines --name ${PREFIX}_consul_1 | json -a ips.1) -else - CONSUL_IP=${CONSUL_IP:-$(docker-machine ip default)} -fi - -echo "Writing template values to Consul at ${CONSUL_IP}" -while : -do - # we'll sometimes get an HTTP500 here if consul hasn't completed - # it's leader election on boot yet, so poll till we get a good response. - sleep 1 - curl --fail -s -X PUT --data-binary @./nginx/default.ctmpl \ - http://${CONSUL_IP}:8500/v1/kv/nginx/template && break - echo -ne . -done -echo -echo 'Opening consul console' -open http://${CONSUL_IP}:8500/ui - -echo 'Starting application servers and Nginx' -${COMPOSE} up -d - -# get network info from Nginx and poll it for liveness -if [ -z "${COMPOSE_CFG}" ]; then - NGINX_IP=$(sdc-listmachines --name ${PREFIX}_nginx_1 | json -a ips.1) -else - NGINX_IP=${NGINX_IP:-$(docker-machine ip default)} -fi -NGINX_PORT=$(docker inspect --format='{{(index (index .NetworkSettings.Ports "80/tcp") 0).HostPort}}' ${PREFIX}_nginx_1) -echo "Waiting for Nginx at ${NGINX_IP}:${NGINX_PORT} to pick up initial configuration." -while : -do - sleep 1 - curl -s --fail -o /dev/null "http://${NGINX_IP}:${NGINX_PORT}/app/" && break - echo -ne . -done -echo -echo 'Opening web page... the page will reload every 5 seconds with any updates.' -open http://${NGINX_IP}:${NGINX_PORT}/app/ - -echo 'Try scaling up the app!' -echo "${COMPOSE} scale app=3" diff --git a/examples/etcd/app/Dockerfile b/examples/etcd/app/Dockerfile deleted file mode 100644 index a3f9715f..00000000 --- a/examples/etcd/app/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -# a minimal Node.js container including containerbuddy -FROM node:slim - -# install curl -RUN apt-get update && \ - apt-get install -y \ - curl && \ - rm -rf /var/lib/apt/lists/* - -# install a simple http server -RUN npm install -g json http-server - -# add containerbuddy and all our configuration -ADD opt/containerbuddy /opt/containerbuddy/ diff --git a/examples/etcd/app/opt/containerbuddy/app.json b/examples/etcd/app/opt/containerbuddy/app.json deleted file mode 100644 index ab937120..00000000 --- a/examples/etcd/app/opt/containerbuddy/app.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "etcd": { - "endpoints": "http://etcd:2379" - }, - "onStart": "/opt/containerbuddy/reload-app.sh", - "services": [ - { - "name": "app", - "port": 8000, - "health": "/usr/bin/curl --fail -s http://localhost:8000", - "poll": 10, - "ttl": 25 - } - ], - "backends": [ - { - "name": "nginx", - "poll": 7, - "onChange": "/opt/containerbuddy/reload-app.sh" - }, - { - "name": "app", - "poll": 5, - "onChange": "/opt/containerbuddy/reload-app.sh" - } - ] -} diff --git a/examples/etcd/app/opt/containerbuddy/reload-app.sh b/examples/etcd/app/opt/containerbuddy/reload-app.sh deleted file mode 100755 index 12c19d28..00000000 --- a/examples/etcd/app/opt/containerbuddy/reload-app.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -# get all the health application servers and write the json to file -curl -s http://etcd:4001/v2/keys/containerbuddy/app?recursive=true | json > /tmp/lastQuery.json - -cat < /srv/index.html - - -Containerbuddy Demo - - - -

Containerbuddy Demo

-

This page served by app server: $(hostname)

-Last service health check changed at $(date): -
-$(cat /tmp/lastQuery.json)
-
-