Kubernetes 初探

Author Avatar
Wincer 3月 16, 2020
  • 在其它设备中阅读本文章

本文最近一次更新于 193 天前,其中所包含的信息很可能已经有所发展或是发生改变。

之前对 Kubernetes(K8s)一直抱有一种很「暧昧」的态度:我想了解它的特性,并且也尝试根据别人总结的经验去接触它,但尝试接触后却总像雾里看花,好像懂了一些又好像什么都不懂。

我对新技术技术又总是充满渴望的,渴望来自于对旧技术的不满,这种不满在最近辞职后达到了顶峰:我不想通过别人总结的「二手知识」来接触 K8s 了,而是希望全面地了解 K8s 如何解决了现有软件架构的缺点从而火起来的。正好借着本文,写一下我近期关于架构方面的一些思考(第一篇架构相关的文章,可能会有些稚嫩)。

软件架构的改进

几年前,软件的架构都是单体式(Monolithic)的:即使一个 Web 系统的大多数模块在业务上并不是紧密相连的,它们仍然会运行在一个操作系统级别的进程当中(如今大把的软件仍然是这样的架构),这也就意味着单独改动某一个模块,在部署时必须重新将整个系统都打包一遍。并且由于大部分单体式的软件在架构层面缺少优化,在打包部署时简直就像是一场灾难,而作为开发人员最难受的是明明有办法阻止灾难发生但却无能为力。

比如我的前公司:先把 Web 应用打一个包(包含各种 pip 的库),再把底层依赖打一个包(Elasticsearch、PostrgeSQL、Nginx,以及各种 rpm 包),这两步下来,包的大小已经直逼 3 个 G 了,每次打包部署的流程差不多都要花费两三个小时,而且有时候还安装失败。

近几年,随着 Docker 的问世,微服务架构(Microservices)火了起来。它将单体式的软件拆分了微小且可独立运行的组件,这些组件之间是相互解耦的,因此它们可以独立地开发、部署、升级。而在微服务架构中修改了某模块之后,因为模块组件之间的运行环境是相互隔离的,也不用将系统整体打包了,只需要单独重新部署这个模块就可以了。

而随着系统的复杂性逐渐提升,微服务架构中的组件必然会越来越多,配置、管理并让组件们一直保持顺畅地运行(即使是系统在升级中)也成为了一个问题。由人工来保证组件自动化配置、监督组件的运行、故障时进行处理,显然是一件吃力不讨好的事,于是 K8s 这类容器编排工具出现了。

非 K8s 不可么

高可用的系统设计至少需要满足以下几点;容灾性(Fault-tolerance)、扩展性(Scalability)、分布式(Distribution)、快反应(Responsiveness)、热升级(Live Update)。

K8s 的确是满足了这几点,但这些并非是 K8s 的「专利」 ,实际上,在二十多年前爱立信所创造的 Erlang 编程语言便已经满足了这几点要求:即使 Erlang 诞生的时候并没有微服务这个概念(没错,我就是 Erlang 吹)。

甚至可以说,K8s 在相当程度上借鉴了 Erlang 的思想:Erlang 虚拟机(BEAM)上运行的所有进程(这里的进程类似 Golang 的协程,并非是操作系统级别的进程)都是相互孤立的,进程之间的通信只能通过 message box 来传递,甚至没有共享内存,这简直就是天然的 Docker 啊!同样是因为进程孤立,跨虚拟机进程通信和虚拟机内的进程通信就只是网络开销不同,也因此 Erlang 天生就支持分布式。至于容灾性,Erlang 同样自带了进程级别的 Supervisor,当进程崩溃的时候,会自动重启。

那么,既然 Erlang 设计的这么厉害,为啥现在火起来的是「借鉴」 Erlang 思想的 K8s 而不是 Erlang 本身呢?因为 Erlang 的运行模型实在太特殊了,比如:Erlang 的数据是绝对不可变的,进程之间传递数据只能复制不能传引用,才导致进程可以完全相互独立,进而保证进程崩溃的时候不会影响到其他进程。

而 K8s,则是将这一系列的思想从特定的平台抽离了出来,不必再拘泥于 Erlang 这么特殊的模型了,让其他任何语言编写的程序均可以做到高可用。而对于如今的公司来说,将服务拆分然后用 K8s 进行部署的难度显然远远低于将现有代码改成用 Erlang 或者 Elixir 实现。

K8s 的实践

在前公司,我曾尝试在架构需要调整的项目里推过 K8s,但架构评审的时候,被领导以同事都不会 K8s 为理由给驳回了,这一点倒是在我意料之中,毕竟领导担心承担决策失败的风险,以及他大概认为给开发一两周的时间熟悉 K8s 给他带来的收益比写一两周代码要少吧 :)

既然目前在工作中用不到,我就只能在自己的项目中用了。

于是决定将 DIEM-API 项目改为使用 K8s 部署,并将整个项目解耦为三个部分:

  1. 基于 Golang + Gin 框架的服务层(Stateless),这里本应该根据功能再划分为不同的服务,但是因为目前只提供了一个 API,已无法更细分了;
  2. 基于 PostgreSQL 的数据持久层(Stateful);
  3. 基于 Redis 的缓冲层,提供限流服务(Stateless)。

单独说明一下第三点:虽然使用了 Redis 作为缓冲,但是其中存的数据仅是 IP 的访问频次,而访问频次对于功能来说并不重要,故这里还是将缓冲层的组件定义为无状态型服务。

无状态型服务(服务层、缓冲层)包含两方面的设置:

  1. 设置 kind 为 Deployment 方便进行 Live Update,同时会自动创建 ReplicationSet 管理 Pods:崩溃时自动创建、可随意扩展服务;
  2. 设置 kind 为 Service,提供静态 IP 以供外部访问,因为 Pods 是不稳定的,可能随时被销毁并重建,同时 Service 也可提供负载均衡服务;

至于有状态服务(数据持久层)要复杂一些,除了包含无状态型服务需要的两方面设置外,还需要配置 PersistentVolume 用来生成存储的资源,以及 PersistentVolumeClaim 用来请求 PersistentVolume 的资源。

有关具体的配置文件信息,请查阅项目内的几个 Yaml 文件。

后记

我在读完《Kubernetes in action》的前十章之后才对 K8s 有了一个比较清晰的认识,也总算明白了之前对着别人的教程创建了 Pods,然后怎么删都删不掉的原因:因为创建的是 ReplicationController,就算删除关联的 Pods 也会被重新创建。

也知道了容器是基于 Linux Kernel 提供的 namespace 和 cgroups 技术来实现的,因此在容器内运行程序几乎没有额外的开销。

本文并没有解释 Pods、ReplicationSet、Services、Deployment 等 K8s 的基础概念,一方面是我担心因为了解不够深入而难以解释清楚(毕竟我也就接触了两三周时间),更重要的是,希望读者能通过阅读书籍和官方文档来获取一手的知识。

本文标题: Kubernetes 初探
最后更新:2020 年 03 月 16 日 - 18:03
本文链接:https://blog.itswincer.com/posts/e0246a27/
本文采用:署名-非商业性使用-禁止演绎 4.0 国际 协议进行许可,阅读 相关说明