介绍
Spring官方提供了Spring Cloud Kubernetes套件,方便把Spring Boot应用程序部署到k8s上,并且享受k8s提供的高可用支持,这主要体现在服务发现以及配置中心上。
服务发现
在使用k8s做为部署环境时,我们可以有两种服务发现的方式。
DiscoveryClient
我们可以利用Spring Cloud提供的DiscoveryClient
来发现服务,就像其他的Spring Cloud系列一样。所以我们需要在配置类上加上注解来启用服务发现,像这样:
java
@SpringBootApplication
@EnableScheduling
@EnableDiscoveryClient
public class HelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldApplication.class, args);
}
}
如果想要DiscoveryClient能够感知服务发生了变化,那么还需要加上注解:@EnableScheduling
,因为Spring使用Schedule来定时发起heartbeat event。
Native方式
因为k8s本身就提供服务发现的能力,所以我们也可以直接使用RestTemplate
来向k8s上的服务名发起调用。当然,更好的做法使用OpenFeign和CircuitBreaker来发起调用。
配置中心
利用k8s的ConfigMap/Secret对象,可以达到配置集中的目的,即:在ConfigMap/Secret中设置的属性,可以被一个服务的多个实例所共享,如果配置需要改动,那么修改一次到处生效。
Spring使用k8s的ConfigMap/Secret,可以有两种方案。
ConfigMap/Secret直接mount到pod
在这种方案下,ConfigMap/Secret直接mount到pod上,然后以环境变量或者本地文件的方式,向Spring应用提供配置属性。
其中一种常见的方式,是把ConfigMap/Secret里面的配置项,暴露成pod运行环境里面的环境变量,然后Spring应用程序通过读取环境变量来获取属性配置的值。
另一种方式,是把ConfigMap/Secret的配置项直接写到文件里面,然后Spring应用程序通过spring.config.import
来引入这个属性文件,从而读取属性配置的值。
使用这种方式,reload功能将不可用,即:ConfigMap/Secret的配置发生改变后,需要Spring程序重新启动,或者发送POST请求到/actuator/refresh端点。
通过k8s客户端api在指定范围内查询所有的ConfigMap/Secret
利用Spring Cloud Kubernetes提供的Fabric8ConfigMapPropertySource
,配合spring.cloud.kubernetes.config.xxx
或者spring.cloud.kubernetes.secret.xxx
的配置,根据名字或者label从指定的namespace中查询所有符合条件的ConfigMap/Secret,然后,按照一定的规则将属性提供给Spring应用程序。
比如:
yaml
spring:
application:
name: cloud-k8s-app
cloud:
kubernetes:
config:
name: default-name
namespace: default-namespace
sources:
# Spring Cloud Kubernetes looks up a ConfigMap named c1 in namespace default-namespace
- name: c1
# Spring Cloud Kubernetes looks up a ConfigMap named default-name in whatever namespace n2
- namespace: n2
# Spring Cloud Kubernetes looks up a ConfigMap named c3 in namespace n3
- namespace: n3
name: c3
Spring应用程序,会按照上述条件查询出所有符合条件的ConfigMap,然后,
- 如果ConfigMap/Secret的名字符合
spring.application.name
或者spring.cloud.kubernetes.config.name
的名字 - 如果ConfigMap中包含文件属性,则按照
spring.application.name
或者spring.cloud.kubernetes.config.name
的名字匹配文件名,如果匹配则加载(同时考虑当前生效的profile) - 如果是单个属性,则直接加载到Spring应用中
Reload(Deprecated)
可以通过Spring Cloud Kubernetes的属性,来开启和配置reload功能。
yaml
spring:
cloud:
kubernetes:
reload:
# 开启自动刷新属性
enabled: true
# 默认ConfigMap在enable=true时自动开启,但是Secret需要显式开启
monitoring-secrets: true
strategy: refresh
period: 15s
特别地,对于strategy,有三种选项:
-
refresh(默认),只有标注了
@ConfigurationProperties
或@RefreshScope
的配置bean才会被刷新 -
restart_context,整个ApplicationContext会被重启,为了让它工作,必须确保开启和暴露restart这个actuator端点
yamlmanagement: endpoint: restart: enabled: true endpoints: web: exposure: include: restart
-
shutdown,ApplicationContext被shutdown,要确保application controller或者replica set配置成重启pod
Configuration Watcher
Spring Cloud Kubernetes提供了一个Controller,可以被部署到k8s集群,来实时分发ConfigMap/Secret的变更事件,来达到Spring应用程序中属性的自动刷新。
部署Configuration Watcher
创建一个yaml文件:spring-config-watcher.yml
yaml
apiVersion: v1
kind: List
items:
- apiVersion: v1
kind: Service
metadata:
labels:
app: spring-cloud-kubernetes-configuration-watcher
name: spring-cloud-kubernetes-configuration-watcher
spec:
ports:
- name: http
port: 8888
targetPort: 8888
selector:
app: spring-cloud-kubernetes-configuration-watcher
type: ClusterIP
- apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: spring-cloud-kubernetes-configuration-watcher
name: spring-cloud-kubernetes-configuration-watcher
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app: spring-cloud-kubernetes-configuration-watcher
name: spring-cloud-kubernetes-configuration-watcher:view
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: namespace-reader
subjects:
- kind: ServiceAccount
name: spring-cloud-kubernetes-configuration-watcher
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: namespace-reader
rules:
- apiGroups: ["", "extensions", "apps"]
resources: ["configmaps", "pods", "services", "endpoints", "secrets"]
verbs: ["get", "list", "watch"]
- apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-kubernetes-configuration-watcher-deployment
spec:
selector:
matchLabels:
app: spring-cloud-kubernetes-configuration-watcher
template:
metadata:
labels:
app: spring-cloud-kubernetes-configuration-watcher
spec:
serviceAccount: spring-cloud-kubernetes-configuration-watcher
containers:
- name: spring-cloud-kubernetes-configuration-watcher
image: springcloud/spring-cloud-kubernetes-configuration-watcher:3.0.4
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
port: 8888
path: /actuator/health/readiness
livenessProbe:
httpGet:
port: 8888
path: /actuator/health/liveness
ports:
- containerPort: 8888
部署到k8s集群
bash
kubectl apply -f spring-config-watcher.yml
监控ConfigMap/Secret
为了被监控到变更,ConfigMap/Secret必须有相应的labels和annotation
yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: example-configmap
labels:
spring.cloud.kubernetes.config: "true"
annotations:
spring.cloud.kubernetes.configmap.apps: "app-a, app-b"
其中:
- 对于ConfigMap,label:
spring.cloud.kubernetes.config=true
是必须的 - 对于Secret,label:
spring.cloud.kubernetes.secret=true
是必须的 - annotation是为了更加细粒度地控制哪些应用(这里的app-a,app-b)想要收到通知
监控端点的设置
默认情况下,接收通知的端点是/actuator/refresh,如果需要定制,可以在k8s的service定义中增加boot.spring.io/actuator
这个annotation。
yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: config-map-demo
name: config-map-demo
annotations:
boot.spring.io/actuator: http://:9090/myactuator/home
spec:
ports:
- name: http
port: 8080
targetPort: 8080
selector:
app: config-map-demo
这里,端点被设置成http://:9090/myactuator/home
Config Server
这个feature是可选的,它是向Spring Cloud Config中增加了一个指向k8s的ConfigMap/Secret的environment repository。它可以让我们同时利用存储在比如git、Vault上的配置,以及存储在ConfigMap/Secret的配置。
部署Config Server
Spring已经提供了现成的Docker镜像
yaml
apiVersion: v1
kind: List
items:
- apiVersion: v1
kind: Service
metadata:
labels:
app: spring-cloud-kubernetes-configserver
name: spring-cloud-kubernetes-configserver
spec:
ports:
- name: http
port: 8888
targetPort: 8888
selector:
app: spring-cloud-kubernetes-configserver
type: ClusterIP
- apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: spring-cloud-kubernetes-configserver
name: spring-cloud-kubernetes-configserver
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app: spring-cloud-kubernetes-configserver
name: spring-cloud-kubernetes-configserver:view
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: namespace-reader
subjects:
- kind: ServiceAccount
name: spring-cloud-kubernetes-configserver
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: namespace-reader
rules:
- apiGroups: ["", "extensions", "apps"]
resources: ["configmaps", "secrets"]
verbs: ["get", "list"]
- apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-kubernetes-configserver-deployment
spec:
selector:
matchLabels:
app: spring-cloud-kubernetes-configserver
template:
metadata:
labels:
app: spring-cloud-kubernetes-configserver
spec:
serviceAccount: spring-cloud-kubernetes-configserver
containers:
- name: spring-cloud-kubernetes-configserver
image: springcloud/spring-cloud-kubernetes-configserver:3.0.4
imagePullPolicy: IfNotPresent
env:
- name: SPRING_PROFILES_INCLUDE
value: "kubernetes"
readinessProbe:
httpGet:
port: 8888
path: /actuator/health/readiness
livenessProbe:
httpGet:
port: 8888
path: /actuator/health/liveness
ports:
- containerPort: 8888
在应用中使用Config Server
如果要在应用中使用这个功能,那么需要:
- 在spring的activeProfiles中增加
Kubernetes
这个profile - 配置PropertySources
- ConfigMap默认是作为PropertySource的(
spring.cloud.kubernetes.config.enableApi=true
) - Secret需要显式启用:
spring.cloud.kubernetes.secrets.enableApi=true
- ConfigMap默认是作为PropertySource的(
Discovery Server
Spring提供了现成的Discover Server镜像,可以直接部署到k8s集群中。
yaml
apiVersion: v1
kind: List
items:
- apiVersion: v1
kind: Service
metadata:
labels:
app: spring-cloud-kubernetes-discoveryserver
name: spring-cloud-kubernetes-discoveryserver
spec:
ports:
- name: http
port: 80
targetPort: 8761
selector:
app: spring-cloud-kubernetes-discoveryserver
type: ClusterIP
- apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: spring-cloud-kubernetes-discoveryserver
name: spring-cloud-kubernetes-discoveryserver
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app: spring-cloud-kubernetes-discoveryserver
name: spring-cloud-kubernetes-discoveryserver:view
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: namespace-reader
subjects:
- kind: ServiceAccount
name: spring-cloud-kubernetes-discoveryserver
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: namespace-reader
rules:
- apiGroups: ["", "extensions", "apps"]
resources: ["services", "endpoints"]
verbs: ["get", "list", "watch"]
- apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-kubernetes-discoveryserver-deployment
spec:
selector:
matchLabels:
app: spring-cloud-kubernetes-discoveryserver
template:
metadata:
labels:
app: spring-cloud-kubernetes-discoveryserver
spec:
serviceAccount: spring-cloud-kubernetes-discoveryserver
containers:
- name: spring-cloud-kubernetes-discoveryserver
image: springcloud/spring-cloud-kubernetes-discoveryserver:3.0.4
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
port: 8761
path: /actuator/health/readiness
livenessProbe:
httpGet:
port: 8761
path: /actuator/health/liveness
ports:
- containerPort: 8761
结论
就像Spring官方提到的,即使不使用Spring Cloud Kubernetes,您照样可以方便地把Spring Boot应用部署到k8s,而且可以享受到k8s提供的高可用功能。这里的重点其实是对属性自动刷新的支持,可以让Spring Boot应用在不重启的情况下,感知ConfigMap/Secret的变化。