用阿里云K8S搭建SpringCloud(服务发现:Consul)微服务--问题小结
发布日期:2021-07-01 05:14:33 浏览次数:2 分类:技术文章

本文共 12688 字,大约阅读时间需要 42 分钟。

导言

公司原有的微服务是基于Eureka做微服务microservice间的服务发现,以及通过阿里云的Loadbalance设施实现gateway层的负载,以及通过流量权重切换实现分批发布。

原来的方式保证了一定程度的微服务化,把很多重要服务的领域边界都可以划分清楚,几乎很少的服务间请求,所有的数据都在gateway层通过rxJava进行并行io请求和数据组装。保证了业务的灵活性和扩展性。
随着公司业务发展,今年开始有一块跟原来业务区别较大的新产品规划。如果再按eureka去搭一套的话,机器资源是相对浪费的,而且eureka目前的社区活跃度也低,以及netflix对其减少维护等原因,更多还是考虑新业务的扩展性较强,业务在做尝试,有很多未知数,所以要有更敏捷高效的部署和构建设施。阿里云的k8s基本配套还是很全的,在最初搭建的过程会麻烦一些,但是后续的服务扩展、微服务的灵活性、部署的效率都会提升很多。因为新的架构和项目,所以也基本用了较新版本的三方包(原来用的是springBootVersion 1.5.x)。全文基于的三方包版本如下:

springBootVersion = "2.3.0.RELEASE"    springCloudVersion = "2.2.4.RELEASE"(cloud-consul,cloud-openfeign版本)    springVersion = "5.2.12.RELEASE"        "org.springframework.cloud:spring-cloud-dependencies:Hoxton.SR7"

下文简单讲一下在搭建整个应用架构(k8s以及springboot2.3)的一些过程和遇到的问题。

镜像仓库&项目构建

原来的工程,打包和构建过程是在一台专门的运维机器上进行的。用阿里云的k8s也就考虑直接使用阿里云的【容器镜像服务】,可以可视化管理一些镜像版本。

  1. 创建namespace以及镜像仓库:需要为每个微服务,包括gateway,以及企业的基础镜像分别创建对应的镜像仓库。一开始的过程稍微麻烦一些,原来在一台运维机器上,通过简单的命令,以及项目自带的gradlew构建工具即可自动tag,push.不过创建好仓库后,还是管理起来更加方便的(毕竟在webUI里可以很方便查看,后续k8s上部署也方便很多)。
  2. 构建镜像: 因为我们的项目microservice是在多个子工程,阿里云镜像仓库目前还不支持。所以还是用原来的基于gradlew插件来build镜像。在阿里云仓库中创建多个微服务的仓库(分别创建),更新项目代码的 ’tag‘ 仓库源为阿里云仓库地址。
  3. 构建提速问题
    原有的打包方式是代码仓库和docker registry在同一机器上,打包速度很快(3,4min左右)。使用阿里云的镜像仓库,需要走公网,构建速度大减,IO时间很长。所以尝试了一些方法后,最后是选择本地构建,然后用“gradle parallel”并行构建。开启parallel并行构建方式:
    构建脚本添加task --parallel。测试工程构建时间节省了40%
> /gradlew buildDocker -x test task --parallel  >output: Parallel execution is an incubating feature. Total time: x mins y secs

阿里云k8s

整个k8s的创建和配置过程基本半小时内可以搞定,可以参考阿里云官方文档即可,具体的配置还是根据业务和体量的情况。我们目前新项目是用的托管集群,只需要创建worker节点。

Consul 服务发现(服务端发现)

基于k8s的服务架构中,我们的服务发现方案是:gateway的服务发现和负载均衡的实现是通过创建kube-proxy为Service提供的机制(默认为iptabels),gateway和microservice之间,以及microservice之间的服务发现和负载均衡是基于Consul。本来是想用Eureka,但eureka项目基本已经没有团队维护,而Consul其实功能更加强大,有自带的web UI可以方便检查服务状态,而且有一致性的KV存储,后期服务之间的一些env也可以用consul的K/V。

对consul感兴趣的,可以看下远古的blog。

搭建的过程其实非常简单,可以直接用Helm工具,部署到k8s相应的Namespace即可。部署后可在控制台的Stateful(有状态)中看到consul服务启动。

consul in k8s

我们部署了两套consul agent是创建两个datacenter,一个用于日常环境的服务发现,一个用于生产环境。

在这里插入图片描述

基于SpringCloud 和 SpringBoot2.3相关的服务配置和升级问题汇总

下面简单汇总部署到k8s后的一些问题和解决方式。

总是连接到localhost

"Fail fast is set and there was an error reading configuration from consul.所以总是连接到localhost “

solution:

配置去掉 spring-cloud-starter-consul-all.使用:

ext.springCloudStartConsul = "org.springframework.cloud:spring-cloud-starter-consul-discovery:${springBootVersion}"

TomcatStarter 报错

2020-12-17 03:55:00.091 ERROR 7 --- [           main] o.s.b.web.embedded.tomcat.TomcatStarter  : Error starting Tomcat context. Exception: org.springframework.beans.factory.BeanCreationException. Message: Error creating bean with name 'servletEndpointRegistrar' defined in class path resource [org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration$WebMvcServletEndpointManagementContextConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar]: Factory method 'servletEndpointRegistrar' threw exception; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration$ServiceRegistryEndpointConfiguration': Unsatisfied dependency expressed through field 'registration'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'consulRegistration' defined in class path resource [org/springframework/cloud/consul/serviceregistry/ConsulAutoServiceRegistrationAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.cloud.consul.serviceregistry.ConsulAutoRegistration]: Factory method 'consulRegistration' threw exception; nested exception is java.lang.IllegalArgumentException: Consul service ids must not be empty, must start with a letter, end with a letter or digit, and have as interior characters only letters, digits, and hyphen: 8080...... 中略Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat	at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:126) ~[spring-boot-2.2.1.RELEASE.jar!/:2.2.1.RELEASE]	at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.
(TomcatWebServer.java:88) ~[spring-boot-2.2.1.RELEASE.jar!/:2.2.1.RELEASE] at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:438) ~[spring-boot-2.2.1.RELEASE.jar!/:2.2.1.RELEASE] at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:191) ~[spring-boot-2.2.1.RELEASE.jar!/:2.2.1.RELEASE] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:180) ~[spring-boot-2.2.1.RELEASE.jar!/:2.2.1.RELEASE] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:153) ~[spring-boot-2.2.1.RELEASE.jar!/:2.2.1.RELEASE] ... 16 common frames omitted

solution:

先是替换了instanceId,servicename等没有解决。

原因1:springboot 和springcloud的版本对应问题.Springboot版本2.2.1 高于Springcloud

升级cloud到 Hoxton.SR7,springboot升级到对应2.2.4(后续又改成了2.3.0是为了适配elasticsearch的版本,也可以正常运行)
后来又遇到了这个问题,后续的原因是没有读取到正确的properties文件。configmap配置的路径问题。

连接 consul失败

以下示例去掉了一些跟服务相关的敏感信息

2020-12-17 09:26:51.585  INFO 6 --- [           main] o.s.c.c.s.ConsulServiceRegistry          : Registering service with consul: NewService{id='service-gateway-xxxx', name='service-gateway', tags=[secure=false], address='xxxxx', meta={}, port=8500, enableTagOverride=null, check=Check{script='null', dockerContainerID='null', shell='null', interval='20s', ttl='null', http='http://xxx:8500/my-health-check', method='null', header={}, tcp='null', timeout='null', deregisterCriticalServiceAfter='null', tlsSkipVerify=null, status='null', grpc='null', grpcUseTLS=null}, checks=null}2020-12-17 09:26:51.603 ERROR 6 --- [TaskScheduler-1] o.s.c.c.discovery.ConsulDiscoveryClient  : Error watching Consul CatalogServicescom.ecwid.consul.transport.TransportException: org.apache.http.conn.HttpHostConnectException: Connect to xxxxxx failed: Connection refused (Connection refused)	at com.ecwid.consul.transport.AbstractHttpTransport.executeRequest(AbstractHttpTransport.java:83) ~[consul-api-1.4.5.jar!/:na]	at com.ecwid.consul.transport.AbstractHttpTransport.makeGetRequest(AbstractHttpTransport.java:36) ~[consul-api-1.4.5.jar!/:na]	at com.ecwid.consul.v1.ConsulRawClient.makeGetRequest(ConsulRawClient.java:139) ~[consul-api-1.4.5.jar!/:na]..... 中略 ... 2020-12-17 09:26:51.623 ERROR 6 --- [           main] o.s.c.c.s.ConsulServiceRegistry          : Error registering service with consul: NewService{id='service-gateway-4dbb291da034bc48c69e0dfa3bf5b418', name='service-gateway', tags=[secure=false], address='xxx', meta={}, port=8500, enableTagOverride=null, check=Check{script='null', dockerContainerID='null', shell='null', interval='20s', ttl='null', http='xxxx', method='null', header={}, tcp='null', timeout='null', deregisterCriticalServiceAfter='null', tlsSkipVerify=null, status='null', grpc='null', grpcUseTLS=null}, checks=null}

solution:

properties配置的consul的连接地址替换成 node的ip+node的port your-nodeip-address:8500

集群环境中 consul,health check 问题

  1. consul上显示的健康检查,链接地址不对。
Get http://xxxx:8500/my-health-check: dial tcp ipxxxx:8500: connect: connection refused

solution:

从官方文档中找到了,配置。正确的配置:

spring.cloud.consul.discovery.health-check-url=http://${spring.cloud.client.ip-address}:${server.port}/my-health-checkspring.cloud.consul.discovery.healthCheckInterval=20sspring.cloud.consul.discovery.health-check-critical-timeout=30s // 服务停止后自动移除注册列表

服务调用java.net.UnknownHostException(集群环境,本地环境不会有)

feign,loadBalancer,discoveryClient都返回了hostname而非IP地址。本地测试的时候显示IP address.

2020-12-21 06:21:06.576 ERROR 6 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://gateway-service/xxx/xxx": gateway-deploy-77456755b7-gsxrj; nested exception is java.net.UnknownHostException: gateway-deploy-77456755b7-gsxrj] with root causejava.net.UnknownHostException: gateway-deploy-77456755b7-gsxrj	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184) ~[na:1.8.0_121]

solution:

添加配置

为了解决这个问题,需要在注册服务的时候,让服务以ip的方式注册:

#${spring.cloud.client.ip-address}这个属性是spring cloud内置,用来获取ip,spring.cloud.consul.discovery.prefer-ip-address=true

logback配置问题

  1. logback 各类日志文件的输出
    springboot2.3.0logging.file配置项的name变更为logging.file.name,原来的配置项将无法把控制台的log信息输出到指定目录的文件。
    另外,因为一些2.3.0的依赖的问题,slf4j和logback的版本也需要调整到对应的版本。

基于k8s的构建应用

基于k8s的微服务架构图

基础的环境和应用必须的配置搭建好之后,整体的服务架构如图:

部署应用Deployment

基于Springboot的微服务架构中,核心应用就是microservice和gateway,都是应用类型适用于Deployment(无状态)类型。

Deployment 配置以Gateway应用举例,查看yaml配置:

apiVersion: apps/v1 # for versions before 1.8.0 use apps/v1beta1kind: Deploymentmetadata:  name: daily-gateway-deploy  labels:    app: daily-gateway-deployspec:  replicas: 2      #副本数量,可以根据实际运行的情况后期逐渐调整。  selector:  #定义 Deployment 如何查找要管理的 Pods    matchLabels:      app: daily-gateway-app  template:    metadata:      labels: #Pod 使用 labels 字段打上 app: daily-gateway-app 标签。        app: daily-gateway-app    spec:      containers:      - name: daily-gateway        image: yourimagerepo:yourversion        ports:        - containerPort: 8080        env:        - name: TZ          value: Asia/Shanghai        volumeMounts:        - name: serviceenv-vol  #挂载的volum,对应volumes. name          mountPath: /opt/app/application.properties          subPath: application.properties        - name: log-vol          mountPath: /opt/app/logback.xml          subPath: logback.xml        - name: logs-dir-vol          mountPath: /opt/app/logs        volumes:      - name: serviceenv-vol        configMap: # k8s内部已创建的configMap资源的名称和内容(k-v对)          name: gateway-conf          items:            - key: application.properties              path: application.properties      - name: log-vol        configMap:            defaultMode: 420 #权限 10进制转linux权限数8进制(644)拥有者有读写权限;而属组用户和其他用户只有读权限            name: log-conf             items:              - key: logback.xml                path: logback.xml         - name: logs-dir-vol          hostPath:  # 挂载到node节点的日志路径,不会随pod销毁而删除            path: /root/workspace/logs/daily/gateway
  • metadata: 应用的元数据信息的声明,labels标签是用户自定义的kv属性
  • spec:定义应用的:副本数量,镜像版本,挂载的volumn资源等等。
  • configMap:配置项,需要提前创建好。一般配置文件名作为key,内容作为value。可以用来配置logback以及应用的properties文件,不同的环境可以配置不同的文件标记,如:application-production。

其他配置-limit,request等

Kubernetes采用静态资源调度方式,对于每个节点上的剩余资源,它是这样计算的:节点剩余资源=节点总资源-已经分配出去的资源,并不是实际使用的资源。

对于没有声明resources的Pod,它被调度到某个节点后,Kubernetes也不会在对应节点上扣掉这个Pod使用的资源。可能会导致节点上调度过去太多的Pod。
配置最好根据实际资源的使用情况配置,request和limit相等,或最好相近。

resources: # 资源声明      requests: #需要的最少的资源        memory: "512Mi"        cpu: "200m" #0.2cpu      limits:        memory: "512Mi"        cpu: "500m"

创建 Service

应用部署起来之后,微服务和gateway之间通过consul可以进行服务间通信和负载,接下来还要为gateway创建一个对外暴露api的Service。

对于已经部署的Gateway Deployment(暴露了 8080 端口,同时还被打上 labels: app: daily-gateway-app 标签),定义如下Service声明yaml.

apiVersion: v1kind: Servicemetadata:  name: daily-gateway-service  labels:    app: daily-gateway-svcspec:  selector:#不断扫描与其选择器匹配的 Pod,然后将所有更新发布到service中    app: daily-gateway-app  ports:  - protocol: TCP    name: http    port: xxxx#对外暴露port,也可以跟targetPort相同    targetPort: 8080#容器port    nodePort: 30xxx  type: LoadBalancer #定义LB类型,会自动创建阿里云上的负载均衡设施。

创建一个 daily-gateway-service 的Service它会将请求代理到使用 TCP 端口 8080,并且具有标签 “app=daily-gateway-app” 的 Pod 上。因为是LoadBalancer类型的Service,同是还会自动创建相应的负载均衡服务。这种适合于gateway类型应用,作为所有服务的api入口。

创建好Service之后就可以通过”外部端口“来访问应用的REST服务了。

kubectl 常用命令

  • kubectl get po ,kubectl get pod 获取容器组列表
  • kubectl get deploy | grep test ,kubectl get deployment| grep test获取name含test的 deployment
  • kubectl get svc | grep test ,kubectl get service 获取服务列表
  • kubectl delete deployment|rc|rs $name 删除资源
  • kubectl delete pod gateway-5d696f58f-n69hz --force --grace-period=0 强制删除
  • kubectl exec -it gateway-deploy-5456f7fb5d-xmmrh – /bin/sh 进入容器组内部,退出不关闭
  • kubectl describe svc daily-gateway-service 查看服务细节,同理deploy,pod
  • kubectl cluster-info 查看节点地址
  • kubectl get pods daily-item-dff6d4c89-hl84t -o yaml 查看容器组运行的yaml

结语

文中省略了比如elasticsearch的搭建,以及具体适配的版本。后续有时间会把基于文中版本的示例的(springboot,springcloud)test工程上传到个人的github上。如果各位在学习和搭建k8s过程有什么问题或者心得欢迎交流。

转载地址:https://nanyao.blog.csdn.net/article/details/113773287 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:ASM(一) 利用Core API 解析和生成字节码
下一篇:【十】分布式微服务架构体系详解——架构设计理念

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2024年04月19日 00时14分48秒