Consul集群中微服务重复注册问题填坑

consul k8s高可用集群

废话不多说先上yaml

apiVersion: v1
kind: Service
metadata:  
    name: consul-service
    namespace: consul
    labels:
      name: consul
spec:  
    type: ClusterIP  
    clusterIP: None
    ports:    
    - name: http      
      port: 8500      
      targetPort: 8500    
    - name: https      
      port: 8443      
      targetPort: 8443    
    - name: rpc      
      port: 8400      
      targetPort: 8400    
    - name: serflan-tcp      
      protocol: "TCP"      
      port: 8301      
      targetPort: 8301    
    - name: serflan-udp      
      protocol: "UDP"      
      port: 8301      
      targetPort: 8301    
    - name: serfwan-tcp      
      protocol: "TCP"      
      port: 8302      
      targetPort: 8302    
    - name: serfwan-udp      
      protocol: "UDP"      
      port: 8302      
      targetPort: 8302    
    - name: server      
      port: 8300      
      targetPort: 8300    
    - name: consuldns      
      port: 8600      
      targetPort: 8600  
    selector:    
        app: consul
---
apiVersion: apps/v1beta1 
kind: StatefulSet
metadata:
  namespace: consul
  name: consul
spec:
  serviceName: consul-service
  replicas: 3
  template: 
    metadata:
      labels:
        app: consul
    spec:
      containers:
      - name: consul
        image: consul:latest
        args:
             - "agent"
             - "-server"
             - "-bootstrap-expect=3"
             - "-ui"
             - "-data-dir=/consul/data"
             - "-bind=0.0.0.0"
             - "-client=0.0.0.0"
             - "-advertise=$(PODIP)"
             - "-retry-join=consul-0.consul-service.$(NAMESPACE).svc.cluster.local"
             - "-domain=cluster.local"
             - "-disable-host-node-id"
        volumeMounts:
            - name: data
              mountPath: /consul/data
        env:
            - name: PODIP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
            - name: NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
        ports:
            - containerPort: 8500
              name: ui-port
            - containerPort: 8400
              name: alt-port
            - containerPort: 53
              name: udp-port
            - containerPort: 8443
              name: https-port
            - containerPort: 8080
              name: http-port
            - containerPort: 8301
              name: serflan
            - containerPort: 8302
              name: serfwan
            - containerPort: 8600
              name: consuldns
            - containerPort: 8300
              name: server
  volumeClaimTemplates:
  - metadata:
      name: data
      labels:
        app: consul
    spec:
      accessModes: [ "ReadWriteMany" ]
      resources:
        requests:
          storage: 2Gi

服务注册的坑

由于采用的是多server的注册方式,存在一个问题,当我们向consul集群的一个agent注册服务的时候,会出现相同的instanceid会在不同的节点上重复注册。这就是一个大坑了,这意味着有多少个节点就有可能出现多少个重复的服务实例。当然实际上需要用到的只有一个,那意味着多余的实例需要删除,但真实操作起来就没有想象中那么简单了。

产生原因

consul的官方文档上有这么一段描述:
The agent is responsible for managing the status of its local services, and for sending updates about its local services to the servers to keep the global catalog in sync.
大致的意思是每个agent都维护着其自己节点上注册的服务,并将其发送到其他的服务上使其在全局的catlog上同步。
这意味着其实每个agent维护的都是自己节点注册的服务,当同一个servername和instanceid在不同节点上进行注册的时候会被认为是不同的服务,而不是想当然的认为每个instanceid都是唯一的,当重复注册的时候会进行覆盖,实际上consul集群不是这么处理的。只有当在同一个节点注册的时候才会出现覆盖操作。这描述真的是很隐晦,就不能直白一点嘛,关键网上找不到什么相关的资料,全靠意会,真的是非常坑爹!
所以consul其实提供了两个注册的接口,/agent/service/register和/catlog/register,其需要的参数都是不一样的,具体的可以去看下consul的文档。

尝试解决方案

多出来了就删嘛

这个想法没问题,但实际操作起来是一堆的坑。
首先来看下consul提供的删除服务的API接口
1、/agent/service/deregister/:service_id
2、/catalog/deregister
第一个看着没啥问题,但是实际上是有问题的,由于考虑到高可用,在k8s里面我们会用一个service代理所有的所有的consul,所以是负载均衡的,这个接口实际上只会删除当前consul server节点上的该服务,其他节点的是不会删除的,所以这个接口不能直接使用

第二个接口看着就正常了,是通过catlog的方式进行删除,调用之后确实把对应节点的服务删掉了,正当你暗自窃喜的时候,这个被删除的服务过了一会儿就又冒出来了,其实他只是删除了缓存在catlog里面的数据,真实节点上的服务并没有删除,过了一会儿节点有把这个服务的信息同步上来了
我这时候已经想骂娘了,这不是个坑爹玩意儿嘛!!!!!直接删的想法只能先放弃了,看看有没有其他的方式来解决。

造轮子解决

主要思路还是两种:
1、多了就删
在看了一遍consul提供的接口之后我的第一反应其实就是需要自己造轮子来解决了,其实通过consul提供的API接口确实也是可以解决的,具体的流程如下,如果你真的最后只能通过这种方式来解决的话也可以借鉴一下,逻辑其实也是比较简单的


consul集群服务重复注册解决方案.jpg

2、注册之前先检查
由于我使用的是springcloud,直接重写注册接口就完事儿

/*
* Consul集群注册
* */
public class ConsulClusterRegistry extends ConsulServiceRegistry {
    @Qualifier("vanillaRestTemplate")
    @Autowired(required = false)
    private RestTemplate restTemplate;

    @Autowired
    private Environment environment;

    private ConsulClient consulClient;

    public ConsulClusterRegistry(ConsulClient client, ConsulDiscoveryProperties properties, TtlScheduler ttlScheduler, HeartbeatProperties heartbeatProperties) {
        super(client, properties, ttlScheduler, heartbeatProperties);
        this.consulClient = client;
    }

    @Override
    public void register(ConsulRegistration reg){
        List<CatalogService> services = consulClient.getCatalogService(reg.getService().getName(),null).getValue();
        //重复InstanceID的服务注销
        for ( CatalogService service : services){
            if (needDeregister(service,reg)) {
                deregisterService(service);
            }
        }
        super.register(reg);
    }

    /*
    * 注销服务
    * 通过特定节点的agent接口进行删除,该方式仅适用于多server节点的consul集群
    * */
    private void deregisterService(CatalogService catalogService){
        String nodeAddr = catalogService.getAddress();
        String restUrl = String.format("http://%s:%s/v1/agent/service/deregister/%s",
            nodeAddr,
            environment.getProperty("spring.cloud.consul.port"),
            catalogService.getServiceId());
        restTemplate.exchange(restUrl, HttpMethod.PUT,null,String.class);
    }

    /*
    * 判断服务是否需要注销
    * 默认只要instanceID重复的服务是需要将其注销的
    * */
    private boolean needDeregister(CatalogService catalogService,ConsulRegistration consulRegistration){
        return catalogService.getServiceId().equals(consulRegistration.getInstanceId());
    }
}

此方法有一个局限性,由于是直接调用对应agent的接口进行服务的注销,那就意味着该服务必须能直接访问到agent,否则就无法实现。

待探索的方式

如上文所说其实consul提供了两个注册的接口,默认使用的是/agent/service/register这个接口而没有使用/catlog/register这个接口,由于时间问题我就没有进行尝试,后续有时间的话会测试一下通过catlog的接口是否就不会存在这个问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容