first commit
This commit is contained in:
163
专栏/CNCFX阿里巴巴云原生技术公开课/01第一堂“云原生”课.md
Normal file
163
专栏/CNCFX阿里巴巴云原生技术公开课/01第一堂“云原生”课.md
Normal file
@ -0,0 +1,163 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
01 第一堂“云原生”课
|
||||
本节课程要点
|
||||
|
||||
|
||||
云原生技术发展历程(为什么要学习这门课)
|
||||
课程简介与预备知识(这门课到底教什么)
|
||||
云原生的定义与技术要点(本节正式内容)
|
||||
|
||||
|
||||
为什么要开设云原生技术公开课?
|
||||
|
||||
云原生技术发展简史
|
||||
|
||||
首先从第一个问题进行分享,那就是“为什么要开设云原生技术公开课?”云原生、CNCF 都是目前非常热门的关键词,但是这些技术并不是非常新鲜的内容。
|
||||
|
||||
|
||||
2004 年— 2007 年,Google 已在内部大规模地使用像 Cgroups 这样的容器技术;
|
||||
2008 年,Google 将 Cgroups 合并进入了 Linux 内核主干;
|
||||
2013 年,Docker 项目正式发布。
|
||||
2014 年,Kubernetes 项目也正式发布。这样的原因也非常容易理解,因为有了容器和 Docker 之后,就需要有一种方式去帮助大家方便、快速、优雅地管理这些容器,这就是 Kubernetes 项目的初衷。在 Google 和 Redhat 发布了 Kubernetes 之后,这个项目的发展速度非常之快。
|
||||
2015 年,由Google、Redhat 以及微软等大型云计算厂商以及一些开源公司共同牵头成立了 CNCF 云原生基金会。CNCF 成立之初,就有 22 个创始会员,而且 Kubernetes 也成为了 CNCF 托管的第一个开源项目。在这之后,CNCF 的发展速度非常迅猛;
|
||||
2017 年,CNCF 达到 170 个成员和 14 个基金项目;
|
||||
2018 年,CNCF 成立三周年有了 195 个成员,19 个基金会项目和 11 个孵化项目,如此之快的发展速度在整个云计算领域都是非常罕见的。
|
||||
|
||||
|
||||
云原生技术生态现状
|
||||
|
||||
因此,如今我们所讨论的云原生技术生态是一个庞大的技术集合。CNCF 有一张云原生全景图(https://github.com/cncf/landscape),在这个全景图里已经有 200 多个项目和产品了,这些项目和产品也都是和 CNCF 的观点所契合的。所以如果以这张全景图作为背景,加以思考就会发现,我们今天所讨论的云原生其实主要谈论了以下几点:
|
||||
|
||||
|
||||
云原生基金会 —— CNCF;
|
||||
云原生技术社区,比如像 CNCF 目前正式托管的 20 多个项目共同构成了现代云计算生态的基石,其中像 Kubernetes 这样的项目已经成为了世界第四活跃的开源项目;
|
||||
除了前面两点之外,现在全球各大公有云厂商都已经支持了 Kubernetes。此外,还有 100 多家技术创业公司也在持续地进行投入。现在阿里巴巴也在谈全面上云,而且上云就要上云原生,这也是各大技术公司拥抱云原生的一个例子。
|
||||
|
||||
|
||||
我们正处于时代的关键节点
|
||||
|
||||
2019 年正是云原生时代的关键节点,为什么这么说?我们这里就为大家简单梳理一下。 从 2013 年 Docker 项目发布开始说起,Docker 项目的发布使得全操作系统语义的沙盒技术唾手可得,使得用户能够更好地、更完整地打包自己的应用,使得开发者可以轻而易举的获得了一个应用的最小可运行单位,而不需要依赖任何 PaaS 能力。这对经典 PaaS 产业其实是一个“降维打击”。 2014 年的时候,Kubernetes 项目发布,其意义在于 Google 将内部的 Borg/Omega 系统思想借助开源社区实现了“重生”,并且提出了“容器设计模式”的思想。而 Google 之所以选择间接开源 Kubernetes 而不是直接开源 Borg 项目,其实背后的原因也比较容易理解:Borg/Omega 这样的系统太复杂了,是没办法提供给 Google 之外的人使用,但是 Borg/Omega 这样的设计思想却可以借助 Kubernetes 让大家接触到,这也是开源 Kubernetes 的重要背景。 这样到了 2015 年到 2016 年,就到了容器编排“三国争霸”的时代,当时 Docker、Swarm、Mesos、Kubernetes 都在容器编排领域展开角逐,他们竞争的原因其实也比较容易理解, 那就是 Docker 或者容器本身的价值虽然大,但是如果想要让其产生商业价值或者说对云的价值,那么就一定需要在编排上面占据一个有利的位置。 Swarm 和 Mesos 的特点,那就是各自只在生态和技术方面比较强,其中,Swarm 更偏向于生态,而 Mesos 技术更强一些。相比之下, Kubernetes 则兼具了两者优势,最终在 2017 年“三国争霸”的局面中得以胜出,成为了当时直到现在的容器编排标准。这一过程的代表性事件就是 Docker 公司宣布在核心产品中内置了 Kubernetes 服务,并且 Swarm 项目逐渐停止维护。 到了 2018 年的时候,云原生技术理念开始逐渐萌芽,这是因为此时 Kubernetes 以及容器都成为了云厂商的既定标准,以“云”为核心的软件研发思想逐步形成。 而到了 2019 年,情况似乎又将发生一些变化。
|
||||
|
||||
2019 年——云原生技术普及元年
|
||||
|
||||
为什么说 2019 年很可能是一个关键节点呢?我们认为 2019 年是云原生技术的普及元年。 首先大家可以看到,在 2019 年,阿里巴巴宣布要全面上云,而且“上云就要上云原生”。我们还可以看到,以“云”为核心的软件研发思想,正逐步成为所有开发者的默认选项。像 Kubernetes 等云原生技术正在成为技术人员的必修课,大量的工作岗位正在涌现出来。
|
||||
|
||||
这种背景下,“会 Kubernetes”已经远远不够了,“懂 Kubernetes”、“会云原生架构”的重要性正日益凸显出来。 从 2019 年开始,云原生技术将会大规模普及,这也是为什么大家都要在这个时间点上学习和投资云原生技术的重要原因。
|
||||
|
||||
“云原生技术公开课”是一门怎样的课程?
|
||||
|
||||
基于上面所提到的技术趋势,所以阿里巴巴和 CNCF 联合开设了云原生技术公开课。 那么这样的公开课到底在讲什么内容呢?
|
||||
|
||||
公开课教学大纲
|
||||
|
||||
第一期云原生公开课的教学大纲,主要以应用容器和 Kubernetes 为核心,在后面几期将会陆续上线 Service Mesh、Serverless 等相关课程。 在第一期公开课中,我们首先将课程分为两部分——基础知识部分和进阶知识部分:
|
||||
|
||||
|
||||
首先,我们希望通过第一部分的课程讲解帮助大家夯实基础。然后,对于更高阶的内容展开更深入的代码级别的剖析。希望通过这样循序渐进的方式帮助大家学习云原生技术;
|
||||
其次,在每个课程后面我们的讲师都会设置对应的课后自测考试题,这些考试题实际上是对本节课程最有效的归纳,我们希望能够通过课后评测的方式来帮助大家总结知识点,打造出属于自己的云原生知识体系;
|
||||
最后,我们的讲师在每个知识点的背后都设计了云端实践,所谓“实践出真知”,学习计算机相关的知识还是需要上手来实际地进行操作才可以。 因此在云端实践部分,讲师会提供详细的实践步骤供大家课后自我联系。并且在这个环节,阿里云还会赠送了定量的阿里云代金券帮助大家更好地在云上进行实践。
|
||||
|
||||
|
||||
以上三个部分就构成了阿里云和 CNCF 联合推出的云原生技术公开课的教学内容。
|
||||
|
||||
公开课授课计划
|
||||
|
||||
在授课计划方面,初步这样安排:第一堂课在 2019 年 9 月上线,此后将会每周更新2节课,总共 29 个课时。每个知识点后面都提供了课后自测。 对于讲师阵容而言,也是本次公开课最引以为傲的部分。我们的公开课将会主要由 CNCF 社区资深成员与项目维护者为大家讲解,很多课程讲师都是阿里云容器平台团队的专家级工程师。同时,我们也会邀请云原生社区的资深专家和外部讲师为大家讲解部分内容。因此在课程进行过程中,我们会不定期地安排大咖直播、课程答疑和落地实践案例。 我们希望将这些内容都集成在一起,为大家呈现一个中国最完整、最权威、最具有影响力的云原生技术公开课。
|
||||
|
||||
课程预备知识
|
||||
|
||||
大家可能存在这样的疑惑,就是想要学习云原生基础知识之前需要哪些预备知识呢?其实大致需要三部分预备知识:
|
||||
|
||||
|
||||
Linux 操作系统知识:主要是一些通识性的基础,最好具有一定的在 Linux 下开发的经验;
|
||||
计算机和程序设计的基础:这一点到入门工程师或者高年级本科生水平就足够了;
|
||||
容器的使用基础:希望大家具有容器的简单使用经验,比如 docker run 以及 docker build 等,最好有一定 Docker 化应用开发的经验。当然,我们在课程中也会讲解相关的基础知识。
|
||||
|
||||
|
||||
什么是“云原生”?云原生该怎么落地?
|
||||
|
||||
在介绍完课程之后,我们再来详细的聊一聊“云原生”:什么是“云原生”?云原生该怎么落地?这两个问题也是整个课程的核心内容。
|
||||
|
||||
云原生的定义
|
||||
|
||||
很多人都会问“到底什么是云原生?” 实际上,云原生是一条最佳路径或者最佳实践。更详细的说,云原生为用户指定了一条低心智负担的、敏捷的、能够以可扩展、可复制的方式最大化地利用云的能力、发挥云的价值的最佳路径。 因此,云原生其实是一套指导进行软件架构设计的思想。按照这样的思想而设计出来的软件:首先,天然就“生在云上,长在云上”;其次,能够最大化地发挥云的能力,使得我们开发的软件和“云”能够天然地集成在一起,发挥出“云”的最大价值。 所以,云原生的最大价值和愿景,就是认为未来的软件,会从诞生起就生长在云上,并且遵循一种新的软件开发、发布和运维模式,从而使得软件能够最大化地发挥云的能力。说到了这里,大家可以思考一下为什么容器技术具有革命性?
|
||||
|
||||
其实,容器技术和集装箱技术的革命性非常类似,即:容器技术使得应用具有了一种“自包含”的定义方式。所以,这样的应用才能以敏捷的、以可扩展可复制的方式发布在云上,发挥出云的能力。这也就是容器技术对云发挥出的革命性影响所在,所以说,容器技术正是云原生技术的核心底盘。
|
||||
|
||||
云原生的技术范畴
|
||||
|
||||
云原生的技术范畴包括了以下几个方面:
|
||||
|
||||
|
||||
第一部分是云应用定义与开发流程。这包括应用定义与镜像制作、配置 CI/CD、消息和 Streaming 以及数据库等。
|
||||
第二部分是云应用的编排与管理流程。这也是 Kubernetes 比较关注的一部分,包括了应用编排与调度、服务发现治理、远程调用、API 网关以及 Service Mesh。
|
||||
第三部分是监控与可观测性。这部分所强调的是云上应用如何进行监控、日志收集、Tracing 以及在云上如何实现破坏性测试,也就是混沌工程的概念。
|
||||
第四部分就是云原生的底层技术,比如容器运行时、云原生存储技术、云原生网络技术等。
|
||||
第五部分是云原生工具集,在前面的这些核心技术点之上,还有很多配套的生态或者周边的工具需要使用,比如流程自动化与配置管理、容器镜像仓库、云原生安全技术以及云端密码管理等。
|
||||
最后则是 Serverless。Serverless 是一种 PaaS 的特殊形态,它定义了一种更为“极端抽象”的应用编写方式,包含了 FaaS 和 BaaS 这样的概念。而无论是 FaaS 还是 BaaS,其最为典型的特点就是按实际使用计费(Pay as you go),因此 Serverless 计费也是重要的知识和概念。
|
||||
|
||||
|
||||
云原生思想的两个理论
|
||||
|
||||
在了解完云原生的技术范畴之后你就会发现,其所包含的技术内容还是很多的,但是这些内容的技术本质却是类似的。云原生技术的本质是两个理论基础。
|
||||
|
||||
|
||||
第一个理论基础是:不可变基础设施。这一点目前是通过容器镜像来实现的,其含义就是应用的基础设施应该是不可变的,是一个自包含、自描述可以完全在不同环境中迁移的东西;
|
||||
第二个理论基础就是:云应用编排理论。当前的实现方式就是 Google 所提出来的“容器设计模式”,这也是本系列课程中的 Kubernetes 部分所需主要讲解的内容。
|
||||
|
||||
|
||||
基础设施向云演进的过程
|
||||
|
||||
首先为大家介绍一下“不可变基础设施”的概念。其实,应用所依赖的基础设施也在经历一个向云演进的过程,举例而言,对于传统的应用基础设施而言,其实往往是可变的。
|
||||
|
||||
大家可能经常会干这样一件事情,比如需要发布或者更新一个软件,那么流程大致是这样的,先通过 SSH 连到服务器,然后手动升级或者降级软件包,逐个调整服务器上的配置文件,并且将新代码直接都部署到现有服务器上。因此,这套基础设施会不断地被调整和修改。 但是在云上,对“云”友好的应用基础设施是不可变的。
|
||||
|
||||
这种场景下的上述更新过程会这么做:一旦应用部署完成之后,那么这套应用基础设施就不会再修改了。如果需要更新,那么需要现更改公共镜像来构建新服务直接替换旧服务。而我们之所以能够实现直接替换,就是因为容器提供了自包含的环境(包含应用运行所需的所有依赖)。所以对于应用而言,完全不需要关心容器发生了什么变化,只需要把容器镜像本身修改掉就可以了。因此,对于云友好的基础设施是随时可以替换和更换的,这就是因为容器具有敏捷和一致性的能力,也就是云时代的应用基础设施。 所以,总结而言,云时代的基础设施就像是可以替代的“牲口”,可以随时替换;而传统的基础设施则是独一无二的“宠物”,需要细心呵护,这就体现出了云时代不可变基础设施的优点。
|
||||
|
||||
基础设施向云演进的意义
|
||||
|
||||
所以,像这样的基础设施向“不可变”演进的过程,为我们提供了两个非常重要的优点。
|
||||
|
||||
|
||||
1、基础设施的一致性和可靠性。同样一个镜像,无论是在美国打开,在中国打开,还是在印度打开都是一样的。并且其中的 OS 环境对于应用而言都是一致的。而对于应用而言,它就不需要关心容器跑在哪里,这就是基础设施一致性非常重要的一个特征。
|
||||
2、这样的镜像本身就是自包含的,其包含了应用运行所需要的所有依赖,因此也可以漂移到云上的任何一个位置。
|
||||
|
||||
|
||||
此外,云原生的基础设施还提供了简单、可预测的部署和运维能力。由于现在有了镜像,应用还是自描述的,通过镜像运行起来的整个容器其实可以像 Kubernetes 的 Operator 技术一样将其做成自运维的,所以整个应用本身都是自包含的行为,使得其能够迁移到云上任何一个位置。这也使得整个流程的自动化变得非常容易。
|
||||
|
||||
应用本身也可以更好地扩容,从 1 个实例变成 100 个实例,进而变成 1 万个实例,这个过程对于容器化后的应用没有任何特殊的。最后,我们这时也能够通过不可变的基础设施来地快速周围的管控系统和支撑组件。因为,这些组件本身也是容器化的,是符合不可变基础设施这样一套理论的组件。 以上就是不可变基础设施为用户带来的最大的优点。
|
||||
|
||||
云原生关键技术点
|
||||
|
||||
当我们回过头来看云原生关键技术点或者说它所依赖的技术理论的时候,可以看到主要有这样的四个方向:
|
||||
|
||||
|
||||
如何构建自包含、可定制的应用镜像;
|
||||
能不能实现应用快速部署与隔离能力;
|
||||
应用基础设施创建和销毁的自动化管理;
|
||||
可复制的管控系统和支撑组件。
|
||||
|
||||
|
||||
这四个云原生关键技术点是落地实现云原生技术的四个主要途径,而这四个技术点也是本门课程的 17 个技术点所主要讲述的核心知识。
|
||||
|
||||
本节总结
|
||||
|
||||
|
||||
“云原生”具备着重要的意义,它是云时代技术人自我提升的必备路径;
|
||||
“云原生”定义了一条云时代应用从开发到交付的最佳路径;
|
||||
“云原生”应用生在云上,长在云上,希望能够将云的能力发挥到极致。
|
||||
|
||||
|
||||
讲师点评
|
||||
|
||||
“未来的软件一定是生长于云上的”这是云原生理念的最核心假设。而所谓“云原生”,实际上就是在定义一条能够让应用最大程度利用云的能力、发挥云的价值的最佳路径。在这条路径上,脱离了“应用”这个载体,“云原生”就无从谈起;容器技术,则是将这个理念落地、将软件交付的革命持续进行下去的重要手段之一。
|
||||
|
||||
而本期云原生公开课重点讲解的 Kubernetes 项目,则是整个“云原生”理念落地的核心与关键所在。它正在迅速成为连通“云”与“应用”的高速公路,以标准、高效的方式将“应用”快速交付到世界上任何一个位置。如今”云原生应用交付“,已经成为了 2019 年云计算市场上最热门的技术关键词之一。希望学习课程的同学们能够学以致用,持续关注以 K8s 为基础进行“云原生应用管理与交付”的技术趋势。
|
||||
|
||||
|
||||
|
||||
|
143
专栏/CNCFX阿里巴巴云原生技术公开课/02容器基本概念.md
Normal file
143
专栏/CNCFX阿里巴巴云原生技术公开课/02容器基本概念.md
Normal file
@ -0,0 +1,143 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
02 容器基本概念
|
||||
容器与镜像
|
||||
|
||||
什么是容器?
|
||||
|
||||
在介绍容器的具体概念之前,先简单回顾一下操作系统是如何管理进程的。
|
||||
|
||||
首先,当我们登录到操作系统之后,可以通过 ps 等操作看到各式各样的进程,这些进程包括系统自带的服务和用户的应用进程。那么,这些进程都有什么样的特点?
|
||||
|
||||
|
||||
第一,这些进程可以相互看到、相互通信;
|
||||
第二,它们使用的是同一个文件系统,可以对同一个文件进行读写操作;
|
||||
第三,这些进程会使用相同的系统资源。
|
||||
|
||||
|
||||
这样的三个特点会带来什么问题呢?
|
||||
|
||||
|
||||
因为这些进程能够相互看到并且进行通信,高级权限的进程可以攻击其他进程;
|
||||
因为它们使用的是同一个文件系统,因此会带来两个问题:这些进程可以对于已有的数据进行增删改查,具有高级权限的进程可能会将其他进程的数据删除掉,破坏掉其他进程的正常运行;此外,进程与进程之间的依赖可能会存在冲突,如此一来就会给运维带来很大的压力;
|
||||
因为这些进程使用的是同一个宿主机的资源,应用之间可能会存在资源抢占的问题,当一个应用需要消耗大量 CPU 和内存资源的时候,就可能会破坏其他应用的运行,导致其他应用无法正常地提供服务。
|
||||
|
||||
|
||||
针对上述的三个问题,如何为进程提供一个独立的运行环境呢?
|
||||
|
||||
|
||||
针对不同进程使用同一个文件系统所造成的问题而言,Linux 和 Unix 操作系统可以通过 chroot 系统调用将子目录变成根目录,达到视图级别的隔离;进程在 chroot 的帮助下可以具有独立的文件系统,对于这样的文件系统进行增删改查不会影响到其他进程;
|
||||
因为进程之间相互可见并且可以相互通信,使用 Namespace 技术来实现进程在资源的视图上进行隔离。在 chroot 和 Namespace 的帮助下,进程就能够运行在一个独立的环境下了;
|
||||
但在独立的环境下,进程所使用的还是同一个操作系统的资源,一些进程可能会侵蚀掉整个系统的资源。为了减少进程彼此之间的影响,可以通过 Cgroup 来限制其资源使用率,设置其能够使用的 CPU 以及内存量。
|
||||
|
||||
|
||||
那么,应该如何定义这样的进程集合呢?
|
||||
|
||||
其实,容器就是一个视图隔离、资源可限制、独立文件系统的进程集合。所谓“视图隔离”就是能够看到部分进程以及具有独立的主机名等;控制资源使用率则是可以对于内存大小以及 CPU 使用个数等进行限制。容器就是一个进程集合,它将系统的其他资源隔离开来,具有自己独立的资源视图。
|
||||
|
||||
容器具有一个独立的文件系统,因为使用的是系统的资源,所以在独立的文件系统内不需要具备内核相关的代码或者工具,我们只需要提供容器所需的二进制文件、配置文件以及依赖即可。只要容器运行时所需的文件集合都能够具备,那么这个容器就能够运行起来。
|
||||
|
||||
什么是镜像?
|
||||
|
||||
综上所述,我们将这些容器运行时所需要的所有的文件集合称之为容器镜像。
|
||||
|
||||
那么,一般都是通过什么样的方式来构建镜像的呢?通常情况下,我们会采用 Dockerfile 来构建镜像,这是因为 Dockerfile 提供了非常便利的语法糖,能够帮助我们很好地描述构建的每个步骤。当然,每个构建步骤都会对已有的文件系统进行操作,这样就会带来文件系统内容的变化,我们将这些变化称之为 changeset。当我们把构建步骤所产生的变化依次作用到一个空文件夹上,就能够得到一个完整的镜像。 changeset 的分层以及复用特点能够带来几点优势:
|
||||
|
||||
|
||||
第一,能够提高分发效率,简单试想一下,对于大的镜像而言,如果将其拆分成各个小块就能够提高镜像的分发效率,这是因为镜像拆分之后就可以并行下载这些数据;
|
||||
第二,因为这些数据是相互共享的,也就意味着当本地存储上包含了一些数据的时候,只需要下载本地没有的数据即可,举个简单的例子就是 golang 镜像是基于 alpine 镜像进行构建的,当本地已经具有了 alpine 镜像之后,在下载 golang 镜像的时候只需要下载本地 alpine 镜像中没有的部分即可;
|
||||
第三,因为镜像数据是共享的,因此可以节约大量的磁盘空间,简单设想一下,当本地存储具有了 alpine 镜像和 golang 镜像,在没有复用的能力之前,alpine 镜像具有 5M 大小,golang 镜像有 300M 大小,因此就会占用 305M 空间;而当具有了复用能力之后,只需要 300M 空间即可。
|
||||
|
||||
|
||||
如何构建镜像?
|
||||
|
||||
如下图所示的 Dockerfile 适用于描述如何构建 golang 应用的。
|
||||
|
||||
|
||||
|
||||
如图所示:
|
||||
|
||||
|
||||
FROM 行表示以下的构建步骤基于什么镜像进行构建,正如前面所提到的,镜像是可以复用的;
|
||||
WORKDIR 行表示会把接下来的构建步骤都在哪一个相应的具体目录下进行,其起到的作用类似于 Shell 里面的 cd;
|
||||
COPY 行表示的是可以将宿主机上的文件拷贝到容器镜像内;
|
||||
RUN 行表示在具体的文件系统内执行相应的动作。当我们运行完毕之后就可以得到一个应用了;
|
||||
CMD 行表示使用镜像时的默认程序名字。
|
||||
|
||||
|
||||
当有了 Dockerfile 之后,就可以通过 docker build 命令构建出所需要的应用。构建出的结果存储在本地,一般情况下,镜像构建会在打包机或者其他的隔离环境下完成。
|
||||
|
||||
那么,这些镜像如何运行在生产环境或者测试环境上呢?这时候就需要一个中转站或者中心存储,我们称之为 docker registry,也就是镜像仓库,其负责存储所有产生的镜像数据。我们只需要通过 docker push 就能够将本地镜像推动到镜像仓库中,这样一来,就能够在生产环境上或者测试环境上将相应的数据下载下来并运行了。
|
||||
|
||||
如何运行容器?
|
||||
|
||||
运行一个容器一般情况下分为三步:
|
||||
|
||||
|
||||
第一步:从镜像仓库中将相应的镜像下载下来;
|
||||
第二步:当镜像下载完成之后就可以通过 docker images 来查看本地镜像,这里会给出一个完整的列表,我们可以在列表中选中想要的镜像;
|
||||
第三步:当选中镜像之后,就可以通过 docker run 来运行这个镜像得到想要的容器,当然可以通过多次运行得到多个容器。一个镜像就相当于是一个模板,一个容器就像是一个具体的运行实例,因此镜像就具有了一次构建、到处运行的特点。
|
||||
|
||||
|
||||
小结
|
||||
|
||||
简单回顾一下,容器就是和系统其它部分隔离开来的进程集合,这里的其他部分包括进程、网络资源以及文件系统等。而镜像就是容器所需要的所有文件集合,其具备一次构建、到处运行的特点。
|
||||
|
||||
容器的生命周期
|
||||
|
||||
容器运行时的生命周期
|
||||
|
||||
容器是一组具有隔离特性的进程集合,在使用 docker run 的时候会选择一个镜像来提供独立的文件系统并指定相应的运行程序。这里指定的运行程序称之为 initial 进程,这个 initial 进程启动的时候,容器也会随之启动,当 initial 进程退出的时候,容器也会随之退出。
|
||||
|
||||
因此,可以认为容器的生命周期和 initial 进程的生命周期是一致的。当然,因为容器内不只有这样的一个 initial 进程,initial 进程本身也可以产生其他的子进程或者通过 docker exec 产生出来的运维操作,也属于 initial 进程管理的范围内。当 initial 进程退出的时候,所有的子进程也会随之退出,这样也是为了防止资源的泄漏。 但是这样的做法也会存在一些问题,首先应用里面的程序往往是有状态的,其可能会产生一些重要的数据,当一个容器退出被删除之后,数据也就会丢失了,这对于应用方而言是不能接受的,所以需要将容器所产生出来的重要数据持久化下来。容器能够直接将数据持久化到指定的目录上,这个目录就称之为数据卷。
|
||||
|
||||
数据卷有一些特点,其中非常明显的就是数据卷的生命周期是独立于容器的生命周期的,也就是说容器的创建、运行、停止、删除等操作都和数据卷没有任何关系,因为它是一个特殊的目录,是用于帮助容器进行持久化的。简单而言,我们会将数据卷挂载到容器内,这样一来容器就能够将数据写入到相应的目录里面了,而且容器的退出并不会导致数据的丢失。
|
||||
|
||||
通常情况下,数据卷管理主要有两种方式:
|
||||
|
||||
|
||||
第一种是通过 bind 的方式,直接将宿主机的目录直接挂载到容器内;这种方式比较简单,但是会带来运维成本,因为其依赖于宿主机的目录,需要对于所有的宿主机进行统一管理。
|
||||
第二种是将目录管理交给运行引擎。
|
||||
|
||||
|
||||
容器项目架构
|
||||
|
||||
moby 容器引擎架构
|
||||
|
||||
moby 是目前最流行的容器管理引擎,moby daemon 会对上提供有关于容器、镜像、网络以及 Volume的管理。moby daemon 所依赖的最重要的组件就是 containerd,containerd 是一个容器运行时管理引擎,其独立于 moby daemon ,可以对上提供容器、镜像的相关管理。
|
||||
|
||||
containerd 底层有 containerd shim 模块,其类似于一个守护进程,这样设计的原因有几点:
|
||||
|
||||
|
||||
首先,containerd 需要管理容器生命周期,而容器可能是由不同的容器运行时所创建出来的,因此需要提供一个灵活的插件化管理。而 shim 就是针对于不同的容器运行时所开发的,这样就能够从 containerd 中脱离出来,通过插件的形式进行管理。
|
||||
其次,因为 shim 插件化的实现,使其能够被 containerd 动态接管。如果不具备这样的能力,当 moby daemon 或者 containerd daemon 意外退出的时候,容器就没人管理了,那么它也会随之消失、退出,这样就会影响到应用的运行。
|
||||
最后,因为随时可能会对 moby 或者 containerd 进行升级,如果不提供 shim 机制,那么就无法做到原地升级,也无法做到不影响业务的升级,因此 containerd shim 非常重要,它实现了动态接管的能力。
|
||||
|
||||
|
||||
本节课程只是针对于 moby 进行一个大致的介绍,在后续的课程也会详细介绍。
|
||||
|
||||
容器 VS VM
|
||||
|
||||
容器和 VM 之间的差异
|
||||
|
||||
VM 利用 Hypervisor 虚拟化技术来模拟 CPU、内存等硬件资源,这样就可以在宿主机上建立一个 Guest OS,这是常说的安装一个虚拟机。
|
||||
|
||||
每一个 Guest OS 都有一个独立的内核,比如 Ubuntu、CentOS 甚至是 Windows 等,在这样的 Guest OS 之下,每个应用都是相互独立的,VM 可以提供一个更好的隔离效果。但这样的隔离效果需要付出一定的代价,因为需要把一部分的计算资源交给虚拟化,这样就很难充分利用现有的计算资源,并且每个 Guest OS 都需要占用大量的磁盘空间,比如 Windows 操作系统的安装需要 10~30G 的磁盘空间,Ubuntu 也需要 5~6G,同时这样的方式启动很慢。正是因为虚拟机技术的缺点,催生出了容器技术。 容器是针对于进程而言的,因此无需 Guest OS,只需要一个独立的文件系统提供其所需要文件集合即可。所有的文件隔离都是进程级别的,因此启动时间快于 VM,并且所需的磁盘空间也小于 VM。当然了,进程级别的隔离并没有想象中的那么好,隔离效果相比 VM 要差很多。
|
||||
|
||||
总体而言,容器和 VM 相比,各有优劣,因此容器技术也在向着强隔离方向发展。
|
||||
|
||||
本节总结
|
||||
|
||||
|
||||
容器是一个进程集合,具有自己独特的视图视角;
|
||||
镜像是容器所需要的所有文件集合,其具备一次构建、到处运行的特点;
|
||||
容器的生命周期和 initial 进程的生命周期是一样的;
|
||||
容器和 VM 相比,各有优劣,容器技术在向着强隔离方向发展。
|
||||
|
||||
|
||||
|
||||
|
||||
|
308
专栏/CNCFX阿里巴巴云原生技术公开课/03Kubernetes核心概念.md
Normal file
308
专栏/CNCFX阿里巴巴云原生技术公开课/03Kubernetes核心概念.md
Normal file
@ -0,0 +1,308 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
03 Kubernetes 核心概念
|
||||
什么是 Kubernetes
|
||||
|
||||
Kubernetes,从官方网站上可以看到,它是一个工业级的容器编排平台。Kubernetes 这个单词是希腊语,它的中文翻译是“舵手”或者“飞行员”。在一些常见的资料中也会看到“ks”这个词,也就是“k8s”,它是通过将8个字母“ubernete ”替换为“8”而导致的一个缩写。
|
||||
|
||||
Kubernetes 为什么要用“舵手”来命名呢?大家可以看一下这张图:
|
||||
|
||||
|
||||
|
||||
这是一艘载着一堆集装箱的轮船,轮船在大海上运着集装箱奔波,把集装箱送到它们该去的地方。我们之前其实介绍过一个概念叫做 container,container 这个英文单词也有另外的一个意思就是“集装箱”。Kubernetes 也就借着这个寓意,希望成为运送集装箱的一个轮船,来帮助我们管理这些集装箱,也就是管理这些容器。
|
||||
|
||||
这个就是为什么会选用 Kubernetes 这个词来代表这个项目的原因。更具体一点地来说:Kubernetes 是一个自动化的容器编排平台,它负责应用的部署、应用的弹性以及应用的管理,这些都是基于容器的。
|
||||
|
||||
Kubernetes 有如下几个核心的功能
|
||||
|
||||
|
||||
服务的发现与负载的均衡;
|
||||
容器的自动装箱,我们也会把它叫做 scheduling,就是“调度”,把一个容器放到一个集群的某一个机器上,Kubernetes 会帮助我们去做存储的编排,让存储的声明周期与容器的生命周期能有一个连接;
|
||||
Kubernetes 会帮助我们去做自动化的容器的恢复。在一个集群中,经常会出现宿主机的问题或者说是 OS 的问题,导致容器本身的不可用,Kubernetes 会自动地对这些不可用的容器进行恢复;
|
||||
Kubernetes 会帮助我们去做应用的自动发布与应用的回滚,以及与应用相关的配置密文的管理;
|
||||
对于 job 类型任务,Kubernetes 可以去做批量的执行;
|
||||
为了让这个集群、这个应用更富有弹性,Kubernetes 也支持水平的伸缩。
|
||||
|
||||
|
||||
下面,我们希望以三个例子跟大家更切实地介绍一下 Kubernetes 的能力。
|
||||
|
||||
调度
|
||||
|
||||
Kubernetes 可以把用户提交的容器放到 Kubernetes 管理的集群的某一台节点上去。Kubernetes 的调度器是执行这项能力的组件,它会观察正在被调度的这个容器的大小、规格。
|
||||
|
||||
比如说它所需要的 CPU以及它所需要的 memory,然后在集群中找一台相对比较空闲的机器来进行一次 placement,也就是一次放置的操作。在这个例子中,它可能会把红颜色的这个容器放置到第二个空闲的机器上,来完成一次调度的工作。
|
||||
|
||||
|
||||
|
||||
自动修复
|
||||
|
||||
Kubernetes 有一个节点健康检查的功能,它会监测这个集群中所有的宿主机,当宿主机本身出现故障,或者软件出现故障的时候,这个节点健康检查会自动对它进行发现。
|
||||
|
||||
下面 Kubernetes 会把运行在这些失败节点上的容器进行自动迁移,迁移到一个正在健康运行的宿主机上,来完成集群内容器的一个自动恢复。
|
||||
|
||||
|
||||
|
||||
水平伸缩
|
||||
|
||||
Kubernetes 有业务负载检查的能力,它会监测业务上所承担的负载,如果这个业务本身的 CPU 利用率过高,或者响应时间过长,它可以对这个业务进行一次扩容。
|
||||
|
||||
比如说在下面的例子中,黄颜色的过度忙碌,Kubernetes 就可以把黄颜色负载从一份变为三份。接下来,它就可以通过负载均衡把原来打到第一个黄颜色上的负载平均分到三个黄颜色的负载上去,以此来提高响应的时间。
|
||||
|
||||
|
||||
|
||||
以上就是 Kubernetes 三个核心能力的简单介绍。
|
||||
|
||||
Kubernetes 的架构
|
||||
|
||||
Kubernetes 架构是一个比较典型的二层架构和 server-client 架构。Master 作为中央的管控节点,会去与 Node 进行一个连接。
|
||||
|
||||
所有 UI 的、clients、这些 user 侧的组件,只会和 Master 进行连接,把希望的状态或者想执行的命令下发给 Master,Master 会把这些命令或者状态下发给相应的节点,进行最终的执行。
|
||||
|
||||
|
||||
|
||||
Kubernetes 的 Master 包含四个主要的组件:API Server、Controller、Scheduler 以及 etcd。如下图所示:
|
||||
|
||||
|
||||
|
||||
|
||||
API Server: 顾名思义是用来处理 API 操作的,Kubernetes 中所有的组件都会和 API Server 进行连接,组件与组件之间一般不进行独立的连接,都依赖于 API Server 进行消息的传送;
|
||||
Controller: 是控制器,它用来完成对集群状态的一些管理。比如刚刚我们提到的两个例子之中,第一个自动对容器进行修复、第二个自动进行水平扩张,都是由 Kubernetes 中的 Controller 来进行完成的;
|
||||
Scheduler: 是调度器,“调度器”顾名思义就是完成调度的操作,就是我们刚才介绍的第一个例子中,把一个用户提交的 Container,依据它对 CPU、对 memory 请求大小,找一台合适的节点,进行放置;
|
||||
etcd: 是一个分布式的一个存储系统,API Server 中所需要的这些原信息都被放置在 etcd 中,etcd 本身是一个高可用系统,通过 etcd 保证整个 Kubernetes 的 Master 组件的高可用性。
|
||||
|
||||
|
||||
我们刚刚提到的 API Server,它本身在部署结构上是一个可以水平扩展的一个部署组件;Controller 是一个可以进行热备的一个部署组件,它只有一个 active,它的调度器也是相应的,虽然只有一个 active,但是可以进行热备。
|
||||
|
||||
Kubernetes 的架构:Node
|
||||
|
||||
Kubernetes 的 Node 是真正运行业务负载的,每个业务负载会以 Pod 的形式运行。等一下我会介绍一下 Pod 的概念。一个 Pod 中运行的一个或者多个容器,真正去运行这些 Pod 的组件的是叫做 kubelet,也就是 Node 上最为关键的组件,它通过 API Server 接收到所需要 Pod 运行的状态,然后提交到我们下面画的这个 Container Runtime 组件中。
|
||||
|
||||
|
||||
|
||||
在 OS 上去创建容器所需要运行的环境,最终把容器或者 Pod 运行起来,也需要对存储跟网络进行管理。Kubernetes 并不会直接进行网络存储的操作,他们会靠 Storage Plugin 或者是网络的 Plugin 来进行操作。用户自己或者云厂商都会去写相应的 Storage Plugin 或者 Network Plugin,去完成存储操作或网络操作。
|
||||
|
||||
在 Kubernetes 自己的环境中,也会有 Kubernetes 的 Network,它是为了提供 Service network 来进行搭网组网的。(等一下我们也会去介绍“service”这个概念。)真正完成 service 组网的组件的是 Kube-proxy,它是利用了 iptable 的能力来进行组建 Kubernetes 的 Network,就是 cluster network,以上就是 Node 上面的四个组件。
|
||||
|
||||
Kubernetes 的 Node 并不会直接和 user 进行 interaction,它的 interaction 只会通过 Master。而 User 是通过 Master 向节点下发这些信息的。Kubernetes 每个 Node 上,都会运行我们刚才提到的这几个组件。
|
||||
|
||||
下面我们以一个例子再去看一下 Kubernetes 架构中的这些组件,是如何互相进行 interaction 的。
|
||||
|
||||
|
||||
|
||||
用户可以通过 UI 或者 CLI 提交一个 Pod 给 Kubernetes 进行部署,这个 Pod 请求首先会通过 CLI 或者 UI 提交给 Kubernetes API Server,下一步 API Server 会把这个信息写入到它的存储系统 etcd,之后 Scheduler 会通过 API Server 的 watch 或者叫做 notification 机制得到这个信息:有一个 Pod 需要被调度。
|
||||
|
||||
这个时候 Scheduler 会根据它的内存状态进行一次调度决策,在完成这次调度之后,它会向 API Server report 说:“OK!这个 Pod 需要被调度到某一个节点上。”
|
||||
|
||||
这个时候 API Server 接收到这次操作之后,会把这次的结果再次写到 etcd 中,然后 API Server 会通知相应的节点进行这次 Pod 真正的执行启动。相应节点的 kubelet 会得到这个通知,kubelet 就会去调 Container runtime 来真正去启动配置这个容器和这个容器的运行环境,去调度 Storage Plugin 来去配置存储,network Plugin 去配置网络。
|
||||
|
||||
这个例子我们可以看到:这些组件之间是如何相互沟通相互通信,协调来完成一次Pod的调度执行操作的。
|
||||
|
||||
Kubernetes 的核心概念与它的 API
|
||||
|
||||
核心概念
|
||||
|
||||
第一个概念:Pod
|
||||
|
||||
Pod 是 Kubernetes 的一个最小调度以及资源单元。用户可以通过 Kubernetes 的 Pod API 生产一个 Pod,让 Kubernetes 对这个 Pod 进行调度,也就是把它放在某一个 Kubernetes 管理的节点上运行起来。一个 Pod 简单来说是对一组容器的抽象,它里面会包含一个或多个容器。
|
||||
|
||||
比如像下面的这幅图里面,它包含了两个容器,每个容器可以指定它所需要资源大小。比如说,一个核一个 G,或者说 0.5 个核,0.5 个 G。
|
||||
|
||||
当然在这个 Pod 中也可以包含一些其他所需要的资源:比如说我们所看到的 Volume 卷这个存储资源;比如说我们需要 100 个 GB 的存储或者 20GB 的另外一个存储。
|
||||
|
||||
|
||||
|
||||
在 Pod 里面,我们也可以去定义容器所需要运行的方式。比如说运行容器的 Command,以及运行容器的环境变量等等。Pod 这个抽象也给这些容器提供了一个共享的运行环境,它们会共享同一个网络环境,这些容器可以用 localhost 来进行直接的连接。而 Pod 与 Pod 之间,是互相有 isolation 隔离的。
|
||||
|
||||
第二个概念:Volume
|
||||
|
||||
Volume 就是卷的概念,它是用来管理 Kubernetes 存储的,是用来声明在 Pod 中的容器可以访问文件目录的,一个卷可以被挂载在 Pod 中一个或者多个容器的指定路径下面。
|
||||
|
||||
而 Volume 本身是一个抽象的概念,一个 Volume 可以去支持多种的后端的存储。比如说 Kubernetes 的 Volume 就支持了很多存储插件,它可以支持本地的存储,可以支持分布式的存储,比如说像 ceph,GlusterFS ;它也可以支持云存储,比如说阿里云上的云盘、AWS 上的云盘、Google 上的云盘等等。
|
||||
|
||||
|
||||
|
||||
第三个概念:Deployment
|
||||
|
||||
Deployment 是在 Pod 这个抽象上更为上层的一个抽象,它可以定义一组 Pod 的副本数目、以及这个 Pod 的版本。一般大家用 Deployment 这个抽象来做应用的真正的管理,而 Pod 是组成 Deployment 最小的单元。
|
||||
|
||||
Kubernetes 是通过 Controller,也就是我们刚才提到的控制器去维护 Deployment 中 Pod 的数目,它也会去帮助 Deployment 自动恢复失败的 Pod。
|
||||
|
||||
比如说我可以定义一个 Deployment,这个 Deployment 里面需要两个 Pod,当一个 Pod 失败的时候,控制器就会监测到,它重新把 Deployment 中的 Pod 数目从一个恢复到两个,通过再去新生成一个 Pod。通过控制器,我们也会帮助完成发布的策略。比如说进行滚动升级,进行重新生成的升级,或者进行版本的回滚。
|
||||
|
||||
|
||||
|
||||
第四个概念:Service
|
||||
|
||||
Service 提供了一个或者多个 Pod 实例的稳定访问地址。
|
||||
|
||||
比如在上面的例子中,我们看到:一个 Deployment 可能有两个甚至更多个完全相同的 Pod。对于一个外部的用户来讲,访问哪个 Pod 其实都是一样的,所以它希望做一次负载均衡,在做负载均衡的同时,我只想访问某一个固定的 VIP,也就是 Virtual IP 地址,而不希望得知每一个具体的 Pod 的 IP 地址。
|
||||
|
||||
我们刚才提到,这个 pod 本身可能 terminal go(终止),如果一个 Pod 失败了,可能会换成另外一个新的。
|
||||
|
||||
对一个外部用户来讲,提供了多个具体的 Pod 地址,这个用户要不停地去更新 Pod 地址,当这个 Pod 再失败重启之后,我们希望有一个抽象,把所有 Pod 的访问能力抽象成一个第三方的一个 IP 地址,实现这个的 Kubernetes 的抽象就叫 Service。
|
||||
|
||||
实现 Service 有多种方式,Kubernetes 支持 Cluster IP,上面我们讲过的 kuber-proxy 的组网,它也支持 nodePort、 LoadBalancer 等其他的一些访问的能力。
|
||||
|
||||
|
||||
|
||||
第五个概念:Namespace
|
||||
|
||||
Namespace 是用来做一个集群内部的逻辑隔离的,它包括鉴权、资源管理等。Kubernetes 的每个资源,比如刚才讲的 Pod、Deployment、Service 都属于一个 Namespace,同一个 Namespace 中的资源需要命名的唯一性,不同的 Namespace 中的资源可以重名。
|
||||
|
||||
Namespace 一个用例,比如像在阿里巴巴,我们内部会有很多个 business units,在每一个 business units 之间,希望有一个视图上的隔离,并且在鉴权上也不一样,在 cuda 上面也不一样,我们就会用 Namespace 来去给每一个 BU 提供一个他所看到的这么一个看到的隔离的机制。
|
||||
|
||||
|
||||
|
||||
Kubernetes 的 API
|
||||
|
||||
下面我们介绍一下 Kubernetes 的 API 的基础知识。从 high-level 上看,Kubernetes API 是由 HTTP+JSON 组成的:用户访问的方式是 HTTP,访问的 API 中 content 的内容是 JSON 格式的。
|
||||
|
||||
Kubernetes 的 kubectl 也就是 command tool,Kubernetes UI,或者有时候用 curl,直接与 Kubernetes 进行沟通,都是使用 HTTP + JSON 这种形式。
|
||||
|
||||
下面有个例子:比如说,对于这个 Pod 类型的资源,它的 HTTP 访问的路径,就是 API,然后是 apiVesion: V1, 之后是相应的 Namespaces,以及 Pods 资源,最终是 Podname,也就是 Pod 的名字。
|
||||
|
||||
|
||||
|
||||
如果我们去提交一个 Pod,或者 get 一个 Pod 的时候,它的 content 内容都是用 JSON 或者是 YAML 表达的。上图中有个 yaml 的例子,在这个 yaml file 中,对 Pod 资源的描述也分为几个部分。
|
||||
|
||||
第一个部分,一般来讲会是 API 的 version。比如在这个例子中是 V1,它也会描述我在操作哪个资源;比如说我的 kind 如果是 pod,在 Metadata 中,就写上这个 Pod 的名字;比如说 nginx,我们也会给它打一些 label,我们等下会讲到 label 的概念。在 Metadata 中,有时候也会去写 annotation,也就是对资源的额外的一些用户层次的描述。
|
||||
|
||||
比较重要的一个部分叫做 Spec,Spec 也就是我们希望 Pod 达到的一个预期的状态。比如说它内部需要有哪些 container 被运行;比如说这里面有一个 nginx 的 container,它的 image 是什么?它暴露的 port 是什么?
|
||||
|
||||
当我们从 Kubernetes API 中去获取这个资源的时候,一般来讲在 Spec 下面会有一个项目叫 status,它表达了这个资源当前的状态;比如说一个 Pod 的状态可能是正在被调度、或者是已经 running、或者是已经被 terminates,就是被执行完毕了。
|
||||
|
||||
刚刚在 API 之中,我们讲了一个比较有意思的 metadata 叫做“label”,这个 label 可以是一组 KeyValuePair。
|
||||
|
||||
比如下图的第一个 pod 中,label 就可能是一个 color 等于 red,即它的颜色是红颜色。当然你也可以加其他 label,比如说 size: big 就是大小,定义为大的,它可以是一组 label。
|
||||
|
||||
这些 label 是可以被 selector,也就是选择器所查询的。这个能力实际上跟我们的 sql 类型的 select 语句是非常相似的,比如下图中的三个 Pod 资源中,我们就可以进行 select。name color 等于 red,就是它的颜色是红色的,我们也可以看到,只有两个被选中了,因为只有他们的 label 是红色的,另外一个 label 中写的 color 等于 yellow,也就是它的颜色是黄色,是不会被选中的。
|
||||
|
||||
|
||||
|
||||
通过 label,kubernetes 的 API 层就可以对这些资源进行一个筛选,那这些筛选也是 kubernetes 对资源的集合所表达默认的一种方式。
|
||||
|
||||
例如说,我们刚刚介绍的 Deployment,它可能是代表一组的 Pod,它是一组 Pod 的抽象,一组 Pod 就是通过 label selector 来表达的。当然我们刚才讲到说 service 对应的一组 Pod,就是一个 service 要对应一个或者多个的 Pod,来对它们进行统一的访问,这个描述也是通过 label selector 来进行 select 选取的一组 Pod。
|
||||
|
||||
所以可以看到 label 是一个非常核心的 kubernetes API 的概念,我们在接下来的课程中也会着重地去讲解和介绍 label 这个概念,以及如何更好地去使用它。
|
||||
|
||||
以一个 demo 结尾
|
||||
|
||||
最后一部分,我想以一个例子来结束,让大家跟我一起来尝试一个 kubernetes,在尝试 Kubernetes 之前,我希望大家能在本机上安装一下 Kubernetes,安装一个 Kubernetes 沙箱环境。
|
||||
|
||||
安装这个沙箱环境,主要有三个步骤:
|
||||
|
||||
|
||||
首先需要安装一个虚拟机,来在虚拟机中启动 Kubernetes。我们会推荐大家利用 virtualbox 来作为虚拟机的运行环境;
|
||||
|
||||
|
||||
安装 VirtualBox: https://www.virtualbox.org/wiki/Downloads
|
||||
|
||||
|
||||
其次我们需要在虚拟机中启动 Kubernetes,Kubernetes 有一个非常有意思的项目,叫 minikube,也就是启动一个最小的 local 的 Kubernetes 的一个环境。
|
||||
|
||||
|
||||
minikube 我们推荐使用下面写到的阿里云的版本,它和官方 minikube 的主要区别就是把 minikube 中所需要的 Google 上的依赖换成国内访问比较快的一些镜像,这样就方便了大家的安装工作;
|
||||
|
||||
安装 MiniKube(中国版): https://yq.aliyun.com/articles/221687
|
||||
|
||||
|
||||
最后在安装完 virtualbox 和 minikube 之后,大家可以对 minikube 进行启动,也就是下面这个命令。
|
||||
|
||||
|
||||
启动命令:minikube start —vm-driver virtualbox
|
||||
|
||||
如果大家不是 Mac 系统,其他操作系统请访问下面这个链接,查看其它操作系统如何安装 minikube 沙箱环境。
|
||||
|
||||
https://kubernetes.io/docs/tasks/tools/install-minikube/
|
||||
|
||||
当大家安装好之后,我会跟大家一起做一个例子,来做三件事情:
|
||||
|
||||
|
||||
提交一个 nginx deployment;
|
||||
|
||||
|
||||
kubectl apply -f https://k8s.io/examples/application/deployment.yaml
|
||||
|
||||
|
||||
升级 nginx deployment;
|
||||
|
||||
|
||||
kubectl apply -f https://k8s.io/examples/application/deployment-update.yaml
|
||||
|
||||
|
||||
扩容 nginx deployment。
|
||||
|
||||
|
||||
kubectl apply -f https://k8s.io/examples/application/deployment-update.yaml
|
||||
|
||||
第一步,我们提交一个 nginx 的 Deployment,然后对这个 Deployment 进行一次版本升级,也就是改变它中间 Pod 的版本。最后我们也会尝试对 nginx 进行一次扩容,进行一次水平的伸缩,下面就让大家一起跟我来尝试这三个操作吧。
|
||||
|
||||
首先,我们先看一下 minikube 的 status,可以看到 kubelet master 和 kubectl 都是配置好的。
|
||||
|
||||
|
||||
|
||||
下一步我们利用 kubectl 来看一下这个集群中节选的状态,可以看到这个master 的节点已经是 running 状态:
|
||||
|
||||
|
||||
|
||||
我们就以这个为节点,下面我们尝试去看一下现在集群中 Deployment 这个资源:
|
||||
|
||||
|
||||
|
||||
可以看到集群中没有任何的 Deployment,我们可以利用 watch 这个语义去看集群中 Deployment 这个资源的变化情况。
|
||||
|
||||
下面我们去做刚才想要的三个操作:第一个操作是去创建一个 Deployment。可以看到下面第一个图,这是一个 API 的 content,它的 kind 是 Deployment,name 是 nginx-deployment, 有图中它的 replicas 数目是2,它的镜像版本是 1.7.9。
|
||||
|
||||
|
||||
|
||||
我们下面还是回到 kubectl 这个 commnd 来执行这次 Deployment 的真正的操作。我们可以看到一个简单的操作,就会去让 Deployment 不停地生成副本。
|
||||
|
||||
|
||||
|
||||
Deployment 副本数目是 2 个,下面也可以 describe 一下现在的 Deployment 的状态。我们知道之前是没有这个 Deployment 的,现在我们去 describe 这个 nginx-deployment。
|
||||
|
||||
下图中可以看到:有一个 nginx-deployment 已经被生成了,它的 replicas 数目也是我们想要的、selector 也是我们想要的、它的 image 的版本也是 1.7.9。还可以看到,里面的 deployment-controller 这种版本控制器也是在管理它的生成。
|
||||
|
||||
|
||||
|
||||
下面我们去升级这个 Deployment 版本,首先下载另外一个 yaml 文件 deployment-update.yaml,可以看到这里面的 image 本身的版本号从 1.7.9 升级到 1.8。
|
||||
|
||||
|
||||
|
||||
接下来我们重新 apply 新的 deployment-update 这个 yaml 文件。
|
||||
|
||||
可以看到,在另一边的屏幕上显示出了这个 Deployment 升级的一些操作,最终它的 up-to-date 值从 0 变成了 2,也就是说所有的容器都是最新版本的,所有的 Pod 都是最新版本的。我们也可以 discribe 具体去看一下是不是所有 Pod 的版本都被更新了,可以看到这个 image 的版本由 1.7.9 真正更新到了 1.8。
|
||||
|
||||
最后,我们也可以看到 controller 又执行了几次新的操作,这个控制器维护了整个 Deployment 和 Pod 状态。
|
||||
|
||||
|
||||
|
||||
最后我们演示一下给 Deployment 做水平扩张,下载另一个 yaml 文件 deployment-scale.yaml,这里面的 replicas 数目已经从 2 改成了 4。
|
||||
|
||||
|
||||
|
||||
回到最开始的窗口,用 kubectl 去 apply 这个新的 deployment-scale.yaml 文件,在另外一个窗口上可以看到,当我们执行了 deployment-scale 操作之后,它的容器 Pod 数目从 2 变成了 4。我们可以再一次 describ 一下当前集群中的 deployment 的情况,可以看到它的 replicas 的数目从 2 变到了 4,同时也可以看到 controller 又做了几次新的操作,这个 scale up 成功了。
|
||||
|
||||
|
||||
|
||||
最后,让我们利用 delete 操作把我们刚才生成的 Deployment 给删除掉。kubectl delete deployment,也是刚才我们本身的 deployment name,当我们把它删除掉之后,我们今天所有的操作就完成了。
|
||||
|
||||
我们再去重新 get 这个 Deployment,也会显示这个资源不再存在,这个集群又回到了最开始干净的状态。
|
||||
|
||||
|
||||
|
||||
以上这就是这堂课中所有的内容了,我们关注了 kubernetes 的核心概念以及 kubernetes 的架构设计,希望大家能在这节课中有所收获,也希望大家能关注云原生技术课堂中的其他内容,谢谢大家的观看!
|
||||
|
||||
本节总结
|
||||
|
||||
|
||||
Kubernetes 是一个自动化的容器编排平台,它负责应用的部署、应用的弹性以及应用的管理,这些都是基于容器的;
|
||||
Kubernetes 架构是一个比较典型的二层架构和 server-client 架构。
|
||||
|
||||
|
||||
|
||||
|
||||
|
269
专栏/CNCFX阿里巴巴云原生技术公开课/04理解Pod和容器设计模式.md
Normal file
269
专栏/CNCFX阿里巴巴云原生技术公开课/04理解Pod和容器设计模式.md
Normal file
@ -0,0 +1,269 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
04 理解 Pod 和容器设计模式
|
||||
为什么需要 Pod
|
||||
|
||||
容器的基本概念
|
||||
|
||||
现在来看第一个问题:为什么需要 Pod?我们知道 Pod 是 Kubernetes 项目里面一个非常重要的概念,也是非常重要的一个原子调度单位,但是为什么我们会需要这样一个概念呢?我们在使用容器 Docker 的时候,也没有这个说法。其实如果要理解 Pod,我们首先要理解容器,所以首先来回顾一下容器的概念:
|
||||
|
||||
容器的本质实际上是一个进程,是一个视图被隔离,资源受限的进程。
|
||||
|
||||
容器里面 PID=1 的进程就是应用本身,这意味着管理虚拟机等于管理基础设施,因为我们是在管理机器,但管理容器却等于直接管理应用本身。这也是之前说过的不可变基础设施的一个最佳体现,这个时候,你的应用就等于你的基础设施,它一定是不可变的。
|
||||
|
||||
在以上面的例子为前提的情况下,Kubernetes 又是什么呢?我们知道,很多人都说 Kubernetes 是云时代的操作系统,这个非常有意思,因为如果以此类推,容器镜像就是这个操作系统的软件安装包,它们之间是这样的一个类比关系。
|
||||
|
||||
|
||||
|
||||
真实操作系统里的例子
|
||||
|
||||
如果说 Kubernetes 就是操作系统的话,那么我们不妨看一下真实的操作系统的例子。
|
||||
|
||||
例子里面有一个程序叫做 Helloworld,这个 Helloworld 程序实际上是由一组进程组成的,需要注意一下,这里说的进程实际上等同于 Linux 中的线程。
|
||||
|
||||
因为 Linux 中的线程是轻量级进程,所以如果从 Linux 系统中去查看 Helloworld 中的 pstree,将会看到这个 Helloworld 实际上是由四个线程组成的,分别是 {api、main、log、compute}。也就是说,四个这样的线程共同协作,共享 Helloworld 程序的资源,组成了 Helloworld 程序的真实工作情况。
|
||||
|
||||
这是操作系统里面进程组或者线程组中一个非常真实的例子,以上就是进程组的一个概念。
|
||||
|
||||
|
||||
|
||||
那么大家不妨思考一下,在真实的操作系统里面,一个程序往往是根据进程组来进行管理的。Kubernetes 把它类比为一个操作系统,比如说 Linux。针对于容器我们前面提到可以类比为进程,就是前面的 Linux 线程。那么 Pod 又是什么呢?实际上 Pod 就是我们刚刚提到的进程组,也就是 Linux 里的线程组。
|
||||
|
||||
进程组概念
|
||||
|
||||
说到进程组,首先建议大家至少有个概念上的理解,然后我们再详细的解释一下。
|
||||
|
||||
还是前面那个例子:Helloworld 程序由四个进程组成,这些进程之间会共享一些资源和文件。那么现在有一个问题:假如说现在把 Helloworld 程序用容器跑起来,你会怎么去做?
|
||||
|
||||
当然,最自然的一个解法就是,我现在就启动一个 Docker 容器,里面运行四个进程。可是这样会有一个问题,这种情况下容器里面 PID=1 的进程该是谁? 比如说,它应该是我的 main 进程,那么问题来了,“谁”又负责去管理剩余的 3 个进程呢?
|
||||
|
||||
这个核心问题在于,容器的设计本身是一种“单进程”模型,不是说容器里只能起一个进程,由于容器的应用等于进程,所以只能去管理 PID=1 的这个进程,其他再起来的进程其实是一个托管状态。 所以说服务应用进程本身就具有“进程管理”的能力。
|
||||
|
||||
比如说 Helloworld 的程序有 system 的能力,或者直接把容器里 PID=1 的进程直接改成 systemd,否则这个应用,或者是容器是没有办法去管理很多个进程的。因为 PID=1 进程是应用本身,如果现在把这个 PID=1 的进程给 kill 了,或者它自己运行过程中死掉了,那么剩下三个进程的资源就没有人回收了,这个是非常非常严重的一个问题。
|
||||
|
||||
而反过来真的把这个应用本身改成了 systemd,或者在容器里面运行了一个 systemd,将会导致另外一个问题:使得管理容器,不再是管理应用本身了,而等于是管理 systemd,这里的问题就非常明显了。比如说我这个容器里面 run 的程序或者进程是 systemd,那么接下来,这个应用是不是退出了?是不是 fail 了?是不是出现异常失败了?实际上是没办法直接知道的,因为容器管理的是 systemd。这就是为什么在容器里面运行一个复杂程序往往比较困难的一个原因。
|
||||
|
||||
这里再帮大家梳理一下:由于容器实际上是一个“单进程”模型,所以如果你在容器里启动多个进程,只有一个可以作为 PID=1 的进程,而这时候,如果这个 PID=1 的进程挂了,或者说失败退出了,那么其他三个进程就会自然而然的成为孤儿,没有人能够管理它们,没有人能够回收它们的资源,这是一个非常不好的情况。
|
||||
|
||||
|
||||
注意:Linux 容器的“单进程”模型,指的是容器的生命周期等同于 PID=1 的进程(容器应用进程)的生命周期,而不是说容器里不能创建多进程。当然,一般情况下,容器应用进程并不具备进程管理能力,所以你通过 exec 或者 ssh 在容器里创建的其他进程,一旦异常退出(比如 ssh 终止)是很容易变成孤儿进程的。
|
||||
|
||||
|
||||
反过来,其实可以在容器里面 run 一个 systemd,用它来管理其他所有的进程。这样会产生第二个问题:实际上没办法直接管理我的应用了,因为我的应用被 systemd 给接管了,那么这个时候应用状态的生命周期就不等于容器生命周期。这个管理模型实际上是非常非常复杂的。
|
||||
|
||||
|
||||
|
||||
Pod = “进程组”
|
||||
|
||||
在 kubernetes 里面,Pod 实际上正是 kubernetes 项目为你抽象出来的一个可以类比为进程组的概念。
|
||||
|
||||
前面提到的,由四个进程共同组成的一个应用 Helloworld,在 Kubernetes 里面,实际上会被定义为一个拥有四个容器的 Pod,这个概念大家一定要非常仔细的理解。
|
||||
|
||||
就是说现在有四个职责不同、相互协作的进程,需要放在容器里去运行,在 Kubernetes 里面并不会把它们放到一个容器里,因为这里会遇到两个问题。那么在 Kubernetes 里会怎么去做呢?它会把四个独立的进程分别用四个独立的容器启动起来,然后把它们定义在一个 Pod 里面。
|
||||
|
||||
所以当 Kubernetes 把 Helloworld 给拉起来的时候,你实际上会看到四个容器,它们共享了某些资源,这些资源都属于 Pod,所以我们说 Pod 在 Kubernetes 里面只有一个逻辑单位,没有一个真实的东西对应说这个就是 Pod,不会有的。真正起来在物理上存在的东西,就是四个容器。这四个容器,或者说是多个容器的组合就叫做 Pod。并且还有一个概念一定要非常明确,Pod 是 Kubernetes 分配资源的一个单位,因为里面的容器要共享某些资源,所以 Pod 也是 Kubernetes 的原子调度单位。
|
||||
|
||||
|
||||
|
||||
上面提到的 Pod 设计,也不是 Kubernetes 项目自己想出来的, 而是早在 Google 研发 Borg 的时候,就已经发现了这样一个问题。这个在 Borg paper 里面有非常非常明确的描述。简单来说 Google 工程师发现在 Borg 下面部署应用时,很多场景下都存在着类似于“进程与进程组”的关系。更具体的是,这些应用之前往往有着密切的协作关系,使得它们必须部署在同一台机器上并且共享某些信息。
|
||||
|
||||
以上就是进程组的概念,也是 Pod 的用法。
|
||||
|
||||
为什么 Pod 必须是原子调度单位?
|
||||
|
||||
可能到这里大家会有一些问题:虽然了解这个东西是一个进程组,但是为什么要把 Pod 本身作为一个概念抽象出来呢?或者说能不能通过调度把 Pod 这个事情给解决掉呢?为什么 Pod 必须是 Kubernetes 里面的原子调度单位?
|
||||
|
||||
下面我们通过一个例子来解释。
|
||||
|
||||
假如现在有两个容器,它们是紧密协作的,所以它们应该被部署在一个 Pod 里面。具体来说,第一个容器叫做 App,就是业务容器,它会写日志文件;第二个容器叫做 LogCollector,它会把刚刚 App 容器写的日志文件转发到后端的 ElasticSearch 中。
|
||||
|
||||
两个容器的资源需求是这样的:App 容器需要 1G 内存,LogCollector 需要 0.5G 内存,而当前集群环境的可用内存是这样一个情况:Node*A:1.25G 内存,Node*B:2G 内存。
|
||||
|
||||
假如说现在没有 Pod 概念,就只有两个容器,这两个容器要紧密协作、运行在一台机器上。可是,如果调度器先把 App 调度到了 Node*A 上面,接下来会怎么样呢?这时你会发现:LogCollector 实际上是没办法调度到 Node*A 上的,因为资源不够。其实此时整个应用本身就已经出问题了,调度已经失败了,必须去重新调度。
|
||||
|
||||
|
||||
|
||||
以上就是一个非常典型的成组调度失败的例子。英文叫做:Task co-scheduling 问题,这个问题不是说不能解,在很多项目里面,这样的问题都有解法。
|
||||
|
||||
比如说在 Mesos 里面,它会做一个事情,叫做资源囤积(resource hoarding):即当所有设置了 Affinity 约束的任务都达到时,才开始统一调度,这是一个非常典型的成组调度的解法。
|
||||
|
||||
所以上面提到的“App”和“LogCollector”这两个容器,在 Mesos 里面,他们不会说立刻调度,而是等两个容器都提交完成,才开始统一调度。这样也会带来新的问题,首先调度效率会损失,因为需要等待。由于需要等还会有外一个情况会出现,就是产生死锁,就是互相等待的一个情况。这些机制在 Mesos 里都是需要解决的,也带来了额外的复杂度。
|
||||
|
||||
另一种解法是 Google 的解法。它在 Omega 系统(就是 Borg 下一代)里面,做了一个非常复杂且非常厉害的解法,叫做乐观调度。比如说:不管这些冲突的异常情况,先调度,同时设置一个非常精妙的回滚机制,这样经过冲突后,通过回滚来解决问题。这个方式相对来说要更加优雅,也更加高效,但是它的实现机制是非常复杂的。这个有很多人也能理解,就是悲观锁的设置一定比乐观锁要简单。
|
||||
|
||||
而像这样的一个 Task co-scheduling 问题,在 Kubernetes 里,就直接通过 Pod 这样一个概念去解决了。因为在 Kubernetes 里,这样的一个 App 容器和 LogCollector 容器一定是属于一个 Pod 的,它们在调度时必然是以一个 Pod 为单位进行调度,所以这个问题是根本不存在的。
|
||||
|
||||
再次理解 Pod
|
||||
|
||||
在讲了前面这些知识点之后,我们来再次理解一下 Pod,首先 Pod 里面的容器是“超亲密关系”。
|
||||
|
||||
这里有个“超”字需要大家理解,正常来说,有一种关系叫做亲密关系,这个亲密关系是一定可以通过调度来解决的。
|
||||
|
||||
|
||||
|
||||
比如说现在有两个 Pod,它们需要运行在同一台宿主机上,那这样就属于亲密关系,调度器一定是可以帮助去做的。但是对于超亲密关系来说,有一个问题,即它必须通过 Pod 来解决。因为如果超亲密关系赋予不了,那么整个 Pod 或者说是整个应用都无法启动。
|
||||
|
||||
什么叫做超亲密关系呢?大概分为以下几类:
|
||||
|
||||
|
||||
比如说两个进程之间会发生文件交换,前面提到的例子就是这样,一个写日志,一个读日志;
|
||||
两个进程之间需要通过 localhost 或者说是本地的 Socket 去进行通信,这种本地通信也是超亲密关系;
|
||||
这两个容器或者是微服务之间,需要发生非常频繁的 RPC 调用,出于性能的考虑,也希望它们是超亲密关系;
|
||||
两个容器或者是应用,它们需要共享某些 Linux Namespace。最简单常见的一个例子,就是我有一个容器需要加入另一个容器的 Network Namespace。这样我就能看到另一个容器的网络设备,和它的网络信息。
|
||||
|
||||
|
||||
像以上几种关系都属于超亲密关系,它们都是在 Kubernetes 中会通过 Pod 的概念去解决的。
|
||||
|
||||
现在我们理解了 Pod 这样的概念设计,理解了为什么需要 Pod。它解决了两个问题:
|
||||
|
||||
|
||||
我们怎么去描述超亲密关系;
|
||||
我们怎么去对超亲密关系的容器或者说是业务去做统一调度,这是 Pod 最主要的一个诉求。
|
||||
|
||||
|
||||
Pod 的实现机制
|
||||
|
||||
Pod 要解决的问题
|
||||
|
||||
像 Pod 这样一个东西,本身是一个逻辑概念。那在机器上,它究竟是怎么实现的呢?这就是我们要解释的第二个问题。
|
||||
|
||||
既然说 Pod 要解决这个问题,核心就在于如何让一个 Pod 里的多个容器之间最高效的共享某些资源和数据。
|
||||
|
||||
因为容器之间原本是被 Linux Namespace 和 cgroups 隔开的,所以现在实际要解决的是怎么去打破这个隔离,然后共享某些事情和某些信息。这就是 Pod 的设计要解决的核心问题所在。
|
||||
|
||||
所以说具体的解法分为两个部分:网络和存储。
|
||||
|
||||
共享网络
|
||||
|
||||
第一个问题是 Pod 里的多个容器怎么去共享网络?下面是个例子:
|
||||
|
||||
比如说现在有一个 Pod,其中包含了一个容器 A 和一个容器 B,它们两个就要共享 Network Namespace。在 Kubernetes 里的解法是这样的:它会在每个 Pod 里,额外起一个 Infra container 小容器来共享整个 Pod 的 Network Namespace。
|
||||
|
||||
Infra container 是一个非常小的镜像,大概 100~200KB 左右,是一个汇编语言写的、永远处于“暂停”状态的容器。由于有了这样一个 Infra container 之后,其他所有容器都会通过 Join Namespace 的方式加入到 Infra container 的 Network Namespace 中。
|
||||
|
||||
所以说一个 Pod 里面的所有容器,它们看到的网络视图是完全一样的。即:它们看到的网络设备、IP地址、Mac地址等等,跟网络相关的信息,其实全是一份,这一份都来自于 Pod 第一次创建的这个 Infra container。这就是 Pod 解决网络共享的一个解法。
|
||||
|
||||
在 Pod 里面,一定有一个 IP 地址,是这个 Pod 的 Network Namespace 对应的地址,也是这个 Infra container 的 IP 地址。所以大家看到的都是一份,而其他所有网络资源,都是一个 Pod 一份,并且被 Pod 中的所有容器共享。这就是 Pod 的网络实现方式。
|
||||
|
||||
由于需要有一个相当于说中间的容器存在,所以整个 Pod 里面,必然是 Infra container 第一个启动。并且整个 Pod 的生命周期是等同于 Infra container 的生命周期的,与容器 A 和 B 是无关的。这也是为什么在 Kubernetes 里面,它是允许去单独更新 Pod 里的某一个镜像的,即:做这个操作,整个 Pod 不会重建,也不会重启,这是非常重要的一个设计。
|
||||
|
||||
|
||||
|
||||
共享存储
|
||||
|
||||
第二问题:Pod 怎么去共享存储?Pod 共享存储就相对比较简单。
|
||||
|
||||
比如说现在有两个容器,一个是 Nginx,另外一个是非常普通的容器,在 Nginx 里放一些文件,让我能通过 Nginx 访问到。所以它需要去 share 这个目录。我 share 文件或者是 share 目录在 Pod 里面是非常简单的,实际上就是把 volume 变成了 Pod level。然后所有容器,就是所有同属于一个 Pod 的容器,他们共享所有的 volume。
|
||||
|
||||
|
||||
|
||||
比如说上图的例子,这个 volume 叫做 shared-data,它是属于 Pod level 的,所以在每一个容器里可以直接声明:要挂载 shared-data 这个 volume,只要你声明了你挂载这个 volume,你在容器里去看这个目录,实际上大家看到的就是同一份。这个就是 Kubernetes 通过 Pod 来给容器共享存储的一个做法。
|
||||
|
||||
所以在之前的例子中,应用容器 App 写了日志,只要这个日志是写在一个 volume 中,只要声明挂载了同样的 volume,这个 volume 就可以立刻被另外一个 LogCollector 容器给看到。以上就是 Pod 实现存储的方式。
|
||||
|
||||
详解容器设计模式
|
||||
|
||||
现在我们知道了为什么需要 Pod,也了解了 Pod 这个东西到底是怎么实现的。最后,以此为基础,详细介绍一下 Kubernetes 非常提倡的一个概念,叫做容器设计模式。
|
||||
|
||||
举例
|
||||
|
||||
接下来将会用一个例子来给大家进行讲解。
|
||||
|
||||
比如我现在有一个非常常见的一个诉求:我现在要发布一个应用,这个应用是 JAVA 写的,有一个 WAR 包需要把它放到 Tomcat 的 web APP 目录下面,这样就可以把它启动起来了。可是像这样一个 WAR 包或 Tomcat 这样一个容器的话,怎么去做,怎么去发布?这里面有几种做法。
|
||||
|
||||
|
||||
|
||||
|
||||
第一种方式:可以把 WAR 包和 Tomcat 打包放进一个镜像里面。但是这样带来一个问题,就是现在这个镜像实际上揉进了两个东西。那么接下来,无论是我要更新 WAR 包还是说我要更新 Tomcat,都要重新做一个新的镜像,这是比较麻烦的;
|
||||
第二种方式:就是镜像里面只打包 Tomcat。它就是一个 Tomcat,但是需要使用数据卷的方式,比如说 hostPath,从宿主机上把 WAR 包挂载进我们 Tomcat 容器中,挂到我的 web APP 目录下面,这样把这个容器启用起来之后,里面就能用了。
|
||||
|
||||
|
||||
但是这时会发现一个问题:这种做法一定需要维护一套分布式存储系统。因为这个容器可能第一次启动是在宿主机 A 上面,第二次重新启动就可能跑到 B 上去了,容器它是一个可迁移的东西,它的状态是不保持的。所以必须维护一套分布式存储系统,使容器不管是在 A 还是在 B 上,都可以找到这个 WAR 包,找到这个数据。
|
||||
|
||||
|
||||
注意,即使有了分布式存储系统做 Volume,你还需要负责维护 Volume 里的 WAR 包。比如:你需要单独写一套 Kubernetes Volume 插件,用来在每次 Pod 启动之前,把应用启动所需的 WAR 包下载到这个 Volume 里,然后才能被应用挂载使用到。
|
||||
|
||||
|
||||
这样操作带来的复杂程度还是比较高的,且这个容器本身必须依赖于一套持久化的存储插件(用来管理 Volume 里的 WAR 包内容)。
|
||||
|
||||
InitContainer
|
||||
|
||||
所以大家有没有考虑过,像这样的组合方式,有没有更加通用的方法?哪怕在本地 Kubernetes 上,没有分布式存储的情况下也能用、能玩、能发布。
|
||||
|
||||
实际上方法是有的,在 Kubernetes 里面,像这样的组合方式,叫做 Init Container。
|
||||
|
||||
|
||||
|
||||
还是同样一个例子:在上图的 yaml 里,首先定义一个 Init Container,它只做一件事情,就是把 WAR 包从镜像里拷贝到一个 Volume 里面,它做完这个操作就退出了,所以 Init Container 会比用户容器先启动,并且严格按照定义顺序来依次执行。
|
||||
|
||||
然后,这个关键在于刚刚拷贝到的这样一个目的目录:APP 目录,实际上是一个 Volume。而我们前面提到,一个 Pod 里面的多个容器,它们是可以共享 Volume 的,所以现在这个 Tomcat 容器,只是打包了一个 Tomcat 镜像。但在启动的时候,要声明使用 APP 目录作为我的 Volume,并且要把它们挂载在 Web APP 目录下面。
|
||||
|
||||
而这个时候,由于前面已经运行过了一个 Init Container,已经执行完拷贝操作了,所以这个 Volume 里面已经存在了应用的 WAR 包:就是 sample.war,绝对已经存在这个 Volume 里面了。等到第二步执行启动这个 Tomcat 容器的时候,去挂这个 Volume,一定能在里面找到前面拷贝来的 sample.war。
|
||||
|
||||
所以可以这样去描述:这个 Pod 就是一个自包含的,可以把这一个 Pod 在全世界任何一个 Kubernetes 上面都顺利启用起来。不用担心没有分布式存储、Volume 不是持久化的,它一定是可以公布的。
|
||||
|
||||
所以这是一个通过组合两个不同角色的容器,并且按照这样一些像 Init Container 这样一种编排方式,统一的去打包这样一个应用,把它用 Pod 来去做的非常典型的一个例子。像这样的一个概念,在 Kubernetes 里面就是一个非常经典的容器设计模式,叫做:“Sidecar”。
|
||||
|
||||
容器设计模式:Sidecar
|
||||
|
||||
什么是 Sidecar?就是说其实在 Pod 里面,可以定义一些专门的容器,来执行主业务容器所需要的一些辅助工作,比如我们前面举的例子,其实就干了一个事儿,这个 Init Container,它就是一个 Sidecar,它只负责把镜像里的 WAR 包拷贝到共享目录里面,以便被 Tomcat 能够用起来。
|
||||
|
||||
其它有哪些操作呢?比如说:
|
||||
|
||||
|
||||
原本需要在容器里面执行 SSH 需要干的一些事情,可以写脚本、一些前置的条件,其实都可以通过像 Init Container 或者另外像 Sidecar 的方式去解决;
|
||||
当然还有一个典型例子就是我的日志收集,日志收集本身是一个进程,是一个小容器,那么就可以把它打包进 Pod 里面去做这个收集工作;
|
||||
还有一个非常重要的东西就是 Debug 应用,实际上现在 Debug 整个应用都可以在应用 Pod 里面再次定义一个额外的小的 Container,它可以去 exec 应用 pod 的 namespace;
|
||||
查看其他容器的工作状态,这也是它可以做的事情。不再需要去 SSH 登陆到容器里去看,只要把监控组件装到额外的小容器里面就可以了,然后把它作为一个 Sidecar 启动起来,跟主业务容器进行协作,所以同样业务监控也都可以通过 Sidecar 方式来去做。
|
||||
|
||||
|
||||
这种做法一个非常明显的优势就是在于其实将辅助功能从我的业务容器解耦了,所以我就能够独立发布 Sidecar 容器,并且更重要的是这个能力是可以重用的,即同样的一个监控 Sidecar 或者日志 Sidecar,可以被全公司的人共用的。这就是设计模式的一个威力。
|
||||
|
||||
|
||||
|
||||
Sidecar:应用与日志收集
|
||||
|
||||
接下来,我们再详细细化一下 Sidecar 这样一个模式,它还有一些其他的场景。
|
||||
|
||||
比如说前面提到的应用日志收集,业务容器将日志写在一个 Volume 里面,而由于 Volume 在 Pod 里面是被共享的,所以日志容器 —— 即 Sidecar 容器一定可以通过共享该 Volume,直接把日志文件读出来,然后存到远程存储里面,或者转发到另外一个例子。现在业界常用的 Fluentd 日志进程或日志组件,基本上都是这样的工作方式。
|
||||
|
||||
|
||||
|
||||
Sidecar:代理容器
|
||||
|
||||
Sidecar 的第二个用法,可以称作为代理容器 Proxy。什么叫做代理容器呢?
|
||||
|
||||
假如现在有个 Pod 需要访问一个外部系统,或者一些外部服务,但是这些外部系统是一个集群,那么这个时候如何通过一个统一的、简单的方式,用一个 IP 地址,就把这些集群都访问到?有一种方法就是:修改代码。因为代码里记录了这些集群的地址;另外还有一种解耦的方法,即通过 Sidecar 代理容器。
|
||||
|
||||
简单说,单独写一个这么小的 Proxy,用来处理对接外部的服务集群,它对外暴露出来只有一个 IP 地址就可以了。所以接下来,业务容器主要访问 Proxy,然后由 Proxy 去连接这些服务集群,这里的关键在于 Pod 里面多个容器是通过 localhost 直接通信的,因为它们同属于一个 network Namespace,网络视图都一样,所以它们俩通信 localhost,并没有性能损耗。
|
||||
|
||||
所以说代理容器除了做了解耦之外,并不会降低性能,更重要的是,像这样一个代理容器的代码就又可以被全公司重用了。
|
||||
|
||||
|
||||
|
||||
Sidecar:适配器容器
|
||||
|
||||
Sidecar 的第三个设计模式 —— 适配器容器 Adapter,什么叫 Adapter 呢?
|
||||
|
||||
现在业务暴露出来的 API,比如说有个 API 的一个格式是 A,但是现在有一个外部系统要去访问我的业务容器,它只知道的一种格式是 API B ,所以要做一个工作,就是把业务容器怎么想办法改掉,要去改业务代码。但实际上,你可以通过一个 Adapter 帮你来做这层转换。
|
||||
|
||||
|
||||
|
||||
现在有个例子:现在业务容器暴露出来的监控接口是 /metrics,访问这个这个容器的 metrics 的这个 URL 就可以拿到了。可是现在,这个监控系统升级了,它访问的 URL 是 /health,我只认得暴露出 health 健康检查的 URL,才能去做监控,metrics 不认识。那这个怎么办?那就需要改代码了,但可以不去改代码,而是额外写一个 Adapter,用来把所有对 health 的这个请求转发给 metrics 就可以了,所以这个 Adapter 对外暴露的是 health 这样一个监控的 URL,这就可以了,你的业务就又可以工作了。
|
||||
|
||||
这样的关键还在于 Pod 之中的容器是通过 localhost 直接通信的,所以没有性能损耗,并且这样一个 Adapter 容器可以被全公司重用起来,这些都是设计模式给我们带来的好处。
|
||||
|
||||
本节总结
|
||||
|
||||
|
||||
Pod 是 Kubernetes 项目里实现“容器设计模式”的核心机制;
|
||||
“容器设计模式”是 Google Borg 的大规模容器集群管理最佳实践之一,也是 Kubernetes 进行复杂应用编排的基础依赖之一;
|
||||
所有“设计模式”的本质都是:解耦和重用。
|
||||
|
||||
|
||||
|
||||
|
||||
|
302
专栏/CNCFX阿里巴巴云原生技术公开课/05应用编排与管理:核心原理.md
Normal file
302
专栏/CNCFX阿里巴巴云原生技术公开课/05应用编排与管理:核心原理.md
Normal file
@ -0,0 +1,302 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
05 应用编排与管理:核心原理
|
||||
资源元信息
|
||||
|
||||
1. Kubernetes 资源对象
|
||||
|
||||
首先,我们来回顾一下 Kubernetes 的资源对象组成:主要包括了 Spec、Status 两部分。其中 Spec 部分用来描述期望的状态,Status 部分用来描述观测到的状态。
|
||||
|
||||
今天我们将为大家介绍 K8s 的另外一个部分,即元数据部分。该部分主要包括了用来识别资源的标签:Labels, 用来描述资源的注解;Annotations, 用来描述多个资源之间相互关系的 OwnerReference。这些元数据在 K8s 运行中有非常重要的作用。后续课程中将会反复讲到。
|
||||
|
||||
2. labels
|
||||
|
||||
第一个元数据,也是最重要的一个元数据是:资源标签。资源标签是一种具有标识型的 Key:Value 元数据,这里展示了几个常见的标签。
|
||||
|
||||
前三个标签都打在了 Pod 对象上,分别标识了对应的应用环境、发布的成熟度和应用的版本。从应用标签的例子可以看到,标签的名字包括了一个域名的前缀,用来描述打标签的系统和工具, 最后一个标签打在 Node 对象上,还在域名前增加了版本的标识 beta 字符串。
|
||||
|
||||
标签主要用来筛选资源和组合资源,可以使用类似于 SQL 查询 select,来根据 Label 查询相关的资源。
|
||||
|
||||
|
||||
|
||||
3. Selector
|
||||
|
||||
最常见的 Selector 就是相等型 Selector。现在举一个简单的例子:
|
||||
|
||||
假设系统中有四个 Pod,每个 Pod 都有标识系统层级和环境的标签,我们通过 Tie:front 这个标签,可以匹配左边栏的 Pod,相等型 Selector 还可以包括多个相等条件,多个相等条件之间是逻辑”与“的关系。
|
||||
|
||||
在刚才的例子中,通过 Tie=front,Env=dev 的Selector,我们可以筛选出所有 Tie=front,而且 Env=dev 的 Pod,也就是下图中左上角的 Pod。另外一种 Selector 是集合型 Selector,在例子中,Selector 筛选所有环境是 test 或者 gray 的 Pod。
|
||||
|
||||
除了 in 的集合操作外,还有 notin 集合操作,比如 tie notin(front,back),将会筛选所有 tie 不是 front 且不是 back 的 Pod。另外,也可以根据是否存在某 lable 的筛选,如:Selector release,筛选所有带 release 标签的 Pod。集合型和相等型的 Selector,也可以用“,”来连接,同样的标识逻辑”与“的关系。
|
||||
|
||||
|
||||
|
||||
4. Annotations
|
||||
|
||||
另外一种重要的元数据是:annotations。一般是系统或者工具用来存储资源的非标示性信息,可以用来扩展资源的 spec/status 的描述,这里给了几个 annotations 的例子:
|
||||
|
||||
第一个例子,存储了阿里云负载器的证书 ID,我们可以看到 annotations 一样可以拥有域名的前缀,标注中也可以包含版本信息。第二个 annotation存储了 nginx 接入层的配置信息,我们可以看到 annotations 中包括“,”这样无法出现在 label 中的特殊字符。第三个 annotations 一般可以在 kubectl apply 命令行操作后的资源中看到, annotation 值是一个结构化的数据,实际上是一个 json 串,标记了上一次 kubectl 操作的资源的 json 的描述。
|
||||
|
||||
|
||||
|
||||
5. Ownereference
|
||||
|
||||
我们当时讲到最后一个元数据叫做 Ownereference,所谓所有者,一般就是指集合类的资源,比如说 Pod 集合,就有 replicaset、statefulset,这个将在后序的课程中讲到。
|
||||
|
||||
集合类资源的控制器会创建对应的归属资源。比如:replicaset 控制器在操作中会创建 Pod,被创建 Pod 的 Ownereference 就指向了创建 Pod 的 replicaset,Ownereference 使得用户可以方便地查找一个创建资源的对象,另外,还可以用来实现级联删除的效果。** **
|
||||
|
||||
操作演示
|
||||
|
||||
这里通过 kubectl 命令去连接我们 ACK 中已经创建好的一个 K8s 集群,然后来展示一下怎么查看和修改 K8s 对象中的元数据,主要就是 Pod 的一个标签、注解,还有对应的 Ownerference。
|
||||
|
||||
首先我们看一下集群里现在的配置情况:
|
||||
|
||||
查看 Pod,现在没有任何的一个 Pod;
|
||||
|
||||
|
||||
kubectl get pods
|
||||
|
||||
|
||||
然后用事先准备好的一个 Pod 的 yaml,创建一个 Pod 出来;
|
||||
|
||||
|
||||
kubectl apply -f pod1.yaml
|
||||
kubectl apply -f pod2.yaml
|
||||
|
||||
|
||||
现在查看一下 Pod 打的标签,我们用 –show-labels 这个选项,可以看到这两个 Pod 都打上了一个部署环境和层级的标签;
|
||||
|
||||
|
||||
kubectl get pods —show-labels
|
||||
|
||||
|
||||
我们也可以通过另外一种方式来查看具体的资源信息。首先查看 nginx1 第一个 Pod 的一个信息,用 -o yaml 的方式输出,可以看到这个 Pod 元数据里面包括了一个 lables 的字段,里面有两个 lable;
|
||||
|
||||
|
||||
kubectl get pods nginx1 -o yaml | less
|
||||
|
||||
|
||||
现在再想一下,怎么样对 Pod 已有的 lable 进行修改?我们先把它的部署环境,从开发环境改成测试环境,然后指定 Pod 名字,在环境再加上它的一个值 test ,看一下能不能成功。 这里报了一个错误,可以看到,它其实是说现在这个 label 已经有值了;
|
||||
|
||||
|
||||
kubectl label pods nginx1 env=test
|
||||
|
||||
|
||||
如果想覆盖掉它的话,得额外再加上一个覆盖的选项。加上之后呢,我们应该可以看到这个打标已经成功了;
|
||||
|
||||
|
||||
kubectl label pods nginx1 env=test —overwrite
|
||||
|
||||
|
||||
我们再看一下现在集群的 lable 设置情况,首先可以看到 nginx1 的确已经加上了一个部署环境 test 标签;
|
||||
|
||||
|
||||
kubectl get pods —show-labels
|
||||
|
||||
|
||||
如果想要对 Pod 去掉一个标签,也是跟打标签一样的操作,但是 env 后就不是等号了。只加上 label 名字,后面不加等号,改成用减号表示去除 label 的 k:v;
|
||||
|
||||
|
||||
kubectl label pods nginx tie-
|
||||
|
||||
|
||||
可以看到这个 label,去标已经完全成功;
|
||||
|
||||
|
||||
kubectl get pods —show-labels
|
||||
|
||||
|
||||
|
||||
|
||||
下面来看一下配置的 label 值,的确能看到 nginx1 的这个 Pod 少了一个 tie=front 的标签。有了这个 Pod 标签之后,可以看一下怎样用 label Selector 进行匹配?首先 label Selector 是通过 -l 这个选项来进行指定的 ,指定的时候,先试一下用相等型的一个 label 来筛选,所以我们指定的是部署环境等于测试的一个 Pod,我们可以看到能够筛选出一台;
|
||||
|
||||
|
||||
kubectl get pods —show-labels -l env=test
|
||||
|
||||
|
||||
假如说有多个相等的条件需要指定的,实际上这是一个与的关系,假如说 env 再等于 dev,我们实际上是一个 Pod 都拿不到的;
|
||||
|
||||
|
||||
kubectl get pods —show-labels -l env=test,env=dev
|
||||
|
||||
|
||||
然后假如说 env=dev,但是 tie=front,我们能够匹配到第二个 Pod,也就是 nginx2;
|
||||
|
||||
|
||||
kubectl get pods —show-labels -l env=dev,tie=front
|
||||
|
||||
|
||||
我们还可以再试一下怎么样用集合型的 label Selector 来进行筛选。这一次我们还是想要匹配出所有部署环境是 test 或者是 dev 的一个 Pod,所以在这里加上一个引号,然后在括号里面指定所有部署环境的一个集合。这次能把两个创建的 Pod 都筛选出来;
|
||||
|
||||
|
||||
kubectl get pods —show-labels -l ’env in (dev,test)’
|
||||
|
||||
|
||||
我们再试一下怎样对 Pod 增加一个注解,注解的话,跟打标是一样的操作,但是把 label 命令改成 annotate 命令;然后,一样指定类型和对应的名字。后面就不是加上 label 的 k:v 了,而是加上 annotation 的 k:v。这里我们可以指定一个任意的字符串,比如说加上空格、加上逗号都可以;
|
||||
|
||||
|
||||
kubectl annotate pods nginx1 my-annotate=‘my annotate,ok’
|
||||
|
||||
|
||||
然后,我们再看一下这个 Pod 的一些元数据,我们这边能够看到这个 Pod 的元数据里面 annotations,这是有一个 my-annotate 这个 Annotations;
|
||||
|
||||
|
||||
kubectl get pods nging1 -o yaml | less
|
||||
|
||||
|
||||
然后我们这里其实也能够看到有一个 kubectl apply 的时候,kubectl 工具增加了一个 annotation,这也是一个 json 串。
|
||||
|
||||
|
||||
|
||||
然后我们再演示一下看 Pod 的 Ownereference 是怎么出来的。原来的 Pod 都是直接通过创建 Pod 这个资源方式来创建的,这次换一种方式来创建:通过创建一个 ReplicaSet 对象来创建 Pod 。首先创建一个 ReplicaSet 对象,这个 ReplicaSet 对象可以具体查看一下;
|
||||
|
||||
|
||||
kubectl apply -f rs.yaml
|
||||
kubectl get replicasets nginx-replicasets -o yaml |less
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
我们可以关注一下这个 ReplicaSet 里面 spec 里面,提到会创建两个 Pod,然后 selector 通过匹配部署环境是 product 生产环境的这个标签来进行匹配。所以我们可以看一下,现在集群中的 Pod 情况;
|
||||
|
||||
|
||||
|
||||
**kubectl get pods **
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
将会发现多了两个 Pod,仔细查看这两个 Pod,可以看到 ReplicaSet 创建出来的 Pod 有一个特点,即它会带有 Ownereference,然后 Ownereference 里面指向了是一个 replicasets 类型,名字就叫做 nginx-replicasets;
|
||||
|
||||
|
||||
|
||||
kubectl get pods nginx-replicasets-rhd68 -o yaml | less
|
||||
|
||||
|
||||
|
||||
|
||||
控制器模式
|
||||
|
||||
1、控制循环
|
||||
|
||||
控制型模式最核心的就是控制循环的概念。在控制循环中包括了控制器,被控制的系统,以及能够观测系统的传感器,三个逻辑组件。
|
||||
|
||||
当然这些组件都是逻辑的,外界通过修改资源 spec 来控制资源,控制器比较资源 spec 和 status,从而计算一个 diff,diff 最后会用来决定执行对系统进行什么样的控制操作,控制操作会使得系统产生新的输出,并被传感器以资源 status 形式上报,控制器的各个组件将都会是独立自主地运行,不断使系统向 spec 表示终态趋近。
|
||||
|
||||
|
||||
|
||||
2、Sensor
|
||||
|
||||
控制循环中逻辑的传感器主要由 Reflector、Informer、Indexer 三个组件构成。
|
||||
|
||||
Reflector 通过 List 和 Watch K8s server 来获取资源的数据。List 用来在 Controller 重启以及 Watch 中断的情况下,进行系统资源的全量更新;而 Watch 则在多次 List 之间进行增量的资源更新;Reflector 在获取新的资源数据后,会在 Delta 队列中塞入一个包括资源对象信息本身以及资源对象事件类型的 Delta 记录,Delta 队列中可以保证同一个对象在队列中仅有一条记录,从而避免 Reflector 重新 List 和 Watch 的时候产生重复的记录。
|
||||
|
||||
Informer 组件不断地从 Delta 队列中弹出 delta 记录,然后把资源对象交给 indexer,让 indexer 把资源记录在一个缓存中,缓存在默认设置下是用资源的命名空间来做索引的,并且可以被 Controller Manager 或多个 Controller 所共享。之后,再把这个事件交给事件的回调函数
|
||||
|
||||
|
||||
|
||||
控制循环中的控制器组件主要由事件处理函数以及 worker 组成,事件处理函数之间会相互关注资源的新增、更新、删除的事件,并根据控制器的逻辑去决定是否需要处理。对需要处理的事件,会把事件关联资源的命名空间以及名字塞入一个工作队列中,并且由后续的 worker 池中的一个 Worker 来处理,工作队列会对存储的对象进行去重,从而避免多个 Woker 处理同一个资源的情况。
|
||||
|
||||
Worker 在处理资源对象时,一般需要用资源的名字来重新获得最新的资源数据,用来创建或者更新资源对象,或者调用其他的外部服务,Worker 如果处理失败的时候,一般情况下会把资源的名字重新加入到工作队列中,从而方便之后进行重试。
|
||||
|
||||
3、控制循环例子-扩容
|
||||
|
||||
这里举一个简单的例子来说明一下控制循环的工作原理。
|
||||
|
||||
ReplicaSet 是一个用来描述无状态应用的扩缩容行为的资源, ReplicaSet controler 通过监听 ReplicaSet 资源来维持应用希望的状态数量,ReplicaSet 中通过 selector 来匹配所关联的 Pod,在这里考虑 ReplicaSet rsA 的,replicas 从 2 被改到 3 的场景。
|
||||
|
||||
|
||||
|
||||
首先,Reflector 会 watch 到 ReplicaSet 和 Pod 两种资源的变化,为什么我们还会 watch pod 资源的变化稍后会讲到。发现 ReplicaSet 发生变化后,在 delta 队列中塞入了对象是 rsA,而且类型是更新的记录。
|
||||
|
||||
Informer 一方面把新的 ReplicaSet 更新到缓存中,并与 Namespace nsA 作为索引。另外一方面,调用 Update 的回调函数,ReplicaSet 控制器发现 ReplicaSet 发生变化后会把字符串的 nsA/rsA 字符串塞入到工作队列中,工作队列后的一个 Worker 从工作队列中取到了 nsA/rsA 这个字符串的 key,并且从缓存中取到了最新的 ReplicaSet 数据。
|
||||
|
||||
Worker 通过比较 ReplicaSet 中 spec 和 status 里的数值,发现需要对这个 ReplicaSet 进行扩容,因此 ReplicaSet 的 Worker 创建了一个 Pod,这个 pod 中的 Ownereference 取向了 ReplicaSet rsA。
|
||||
|
||||
|
||||
|
||||
然后 Reflector Watch 到的 Pod 新增事件,在 delta 队列中额外加入了 Add 类型的 deta 记录,一方面把新的 Pod 记录通过 Indexer 存储到了缓存中,另一方面调用了 ReplicaSet 控制器的 Add 回调函数,Add 回调函数通过检查 pod ownerReferences 找到了对应的 ReplicaSet,并把包括 ReplicaSet 命名空间和字符串塞入到了工作队列中。
|
||||
|
||||
ReplicaSet 的 Woker 在得到新的工作项之后,从缓存中取到了新的 ReplicaSet 记录,并得到了其所有创建的 Pod,因为 ReplicaSet 的状态不是最新的,也就是所有创建 Pod 的数量不是最新的。因此在此时 ReplicaSet 更新 status 使得 spec 和 status 达成一致。
|
||||
|
||||
|
||||
|
||||
控制器模式总结
|
||||
|
||||
1、两种 API 设计方法
|
||||
|
||||
Kubernetes 控制器模式依赖声明式的 API。另外一种常见的 API 类型是命令式 API。为什么 Kubernetes 采用声明式 API,而不是命令式 API 来设计整个控制器呢?
|
||||
|
||||
首先,比较两种 API 在交互行为上的差别。在生活中,常见的命令式的交互方式是家长和孩子交流方式,因为孩子欠缺目标意识,无法理解家长期望,家长往往通过一些命令,教孩子一些明确的动作,比如说:吃饭、睡觉类似的命令。我们在容器编排体系中,命令式 API 就是通过向系统发出明确的操作来执行的。
|
||||
|
||||
而常见的声明式交互方式,就是老板对自己员工的交流方式。老板一般不会给自己的员工下很明确的决定,实际上可能老板对于要操作的事情本身,还不如员工清楚。因此,老板通过给员工设置可量化的业务目标的方式,来发挥员工自身的主观能动性。比如说,老板会要求某个产品的市场占有率达到 80%,而不会指出要达到这个市场占有率,要做的具体操作细节。
|
||||
|
||||
类似的,在容器编排体系中,我们可以执行一个应用实例副本数保持在 3 个,而不用明确的去扩容 Pod 或是删除已有的 Pod,来保证副本数在三个。
|
||||
|
||||
|
||||
|
||||
2、命令式 API 的问题
|
||||
|
||||
在理解两个交互 API 的差别后,可以分析一下命令式 API 的问题。
|
||||
|
||||
|
||||
命令 API 最大的一个问题在于错误处理;
|
||||
|
||||
|
||||
在大规模的分布式系统中,错误是无处不在的。一旦发出的命令没有响应,调用方只能通过反复重试的方式来试图恢复错误,然而盲目的重试可能会带来更大的问题。
|
||||
|
||||
假设原来的命令,后台实际上已经执行完成了,重试后又多执行了一个重试的命令操作。为了避免重试的问题,系统往往还需要在执行命令前,先记录一下需要执行的命令,并且在重启等场景下,重做待执行的命令,而且在执行的过程中,还需要考虑多个命令的先后顺序、覆盖关系等等一些复杂的逻辑情况。
|
||||
|
||||
|
||||
实际上许多命令式的交互系统后台往往还会做一个巡检的系统,用来修正命令处理超时、重试等一些场景造成数据不一致的问题;
|
||||
|
||||
|
||||
然而,因为巡检逻辑和日常操作逻辑是不一样的,往往在测试上覆盖不够,在错误处理上不够严谨,具有很大的操作风险,因此往往很多巡检系统都是人工来触发的。
|
||||
|
||||
|
||||
最后,命令式 API 在处理多并发访问时,也很容易出现问题;
|
||||
|
||||
|
||||
假如有多方并发的对一个资源请求进行操作,并且一旦其中有操作出现了错误,就需要重试。那么最后哪一个操作生效了,就很难确认,也无法保证。很多命令式系统往往在操作前会对系统进行加锁,从而保证整个系统最后生效行为的可预见性,但是加锁行为会降低整个系统的操作执行效率。
|
||||
|
||||
|
||||
相对的,声明式 API 系统里天然地记录了系统现在和最终的状态。
|
||||
|
||||
|
||||
不需要额外的操作数据。另外因为状态的幂等性,可以在任意时刻反复操作。在声明式系统运行的方式里,正常的操作实际上就是对资源状态的巡检,不需要额外开发巡检系统,系统的运行逻辑也能够在日常的运行中得到测试和锤炼,因此整个操作的稳定性能够得到保证。
|
||||
|
||||
最后,因为资源的最终状态是明确的,我们可以合并多次对状态的修改。可以不需要加锁,就支持多方的并发访问。
|
||||
|
||||
|
||||
|
||||
3、控制器模式总结
|
||||
|
||||
最后我们总结一下:
|
||||
|
||||
|
||||
Kubernetes 所采用的控制器模式,是由声明式 API 驱动的。确切来说,是基于对 Kubernetes 资源对象的修改来驱动的;
|
||||
Kubernetes 资源之后,是关注该资源的控制器。这些控制器将异步的控制系统向设置的终态驱近;
|
||||
这些控制器是自主运行的,使得系统的自动化和无人值守成为可能;
|
||||
因为 Kubernetes 的控制器和资源都是可以自定义的,因此可以方便的扩展控制器模式。特别是对于有状态应用,我们往往通过自定义资源和控制器的方式,来自动化运维操作。这个也就是后续会介绍的 operator 的场景。
|
||||
|
||||
|
||||
|
||||
|
||||
本节总结
|
||||
|
||||
本节课的主要内容就到此为止了,这里为大家简单总结一下:
|
||||
|
||||
|
||||
Kubernetes 资源对象中的元数据部分,主要包括了用来识别资源的标签:Labels, 用来描述资源的注解;Annotations, 用来描述多个资源之间相互关系的 OwnerReference。这些元数据在 K8s 运行中有非常重要的作用;
|
||||
控制型模式中最核心的就是控制循环的概念;
|
||||
两种 API 设计方法:声明式 API 和命令式 API ;Kubernetes 所采用的控制器模式,是由声明式 API 驱动的。
|
||||
|
||||
|
||||
|
||||
|
||||
|
302
专栏/CNCFX阿里巴巴云原生技术公开课/06应用编排与管理.md
Normal file
302
专栏/CNCFX阿里巴巴云原生技术公开课/06应用编排与管理.md
Normal file
@ -0,0 +1,302 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
06 应用编排与管理
|
||||
需求来源
|
||||
|
||||
背景问题
|
||||
|
||||
首先,我们来看一下背景问题。如下图所示:如果我们直接管理集群中所有的 Pod,应用 A、B、C 的 Pod,其实是散乱地分布在集群中。
|
||||
|
||||
|
||||
|
||||
现在有以下的问题:
|
||||
|
||||
|
||||
首先,如何保证集群内可用 Pod 的数量?也就是说我们应用 A 四个 Pod 如果出现了一些宿主机故障,或者一些网络问题,如何能保证它可用的数量?
|
||||
如何为所有 Pod 更新镜像版本?我们是否要某一个 Pod 去重建新版本的 Pod?
|
||||
然后在更新过程中,如何保证服务的可用性?
|
||||
以及更新过程中,如果发现了问题,如何快速回滚到上一个版本?
|
||||
|
||||
|
||||
Deployment:管理部署发布的控制器
|
||||
|
||||
这里就引入了我们今天课程的主题:Deployment 管理部署发布的控制器。
|
||||
|
||||
|
||||
|
||||
可以看到我们通过 Deployment 将应用 A、B、C 分别规划到不同的 Deployment 中,每个 Deployment 其实是管理的一组相同的应用 Pod,这组 Pod 我们认为它是相同的一个副本,那么 Deployment 能帮我们做什么事情呢?
|
||||
|
||||
首先,Deployment 定义了一种 Pod 期望数量,比如说应用 A,我们期望 Pod 数量是四个,那么这样的话,controller 就会持续维持 Pod 数量为期望的数量。当我们与 Pod 出现了网络问题或者宿主机问题的话,controller 能帮我们恢复,也就是新扩出来对应的 Pod,来保证可用的 Pod 数量与期望数量一致;
|
||||
|
||||
配置 Pod 发布方式,也就是说 controller 会按照用户给定的策略来更新 Pod,而且更新过程中,也可以设定不可用 Pod 数量在多少范围内;
|
||||
|
||||
如果更新过程中发生问题的话,即所谓“一键”回滚,也就是说你通过一条命令或者一行修改能够将 Deployment 下面所有 Pod 更新为某一个旧版本 。
|
||||
|
||||
用例解读
|
||||
|
||||
Deployment 语法
|
||||
|
||||
下面我们用一个简单的用例来解读一下如何操作 Deployment。
|
||||
|
||||
|
||||
|
||||
上图可以看到一个最简单的 Deployment 的 yaml 文件。
|
||||
|
||||
“apiVersion:apps/v1”,也就是说 Deployment 当前所属的组是 apps,版本是 v1。“metadata”是我们看到的 Deployment 元信息,也就是往期回顾中的 Labels、Selector、Pod.image,这些都是在往期中提到的知识点。
|
||||
|
||||
Deployment 作为一个 K8s 资源,它有自己的 metadata 元信息,这里我们定义的 Deployment.name 是 nginx.Deployment。Deployment.spec 中首先要有一个核心的字段,即 replicas,这里定义期望的 Pod 数量为三个;selector 其实是 Pod 选择器,那么所有扩容出来的 Pod,它的 Labels 必须匹配 selector 层上的 image.labels,也就是 app.nginx。
|
||||
|
||||
就如上面的 Pod 模板 template 中所述,这个 template 它其实包含了两部分内容:
|
||||
|
||||
|
||||
一部分是我们期望 Pod 的 metadata,其中包含了 labels,即跟 selector.matchLabels 相匹配的一个 Labels;
|
||||
第二部分是 template 包含的一个 Pod.spec。这里 Pod.spec 其实是 Deployment 最终创建出来 Pod 的时候,它所用的 Pod.spec,这里定义了一个 container.nginx,它的镜像版本是 nginx:1.7.9。
|
||||
|
||||
|
||||
下面是遇到的新知识点:
|
||||
|
||||
|
||||
第一个是 replicas,就是 Deployment 中期望的或者终态数量;
|
||||
第二个是 template,也就是 Pod 相关的一个模板。
|
||||
|
||||
|
||||
查看 Deployment 状态
|
||||
|
||||
当我们创建出一个 Deployment 的时候,可以通过 kubectl get deployment,看到 Deployment 总体的一个状态。如下图所示:
|
||||
|
||||
|
||||
|
||||
上图中可以看到:
|
||||
|
||||
|
||||
DESIRED:期望的 Pod 数量是 3 个;
|
||||
CURRENT:当前实际 Pod 数量是 3 个;
|
||||
UP-TO-DATE:其实是到达最新的期望版本的 Pod 数量;
|
||||
AVAILABLE:这个其实是运行过程中可用的 Pod 数量。后面会提到,这里 AVAILABLE 并不简单是可用的,也就是 Ready 状态的,它其实包含了一些可用超过一定时间长度的 Pod;
|
||||
AGE:deployment 创建的时长,如上图 Deployment 就是已经创建了 80 分钟。
|
||||
|
||||
|
||||
查看 Pod
|
||||
|
||||
最后我们可以查看一下 Pod。如下图所示:
|
||||
|
||||
|
||||
|
||||
上图中有三个 Pod,Pod 名字格式我们不难看到。
|
||||
|
||||
最前面一段:nginx-deployment,其实是 Pod 所属 Deployment.name;中间一段:template-hash,这里三个 Pod 是一样的,因为这三个 Pod 其实都是同一个 template 中创建出来的。
|
||||
|
||||
最后一段,是一个 random 的字符串,我们通过 get.pod 可以看到,Pod 的 ownerReferences 即 Pod 所属的 controller 资源,并不是 Deployment,而是一个 ReplicaSet。这个 ReplicaSet 的 name,其实是 nginx-deployment 加上 pod.template-hash,后面会提到。所有的 Pod 都是 ReplicaSet 创建出来的,而 ReplicaSet 它对应的某一个具体的 Deployment.template 版本。
|
||||
|
||||
更新镜像
|
||||
|
||||
接下来我们可以看一下,如何对一个给定的 Deployment 更新它所有Pod的镜像版本呢?这里我们可以执行一个 kubectl 命令:
|
||||
|
||||
kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1
|
||||
|
||||
首先 kubectl 后面有一个 set image 固定写法,这里指的是设定镜像;其次是一个 deployment.v1.apps,这里也是一个固定写法,写的是我们要操作的资源类型,deployment 是资源名、v1 是资源版本、apps 是资源组,这里也可以简写为 deployment 或者 deployment.apps,比如说写为 deployment 的时候,默认将使用 apps 组 v1 版本。
|
||||
|
||||
第三部分是要更新的 deployment 的 name,也就是我们的 nginx-deployment;再往后的 nginx 其实指的是 template,也就是 Pod 中的 container.name;这里我们可以注意到:一个 Pod 中,其实可能存在多个 container,而我们指定想要更新的镜像的 container.name,就是 nginx。
|
||||
|
||||
最后,指定我们这个容器期望更新的镜像版本,这里指的是 nginx: 1.9.1。如下图所示:当执行完这条命令之后,可以看到 deployment 中的 template.spec 已经更新为 nginx: 1.9.1。
|
||||
|
||||
|
||||
|
||||
快速回滚
|
||||
|
||||
如果我们在发布过程中遇到了问题,也支持快速回滚。通过 kubectl 执行的话,其实是“kubectl rollout undo”这个命令,可以回滚到 Deployment 上一版本;通过“rollout undo”加上“to-revision”来指定可以回滚到某一个具体的版本。
|
||||
|
||||
|
||||
|
||||
DeploymeStatus
|
||||
|
||||
最后我们来看一下 DeploymeStatus。前面的课程我们学习到,每一个资源都有它的 spec.Status。这里可以看一下,deploymentStatus 中描述的三个其实是它的 conversion 状态,也就是 Processing、Complete 以及 Failed。
|
||||
|
||||
|
||||
|
||||
以 Processing 为例:Processing 指的是 Deployment 正在处于扩容和发布中。比如说 Processing 状态的 deployment,它所有的 replicas 及 Pod 副本全部达到最新版本,而且是 available,这样的话,就可以进入 complete 状态。而 complete 状态如果发生了一些扩缩容的话,也会进入 processing 这个处理工作状态。
|
||||
|
||||
如果在处理过程中遇到一些问题:比如说拉镜像失败了,或者说 readiness probe 检查失败了,就会进入 failed 状态;如果在运行过程中即 complete 状态,中间运行时发生了一些 pod readiness probe 检查失败,这个时候 deployment 也会进入 failed 状态。进入 failed 状态之后,除非所有点 replicas 均变成 available,而且是 updated 最新版本,deployment 才会重新进入 complete 状态。
|
||||
|
||||
操作演示
|
||||
|
||||
Deployment 创建及状态
|
||||
|
||||
下面我们来进行操作演示:这里连接一个阿里云服务集群。我们可以看到当前集群已经有几个可用的 node。
|
||||
|
||||
|
||||
|
||||
首先创建对应的 deployment。可以看到 deployment 中的 desired、current、up-to-date 以及 available 已经都达到了可用的期望状态。
|
||||
|
||||
|
||||
|
||||
Deployment 的结构
|
||||
|
||||
这里看到 spec 中的 replicas 是三个,selector 以及 template labels中定义的标签都是 app:nginx,spec 中的 image 是我们期望的 nginx: 1.7.9;status 中的 available.replicas,readReplicas 以及 updatedReplicas 都是 3 个。
|
||||
|
||||
|
||||
|
||||
Pod 状态
|
||||
|
||||
我们可以再选择一个 Pod 看一下状态:
|
||||
|
||||
可以看到:Pod 中 ownerReferences 的功能是 ReplicaSet;pod.spec.container 里的镜像是 1.7.9。这个 Pod 已经是 Running 状态,而且它的 conditions.status 是“true”,表示它的服务已经可用了。
|
||||
|
||||
|
||||
|
||||
更新升级
|
||||
|
||||
当前只有最新版本的 replicaset,那么现在尝试对 deployment 做一次升级。
|
||||
|
||||
|
||||
|
||||
“kubectl set image”这个操作命令,后面接 “deployment”,加 deployment.name,最后指定容器名,以及我们期望升级的镜像版本。
|
||||
|
||||
|
||||
|
||||
接下来我们看下 deployment 中的 template 中的 image 已经更新为 1.9.1。
|
||||
|
||||
|
||||
|
||||
这个时候我们再 get pod 看一下状态。
|
||||
|
||||
|
||||
|
||||
三个 pod 已经升级为新版本,pod 名字中的 pod-template-hash 也已更新。
|
||||
|
||||
|
||||
|
||||
可以看到:旧版本 replicaset 的 spec 数量以及 pod 数量是都是 0,新版本的 pod 数量是 3 个。
|
||||
|
||||
|
||||
|
||||
假设又做了一次更新,这个时候 get.pod 其实可以看到:当前的 pod 其实是有两个旧版本的处于 running,另一个旧版本是在删除中;而两个新版本的 pod,一个已经进入 running,一个还在 creating 中。
|
||||
|
||||
这时我们可用的 pod 数量即非删除状态的 pod 数量,其实是 4 个,已经超过了 replica 原先在 deployment 设置的数量 3 个。这个原因是我们在 deployment 中有 maxavailable 和 maxsugar 两个操作,这两个配置可以限制我们在发布过程中的一些策略。在后面架构设计中会讲到这个问题。
|
||||
|
||||
** **
|
||||
|
||||
历史版本保留 revisionHistoryLimit
|
||||
|
||||
上图看到,我们当前最新版本的 replicaset 是 3 个 pod,另外还有两个历史版本的 replicaset,那么会不会存在一种情况:就是随着 deployment 持续的更新,这个旧版本的 replicaset 会越积越多呢?其实 deployment 提供了一个机制来避免这个问题:在 deployment spec 中,有一个 revisionHistoryLimit,它的默认值为 10,它其实保证了保留历史版本的 replicaset 的数量,我们尝试把它改为 1。
|
||||
|
||||
|
||||
|
||||
由上面第二张图,可以看到两个 replicaset,也就是说,除了当前版本的 replicaset 之外,旧版本的 replicaset 其实只保留了一个。
|
||||
|
||||
回滚
|
||||
|
||||
最后再尝试做一下回滚。首先再来看一下 replicaset,这时发现旧版本的 replicaset 数量从 0 个增到 2 个,而新版本的 replicaset 数量从 3 个削减为 1 个,表示它已经开始在做回滚的操作。然后再观察一下, 旧版本的数量已经是 3 个,即已经回滚成功,而新版本的 pod 数量变为 0 个。
|
||||
|
||||
|
||||
|
||||
我们最后再 get pod 看一下:
|
||||
|
||||
|
||||
|
||||
这时,3 个 pod.template-hash 已经更新为旧版本的 hash,但其实这 3 个 pod 都是重新创建出来的,而并非我们在前一版本中创建的 3 个 pod。换句话说,也就是我们回滚的时候,其实是创建了 3 个旧版本的 pod,而并非把先前的 3 个 pod 找回来。
|
||||
|
||||
架构设计
|
||||
|
||||
管理模式
|
||||
|
||||
|
||||
|
||||
我们来看一下架构设计。首先简单看一下管理模式:Deployment 只负责管理不同版本的 ReplicaSet,由 ReplicaSet 来管理具体的 Pod 副本数,每个 ReplicaSet 对应 Deployment template 的一个版本。在上文的例子中可以看到,每一次修改 template,都会生成一个新的 ReplicaSet,这个 ReplicaSet 底下的 Pod 其实都是相同的版本。
|
||||
|
||||
如上图所示:Deployment 创建 ReplicaSet,而 ReplicaSet 创建 Pod。他们的 OwnerRef 其实都对应了其控制器的资源。
|
||||
|
||||
Deployment 控制器
|
||||
|
||||
我们先简单看一下控制器实现原理。
|
||||
|
||||
首先,我们所有的控制器都是通过 Informer 中的 Event 做一些 Handler 和 Watch。这个地方 Deployment 控制器,其实是关注 Deployment 和 ReplicaSet 中的 event,收到事件后会加入到队列中。而 Deployment controller 从队列中取出来之后,它的逻辑会判断 Check Paused,这个 Paused 其实是 Deployment 是否需要新的发布,如果 Paused 设置为 true 的话,就表示这个 Deployment 只会做一个数量上的维持,不会做新的发布。
|
||||
|
||||
|
||||
|
||||
如上图,可以看到如果 Check paused 为 Yes 也就是 true 的话,那么只会做 Sync replicas。也就是说把 replicas sync 同步到对应的 ReplicaSet 中,最后再 Update Deployment status,那么 controller 这一次的 ReplicaSet 就结束了。
|
||||
|
||||
那么如果 paused 为 false 的话,它就会做 Rollout,也就是通过 Create 或者是 Rolling 的方式来做更新,更新的方式其实也是通过 Create/Update/Delete 这种 ReplicaSet 来做实现的。
|
||||
|
||||
ReplicaSet 控制器
|
||||
|
||||
|
||||
|
||||
当 Deployment 分配 ReplicaSet 之后,ReplicaSet 控制器本身也是从 Informer 中 watch 一些事件,这些事件包含了 ReplicaSet 和 Pod 的事件。从队列中取出之后,ReplicaSet controller 的逻辑很简单,就只管理副本数。也就是说如果 controller 发现 replicas 比 Pod 数量大的话,就会扩容,而如果发现实际数量超过期望数量的话,就会删除 Pod。
|
||||
|
||||
上面 Deployment 控制器的图中可以看到,Deployment 控制器其实做了更复杂的事情,包含了版本管理,而它把每一个版本下的数量维持工作交给 ReplicaSet 来做。
|
||||
|
||||
扩/缩容模拟
|
||||
|
||||
下面来看一些操作模拟,比如说扩容模拟。这里有一个 Deployment,它的副本数是 2,对应的 ReplicaSet 有 Pod1 和 Pod2。这时如果我们修改 Deployment replicas, controller 就会把 replicas 同步到当前版本的 ReplicaSet 中,这个 ReplicaSet 发现当前有 2 个 Pod,不满足当前期望 3 个,就会创建一个新的 Pod3。
|
||||
|
||||
|
||||
|
||||
发布模拟
|
||||
|
||||
我们再模拟一下发布,发布的情况会稍微复杂一点。这里可以看到 Deployment 当前初始的 template,比如说 template1 这个版本。template1 这个 ReplicaSet 对应的版本下有三个 Pod:Pod1,Pod2,Pod3。
|
||||
|
||||
这时修改 template 中一个容器的 image, Deployment controller 就会新建一个对应 template2 的 ReplicaSet。创建出来之后 ReplicaSet 会逐渐修改两个 ReplicaSet 的数量,比如它会逐渐增加 ReplicaSet2 中 replicas 的期望数量,而逐渐减少 ReplicaSet1 中的 Pod 数量。
|
||||
|
||||
那么最终达到的效果是:新版本的 Pod 为 Pod4、Pod5和Pod6,旧版本的 Pod 已经被删除了,这里就完成了一次发布。
|
||||
|
||||
|
||||
|
||||
回滚模拟
|
||||
|
||||
来看一下回滚模拟,根据上面的发布模拟可以知道 Pod4、Pod5、Pod6 已经发布完成。这时发现当前的业务版本是有问题的,如果做回滚的话,不管是通过 rollout 命令还是通过回滚修改 template,它其实都是把 template 回滚为旧版本的 template1。
|
||||
|
||||
这个时候 Deployment 会重新修改 ReplicaSet1 中 Pod 的期望数量,把期望数量修改为 3 个,且会逐渐减少新版本也就是 ReplicaSet2 中的 replica 数量,最终的效果就是把 Pod 从旧版本重新创建出来。
|
||||
|
||||
|
||||
|
||||
发布模拟的图中可以看到,其实初始版本中 Pod1、Pod2、Pod3 是旧版本,而回滚之后其实是 Pod7、Pod8、Pod9。就是说它的回滚并不是把之前的 Pod 重新找出来,而是说重新创建出符合旧版本 template 的 Pod。
|
||||
|
||||
spec 字段解析
|
||||
|
||||
最后再来简单看一些 Deployment 中的字段解析。首先看一下 Deployment 中其他的 spec 字段:
|
||||
|
||||
|
||||
MinReadySeconds:Deployment 会根据 Pod ready 来看 Pod 是否可用,但是如果我们设置了 MinReadySeconds 之后,比如设置为 30 秒,那 Deployment 就一定会等到 Pod ready 超过 30 秒之后才认为 Pod 是 available 的。Pod available 的前提条件是 Pod ready,但是 ready 的 Pod 不一定是 available 的,它一定要超过 MinReadySeconds 之后,才会判断为 available;
|
||||
revisionHistoryLimit:保留历史 revision,即保留历史 ReplicaSet 的数量,默认值为 10 个。这里可以设置为一个或两个,如果回滚可能性比较大的话,可以设置数量超过 10;
|
||||
paused:paused 是标识,Deployment 只做数量维持,不做新的发布,这里在 Debug 场景可能会用到;
|
||||
progressDeadlineSeconds:前面提到当 Deployment 处于扩容或者发布状态时,它的 condition 会处于一个 processing 的状态,processing 可以设置一个超时时间。如果超过超时时间还处于 processing,那么 controller 将认为这个 Pod 会进入 failed 的状态。
|
||||
|
||||
|
||||
|
||||
|
||||
升级策略字段解析
|
||||
|
||||
最后来看一下升级策略字段解析。
|
||||
|
||||
Deployment 在 RollingUpdate 中主要提供了两个策略,一个是 MaxUnavailable,另一个是 MaxSurge。这两个字段解析的意思,可以看下图中详细的 comment,或者简单解释一下:
|
||||
|
||||
|
||||
MaxUnavailable:滚动过程中最多有多少个 Pod 不可用;
|
||||
MaxSurge:滚动过程中最多存在多少个 Pod 超过预期 replicas 数量。
|
||||
|
||||
|
||||
上文提到,ReplicaSet 为 3 的 Deployment 在发布的时候可能存在一种情况:新版本的 ReplicaSet 和旧版本的 ReplicaSet 都可能有两个 replicas,加在一起就是 4 个,超过了我们期望的数量三个。这是因为我们默认的 MaxUnavailable 和 MaxSurge 都是 25%,默认 Deployment 在发布的过程中,可能有 25% 的 replica 是不可用的,也可能超过 replica 数量 25% 是可用的,最高可以达到 125% 的 replica 数量。
|
||||
|
||||
这里其实可以根据用户实际场景来做设置。比如当用户的资源足够,且更注重发布过程中的可用性,可设置 MaxUnavailable 较小、MaxSurge 较大。但如果用户的资源比较紧张,可以设置 MaxSurge 较小,甚至设置为 0,这里要注意的是 MaxSurge 和 MaxUnavailable 不能同时为 0。
|
||||
|
||||
理由不难理解,当 MaxSurge 为 0 的时候,必须要删除 Pod,才能扩容 Pod;如果不删除 Pod 是不能新扩 Pod 的,因为新扩出来的话,总共的 Pod 数量就会超过期望数量。而两者同时为 0 的话,MaxSurge 保证不能新扩 Pod,而 MaxUnavailable 不能保证 ReplicaSet 中有 Pod 是 available 的,这样就会产生问题。所以说这两个值不能同时为 0。用户可以根据自己的实际场景来设置对应的、合适的值。
|
||||
|
||||
|
||||
|
||||
本节总结
|
||||
|
||||
本节课的主要内容就到此为止了,这里为大家简单总结一下。
|
||||
|
||||
|
||||
Deployment 是 Kubernetes 中常见的一种 Workload,支持部署管理多版本的 Pod;
|
||||
Deployment 管理多版本的方式,是针对每个版本的 template 创建一个 ReplicaSet,由 ReplicaSet 维护一定数量的 Pod 副本,而 Deployment 只需要关心不同版本的 ReplicaSet 里要指定多少数量的 Pod;
|
||||
因此,Deployment 发布部署的根本原理,就是 Deployment 调整不同版本 ReplicaSet 里的终态副本数,以此来达到多版本 Pod 的升级和回滚。
|
||||
|
||||
|
||||
|
||||
|
||||
|
0
专栏/CNCFX阿里巴巴云原生技术公开课/07应用编排与管理:Job&DaemonSet.md
Normal file
0
专栏/CNCFX阿里巴巴云原生技术公开课/07应用编排与管理:Job&DaemonSet.md
Normal file
286
专栏/CNCFX阿里巴巴云原生技术公开课/08应用配置管理.md
Normal file
286
专栏/CNCFX阿里巴巴云原生技术公开课/08应用配置管理.md
Normal file
@ -0,0 +1,286 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
08 应用配置管理
|
||||
本节课程要点
|
||||
|
||||
|
||||
ConfigMaps 和 Secret 资源的创建和使用;
|
||||
Pod 身份认证的实现和原理;
|
||||
容器资源、安全、前置校验等配置和使用。
|
||||
|
||||
|
||||
细分为以下八个方面:
|
||||
|
||||
|
||||
|
||||
需求来源
|
||||
|
||||
背景问题
|
||||
|
||||
首先一起来看一下需求来源。大家应该都有过这样的经验,就是用一个容器镜像来启动一个 container。要启动这个容器,其实有很多需要配套的问题待解决:
|
||||
|
||||
|
||||
第一,比如说一些可变的配置。因为我们不可能把一些可变的配置写到镜像里面,当这个配置需要变化的时候,可能需要我们重新编译一次镜像,这个肯定是不能接受的;
|
||||
第二就是一些敏感信息的存储和使用。比如说应用需要使用一些密码,或者用一些 token;
|
||||
第三就是我们容器要访问集群自身。比如我要访问 kube-apiserver,那么本身就有一个身份认证的问题;
|
||||
第四就是容器在节点上运行之后,它的资源需求;
|
||||
第五个就是容器在节点上,它们是共享内核的,那么它的一个安全管控怎么办?
|
||||
最后一点我们说一下容器启动之前的一个前置条件检验。比如说,一个容器启动之前,我可能要确认一下 DNS 服务是不是好用?又或者确认一下网络是不是联通的?那么这些其实就是一些前置的校验。
|
||||
|
||||
|
||||
Pod 的配置管理
|
||||
|
||||
在 Kubernetes 里面,它是怎么做这些配置管理的呢?如下图所示:
|
||||
|
||||
|
||||
|
||||
|
||||
可变配置就用 ConfigMap;
|
||||
敏感信息是用 Secret;
|
||||
身份认证是用 ServiceAccount 这几个独立的资源来实现的;
|
||||
资源配置是用 Resources;
|
||||
安全管控是用 SecurityContext;
|
||||
前置校验是用 InitContainers 这几个在 spec 里面加的字段,来实现的这些配置管理。
|
||||
|
||||
|
||||
ConfigMap
|
||||
|
||||
ConfigMap 介绍
|
||||
|
||||
下面我们来介绍第一个部分,就是 ConfigMap。我们先来介绍 ConfigMap 它是用来做什么的、以及它带来的一个好处。它其实主要是管理一些可变配置信息,比如说我们应用的一些配置文件,或者说它里面的一些环境变量,或者一些命令行参数。
|
||||
|
||||
它的好处在于它可以让一些可变配置和容器镜像进行解耦,这样也保证了容器的可移植性。看一下下图中右边的编排文件截图。
|
||||
|
||||
|
||||
|
||||
这是 ConfigMap 本身的一个定义,它包括两个部分:一个是 ConfigMap 元信息,我们关注 name 和 namespace 这两个信息。接下来这个 data 里面,可以看到它管理了两个配置文件。它的结构其实是这样的:从名字看ConfigMap中包含Map单词,Map 其实就是 key:value,key 是一个文件名,value 是这个文件的内容。
|
||||
|
||||
ConfigMap 创建
|
||||
|
||||
看过介绍之后,再具体看一下它是怎么创建的。我们推荐用 kubectl 这个命令来创建,它带的参数主要有两个:一个是指定 name,第二个是 DATA。其中 DATA 可以通过指定文件或者指定目录,以及直接指定键值对,下面可以看一下这个例子。
|
||||
|
||||
|
||||
|
||||
指定文件的话,文件名就是 Map 中的 key,文件内容就是 Map 中的 value。然后指定键值对就是指定数据键值对,即:key:value 形式,直接映射到 Map 的key:value。
|
||||
|
||||
ConfigMap 使用
|
||||
|
||||
创建完了之后,应该怎么使用呢?
|
||||
|
||||
|
||||
|
||||
如上图所示,主要是在 pod 里来使用 ConfigMap:
|
||||
|
||||
|
||||
第一种是环境变量。环境变量的话通过 valueFrom,然后 ConfigMapKeyRef 这个字段,下面的 name 是指定 ConfigMap 名,key 是 ConfigMap.data 里面的 key。这样的话,在 busybox 容器启动后容器中执行 env 将看到一个 SPECIAL*LEVEL*KEY 环境变量;
|
||||
第二个是命令行参数。命令行参数其实是第一行的环境变量直接拿到 cmd 这个字段里面来用;
|
||||
最后一个是通过 volume 挂载的方式直接挂到容器的某一个目录下面去。上面的例子是把 special-config 这个 ConfigMap 里面的内容挂到容器里面的 /etc/config 目录下,这个也是使用的一种方式。
|
||||
|
||||
|
||||
ConfigMap 注意要点
|
||||
|
||||
现在对 ConfigMap 的使用做一个总结,以及它的一些注意点,注意点一共列了以下五条:
|
||||
|
||||
|
||||
ConfigMap 文件的大小。虽然说 ConfigMap 文件没有大小限制,但是在 ETCD 里面,数据的写入是有大小限制的,现在是限制在 1MB 以内;
|
||||
第二个注意点是 pod 引入 ConfigMap 的时候,必须是相同的 Namespace 中的 ConfigMap,前面其实可以看到,ConfigMap.metadata 里面是有 namespace 字段的;
|
||||
第三个是 pod 引用的 ConfigMap。假如这个 ConfigMap 不存在,那么这个 pod 是无法创建成功的,其实这也表示在创建 pod 前,必须先把要引用的 ConfigMap 创建好;
|
||||
第四点就是使用 envFrom 的方式。把 ConfigMap 里面所有的信息导入成环境变量时,如果 ConfigMap 里有些 key 是无效的,比如 key 的名字里面带有数字,那么这个环境变量其实是不会注入容器的,它会被忽略。但是这个 pod 本身是可以创建的。这个和第三点是不一样的方式,是 ConfigMap 文件存在基础上,整体导入成环境变量的一种形式;
|
||||
最后一点是:什么样的 pod 才能使用 ConfigMap?这里只有通过 K8s api 创建的 pod 才能使用 ConfigMap,比如说通过用命令行 kubectl 来创建的 pod,肯定是可以使用 ConfigMap 的,但其他方式创建的 pod,比如说 kubelet 通过 manifest 创建的 static pod,它是不能使用 ConfigMap 的。
|
||||
|
||||
|
||||
Secret
|
||||
|
||||
Secret 介绍
|
||||
|
||||
现在我们讲一下 Secret,Secret 是一个主要用来存储密码 token 等一些敏感信息的资源对象。其中,敏感信息是采用 base-64 编码保存起来的,我们来看下图中 Secret 数据的定义。
|
||||
|
||||
|
||||
|
||||
元数据的话,里面主要是 name、namespace 两个字段;接下来是 type,它是非常重要的一个字段,是指 Secret 的一个类型。Secret 类型种类比较多,下面列了常用的四种类型:
|
||||
|
||||
|
||||
第一种是 Opaque,它是普通的 Secret 文件;
|
||||
第二种是 service-account-token,是用于 service-account 身份认证用的 Secret;
|
||||
第三种是 dockerconfigjson,这是拉取私有仓库镜像的用的一种 Secret;
|
||||
第四种是 bootstrap.token,是用于节点接入集群校验用的 Secret。
|
||||
|
||||
|
||||
再接下来是 data,是存储的 Secret 的数据,它也是 key-value 的形式存储的。
|
||||
|
||||
Secret 创建
|
||||
|
||||
接下来我们看一下 Secret 的创建。
|
||||
|
||||
|
||||
|
||||
如上图所示,有两种创建方式:
|
||||
|
||||
|
||||
系统创建:比如 K8s 为每一个 namespace 的默认用户(default ServiceAccount)创建 Secret;
|
||||
用户手动创建:手动创建命令,推荐 kubectl 这个命令行工具,它相对 ConfigMap 会多一个 type 参数。其中 data 也是一样,它也是可以指定文件和键值对的。type 的话,要是你不指定的话,默认是 Opaque 类型。
|
||||
|
||||
|
||||
上图中两个例子。第一个是通过指定文件,创建了一个拉取私有仓库镜像的 Secret,指定的文件是 /root/.docker/config.json。type 的话指定的是 dockerconfigjson,另外一个我们指定键值对,我们 type 没有指定,默认是 Opaque。键值对是 key:value 的形式,其中对 value 内容进行 base64 加密。创建 Secret 就是这么一个情况。
|
||||
|
||||
Secret 使用
|
||||
|
||||
创建完 Secret 之后,再来看一下如何使用它。它主要是被 pod 来使用,一般是通过 volume 形式挂载到容器里指定的目录,然后容器里的业务进程再到目录下读取 Secret 来进行使用。另外在需要访问私有镜像仓库时,也是通过引用 Secret 来实现。
|
||||
|
||||
|
||||
|
||||
我们先来看一下挂载到用户指定目录的方式:
|
||||
|
||||
|
||||
第一种方式:如上图左侧所示,用户直接指定,把 mysecret 挂载到容器 /etc/foo 目录下面;
|
||||
第二种方式:如上图右侧所示,系统自动生成,把 serviceaccount-secret 自动挂载到容器 /var/run/secrets/kubernetes.io/serviceaccount 目录下,它会生成两个文件,一个是 ca.crt,一个是 token。这是两个保存了认证信息的证书文件。
|
||||
|
||||
|
||||
使用私有镜像库
|
||||
|
||||
下面看一下用 Secret 来使用私有镜像仓库。首先,私有镜像仓库的信息是存储在 Secret 里面的(具体参照上述的Secret创建章节),然后拉取私有仓库镜像,那么通过下图中两种方法的配置就可以:
|
||||
|
||||
|
||||
第一种方式:如下图左侧所示,直接在 pod 里面,通过 imagePullSecrets 字段来配置;
|
||||
第二种方式是自动注入。用户提前在 pod 会使用的 serviceaccount 里配置 imagePullSecrets,Pod创建时系统自动注入这个 imagePullSecrets。
|
||||
|
||||
|
||||
|
||||
|
||||
Secret 使用注意要点
|
||||
|
||||
最后来看一下 Secret 使用的一些注意点,下面列了三点:
|
||||
|
||||
|
||||
第一个是 Secret 的文件大小限制。这个跟 ConfigMap 一样,也是 1MB;
|
||||
第二个是 Secret 采用了 base-64 编码,但是它跟明文也没有太大区别。所以说,如果有一些机密信息要用 Secret 来存储的话,还是要很慎重考虑。也就是说谁会来访问你这个集群,谁会来用你这个 Secret,还是要慎重考虑,因为它如果能够访问这个集群,就能拿到这个 Secret。
|
||||
|
||||
|
||||
如果是对 Secret 敏感信息要求很高,对加密这块有很强的需求,推荐可以使用 Kubernetes 和开源的 vault做一个解决方案,来解决敏感信息的加密和权限管理。
|
||||
|
||||
|
||||
第三个就是 Secret 读取的最佳实践,建议不要用 list/watch,如果用 list/watch 操作的话,会把 namespace 下的所有 Secret 全部拉取下来,这样其实暴露了更多的信息。推荐使用 GET 的方法,这样只获取你自己需要的那个 Secret。
|
||||
|
||||
|
||||
ServiceAccount
|
||||
|
||||
ServiceAccount 介绍
|
||||
|
||||
接下来,我们讲一下 ServiceAccount。ServiceAccount 首先是用于解决 pod 在集群里面的身份认证问题,身份认证信息是存在于 Secret 里面。
|
||||
|
||||
|
||||
|
||||
先看一下上面的左侧截图,可以看到最下面的红框里,有一个 Secret 字段,它指定 ServiceAccount 用哪一个 Secret,这个是 K8s 自动为 ServiceAccount 加上的。然后再来看一下上图中的右侧截图,它对应的 Secret 的 data 里有两块数据,一个是 ca.crt,一个是 token。ca.crt 用于对服务端的校验,token 用于 Pod 的身份认证,它们都是用 base64 编码过的。然后可以看到 metadata 即元信息里,其实是有关联 ServiceAccount 信息的(这个 secret 被哪个 ServiceAccount 使用)。最后我们注意一下 type,这个就是 service-account-token 这种类型。
|
||||
|
||||
举例:Pod 里的应用访问它所属的 K8s 集群
|
||||
|
||||
介绍完 ServiceAccount 以及它对应的 secret 后,我们来看一下,pod 是怎么利用 ServiceAccount 或者说它是怎么利用 secret 来访问所属 K8s 集群的。
|
||||
|
||||
其实 pod 创建的时候,首先它会把这个 secret 挂载到容器固定的目录下,这是 K8s 功能上实现的。它要把这个 ca.crt 和 token 这两个文件挂载到固定目录下面。
|
||||
|
||||
pod 要访问集群的时候,它是怎么来利用这个文件的呢?我们看一下下面的代码截图:
|
||||
|
||||
|
||||
|
||||
我们在 Go 里面实现 Pod 访问 K8s 集群时,一般直接会调一个 InClusterConfig 方法,来生成这个访问服务 Client 的一些信息。然后可以看一下,最后这个 Config 里面有两部分信息:
|
||||
|
||||
|
||||
一个是 tlsClientConfig,这个主要是用于 ca.crt 校验服务端;
|
||||
第二个是 Bearer Token,这个就是 pod 的身份认证。在服务端,会利用 token 对 pod 进行一个身份认证。
|
||||
|
||||
|
||||
再次回到上图左侧。认证完之后 pod 的身份信息会有两部分:一个是 Group,一个是 User。身份认证是就是认证这两部分信息。接着可以使用 RBAC 功能,对 pod 进行一个授权管理。
|
||||
|
||||
假如 RBAC 没有配置的话,默认的 pod 具有资源 GET 权限,就是可以从所属的 K8s 集群里 get 数据。如果是需要更多的权限,那么就需要 自行配置 RBAC 。RBAC 的相关知识,我们在后面的课程里面会详细介绍,大家可以关注一下。
|
||||
|
||||
Resource
|
||||
|
||||
容器资源配合管理
|
||||
|
||||
下面介绍一下 Resource,即:容器的一个资源配置管理。
|
||||
|
||||
目前内部支持类型有三种:CPU、内存,以及临时存储。当用户觉得这三种不够,有自己的一些资源,比如说 GPU,或者其他资源,也可以自己来定义,但配置时,指定的数量必须为整数。目前资源配置主要分成 request 和 limit 两种类型,一个是需要的数量,一个是资源的界限。CPU、内存以及临时存储都是在 container 下的 Resource 字段里进行一个声明。
|
||||
|
||||
|
||||
|
||||
举个例子,wordpress 容器的资源需求,一个是 request ,一个是 limits,它分别对需要的资源和资源临界进行一个声明。
|
||||
|
||||
Pod 服务质量 (QoS) 配置
|
||||
|
||||
根据 CPU 对容器内存资源的需求,我们对 pod 的服务质量进行一个分类,分别是 Guaranteed、Burstable 和 BestEffort。
|
||||
|
||||
|
||||
Guaranteed :pod 里面每个容器都必须有内存和 CPU 的 request 以及 limit 的一个声明,且 request 和 limit 必须是一样的,这就是 Guaranteed;
|
||||
Burstable:Burstable 至少有一个容器存在内存和 CPU 的一个 request;
|
||||
BestEffort:只要不是 Guaranteed 和 Burstable,那就是 BestEffort。
|
||||
|
||||
|
||||
那么这个服务质量是什么样的呢?资源配置好后,当这个节点上 pod 容器运行,比如说节点上 memory 配额资源不足,kubelet会把一些低优先级的,或者说服务质量要求不高的(如:BestEffort、Burstable)pod 驱逐掉。它们是按照先去除 BestEffort,再去除 Burstable 的一个顺序来驱逐 pod 的。
|
||||
|
||||
SecurityContext
|
||||
|
||||
SecurityContext 介绍
|
||||
|
||||
SecurityContext 主要是用于限制容器的一个行为,它能保证系统和其他容器的安全。这一块的能力不是 Kubernetes 或者容器 runtime 本身的能力,而是 Kubernetes 和 runtime 通过用户的配置,最后下传到内核里,再通过内核的机制让 SecurityContext 来生效。所以这里讲的内容,会比较简单或者说比较抽象一点。
|
||||
|
||||
SecurityContext 主要分为三个级别:
|
||||
|
||||
|
||||
第一个是容器级别,仅对容器生效;
|
||||
第二个是 pod 级别,对 pod 里所有容器生效;
|
||||
第三个是集群级别,就是 PSP,对集群内所有 pod 生效。
|
||||
|
||||
|
||||
权限和访问控制设置项,现在一共列有七项(这个数量后续可能会变化):
|
||||
|
||||
|
||||
第一个就是通过用户 ID 和组 ID 来控制文件访问权限;
|
||||
第二个是 SELinux,它是通过策略配置来控制用户或者进程对文件的访问控制;
|
||||
第三个是特权容器;
|
||||
第四个是 Capabilities,它也是给特定进程来配置一个 privileged 能力;
|
||||
第五个是 AppArmor,它也是通过一些配置文件来控制可执行文件的一个访问控制权限,比如说一些端口的读写;
|
||||
第六个是一个对系统调用的控制;
|
||||
第七个是对子进程能否获取比父亲更多的权限的一个限制。
|
||||
|
||||
|
||||
最后其实都是落到内核来控制它的一些权限。
|
||||
|
||||
|
||||
|
||||
上图是对 pod 级别和容器级别配置 SecurityContext 的一个例子,如果大家对这些内容有更多的需求,可以根据这些信息去搜索更深入的资料来学习。
|
||||
|
||||
InitContainer
|
||||
|
||||
InitContainer 介绍
|
||||
|
||||
接下来看一下 InitContainer,首先介绍 InitContainer 和普通 container 的区别,有以下三点内容:
|
||||
|
||||
|
||||
InitContainer 首先会比普通 container 先启动,并且直到所有的 InitContainer 执行成功后,普通 container 才会被启动;
|
||||
InitContainer 之间是按定义的次序去启动执行的,执行成功一个之后再执行第二个,而普通的 container 是并发启动的;
|
||||
InitContainer 执行成功后就结束退出,而普通容器可能会一直在执行。它可能是一个 longtime 的,或者说失败了会重启,这个也是 InitContainer 和普通 container 不同的地方。
|
||||
|
||||
|
||||
根据上面三点内容,我们看一下 InitContainer 的一个用途。它其实主要为普通 container 服务,比如说它可以为普通 container 启动之前做一个初始化,或者为它准备一些配置文件, 配置文件可能是一些变化的东西。再比如做一些前置条件的校验,如网络是否联通。
|
||||
|
||||
|
||||
|
||||
上面的截图是 flannel 组件的 InitContainer 的一个配置,它的 InitContainer 主要是为 kube-flannel 这个普通容器启动之前准备一些网络配置文件。
|
||||
|
||||
结束语
|
||||
|
||||
|
||||
ConfigMap 和 Secret: 首先介绍了 ConfigMap 和 Secret 的创建方法和使用场景,然后对 ConfigMap 和 Secret 的常见使用注意点进行了分类和整理。最后介绍了私有仓库镜像的使用和配置;
|
||||
Pod 身份认证: 首先介绍了 ServiceAccount 和 Secret 的关联关系,然后从源码角度对 Pod 身份认证流程和实现细节进行剖析,同时引出了 Pod 的权限管理(即 RBAC 的配置管理);
|
||||
容器资源和安全: 首先介绍了容器常见资源类型 (CPU/Memory) 的配置,然后对 Pod 服务质量分类进行详细的介绍。同时对 SecurityContext 有效层级和权限配置项进行简要说明;
|
||||
InitContainer: 首先介绍了 InitContainer 和普通 container 的区别以及 InitContainer 的用途。然后基于实际用例对 InitContainer 的用途进行了说明。
|
||||
|
||||
|
||||
好的,我们今天的内容讲到这里,谢谢大家。
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user