这篇文章继续聊容器编排,聊一下最重要的容器编排技术Kubernetes。
大约在几年以前,容器编排还存在一些竞争,比如Kubernetes,Docker Swarm等。但是,从现在的情况来说,Kubernetes几乎占据绝对的主流,成为了容器编排事实上的标准。
而所谓的CNCF(云原生计算基金会)下的这些项目,几乎都是以K8S为核心,在K8S基础之上提供平台层面的能力补充。诸如用fluentd来做日志收集,envoy来做边缘网关,jaeger来做链路追踪等。
Kubernetes,简称K8S。来源于Google的大规模容器化编排与运营的实践与技术之上。我们都清楚,Docker对于容器化起到非常重要的推动,它极大的简化了容器技术的实现与使用,使之迅速流行起来。但早在Docker及容器化大众化兴起之前,诸如AWS,Google这样的大公司内部已经是发展出了大规模服务编排与运营的技术。
最开始,Google内部使用的是Brog这个技术,主要是用做管理长期运行的服务及批量化部署等编排能力,在使用了很长一段时间并发展出了很多诸如服务服务与查找,负载均衡,自动扩容等能力,随后Google在借鉴Brog的经验之上,发展出了新的容器编排技术- Omega。但无论是Brog还是Omega,都是Google内部使用的技术。
最后,在Docker使得容器变得流行之后,Google重新开发了Kubernetes,并将其开源出来,最终捐献给了CNCF。Kubernetes借鉴了大量的Brog与Omega的经验,由于出众的能力与可靠性,使得其迅速成为最受欢迎的容器编排技术。
K8S是最大特色是声明式部署,虽然它也支持命令式部署,但我们更愿意使用声明式部署,特别是在大规模服务的管理与运维中,声明式部署的优点特别明显。
命令式部署是传统的部署方式,你通过命令,主动告知系统需要做什么。而部署一个项目,可能是一系列命令的集合,比如先下载安装包,再解压,再安装,再做成服务,再运行,这一系列的过程都需要你一步一个命令来实现。
而声明式部署则完全不同,它只需要你提供一个期望的状态的声明文件,背后的支撑机制会帮你去实现从镜像下载,镜像运行,启动你期望的实例数量等,最终达到你声明的期望。
K8S之所以能应对大规模服务,根本原因就在于它这种声明式部署的优势,否则大规模的部署与运营难以实现。
如下面这个示例,仅通过定义一个yml文件,来声明我期望部署一个distributed
的服务,实例数是3
,部署镜像是lingen/java-grpc-sample-distributed:1
我不需要关心K8S是怎么实现的期望的,我要关心只是告知K8S我的期望。这就是声明式部署。
apiVersion: apps/v1
kind: Deployment
metadata:
name: distributed
labels:
app: distributed
spec:
replicas: 3
selector:
matchLabels:
app: distributed
template:
metadata:
labels:
app: distributed
spec:
containers:
- name: distributed
image: lingen/java-grpc-sample-distributed:1
ports:
- containerPort: 8080
K8S的相关书与教程也非常多,如果想彻底了解它,阅读官网的教程与其它书是必不可少的;而要掌握它,则需要大量的实践与尝试。
而本篇文章,我仅列了一些K8S中的关键技术点,理解了这些技术点,你也能对K8S有个非常初步的理解了。
K8S是容器编排技术,它并不是容器技术,这一点要区分开来。K8S本身是运行在容器技术的上一层,提供容器的编排,管理与大规模运营能力。
这就意味着,K8S需要一个容器运行引擎。K8S本身并不自带一个这样的引擎,需要你去安装一个它支持的引擎技术。
早些年,K8S与特定的容器技术绑定,如Docker。但这种方式肯定不是一个好方法,编程的我们都清楚,依赖接口与协议远优于依赖于特定技术。所以K8S提出了CRI(容器运行引擎)规范。任何支持CRI的容器技术,K8S都可以运行。
当前,Docker是基于Containerd容器引擎的技术,而Containerd是实现了CRI规范的容器技术。所以,如果需要一个K8S环境,你需要首先安装Containerd或Docker容器技术,这两个仍然是最主流的选择。
这并不是你的唯一选择,还有其它一些容器技术,也在K8S的支持之列,比如K8S官网还列出了诸如CRI-O,Mirantis Container Runtime等可选项。
事实上,使用K8S,你只需要一个这样的容器运行引擎,并不需要去使用或关注它。因此,选择你最熟悉的一种就可以了,当前主流可能仍然是Docker Engine
K8S是容器编排技术,本质是是对大规模服务的管理,理所当然的需要大量节点来支持大量服务的运行。
这样的服务节点在一些大型系统中可能是成千上万,甚至更多。因此K8S是如何来管理这些节点的?这就依赖于K8S的的核心管理模式,管理节点与工作节点。
K8S是由少数的管理节点(通常是3,5或7个)以及不限数量的大量的工作节点共同协作完成的。
工作节点的任务就是运行各个不同的服务,至于运行哪个服务,运行多少数量的服务,工作节点并不关心,统一由管理节点来分配与安排。
管理节点的任务与之相对应,就是用来协调,分配,监控工作节点的。管理节点接受到任务,根据一定的规则来分配任务到不同的工作节点,同时根据设定来监控服务的健康并在适当的时候做出任务的变更(比如服务当机时重启一个新的服务)
而做为运维的程序员,只需要同管理节点打交道就可以了。我们通常在管理节点上通过kubectl来管理与部署服务。
熟悉容器技术的都知道,容器的最小单位是镜像。但在K8S中,一个最小的单位并不是镜像,而是Pod,而所谓的Pod,内部包含有一个或多少镜像。
其实严格说来,一个Pod并不只有一个镜像,就算你只指定一个镜像。每个Pod默认就带有一个名为Pause的镜像,而这个Pause镜像是给这个Pod内部提供统一的网络及存储等通用服务的。
这样的好处在于,K8S在工具层面,基于这种Pod允许多个镜像的机制,提供了很多能力,比如enovy这样的服务网格,还有很多边车服务能力,都是依赖于这种设计。
在Pod中部署一个服务镜像,再添加一个或多少支持能力镜像,比如网络代理,日志收集,负载均衡,授权认证等,这样就把架构上的这些需求从架构层上交到了K8S容器编排这一层,可以简化了架构层的负担,你在架构中更多的只需要考虑业务服务的实现就OK了。怎么用代理能力(重试,超时,断路等)来优化分布式可靠性,怎么做日志收集,这些K8S可以结合一些连车服务来帮你实现。
虽然最小部署单位是Pod,但在实际使用中,很少会直接以Pod来做为基本部署单位。而最常用的部署单位,则是Deployment,Service,Daemonset,CronJob等几种。
它们各有特点,适合不同的场景。
Deployment
Deployment是基于ReplicaSet之上提供的一种部署模式。而ReplicaSet则是基于Pod之上的支持自愈,扩展的一种部署能力。而Deployment则在ReplicaSet之上,又提供了扩容,更新与回滚等更进一步的能力。所以,基本上,我们对于服务的最主要的部署模式就是Deployment。
Service
Service是对一系列Pod集的调用封装,在K8S中,服务的运行节点与IP等参数都是不可预测的,所以直接访问服务是很难做到的,而且每个服务都有N多个实例,这就需要一种机制来对这些实例的访问进行抽象与负载。这就是Service的能力。
通过Service,你只需要知道Service的地址(DNS或名称,或Global IP等),就可以访问到Service背后的这些Pod实例,还能进行负载等。
Daemonset
Daemonset是一种特别的部署单位。它是在每个工作节点上额定会运行一个这样的服务。特别适合诸如日志收集,节点监控等场景。
只要你部署一个Daemonset,每加入一个新的工作节点时,会自动启动一个类似的Daemonset服务。而工作节点从K8S中删除之后,这个Daemonset服务也会自动停掉。
CronJob
Job是执行一次性任务的部署单位,这个服务只运行一次,运行完就停止。而在Job之上,还有CronJob,顾名思议,它是定期运行一些任务的概念。
如果你有一些需要定期执行的任务,使用CronJob非常适合的。
在云服务中,我们尽量让服务属无状态,这是最佳的实现方式,无状态的服务可以随意的扩容,而不用担心有状态需要处理。
在实现中,我们可以尽量把有状态的服务变更为无服务状态,比如一个服务有用户会话Session的状态,那我们可以把Session迁移到Redis缓存中,这样这个服务就变成无状态的了。
但一个系统中,不可能所有功能点都能做到无状态,有时候一些状态是必不可少的,比如典型的数据库。任务一个数据库服务都是有状态的,它的数据是需要存储与加载的。
所以,K8S也提供了支持有状态服务的部署单元,它就是StatefulSet了,StatefulSet适合部署一些有状态的服务。K8S保证一个有状态的服务停止掉,下次重启时,状态仍然会得以保留。
但是仍然要非常小心,尽量避免有状态的服务。有状态的服务很容易成为系统的阻碍。
任何一个系统,不可能不需要存储。同样,在大规模容器运行的环境中,如何处理存储是必不可少的一环了。
对于存储,K8S提供了PV与PVC的机制来支持存储的实现。
PV是PersistentVolume,是最小存储单元。K8S的管理员,需要事先申明与定义一些PV存储,PV支持本机,也支持很多云存储。
而PVC则是PersistentVolumeClaim,是一个部署申明需要存储的声明。比如你部署的某个服务,需要10G的存储,你可以定义一个PVC,这样K8S在启动服务时,会满足你的申明,分配10G的存储给你。
K8S同样提出了CSI(容器存储接口)标准,任何实现了CSI标准的存储技术,都可以被K8S使用。
早期PV都是静态存储申明,现在又增加了支持动态申明,就是不限定存储容量,按需分配。
在系统中,我们经常需要一些配置文件,甚至一些用户名与密码等。比如数据库的用户名与密码。而在分布式环境中,如何处理统一的配置也非常重要。
K8S同样也提供了ConfigMap与Secret的概念,用于配置的支持。你可以将你的配置定义在ConfigMap中,如果是机密的信息,那就定义在Secret中。
这样在定义部署单元时,你可以引用这些配置,无论你最终的服务是运行在哪个工作节点上,都能顺利的读取到对应的配置。
上面这些只是K8S中最基础,也是最常用的一些能力。当然K8S做为一个复杂的云原生系统,是非常强大与复杂的,仍然有更多的能力没有列出来。但上面这些是你开始学习与使用K8S时,最先需要理解的概念。
事实上,也是因为K8S中这些概念太多,导致K8S被认为非常复杂,难以学习与掌握。学习与掌握K8S的确需要一定的时间,特别是不断的动手与试错是非常重要的。
但理解它的一些核心理念,是根本的前提。
希望我的这篇文章能让你对K8S有一个初步的理解。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有