Spring Cloud 与 K8s 的微服务设计(一)
Spring Cloud 与 K8s 的微服务设计(一)
Spring Boot 1.x 与 2.x 的区别
其实 Spring Boot 在一开始时,运用到的基本就是 Eureka、Config、Zuul、Ribbon、Feign、Hystrix 等。到了 Spring Boot 2.x 的时候,大量的组件开始风云崛起。下面简单列下这两个版本之间的区别如下。
Spring Boot 1.x 中,session 的超时时间是这样的:
server.session.timeout=3600
复制代码
而在 2.x 中:
server.servlet.session.timeout=PT120M
复制代码
截然不同的写法,cookie 也是一样的:
server:
servlet:
session:
timeout: PT120M
cookie:
name: ORDER-SERVICE-SESSIONID
复制代码
-
应用的 ContextPath 配置属性改动,跟上面的 session 一样,加上了一个 servlet。
-
Spring Boot 2.x 基于 Spring 5,而 Spring Boot 1.x 基于 Spring 4 或较低。
-
统一错误处理的基类 AbstarctErrorController 的改动。
-
配置文件的中文可以直接读取,不需要转码。
-
Acutator 变化很大,默认情况不再启用所有监控,需要定制化编写监控信息,完全需要重写,HealthIndicator,EndPoint 同理。
-
从 Spring Boot 2.x 开始,可以与 K8s 结合来实现服务的配置管理、负载均衡等,这是与 1.x 所不同的。
K8s 的一些资源的介绍
上面说到 Spring Boot 2.x 可以结合 K8s 来作为微服务的架构设计,那么就先来说下 K8s 的一些组件吧。
ConfigMap,看到这个名字可以理解:它是用于保存配置信息的键值对,可以用来保存单个属性,也可以保存配置文件。对于一些非敏感的信息,比如应用的配置信息,则可以使用 ConfigMap。
创建一个 ConfigMap 有多种方式如下。
1. key-value 字符串创建
kubectl create configmap test-config --from-literal=baseDir=/usr
复制代码
上面的命令创建了一个名为 test-config,拥有一条 key 为 baseDir,value 为 "/usr" 的键值对数据。
2. 根据 yml 描述文件创建
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
data:
baseDir: /usr
复制代码
也可以这样,创建一个 yml 文件,选择不同的环境配置不同的信息:
kind: ConfigMap
apiVersion: v1
metadata:
name: cas-server
data:
application.yaml: |-
greeting:
message: Say Hello to the World
---
spring:
profiles: dev
greeting:
message: Say Hello to the Dev
spring:
profiles: test
greeting:
message: Say Hello to the Test
spring:
profiles: prod
greeting:
message: Say Hello to the Prod
复制代码
注意点:
-
ConfigMap 必须在 Pod 使用其之前创建。
-
Pod 只能使用同一个命名空间的 ConfigMap。
当然,还有其他更多用途,具体可以参考官网。
Service,顾名思义是一个服务,什么样的服务呢?它是定义了一个服务的多种 pod 的逻辑合集以及一种访问 pod 的策略。
service 的类型有四种:
-
ExternalName:创建一个 DNS 别名指向 service name,这样可以防止 service name 发生变化,但需要配合 DNS 插件使用。
-
ClusterIP:默认的类型,用于为集群内 Pod 访问时,提供的固定访问地址,默认是自动分配地址,可使用 ClusterIP 关键字指定固定 IP。
-
NodePort:基于 ClusterIp,用于为集群外部访问 Service 后面 Pod 提供访问接入端口。
-
LoadBalancer:它是基于 NodePort。
如何使用 K8s 来实现服务注册与发现
从上面讲的 Service,我们可以看到一种场景:所有的微服务在一个局域网内,或者说在一个 K8s 集群下,那么可以通过 Service 用于集群内 Pod 的访问,这就是 Service 默认的一种类型 ClusterIP,ClusterIP 这种的默认会自动分配地址。
那么问题来了,既然可以通过上面的 ClusterIp 来实现集群内部的服务访问,那么如何注册服务呢?其实 K8s 并没有引入任何的注册中心,使用的就是 K8s 的 kube-dns 组件。然后 K8s 将 Service 的名称当做域名注册到 kube-dns 中,通过 Service 的名称就可以访问其提供的服务。那么问题又来了,如果一个服务的 pod 对应有多个,那么如何实现 LB?其实,最终通过 kube-proxy,实现负载均衡。
说到这,我们来看下 Service 的服务发现与负载均衡的策略,Service 负载分发策略有两种:
-
RoundRobin:轮询模式,即轮询将请求转发到后端的各个 pod 上,其为默认模式。
-
SessionAffinity:基于客户端 IP 地址进行会话保持的模式,类似 IP Hash 的方式,来实现服务的负载均衡。
其实,K8s 利用其 Service 实现服务的发现,其实说白了,就是通过域名进行层层解析,最后解析到容器内部的 ip 和 port 来找到对应的服务,以完成请求。
下面写一个很简单的例子:
apiVersion: v1
kind: Service
metadata:
name: cas-server-service
namespace: default
spec:
ports:
- name: cas-server01
port: 2000
targetPort: cas-server01
selector:
app: cas-server
复制代码
可以看到执行 kubectl apply -f service.yaml
后:
root@ubuntu:~$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
admin-web-service ClusterIP 10.16.129.24 <none> 2001/TCP 84d
cas-server-service ClusterIP 10.16.230.167 <none> 2000/TCP 67d
cloud-admin-service-service ClusterIP 10.16.25.178 <none> 1001/TCP 190d
复制代码
这样,我们可以看到默认的类型是 ClusterIP,用于为集群内 Pod 访问时,可以先通过域名来解析到多个服务地址信息,然后再通过 LB 策略来选择其中一个作为请求的对象。
K8s 如何来处理微服务中常用的配置
在上面,我们讲过了几种创建 ConfigMap 的方式,其中有一种在 Java 中常常用到:通过创建 yml 文件来实现配置管理。
比如:
kind: ConfigMap
apiVersion: v1
metadata:
name: cas-server
data:
application.yaml: |-
greeting:
message: Say Hello to the World
---
spring:
profiles: dev
greeting:
message: Say Hello to the Dev
spring:
profiles: test
greeting:
message: Say Hello to the Test
spring:
profiles: prod
greeting:
message: Say Hello to the Prod
复制代码
上面创建了一个 yml 文件,同时,通过 spring.profiles 指定了开发、测试、生产等每种环境的配置。
具体代码:
apiVersion: apps/v1
kind: Deployment
metadata:
name: cas-server-deployment
labels:
app: cas-server
spec:
replicas: 1
selector:
matchLabels:
app: cas-server
template:
metadata:
labels:
app: cas-server
spec:
nodeSelector:
cas-server: "true"
containers:
- name: cas-server
image: {{ cluster_cfg['cluster']['docker-registry']['prefix'] }}cas-server
imagePullPolicy: Always
ports:
- name: cas-server01
containerPort: 2000
volumeMounts:
- mountPath: /home/cas-server
name: cas-server-path
args: ["sh", "-c", "nohup java $JAVA_OPTS -jar -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms1024m -Xmx1024m -Xmn256m -Xss256k -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC cas-server.jar --spring.profiles.active=dev", "&"]
hostAliases:
- ip: "127.0.0.1"
hostnames:
- "gemantic.localhost"
- ip: "0.0.0.0"
hostnames:
- "gemantic.all"
volumes:
- name: cas-server-path
hostPath:
path: /var/pai/cas-server
复制代码
这样,当我们启动容器时,通过 --spring.profiles.active=dev
来指定当前容器的活跃环境,即可获取 ConfigMap 中对应的配置。是不是感觉跟 Java 中的 Config 配置多个环境的配置有点类似呢?但是,我们不用那么复杂,这些统统可以交给 K8s 来处理。只需要你启动这一命令即可,是不是很简单?
Spring Boot 2.x 的新特性
在第一节中,我们就讲到 1.x 与 2.x 的区别,其中最为凸显的是,Spring Boot 2.x 结合了 K8s 来实现微服务的架构设计。其实,在 K8s 中,更新 ConfigMap 后,pod 是不会自动刷新 configMap 中的变更,如果想要获取 ConfigMap 中最新的信息,需要重启 pod。
但 2.x 提供了自动刷新的功能:
spring:
application:
name: cas-server
cloud:
kubernetes:
config:
sources:
- name: ${spring.application.name}
namespace: default
discovery:
all-namespaces: true
reload:
enabled: true
mode: polling
period: 500
复制代码
如上,我们打开了自动更新配置的开关,并且设置了自动更新的方式为主动拉取,时间间隔为 500ms,同时,还提供了另外一种方式——event 事件通知模式。这样,在 ConfigMap 发生改变时,无需重启 pod 即可获取最新的数据信息。
同时,Spring Boot 2.x 结合了 K8s 来实现微服务的服务注册与发现:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-discovery</artifactId>
</dependency>
复制代码
开启服务发现功能:
spring:
cloud:
kubernetes:
discovery:
all-namespaces: true
复制代码
其实最终是向 K8s 的 API Server 发起 http 请求,获取 Service 资源的数据列表。然后根据底层的负载均衡策略来实现服务的发现,最终解析到某个 pod 上。那么为了同一服务的多个 pod 存在,我们需要执行:
kubectl scale --replicas=2 deployment admin-web-deployment
复制代码
同时,我们如果通过 HTTP 的 RestTemplate Client 来作服务请求时,可以配置一些请求的策略,RestTemplate 一般与 Ribbon 结合使用:
client:
http:
request:
connectTimeout: 8000
readTimeout: 3000
backend:
ribbon:
eureka:
enabled: false
client:
enabled: true
ServerListRefreshInterval: 5000
ribbon:
ConnectTimeout: 8000
ReadTimeout: 3000
eager-load:
enabled: true
clients: cas-server-service,admin-web-service
MaxAutoRetries: 1 #对第一次请求的服务的重试次数
MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务)
#ServerListRefreshInterval: 2000
OkToRetryOnAllOperations: true
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #com.damon.config.RibbonConfiguration #分布式负载均衡策略
复制代码
可以配置一些服务列表,自定义一些负载均衡的策略。
如果你是使用 Feign 来作为 LB,其实与 Ribbon 只有一点点不一样,因为 Feign 本身是基于 Ribbon 来实现的,除了加上注解 @EnableFeignClients 后,还要配置:
feign:
client:
config:
default: #provider-service
connectTimeout: 8000 #客户端连接超时时间
readTimeout: 3000 #客户端读超时设置
loggerLevel: full
复制代码
其他的可以自定义负载均衡策略,这一点是基于 Ribbon 的,所以是一样的。
更多推荐
所有评论(0)