mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-19 07:33:47 +08:00
mod
This commit is contained in:
57
极客时间专栏/深入剖析Kubernetes/特别放送/特别放送 | 2019 年,容器技术生态会发生些什么?.md
Normal file
57
极客时间专栏/深入剖析Kubernetes/特别放送/特别放送 | 2019 年,容器技术生态会发生些什么?.md
Normal file
@@ -0,0 +1,57 @@
|
||||
<audio id="audio" title="特别放送 | 2019 年,容器技术生态会发生些什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/60/01/60599d21a5cad232cd2e40fbaa6f3001.mp3"></audio>
|
||||
|
||||
你好,我是张磊。
|
||||
|
||||
虽然“深入剖析Kubernetes”专栏已经更新结束了,但我仍在挂念着每一个订阅专栏的“你”,也希望能多和你分享一些我的观点和看法,希望对你有所帮助。今天我和你分享的主题是:2019年,容器技术生态会发生些什么。
|
||||
|
||||
# 1. Kubernetes 项目被采纳度将持续增长
|
||||
|
||||
作为“云原生”(Cloud Native)理念落地的核心,Kubernetes 项目已经成为了构建容器化平台体系的默认选择。但是,不同于一个只能生产资源的集群管理工具,**Kubernetes 项目最大的价值,乃在于它从一开始就提倡的声明式 API 和以此为基础“控制器”模式。**
|
||||
|
||||
在这个体系的指导下, Kubernetes 项目保证了在自身突飞猛进的发展过程中 API 层的相对稳定和一定的向后兼容能力,这是作为一个平台级项目被用户广泛接受和认可的重要前提。
|
||||
|
||||
**更重要的是,Kubernetes 项目为使用者提供了宝贵的 API 可扩展能力和良好的 API 编程范式,催生出了一个完全基于Kubernetes API 构建出来的上层应用服务生态。可以说,正是这个生态的逐步完善与日趋成熟,才确立了 Kubernetes 项目如今在云平台领域牢不可破的领导地位**,也间接宣告了其他竞品方案的边缘化。
|
||||
|
||||
与此同时,上述事实标准的确立,也使得“正确和合理地使用了 Kubernetes 的能力”,在某种意义上成为了评判上层应用服务框架(比如 PaaS 和 Serverless )的一个重要依据:这不仅包括了对框架本身复杂性和易用性的考量,也包括了对框架可扩展性和演进趋势的预期与判断。
|
||||
|
||||
不过,相比于国外公有云上以 Kubernetes 为基础的容器化作业的高占比,国内公有云市场对容器的采纳程度目前仍然处于比较初步的水平,直接贩卖虚拟机及其关联 IaaS 层能力依然是国内绝大多数公有云提供商的主要业务形态。
|
||||
|
||||
所以,不同于国外市场容器技术增长逐步趋于稳定、Kubernetes 公有云服务已经开始支撑头部互联网客户的情况,**Kubernetes 以及容器技术在国内云计算市场里依然具有巨大的增长空间和强劲的发展势头。**
|
||||
|
||||
不难预测,Kubernetes 项目在国内公有云上的逐渐铺开,会逐渐成为接下来几年国内公有云市场上的一个重要趋势。而无论是国内外,大量 Kubernetes 项目相关岗位的涌现,正是验证这个趋势与变化的一个最直接的征兆。
|
||||
|
||||
# 2. “Serverless 化”与“多样性”将成为上层应用服务生态的两大关键词
|
||||
|
||||
当云上的平台层被 Kubernetes 项目逐步统一之后,过去长期纠结在应用编排、调度与资源管理上裹足不前的 PaaS 项目得到了生产力的全面释放,进而在云平台层之上催生出了一个日趋多样化的应用服务生态。
|
||||
|
||||
事实上,这个生态的本质与2014年之前的 PaaS 生态没有太大不同。只不过,当原本 PaaS 项目的平台层功能(编排、调度、资源管理等)被剥离了出来之后,PaaS 终于可以专注于应用服务和发布流程管理这两个最核心的功能,开始向更轻、更薄、更以应用为中心的方向进行演进。而在这个过程中, Serverless 自然开始成为了主流话题。
|
||||
|
||||
这里需要指出的是,Serverless 从2014年 AWS 发布 Lambda时专门用来指代函数计算(或者说 FaaS)发展到今天,已经被扩展成了包括大多数 PaaS 功能在内的一个泛指术语,即:Serverless = FaaS + BaaS。
|
||||
|
||||
而究其本质,**“高可扩展性”、“工作流驱动”和“按使用计费”**,可以认为是 Serverless 最主要的三个特征。这也是为什么我们会说今天大家所谈论的 Serverless,其实是经典 PaaS 演进到今天的一种“极端”形态。
|
||||
|
||||
伴随着 Serverless 概念本身的“横向发展”,我们不难预料到,2019年之后云端的应用服务生态,一定会趋于多样化,进而覆盖到更多场景下的应用服务管理需求。**并且,无论是Function、传统应用、容器、存储服务、网络服务,都会开始尝试以不同的方式和形态嵌入到“高可扩展性”、“工作流驱动”和“按使用计费”这三个特征当中**。
|
||||
|
||||
当然,这种变化趋势的原因也不言而喻:Serverless 三个特征背后所体现的,**乃是云端应用开发过程向“用户友好”和“低心智负担”方向演进的最直接途径。而这种“简单、经济、可信赖”的朴实诉求,正是云计算诞生的最初期许和永恒的发展方向。**
|
||||
|
||||
而在这种上层应用服务能力向 Serverless 迁移的演进过程中,不断被优化的 Auto-scaling 能力和细粒度的资源隔离技术,将会成为确保 Serverless 能为用户带来价值的最有力保障。
|
||||
|
||||
# 3. 看得见、摸得着、能落地的“云原生”
|
||||
|
||||
自从 CNCF 社区迅速崛起以来,“云原生”三个字就成了各大云厂商竞相角逐的一个关键词。不过,相比于 Kubernetes 项目和容器技术实实在在的发展和落地过程,云原生(Cloud Native)的概念却长期以来“曲高和寡”,让人很难说出个所以然来。
|
||||
|
||||
其实,“云原生”的本质,不是简单对 Kubernetes 生态体系的一个指代。“云原生” **刻画出的,是一个使用户能低心智负担的、敏捷的,以可扩展、可复制的方式,最大化利用“云”的能力、发挥“云”的价值的一条最佳路径**。
|
||||
|
||||
而这其中,**“不可变基础设施”** 是“云原生”的实践基础(这也是容器技术的核心价值);而 Kubernetes、Prometheus、Envoy 等 **CNCF 核心项目,则可以认为是这个路径落地的最佳实践**。这套理论体系的发展过程,与 CNCF 基金会创立的初衷和云原生生态的发展历程是完全一致的。
|
||||
|
||||
也正是伴随着这样的发展过程,云原生对于它的使用者的意义,在2019年之后已经变得非常清晰:**是否采用云原生技术体系,实际上已经成为了一个关系到是不是要最大化“云”的价值、是不是要在“云”上赢取最广泛用户群体的一个关键取舍**。这涉及到的,是关系到整个组织的发展、招聘、产品形态等一系列核心问题,而绝非一个单纯的技术决定。
|
||||
|
||||
明白了这一层道理,在2019年,我们已经不难看到,国内最顶尖的技术公司们,都已经开始在云原生技术框架下发起了实实在在的技术体系升级与落地的“战役”。显然,大家都已经注意到,**相比于纠结于“云原生到底是什么”这样意识形态话题,抓紧时间和机遇将 Kubernetes 及其周边核心技术生态在组织中生长起来,并借此机会完成自身基础技术体系的转型与升级,才是这些体量庞大的技术巨人赶上这次云计算浪潮的不二法宝**。
|
||||
|
||||
在这个背景下,所谓“云原生”体系在这些公司的落地,只是这个激动人心的技术革命背后的一个附加值而已。
|
||||
|
||||
而在“云原生”这个关键词的含义不断清晰的过程中,我们一定要再次强调:**云原生不等于 CNCF,更不等于 Kubernetes**。云原生固然源自于 Kubernetes 技术生态和理念,**但也必然是一个超越 CNCF 和 Kubernetes 存在的一个全集**。它被创立的目的和始终在坚持探索的方向,**是使用户能够最大化利用“云”的能力、发挥“云”的价值,而不是在此过程中构建一个又一个不可复制、不可扩展的“巨型烟囱”。**
|
||||
|
||||
所以说,云原生这个词语的准确定义,是围绕着 Kubernetes 技术生态为核心的,但也一定是一个伴随着 CNCF 社区和 Kubernetes 项目不断演进而日趋完善的一个动态过程。而更为重要的是,**在这次以“云”为关键词的技术革命当中,我们每一个人都有可能成为“云原生”的一个重要的定义者。**
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/96/25/96ef8576a26f5e6266c422c0d6519725.jpg" alt="">
|
||||
@@ -0,0 +1,171 @@
|
||||
<audio id="audio" title="特别放送 | 基于 Kubernetes 的云原生应用管理,到底应该怎么做?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fa/13/fa1d1648d2fe20ee31382103e9e8e513.mp3"></audio>
|
||||
|
||||
你好,我是张磊。
|
||||
|
||||
虽然《深入剖析 Kubernetes》专栏已经完结了一段时间了,但是在留言中,很多同学依然在不时地推敲与消化专栏里的知识和案例。对此我非常开心,同时也看到大家在实践 Kubernetes的过程中存在的很多问题。所以在接下来的一段时间里,我会以 Kubernetes 最为重要的一个主线能力作为专题,对专栏内容从广度和深度两个方向上进行一系列延伸与拓展。希望这些内容,能够帮助你在探索这个全世界最受欢迎的开源生态的过程中,更加深刻地理解到 Kubernetes 项目的内涵与本质。
|
||||
|
||||
随着 Kubernetes 项目的日趋成熟与稳定,越来越多的人都在问我这样一个问题:现在的 Kubernetes 项目里,最有价值的部分到底是哪些呢?
|
||||
|
||||
为了回答这个问题,我们不妨一起回到第13篇文章[《为什么我](https://time.geekbang.org/column/article/40092)[们需要P](https://time.geekbang.org/column/article/40092)[od?》](https://time.geekbang.org/column/article/40092)中,来看一下几个非常典型的用户提问。
|
||||
|
||||
>
|
||||
用户一:关于升级War和Tomcat那块,也是先修改yaml,然后Kubenertes执行升级命令,pod会重新启动,生产也是按照这种方式吗?所以这种情况下,如果只是升级个War包,或者加一个新的War包,Tomcat也要重新启动?这就不是完全松耦合了?
|
||||
|
||||
|
||||
>
|
||||
用户二:WAR包的例子并没有解决频发打包的问题吧? WAR包变动后, geektime/sample:v2包仍然需要重新打包。这和东西一股脑装在tomcat中后, 重新打tomcat 并没有差太多吧?
|
||||
|
||||
|
||||
>
|
||||
用户三:关于部署war包和tomcat,在升级war的时候,先修改yaml,然后Kubernetes会重启整个pod,然后按照定义好的容器启动顺序流程走下去?正常生产是按照这种方式进行升级的吗?
|
||||
|
||||
|
||||
在《为什么我们需要Pod?》这篇文章中,为了讲解 Pod 里容器间关系(即:容器设计模式)的典型场景,我举了一个“WAR 包与 Web 服务器解耦”的例子。在这个例子中,我既没有让你通过 Volume 的方式将 WAR 包挂载到 Tomcat 容器中,也没有建议你把 WAR 包和 Tomcat 打包在一个镜像里,而是用了一个 InitContainer 将 WAR 包“注入”给了Tomcat 容器。
|
||||
|
||||
不过,不同用户面对的场景不同,对问题的思考角度也不一样。所以在这一节里,大家提出了很多不同维度的问题。这些问题总结起来,其实无外乎有两个疑惑:
|
||||
|
||||
1. 如果 WAR 包更新了,那不是也得重新制作 WAR 包容器的镜像么?这和重新打 Tomcat 镜像有很大区别吗?
|
||||
1. 当用户通过 YAML 文件将 WAR 包镜像更新后,整个 Pod 不会重建么?Tomcat 需要重启么?
|
||||
|
||||
这里的两个问题,实际上都聚焦在了这样一个对于 Kubernetes 项目至关重要的核心问题之上:**基于 Kubernetes 的应用管理,到底应该怎么做?**
|
||||
|
||||
比如,对于第一个问题,在不同规模、不同架构的组织中,可能有着不同的看法。一般来说,如果组织的规模不大、发布和迭代次数不多的话,将 WAR 包(应用代码)的发布流程和 Tomcat (Web 服务器)的发布流程解耦,实际上很难有较强的体感。在这些团队中,Tomcat 本身很可能就是开发人员自己负责管理的,甚至被认为是应用的一部分,无需进行很明确的分离。
|
||||
|
||||
而对于更多的组织来说,Tomcat 作为全公司通用的 Web 服务器,往往有一个专门的小团队兼职甚至全职负责维护。这不仅包括了版本管理、统一升级和安全补丁等工作,还会包括全公司通用的性能优化甚至定制化内容。
|
||||
|
||||
在这种场景下,WAR 包的发布流水线(制作 WAR包镜像的流水线),和 Tomcat 的发布流水线(制作 Tomcat 镜像的流水线)其实是通过两个完全独立的团队在负责维护,彼此之间可能都不知晓。
|
||||
|
||||
这时候,在 Pod 的定义中直接将两个容器解耦,相比于每次发布前都必须先将两个镜像“融合”成一个镜像然后再发布,就要自动化得多了。这个原因是显而易见的:开发人员不需要额外维护一个“重新打包”应用的脚本、甚至手动地去做这个步骤了。
|
||||
|
||||
**这正是上述设计模式带来的第一个好处:自动化。**
|
||||
|
||||
当然,正如另外一些用户指出的那样,这个“解耦”的工作,貌似也可以通过把 WAR 包以 Volume 的方式挂载进 Tomcat 容器来完成,对吧?
|
||||
|
||||
然而,相比于 Volume 挂载的方式,通过在 Pod 定义中解耦上述两个容器,其实还会带来**另一个更重要的好处,叫作:自描述。**
|
||||
|
||||
为了解释这个好处,我们不妨来重新看一下这个 Pod 的定义:
|
||||
|
||||
```
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: javaweb-2
|
||||
spec:
|
||||
initContainers:
|
||||
- image: geektime/sample:v2
|
||||
name: war
|
||||
command: ["cp", "/sample.war", "/app"]
|
||||
volumeMounts:
|
||||
- mountPath: /app
|
||||
name: app-volume
|
||||
containers:
|
||||
- image: geektime/tomcat:7.0
|
||||
name: tomcat
|
||||
command: ["sh","-c","/root/apache-tomcat-7.0.42-v2/bin/start.sh"]
|
||||
volumeMounts:
|
||||
- mountPath: /root/apache-tomcat-7.0.42-v2/webapps
|
||||
name: app-volume
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
hostPort: 8001
|
||||
volumes:
|
||||
- name: app-volume
|
||||
emptyDir: {}
|
||||
|
||||
```
|
||||
|
||||
现在,我来问你这样一个问题:**这个 Pod 里,应用的版本是多少?Tomcat 的版本又是多少?**
|
||||
|
||||
相信你一眼就能看出来:应用版本是 v2,Tomcat 的版本是 7.0.42-v2。
|
||||
|
||||
**没错!所以我们说,一个良好编写的 Pod的 YAML 文件应该是“自描述”的,它直接描述了这个应用本身的所有信息。**
|
||||
|
||||
但是,如果我们改用 Volume 挂载的方式来解耦WAR 包和 Tomcat 服务器,这个 Pod 的 YAML 文件会变成什么样子呢?如下所示:
|
||||
|
||||
```
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: javaweb-2
|
||||
spec:
|
||||
containers:
|
||||
- image: geektime/tomcat:7.0
|
||||
name: tomcat
|
||||
command: ["sh","-c","/root/apache-tomcat-7.0.42-v2/bin/start.sh"]
|
||||
volumeMounts:
|
||||
- mountPath: /root/apache-tomcat-7.0.42-v2/webapps
|
||||
name: app-volume
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
hostPort: 8001
|
||||
volumes:
|
||||
- name: app-volume
|
||||
flexVolume:
|
||||
driver: "alicloud/disk"
|
||||
fsType: "ext4"
|
||||
options:
|
||||
volumeId: "d-bp1j17ifxfasvts3tf40"
|
||||
|
||||
```
|
||||
|
||||
在上面这个例子中,我们就通过了一个名叫“app-volume”的数据卷(Volume),来为我们的 Tomcat 容器提供 WAR 包文件。需要注意的是,这个 Volume 必须是持久化类型的数据卷(比如本例中的阿里云盘),绝不可以是 emptyDir 或者 hostPath 这种临时的宿主机目录,否则一旦 Pod 重调度你的 WAR 包就找不回来了。
|
||||
|
||||
然而,如果这时候我再问你:这个 Pod 里,应用的版本是多少?Tomcat 的版本又是多少?
|
||||
|
||||
这时候,你可能要傻眼了:在这个 Pod YAML 文件里,根本看不到应用的版本啊,它是通过 Volume 挂载而来的!
|
||||
|
||||
**也就是说,这个 YAML文件再也没有“自描述”的能力了。**
|
||||
|
||||
更为棘手的是,在这样的一个系统中,你肯定是不可能手工地往这个云盘里拷贝 WAR 包的。所以,上面这个Pod 要想真正工作起来,你还必须在外部再维护一个系统,专门负责在云盘里拷贝指定版本的 WAR 包,或者直接在制作这个云盘的过程中把指定 WAR 包打进去。然而,无论怎么做,这个工作都是非常不舒服并且自动化程度极低的,我强烈不推荐。
|
||||
|
||||
**要想 “Volume 挂载”的方式真正能工作,可行方法只有一种:那就是写一个专门的 Kubernetes Volume 插件(比如,Flexvolume或者CSI插件)** 。这个插件的特殊之处,在于它在执行完 “Mount 阶段”后,会自动执行一条从远端下载指定 WAR 包文件的命令,从而将 WAR 包正确放置在这个 Volume 里。这个 WAR 包文件的名字和路径,可以通过 Volume 的自定义参数传递,比如:
|
||||
|
||||
```
|
||||
...
|
||||
volumes:
|
||||
- name: app-volume
|
||||
flexVolume:
|
||||
driver: "geektime/war-vol"
|
||||
fsType: "ext4"
|
||||
options:
|
||||
downloadURL: "https://github.com/geektime/sample/releases/download/v2/sample.war"
|
||||
|
||||
```
|
||||
|
||||
在这个例子中, 我就定义了 app-volume 的类型是 geektime/war-vol,在挂载的时候,它会自动从 downloadURL 指定的地址下载指定的 WAR 包,问题解决。
|
||||
|
||||
可以看到,这个 YAML 文件也是“自描述”的:因为你可以通过 downloadURL 等参数知道这个应用到底是什么版本。看到这里,你是不是已经感受到 “Volume 挂载的方式” 实际上一点都不简单呢?
|
||||
|
||||
在明白了我们在 Pod 定义中解耦 WAR 包容器和 Tomcat 容器能够得到的两个好处之后,第一个问题也就回答得差不多了。这个问题的本质,其实是一个关于“ Kubernetes 应用究竟应该如何描述”的问题。
|
||||
|
||||
**而这里的原则,最重要的就是“自描述”。**
|
||||
|
||||
我们之前已经反复讲解过,Kubernetes 项目最强大的能力,就是“声明式”的应用定义方式。这个“声明式”背后的设计思想,是在YAML 文件(Kubernetes API 对象)中描述应用的“终态”。然后 Kubernetes 负责通过“控制器模式”不断地将整个系统的实际状态向这个“终态”逼近并且达成一致。
|
||||
|
||||
而“声明式”最大的好处是什么呢?
|
||||
|
||||
**“声明式”带来最大的好处,其实正是“自动化”**。作为一个 Kubernetes 用户,你只需要在 YAML 里描述清楚这个应用长什么样子,那么剩下的所有事情,就都可以放心地交给 Kubernetes 自动完成了:它会通过控制器模式确保这个系统里的应用状态,最终并且始终跟你在 YAML 文件里的描述完全一致。
|
||||
|
||||
这种**“把简单交给用户,把复杂留给自己”**的精神,正是一个“声明式”项目的精髓所在了。
|
||||
|
||||
这也就意味着,如果你的 YAML 文件不是“自描述”的,那么 Kubernetes 就不能“完全”理解你的应用的“终态”到底是什么样子的,它也就没办法把所有的“复杂”都留给自己。这不,你就得自己去写一个额外 Volume 插件去了。
|
||||
|
||||
回到之前用户提到的第二个问题:当通过 YAML 文件将 WAR 包镜像更新后,整个 Pod 不会重建么?Tomcat 需要重启么?
|
||||
|
||||
实际上,当一个 Pod 里的容器镜像被更新后,kubelet 本身就能够判断究竟是哪个容器需要更新,而不会“无脑”地重建整个Pod。当然,你的 Tomcat 需要配置好 reloadable=“true”,这样就不需要重启 Tomcat 服务器了,这是一个非常常见的做法。
|
||||
|
||||
但是,**这里还有一个细节需要注意**。即使 kubelet 本身能够“智能”地单独重建被更新的容器,但如果你的 Pod 是用 Deployment 管理的话,它会按照自己的发布策略(RolloutStrategy) 来通过重建的方式更新 Pod。
|
||||
|
||||
这时候,如果这个 Pod 被重新调度到其他机器上,那么 kubelet “单独重建被更新的容器”的能力就没办法发挥作用了。所以说,要让这个案例中的“解耦”能力发挥到最佳程度,你还需要一个“原地升级”的功能,即:允许 Kubernetes 在原地进行 Pod 的更新,避免重调度带来的各种麻烦。
|
||||
|
||||
原地升级能力,在 Kubernetes 的默认控制器中都是不支持的。但,这是社区开源控制器项目 [https://](https://github.com/openkruise/kruise)[github.com/open](https://github.com/openkruise/kruise)[kru](https://github.com/openkruise/kruise)[ise/kruise](https://github.com/openkruise/kruise) 的重要功能之一,如果你感兴趣的话可以研究一下。
|
||||
|
||||
## 总结
|
||||
|
||||
说到这里,再让我们回头看一下文章最开始大家提出的共性问题:现在的 Kubernetes 项目里,最有价值的部分到底是哪些?这个项目的本质到底在哪部分呢?
|
||||
|
||||
实际上,通过深入地讲解 “Tomcat 与 WAR 包解耦”这个案例,你可以看到 Kubernetes 的“声明式 API”“容器设计模式”“控制器原理”,以及kubelet 的工作机制等很多核心知识点,实际上是可以通过一条主线贯穿起来的。这条主线,从“应用如何描述”开始,到“容器如何运行”结束。
|
||||
|
||||
**这条主线,正是 Kubernetes 项目中最具价值的那个部分,即:云原生应用管理(Cloud Native Application Management)**。它是一条连接 Kubernetes 项目绝大多数核心特性的关键线索,也是 Kubernetes 项目乃至整个云原生社区这五年来飞速发展背后唯一不变的精髓。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/96/25/96ef8576a26f5e6266c422c0d6519725.jpg" alt="">
|
||||
Reference in New Issue
Block a user