Skip to content

Commit

Permalink
Merge pull request #16 from jokester/revise-demo
Browse files Browse the repository at this point in the history
Revise demo
  • Loading branch information
jokester committed Jan 10, 2024
2 parents 6e4dca3 + ca9ae67 commit 37d9ec2
Show file tree
Hide file tree
Showing 14 changed files with 134 additions and 5,203 deletions.
23 changes: 9 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ Prepend `publicaddr` wrapper to your `node` binary (or other interpreter like `t

And [prebuild/prebuildify](https://github.com/prebuild/prebuildify) [prebuild/prebuildify-cross](https://github.com/prebuild/prebuildify-cross) [prebuild/node-gyp-build](https://github.com/prebuild/node-gyp-build/blob/master/node-gyp-build.js), they made shipping multiarch prebuilt native modules incredibly simple.

## Demo

### Simple usage
## Demo: Rolling update between 2 versions without downtime

Clone [this repo](https://github.com/jokester/publicaddr) and run multiple instances like:

Expand All @@ -55,22 +53,19 @@ git clone https://github.com/jokester/publicaddr

cd publicaddr/demo

npm install
# run version a with 4 containers
docker-compose up -d

npm start &
npm start &
npm start &
npm start &
# start a HTTP benchmark
wrk -t6 -c2000 -d120s http://127.0.0.1:3000 &

wrk -t6 -c2000 -d10s http://127.0.0.1:3000 # or other HTTP benchmark tool
# switch between version a / b, for a few times
./switch-version.sh a b a b

kill %1 %2 %3 %4
# after benchmark ends
docker-compose down
```

### Inside containers

Clone [this repo](https://github.com/jokester/publicaddr) and run `docker-compose up` in `demo/`

## License

BSD
Expand Down
5 changes: 2 additions & 3 deletions bin/publicaddr
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ if [ "x${SO_PATH}" != "x" ] && [ -f "$SO_PATH" ]; then
set -x
LD_PRELOAD="${SO_PATH} ${LD_PRELOAD:-}" exec "$@"
else
echo "[publicaddr] native module not found. still execing next process."
set -x
exec "$@"
echo "[publicaddr] native module not found. your OS/platform may be unsupported."
exit 1
fi
8 changes: 4 additions & 4 deletions demo/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Dockerfile for demo
# or, FROM node:18-alpine
FROM node:18-bookworm-slim

COPY . /opt/publicaddr

RUN set -uex \
&& cd /opt/publicaddr/demo \
&& npm i \
&& npm i -g publicaddr@0.4.3 \
&& npm cache clean --force

COPY ./demo-server.mjs /opt/demo-server.mjs
9 changes: 0 additions & 9 deletions demo/Dockerfile-alpine

This file was deleted.

15 changes: 0 additions & 15 deletions demo/Dockerfile-localbuild

This file was deleted.

44 changes: 0 additions & 44 deletions demo/demo-server.js

This file was deleted.

62 changes: 62 additions & 0 deletions demo/demo-server.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import http from 'node:http';
const wait = timeout => new Promise(f => setTimeout(f, timeout))
const waitRandom = (min, max) => wait(min + Math.random() * (max - min))

const processTag = `demo-server-version-${process.env.VERSION ?? '?'}-${Math.random().toString(16).slice(2, 10)}`

let reqCount = 0
let onFlight = 0

const server = http.createServer(async (req, res) => {
++onFlight;
res.writeHead(200, undefined,
// { connection: 'Close' }
);
await waitRandom(20, 100);
res.end(`reqCount: ${++reqCount} from ${processTag}`)
--onFlight;
})

function log(...args) {
console.info(`${Date.now().toFixed(2)} ${processTag}:`, ...args)
}

async function waitRequestsEnd() {
while (onFlight > 0) {
await wait(1e3)
}
}

function startReportCount() {
let lastReqCountReported = 0
const interval = 10 // s
const timer = setInterval(() => {
const delta = reqCount - lastReqCountReported
lastReqCountReported = reqCount
log(`reqCount: ${reqCount}`);
log(`mean RPS: ${(delta / interval).toFixed(2)}`);
}, interval * 1e3)
return () => clearInterval(timer)
}

async function main() {
const port = Number(process.env.PORT || 3000)
const gotSignal = new Promise((resolve, reject) => {
process.on('SIGINT', resolve)
process.on('SIGTERM', resolve)
})
const stopReportCount = startReportCount()

server.listen(port, () => {
log(`listening on port ${port}`)
})
await gotSignal
const error = await new Promise(f => server.close(f))
if (error) {
console.error('server close error:', error)
}
stopReportCount()
log(`app closing.`)
}

await main();
46 changes: 21 additions & 25 deletions demo/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
version: "3"
services:
demo-server-g1: &demo-server
# build:
# context: ..
# dockerfile: demo/Dockerfile
# dockerfile: demo/Dockerfile-alpine
image: ${SLOT1_IMAGE}
command: npm start
working_dir: /opt/publicaddr/demo
# see https://iximiuz.com/en/posts/multiple-containers-same-port-reverse-proxy/ for explanation
sandbox:
image: alpine
command: sleep infinity
ports:
- 3000:3000

demo-server-a: &demo-server
build: .
command: publicaddr node /opt/demo-server.mjs
network_mode: service:sandbox
environment:
VERSION: 'a'
deploy:
replicas: ${SLOT1_REPLICA:-4}
# NOTE I have no idea how to get default network_mode (requires port mapping) + replica working
# network_mode=host (and perhaps some other) works with SO_REUSEPORT
network_mode: host
replicas: ${VERSION_A_REPLICA:-4}

# with publicaddr one can even do zero-downtime rollout
#
# e.g.
# 0. Start with g1.replica=6 g2.replica=0 # Users only see reply from g1
# 1. Set new image in g2
# 2. Set g1.replicas=3 g1.replicas=3
# 3. docker-compose up -d # user starts to see reply from g1 / g2
# 4. Set g1.replicas=0 g2.replicas=6
# 5. docker-compose up -d # user starts to see reply from g2
demo-server-g2:
<<: *demo-server
image: ${SLOT2_IMAGE}
demo-server-b: &demo-server
build: .
command: publicaddr node /opt/demo-server.mjs
network_mode: service:sandbox
environment:
VERSION: 'b'
deploy:
replicas: ${SLOT2_REPLICA:-0}
replicas: ${VERSION_B_REPLICA:-0}
Loading

0 comments on commit 37d9ec2

Please sign in to comment.