从零放弃学习 Spring - Spring Cloud 与 Kubernetes
在 Kubernetes 中运行 Spring Boot 应用
Kubernetes (以下简称 K8s) 可以直接运行 Spring Boot 应用,这部分不需要 Spring Cloud 支持。
代码参考 spring-boot-k8s。
K8s 环境准备
使用 kind 来创建一个 K8s 集群:
1 | kind create cluster |
构建应用镜像
创建一个简单的 Spring Boot 项目,选择 Spring Web
和 Spring Boot Actuator
依赖,然后随便写两个接口方便测试。
1 |
|
之所以加一个 /now
是为了后续测试时区的问题,Spring Boot Actuator 用来为应用提供探针。
Spring Boot 构建插件支持直接从项目构建出 Docker 镜像:
1 | mvn spring-boot:build-image |
构建容器会去下载一个 JRE 环境,非常抽象,所以我选通过 Dockerfile 来构建镜像:
1 | FROM maven:3.9-eclipse-temurin-17-alpine AS builder |
这里我用了 alpine 镜像,纯粹是个人爱好。然后就是构建 Docker 镜像和在 Docker 中运行的命令:
1 | cd path/to/project |
部署
在 K8s 部署应用是常规操作,除了内存开大一点以外没有什么区别,我在 Demo 项目里给了对应文件,说起来这步要上传镜像到 Registry,不能直接使用本地镜像,我没有深究是不是 namespace 的原因。
Spring Boot Actuator 提供了开箱即用的 Health Endpoint:/actuator/health/liveness
和 /actuator/health/readiness
,作为应用部署在 K8s 需要的健康检测探针,如果有更复杂需求也可以实现 HealthIndicator
。
NOTE: 当 Spring 应用运行在 K8s 中时,Spring Boot Actuator 会自动启用这两个探针,你可以通过
management.health.probes.enabled=true
主动启用探针
Java 时区问题
在 K8s 中给一个容器配置时区最简单的方法是用 hostPath
把 /etc/localtime
到容器中 /etc/localtime
,绝大部分应用有这个文件就正确显示时区了,但是 Java 应用不行。Java 应用的时区配置还需要 /etc/timezone
或者环境变量 TZ
设为对应时区,例如 Asia/Shanghai
。
Spring Cloud Kubernetes
Spring Cloud 本身是一个微服务框架,在云原生大趋势下也对 K8s 做了支持。Java 有两个 K8s 客户端实现 Fabric8 Kubernetes Java Client 和 Kubernetes Java Client,它们有各自对应的 Spring Cloud Kubernetes 生态,我这边示例用到的是后者。
新建一个项目,添加依赖 Spring Web
、Spring Boot Actuator
、OpenFeign
。不知道为什么 Spring Initializr 上不提供添加 K8s 有关的依赖,手动添加 spring-cloud-starter-kubernetes-client-all
:
1 | <dependency> |
spring-cloud-starter-kubernetes-client-all 依赖包含 discovery、loadbalancer 和 config,引入后 Spring 框架会自动激活 kubernetes
profile。
代码参考 spring-cloud-k8s。
Service Account
K8s 通过 Service Account 为 Pod 访问 K8s API 提供身份信息,如果没有指定,默认是 default
。
因为 Spring Cloud Kubernetes 需要访问 API,所以我们要给 Service Account 配置一些权限,这里直接用了 Spring Cloud Kubernetes 文档中的配置,它为 default
Account 增加了几个资源的的 get
、list
、watch
权限:
1 | kind: Role |
这么配置只是为了方便使用,不是一个正规的配置,如果在生产环境用还是要按需配置的。
服务发现和 LoadBalancer
K8s 本身提供了 Service 管理网络流量,Service 通过 Selector 关联 Endpoint。Spring Cloud 框架也自带了服务发现功能,所以为了结合 K8s,Spring Cloud Kubernetes 服务发现通过调用 K8s API 获得 Service 的 Endpoint,如果 Endpoint 有多个端口,默认选择第一个。
对服务的访问和其他 Spring Cloud 的区别不大,注意注解 @FeignClient
要写成服务名,而不是应用名称,例如我们使用 OpenFeign 去调用上一节提到的 Spring Boot 应用。
接口代码:
1 |
|
调用代码:
1 |
|
代码还展示了用 RestTemplate 访问其他服务的方式,这种方式下负载均衡只会提供 K8s Service 完成。
默认情况下,负载均衡使用 POD
模式,Spring Cloud Kubernetes Discovery 通过服务名获取的 Pod IP 列表,服务调用直接访问 Pod(类似 Headless Service 的作法),这是兼容 Spring Cloud 机制的,如果想要直接使用 K8s Service 自带的流量管理,可以把模式改为 SERVICE
:
1 | spring: |
服务发现默认工作在当前命名空间,可以通过配置指定命名空间跨命名空间服务发现:
1 | spring: |
另外,因为 Service 的 Endpoint 的注册和取消注册由 Pod 生命周期控制,所以 Spring Cloud Kubernetes 不再需要注册服务,这部分机制会被自动屏蔽。
配置
之前已经演示过了从 spring.config.location
加载配置文件,接入 K8s 后,框架可以从 ConfigMap 获取应用配置(我这里只谈 ConfigMap,Secret 基本相同)。
通过设置 spring.config.import=kubernetes:
让应用从 Spring Cloud Kubernetes Config 获得配置,这部分和用其他配置管理中间件很像。加入配置中设置了 spring.application.name
,配置客户端会去获取同名的 ConfigMap,如果没有设置,客户端获取 application
ConfigMap,如果你希望指定其他名字,可以通过 spring.cloud.kubernetes.config.name
直接指定 ConfigMap 名称。
创建两个 ConfigMap,一个用于启动配置(这一步不是必须的),一个用会被配置客户端自动读取。
startup-cm.yaml:
1 | apiVersion: v1 |
app-cm.yaml:
1 | apiVersion: v1 |
通过执行命令查看配置生效情况:
1 | $ kubectl exec -it pods/sck-app-7b9d868847-6g4zn -- /bin/sh |
虽然 K8s 会 Pod 挂载的 ConfigMap 文件同步改动,但是 Spring 应用并不监控本地文件,而是使用了 API 获取 configmap。如果应用需要及时配置变更就需要配置 spring.cloud.kubernetes.reload.enabled=true
,这样配置客户端就回去 watch configmap,别忘了代码中加上注解 @RefreshScope
。此外,配置客户端也支持通过配置 spring.cloud.kubernetes.reload.period
周期性从配置检查变更,文档提到这个功能在文件挂载时是无需权限的。
discovery-server 和 config-server
除了框架本身的组件外,Spring Cloud Kubernetes 生态还提供配置服务 spring-cloud-kubernetes-configserver 和发现服务 spring-cloud-kubernetes-discoveryserver。
spring-cloud-kubernetes-configserver 在 Config Server 基础上增加了对 K8s ConfigMap 和 Secret 支持。有一点比较令人迷惑,Kubernetes Config Server 不支持配置的 watch,有这方面需求的话就要结合 Kubernetes Configuration Watcher 一起使用。
spring-cloud-kubernetes-discoveryserver 封装了对 K8s Service 访问,你可以直接访问 HTTP API 获取服务 Endpoint,也能使用对应的客户端 spring-cloud-starter-kubernetes-discoveryclient 把服务集成到 Spring Cloud Discovery 能力。
这两个服务虽然可选,但是我还是比较推荐使用,因为抛开功能还有一点很重要,就是它可以减少对 K8s API Server 的压力。
最后
Spring Cloud 到底是一个微服务框架,它和 K8s 结合的方式是通过 K8s 机制的 API 和调整了自身部分的机制,而一个普通的云原生应用应该是在低状态的前提下,也避免和 API 有所交流,所以这是一件很违和的事情。
参考
- https://spring.io/blog/2020/01/27/creating-docker-images-with-spring-boot-2-3-0-m1
- https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints.kubernetes-probes
- https://docs.spring.io/spring-cloud-kubernetes/docs/current/reference/html/
- https://juejin.cn/post/6844904202372644877
- https://juejin.cn/post/6844904200443265037
- https://blog.51cto.com/u_2837193/4926721
- https://piotrminkowski.com/2020/09/10/spring-cloud-kubernetes-load-balancer-guide/
- https://blogs.asarkar.com/technical/spring-cloud-config/