在云原生环境中,基于Kubernetes的工具链一方面简化了开发者的许多日常琐碎,另一方面也带来了许多新的概念和工作方式的改变。本篇文章将聚焦于云原生基础设施,谈谈如何在面向云原生的开发流程中,高效地进行开发调测以及发布。
首先,从通用意义上讲,作为一个开发者,你期望怎样的研发流程?
我理解的理想研发过程
基于这个理想的研发流程,当研发基础设施迁移到云原生和微服务架构之后,在开发、调试和发布方面会遇到什么问题,又要如何解决?
一个典型的研发过程包含三个环节:开发、测试和部署。
开发主要指的是代码的编写和自测。当你写好代码和单元测试之后,需要在一个运行环境中进行功能性验证。本地IDE提供了大量用于调测的功能,并且本地服务的重新启动速度也比较快。相比将代码部署到远端的测试环境,能够完全在本地进行编写、启动、debug,是最理想的工作方式。
实际的工作中,为了验证一个特定功能场景,往往需要配合其它的外部依赖,比如当前我编写的这个服务依赖什么别的服务;还有什么其他服务需要调用我的服务,我才能做一个完整的验证?
这些问题都需要很好的解决,才能真正享受到本地开发的便利。
测试一般指的是在CI环境中的各种自动化测试和验收环境中的测试验证,本文的重点不在这里,因此假设我们已经完成了这些操作。来到了部署的环节。
虽然已经经过了很多的验证,我们对要发布的版本的质量已经有了相当程度的信心。但发布发上线之后,也不可避免的时不时会引入一些缺陷,因此如何让出现的这些问题的影响面最小,就是所谓的稳健发布。
首先来关注在云原生下的开发和调试。
随着微服务技术和各类开源服务组件普及,如今的软件系统或多或少的都会包含数个相互独立的服务实体,之间通过接口调用相互连接。因此在本地进行服务测试的时候,难免涉及到与上下游的其他服务的互动,特别是在进行完整功能验证时,常常需要在本地将上下游链路的所有服务全部启动起来。然而随着系统的演进和服务的增多,本地资源很快就无法支撑整体系统启动了。那么,是否能够将测试环境中的公共服务节点和本地服务串联在一起,行成完整的测试链路呢?
在云原生的环境下,测试环境被Kubernetes的集群网络边界所隔离。从集群外部访问测试集群中的服务需要经过统一的Ingress网关,且只能访问配置了网关路由的一小部分部分服务。同时由于开发者的本地主机通常没有公网IP地址,从测试环境中完全无法连接到本地的服务实例。
为此云效创造了kt-connect工具来解决本地测试时的网络联通问题,它能够在开发者的本地环境和Kubernetes测试集群之间,建立起一条虚拟的双向网络通路。
kt-connect是一款简单易用的命令行工具。对于从本地连接测试环境的情况,它提供了一个connect命令,利用在集群中部署一个作为网络代理的Pod节点,使得从本地网络能够直接访问集群中的任意Service域名、IP地址和任意Pod的IP地址。而对于其从集群访问本地的情况,kt-connect提供了exchange命令,通过另一个反向的代理节点实现将集群中流入指定服务的所有请求导向到本地的指定端口。
对于个人开发者的使用场景来说,以上两个命令就能够完全满足日常工作了。然而对于团队开发的场景下,则会带来新的问题。当一个开发者使用了exchange命令,将特定服务实例的流量全部导向本地,在同一个集群中工作的所有其他开发者都会随之受到影响。为了避免这样的相互干扰,kt-connect又创造了第三种命令mesh,它的功能与exchange命令相似,但并不会将网络中的所有流量全部导入到开发者本地,而是基于特定的网格规则,只将符合要求的测试流量导向开发者的本地环境,从而实现测试环境资源的最大化利用率和多项目的和平共处。
从本质上来说,kt-connect主要利用了Kubernetes原生命令行的端口转发和开源SSH工具的四层网络代理能力实现,对应用本身不产生任何侵入,目前我们已经将它的所有源代码在Github开源。
接下来来到发布的环节。
云原生基础设施内置了滚动发布的能力,可以很好的满足发布本身的可靠性的需求,保证整个发布过程是优雅的。但这个模式有一些问题,比如发布和回滚的时间都比较长,且无法暂定下来进行业务运行状态的观察。一个进阶的模式是蓝绿发布,新启动一个副本,然后把所有流量全切到新的版本,这样发布和回滚都很快了,但所有流量还是一次性切换的,没法进行增量验证。金丝雀发布可以解决这个问题,可以通过路由控制,逐步将流量导入到新版本上。但一般是采用流量百分比的方式,所有没法指定特定人群使用新版本。
一个更加可控的金丝雀方式,需要给每个用户设置一个流量标志,比如使用cookie。也就是说通过一个类似interpcetor的机制,判断当前用户是否应该是一个灰度用户,如果是的话,就给他设置一个cookie,后续来自该用户的所有流量都会带上这个cookie。有了这个流量标志,就可以在流量入口处根据cookie的值判断该请求应该到新版本还是老版本。
有了这个路由机制,还是不够。因为我们实际的应用程序并不是只有一个服务,而是像图中的由多个服务互相调用而组成。比如当我要发布服务B的时候,因为服务B并不是直接面向浏览器的,所有无法接收到用户的cookie。这时就需要有一个流量表自动传递机制。一般的做法是在请求的入口处把灰度标记存在一个ThreadLocal中,然后在应用的出口处,比如一个OkHttpClient的调用处再把这个ThreadLocal中的值放到cookie中继续往下传递。
我们已经理解了“全链路可控金丝雀发布”的做法,接下来要探讨在技术上如何实现。在阿里巴巴,我们使用了一个叫做统一接入的技术,所有的请求(包括入口流量和内部服务之间互相调用的流量)都会经过通过接入,然后有统一接入决定该将这个请求分发到哪里。
到了云原生时代,ServiceMesh的概念兴起,其实本质上就是一个“分布式统一接入”。这个统一接入不再是一个中心化的服务,而是随着每个服务的每个实例一起部署在一起的进程,这个进程负责接收改实例的入口流量,并转发给实际的服务;同时也拦截实例的出口流量,并决定下一跳应该是谁。
Istio是ServiceMesh的一个被广泛采用的实现。
了解了这个原理之后,从上图中,可以看到一次发布过程是什么样子。
这个发布过程中涉及到多次Kubernetes资源的更新操作,如果完全采用原生命令+手工配置来操作,不仅复杂还容易出错。为此云效对云原生的各种常见发布模式都进行了产品化封装,开发者只需要配置一些简单的发布和路由规则,就可以轻松地实现安全可控发布过程。
点击此处可以了解更多云效云原生开发内容,也欢迎大家直接加入ktconnect钉钉交流群(钉钉群号:23314962),将你的想法随时告诉我们。