blue/green 使用 gitlab 部署到 portainer CI/CD

blue/green deployment to portainer using gitlab CI/CD

我有使用websockets的webservice,需要实现零宕机部署。因为我不想在部署时删除现有连接,所以我决定实施 blue/green 部署。我的实际解决方案如下:

  1. 我在 portainer 中创建了两个相同的服务,监听不同的端口。每个服务都在节点环境中设置了一些标识符,例如 alfabeta
  2. 这两个服务都隐藏在负载均衡器后面,均衡器会定期检查每个服务的状态。如果服务在特定路由 (/balancer-keepalive-check) 上响应字符串“OK”,则该服务处于活动状态并且平衡器可以路由到该服务。如果服务以字符串“STOP”响应,平衡器将此服务标记为不可访问,但活动连接将被保留
  3. 哪个服务是活跃的,哪个是停止的,是通过redis同步的。在 Redis 中有键 lb.service.alfalb.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>`;
            })
        }
    }
  1. 在 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

我不知道如何解决的问题是:

补充信息:无法从服务中使用 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)