likes
comments
collection
share

Spring Cloud Kubernetes笔记

作者站长头像
站长
· 阅读数 18

介绍

Spring官方提供了Spring Cloud Kubernetes套件,方便把Spring Boot应用程序部署到k8s上,并且享受k8s提供的高可用支持,这主要体现在服务发现以及配置中心上。

服务发现

在使用k8s做为部署环境时,我们可以有两种服务发现的方式。

DiscoveryClient

我们可以利用Spring Cloud提供的DiscoveryClient来发现服务,就像其他的Spring Cloud系列一样。所以我们需要在配置类上加上注解来启用服务发现,像这样:

@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应用程序。

比如:

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功能。

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端点

    management:
      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

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集群

kubectl apply -f spring-config-watcher.yml

监控ConfigMap/Secret

为了被监控到变更,ConfigMap/Secret必须有相应的labels和annotation

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。

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镜像

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

Discovery Server

Spring提供了现成的Discover Server镜像,可以直接部署到k8s集群中。

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的变化。