How to scale your services with session affinity on docker in 5 minutes!
Assumes you know docker, docker swarm concepts and can understand something easy about node.js
Developing services with an eye on scalability? Who isn't right! This should give you some ideas on how to minimally setup and run a load balancer with your docker swarm to create sticky sessions where you want them.
I have two example Node.js services in this use case running, one of which I wish to track application state, and the other running stateless.
- Create two of these index.js files in separate directories, service1 and service2.
- Change the src root /serviceX and the res.end(`<h1> I'm serviceX.... to service1 and service2 to match the aforementioned directory naming
- In each service dir run npm init, then npm i S cookie-parser express express-session
- run node index.js and test each out individually by hitting localhost:8080/serviceX, you should see each subsequent reload increment a counter.
var express = require('express');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var os = require('os');
var app = express();
app.use(cookieParser());
app.use(session({secret: "Shh, its a secret!"}));
app.get('/service1', function(req, res){
if(req.session.page_views){
req.session.page_views++;
} else {
req.session.page_views = 1;
}
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(JSON.stringify(req.headers));
res.end(`<h1>I'm service1 on ${os.hostname()} non unique visits ${req.session.page_views}</h1>`);
});
app.listen(8080);
Now dockerize each service like this in directories service1, service2, with a Dockerfile.
FROM node
RUN mkdir -p /usr/src/app
COPY index.js /usr/src/app
COPY package*.json ./
RUN npm install
EXPOSE 8080
CMD ["node","/usr/src/app/index"]
- Add a .dockerignore file in each to leave behind the modules.
node_modules
npm-debug.log
2. Build a docker image in each directory docker build -t serviceX .
3. Build a docker compose file, one level up from the service dirs, here it is
version: '3'
services:
service1:
image: service1
ports:
- 8080
environment:
- SERVICE_PORTS=8080
- COOKIE=connect.sid prefix nocache
- VIRTUAL_HOST=*/service1
deploy:
replicas: 2
update_config:
parallelism: 5
delay: 10s
restart_policy:
condition: on-failure
max_attempts: 3
window: 120s
networks:
- web
service2:
image: service2
ports:
- 8080
environment:
- SERVICE_PORTS=8080
- VIRTUAL_HOST=*/service2
deploy:
replicas: 2
update_config:
parallelism: 5
delay: 10s
restart_policy:
condition: on-failure
max_attempts: 3
window: 120s
networks:
- web
proxy:
image: dockercloud/haproxy
Only two replicas, make it whatever you want. What's really interesting about this are two things, BOTH javascript files build a session but only one in the compose file is affinity assigned, service1.
- COOKIE=connect.sid prefix nocache
That's the default cookie name given by express-session you can of course make changes to that, prefix adds the server routing and nocache is uh, no cacheing it.
The other thing interesting is this entry, virtual hosts, these must match the /root of your services for routing purposes, both services must have this unless you are serving from a plain root e.g. "/"
- VIRTUAL_HOST=*/service1
Run this command to launch the replicated services with affinity on service1.
- docker swarm init
- docker stack deploy --compose-file=docker-compose.yml test
- docker service -ls
- point to localhost/serviceX for results, service1 will be sticky and incrementing each time, service2 you will see a new service and session each time. Clear cookies to get over to another server1.
Here are the official options for haproxy.
http://cbonte.github.io/haproxy-dconv/1.9/configuration.html#7.1
I'd add this to git but five min was up ten minutes ago.