随着容器化部署方案的成熟,以及Kubernetes成为容器编排领域的实施标准,有越来越多的企业逐渐接受并将自己的核心业务系统迁移到Kubernetes平台。Kubernetes的流行有很多驱动因素,但容器化部署成熟以及微服务架构绝对是最为核心的两个。从运维的角度看,随着开发和运维团队对构建,运行和运维容器化部署的微服务应用有体系化的诉求,Kubernetes恰逢时机的出现,成了很多企业的选择。不过和Linux这样的开源操作系统相比,Kubernetes显得还很“年轻”,这种年轻不光体现在时间,也体现在平台的成熟度以及客户的接受程度。笔者接触的很多客户都声称自己的企业onboard了Kubernetes,深入交流的结果是只有少数将实际的生产业务跑在集群上,大规模运行Kubernetes的更是少之又少。
本着修炼自己,造福他人的原则,笔者决定结合自己过往几年大规模Kubernetes落地实施的经验,从如何将应用部署到Kubernetes平台为目的,来为读者带来经过实践验证,行之有效的Kubernetes生产化之路。考虑读者可能接触Kubernetes平台实践不长,Kubernetes本身的复杂度决定了我们必须对后续文章涉及到的基本概念进行介绍,才能达成有更多受众群体的目的。因此咱们第一部分包含三篇文章,主要围绕Kubernetes,应用程序平台,以及如何在Kubernetes上构建应用程序平台这三个问题,咱们第一篇文章先从认识和定义Kubernetes容器化部署平台开始。
谈到Kubernetes,“容器调度平台”这个标签已经显得有点管中窥豹了,因为Kubernetes已经远远超出容器调度这个范畴,我们可以在Kubernetes中看到和基础设施管理相关的模块,比如存储,网络等;我们也经常把Kubernetes称作是应用程序部署平台,比如应用程序的部署,扩缩容,服务接入,服务治理等。因此我们很难再用容器调度和编排平台这样的定义来准确的表述Kubernetes,我们需要更加完整的定义。虽说定义本身并不复杂,当你试图为Kubernetes这样快速发展的平台寻找一个准确定义的时候,挑战是非常艰巨的,因为很有可能当我们写完Kubernetes平台的准确定义,下一刻就过期了。百度上从不缺关于Kubernetes的优秀定义,咱们就不画蛇添足了,笔者决定从Kubernetes解决问题的角度,为大家从实用的角度,详细介绍Kubernetes给应用程序部署和运维带来的价值。
Kubernetes生产化之路系列文章的目标是让读者,特别是作为企业的CIO,运维负责人,架构师能够通过阅读这些文章,在企业内部构件出合适的Kubernetes集群和运维,监控流程,让企业的核心业务系统能够顺利的部署到Kubernetes平台上,进而充分享受云计算带来的灵活性和云原生架构带来的可靠性和稳定性,以及Kubernetes平台带来的成本优化,以及综合经济效益。
Kubernetes这个名称可以说包罗万象,它并不只是代表一个应用,组件,而是一组工具,组件和软件的统称,因此读者需要理解Kubernetes是一个umbrella(伞)类型的概念,如果我们查看Github上Kubernetes项目(https://kubernetes.io),你会发现这个项目下有75个子仓库(截止2021-12-12)。另外一个和Kubernetes相关的项目kubernetes-sigs下有136个仓库,云原生基金会CNCF项目下的仓库数量基本持平,笔者在写这篇文章的时候,共有111个仓库工程。并且随着时间的推移,毫无疑问的是这些数量只会变多,不会减少。
这么多代码工程,组件以及工具,为了不失去焦点,咱们系列文章会围绕核心工程kubernetes/kubernetes这个工程展开,这个工程中包含的是最核心的支撑Kubernetes集群运行的组件和工具,这些也是我们在大多数Kubernetes集群能够找到的组件。当kubernetes/kubernetes下的代码,组件运行起来后,我们可以从运行集群中看到如下的功能清单:
- 应用程序能被调度到多个个工作节点上运行
- 集群对外暴露了一组声明式的接口供开发和运维人员使用,并且这些API接口支持扩展能力
- 除了直接调用API接口之外,平台还提供了客户端命令行工具kubectl,运维人员可以通过客户端工具来操作集群
- 集群提供了服务(services)抽象,让运行在POD中的应用能够被外部应用访问
- 集群提供了网络,存储以及容器运行时的扩展接口,来灵活支持更多的使用场景
当然上边的清单并不完整,但是从笔者的经验看,上边罗列的功能构成了我们通常认知的“生产级容器调度平台(production grade container orchestrator)”。企业级容器调度平台这个词对很多同学来说稍微有点抽象,用大白话说Kubernetes为了我们提供在多个宿主机上运行和调度容器化部署应用的能力。这是Kubernetes这个平台最核心的功能,当然随着后续文章的展开,我们会发现要做好这个应用部署调度的事情,平台需要做更多的事情,导致Kubernetes越来越负责,以及Kubernetes是如何通过接口来逐步构建出云计算时代的操作系统,并将某些核心能力的实现留给生态,秉承做大做强这个理念。
读到这里读者可能会问,Kubernetes提供的这些功能,背后到底是如何实现的?或者说具体有哪些组件工作在一起,提供了这些容器调度,监控,扩展等功能。前文中咱们提到Kubernetes的核心功能组件和源代码都在kubernetes/kubernetes仓库中,但是仓库中的是代码或者可运行的二进制程序,离我们能够感受到的,比如通过kubectl apply来部署一个deployment中间有加大的GAP。
举个例子,很多同学使用Kubernetes都是通过阿里巴巴提供的ACK服务,我们通过在自己的阿里云账号上开通ACK服务来部署容器应用到集群中,如果读者有权限查看集群的Master节点和Worker节点,应该不难在阿里云托管的集群中找到kubernetes/kubernetes这个目录下的所有组件。除了阿里云的托管服务之外,也有些读者自己搭建了一套Kubernetes集群(笔者强烈建议使用阿里云的托管服务来运行自己的生产环境,上云就上阿里云不仅仅是一句campaign,阿里云上提供的ACK服务绝对是灵活性,易用性,稳定性,安全性最高的托管服务),安装包要么从github上直接下载,要么下载源代码自己编译,以及从厂商那里直接获取编译好的签名版本。无论哪种方式,我们都可以通过在对应的解压包中运行cluster/get-kube-binaries.sh来自动探测安装环境,以及下载适合安装环境的客户端和服务器端组件二进制包。
咱们来验证一下,笔者在自己机器上下载了最新的release版本,然后解压后,运行./cluster/get-kube-binaries.sh,输出如下:
Kubernetes release: v1.21.6
Server: linux/amd64 (to override, set KUBERNETES_SERVER_ARCH)
Client: linux/amd64 (autodetected)
Will download kubernetes-server-linux-amd64.tar.gz from https://dl.k8s.io/v1.21.6
Will download and extract kubernetes-client-linux-amd64.tar.gz
Is this ok? [Y]/n
当我输入Y之后,脚本会自动下载所有的客户端和服务器端二进制可执行文件。下载完成后,我们就可以在服务器端组件对应文件夹中找到Kubernetes集群核心组件:
- API Server,此组件是Kubernetes集群和外部交互的唯一窗口。对于使用kubectl的用户来说,我们kubect get,apply,delete操作背后的原理就是调用API Server提供的Restful接口。对于状态更新操作,API Server会将应用程序的状态持久化到etcd数据库服务中。
- kubelet组件运行在集群中的服务器上,或者叫node上。kubelet也被称作是on-host agent代理,主要用来和API Server进行双向通信,比如获取调度器分配给节点的任务,报告节点上任务的运行状态等。另外kubelet也负责和节点上的容器运行时交互(比如docker),来驱动运行时将应用程序运行起来,并健康运行。
- Controller Manager是一组controllers的集合,多个类型的controller被构建到相同的二进制可执行文件中,controller的核心任务是调谐,也就是将应用的当前状态调谐到用户的期望状态。我们一般通过kubectl apply或者kubectl scale来向Kubernetes声明我们期望的应用程序状态,当状态被更新到etcd后,控制器会负责对比应用的当前状态和期望状态,如果有任何不一致,controller负责将状态调谐一致。
- Scheduler调度器的核心任务是为任务确定最合适的运行节点,从原理上讲,调度器通过filtering和scoring两个阶段来为应用程序确定部署节点。
- Kube proxy用来为Service服务提供虚拟IP能力,由于POD的易变性,我们需要具备固定IP的Kubernetes对象作为对外提供服务入口,来讲对服务的访问负载到多个服务支撑POD上。从原理上讲,服务的这种负载能力通过数据包过滤机制来实现,依赖于Linux操作系统的iptables和ipvs内核能力。
脚本运行完成后,我们可以在本地下载文件夹中看到所有上边提到的组件,如下图所示:
当然除了上边介绍的5个组件之外,Kubernetes还有很多其他的组件协作来完成容器化应用程序的部署和调度工作,比如和网络,存储相关的插件,但是基于笔者过去多年的经验,这五个组件是整个Kubernetes集群的核心组件,套用DDD的属于就是,这五个组件是Kubernetes生产化这个领域的核心域,使我们必须掌握并且精通的那20%。接下来咱们看看Kubernetes的架构图,读者需要注意的是这张图并不是面面俱到,因为在有些部署方案中,kube-apiserver,scheduler和container会以容器的方式运行,这就意味着master节点上也可能运行着docker这样的运行时,并且也运行了kubelet,以及kube-proxy等。不过这张图应该和大部分生产环境的部署架构一致。
注:上图中虚线中的组件一般不认为是Kubernetes的核心组件,但是这些组件对于我们部署和运行应用程序来说,至关重要。
上图中大部分组件咱们已经反复介绍过很多次了,特别是和容器调度相关的API Server,调度器,控制器以及kubelet四个组件。Kubernetes除了容器调度之外,还提供了很多和应用程序相关的其他功能,比如kube-proxy组件为一组应用实例(PODS)提供了虚拟IP,这样外部的客户端就可以访问这个固定的IP地址,解决POD易变性导致的IP地址易变性问题。
理论上Kubernetes可以不把Service作为核心组件来实现,而是定义一组Service API,把具体的实现留给厂商作为插件扩展,用户在具体使用的时候,就需要从多个具体实现中来选择最适合自己业务场景的方案。这样做的好处是显而易见的,灵活性得到了进一步的提升,并且缩减了Kubernetes核心组件的范围,稳定性和复杂性得到了进一步的提升。
在Kuberntes体系中,这种Kuberntes定义接口规范(interface),社区和厂商来具体实现的模式已经非常常见,我们熟悉的Ingress和NetworkPolicy采用的就是这种模式,具体来说,在Kubernetes中创建Ingress对象并不总是能成功,因为虽然说这个API存在,但是由于不是core(核心)功能,因此团队必须在众多的实现方案中选择一个并安装,Kubernetes才能调用具体的实现来创建对应的Ingress对象,解决南北流量的问题。
对于Ingress的具体实现来说,ingress-nginx是最大家熟知的一种,ingress-nginx会在集群上创建对应的控制器,控制器会在我们创建ingress对象的时候,结合配置信息,为我们创建对一个的Ngxin实例,并配置Nginx实例将访问流量分发到多个后端的POD实例中。除了ingress-nginx之外,Project Contour是另外一个选项,实现了相同的Ingress API,但是具体实现上使用代理envoy来实现流量分发。正式由于Kubernetes提供的这种灵活的模式,我们在设计应用部署方案的时候,可以从很多优秀的实现中选择最符合自己所面对业务场景的一个。
Kubernetes提供的这种接口设计模式让我们可以灵活的扩展Kuberntes的功能来满足各种各种的定制化需求。接口这个概念对有经验的开发人员来说并不面陌生,特别是微服务架构下,我们每天几乎都在围绕这接口设计,开发,实现,测试,集成等等,接口本质上就是一个合同,定义了信息交互双方应该遵守的规则,规则包含但不限于:数据模式,类型,协议,参数,返回值等。在Kuberntes体系中,Kubernetes这种接口模式和我们日常的应用开发完全相同,具体来说Kubernetes定义了某项功能的功能(接口),厂商和社区通过实现插架来具体实现这些接口来,插件需要被安装到集群的节点上,来提供具体功能的实现,典型的例子就是网络插件calico等。
另外一个典型的接口/插件模式是CRI(Container Runtime Interface,也叫容器运行时接口)。在Kubernetes刚刚出现的时候,只支持唯一的Docker运行时,随着Kubernetes逐渐发展和被广泛接受,社区认识到只支持一种运行时的局限性,并且在核心代码分支上维护多套容器运行时代码显得非常臃肿,最要命的就是Kubernetes的版本发布和下游厂商强依赖,因此社区很早就通过定义CRI接口来规范运行时应该提供的功能,而Docker只是是现在这套CRI接口的一种运行时。除了Docker之外,我们还可以选择containerd以及CRI-O来在节点上运行容器化应用。下图是运行时和Kuberntes的核心中间件kubelet之间的关系。
在Kubernetes定义的诸多接口中,接口请求比如CreateContainerRequest或者PortForwardRequest通常以RPC(remote procedure call)的方式进行调用。对于CRI来说,kubelets和容器运行时间的通信通过GRPC协议进行,并且kubelet发送请求后,期望分别收到CreateContainerResponse和PortForwardResponse响应。
如果大家仔细观察图1.3所示的两种CRI实现方式稍有不同,具体来说CRI-O由于从头开始完整实现了容器运行时接口能力,因此kubelet直接发送容器实例相关的指令给CRI-O组件;而containerd由于在CRI之前就已经被广泛使用,因此containerd实现了一个叫shim的插件,来作为代理解决CRI和containerd接口不匹配的问题。不过从结果上看,无论哪种类型的架构,kubelet都可以在不了解具体实现插件工作原理的前提下,驱动容器运行时来进行容器实例的创建,管理工作。
正是由于CRI接口和插件实现这种模式的灵活性,随着时间的推移,我们看到Kubernetes体系中越来越多的核心组件被标准接口化,也就是具体的实现被从核心工程中移出。我们通常把包含在Kubernetes核心代码中的功能称作“in-tree”功能,in-tree功能代表此功能的实现代码被维护在kubernetes/kubernetes代码目录中。
另外一个典型的例子是CPI功能(Cloud-provider integrations,也就是和云平台的集成接口),大部分CPIs集成功能传统上都直接被硬编码在核心组件kube-controller-manager或者kubelet中,CPI集成代码主要负责像创建存储数据卷,负载均衡等。由于要处理的数据卷类型很多,因此不可避免的要经常修改这部分代码,特别是经常会出现新的云平台服务商,因此将这些代码维护在in-tree中不是一个好主意。
社区当然也意识到这个问题,通过将这些CPI相关的接口功能从核心功能中移动到自己的接口,来分离核心组件和CPI功能这两种不同的关注点。因此所有和CPI相关的功能都被移动到kubernetes/cloud-provider目录下,允许我们在不涉及核心组件的情况下,来对CPI的功能进行版本更新,功能扩展以及解决缺陷和安全隐患等。
正是由于社区的高瞻远瞩,因此我们在最新版本的Kubernetes中可以看到如下罗列的接口定义,这些接口留给生态以及厂商巨大的扩展空间,是Kubernetes被广泛接受的关键:
- Container Networking interface(CNI)接口让网络供应商可以定制化从IPAM(IP分发管理)到数据包路由的所有功能。
- Container Storage interface(CSI)接口允许存储供应商提供应用程序和集群运行过程中的存储需求,常见的存储技术包括但不限于ceph,vSAN和EBS。
- Container Runtime interface(CRI)接口咱们上边详细介绍过,允许Kubernetes将任务调度到不同的运行时上,比如Docker,containerd,CRI-O,podman等。另外随着Kubernetes的流行,出现了一些不那么容器化的运行时,比如firecracker,提供了一种基于VM隔离性更好的应用程序运行方式(依赖于内核的KVM功能)。
- Service Mesh inteface(SMI)是一组比较新的接口定义,主要是规范服务网格的功能接口,比如流量策略,流量管理,监控数据收集等。
- Cloud Provider interface(CPI)接口咱们前边也介绍过,通过定义和云厂商阿里云,华为云,AWS等集成接口,来充分使用云厂商提供的能力,并最大限度解决云厂商锁定的问题。
- Open Container Initiative Runtime spec(OCI)规范标准化了容器镜像的格式,让不同工具构建的容器镜像相互兼容,可以在任何兼容OCI的运行时上被run起来,虽然这个规范和Kubernetes没有直接的关系,但是间接上促进了CRI被广泛接受。
最后我们来总结一下Kubernetes,首先Kubenretes是一个容器调度平台,其次Kubernetes包含了诸如服务,网络策略,Ingress等其他功能。另外Kubernetes提供了一组可扩展的接口,来让我们通过插件的方式自定义或者扩展Kubernetes的默认功能。这些优秀的PASS能力让kubernetes成为很多企业梦寐以求的应用程序平台。
但是结合笔者过去几年的从业经验,事情并没有那么简单,如果我们只是将现有的应用程序简单的迁移到Kubernetes平台上,就完事了吗?当然事情没有这么简单,并且把应用程序从现有的部署平台迁移到Kubernetes上,并不是简单的重新在Kubernetes上部署应用这么直接,我们还需要考虑应用依赖的组件,配置,底层的机器等等。
笔者在过去几年接触过很多不同类型的企业,大家都非常清楚Kubernetes给企业带来的价值,因此将数字化转型涉及到部署和应用程序管理部分聚焦于如何把Kubernetes引进来并用起来。实际上这是相当失焦的一种做法,因为Kubernetes只是一种进行容器话部署的技术而已,不应该是解决企业数字化转型需要的基础设施现代化,平台化工具的全部。
虽然说这个道理非常的显而易见,但是笔者观察到很多企业的CIO或者技术负责人,都把引进Kubernetes当做“银弹”,笔者需要反复的强调,Kubenrete只是一种技术,无法解决应用程序交付的问题,软件研发的问题,以及组织和人员结构的问题。Kubernetes是企业数字化转型需要的众多武器中的一个,也是一种火力强大的武器,需要和其他的“武器”一起工作,来帮助企业解决数字化转型遇到的诸多挑战。
好了,这篇文章的内容就这么多了,咱们有了对Kubernetes的全面认知只有,下篇文章围绕应用程序平台,来看看什么是应用程序平台,有哪些特性,敬请期待!