本文共 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也就考虑直接使用阿里云的【容器镜像服务】,可以可视化管理一些镜像版本。
- 创建namespace以及镜像仓库:需要为每个微服务,包括gateway,以及企业的基础镜像分别创建对应的镜像仓库。一开始的过程稍微麻烦一些,原来在一台运维机器上,通过简单的命令,以及项目自带的gradlew构建工具即可自动tag,push.不过创建好仓库后,还是管理起来更加方便的(毕竟在webUI里可以很方便查看,后续k8s上部署也方便很多)。
- 构建镜像: 因为我们的项目microservice是在多个子工程,阿里云镜像仓库目前还不支持。所以还是用原来的基于gradlew插件来build镜像。在阿里云仓库中创建多个微服务的仓库(分别创建),更新项目代码的 ’tag‘ 仓库源为阿里云仓库地址。
- 构建提速问题 原有的打包方式是代码仓库和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 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 问题
- 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配置问题
- 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!