使用Consul作为微服务的注册中心

原文地址

原文链接

前言

看了相关的资料,我们选择consul作为注册中心,不用nacos的原因是nacos太大了,不用eureka的原因是已经停止维护,其他的Istio,Linkerd等都是主要基于Kubernetes的,所以我们这里选择consul,参考1参考2参考3

部署

基本部署

官方文档

首先,根据官方对于架构的介绍,我们至少需要一个server,甚至可以不需要client,也可以完成功能,我们这里使用单个server的方案。虽然多个server可用保证可用性,参考官网文档的Consensus Protocol部分,我们应当使用3个server节点去部署。但是这个对于单服务这种部署是没有意义的。因为单服务中,我们自己的服务只能注册到其中一个服务器,那么如果这个服务器挂了,我们在这个服务器上所有注册的服务都挂了。他的容灾策略从我目前所看到的文档总结来说,如果不增加一层自主转发的情况下(比如nginx?我没有具体尝试,不是特别确定是否可行),是保证不同服务器的数据互通以及服务器对外输出的高可用,但是起码对于spring-cloud而言,单个服务实例只能注册到一个服务器上,虽然他能发现本集群的其他服务器上注册的服务,但是如果该服务器一旦挂掉,上面注册的服务实例也会挂掉,所以在单台服务器下这种部署是没有意义的,这里可以参考Registering Multiple Same-Host ServicesA question about the registration of the consul cluster。这里官方Docker部署文档,并没有进行相关说明,甚至其他其余的两个服务都没有开放注册端口,非常的误导人

这里也给出多server和client的方案以防万一

version: "3.7"
services:
  consul-server-y:
    image: 'hashicorp/consul:latest'
    restart: always
    container_name: 'consul-server-y'
    hostname: 'consul-server-y'
    ports:
      - '9527:8500'
      - '19527:8600/tcp'
      - '19527:8600/udp'
    volumes:
      - ./server-y.json:/consul/config/server-y.json:ro
      - ./certs/:/consul/config/certs/:ro
    command: "agent -bootstrap-expect=3"

  consul-server-x:
    image: 'hashicorp/consul:latest'
    restart: always
    container_name: 'consul-server-x'
    hostname: 'consul-server-x'
    ports:
      - '9528:8500'
      - '19528:8600/tcp'
      - '19528:8600/udp'
    volumes:
      - ./server-x.json:/consul/config/server-x.json:ro
      - ./certs/:/consul/config/certs/:ro
    command: "agent -bootstrap-expect=3"
    
  consul-server-z:
    image: 'hashicorp/consul:latest'
    restart: always
    container_name: 'consul-server-z'
    hostname: 'consul-server-z'
    ports:
      - '9529:8500'
      - '19529:8600/tcp'
      - '19529:8600/udp'
    volumes:
      - ./server-z.json:/consul/config/server-z.json:ro
      - ./certs/:/consul/config/certs/:ro
    command: "agent -bootstrap-expect=3"
    
  consul-client:
    image: 'hashicorp/consul:latest'
    container_name: consul-client
    restart: always
    volumes:
      - ./client.json:/consul/config/client.json:ro
      - ./certs/:/consul/config/certs/:ro
    command: "agent"

服务端配置文件,三个配置文件都差不多,照葫芦画瓢稍微改下就行

{
  "node_name": "consul-server-x",
  "server": true,
  "ui_config": {
    "enabled": true
  },
  "retry_join": ["consul-server-y", "consul-server-z"],
  "data_dir": "/consul/data",
  "addresses": {
    "http": "0.0.0.0"
  },
  "encrypt": "aPuGh+5UDskRAbkLaXRzFoSOcSM+5vAK+NEYOWHJH7w=",
  "verify_incoming": false,
  "verify_outgoing": false,
  "verify_server_hostname": false
}

客户端配置文件

{
  "node_name": "consul-client",
  "data_dir": "/consul/data",
  "retry_join": ["consul-server-y", "consul-server-x", "consul-server-z"],
  "encrypt": "aPuGh+5UDskRAbkLaXRzFoSOcSM+5vAK+NEYOWHJH7w=",
  "verify_incoming": false,
  "verify_outgoing": false,
  "verify_server_hostname": false
}

这里如果需要保证高可用,服务需要启动三个实例,分别注册到9527,9528,9529端口,这样才能保证在其中一个server挂掉的情况,我们自己服务的高可用,或者只启动一个实例使用中间层做转发,但是如果这样consul服务的功能就看起来不是很完整了

官方更多配置命令行启动相关参数

ACL

Access Control Lists,需要配置注册中心的访问权限,首先需要添加访问权限在server的json配置文件中增加

{
  "acl":{
    "enabled": true,
    "default_policy":"deny",
    "enable_token_persistence": true
  }
}
  • enabled 是否开启ACL
  • default_policy 自定义权限需要将其设置为deny
  • enable_token_persistence token持久化到磁盘

然后进入集群内任意server中,执行命令

consul acl bootstrap

获取SecretID,然后在登录框内输入密钥id即可

由于我们这里修改了默认的策略,所以此时获得的SecretID只能用来登录,并不能用来注册,我们需要配置角色和权限来生成可以注册的密钥。当然如果简单使用,不设置default_policy,使用默认allow然后拿生成的SecretID就可以直接注册了

我们这里登录ui进行先新建策略,可以将策略定在node(server/client)上,也可以定在服务或者配置上,参考官方策略配置,我们这里需要服务注册的token所以定在服务上,这里如果需要完成服务的注册和发现,还需要对于node节点有读权限,否则无法发现其他服务

node_prefix "" {
  policy = "read"
}
service_prefix "" {
  policy = "write"
}
使用Consul作为微服务的注册中心_1.jpg

更多配置参考官方文档

然后再去角色页面新建角色,配置角色所拥有的策略选择刚才新建的策略,最后新建token选择刚才的角色即可(当然也可以不建角色角色直接配置策略在token上),至此我们获得一个可以用于服务注册的token

Gossip encryption

我们需要在集群中配置相同的密钥encrypt才能保证集群通信的可用性,我们现在需要生成自己的密钥,在容器内执行命令

 consul keygen
 #ABCDEFGHIKOPF123456781234567=

获取密钥,然后在各个server中添加配置文件即可

{
  "encrypt": "ABCDEFGHIKOPF123456781234567=",
  "encrypt_verify_incoming": true,
  "encrypt_verify_outgoing": true
}

mTLS

参考官方文档,进入任意server执行命令

consul tls ca create
consul tls cert create -server -dc dc1

然后在宿主机将文件拷贝出来,放入之前设置的docker映射卷目录中

docker cp  e36:dc1-server-consul-0-key.pem ./
docker cp  e36:dc1-server-consul-0.pem ./
docker cp  e36:consul-agent-ca.pem ./

这里需要注意拷贝出来的文件的权限问题,否则docker容器可能没有读权限

chmod 644 dc1-server-consul-0-key.pem 

最后增加mTLS相关认证配置在server和client配置文件中

{
  "verify_incoming": true,
  "verify_outgoing": true,
  "verify_server_hostname": true,
  "ca_file": "/consul/config/certs/consul-agent-ca.pem",
  "cert_file": "/consul/config/certs/dc1-server-consul-0.pem",
  "key_file": "/consul/config/certs/dc1-server-consul-0-key.pem"
}

HTTPS

看下SSL和mTLS的关系,具体配置这里就不赘述了,比较简单也不是必须的。运营商申请子域名,配ssl证书,最后配一下nginx,然后关闭测试端口,这样我们基本的生产环境的的consul就配置完成了

服务的注册和发现

我们这里选择使用Spring Cloud的项目进行服务注册和发现,这里核心参考spring-cloud-consul官方文档

我们使用openfeign进行服务间调用,故而这里引用的核心依赖有

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--有一部分文档这里引用的是spring-cloud-starter-consul-all,不建议这么做,会导致部分配置属性如enabled功能失效 
参考 https://github.com/spring-cloud/spring-cloud-consul/issues/309-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
<!--识别bootstrap文件了-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
<!--如果作为基础服务就不需要openfeign了-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

首先启动类增加注解

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

添加consul相关配置

spring:
  cloud:
    consul:
      enabled: true
      #你的域名或者你的服务器ip地址
      host: your_domin_or_your_ip_address
      #这里如果使用nginx转发则为80或者443端口,否则为你的docker-compose中配置的端口,记得配置防火墙
      port: 443
      discovery:
      #如果有需要这里需要配置不同实例标识
        instance-id: ${spring.application.name}
        #如果无法从consul地址ping到service地址,那么consul的健康检测是无法通过的,需要持续由服务去ping到consul来保证服务的可用性
        #但是目前版本心跳的ping并不会带上token需要写一个切面去处理
        #正式环境不建议这么写,consul+springcloud目前我的使用情况是,使用心跳注册在服务关闭时候无法检测到服务关闭仍然在active状态原因不明
        #所以以服务稳定为前提最好还是使用host+port+healthPath的方式在证书环境注册
        heartbeat:
          enabled: true
         #在ACL步骤配置的可以用于服务注册的token
        acl-token: your_token
        #如果这里nginx配置了证书,那么我们需要HTTPS协议,默认HTTP不用设置
      scheme: HTTPS

如果服务死亡,springcloudconsul在目前的4.0.2的版本还不支持consul的deregister_critical_service_after字段去自动取消注册,起码在我的环境中health-check-critical-timeout等这种字段是不能如期望生效的

    /**
     * Timeout to deregister services critical for longer than timeout (e.g. 30m).
     * Requires consul version 7.x or higher.
     */
    private String healthCheckCriticalTimeout;

导致死掉的服务仍然会在平衡策略中被请求到,目前没有比较好的处理的方法,只能手动删除,或者写定时任务脚本,参考Consul not deregistering zombie servicesConsul deregister 'failing' services,以及其他参考1参考2参考3,这里摘录脚本(不推荐写脚本,有这功夫不如手写注册模块)

leader="$(curl http://ONE-OF-YOUR-CLUSTER:8500/v1/status/leader | sed 

's/:8300//' | sed 's/"//g')"
while :
do
serviceID="$(curl http://$leader:8500/v1/health/state/critical | ./jq '.[0].ServiceID' | sed 's/"//g')"
node="$(curl http://$leader:8500/v1/health/state/critical | ./jq '.[0].Node' | sed 's/"//g')"
echo "serviceID=$serviceID, node=$node"
size=${#serviceID}
echo "size=$size"
if [ $size -ge 7 ]; then
curl --request PUT http://$node:8500/v1/agent/service/deregister/$serviceID
else
break
fi
done
curl http://$leader:8500/v1/health/state/critical

还是聊一句,spring-cloud-consul的维护进度确实太慢了,看了一些issue基本有问题就是欢迎PR,所以有不忙的兄弟可以去PR,如果项目比较重要的目前阶段还是建议手写注册模块,使用他的配置去注册坑实在是有点多

如果由consul可以ping到service地址且是通过hostname可以调用到,那么则不需要任何连接配置,如果不是通过host则需要在discovery中提示service的地址,参考此问题的说明。如果不能ping到,那么为了维持服务在consul的可用标志,我们需要让服务不断给consul发心跳,这种情况下由于consul本身的问题,在目前版本并不会带上acl-token,我们这里给出一个切面处理,代码来源该问题已经在2023.3.29的spring-cloud-starter-consul-discovery:4.0.2版本中修复,不需要再增加切面

@Aspect
@Configuration
@RequiredArgsConstructor
@ConditionalOnConsulEnabled
@ConditionalOnProperty("spring.cloud.consul.discovery.acl-token")
@Slf4j
public class ConsulClientAspect {
    private final ConsulDiscoveryProperties consulDiscoveryProperties;

    @PostConstruct
    public void init() {
        log.info("Hooking ConsulClient.agentCheckPass(String) calls to enforce acl token passing");
    }

    /**
     * Trap calls made to {@link ConsulClient#agentCheckPass(String)} and call the overridden method variant with ACL
     * token
     */
    @SneakyThrows
    @Around("execution (* com.ecwid.consul.v1.ConsulClient.agentCheckPass(String))")
    public Response<Void> trapAgentCheckPass(final ProceedingJoinPoint joinPoint) {
        final String checkId = (String) joinPoint.getArgs()[0];
        final ConsulClient client = (ConsulClient) joinPoint.getThis();
        return client.agentCheckPass(checkId, null, this.consulDiscoveryProperties.getAclToken());
    }
}

原文地址

原文链接

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容