一旦决定做微服务架构,有很多现实问题摆在面前,比如技术选型、业务拆分问题、高可用、服务通信、服务发现和治理、集群容错、配置管理、数据一致性问题、康威定律、分布式调用跟踪、CI/CD、微服务测试,以及调度和部署等等......这并非一些简单招数能够化解。
作为主要编程语言是 Java 的容器服务来说,选择 Spring Cloud 去搭配 K8S 是一个很自然的事情。Spring Cloud 和 K8S 都是很好的微服务开发和运行框架。从应用的生命周期角度来看,K8S 覆盖了更广的范围,特别像资源管理,应用编排、部署与调度等,Spring Cloud 则对此无能为力。从功能上看,两者存在一定程度的重叠,比如服务发现、负载均衡、配置管理、集群容错等方面,但两者解决问题的思路完全不同,Spring Cloud 面向的纯粹是开发者,开发者需要从代码级别考虑微服务架构的方方面面,而 K8S 面向的是 DevOps 人员,提供的是通用解决方案,它试图将微服务相关的问题都在平台层解决,对开发者屏蔽复杂性。举个简单的例子,关于服务发现,Spring Cloud 给出的是传统的带注册中心 Eureka 的解决方案,需要开发者维护 Eureka 服务器的同时,改造服务调用方与服务提供方代码以接入服务注册中心,开发者需关心基于 Eureka 实现服务发现的所有细节。而 K8S 提供的是一种去中心化方案,抽象了服务 (Service),通过 DNS+ClusterIP+iptables 解决服务暴露和发现问题,对服务提供方和服务调用方而言完全没有侵入。
对于技术选型,我们有自己的考量,优先选择更稳定的方案,毕竟稳定性是云计算的生命线。我们并不是 “K8S 原教旨主义者”,对于前面提到的微服务架构的各要点,我们有选择基于 K8S 实现,比如服务发现、负载均衡、高可用、集群容错、调度与部署等。有选择使用 Spring Cloud 提供的方案,比如同步的服务间通信;也有结合两者的优势共同实现,比如服务的故障隔离和熔断;当然,也有基于一些成熟的第三方方案和自研系统实现,比如配置管理、日志采集、分布式调用跟踪、流控系统等。
我们利用 K8S 管理微服务带来的最大改善体现在调度和部署效率上。以我们当前的情况来看,不同的服务要求部署在不同的机房和集群(联调环境、测试环境、预发布环境、生产环境等),有着不同需求的软硬件配置(内存、SSD、安全、海外访问加速等),这些需求已经较难通过传统的自动化工具实现。K8S 通过对 Node 主机进行 Label 化管理,我们只要指定服务属性 (Pod label),K8S 调度器根据 Pod 和 Node Label 的匹配关系,自动将服务部署到满足需求的 Node 主机上,简单而高效。内置滚动升级策略,配合健康检查 (liveness 和 readiness 探针)和 lifecycle hook 可以完成服务的不停服更新和回滚。此外,通过配置相关参数还可以实现服务的蓝绿部署和金丝雀部署。集群容错方面,K8S 通过副本控制器维持服务副本数 (replica),无论是服务实例故障(进程异常退出、oom-killed 等)还是 Node 主机故障(系统故障、硬件故障、网络故障等),服务副本数能够始终保持在固定数量。
Docker 通过分层镜像创造性地解决了应用和运行环境的一致性问题,但是通常来讲,不同环境下的服务的配置是不一样的。配置的不同使得开发环境构建的镜像无法直接在测试环境使用,QA 在测试环境验证过的镜像无法直接部署到线上……导致每个环境的 Docker 镜像都要重新构建。解决这个问题的思路无非是将配置信息提取出来,以环境变量的方式在 Docker 容器启动时注入,K8S 也给出了 ConfigMap 这样的解决方案,但这种方式有一个问题,配置信息变更后无法实时生效。我们采用的是使用 Disconf 统一配置中心解决。配置统一托管后,从开发环境构建的容器镜像,可以直接提交到测试环境测试,QA 验证通过后,上到演练环境、预发布环境和生产环境。一方面避免了重复的应用打包和 Docker 镜像构建,另一方面真正实现了线上线下应用的一致性。