K8S下微服务平滑发布的一些思考与总结

目录

[0. 平滑发布的概念解释](#0. 平滑发布的概念解释)

[1. k8s的下一些知识点](#1. k8s的下一些知识点)

[2. Springboot 健康检查](#2. Springboot 健康检查)

[3. ribbon的客户端负载均衡机制](#3. ribbon的客户端负载均衡机制)

[4. 实现方案一](#4. 实现方案一)

[5. 实现方案二](#5. 实现方案二)

[6. 参考](#6. 参考)


0. 平滑发布的概念解释

在发布工程中,老的版本所有的请求处理完毕,新的请求正确的请求到新的节点上这里会涉及到新的几个问题;

a. 老服务能否将请求都出完成; 包括线程池正常的关闭,已经拉取mq正常都处理完成等

b. 新的请求不会请求到老的节点上

发布系统包括k8s如何下线一个应用,再销毁这个容器呢?

bash 复制代码
kill SIGTERM pid

kill SIGTERM 会做什么呢 ?

系统会发送一个SIGTERM的信号给对应的程序。当程序接收到该signal后,将会发生以下的事情

  1. 程序立刻停止
  2. 当程序释放相应资源后再停止
  3. 程序可能仍然继续运行

大部分程序接收到SIGTERM信号后,会先释放自己的资源,然后在停止。但是也有程序可以在接受到信号量后,做一些其他的事情,并且这些事情是可以

配置的。如果程序正在等待IO,可能就不会立马做出相应。

  也就是说,SIGTERM多半是会被阻塞的、忽略。

1. k8s的下一些知识点

livenessProbe: 用于探测pod节点是否存活一个endpoint

readinessProbe: 用于探测pod 节点是否ready 的endpoint

terminationGracePeriodSeconds: deployment 中设置停止pod前等待时间(默认30s)

k8s pod lifecycle生命周期相关:

可以设置 preStop 与 preStart

bash 复制代码
lifecycle:
  postStartEnabled: false
  preStop:
    exec:
      command:
      - "/bin/sh"
      - "-cx"
      - "curl http://127.0.0.1:8080/springboot-demo/offline"
      - "sleep 20s"
  preStopEnabled: true

2. Springboot 健康检查

复制代码
添加actuator依赖,默认会暴露health 相关接口
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

访问/actuator/health 可以查看详细的信息,结合k8s 的readinessProbe ,可以做到等到服务启动成功,再将流量请求到该pod;

bash 复制代码
{
    "status": "UP",
    "details": {
        "diskSpace": {
            "status": "UP",
            "details": {
                "total": 2945792487424,
                "free": 2424665210880,
                "threshold": 10485760
            }
        },
        "mongo": {
            "status": "UP",
            "details": {
                "version": "4.0.22"
            }
        },
        "db": {
            "status": "UP",
            "details": {
                "ouserDataSource": {
                    "status": "UP",
                    "details": {
                        "database": "MySQL",
                        "hello": 1
                    }
                },
                "readDataSource": {
                    "status": "UP",
                    "details": {
                        "database": "MySQL",
                        "hello": 1
                    }
                },
                "writeDataSource": {
                    "status": "UP",
                    "details": {
                        "database": "MySQL",
                        "hello": 1
                    }
                },
                "oscDataSourceRead": {
                    "status": "UP",
                    "details": {
                        "database": "MySQL",
                        "hello": 1
                    }
                },
                "oscDataSourceWrite": {
                    "status": "UP",
                    "details": {
                        "database": "MySQL",
                        "hello": 1
                    }
                },
                "ouserFilterDataSourceRead": {
                    "status": "UP",
                    "details": {
                        "database": "MySQL",
                        "hello": 1
                    }
                },
                "ouserFilterDataSourceWrite": {
                    "status": "UP",
                    "details": {
                        "database": "MySQL",
                        "hello": 1
                    }
                },
                "miscDataSource": {
                    "status": "UP",
                    "details": {
                        "database": "MySQL",
                        "hello": 1
                    }
                }
            }
        },
        "refreshScope": {
            "status": "UP"
        },
        "discoveryComposite": {
            "status": "UP",
            "details": {
                "discoveryClient": {
                    "description": "Discovery Client not initialized",
                    "status": "UNKNOWN"
                },
                "zookeeper": Object{...}
            }
        },
        "zookeeper": {
            "status": "UP",
            "details": {
                "connectionString": "127.0.0.1:2181",
                "state": "STARTED"
            }
        },
        "redis": {
            "status": "UP",
            "details": {
                "version": "4.0.14"
            }
        }
    }
}

3. ribbon的客户端负载均衡机制

ribbon 客户端缓存所有的生产者服务的节点信息;源码分析见本人的另外一篇博客: https://caicongyang.blog.csdn.net/article/details/125954437?spm=1001.2014.3001.5502

springboot3.x 版本的loadbalan客户端原理也类似;

可以通过配置ribbon.ServerListRefreshInterval = 5000 修改ribbon去nacos 或者eurka 等注册中心的刷新频率

4. 实现方案一

在eurka 或者zookeeper作为注册中心的过程中,可以结合k8s 的lifecycle,在prestop 的时候,提前讲微服务从注册中心提前下线,并且设置terminationGracePeriodSeconds 大于ribbon.ServerListRefreshInterval 的时间,从而保证服务下线之前,各个调用该服务的调用方的缓存已经更新;

5. 实现方案二

nacos作为注册中心时,可以通过监听Subscriber<InstancesChangeEvent> 实例变更事件,来更新ribbon 的客户端缓存,从而保证新的请求不会请求到老的节点;

java 复制代码
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.client.naming.event.InstancesChangeEvent;
import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.Subscriber;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
import lombok.extern.slf4j.Slf4j;

import org.springframework.cloud.netflix.ribbon.SpringClientFactory;

import java.util.Optional;

@Slf4j
public class RibbonServiceChangeNotifier extends Subscriber<InstancesChangeEvent> {

    @Resource
    private SpringClientFactory springClientFactory;

    @PostConstruct
    public void init() {
        // 注册当前自定义的订阅者以获取通知
        NotifyCenter.registerSubscriber(this);
    }

    @Override
    public void onEvent(InstancesChangeEvent event) {
        String serviceName = event.getServiceName();
        // 使用 dubbo 时包含 rpc 服务类会注册以 providers: 或者 consumers: 开头的服务
        // 由于不是正式的服务, 这里需要进行排除, 如果未使用 dubbo 则不需要该处理
        if (serviceName.contains(":")) {
            return;
        }
        // serviceName 格式为 groupName@@name
        String split = Constants.SERVICE_INFO_SPLITER;
        if (serviceName.contains(split)) {
            serviceName = serviceName.substring(serviceName.indexOf(split) + split.length());
        }
        log.info("服务上下线: {}", serviceName);
        // 针对服务进行后续更新操作
        // 如果自定义负载均衡方式则将默认的 ZoneAwareLoadBalancer 替换为自己的实现即可
        Optional.ofNullable(springClientFactory.getLoadBalancer(serviceName))
                .ifPresent(loadBalancer ->
                        ((ZoneAwareLoadBalancer<?>) loadBalancer).updateListOfServers());
    }

    @Override
    public Class<? extends Event> subscribeType() {
        return InstancesChangeEvent.class;
    }

}

6. 参考

Nacos 实现服务平滑上下线(Ribbon 和 LB)-WinFrom控件库|.net开源控件库|HZHControls官网

相关推荐
菜地里的小菜鸟3 天前
failed to get imageFs info: non-existent label “docker-images
k8s·failedtogetimagefsin·k8s报错
菜地里的小菜鸟4 天前
kubectl debug
k8s·kubectldebug
腾飞开源6 天前
01_K8s干货笔记之认识K8s
运维·笔记·云原生·容器·kubernetes·k8s·容器化部署
小匠石钧知9 天前
02_在多个RockyLinux10虚拟机上安装k8s集群
云原生·容器·kubernetes·k8s
ShirleyWang01215 天前
让headlamp控制台能访问
linux·服务器·python·k8s·k3s
spider_xcxc15 天前
K8s 部署学习笔记
docker·容器·kubernetes·云计算·k8s
ShirleyWang01215 天前
Day02 K3s NGF(Nginx Gateway Fabric)单 Worker 环境网关更新与故障处置 SOP
linux·服务器·python·k8s·k3s
ShirleyWang01215 天前
DAY01 K3s 私有化部署排障复盘与 SOP
linux·服务器·k8s·k3s·企业部署
XUHUOJUN19 天前
AKS 不是安装在 Windows Server 上,而是运行在 Windows Server 之上的 Azure 平台能力
windows·架构·k8s·azure local·azure stack
Geek-Chow19 天前
Connecting kubectl to a Private EKS Cluster Over an Internal Domain
kubernetes·k8s·aws