DockOne微信分享(二三一):玩转Kubernetes开发测试环境

【编者的话】DockOne微信分享(二三一)实录,分享内容如题。

大家好,我是来自阿里巴巴研发效能的郑云龙,今天是第三次在Dockone上做线上分享。今天的分享我会主要讨论如何能更高效的在开发阶段利用Kubernetes作为开发阶段的基础设施,并围绕讨论研发协作的问题。

背景信息

现在越来越多的研发团队开始完全基于Kubernetes开构建自己的研发基础设施和持续交付过程,Kubernetes原生的一些特性在一定程度上可以帮助开发者解决一些开发过程中的联调问题,但是软件研发根本要解决的是人与人直接的协作问题,今天的分享将围绕云原生下的开发协作和效能提升问题来讨论。

说到软件交付效率提升,我们的第一反应一定会是持续集成/持续交付的相关实践。而在CI/CD中最重要的一个关键实践就是建立团队的持续交付流水线。上面这张图展示了从持续集成流水线到持续交付流水线模式的转变。

而实践持续交付时,我们基本的目标就是竟可能的自动化所有需要人工参与的流程,通过自动化流水线的方式将代码发布到线上环境。 因此流水线就像是一个万能的框,什么合适都能往里面装。不对不同类型的团队流水线也能被玩出各种不同的花样,但是大体流程都会和上面这张图的类似。 通过更多的测试自动化/运维自动化来消除整个发布过程中的风险。 通过优化每个流程执行来优化交付等待时间。

而最终我们会发现,从瀑布-敏捷到持续交付,整个软件交付过程中耗时最多的部分是开发阶段。而在开发阶段,作为程序员,应该大体都是在 “编码 – 单元测试(可选) – 等待 – 集成”的流程里面循环往复,直到代码能够满足预期交付上线。那如果我们能够有有效的减少软件交付团队在 “开发”这个阶段的耗时,那么整个软件的交付效率在已经实施持续交付实践的基础上又能往前迈出巨大的一步。 这也是我们为什么会开始关注“开发阶段”效能的最重要原因。

而在实际的工作场景下,我们会发现在某些情况下,持续交付流水线虽然能一定程度上解决自动化的问题。 但是对于开发人员来说其实并不友好。其中最关键的问题就是日常测试环境的稳定性。一般来说,我们希望开发人员能够频繁的提交代码,通过构建测试,并部署到日常测试环境已触发一些验收测试用例,以及方便开发人员自测。 而这个模式除非整个团队只有一个人的情况以外,只要涉及到多人协作就会产生巨大的问题。这里列举了一些典型的问题。

流水线的副作用与应对

为了解决上述问题,在内部的交付平台中,我们做了大量的工作来确保开发人员能够高效的实现开发与集成。其中最主要的实践包括:

* 每一个开发人员的变更可以按需独占一套完整的项目测试环境,当变更发布后自动销毁

* 在网络层面日常办公网络与日常测试环境之间网络是直接打通的,开发人员可以直接在本地与日常环境上的服务联调

* 强大的中间件隔离能力,从而实现本地联调远端以及远端联调本地

除此之外,除了日常测试环境以外还有单独的主干环境,用以确保当多服务联调时的测试环境稳定性。通过上述的应对策略,在内部,开发人员在变更上完成项目的集成与测试,并尽可能的减少开发人员之间的依赖。从而提升开发效率。

Kubernetes面向开发者的不足

上述这些能力都是建立在阿里内部常年积累的研发基础设施之上,在外部环境中其实很难找到类似的环境,或者要重新实现的话,成本是非常大的。那我们回到Kubernetes。 对于Kubernetes而言,其原生提供的一些能力,在一定程度上能够帮助开发人员完成一些日常测试环境的治理能力。

例如,在基础设施资源充足的情况下,我们完全可以基于Namespace为开发人员部署独占的集成测试环境。同时原生的kubectl命令中使用port-forward也可以方便的将远程服务映射到本地。 从而实现本地与远端服务的联调。但是,我们也应该知道其原生能力的不足,在开发场景特别是微服务模式下,联调这个动作除了我依赖别人以外,还有别人依赖我。 而在目前的能力上,唯一能够让其它人与“我”进行联调的唯一方式就是通过流水线将其部署到测试环境,那这就意味着我们可能会碰到很多刚才列举的实践流水线的种种问题。Kubernetes原生能力的不足包括:

  • Port-forward虽然能够将远端服务映射到本地,但是意味着本地调用服务的方式和在集群中调用服务的方式不一致,联调完可能还要继续修改代码,发布到集群进一步测试。
  • 单向调用:只能是从本地向集群的请求,集群内部的请求无法转发到本地
  • 多个服务之间的集成成本大: 需要依赖一个独立稳定的联调测试环境

探索可行的解决方案

那么我们这时就可以开一下脑洞了,如果我们希望在原生Kubernetes上能够实现在阿里内部的研发流程。 那就意味着:

  • 本地能否直接访问PodIP/ClusterIP甚至是集群内的DNS域名
  • 集群内的网络请求能否知己访问到我本地? 这样我其实都不需要进行部署动作,直接在本地进行联调测试
  • 在调用链路上,我能直接访问其他开发者的本地服务吗?

    那接下来,我们看看这些问题在原生Kubernetes下是否可行。

方案一:SSH端口转发

我们知道要想透过网络隔离访问内网资源,SSH端口转发是个简单可行的方式。 通过SSH隧道,我们可以将本地的端口映射到远端的内网服务,或者是通过远程端口转发,把内网中对服务器特定端口的请求转发到本地。

在Kubernetes下,我们也能实现类似的能力,首先是在集群内部部署一个启动了SSH服务的Pod容器。 为了能够在本地使用SSH隧道,直接通过kubectl的port-forward转发即可。如上,SSH协议配合kubectl的port forward我们可以打通本地到集群或者是从集群到本地的网络。 但是实际的体验和操作效果其实非常差,

方式二 sshuttle

同样是基于ssh协议,sshuttle提供了一个基于SSH的VPN网络,使用方式也很简单,同样在集群中部署一个包含SSH服务的代理容器,在通过kubectl将ssh端口映射到本地,最后通过sshuttle建立VPN网络连接,在sshuttle的命令中需要指定需要代理的网段,在Kubernetes下指定PodIP和ClusterIP的网段即可。

同时sshuttle也支持使用—ns指定nameserver。 所以如果你实现一个kubernetes集群的nameserver服务,那就可以直接在本地访问集群内Service的域名地址。

构建面向研发协作的解决方案

将上述能力进行封装和整合,我们就可以得到一个轻量级的开发协作工具KT Connect ( https://github.com/alibaba/kt-connect )

KT Connect通过将上述基础能力进行封装,并对外提供了3个命令已支持不同场景的研发协作。 如上,分别是connect,exchange和mesh。这三个命令的组合可以达到什么效果呢? 我们来看看具体的场景。

场景一:本地与远端服务联调

在这种场景下,开发者只要直接使用ktctl connect打通本地到集群的网络即可,就可以直接在本地通过PodIP/ClusterIP/DNS地址访问集群中的服务。 connect背后做的事情如上图所示。

场景二:集群内服务于联调本地

在这个场景下,我们希望集群中所有对服务C的访问能够请求到本地正在开发的C’。因此开发者可以通过ktctl exhcnage命令,在集群内部署一个Shadow容器已接管所有原本对C实例的请求,再通过Shadow容器将请求转发到本地。 并在退出命令式恢复集群中C的实例状态。但是这种模式有一个问题就是开发测试环境依然会出现独占的情况,那有没有更灵活的方式呢?答案肯定是有的。

场景三:集群内服务于联调本地(共享模式)

mesh与exchange的最大区别在于,exchange会完全替换原有的应用实例。mesh命令创建代理容器,但是会保留原应用容器,代理容器会动态生成version标签,以便用于可以通过Istio流量规则将特定的流量转发到本地,同时保证环境正常链路始终可用。在这种场景下,在确保开发测试环境稳定的同时,基于Istio的流量规则,我们可以把部分流量转到本地,从而实现在共享的开发测试环境中的联调能力。

场景四:更复杂的联调场景

在微服务模式下,服务将往往会存在大量的相互依赖。通过connect/exchange/mesh的组合,可以方便的实现在团队协作下的开发与联调。通过这种方式,开发人员可以极大的提升本地的开发效率,并且可以快速的实现服务与服务之间的集成与测试。

总结

最后总结一下:如在最开始所说的,软件研发效率提升最终是要解决人与人直接的协作问题,通过DevOps以及持续交付流水线我们可以打通部门墙,让开发/测试/运维人员进行不断协作,持续的交付可靠的软件。 而当持续交付成熟度达到一定程度后,我们会发现最整合软件交付过程中最大的效率问题依然是出现在开发阶段的协作上。 如果能解决开发阶段的效率问题,整个软件交付的效率又能往前迈一个大步。

今天的分享为大家提供了一种在Kubernetes下实现开发阶段效率提升的一个参考实现。KT Connect目前开源在 https://github.com/alibaba/kt-connect ,如果觉得今天分享的内容还不错,可以star或者关注一下项目。最后,作为开发人员,你在Kubernetes基础设施上进行代码开发和测试时,有遇到什么问题?是怎么解决的? 效果如何?