Skip to content

Commit 4d0cfb2

Browse files
maxbeattyasilluron
authored andcommitted
dockerize node app, mysql, and load balancer with dynamic configuration
1 parent 889717b commit 4d0cfb2

22 files changed

+404
-142
lines changed

.dockerignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.git
2+
node_modules
3+
.dockerignore
4+
.editorconfig
5+
.eslintignore
6+
.eslintrc
7+
.gitattributes
8+
.gitignore
9+
.nvmrc
10+
*.md
11+
docker-compose.yml
12+
Dockerfile

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v4.2.1
1+
v5.3.0

.travis.yml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
11
language: node_js
2-
node_js:
3-
- 4
42
env: PORT=3000
5-
before_install:
6-
- npm install -g npm@latest
73
after_script:
84
- npm install -g codeclimate-test-reporter
95
- codeclimate-test-reporter < lcov.info || echo "Could not upload code coverage to CodeClimate"
10-
deploy:
11-
provider: heroku
12-
app:
13-
master: jsperf-dev
14-
api_key:
15-
secure: Enbwr5BsIj6Q12tlrPytm7aoOgOBc5649NrRZaUzXbPxBMZslrs//bE2ibYE/UgUu55gzNh8a5orGVNj2i+wiisFsblF4K/GgNxlNWgTJtdd7Y27JKBGXJdXUJk6hMU4ksgNy9YM/fJAvPQOdyI7Pb+fM7oXwTvWeciSeUrev44=

Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM node
2+
3+
RUN mkdir /code
4+
WORKDIR /code
5+
6+
ADD package.json /code
7+
RUN npm install
8+
9+
ADD . /code
10+
11+
EXPOSE 3000
12+
13+
CMD ["node", "server.js"]

Procfile

Lines changed: 0 additions & 1 deletion
This file was deleted.

README.md

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,44 @@
88

99
### Prerequisites
1010

11-
You’ll need [node.js](https://nodejs.org/en/) and [MySQL](https://www.mysql.com/downloads/) installed.
12-
13-
1. Clone the repository (`git clone https://github.com/jsperf/jsperf.com.git`).
14-
2. Install dependencies (`npm install`).
15-
3. Get a [Browserscope.org](https://www.browserscope.org/) API key by signing in and going to [the settings page](https://www.browserscope.org/user/settings). (You'll need this in the next step)
16-
4. Register a new OAuth GitHub development application by going to [your settings page in github](https://github.com/settings/applications/new). Take note to copy the "Client ID" and "Client Secret". The callback URL is simply the root url of the application, e.g., "http://localhost:3000"
17-
5. Setup database and other environment configuration (`npm run setup`).
11+
1. Clone the repository: `git clone https://github.com/jsperf/jsperf.com.git`
12+
2. Use the version of `node` for this project defined in `.nvmrc`: `nvm install` ([More on `nvm`](https://github.com/creationix/nvm))
13+
2. Install dependencies: `npm install`
14+
3. Get a [Browserscope.org](http://www.browserscope.org/) API key by signing in and going to [the settings page](http://www.browserscope.org/user/settings). (You'll need this in the last step)
15+
4. Register a new OAuth GitHub development application by going to [your settings page in github](https://github.com/settings/applications/new). Take note to copy the "Client ID" and "Client Secret". The callback URL is simply the root url of the application, e.g., `http://localhost:3000`
16+
5. Setup environment configuration: `npm run setup`
1817

1918
### Running the server
2019

20+
#### Docker
21+
22+
##### One-time Setup
23+
24+
1. Install [Docker Toolbox](https://docs.docker.com/engine/installation/) so you have `docker` and `docker-compose`
25+
2. Create a Data Volume Container to persist data: `docker create -v /var/lib/mysql --name data-jsperf-mysql mysql /bin/true`
26+
3. Setup database tables: `docker-compose run web node /code/setup/tables`
27+
28+
##### Compose
29+
30+
`docker-compose.yml` orchestrates a load balancer (nginx), the app (this node project), and a database (mysql) with some additional services to help with continuous deployment. To start everything up, run: `docker-compose up`. Pressing `ctrl+c` or sending a similar interruption will stop all of the containers. To run the composed containers in the background, use the `-d` argument.
31+
32+
You can start additional app containers by running `docker-compose scale web=3` where `3` is the total number of containers. The load balancer will automatically reconfigure itself to include the new containers. Similarly, you can scale down the containers by running `docker-compose scale web=1` and the load balancer will, again, reconfigure itself accordingly.
33+
34+
Once you've built the images with `docker-compose`, you can manually run additional containers similar to how `docker-compose scale` would.
35+
36+
```
37+
docker run -d --name jsperfcom_web_man \
38+
--link jsperfcom_db_1:db \
39+
--env-file .env \
40+
--env SERVICE_3000_CHECK_HTTP=/health \
41+
--env SERVICE_3000_CHECK_INTERVAL=1s \
42+
jsperfcom_web
43+
```
44+
45+
#### Local
46+
47+
You’ll need [node.js](https://nodejs.org/en/) and [MySQL](https://www.mysql.com/downloads/) installed.
48+
2149
```
2250
npm start
2351
```
@@ -39,7 +67,7 @@ npm test -- test/server/web/contributors/index.js
3967

4068
_If you'd just like to lint and save a little time, you can run `npm run lint` which skips the tests._
4169

42-
_If you'd like an HTML report with code coverage, you can run `npm run test-cov-html` which will create `coverage.html` in the root of the project._
70+
_If you're missing code coverage, open `coverage.html` in the root of the project for a detailed visual report._
4371

4472
## Gotchas
4573

config.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,6 @@ var config = {
4545
email: process.env.ADMIN_EMAIL
4646
},
4747
browserscope: process.env.BROWSERSCOPE,
48-
db: {
49-
host: process.env.DB_HOST,
50-
port: process.env.DB_PORT,
51-
user: process.env.DB_USER,
52-
pass: process.env.DB_PASS,
53-
name: process.env.DB_NAME
54-
},
5548
debug: {
5649
$filter: 'env',
5750
development: true,

docker-compose.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
web:
2+
build: .
3+
env_file: .env
4+
links:
5+
- db
6+
environment:
7+
SERVICE_3000_CHECK_HTTP: '/health'
8+
SERVICE_3000_CHECK_INTERVAL: '1s'
9+
db:
10+
image: mysql
11+
volumes_from:
12+
- data-jsperf-mysql
13+
env_file: .env
14+
environment:
15+
MYSQL_RANDOM_ROOT_PASSWORD: 'true'
16+
MYSQL_DATABASE: jsperf
17+
MYSQL_USER: jsperf
18+
lb:
19+
build: ./docker
20+
dockerfile: Dockerfile-lb
21+
links:
22+
- web
23+
- consul
24+
ports:
25+
- '80:80'
26+
consul:
27+
image: gliderlabs/consul-server:0.6
28+
command: -bootstrap
29+
registrator:
30+
image: gliderlabs/registrator:v6
31+
command: -internal consul://consul:8500
32+
links:
33+
- consul
34+
volumes:
35+
- "/var/run/docker.sock:/tmp/docker.sock"

docker/Dockerfile-lb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM gliderlabs/alpine
2+
3+
RUN echo "@testing http://dl-4.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
4+
5+
RUN apk-install nginx curl runit@testing
6+
7+
RUN curl -L -o /tmp/consul-template.zip http://releases.hashicorp.com/consul-template/0.12.0/consul-template_0.12.0_linux_amd64.zip
8+
RUN unzip /tmp/consul-template.zip -d /usr/local/bin
9+
10+
ADD nginx.service /etc/service/nginx/run
11+
ADD consul-template.service /etc/service/consul-template/run
12+
13+
ADD nginx.conf /etc/consul-templates/nginx.conf
14+
15+
CMD ["/sbin/runsvdir", "/etc/service"]

docker/consul-template.service

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/sh
2+
3+
exec consul-template \
4+
-consul=consul:8500 \
5+
-template "/etc/consul-templates/nginx.conf:/etc/nginx/nginx.conf:sv hup nginx"

docker/nginx.conf

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
worker_processes 1;
2+
3+
events {
4+
worker_connections 1024;
5+
}
6+
7+
http {
8+
upstream app {
9+
least_conn;
10+
{{range service "jsperfcom_web"}}
11+
server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=60 weight=1;
12+
{{else}}
13+
server 127.0.0.1:65535; # force a 502
14+
{{end}}
15+
}
16+
17+
server {
18+
listen 80 default_server;
19+
20+
location / {
21+
proxy_pass http://app;
22+
proxy_http_version 1.1;
23+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
24+
proxy_set_header X-Real-IP $remote_addr;
25+
proxy_set_header Host $host;
26+
proxy_set_header Upgrade $http_upgrade;
27+
proxy_set_header Connection 'upgrade';
28+
proxy_cache_bypass $http_upgrade;
29+
}
30+
}
31+
}

docker/nginx.service

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/sh
2+
3+
/usr/sbin/nginx -c /etc/nginx/nginx.conf -t && \
4+
exec /usr/sbin/nginx -c /etc/nginx/nginx.conf -g "daemon off;"

manifest.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ var manifest = {
7474
{'./server/web/contributors': {}},
7575
{'./server/web/errors': {}},
7676
{'./server/web/faq': {}},
77+
{'./server/web/health': {}},
7778
{'./server/web/home': {}},
7879
{'./server/web/popular': {}},
7980
{'./server/web/public': {}},

package.json

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
"clean": "rm npm-debug.log lcov.info coverage.html",
88
"lint": "lab -d -L",
99
"setup": "node setup",
10-
"start": "nodemon --ext js,hbs server.js",
11-
"test": "lab -t 100 -c -L -r lcov -o lcov.info -r console -o stdout",
12-
"test-cov-html": "lab -r html -o coverage.html && open coverage.html"
10+
"start": "node server.js",
11+
"test": "lab -t 100 -c -L -r lcov -o lcov.info -r console -o stdout -r html -o coverage.html",
12+
"watch": "nodemon --ext js,hbs server.js"
1313
},
1414
"repository": {
1515
"type": "git",
@@ -57,9 +57,5 @@
5757
"prompt": "^0.2.14",
5858
"proxyquire": "^1.4.0",
5959
"sinon": "^1.15.4"
60-
},
61-
"engines": {
62-
"node": "4.x.x",
63-
"npm": "3.x.x"
6460
}
6561
}

server.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,13 @@ composer(function (err, server) {
77

88
server.start(function () {
99
server.log('info', 'Server running at: ' + server.info.uri);
10+
11+
process.on('SIGTERM', function () {
12+
server.log('info', 'Received SIGTERM. Attempting to stop server');
13+
server.stop({ timeout: 10000 }, function () {
14+
server.log('info', 'Server stopped. Exiting');
15+
process.exit(0);
16+
});
17+
});
1018
});
1119
});

server/lib/db.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ module.exports = {
1010
}
1111
return new Promise(function (resolve, reject) {
1212
var conn = mysql.createConnection({
13-
host: config.get('/db/host'),
14-
port: config.get('/db/port'),
15-
user: config.get('/db/user'),
16-
password: config.get('/db/pass'),
17-
database: config.get('/db/name'),
13+
host: 'db',
14+
port: 3306,
15+
user: process.env.DB_ENV_MYSQL_USER,
16+
password: process.env.DB_ENV_MYSQL_PASSWORD,
17+
database: process.env.DB_ENV_MYSQL_DATABASE,
1818
// query and rows will print to stdout
1919
debug: config.get('/debug') ? ['ComQueryPacket', 'RowDataPacket'] : false,
2020
charset: 'utf8mb4'

server/web/health/index.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const db = require('../../lib/db');
2+
3+
exports.register = function (server, options, next) {
4+
server.route({
5+
method: 'GET',
6+
path: '/health',
7+
handler: function (request, reply) {
8+
db.genericQuery('SELECT 1;')
9+
.then(function () {
10+
reply();
11+
})
12+
.catch(function () {
13+
reply(new Error('Unhealthy'));
14+
});
15+
}
16+
});
17+
18+
return next();
19+
};
20+
21+
exports.register.attributes = {
22+
name: 'web/health'
23+
};

0 commit comments

Comments
 (0)