blue/green 使用 gitlab 部署到 portainer CI/CD
blue/green deployment to portainer using gitlab CI/CD
我有使用websockets的webservice,需要实现零宕机部署。因为我不想在部署时删除现有连接,所以我决定实施 blue/green 部署。我的实际解决方案如下:
- 我在 portainer 中创建了两个相同的服务,监听不同的端口。每个服务都在节点环境中设置了一些标识符,例如
alfa
和 beta
- 这两个服务都隐藏在负载均衡器后面,均衡器会定期检查每个服务的状态。如果服务在特定路由 (/balancer-keepalive-check) 上响应字符串“OK”,则该服务处于活动状态并且平衡器可以路由到该服务。如果服务以字符串“STOP”响应,平衡器将此服务标记为不可访问,但活动连接将被保留
- 哪个服务是活跃的,哪个是停止的,是通过redis同步的。在 Redis 中有键
lb.service.alfa
和 lb.service.beta
,它们可以包含值 1 表示活动,0 表示不活动。 nestjs中实现/balancer-keepalive-check路由的例子:
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>`;
})
}
}
- 在 gitlab CI 中创建 docker 提交时标记的图像,并重新启动服务调用 portainer webhook 以获取特定服务。这适用于 1 个服务,但不知道如何使用 2 个不同的 DEPLOY_WEBHOOK CI 变量并在它们之间切换。
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
我不知道如何解决的问题是:
- 当我有 2 个服务时,我有 2 个不同的部署 webhooks,我需要在部署后从中调用一个,因为我不想重新启动这两个服务。如何确定是哪一个?如果此部署是针对“alfa”或“beta”服务,如何实施某种计数器?我应该在每次部署后使用 gitlab api and update DEPLOY_WEBHOOK 吗?或者我应该摆脱这个 gitlab CI/CD 变量并在服务上使用一些 API 这会告诉我 webhook url?
- 如何更新redis中的值?我应该为此实现自定义 API 吗?
- 是否存在更好的方法来实现这一点?
补充信息:无法从服务中使用 gitlab api,因为我们的 gitlab 是自托管在只能从我们的专用网络访问的域上。
我修改了 AppController。现在有 2 个新端点,一个用于识别哪个服务是 运行,第二个用于 redis 中的开关值:
private serviceId:string = process.env.ID || 'alfa';
@Get('running-service-id')
info(){
return this.serviceId
}
@Get('switch')
switch(){
const play = this.serviceId == 'alfa' ? `lb-status-beta` : `lb-status-alfa`;
const stop = `lb-status-${this.serviceId}`;
this.redisClient.set(play, '1', (err) => {
if(!err){
this.redisClient.set(stop, '0');
}
})
}
之后,我修改了我的gitlab-ci.yml如下:
image: registry.rassk.work/pokec/pokec-nodejs-build-image:p1.0.1
services:
- name: docker:dind
stages:
- build
- deploy
- switch
variables:
DOCKER_TAG: platform-websocket:$CI_COMMIT_TAG
test:
stage: build
allow_failure: true
tags:
- dtm-builder
script:
- npm set registry http://some-private-npm-registry-url.sk
- npm install
- npm run test
build:
stage: build
tags:
- dtm-builder
environment:
name: $CI_COMMIT_TAG
script:
- 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
only:
- tags
deploy:
stage: deploy
needs: [build, test]
environment:
name: $CI_COMMIT_TAG
script:
- 'SERVICE_RUNNING=$(curl --request GET http://172.17.101.125/running-service-id)'
- echo $SERVICE_RUNNING
- if [ "$SERVICE_RUNNING" == "1" ]; then curl --request POST $DEPLOY_WEBHOOK_2; fi
- if [ "$SERVICE_RUNNING" == "2" ]; then curl --request POST $DEPLOY_WEBHOOK_1; fi
only:
- tags
switch:
stage: switch
needs: [deploy]
environment:
name: $CI_COMMIT_TAG
script:
- sleep 10
- curl --request GET http://172.17.101.125/switch
only:
- tags
在作业 build 中构建 docker 映像。之后运行作业 deploy,它向 /运行-service-id 发出请求并识别正在运行的服务。然后将图像部署到已停止的服务。最后一个是作业 switch,它将向 /switch 路由发出请求,这将在 redis 中切换值。
这很好用。我需要实现的最后一件事是这两条路线的某种秘密(例如 jwt)
我有使用websockets的webservice,需要实现零宕机部署。因为我不想在部署时删除现有连接,所以我决定实施 blue/green 部署。我的实际解决方案如下:
- 我在 portainer 中创建了两个相同的服务,监听不同的端口。每个服务都在节点环境中设置了一些标识符,例如
alfa
和beta
- 这两个服务都隐藏在负载均衡器后面,均衡器会定期检查每个服务的状态。如果服务在特定路由 (/balancer-keepalive-check) 上响应字符串“OK”,则该服务处于活动状态并且平衡器可以路由到该服务。如果服务以字符串“STOP”响应,平衡器将此服务标记为不可访问,但活动连接将被保留
- 哪个服务是活跃的,哪个是停止的,是通过redis同步的。在 Redis 中有键
lb.service.alfa
和lb.service.beta
,它们可以包含值 1 表示活动,0 表示不活动。 nestjs中实现/balancer-keepalive-check路由的例子:
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>`;
})
}
}
- 在 gitlab CI 中创建 docker 提交时标记的图像,并重新启动服务调用 portainer webhook 以获取特定服务。这适用于 1 个服务,但不知道如何使用 2 个不同的 DEPLOY_WEBHOOK CI 变量并在它们之间切换。
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
我不知道如何解决的问题是:
- 当我有 2 个服务时,我有 2 个不同的部署 webhooks,我需要在部署后从中调用一个,因为我不想重新启动这两个服务。如何确定是哪一个?如果此部署是针对“alfa”或“beta”服务,如何实施某种计数器?我应该在每次部署后使用 gitlab api and update DEPLOY_WEBHOOK 吗?或者我应该摆脱这个 gitlab CI/CD 变量并在服务上使用一些 API 这会告诉我 webhook url?
- 如何更新redis中的值?我应该为此实现自定义 API 吗?
- 是否存在更好的方法来实现这一点?
补充信息:无法从服务中使用 gitlab api,因为我们的 gitlab 是自托管在只能从我们的专用网络访问的域上。
我修改了 AppController。现在有 2 个新端点,一个用于识别哪个服务是 运行,第二个用于 redis 中的开关值:
private serviceId:string = process.env.ID || 'alfa';
@Get('running-service-id')
info(){
return this.serviceId
}
@Get('switch')
switch(){
const play = this.serviceId == 'alfa' ? `lb-status-beta` : `lb-status-alfa`;
const stop = `lb-status-${this.serviceId}`;
this.redisClient.set(play, '1', (err) => {
if(!err){
this.redisClient.set(stop, '0');
}
})
}
之后,我修改了我的gitlab-ci.yml如下:
image: registry.rassk.work/pokec/pokec-nodejs-build-image:p1.0.1
services:
- name: docker:dind
stages:
- build
- deploy
- switch
variables:
DOCKER_TAG: platform-websocket:$CI_COMMIT_TAG
test:
stage: build
allow_failure: true
tags:
- dtm-builder
script:
- npm set registry http://some-private-npm-registry-url.sk
- npm install
- npm run test
build:
stage: build
tags:
- dtm-builder
environment:
name: $CI_COMMIT_TAG
script:
- 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
only:
- tags
deploy:
stage: deploy
needs: [build, test]
environment:
name: $CI_COMMIT_TAG
script:
- 'SERVICE_RUNNING=$(curl --request GET http://172.17.101.125/running-service-id)'
- echo $SERVICE_RUNNING
- if [ "$SERVICE_RUNNING" == "1" ]; then curl --request POST $DEPLOY_WEBHOOK_2; fi
- if [ "$SERVICE_RUNNING" == "2" ]; then curl --request POST $DEPLOY_WEBHOOK_1; fi
only:
- tags
switch:
stage: switch
needs: [deploy]
environment:
name: $CI_COMMIT_TAG
script:
- sleep 10
- curl --request GET http://172.17.101.125/switch
only:
- tags
在作业 build 中构建 docker 映像。之后运行作业 deploy,它向 /运行-service-id 发出请求并识别正在运行的服务。然后将图像部署到已停止的服务。最后一个是作业 switch,它将向 /switch 路由发出请求,这将在 redis 中切换值。
这很好用。我需要实现的最后一件事是这两条路线的某种秘密(例如 jwt)