从技术的本质看 Kubernetes 的诞生

软件工程的角度看k8s

我们先来看看软件工程的定义,其实总结成一句话就是为了交付某些产品或者功能的方法论。 在"没有银弹"的论文中讨论了本质复杂度和偶然复杂度。 本质复杂度是指实现问题的本身就有的复杂度,偶然复杂度是指实现问题的过程中产生的复杂度。

k8s主要要解决的则是软件层面服务部署很运维的复杂度,能够实现软件的快速部署和回滚,最核心的Pod的抽象也是解决了容器创建、调度和销毁的问题。

知道了它解决的核心问题,那么有些内容其实就可以交由第三方来进行拓展,比如长连接的负载均衡则是在应用层相对比较灵活的,涉及业务本身的流量治理的问题,所以k8s本身并不支持长连接的负载均衡,而是交给第三方去做,比如service mesh的sidecar模式就是这样演化出来的。这样既复用了k8s Pod的抽象,也能够在不改动k8s本身代码的情况下去实现长连接的负载均衡。

Kubernetes 技术的本质是什么?

《技术的本质》中提出了新技术是从老技术中孕育出来,这种机制为组合进化。

Kubernetes 也是老技术的新组合,为了部署运维大量集群服务而产生的工具,它使用操作系统已有的功能组合降低应用运维的难度。

技术是那些被捕捉并加以应用的现在的集合。为什么要制造这个概念?

通过概念的定义能让我们去研究和讨论技术是如何发展的。

因为社会的发展不随随人的意志而发生转移,社会的发展依托于技术的发展,那技术的发展是怎么实现的呢?这就是所要思考的技术的本质。

从促进业务发展的角度去看Kubernetes,它的出现也是为了促进 Devops 的发展,让我们的应用运维部署更加简单,减轻了人工的操作,即软件工程中的accidential complexity。

这个时候我们上线一款新的应用的时候,可以专注于解决业务问题本身。

  • 有了操作系统隔离技术cgroup的支持,才有容器化部署的基础。
  • 有了操作系统提供的 netlink和netfilter功能, 才有了网络功能的基础。

Kubernetes 将这些模块功能进行重新组合,用来解决部署运维时需要手工创建虚拟环境和网络设置等的问题。

搞清楚技术的本质我们也可以组合我们的技术来进行进化,比如我们知道了网络上Kubernetes iptables由于规则增加会带来性能瓶颈,这个时候我们可以更换 iptables 这个零件,换成Linux内核的新功能比如 ipvs或者是 nftable。

理解了 Kubernetes 如何对技术进行编排后,我们对 Kubernetes 新出的特性其实就很容易理解,都是在主程序不变的情况下更换更优质的零件,让整体的性能表现的更好。

我们解决产品需求的功能也是对既有技术的一个编程,通过数据库技术将数据提供给业务产品进行使用。

以物流为例,我们在配送订单的场景时,会使用关系型数据库来解决业务上的功能,通过将每个订单存储成一行的数据,从而在用户需要进行操作或者其他运营统计的时候,可以快速访问到。

然后随着订单量的增大,我们会根据不同场景的操作需求,引入分布式KV缓存、内存缓存、分库分表等优化技术,一步一步替换我们最初的实现。

可以看到我们去实现业务需求的时候也是对现有技术的一个组合。

所以 Kubernetes技术的本质这个问题也显而易见,就是为了解决于手工运维的困难,通过代码来将可以自动化的功能进行编排,让机器代码人工来实现。

技术的出现本身就是为了解决问题,为了促进效率的提升,从而让软件产品的研发提效,更快的产出价值。

多个角度去看 Kuberntes

同一个信息,拥有越多的视角,则能获得的信息越多。

同样是K8s,作为运维开发能看到的是应用部署操作的简易程度。

作为后端研发则是看到它在并发编程的优雅处理,也可以看到它通过将偶然复杂度的封装,让我们研发过程中能屏蔽掉运维部署的复杂度。

作为产品经理能看到的是云原生带来的持续高可用,不需要再过度担心服务 SLA 的问题。

从技术本质的角度看 Kuberntes 容器编排的核心能力,搭配上CNI、CRI、CSI等插件的接入,更大程度的支持了虚拟化的进程。

Kubernetes 模块设计

K8s 不同模块组成了整个集群管理的工作架构。

apiserver 负责接收外部变更,并且提供了informer通知机制,ETCD提供内部存储实现集群状态的管理。

CNI 插件为集群维护网络设置,当有新的资源上线需要网络的时候先为它添加物理上的网络

kube-proxy 实现了网络的负载均衡,通过订阅 informer 来维护CNI创建的网络信息。

kubelet 接收通知来维护各种资源的状态。

因为通知机制是所有组件联动的核心,所以单独成独立的模块进行复用是有价值的,并且模块化后可以单独提升模块的可用性和性能。

分模块后我们做设计也更能聚焦于功能建设,在需要相似功能的依赖时不需要过多思考直接复用,降低了其他依赖模块实现难度,能更好的思考如何服务整体系统而不会掉入细节中。

为什么 Kubernetes 用Go实现?

当我们学 Kubernetes 源码的过程就会发现,它实际上是对集群的状态进行管理,并且在整体架构的抽象上,有 api-server 处理外部的请求,并且将请求通过推送的方式发给 kubelet 组件进行维护。 kubelet 组件则是通过调用 CRI 来实现 sandbox 的创建,所以在事件的传递上,用 Go 的实现是非常简单并且非常高效的。

后端研发不是一门语言说完的,而是由很多技术栈构成的,每个语言都有自己擅长的领域,用契合的技术实现自己的想法才是关键。

Go被推上神坛也是由于它在云原生扮演了重要的作用,在并发编程这一块有context和 协程作为支撑,让它广受欢迎,也由于微服务的推广,可以用go来实现业务系统的后端。

Java虽然在企业级开发里面统一了位置,但是也由于微服务大部分不需要如此复杂和繁重的架构,让go能够占据一席之地。

python在机器学习和算法实现的简便性上无可替代,但是也由于弱类型的特性,让它在做企业级应用上并不适合多人协作,且协程编程这一块过于复杂,导致难以胜任 Kubernetes 这种需要大量并发编程的场景。

通过软件架构设计解决数据库产品使用的性能短板,比如Kubernetes中就使用了大量的并发编程来充分利用cpu 资源,也提升了整体事件变更的处理性能,这也是K8s选择go语言进行编程的原因,因为它的并发编程模型非常简单且高效。

比如我们可以通过 channel 来进行数据的共享,并且可以用 context.Done 的信号来控制所有进程是否退出。

csharp 复制代码
func watch(ctx context.Context, event chan string){
	    for {
        select {
        case <-ctx.Done():
            return
        case <-event:
            // do something
        }
    }
}

或者是需要通过定时器来定时维护集群的状态

go 复制代码
func watch(ctx context.Context){
    ticker := time.NewTicker(time.Second * 10)
    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            // do something
        }
    }
}

编写起来都是十分简单且易于理解的,这样 Kubernetes 就可以更加专注于容器编排的逻辑实现。

相关推荐
追逐时光者4 小时前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_4 小时前
敏捷开发流程-精简版
前端·后端
苏打水com5 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
间彧6 小时前
Spring Cloud Gateway与Kong或Nginx等API网关相比有哪些优劣势?
后端
间彧6 小时前
如何基于Spring Cloud Gateway实现灰度发布的具体配置示例?
后端
间彧6 小时前
在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
后端
间彧6 小时前
如何为Spring Cloud Gateway配置具体的负载均衡策略?
后端
间彧6 小时前
Spring Cloud Gateway详解与应用实战
后端
-L76 小时前
【Kubernetes】常见面试题汇总(十九)
云原生·容器·kubernetes
EnCi Zheng7 小时前
SpringBoot 配置文件完全指南-从入门到精通
java·spring boot·后端