I have webservice using websockets, and need to implement zero-downtime deployment. Because I don't want drop existing connections on deploy, I've decided to implement blue/green deploy. My actual solution looks like:
- I've created two identical services in portainer, listening on different ports. Every service has set in node environments some identifier, for example
alfa
andbeta
- Both services are hidden behind load balancer, and balancer is periodically checking status of each service. If service responds on specific route (/balancer-keepalive-check) with string "OK", this service is active and balancer can routing to this service. If service is responding with string "STOP", balancer mark this service as inaccessible, but active connections will be preserved
- which service is active and which is stopped is synced over redis. In redis there are keys
lb.service.alfa
andlb.service.beta
which can contains values 1 for active and 0 for inactive. Example of implementation /balancer-keepalive-check route in nestjs:
import {Controller, Get} from '@nestjs/common';
import {RedisClient} from "redis";
const { promisify } = require("util");
@Controller()
export class AppController {
private redisClient = new RedisClient({host: process.env.REDIS_HOST});
private serviceId:string = process.env.ID; //alfa, beta
@Get('balancer-keepalive-check')
async balancerCheckAlive(): Promise<string> {
const getAsync = promisify(this.redisClient.get).bind(this.redisClient);
return getAsync(`lb-status-${this.serviceId}`).then(status => {
const reply: string = status == 1 ? 'OK' : 'STOP';
return `<response>${reply}</response>`;
})
}
}
- in gitlab CI create docker image tagged by tag on commit, and restart service calling portainer webhook for specific service. This works well for 1 service, but don't know how to use 2 different DEPLOY_WEBHOOK CI variables and switch between them.
image: registry.rassk.work/pokec/pokec-nodejs-build-image:p1.0.1
services:
- name: docker:dind
variables:
DOCKER_TAG: platform-websocket:$CI_COMMIT_TAG
deploy:
tags:
- dtm-builder
environment:
name: $CI_COMMIT_TAG
script:
- npm set registry http://some-private-npm-registry-url.sk
- if [ "$ENV_CONFIG" ]; then cp $ENV_CONFIG $PWD/.env; fi
- if [ "$PRIVATE_KEY" ]; then cp $PRIVATE_KEY $PWD/privateKey.pem; fi
- if [ "$PUBLIC_KEY" ]; then cp $PUBLIC_KEY $PWD/publicKey.pem; fi
- docker build -t $DOCKER_TAG .
- docker tag $DOCKER_TAG registry.rassk.work/community/$DOCKER_TAG
- docker push registry.rassk.work/community/$DOCKER_TAG
- curl --request POST $DEPLOY_WEBHOOK
only:
- tags
My questions, which I don't know how to solve are:
- When I have 2 services, I have 2 different deploy webhooks from which I need to call one after deploy, because I don't want to restart both services. How to determine which one? How to implement some kind of counter, if this deploy is to "alfa" or "beta" service? Should I use gitlab api and update DEPLOY_WEBHOOK after each deploy? Or shoud I get rid of this gitlab CI/CD variable and use some API on services which will tell me webhook url?
- How to update values in redis? Should I implement custom API for this?
- Exists there better way how to achieve this?
addition info: Can't use gitlab api from serviceses, because our gitlab is self-hosted on domain accessible only from our private network.