关于consul:prometheus结合consulconfd实现动态注册服务和动态更新配置告警规则

一、prometheus装置应用1.创立配置文件mkdir /opt/prometheus cd /opt/prometheus/vim prometheus.yml内容如下:global: scrape_interval: 60s #抓取数据的工夫距离 evaluation_interval: 60s #触发告警检测的工夫scrape_configs: - job_name: prometheus #工作名称 static_configs: #动态配置节点 - targets: ['localhost:9090'] labels: instance: prometheus #实例名称2.装置prometheusdocker run -d \ -p 9090:9090 \ -v /opt/prometheus/prometheus1.yml:/etc/prometheus/prometheus.yml \ -v /opt/prometheus/data:/prometheus \ prom/prometheus \3.拜访地址: http://localhost:9090/graph二、consul装置应用1.装置consuldocker run \ --name consul \ -p 8500:8500 \ -v /data/consul/conf/:/consul/conf/ \ -v /data/consul/data/:/consul/data/ \ -d consul拜访地址: http://ip:8500/graph(ip为你装置的服务器地址)2.应用consul首先理解consul+prometheus的大抵流程(1) 在服务器上装一个node节点(exporter节点),启动节点,例如mysql-exporter端口为9104(2) 将该服务器注册到consul的server中,在装置consul的服务器执行命令curl -X PUT -d '{"id": "test-exporter","name": "test-exporter","address": "ip","port": 9104,"tags": ["jvm-exporter"],"checks": [{"http": "http://ip:9104/metrics","interval":"5s"}]}' http://ip:8500/v1/agent/service/register name:consul的service注册名称id:consul的实例名称address:监控地址ipport:监控的端口号tags:标签名checks:查看的节点的门路(3) 在prometheus中配置服务主动发现,去读取consul中配置的服务器,只有consul有新的服务注册,prometheus也会更新节点,在prometheus.yml中批改为- job_name: 'consul-node-exporter' consul_sd_configs: - server: 'ip:8500' #consul的地址(4) 配置不同分类的job,对增加的标签进行正则表达式过滤,合乎则增加到相应job分类如上配置的毛病是,所有的注册节点都会归类到consul-node-exporter这个工作类别上面,有时候想对注册的服务进行分类,比方mysql为一类,oracle为一类,就要批改配置为: ...

April 18, 2023 · 2 min · jiezi

关于consul:不背锅运维配置管理和服务发现之Confd和Consul

Confd和Consul是什么鬼?Confd和Consul都是用于配置管理和服务发现的工具。https://www.consul.io/https://www.tail-f.com/confd-basic/ConfdConfd是一个轻量级的工具,用于治理分布式系统中的配置文件。它通过将配置文件和模板拆散来解决配置管理的挑战。Confd监督由Etcd、Zookeeper、Consul等后端存储的配置更改,而后依据定义的模板生成配置文件,并将其散发到零碎中的所有节点。Confd还反对从命令行或环境变量中读取配置参数,并将其注入到模板中。 在实践中,Confd能够用于治理诸如Nginx、Apache等Web服务器的配置文件,以及运行在Docker或Kubernetes容器中的应用程序的配置文件。Confd还能够通过与Vault等密钥管理工具的集成来提供平安的配置存储和传输。 ConsulConsul是一个功能强大的服务发现和配置管理平台。它提供了分布式KV存储、健康检查、DNS和HTTP API等性能,使得服务的发现和治理变得非常简单。Consul还反对多数据中心和平安通信,以保证系统的高可用性和安全性。 在实践中,Consul能够用于治理多种类型的服务,包含Web应用程序、数据库、音讯队列等。它还能够与容器编排工具(如Docker Compose和Kubernetes)和配置管理工具(如Ansible和Chef)等集成,提供全面的配置管理和服务发现解决方案。 总的来说,Confd和Consul都是十分有用的工具,用于治理分布式系统的配置和服务发现。抉择哪个工具取决于您的具体需要,例如您须要治理什么类型的服务,以及您心愿在零碎中应用哪些特定的性能。利用场景Confd和Consul都是用于分布式系统配置管理和服务发现的工具,实用于许多不同的利用场景。以下是一些可能的利用场景: Confd配置Web服务器:Confd能够用于治理Nginx、Apache等Web服务器的配置文件,并主动将配置文件散发到所有节点。配置容器:Confd能够在Docker或Kubernetes容器中运行,并将容器所需的配置文件动静生成并散发到容器中的应用程序。集成密钥管理工具:Confd能够与Vault等密钥管理工具集成,提供平安的配置存储和传输。治理分布式系统配置:Confd能够治理分布式系统中的各种配置文件,例如数据库配置、应用程序配置等。Consul服务发现:Consul提供了弱小的服务发现性能,能够帮忙应用程序发现和连贯到其余服务。治理多数据中心环境:Consul能够治理多个数据中心之间的服务发现和配置管理,以保证系统的高可用性。DNS和HTTP API:Consul提供了DNS和HTTP API接口,以便应用程序能够轻松地发现和连贯到其余服务。健康检查:Consul能够查看服务的衰弱状态,并在服务呈现故障时主动将流量路由到衰弱的节点上。简而言之,Confd和Consul能够用于治理各种类型的配置文件和服务发现需要,实用于各种分布式系统和利用场景。抉择哪种工具取决于您的具体需要和偏好。Confd+Consul联合应用Confd和Consul能够提供更全面和灵便的分布式系统配置管理和服务发现解决方案,实用于许多不同的利用场景。以下是一些可能的联合应用场景: 在Docker容器中运行Confd,应用Consul来发现和治理容器中运行的服务。这种办法能够提供动静配置生成和散发以及服务发现和健康检查性能。应用Confd从后端存储(如Etcd、Zookeeper、Consul等)中获取配置信息,并应用Consul来发现服务和治理它们的衰弱状态。这种办法能够提供动静配置生成和散发以及服务发现和健康检查性能。应用Consul的KV存储来存储应用程序的配置信息,而后应用Confd从KV存储中获取配置并将其注入到应用程序的模板中。这种办法能够提供平安的配置存储和传输,同时提供灵便的配置选项。应用Confd和Consul来治理多个数据中心之间的服务发现和配置管理,以保证系统的高可用性。这种办法能够提供跨数据中心的服务发现和配置管理性能。将Confd和Consul与Vault等密钥管理工具联合应用,提供平安的配置存储和传输。这种办法能够确保应用程序的配置信息失去充沛爱护。联合应用Confd和Consul能够提供更全面和灵便的配置管理和服务发现解决方案,实用于各种分布式系统和利用场景。抉择哪种联合应用办法取决于您的具体需要和偏好。实战上面分享两个Confd和Consul的简略实战,心愿能起到抛砖引玉的成果。案例1场景:应用Confd、Consul和nginx来管理应用程序的动静配置和负载平衡:正式开撸装置etcd或Consul、Confd和nginx。创立一个Confd配置文件,指定etcd或Consul的地址和端口等信息,并指定要监督的配置文件的门路和格局。例如,以下是一个Confd配置文件的示例:[template]src = "/path/to/nginx.conf.tmpl"dest = "/etc/nginx/nginx.conf"keys = [  "/nginx/upstream/backend1/server1",  "/nginx/upstream/backend1/server2",]此配置指定将从etcd或Consul中监督/nginx/upstream/backend1/server1和/nginx/upstream/backend1/server2键,并应用nginx.conf.tmpl模板生成配置文件nginx.conf。 创立一个nginx.conf.tmpl模板文件,其中蕴含应用程序的负载平衡配置数据。例如:http {  upstream backend {    server {{key "/nginx/upstream/backend1/server1"}};    server {{key "/nginx/upstream/backend1/server2"}};  }  server {    listen 80;    server_name example.com;    location / {      proxy_pass http://backend;    }  }}此模板文件应用Confd的key函数将/nginx/upstream/backend1/server1和/nginx/upstream/backend1/server2键的值注入到nginx.conf中的upstream局部。 启动Confd,并应用以下命令指定下面创立的Confd配置文件:confd -config-file /path/to/confd.conf此命令将启动Confd并开始监督指定的键。 启动nginx,并指定应用生成的配置文件nginx.conf:nginx -c /etc/nginx/nginx.conf此命令将启动nginx,并应用生成的配置文件。 在Consul中注册后端服务,并增加服务器地址和端口。例如,以下是一个应用Consul API注册后端服务并增加服务器地址和端口的示例:curl -X PUT -d @service.json http://localhost:8500/v1/agent/service/registercurl -X PUT -d 'backend1.example.com:8080' http://localhost:8500/v1/kv/nginx/upstream/backend1/server1curl -X PUT -d 'backend2.example.com:8080' http://localhost:8500/v1/kv/nginx/upstream/backend1/server2此命令将注册一个名为backend1的服务,并增加服务器地址和端口到Consul的键/值存储中。 在浏览器中拜访nginx的IP地址或域名,例如example.com,以测试负载平衡性能。这个案例简略演示了如何应用Confd、Consul和nginx来实现动静配置和负载平衡性能,能够依据理论需要进行批改和扩大。案例2场景:在一个基于Docker的分布式应用程序中,咱们应用Confd从Consul中获取Nginx的配置信息,并将配置文件注入到Nginx容器中,以便Nginx能够自动更新其配置并反向代理到其余服务。正式开撸装置Docker、Confd和Consul。启动Consul服务器:consul agent -server -bootstrap-expect=1 -data-dir=consul-data -ui -bind=<ip-address>在Consul中注册其余服务,例如一个名为web-service的Web服务:consul services register -name web-service -port 8080在Consul中存储Nginx的配置信息,例如一个名为nginx.conf的配置文件:consul kv put nginx.conf 'server {    listen 80;    server_name example.com;    location / {        proxy_pass http://web-service:8080;    }}'启动Nginx容器,并在容器中运行Confd:docker run -d --name nginx \  -p 80:80 \  -v /etc/nginx/conf.d \  -e CONSUL_HTTP_ADDR=<ip-address>:8500 \  nginxdocker run -d --name confd \  -e CONSUL_HTTP_ADDR=<ip-address>:8500 \  -v /etc/nginx/conf.d \  confd -backend=consul -node=<ip-address>:8500 -watch在Nginx容器中,创立一个Confd模板文件nginx.conf.tmpl,用于将Consul中存储的配置信息注入到Nginx配置文件中:server {    listen 80;    server_name example.com;    {{range services "web-service"}}    location / {        proxy_pass http://{{.Address}}:{{.Port}};    }    {{end}}}在Nginx容器中,创立一个Confd配置文件nginx.toml,指定Confd如何将Consul中的配置信息注入到Nginx配置文件中:[template]src = "nginx.conf.tmpl"dest = "/etc/nginx/conf.d/nginx.conf"keys = [    "nginx.conf",]check_cmd = "/usr/sbin/nginx -t -c /etc/nginx/nginx.conf"reload_cmd = "/usr/sbin/nginx -s reload -c /etc/nginx/nginx.conf"启动Confd容器,并将Confd模板和配置文件挂载到Nginx容器中:$ docker run -d --name confd \  -v /etc/nginx/conf.d \  -v /etc/confd/conf.d \  -v /etc/confd/templates \  --link nginx \  confd -backend=consul -node=<ip-address>:8500 -watch拜访Nginx的Web服务,查看是否能够胜利反向代理到其余服务。本文转载于(喜爱的盆友关注咱们):https://mp.weixin.qq.com/s/daytnZIst7s88HUdag1x_Q

March 7, 2023 · 1 min · jiezi

关于consul:Apache-APISIX-集成-Consul-KV服务发现能力再升级

背景信息Consul 是一个服务网格解决方案,其外围之一的 Consul KV 是一个分布式键值数据库,主要用途是存储配置参数和元数据,同时也容许用户存储索引对象。 在微服务架构模式下,当扩容、硬件故障等导致上游服务产生变动的状况呈现时,通过手动撰写配置,保护上游服务信息的形式,会导致保护老本陡增。对此,Apache APISIX 提供了服务发现注册核心的性能,实现动静获取最新的服务实例信息,以升高用户的保护老本。 目前,Apache APISIX 借由社区奉献的 consul_kv 模块,反对了基于 Consul KV 的服务发现注册核心。 插件工作原理Apache APISIX 利用 Consul KV 分布式键值存储能力的 consul_kv 模块,解耦了服务的提供者和消费者,实现了服务发现注册核心的两大外围性能。 服务注册:服务提供者向注册核心注册服务。服务发现:服务消费者通过注册核心查找服务提供者的路由信息。在此基础上构建的 Apache APISIX 将更灵便地适应现有的微服务架构,更好地满足用户需要。 如何应用本文中的测试环境均应用 docker-compose 在 Docker 中搭建。 下载 Apache APISIX。# 拉取 apisix-docker 的 Git 仓库git clone https://github.com/apache/apisix-docker.git 创立 Consul 文件夹和配置文件。# 创立 consul 文件夹mkdir -p ~/docker-things/consul/ && cd "$_" # 创立配置文件touch docker-compose.yml server1.json批改 docker-compose.yml 文件。version: '3.8'services: consul-server1: image: consul:1.9.3 container_name: consul-server1 restart: always volumes: - ./server1.json:/consul/config/server1.json:ro networks: - apisix ports: - '8500:8500' command: 'agent -bootstrap-expect=1'networks: apisix: external: true name: example_apisix批改 server1.json 文件。{ "node_name": "consul-server1", "server": true, "addresses": { "http": "0.0.0.0" }}在 Apache APISIX 中的配置文件 apisix_conf/config.yaml 增加 Consul 的相干配置信息。# config.yml# ...other configdiscovery: consul_kv: servers: - "http://consul-server1:8500" prefix: "upstreams"启动 Apache APISIX 和 Consul。# 进入 example、consul 文件夹,顺次启动 APISIX、Consuldocker-compose up -d将测试服务注册到 Consul。example 蕴含了两个 Web 服务,你能够间接应用这两个服务进行测试。# 查看 example 的 docker-compose.yml 就能够看到两个 Web 服务$ cat docker-compose.yml | grep web# 输入web1: - ./upstream/web1.conf:/etc/nginx/nginx.confweb2: - ./upstream/web2.conf:/etc/nginx/nginx.conf确认 Web 服务的 IP 地址。$ sudo docker inspect -f='{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(sudo docker ps -aq) | grep web# 输入/example-web1-1 - 172.26.0.7/example-web2-1 - 172.26.0.2在终端中对 Consul 的 HTTP API 进行申请以注册测试服务。# 应用对应的 IP 进行注册curl \ -X PUT \ -d ' {"weight": 1, "max_fails": 2, "fail_timeout": 1}' \ http://127.0.0.1:8500/v1/kv/upstreams/webpages/172.26.0.7:80 curl \ -X PUT \ -d ' {"weight": 1, "max_fails": 2, "fail_timeout": 1}' \ http://127.0.0.1:8500/v1/kv/upstreams/webpages/172.26.0.2:80其中,/v1/kv/ 后的门路依照 {Prefix}/{Service Name}/{IP}:{Port} 的格局形成。 ...

March 8, 2022 · 2 min · jiezi

关于consul:如何安装部署Consul

Consul蕴含多个组件,然而作为一个整体,为你的基础设施提供服务发现和服务配置的工具.他提供以下要害个性: 1、服务发现 Consul的客户端可用提供一个服务,比方 api 或者mysql ,另外一些客户端可用应用Consul去发现一个指定服务的提供者.通过DNS或者HTTP应用程序可用很容易的找到他所依赖的服务. 2、健康检查 Consul客户端可用提供任意数量的健康检查,指定一个服务(比方:webserver是否返回了200 OK 状态码)或者应用本地节点(比方:内存应用是否大于90%). 这个信息可由operator用来监督集群的衰弱.被服务发现组件用来防止将流量发送到不衰弱的主机. 3、Key/Value存储 应用程序可用依据本人的须要应用Consul的层级的Key/Value存储.比方动静配置,性能标记,协调,首领选举等等,简略的HTTP API让他更易于应用. 多数据中心: Consul反对开箱即用的多数据中心.这意味着用户不须要放心须要建设额定的形象层让业务扩大到多个区域. Consul面向DevOps和利用开发者敌对.是他适宜古代的弹性的基础设施. 上面咱们就来学习一下如何简略疾速的装置部署好Consul服务 1.找到Consul的装置服务可点击试用。 2.装置部署增加节点-抉择版本-填写参数-部署胜利 装置部署过程简略又疾速,具体的装置教程如下: 如何增加节点?https://www.bilibili.com/vide...如何装置部署Consul?https://www.bilibili.com/vide...

September 26, 2021 · 1 min · jiezi

SpringCloud-初始化父子项目并集成-Consul-服务发现

SpringCloud 初始化父子项目并集成 Consul 服务发现准备工作IDEA2020.1JDK1.8Spring Cloud版本:Hoxton.SR5Spring Boot 版本:2.3.0.RELEASE安装Consulwindows安装:https://www.yuque.com/ekko/ap...Mac安装:https://www.yuque.com/ekko/ap...开始简介:consul的功能 服务发现Key/Value存储健康检查今天我们主要来学习实践服务发现功能先到 https://start.spring.io/ 初始化一个父项目生成之后解压,先用IDE编辑文件修改pom.xml,如下图,在底部 </build> 标签下切换国内阿里源 <repositories> <!--阿里云主仓库,代理了maven central和jcenter仓库--> <repository> <id>aliyun</id> <name>aliyun</name> <url>https://maven.aliyun.com/repository/public</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> <!--阿里云代理Spring 官方仓库--> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://maven.aliyun.com/repository/spring</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>保存,然后使用 IDEA 打开,等Maven加载完毕 新建子项目右击项目名称,New -> Module 包名自定义,不一定非要相同,这里的包名或许后面也要改掉下一步选择包,先跳过,点击Finish 完成 关联父子项目在父 Module的pom.xml中的<dependencies>标签上部新增 <modules> <module>pro-service</module> </modules>如图示在子项目 pro-service 中的 <modelVersion>4.0.0</modelVersion> 标签后追加 <parent> <groupId>com.github.springtools</groupId> <artifactId>SpringCloudPro</artifactId> <version>0.0.1</version> </parent>到这里会发现Maven找不到一些Spring Boot的依赖在父项目增加管理依赖版本号的定义最终的父项目pom.xml <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.github.springtools</groupId> <artifactId>SpringCloudPro</artifactId> <version>0.0.1</version> <name>SpringCloudPro</name> <description>父项目</description> <packaging>pom</packaging> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR5</spring-cloud.version> <spring-boot.version>2.3.0.RELEASE</spring-boot.version> </properties> <modules> <module>pro-service</module> </modules> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <!--监控--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!--支持Spring Boot 2.3.X--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <!--阿里云主仓库,代理了maven central和jcenter仓库--> <repository> <id>aliyun</id> <name>aliyun</name> <url>https://maven.aliyun.com/repository/public</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> <!--阿里云代理Spring 官方仓库--> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://maven.aliyun.com/repository/spring</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories></project>子项目pom.xml ...

June 3, 2020 · 2 min · jiezi

consuldocker实现服务注册

近期新闻css宣布支持三角函数ES10即将来临 基本架构注册中心: 每个服务提供者向注册中心登记自己的服务,将服务名与主机Ip,端口等一些附加信息告诉注册中心,注册中心按服务名分类组织服务清单。如A服务运行在192.168.1.82:3000,192.168.1.83:3000实例上。那么维护的内容如下:简单来说,服务中心会维护一份,在册的服务名与服务ip的映射关系。同时注册中心也会检查注册的服务是否可用,不可用则剔除。服务消费者:即在注册中心注册的服务,他们之间不再通过具体地址访问,而是通过服务名访问其他在册服务的资源。 技术说明Consul:采用Go开发的高可用的服务注册与配置服务,本文的注册中心采用Consul实现。Docker:基于Go语言的开源应用容器引擎。Registor:Go语言编写,针对docker使用的,通过检查本机容器进程在线或者停止运行状态,去注册服务的工具。这里主要用作对服务消费者的监控。示例环境: 系统:macos; docker: 17.09.1-ce; docker-compose:1.17.1。 docker命令创建注册中心docker run -h node1 --name consul -d --restart=always\ -p 8400 \ -p 8500:8500 \progrium/consul -server \-bootstrap-expect 1 -advertise 127.0.0.1下面来解释下各个参数-h 节点名字--name 容器(container)名称,后期用来方便启动关闭,看日志等,这个一定要写-d 后台运行-v /data:/data 使用宿主机的/data目录映射到容器内部的/data,用于保存consul的注册信息,要不docker 一重启,数据是不保留的。--restart=always 这个可以活得长一点下面几个参数都是consul集群用的,非集群模式可以不使用。 -p 8500:8500 \progrium/consul 镜像名称,本地没有就自动从公共docker库下载后面的都是consul的参数:-server 以服务节点启动-bootstrap-expect 3 预期的启动节点数3,最少是3,要不达不到cluster的效果-advertise 127.0.0.1 告诉集群,我的ip是什么,就是注册集群用的 使用docker-compose创建注册中心使用docker命令创建注册中心比较麻烦,并且不好维护,这里使用docker-compose来实现。docker-compose:compose是 Docker 容器进行编排的工具,定义和运行多容器的应用,可以一条命令启动多个容器,使用Docker Compose不再需要使用shell脚本来启动容器。 Compose 通过一个配置文件来管理多个Docker容器,在配置文件中,所有的容器通过services来定义,然后使用docker-compose脚本来启动,停止和重启应用,和应用中的服务以及所有依赖服务的容器,非常适合组合使用多个容器进行开发的场景。首先看下docker-compose配置文件: version: "3.0"services: # consul server,对外暴露的ui接口为8500,只有在2台consul服务器的情况下集群才起作用 consulserver: image: progrium/consul:latest hostname: consulserver ports: - "8300" - "8400" - "8500:8500" - "53" command: -server -ui-dir /ui -data-dir /tmp/consul --bootstrap-expect=3 # consul server1在consul server服务起来后,加入集群中 consulserver1: image: progrium/consul:latest hostname: consulserver1 depends_on: - "consulserver" ports: - "8300" - "8400" - "8500" - "53" command: -server -data-dir /tmp/consul -join consulserver # consul server2在consul server服务起来后,加入集群中 consulserver2: image: progrium/consul:latest hostname: consulserver2 depends_on: - "consulserver" ports: - "8300" - "8400" - "8500" - "53" command: -server -data-dir /tmp/consul -join consulserver这里consulserver,consulserver1等是服务名,image定义镜像名,depends_on定义依赖容器,command可以覆盖容器启动后默认命令。详细的配置项介绍及compose命令,可查看这里。下面进入模版目录,运行 ...

June 19, 2019 · 2 min · jiezi

React搭建个人博客二consultemplatenginxdocker实现负载均衡

一.简介上一篇只讲了博客的前端问题,这一篇讲一下后端的微服务搭建。项目的后端使用的thinkjs框架,在我之前的博客中已经写过,这里就不重点说明了。后端项目分为三个: 博客前台页面服务端:在这里。博客后台页面服务端:在这里。consul-template+nginx实现的基于微服务注册发现的负载均衡:在这里。前两个数据业务相关的服务即下图的service_web,第三个项目就是consul-template+nginx的实现的负载均衡。如果对consul基础概念不了解,建议读完我博客里这两篇文章再继续看下面的内容。consul+docker实现服务注册。consul+docker实现服务发现及网关。首先看下架构图: 架构图解析consul-template会订阅consul注册中心上的服务消息,当service-web改变时,consul注册中心会将新的service web信息推送给consul-template,consul-template会修改nginx配置文件,nginx重载入配置后,就达到了可以自动修改更新的负载均衡。 二.consul-template+nginx实现基于微服务的负载均衡consul-template 介绍Consul-Template是基于Consul的自动替换配置文件的应用。在Consul-Template没出现之前,大家构建服务发现系统大多采用的是Zookeeper、Etcd+Confd这样类似的系统。 使用场景:可以查询Consul中的服务目录、Key、Key-values等。这种强大的抽象功能和查询语言模板可以使Consul-Template特别适合动态的创建配置文件。例如:创建Apache/Nginx Proxy Balancers、Haproxy Backends、Varnish Servers、Application Configurations等。 代码介绍当consul注册中心的的服务改变时,consul-template会根据nginx-consul-template重新生成nginx.conf。首先看一下nginx.conf.ctmpl的代码: nginx.conf.ctmplupstream admin { {{range service "service-admin"}}server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=60 weight=1; {{else}}server 127.0.0.1:65535; # force a 502{{end}}}upstream app { {{range service "service-web"}}server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=60 weight=1; {{else}}server 127.0.0.1:65535; # force a 502{{end}}}server { listen 80 default_server;# 映射博客后台管理服务 location /admin{ proxy_pass http://admin/; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } #请求博客后台服务的静态资源 location ^~/static/blog-backend-react{ proxy_pass http://admin/static/blog-backend-react; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } #请求博客前台服务的静态资源 location ^~/static/blog-react{ proxy_pass http://app/static/blog-react; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 接口请求,映射博客前台服务 location /font{ proxy_pass http://app; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 接口请求,映射博客后台服务 location /api{ proxy_pass http://admin/api; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 默认映射到博客前端服务 location / { proxy_pass http://app; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }}template与正常nginx.conf区别就是upstream里部分,根据服务名——这里是两个服务,self-blog-backend博客后台管理服务与self-blog-fontend博客前台服务——动态生成服务对应的ip。template的反向代理部分,与正常nginx配置一致,因为有两个项目,所以每个url请求接口,静态资源,服务,都需要映射到特定的服务。服务启动后,根据模版生成的nginx配置文件如下: ...

June 19, 2019 · 3 min · jiezi

Consul-命令行最全文档

1.启动一个带ACL 控制的Agent首先,从这个网址下载consul,解压后发现就是个可执行文件,如果不可以执行,chmod +x consul 一下。 为了试验Consul较多的功能,这里我们打算启用一个dev模式,带ACL控制的Consul代理。配置文件config.json如下 { "datacenter":"dc1", "primary_datacenter":"dc1", "data_dir":"/opt/consul/data/", "enable_script_checks":false, "bind_addr":"127.0.0.1", "node_name":"consul-dev", "enable_local_script_checks":true, "log_file":"/opt/consul/log/", "log_level":"info", "log_rotate_bytes":100000000, "log_rotate_duration":"24h", "encrypt":"krCysDJnrQ8dtA7AbJav8g==", "acl":{ "enabled":true, "default_policy":"deny", "enable_token_persistence":true, "tokens":{ "master":"cd76a0f7-5535-40cc-8696-073462acc6c7" } }}下面是参数说明: datacenter 此标志表示代理运行的数据中心。如果未提供,则默认为“dc1”。 Consul拥有对多个数据中心的一流支持,但它依赖于正确的配置。同一数据中心中的节点应在同一个局域网内。primary_datacenter: 这指定了对ACL信息具有权威性的数据中心。必须提供它才能启用ACL。bind_addr: 内部群集通信绑定的地址。这是群集中所有其他节点都应该可以访问的IP地址。默认情况下,这是“0.0.0.0”,这意味着Consul将绑定到本地计算机上的所有地址,并将第一个可用的私有IPv4地址通告给群集的其余部分。如果有多个私有IPv4地址可用,Consul将在启动时退出并显示错误。如果指定“[::]”,Consul将通告第一个可用的公共IPv6地址。如果有多个可用的公共IPv6地址,Consul将在启动时退出并显示错误。 Consul同时使用TCP和UDP,并且两者使用相同的端口。如果您有防火墙,请务必同时允许这两种协议。advertise_addr: 更改我们向群集中其他节点通告的地址。默认情况下,会使用-bind参数指定的地址.server: 是否是server agent节点。connect.enabled: 是否启动Consul Connect,这里是启用的。node_name:节点名称。data_dir: agent存储状态的目录。enable_script_checks: 是否在此代理上启用执行脚本的健康检查。有安全漏洞,默认值就是false,这里单独提示下。enable_local_script_checks: 与enable_script_checks类似,但只有在本地配置文件中定义它们时才启用它们。仍然不允许在HTTP API注册中定义的脚本检查。log-file: 将所有Consul Agent日志消息重定向到文件。这里指定的是/opt/consul/log/目录。log_rotate_bytes:指定在需要轮换之前应写入日志的字节数。除非指定,否则可以写入日志文件的字节数没有限制log_rotate_duration:指定在需要旋转日志之前应写入日志的最长持续时间。除非另有说明,否则日志会每天轮换(24小时。单位可以是"ns", "us" (or "µs"), "ms", "s", "m", "h", 比如设置值为24hencrypt:用于加密Consul Gossip 协议交换的数据。在启动各个server之前,配置成同一个UUID值就行,或者你用命令行consul keygen 命令来生成也可以。acl.enabled: 是否启用acl.acl.default_policy: “allow”或“deny”; 默认为“allow”,但这将在未来的主要版本中更改。当没有匹配规则时,默认策略控制令牌的行为。在“allow”模式下,ACL是黑名单:允许任何未明确禁止的操作。在“deny”模式下,ACL是白名单:阻止任何未明确允许的操作.acl.enable_token_persistence: 可能值为true或者false。值为true时,API使用的令牌集合将被保存到磁盘,并且当代理重新启动时会重新加载。acl.tokens.master: 具有全局管理的权限,也就是最大的权限。它允许操作员使用众所周知的令牌密钥ID来引导ACL系统。需要在所有的server agent上设置同一个值,可以设置为一个随机的UUID。这个值权限最大,注意保管好。接着我们以dev模式启动agent。 ./consul agent -dev -config-file ./config.json 启动完之后,你会发现出现下面的错误这个错误是没有设置agent-token造成的,agent-token主要用于客户端和服务器执行内部操作.比如catalog api的更新,反熵同步等。 ...

June 9, 2019 · 3 min · jiezi

学习笔记关于consul-的理解

首先的首先,按照我最近一段时间的学习,推荐入门的几篇文章。然后这里记录下我自己在此基础上的理解0. 入门学习第一个 :初学通过这个先了解下 什么是 consul,知道一些基本的概念官网 :consul官网中文翻译知道一个全面的大概consul 可以用来做什么基于Docker + Consul + Nginx + Consul-Template的服务负载均衡实现应用到 spring boot 项目中注册中心 Consul 使用详解5. 实际项目介绍可以自己公司的一个实际项目。 todo

May 7, 2019 · 1 min · jiezi

用 Consul 来做服务注册与服务发现

服务注册与服务发现是在分布式服务架构中常常会涉及到的东西,业界常用的服务注册与服务发现工具有 ZooKeeper、etcd、Consul 和 Eureka。Consul 的主要功能有服务发现、健康检查、KV存储、安全服务沟通和多数据中心。Consul 与其他几个工具的区别可以在这里查看 Consul vs. Other Software。为什么需要有服务注册与服务发现?假设在分布式系统中有两个服务 Service-A (下文以“S-A”代称)和 Service-B(下文以“S-B”代称),当 S-A 想调用 S-B 时,我们首先想到的时直接在 S-A 中请求 S-B 所在服务器的 IP 地址和监听的端口,这在服务规模很小的情况下是没有任何问题的,但是在服务规模很大每个服务不止部署一个实例的情况下是存在一些问题的,比如 S-B 部署了三个实例 S-B-1、S-B-2 和 S-B-3,这时候 S-A 想调用 S-B 该请求哪一个服务实例的 IP 呢?还是将3个服务实例的 IP 都写在 S-A 的代码里,每次调用 S-B 时选择其中一个 IP?这样做显得很不灵活,这时我们想到了 Nginx 刚好就能很好的解决这个问题,引入 Nginx 后现在的架构变成了如下图这样:引入 Nginx 后就解决了 S-B 部署多个实例的问题,还做了 S-B 实例间的负载均衡。但现在的架构又面临了新的问题,分布式系统往往要保证高可用以及能做到动态伸缩,在引入 Nginx 的架构中,假如当 S-B-1 服务实例不可用时,Nginx 仍然会向 S-B-1 分配请求,这样服务就不可用,我们想要的是 S-B-1 挂掉后 Nginx 就不再向其分配请求,以及当我们新部署了 S-B-4 和 S-B-5 后,Nginx 也能将请求分配到 S-B-4 和 S-B-5,Nginx 要做到这样就要在每次有服务实例变动时去更新配置文件再重启 Nginx。这样看似乎用了 Nginx 也很不舒服以及还需要人工去观察哪些服务有没有挂掉,Nginx 要是有对服务的健康检查以及能够动态变更服务配置就是我们想要的工具,这就是服务注册与服务发现工具的用处。下面是引入服务注册与服务发现工具后的架构图:在这个架构中:首先 S-B 的实例启动后将自身的服务信息(主要是服务所在的 IP 地址和端口号)注册到注册工具中。不同注册工具服务的注册方式各不相同,后文会讲 Consul 的具体注册方式。服务将服务信息注册到注册工具后,注册工具就可以对服务做健康检查,以此来确定哪些服务实例可用哪些不可用。S-A 启动后就可以通过服务注册和服务发现工具获取到所有健康的 S-B 实例的 IP 和端口,并将这些信息放入自己的内存中,S-A 就可用通过这些信息来调用 S-B。S-A 可以通过监听(Watch)注册工具来更新存入内存中的 S-B 的服务信息。比如 S-B-1 挂了,健康检查机制就会将其标为不可用,这样的信息变动就被 S-A 监听到了,S-A 就更新自己内存中 S-B-1 的服务信息。所以务注册与服务发现工具除了服务本身的服务注册和发现功能外至少还需要有健康检查和状态变更通知的功能。ConsulConsul 作为一种分布式服务工具,为了避免单点故障常常以集群的方式进行部署,在 Consul 集群的节点中分为 Server 和 Client 两种节点(所有的节点也被称为Agent),Server 节点保存数据,Client 节点负责健康检查及转发数据请求到 Server;Server 节点有一个 Leader 节点和多个 Follower 节点,Leader 节点会将数据同步到 Follower 节点,在 Leader 节点挂掉的时候会启动选举机制产生一个新的 Leader。Client 节点很轻量且无状态,它以 RPC 的方式向 Server 节点做读写请求的转发,此外也可以直接向 Server 节点发送读写请求。下面是 Consul 的架构图:Consule 的安装和具体使用及其他详细内容可浏览官方文档。下面是我用 Docker 的方式搭建了一个有3个 Server 节点和1个 Client 节点的 Consul 集群。# 这是第一个 Consul 容器,其启动后的 IP 为172.17.0.5docker run -d –name=c1 -p 8500:8500 -e CONSUL_BIND_INTERFACE=eth0 consul agent –server=true –bootstrap-expect=3 –client=0.0.0.0 -uidocker run -d –name=c2 -e CONSUL_BIND_INTERFACE=eth0 consul agent –server=true –client=0.0.0.0 –join 172.17.0.5docker run -d –name=c3 -e CONSUL_BIND_INTERFACE=eth0 consul agent –server=true –client=0.0.0.0 –join 172.17.0.5#下面是启动 Client 节点docker run -d –name=c4 -e CONSUL_BIND_INTERFACE=eth0 consul agent –server=false –client=0.0.0.0 –join 172.17.0.5启动容器时指定的环境变量 CONSUL_BIND_INTERFACE 其实就是相当于指定了 Consul 启动时 –bind 变量的参数,比如可以把启动 c1 容器的命令换成下面这样,也是一样的效果。docker run -d –name=c1 -p 8500:8500 -e consul agent –server=true –bootstrap-expect=3 –client=0.0.0.0 –bind=’{{ GetInterfaceIP “eth0” }}’ -ui操作 Consul 有 Commands 和 HTTP API 两种方式,进入任意一个容器执行 consul members 都可以有如下的输出,说明 Consul 集群就已经搭建成功了。Node Address Status Type Build Protocol DC Segment2dcf0c824cf0 172.17.0.7:8301 alive server 1.4.4 2 dc1 <all>64746cffa116 172.17.0.6:8301 alive server 1.4.4 2 dc1 <all>77af7d94a8ca 172.17.0.5:8301 alive server 1.4.4 2 dc1 <all>6c71148f0307 172.17.0.8:8301 alive client 1.4.4 2 dc1 <default>代码实践假设现在有一个用 Node.js 写的服务 node-server 需要通过 gRPC 的方式调用一个用 Go 写的服务 go-server。下面是用 Protobuf 定义的服务和数据类型文件 hello.proto。syntax = “proto3”;package hello;option go_package = “hello”;// The greeter service definition.service Greeter { // Sends a greeting rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}}// The request message containing the user’s name.message HelloRequest { string name = 1;}// The response message containing the greetingsmessage HelloReply { string message = 1;}用命令通过 Protobuf 的定义生成 Go 语言的代码:protoc –go_out=plugins=grpc:./hello ./*.proto 会在 hello 目录下得到 hello.pb.go 文件,然后在 hello.go 文件中实现我们定义的 RPC 服务。// hello.gopackage helloimport “context"type GreeterServerImpl struct {}func (g *GreeterServerImpl) SayHello(c context.Context, h *HelloRequest) (*HelloReply, error) { result := &HelloReply{ Message: “hello” + h.GetName(), } return result, nil}下面是入口文件 main.go,主要是将我们定义的服务注册到 gRPC 中,并建了一个 /ping 接口用于之后 Consul 的健康检查。package mainimport ( “go-server/hello” “google.golang.org/grpc” “net” “net/http”)func main() { lis1, _ := net.Listen(“tcp”, “:8888”) lis2, _ := net.Listen(“tcp”, “:8889”) grpcServer := grpc.NewServer() hello.RegisterGreeterServer(grpcServer, &hello.GreeterServerImpl{}) go grpcServer.Serve(lis1) go grpcServer.Serve(lis2) http.HandleFunc("/ping”, func(res http.ResponseWriter, req *http.Request){ res.Write([]byte(“pong”)) }) http.ListenAndServe(":8080", nil)}至此 go-server 端的代码就全部编写完了,可以看出代码里面没有任何涉及到 Consul 的地方,用 Consul 做服务注册是可以做到对项目代码没有任何侵入性的。下面要做的是将 go-server 注册到 Consul 中。将服务注册到 Consul 可以通过直接调用 Consul 提供的 REST API 进行注册,还有一种对项目没有侵入的配置文件进行注册。Consul 服务配置文件的详细内容可以在此查看。下面是我们通过配置文件进行服务注册的配置文件 services.json:{ “services”: [ { “id”: “hello1”, “name”: “hello”, “tags”: [ “primary” ], “address”: “172.17.0.9”, “port”: 8888, “checks”: [ { “http”: “http://172.17.0.9:8080/ping”, “tls_skip_verify”: false, “method”: “GET”, “interval”: “10s”, “timeout”: “1s” } ] },{ “id”: “hello2”, “name”: “hello”, “tags”: [ “second” ], “address”: “172.17.0.9”, “port”: 8889, “checks”: [ { “http”: “http://172.17.0.9:8080/ping”, “tls_skip_verify”: false, “method”: “GET”, “interval”: “10s”, “timeout”: “1s” } ] } ]}配置文件中的 172.17.0.9 代表的是 go-server 所在服务器的 IP 地址,port 就是服务监听的不同端口,check 部分定义的就是健康检查,Consul 会每隔 10秒钟请求一下 /ping 接口以此来判断服务是否健康。将这个配置文件复制到 c4 容器的 /consul/config 目录,然后执行consul reload 命令后配置文件中的 hello 服务就注册到 Consul 中去了。通过在宿主机执行curl http://localhost:8500/v1/catalog/services?pretty就能看到我们注册的 hello 服务。下面是 node-server 服务的代码:const grpc = require(‘grpc’);const axios = require(‘axios’);const protoLoader = require(’@grpc/proto-loader’);const packageDefinition = protoLoader.loadSync( ‘./hello.proto’, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true });const hello_proto = grpc.loadPackageDefinition(packageDefinition).hello;function getRandNum (min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min;};const urls = []async function getUrl() { if (urls.length) return urls[getRandNum(0, urls.length-1)]; const { data } = await axios.get(‘http://172.17.0.5:8500/v1/health/service/hello’); for (const item of data) { for (const check of item.Checks) { if (check.ServiceName === ‘hello’ && check.Status === ‘passing’) { urls.push(${item.Service.Address}:${item.Service.Port}) } } } return urls[getRandNum(0, urls.length - 1)];}async function main() { const url = await getUrl(); const client = new hello_proto.Greeter(url, grpc.credentials.createInsecure()); client.sayHello({name: ‘jack’}, function (err, response) { console.log(‘Greeting:’, response.message); }); }main()代码中 172.17.0.5 地址为 c1 容器的 IP 地址,node-server 项目中直接通过 Consul 提供的 API 获得了 hello 服务的地址,拿到服务后我们需要过滤出健康的服务的地址,再随机从所有获得的地址中选择一个进行调用。代码中没有做对 Consul 的监听,监听的实现可以通过不断的轮询上面的那个 API 过滤出健康服务的地址去更新 urls 数组来做到。现在启动 node-server 就可以调用到 go-server 服务。服务注册与发现给服务带来了动态伸缩的能力,也给架构增加了一定的复杂度。Consul 除了服务发现与注册外,在配置中心、分布式锁方面也有着很多的应用。 ...

April 1, 2019 · 4 min · jiezi

spring cloud consul注册的服务有报错 critical

测试spring cloud 使用consul注册服务的时候,出现critical,如下:怎么解决这个问题,现在只能看到health check检查失败了。受限调用这个请求Get http://consulIp:8500/v1/agent/checks,调完请求,就会拿到返回数据:{ …… “service:test-service-xx-xx-xx-xx”: { “Node”: “zookeeper-server1”, “CheckID”: “service:test-service-xx-xx-xx-xx”, “Name”: “Service ’test-service’ check”, “Status”: “critical”, “Notes”: “”, “Output”: “HTTP GET http://xxx.xx.xxx.xxx:19008/actuator/health: 404 Output: <html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id=‘created’>Fri Mar 15 11:03:30 CST 2019</div><div>There was an unexpected error (type=Not Found, status=404).</div><div>No message available</div></body></html>”, “ServiceID”: “test-service-xx-xx-xx-xx”, “ServiceName”: “test-service”, “ServiceTags”: [ “version=1.0”, “secure=false” ], “Definition”: {}, “CreateIndex”: 0, “ModifyIndex”: 0 } ……..}就能看到consul调用http://xxx.xx.xxx.xxx:19008/actuator/health来检查servoce健康,却发现接口404,所以才会在页面出现错误。我的测试环境是:spring cloud Finch1ey.SR2consul v1.4.3bootstrap.yml配置是:spring: cloud: consul: host: xxx.xxx.xxx.xxx port: 8500 discovery: prefer-ip-address: true tags: version=1.0 instance-id: ${spring.application.name}:${spring.cloud.client.ip-address} healthCheckInterval: 15s health-check-url: http://${spring.cloud.client.ip-address}:${server.port}/actuator/health显然consul不能在这个服务上找到actuator/health接口,因为我用了actuator,所以service中应该配置了spring cloud actuator。经过检查发现没有配置,所以actuator这个端点不能使用,加上这个包,问题就解决了。 ...

March 15, 2019 · 1 min · jiezi

spring cloud consul使用ip注册服务

我测试spring cliud使用consul作为注册中心的时候,发现服务注册的时候,注册的都是hostname,比如:注册了一个commonservice,在consul中是这样的:{ “ID”:“commonservice123”, “address”:“testcommonserver” ……..}这肯定是不对的。加入我有一个服务payservice需要调用commonservice,payservice从consul中获取的commonservice的地址是testcommonserver,而payservice所在的服务器地址是121.57.68.98上,这台服务器无法解析hostname是testcommonserver的服务器的ip地址,无法调用commonservie,这时候就会报下面这个错误:unKnownHostException…….为了解决这个问题,我需要在注册服务的时候,让服务以ip的方式注册,我的测试环境是:spring cloud Finch1ey.SR2consul v1.4.3修改bootstrap.yml配置文件:spring: cloud: consul: host: xxx.xxx.xxx.xxxx port: 8500 discovery: prefer-ip-address: true //这个必须配 tags: version=1.0 instance-id: ${spring.application.name}:${spring.cloud.client.ip-address} healthCheckInterval: 15s health-check-url: http://${spring.cloud.client.ip-address}:${server.port}/actuator/health${spring.cloud.client.ip-address}这个属性是spring cloud内置,用来获取ip,不同的spring cloud版本可能稍有不同,如果想要确定自己的版本是什么样的,可以查看这个文件:HostInfoEnvironmentPostProcessor @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { InetUtils.HostInfo hostInfo = getFirstNonLoopbackHostInfo(environment); LinkedHashMap<String, Object> map = new LinkedHashMap<>(); map.put(“spring.cloud.client.hostname”, hostInfo.getHostname()); map.put(“spring.cloud.client.ip-address”, hostInfo.getIpAddress()); MapPropertySource propertySource = new MapPropertySource( “springCloudClientHostInfo”, map); environment.getPropertySources().addLast(propertySource); }这时候再启动项目测试,发现注册地址变了:{ “ID”:“commonservice123”, “address”:“10.52.xx.xx” ……..}注册的address变成了服务的内网地址,如果其它服务和commonservice在同一个网络中,可以通过内网访问,这样也是可以的,但是如果内网不能访问,其它服务仍然不能访问,这时候就需要注册服务的时候以公网的ip注册才行。修改bootstrap.yml配置文件:spring: cloud: consul: host: xxx.xxx.xxx.xxx port: 8500 config: data-key: data format: yaml discovery: prefer-ip-address: true //这个必须配 tags: version=1.0 instance-id: ${spring.application.name}:${spring.cloud.client.ip-address} healthCheckInterval: 15s health-check-url: http://${spring.cloud.client.ip-address}:${server.port}/actuator/health inetutils: preferred-networks: - 公网ip1 - 公网ip2可以看到增加一个inetutils配置,这个配置是spring cloud的网络工具类,这个配置的含义是如果获取ip时获取到多个ip(内网、外网),就优先选择我配置的ip中存在的ip,这样再测试就会发现,注册service的时候就变成了公网ip。 ...

March 15, 2019 · 1 min · jiezi

用consul做grpc的服务发现

用consul做grpc的服务发现与健康检查consul服务发现与负载均衡当server端是集群部署时,client调用server就需要用到服务发现与负载均衡。通常有两总方式:一种方式是在client与server之间加代理,由代理来做负载均衡一种方式是将服务注册到一个数据中心,client通过数据中心查询到所有服务的节点信息,然后自己选择负载均衡的策略。第一种方式常见的就是用nginx给http服务做负载均衡,client端不直接与server交互,而是把请求并给nginx,nginx再转给后端的服务。这种方式的优点是:client和server无需做改造,client看不到server的集群,就像单点一样调用就可以这种方式有几个缺点:所有的请求都必须经过代理,代理侧容易出现性能瓶颈代理不能出故障,一旦代理挂了服务就没法访问了。第二种方式可以参考dubbo的rpc方式,所有的服务都注册在zookeeper上,client端从zookeeper订阅server的列表,然后自己选择把请求发送到哪个server上。对于上面提到的两个缺点,这种方式都很好的避免了:client与server端是直接交互的,server可以做任意的水平扩展,不会出现性能瓶颈注册中心(zookeeper)通过raft算法实现分布式高可用,不用担心注册中心挂了服务信息丢失的情况。这种方式的缺点就是实现起来比较复杂。用第一种方式做grpc的负载均衡时可以有以下的选择:nginx grpctraefik grpc用第二种方式时,可以选择的数据中心中间件有:zookeeperetcd Etcd是Kubernetes集群中的一个十分重要的组件consul他们都实现了raft算法,都可以用来做注册中心,本篇文章选择consul是因为consul的特点就是做服务发现,有现成的api可以用。用consul给golang的grpc做服务注册与发现grpc的resolvergrpc的Dial()和DialContent()方法中都可以添加Load-Balance的选项,Dial方法已经被废弃了,本篇文章介绍使用DialContext的方法。grpc官方实现了dns_resolver用来做dns的负载均衡。我们通过例子看看grpc client端的代码是怎么写的,然后再理解dns_resolver的源码,最后参照dns_resolver来写自己的consul_resovler。dns的负载均衡的例子:package mainimport ( “context” “log” “google.golang.org/grpc” “google.golang.org/grpc/balancer/roundrobin” pb “google.golang.org/grpc/examples/helloworld/helloworld” “google.golang.org/grpc/resolver”)const ( address = “dns:///dns-record-name:443” defaultName = “world”)func main() { // The secret sauce resolver.SetDefaultScheme(“dns”) // Set up a connection to the server. ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) conn, err := grpc.DialContext(ctx, address, grpc.WithInsecure(), grpc.WithBalancerName(roundrobin.Name)) if err != nil { log.Fatalf(“did not connect: %v”, err) } defer conn.Close() c := pb.NewGreeterClient(conn) // Contact the servers in round-robin manner. for i := 0; i < 3; i++ { ctx := context.Background() r, err := c.SayHello(ctx, &pb.HelloRequest{Name: defaultName}) if err != nil { log.Fatalf(“could not greet: %v”, err) } log.Printf(“Greeting: %s”, r.Message) }}DialContext的定义如下:func DialContext(ctx context.Context, target string, opts …DialOption) (conn *ClientConn, err error)下面这行代码指明了用dns_resolver,实际上也可以不写,grpc会根据DialContext的第二个参数target来判断选用哪个resolver,例子中传给DialContext的target是 dns:///dns-record-name:443,grpc会自动选择dns_resolverresolver.SetDefaultScheme(“dns”)下面的这个选项,指明了grpc用轮询做为负载均衡的策略grpc.WithBalancerName(roundrobin.Name)调用grpc.DialContext之后,grpc会找到对应的resovler,拿到服务的地址列表,然后在调用服务提供的接口时,根据指定的轮询策略选择一个服务。gRPC Name Resolution里面说了,可以实现自定义的resolver作为插件。先看看resolver.go的源码,源码路径是$GOPATH/src/google.golang.org/grpc/resolver/resolver.gom = make(map[string]Builder) //scheme到Builder的mapfunc Register(b Builder) { //用于resolver注册的接口,dns_resolver.go的init方中调用了这个方法,实际就是更新了map m[b.Scheme()] = b}type Resolver interface { ResolveNow(ResolveNowOption) //立即resolve,重新查询服务信息 Close() //关闭这个Resolver}type Target struct {//uri解析之后的对象, uri的格式详见RFC3986 Scheme string Authority string Endpoint string}type Address struct {//描述一个服务的地址信息 Addr string //格式是 host:port Type AddressType ServerName string Metadata interface{}}type ClientConn interface {//定义了两个callback函数,用于通知服务信息的更新 NewAddress(addresses []Address) NewServiceConfig(serviceConfig string)}type Builder interface { Build(target Target, cc ClientConn, opts BuildOption) (Resolver, error) //返回一个Resolver Scheme() string //返回scheme如 “dns”, “passthrough”, “consul”}func Get(scheme string) Builder { //grpc.ClientConn会高用这个方法获取指定的Builder接口的实例 if b, ok := m[scheme]; ok { return b } return nil}即使加了注释,估计也很难马上理解这个其中的具体含意,博主也是结合dns_resolver.go,反复读了好几遍才理解resolver.go。其大致的意思是,grpc.DialContext方法调用之后:解析target(例如dns:///dns-record-name:443)获取scheme调用resolver.Get方法根据scheme拿到对应的Builder调用Builder.Build方法解析target获取服务地址的信息调用ClientConn.NewAddress和NewServiceConfig这两个callback把服务信息传递给上层的调用方返回Resolver接口实例给上层上层可以通过Resolver.ResolveNow方法主动刷新服务信息了解了resolver源码的意思之后,再看一下dns_resolver.go就比较清晰了//注册一个Builder到resolver的map里面//这个方法会被默认调用,了解go的init可以自行百度func init() { resolver.Register(NewBuilder())}func NewBuilder() resolver.Builder {//创建一个resolver.Builder的实例 return &dnsBuilder{minFreq: defaultFreq}}func (b *dnsBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) { //解析target拿到ip和端口 host, port, err := parseTarget(target.Endpoint, defaultPort) if err != nil { return nil, err } // IP address. if net.ParseIP(host) != nil { host, _ = formatIP(host) addr := []resolver.Address{{Addr: host + “:” + port}} i := &ipResolver{ cc: cc, ip: addr, rn: make(chan struct{}, 1), q: make(chan struct{}), } cc.NewAddress(addr) go i.watcher() return i, nil } // DNS address (non-IP). ctx, cancel := context.WithCancel(context.Background()) d := &dnsResolver{ freq: b.minFreq, backoff: backoff.Exponential{MaxDelay: b.minFreq}, host: host, port: port, ctx: ctx, cancel: cancel, cc: cc, t: time.NewTimer(0), rn: make(chan struct{}, 1), disableServiceConfig: opts.DisableServiceConfig, } if target.Authority == "" { d.resolver = defaultResolver } else { d.resolver, err = customAuthorityResolver(target.Authority) if err != nil { return nil, err } } d.wg.Add(1) go d.watcher()//起一个goroutine,因为watcher这个方法是个死循环,当定时器 return d, nil}func (d *dnsResolver) watcher() { defer d.wg.Done() for { //这个select没有default,当没有case满足时会一直阻塞 //结束阻塞的条件是定时器超时d.t.C,或者d.rn这个channel中有数据可读 select { case <-d.ctx.Done(): return case <-d.t.C: case <-d.rn: } result, sc := d.lookup() // Next lookup should happen within an interval defined by d.freq. It may be // more often due to exponential retry on empty address list. if len(result) == 0 { d.retryCount++ d.t.Reset(d.backoff.Backoff(d.retryCount)) } else { d.retryCount = 0 d.t.Reset(d.freq) } //resolver.ClientConn的两个callback的调用,实现服务信息传入上层 d.cc.NewServiceConfig(sc) d.cc.NewAddress(result) }}//向channel中写入,用于结束watcher中那个select的阻塞状态,后面的代码就是重新查询服务信息的逻辑func (i *ipResolver) ResolveNow(opt resolver.ResolveNowOption) { select { case i.rn <- struct{}{}: default: }}实现consul_resovler上面我们了解了grpc的resolver的机制,接下来实现consul_resolver, 我们先把代码的架子搭起来init() //返回一个resolver.Builder的实例//实现resolver.Builder的接口中的所有方法就是一个resolver.Buildertype consulBuidler strcut {}func (cb *consulBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) { //TODO 解析target, 拿到consul的ip和端口 //TODO 用consul的go api连接consul,查询服务结点信息,并且调用resolver.ClientConn的两个callback}func (cb *consulBuilder) Scheme() string { return “consul”}//ResolverNow方法什么也不做,因为和consul保持了发布订阅的关系//不需要像dns_resolver那个定时的去刷新func (cr *consulResolver) ResolveNow(opt resolver.ResolveNowOption) {}//暂时先什么也不做吧func (cr *consulResolver) Close() {}现在来看,实现consul_resolver.go最大的问题就是怎么用consul提供的go api了,参考这篇文章就可以了,然后consul_resolver.go的代码就出来了package consulimport ( “errors” “fmt” “github.com/hashicorp/consul/api” “google.golang.org/grpc/resolver” “regexp” “sync”)const ( defaultPort = “8500”)var ( errMissingAddr = errors.New(“consul resolver: missing address”) errAddrMisMatch = errors.New(“consul resolver: invalied uri”) errEndsWithColon = errors.New(“consul resolver: missing port after port-separator colon”) regexConsul, _ = regexp.Compile("^([A-z0-9.]+)(:[0-9]{1,5})?/([A-z_]+)$"))func Init() { fmt.Printf(“calling consul init\n”) resolver.Register(NewBuilder())}type consulBuilder struct {}type consulResolver struct { address string wg sync.WaitGroup cc resolver.ClientConn name string disableServiceConfig bool lastIndex uint64}func NewBuilder() resolver.Builder { return &consulBuilder{}}func (cb *consulBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) { fmt.Printf(“calling consul build\n”) fmt.Printf(“target: %v\n”, target) host, port, name, err := parseTarget(fmt.Sprintf("%s/%s", target.Authority, target.Endpoint)) if err != nil { return nil, err } cr := &consulResolver{ address: fmt.Sprintf("%s%s", host, port), name: name, cc: cc, disableServiceConfig: opts.DisableServiceConfig, lastIndex: 0, } cr.wg.Add(1) go cr.watcher() return cr, nil}func (cr *consulResolver) watcher() { fmt.Printf(“calling consul watcher\n”) config := api.DefaultConfig() config.Address = cr.address client, err := api.NewClient(config) if err != nil { fmt.Printf(“error create consul client: %v\n”, err) return } for { services, metainfo, err := client.Health().Service(cr.name, cr.name, true, &api.QueryOptions{WaitIndex: cr.lastIndex}) if err != nil { fmt.Printf(“error retrieving instances from Consul: %v”, err) } cr.lastIndex = metainfo.LastIndex var newAddrs []resolver.Address for _, service := range services { addr := fmt.Sprintf("%v:%v", service.Service.Address, service.Service.Port) newAddrs = append(newAddrs, resolver.Address{Addr: addr}) } fmt.Printf(“adding service addrs\n”) fmt.Printf(“newAddrs: %v\n”, newAddrs) cr.cc.NewAddress(newAddrs) cr.cc.NewServiceConfig(cr.name) }}func (cb *consulBuilder) Scheme() string { return “consul”}func (cr *consulResolver) ResolveNow(opt resolver.ResolveNowOption) {}func (cr *consulResolver) Close() {}func parseTarget(target string) (host, port, name string, err error) { fmt.Printf(“target uri: %v\n”, target) if target == "" { return “”, “”, “”, errMissingAddr } if !regexConsul.MatchString(target) { return “”, “”, “”, errAddrMisMatch } groups := regexConsul.FindStringSubmatch(target) host = groups[1] port = groups[2] name = groups[3] if port == "" { port = defaultPort } return host, port, name, nil}到此,grpc客户端服务发现就搞定了。consul的服务注册服务注册直接用consul的go api就可以了,也是参考前一篇文章,简单的封装一下,consul_register.go的代码如下:package consulimport ( “fmt” “github.com/hashicorp/consul/api” “time”)type ConsulService struct { IP string Port int Tag []string Name string}func RegitserService(ca string, cs *ConsulService) { //register consul consulConfig := api.DefaultConfig() consulConfig.Address = ca client, err := api.NewClient(consulConfig) if err != nil { fmt.Printf(“NewClient error\n%v”, err) return } agent := client.Agent() interval := time.Duration(10) * time.Second deregister := time.Duration(1) * time.Minute reg := &api.AgentServiceRegistration{ ID: fmt.Sprintf("%v-%v-%v", cs.Name, cs.IP, cs.Port), // 服务节点的名称 Name: cs.Name, // 服务名称 Tags: cs.Tag, // tag,可以为空 Port: cs.Port, // 服务端口 Address: cs.IP, // 服务 IP Check: &api.AgentServiceCheck{ // 健康检查 Interval: interval.String(), // 健康检查间隔 GRPC: fmt.Sprintf("%v:%v/%v", cs.IP, cs.Port, cs.Name), // grpc 支持,执行健康检查的地址,service 会传到 Health.Check 函数中 DeregisterCriticalServiceAfter: deregister.String(), // 注销时间,相当于过期时间 }, } fmt.Printf(“registing to %v\n”, ca) if err := agent.ServiceRegister(reg); err != nil { fmt.Printf(“Service Register error\n%v”, err) return }}改造一下grpc的helloworld把grpc的helloworld的demo改一下,用consul来做服务注册和发现。server端代码:package mainimport ( “context” “fmt” “google.golang.org/grpc” “google.golang.org/grpc/health/grpc_health_v1” “log” “net” “server/internal/consul” pb “server/proto/helloworld”)const ( port = “:50051”)// server is used to implement helloworld.GreeterServer.type server struct{}// SayHello implements helloworld.GreeterServerfunc (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { log.Printf(“Received: %v”, in.Name) return &pb.HelloReply{Message: “Hello " + in.Name}, nil}func RegisterToConsul() { consul.RegitserService(“127.0.0.1:8500”, &consul.ConsulService{ Name: “helloworld”, Tag: []string{“helloworld”}, IP: “127.0.0.1”, Port: 50051, })}//healthtype HealthImpl struct{}// Check 实现健康检查接口,这里直接返回健康状态,这里也可以有更复杂的健康检查策略,比如根据服务器负载来返回func (h *HealthImpl) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) { fmt.Print(“health checking\n”) return &grpc_health_v1.HealthCheckResponse{ Status: grpc_health_v1.HealthCheckResponse_SERVING, }, nil}func (h *HealthImpl) Watch(req grpc_health_v1.HealthCheckRequest, w grpc_health_v1.Health_WatchServer) error { return nil}func main() { lis, err := net.Listen(“tcp”, port) if err != nil { log.Fatalf(“failed to listen: %v”, err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) grpc_health_v1.RegisterHealthServer(s, &HealthImpl{}) RegisterToConsul() if err := s.Serve(lis); err != nil { log.Fatalf(“failed to serve: %v”, err) }}client端代码:package mainimport ( “client/internal/consul” pb “client/proto/helloworld” “context” “google.golang.org/grpc” “log” “os” “time”)const ( target = “consul://127.0.0.1:8500/helloworld” defaultName = “world”)func main() { consul.Init() // Set up a connection to the server. ctx, _ := context.WithTimeout(context.Background(), 5time.Second) conn, err := grpc.DialContext(ctx, target, grpc.WithBlock(), grpc.WithInsecure(), grpc.WithBalancerName(“round_robin”)) if err != nil { log.Fatalf(“did not connect: %v”, err) } defer conn.Close() c := pb.NewGreeterClient(conn) // Contact the server and print out its response. name := defaultName if len(os.Args) > 1 { name = os.Args[1] } for { ctx, _ := context.WithTimeout(context.Background(), time.Second) r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name}) if err != nil { log.Fatalf(“could not greet: %v”, err) } log.Printf(“Greeting: %s”, r.Message) time.Sleep(time.Second * 2) }}运行一把启动consulconsul agent -dev启动hello servercd servergo run cmd/main.go启动hello clientcd clientgo run cmd/main.go运行结果://client2019/03/07 17:22:04 Greeting: Hello world2019/03/07 17:22:06 Greeting: Hello world//server2019/03/07 17:22:04 Received: world2019/03/07 17:22:06 Received: world完整工程的git地址工程使用方法:cd servergo mod tidygo run cmd/main.gocd clientgo mod tidygo run cmd/main.go请自行解决防火墙的问题参考文章grpc 名称发现与负载均衡golang consul-grpc 服务注册与发现gRPC Client-Side Load Balancing in Gogodoc grpcconsul go api ...

March 7, 2019 · 6 min · jiezi

laravel接入Consul

配置文件:return [ ‘url’ => ‘http://127.0.0.1:8500/v1/kv/’,];命令行:namespace App\Console\Commands\Consul;use GuzzleHttp\Client;use Illuminate\Console\Command;class DeamonCommand extends Command{ /** * The name and signature of the console command. * * @var string / protected $signature = ‘ue:consul:deamon { path : 路径 } ‘; protected $items = []; /* * The console command description. * * @var string / protected $description = ‘consul 守护进程’; /* * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); } public function handle() { $path = trim($this->argument(‘path’), ‘/’); $url = rtrim(config(‘consul.url’), ‘/’) . ‘/’ . $path . ‘/?recurse’; $client = new Client(); try { $response = $client->request(‘GET’, $url); $rows = json_decode($response->getBody()->getContents(), true); if (count($rows) == 1) { return true; } $name = null; foreach ($rows as $key => $row) { if (substr($row[‘Key’], -1) == ‘/’) { $name = substr($row[‘Key’], 0, -1); $this->items[$name] = []; continue; } $key = trim(str_replace($name, ’ ‘, $row[‘Key’]), ’ /’); $this->items[$name][$key] = base64_decode($row[‘Value’]); } $this->doSave(); } catch (\Exception $ex) { $this->error($ex->getMessage()); return true; } } protected function doSave() { foreach ($this->items as $path => $item) { if (!$item) { $this->error($path .’ : Is Empty.’); continue; } $content = [ “<?php”, “return”, var_export($item, true) . ‘;’ ]; try { \Storage::disk(‘config’)->put($path . ‘.php’, implode("\r\n", $content)); $this->line($path . ’ : Write Success.’); } catch (\Exception $ex) { $this->error($path .’ : ’ .$ex->getMessage()); } } }} ...

February 28, 2019 · 2 min · jiezi

grpc和consul结合实现分布式rpc调用

GRPC主要介绍了grpc在使用示例和原理,以及如何与consul结合gRPC 是什么?gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得我们能够更容易地创建分布式应用和服务。参考文档:gRPC Python Quickstart开始前确保已经安装grpcio-tools和grpcio这两个包定义一个GRPC有如下三个步骤:定义一个消息类型编译该proto文件编写服务端代码编写客户端代码我们以实现一个echo的grpc为例。定义一个消息类型首先定义通信双方(即客户端和服务端)交互的消息格式(protobuf消息的格式),然后定义该echo服务如下:syntax = “proto3”; // 声明使用 proto3 语法// 定义客户端请求的protobuf格式,如下所示,包含一个字符串字段qmessage Req { string q = 1;}// 定义服务端相应的protobuf格式,如下所示,包含一个字符串字段amessage Resp { string a = 1;}// 定义echo服务,如下所示,该服务包含一个名称为"echo"的rpcservice Echoer{ rpc echo (Req) returns (Resp) {}}使用以下命令编译:python -m grpc_tools.protoc -I./ –python_out=. –grpc_python_out=. ./Echoer.proto生成两个py文件Echoer_pb2.py 此文件包含生成的 request(Req) 和 response(Resp) 类。Echoer_pb2_grpc.py 此文件包含生成的 客户端(EchoerStub)和服务端(EchoerServicer)的类创建服务端代码创建和运行 Echoer 服务可以分为两个部分:实现我们服务定义的生成的服务接口:做我们的服务的实际的“工作”的函数。运行一个 gRPC 服务器,监听来自客户端的请求并传输服务的响应。在当前目录,创建文件 Echoer_server.py,实现一个新的函数:from concurrent import futuresimport timeimport grpcimport Echoer_pb2import Echoer_pb2_grpc_ONE_DAY_IN_SECONDS = 60 * 60 * 24class Echoer(Echoer_pb2_grpc.EchoerServicer): # 工作函数 def SayHello(self, request, context): return Echoer_pb2.Resp(a=“echo”)def serve(): # gRPC 服务器 server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) Echoer_pb2_grpc.add_EchoerServicer_to_server(Echoer(), server) server.add_insecure_port(’[::]:50051’) server.start() # start() 不会阻塞,如果运行时你的代码没有其它的事情可做,你可能需要循环等待。 try: while True: time.sleep(_ONE_DAY_IN_SECONDS) except KeyboardInterrupt: server.stop(0)if name == ‘main’: serve()创建客户端代码在当前目录,打开文件 Echoer_client.py,实现一个新的函数:from future import print_functionimport grpcimport Echoer_pb2import Echoer_pb2_grpcdef run(): channel = grpc.insecure_channel(’localhost:50051’) # 创建信道 stub = Echoer_pb2_grpc.EchoerStub(channel) # 通过信道获取凭据,即Stub response = stub.echo(Echoer_pb2.Req(q=‘echo’)) # 调用rpc,获取响应 print(“Echoer client received: " + response.a)if name == ‘main’: run()运行代码首先运行服务端代码python Echoer_server.py复制代码然后运行客户端代码python Echoer_client.py# outputEchoer client received: echo进阶点击查看参考博客为了通信安全起见,GRPC提供了TSlSSL的支持。首先利用openssl创建一个自签名证书$ openssl genrsa -out server.key 2048Generating RSA private key, 2048 bit long modulus (2 primes)……………………………………………………+++++………………………………………………………………………………………………………………..+++++e is 65537 (0x010001)$ openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650You are about to be asked to enter information that will be incorporatedinto your certificate request.What you are about to enter is what is called a Distinguished Name or a DN.There are quite a few fields but you can leave some blankFor some fields there will be a default value,If you enter ‘.’, the field will be left blank.—–Country Name (2 letter code) [AU]:State or Province Name (full name) [Some-State]:Locality Name (eg, city) []:Organization Name (eg, company) [Internet Widgits Pty Ltd]:Organizational Unit Name (eg, section) []:Common Name (e.g. server FQDN or YOUR name) []:EchoerEmail Address []:生成了server.key和server.crt两个文件,服务端两个文件都需要,客户端只需要crt文件修改服务端代码server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))Echoer_pb2_grpc.add_EchoerServicer_to_server(Echoer(), server)# 读取 key and certificatewith open(os.path.join(os.path.split(file)[0], ‘server.key’)) as f: private_key = f.read().encode()with open(os.path.join(os.path.split(file)[0], ‘server.crt’)) as f: certificate_chain = f.read().encode()# 创建 server credentialsserver_creds = grpc.ssl_server_credentials(((private_key, certificate_chain,),))# 调用add_secure_port方法,而不是add_insesure_port方法server.add_secure_port(’localhost:50051’, server_creds)修改客户端代码# 读取证书with open(‘server.crt’) as f: trusted_certs = f.read().encode()# 创建 credentialscredentials = grpc.ssl_channel_credentials(root_certificates=trusted_certs)# 调用secure_channel方法,而不是insecure_channel方法channel = grpc.secure_channel(’localhost:50051’, credentials)启动服务端后,启动客户端,会出现以下错误:grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with: status = StatusCode.UNAVAILABLE details = “Connect Failed” debug_error_string = “{“created”:"@1547552759.642000000”,“description”:“Failed to create subchannel”,“file”:“src/core/ext/filters/client_channel/client_channel.cc”,“file_line”:2721,“referenced_errors”:[{“created”:"@1547552759.642000000”,“description”:“Pick Cancelled”,“file”:“src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc”,“file_line”:241,“referenced_errors”:[{“created”:"@1547552759.642000000",“description”:“Connect Failed”,“file”:“src/core/ext/filters/client_channel/subchannel.cc”,“file_line”:689,“grpc_status”:14,“referenced_errors”:[{“created”:"@1547552759.642000000",“description”:“Peer name localhost is not in peer certificate”,“file”:“src/core/lib/security/security_connector/security_connector.cc”,“file_line”:880}]}]}]}">!!! 警告:这是因为TSLSSL模式下,客户端是通过服务名称:port来获取服务的凭据,而不是ip:port, 所以对客户端做如下修改:# 修改前channel = grpc.secure_channel(’localhost:50051’, credentials)# 修改后channel = grpc.secure_channel(‘Echoer:50051’, credentials)!!! 警告:其次,在TSLSSL模式下,客户端对服务名称:port解析时候需要dns支持,目前不知道如何解决,只能够采取以下措施解决,通过修改windows的host文件,利用host将服务名称解析为IP地址,打开windows的host文件,地址:C:\Windows\System32\drivers\etc\hosts备份后修改如下,添加:# 服务的IP地址 服务名称127.0.0.1 Echoer保存即可修改后,再次运行,即可运行成功注意事项:CA证书和私钥key都是配套的,不配套的CA证书和key是无法校验成功的结合consul注意事项:确保consul已经正确启动,查看http://ip:port:8500/, 可查看consul的状态,确保已经安装python-consul这个库,否则无法操作consul首先想象我们以上的grpc示例程序之所以成功的有限制条件,我们知道服务端已经正常启动我们知道了服务端的ip和端口但在实际过程中,一般是不可能确切知道服务的ip和端口的,所以consul就起了个中间桥梁的作用,具体如下:服务注册服务注册,顾名思义,服务在启动之前,必须现在consul中注册。服务端:当服务端启动之后,consul会利用服务注册时获得的ip和port同服务建立联系,其中最重要的就是health check即心跳检测。consul通过心跳检测来判定该服务是否正常。客户端:客户端通过consul来查询所需服务的ip和port,若对应服务已经注册且心跳检测正常,则会返回给客户端对应的ip和port信息,然后客户端就可以利用这个来连接服务端了服务注册示例代码如下:def register(self, server_name, ip, port, consul_host=CONSUL_HOST): """ server_name: 服务名称 ip: 服务IP地址 port: 服务监听的端口 consul_host: 所连接的consul服务器的IP地址 """ c = consul.Consul(host=consul_host) # 获取与consul的连接 print(f"开始注册服务{server_name}") check = consul.Check.tcp(ip, port, “10s”) # 设置心跳检测的超时时间和对应的ip和port端口 c.agent.service.register(server_name, f"{server_name}-{ip}-{port}", address=ip, port=port, check=check) # 注册既然有服务注册,当然会有服务注销,示例代码如下:def unregister(self, server_name, ip, port, consul_host=CONSUL_HOST): c = consul.Consul(host=consul_host) print(f"开始退出服务{server_name}") c.agent.service.deregister(f"{server_name}-{ip}-{port}")服务查询客户端则需要在consul中查询对应服务的IP和port,但由于在TSL/SSL模式下,所需的只是服务名称和port,故而只需要查询port端口即可。客户端服务查询采用的是DNS的查询方式,必须确保安装dnspython库,用于创建DNS查询服务查询示例代码如下:# 创建一个consul dns查询的 resolverconsul_resolver = resolver.Resolver()consul_resolver.port = 8600consul_resolver.nameservers = [consul_host]def get_host_port(self, server_name): try: dns_answer_srv = consul_resolver.query(f"{server_name}.service.consul", “SRV”) # 查询对应服务的port, except DNSException as e: return None, None return server_name, dns_answer_srv[0].port # 返回服务名和端口grpc流模式grpc总共提供了四种数据交互模式:simpe 简单模式 RPC:即上述的所有的grpcserver-side streaming 服务端流式 RPCclient-side streaming 客户端流式 RPCBidirectional streaming 双向数据流模式的 gRPC由于grpc对于消息有大小限制,diff数据过大会导致无法接收数据,我们在使用过程中,使用了流模式来解决了此类问题,在此模式下,客户端传入的参数由具体的protobuf变为了protobuf的迭代器,客户端接收的响应也变为了迭代器,获取完整的响应则需要迭代获取。服务端响应也变为了一个迭代器。修改服务定义文件:# 修改前service Echoer{ rpc echo (Req) returns (Resp) {}}# 修改后service Echoer{ rpc echo (stream Req) returns (stream Resp) {}}重新编译修改服务端将工作函数修改为如下所示, 即工作函数变成了一个迭代器:def echo(self, request_iterator, context): for i in range(10): yield Echoer_pb2.Resp(a=“echo”)修改客户端将echo的传入参数修改为迭代器:def qq(): for i in range(10): yield Echoer_pb2.Req(q=“echo”)response = stub.echo(qq())for resp in response: print(“Echoer client received: " + response.a)重新运行,接收结果如下:$ python Echoer_client.pyEchoer client received: echoEchoer client received: echoEchoer client received: echoEchoer client received: echoEchoer client received: echoEchoer client received: echoEchoer client received: echoEchoer client received: echoEchoer client received: echoEchoer client received: echo ...

January 19, 2019 · 3 min · jiezi