【devops系列】一文了解K8s
k8s 基础入门
前言
在我第一次接触Kubernetes的时候,被它天生高可用、负载均衡、弹性计算、自动扩容缩容和全自动容灾机制的设计理念吸引,于是自己便踏入了k8s这条不归路,在调研学习的过程中,开始不断填坑、挖坑再填坑,周而复始。
之前公司还在使用裸Docker部署一些无状态的应用,随着越来越多的Docker实例,发生故障时,人工填坑的方式,让我们实在头疼,有时候大半夜正在做着美梦的时候,被一个电话铃喊起来处理着由于宿主机宕机或者网络不可达引起的各类问题。这种惨绝人寰的处理方式更是让我们头疼难忍。虽然后来引用了Swarm和Compose,但随着业务的不断增多,已经越来越来满足不了我们的应用场景,然后就想着把所有Docker应用迁移到Kubernetes中去管理。
后来我们决定使用K8s管理容器,但事情并没有我们想象中的那么简单,k8s的设计太多复杂,概念实在是太多,搭建过程更是让人吐血,当时可参考的文章又是少之又少。在经过一个多月的研究与不断回血之后,终于在k8s集群中部署了我们的第一个应用。虽然整个过程中踩了无数坑,遇到了无数个难题,但从始至终没有放弃,最终实现一整套的流程设计,从开发提交代码至Git,到最后成功部署到k8s集群中,彻底释放了双手。遇到集群中的宿主机宕机的时候,不用再去人工去处理,这一切k8s都帮你默默的处理了…
Kubernetes带来的变革
对于开发人员
由于公司业务多,开发环境、测试环境、预生产环境和生产环境都是隔离的,而且除了生产环境,为了节省成本,其他环境是没有日志收集的,在没有用k8s的时候,查看线下测试的日志,需要开发或者测试人员,找到对应的机器,在找到对应的容器,然后才能查看日志,在用了k8s之后,开发和测试可以直接在k8s的dashboard到对应的namespace,即可定位到业务的容器,然后可以直接通过控制台查看到对应的日志,大大降低了操作时间。
把应用部署到k8s之后,代码的发布、回滚,以及蓝绿发布、金丝雀发布等都变得特别简单,不仅加快了业务代码迭代的速度,而且全程无需人工干预。目前我们使用jenkins、gitrunner进行发版或者回滚等,从开发环境到测试环境,到最后的生产环境,完全遵守一次构建,多集群、多环境部署,通过不同的启动参数、不同的环境变量、不同的配置文件实现区分不同的环境。目前已经实现Python、Java、PHP、NodeJS、Go、.NET Core等多种语言的一键式发版、一键式回滚,大大提高了开发人员的开发效率。
在使用服务网格后,开发人员在开发应用的过程中,不用再关心代码的网络部分,这些功能都被服务网格实现,让开发人员可以只关心代码逻辑部分,即可实现网络部分的功能,比如:断流、分流、路由、负载均衡、限速和触发故障等功能。
测试过程中,可能同时多套环境,当然也会需要再创建一套测试环境,之前测试环境的创建,需要找运维或者自行手工搭建。在迁移至k8s集群后,只需要在jenkins上点点鼠标即可在k8s集群上创建一套新的测试环境。
对于运维人员
如果你是一名运维人员,可能经常因为一些重复、繁琐的工作感觉厌倦。比如:这个需要一套新的测试环境,那个需要一套新的测试环境,之前可能需要装系统、装依赖环境、开通权限等等。而如今,可以直接用镜像直接部署一套新的测试环境,甚至全程无需自己干预,开发人员通过jenkins或者自动化运维平台即可一键式创建,大大降低了运维成本。
一开始,公司业务故障,可能是因为基础环境不一致、依赖不一致、端口冲突等等问题,现在实现镜像部署,所有的依赖、基础都是一样的,大大减少了因为这类基础问题引发的故障。也有可能公司业务是由于服务器宕机、网络等问题,造成服务不可用,此类情况均需要运维人员及时去修复,而如今,可能在你收到告警信息的时候,k8s已经帮你恢复了。
在没有使用k8s时,业务应用的扩容和缩容,都需要人工去处理,从采购服务器、上架、到部署依赖环境,不仅需要大量的人力物力,而且非常容易在中间过程出现问题,又要花费大量的时间去查找问题。成功上架后,还需要在前端反代端添加或该服务器,而如今,可以利用k8s的弹性计算,一键式进行扩容和缩容,不仅大大提高了运维效率,而且还节省了不少的服务器资源,提高了资源利用率。
对于反代配置方面,比如可能你并不会,或者对nginx的配置规则并不熟悉,一些高级的功能你也不会实现,而如今,利用k8s的ingress即可简单的实现那些负责的逻辑。并且也不会在遇到nginx少加一个斜杠和多加一个斜杠的问题。
对于负载均衡方面,之前负载均衡可能是Nginx、LVS、HAProxy、F5等,云上可能是云服务商提供的不在均衡机制。每次添加删除节点时,都需要手动去配置前端负载均衡,手动去匹配后端节点,而如今,使用k8s内部的service可以动态发现实现自动管理节点,并且支持自动扩容缩容。之前遇到高峰流量时,经常服务器性能不够,需要临时加服务器面对高峰流量,而如今对于高性能k8s集群,无需管理,自动扩容。
对于高可用方面,k8s天生的高可用功能,彻底释放了双手,无需再去创建各类高可用工具、检测检查脚本。k8s支持进程级别的健康检查,如发现接口超时或者返回值不正确,会自动处理该问题。
对于中间件搭建方面,根据定义好的deploy文件,可以实现秒级搭建各类中间件高可用集群,如Redis、RabbitMQ、Zookeeper等,并且大大减少了出错的概率。
对于应用端口方面,传统行业中,一个服务器可能跑了很多进程,每个进程都有一个端口,需要人为的去配置端口,并且还需要考虑端口冲突的问题,如果有防火墙的话,还需要配置防火墙,在k8s中,端口统一管理,统一配置,每个应用的端口都可设置成一样的,之后通过service进行负载均衡。
无论是对于开发人员、测试人员还是运维人员,k8s的诞生,不仅减少了工作的复杂性,还减少了各种成本。上述带来的变革只是其中比较小的一部分,更多优点只有用了才能体会到。
Kubernetes带来的挑战
首先是对于k8s的学习本身就是很难的,概念太多,无从入手,可能学习了一个月也无法入门,甚至连集群也搭建不出来,使人望而却步。并且k8s对运维的技术能力要求比较高,已经不仅仅局限于传统运维,有时候你可能要修改业务代码等。并且需要掌握的知识也需要很多,你可能需要掌握公司所有使用到的代码,比如代码是如何进行编译的、如何正确发布、如何修改代码配置文件等,这对于运维人员,也是一种挑战。Kubernetes之所以被叫做k8s,业界有两种说法,通俗的说法是k和s之间有8个字母,另一种比较说法是k8s集群至少需要搭建8遍才能搭建成功。当然,在实际使用时,可能不止8遍。k8s的诞生,把运维从传统转变到了DevOps方向,需要面临的问题会更多,需要面临的新技术也有很多,但是当你掌握到了k8s的核心使用,就会受益终身。
对于开发人员来说,对开发方式也有一些变化。从k8s的诞生到如火如荼,慢慢的k8s变成了一种标准,开发再进行代码开发时需要遵循Docker和k8s规范,严格遵守一次构建,多次部署原则,所有的配置都通过参数、变量或者k8s配置管理注入。并且对应用,要求是无状态的,因为Docker每次重启都会以最干净的基础启动。无论公司有没有进行业务容器化,遵循Docker和k8s规范都将成为未来的趋势,2019年7月,某公司因为代码不能和k8s兼容,导致一年亏损38亿美元…
1.1 部署模式发展
1.2 物理单机(~2000)
早期在物理服务器上运行应用程序也叫做传统的部署。
- 在商用服务计算领域几乎都是以单机为基础计算单元对计算资源 进行管理和协调控制的
- 部署新应用往往需要购买一台物理机器或者一组机器,并在机器上进行构建,部署和运行,而且一台机器往往只能运行单个应用,成本高,利用率低
1.2.1 主要代表
IBM、Sun公司
早期在物理服务器上运行应用程序也叫做传统的部署。
传统部署时代: 在物理服务器上运行应用程序。无法为物理服务器中的应用程序定义资源边界,这会导致资源分配的问题。
例如,如果在物理服务器上运行多个应用程序,则可能存在一个应用程序占用大部分资源的情况,因此导致其他应用程序获取不到资源。所以往往解决方案是在不同的物理服务器上运行每个应用程序。但是由于资源未得到充分利用,没有扩展,组织和维护这么多物理服务器的成本很高。
1.3 虚拟化:初期(2001~2009)
1.3.1 VMware
VMware:2001年,Xen:2003年,KVM:2007年
- VMware发布了针对服务器市场的虚拟化技术方案:提升计算资源的利用率和降低使用成本
- Vmware、Xen和KVM三足鼎立,促进了VM概念的普及,拉开了虚拟化云计算时代的大幕,基础计算单元变为VM,服务端应用的构建、部署和运行逐步迁移到虚拟机VM上了。
- 充分地物理单机将划分为多个虚拟机,提高计算机资源的利用率和降低成本
1.3.2 laaS
AWS 2006年,GCE(Google Compute Engine)2008年
- 基于虚拟机技术的Amazon Web Services(AWS)开启了Infrastructure-as-a-Service(IaaS基础设施即服务)的市场
- 实现了自助的、按需租用以VM为基本计算单元的计算资源。
- 应用的部署运行依然以vm为单元并通过laaS厂商提供的控制台实现高效的计算资源管理。
这个时期也称为虚拟化部署时代:作为解决方案,引入了虚拟化。它允许在单个物理服务器的CPU上运行多个虚拟机(VM)。虚拟化允许应用程序在VM之间隔离,并提供一定程度的安全性,因为一个应用程序无法自由访问另一个应用程序的信息。
虚拟化可以更好地利用物理服务器中的资源,并且可以实现更好的可扩展性,因为可以轻松添加或更新应用程序,降低硬件成本等等。每个VM都是在虚拟化硬件之上运行所有组件(包括其自己的操作系统)的完整计算机。
1.4 虚拟化:成熟期(2010~至今)
1.4.1 OpenStack
OpenStack 2010 诞生,推动开源laaS平台的快速发展,推动商家将自有数据中心改造为虚拟化平台,部署数据敏感、业务敏感的核心应用
- 部署形式:公有云、私有云、混合云
- 服务模式:laaS、PaaS(Heroku 2009)、SaaS等
1.4.2 虚拟化四巨头
AWS、Azure、Aliyun、GCE(Google Compute Engine):2015-2017
基于虚拟化技术的公有云爆发式增长,形成公有云laaS四巨头
2017年底,全球企业的一半以上的计算资源放在了公有云上,半数企业在内部完成了私有云部署
1.5 容器化:(2013-至今)
1.5.1 Docker
2013 年诞生
- 以Docker为代表的内核容器技术不是新技术,而是将已有技术(LXC、cgroups、UnionFS)进行了更好的整合和包装,并形成了一种标准镜像格式。
- 与VM相比,容器具有开发交付流程操作对象同步、执行更为高效、资源占用更为集约等优势。
- 计算基本单元由虚拟机变为了容器,越来越多应用的构建、部署与运行选择在容器中进行。
Docker能够解决什么问题?如果我们在一台服务器上只跑很多个服务,比如说有一个服务内存泄漏把整个服务器内存占满了,其他服务也跟着倒霉,所以需要把每个服务隔离起来,让它们只使用自己那部分有限的CPU,内存和磁盘以及依赖的软件包。Docker相比虚拟机来说少了操作系统这一层,所以占用的资源少,启动速度快,能够提供一定程度的隔离。而且运维简单,可以克隆多个个环境相同的容器。
这个时期也称为容器部署时代:容器类似于VM,但它们具有宽松的隔离属性,可在应用程序之间共享操作系统(OS)。因此,容器被认为是轻量的的。与VM类似,容器具有自己的文件系统,CPU,内存,进程空间等。
1.6 云原生:初期(2015-至今)
1.6.1 云原生模式
- 随着容器技术的出现以及应用所面临的外部环境的变化,云原生逐渐成为一种应用云化开发、部署和运行的主流方式。基础前提:应用的容器化和微服务化。容器,作为应用部署、运行和管理的基本单元;
- 核心:借助容器管理自动化平台进行动态编排和资源优化利用。
1.6.2 K8S
CNCF,Kubernetes:2015年
就在Docker容器技术被炒得热火朝天之时,大家发现,如果想要将Docker应用于具体的业务实现,是存在困难的——编排、管理和调度等各个方面,都不容易,于是,人们迫切需要一套管理系统,对Docker及容器进行更高级更灵活的管理,Kuberentes可以说是乘着Docker和微服务的东风,一经推出便迅速蹿红,它的很多设计思想都契合了微服务和云原生应用的设计法则,Kuberentes从众多强大对手中脱颖而出。
CNCF组织的成立为应用上云安全地采用云原生模式提供了更稳、更快、更安全的解决方案,其核心是Kubernetes。从众多强大对手中脱颖而出,Kubernetes为云原生模式下应用的部署、运行和管理提供了可移植的、云厂商无关的事实标准。
1.6.3 趋势
- 应用部署运行模式:单机->虚拟机->容器->云原生
- 应用部署运行:更敏捷、更自动化、更有效率、更低成本
- 开发者:更聚焦于应用本身
1.7 发展变迁
应用部署运行模式的演变图:
2. k8s 概述
2.1 k8s 是什么
K8S是Kubernetes
的全称,官方称其是
Kubernetes is an open source system for managing containerized applications across multiple hosts. It provides basic mechanisms for deployment, maintenance, and scaling of applications. 用于自动部署、扩展和管理“容器化(containerized)应用程序”的开源系统。
2.2 K8s的由来
源自谷歌的Borg,Borg是谷歌公司的内部容器管理系统。
Borg系统运行几十万个以上的任务,来自几千个不同的应用,跨多个集群,每个集群(cell)有上万个机器。它通过管理控制、高效的任务包装、超售、和进程级别性能隔离实现了高利用率,它支持高可用性应用程序与运行时功能,最大限度地减少故障恢复时间,减少相关故障概率的调度策略,该项目的目的是实现资源管理的自动化以及跨多个数据中心的资源利用率最大化,Kubernetes项目的目的就是将Borg最精华的部分提取出来,使现在的开发者能够更简单、直接地应用。它以Borg为灵感,但又没那么复杂和功能全面,更强调了模块性和可理解性。
2.2.1 K8s发展历程
Kubernetes 是 Google 开源的容器编排系统,2014 年对外宣布,2015 年发布 1.0 版本,同年 Google 与 Linux 基金会一起成立云原生计算基金会(CNCF-Cloud Native Computing Foundation),并把 Kubernetes 作为种子产品捐赠给了 CNCF,Google 一直在带领着 Kubernetes 的开发,我们也可以看到 CNCF 的 Kubernetes 项目代码贡献量,Google 所占比重是最高的。
2.2.2 发展时间线
Google 在容器领域拥有超过 15 年的经验。
- 2003 年,Google 内部几个工程师做了一个集群自动化管理的工具,叫做 Borg;
- 2012 年,Google Borg 升级成 Omega,实现容器的管理;
- 2013 年,随着业界 Docker 发布,整个行业开始往容器方向迁移;
- 2014 年,Borg/Omega 开源为 Kubernetes 项目;
- 到如今,Kubernetes 已经成为整个容器编排的主流技术。
2.3 为什么使用k8s
Kubernetes (Kube 或 K8s)越来越流行,他是市场上最好的容器编排工具之一。
2.3.1 什么是容器
- 容器就是一个包,其中包含了应用及其所有依赖。
- 容器中的应用与主机系统是隔离的,不关注环境。
- 不像虚拟机,容器不需要启动操作系统的完整周期,这就是为啥容器启动和停止都非常快,并且可以更高效使用磁盘、内存、处理器的原因。
- 你不必记着你的应用是用什么语言和框架开发的,因为所需的一切都打包在了容器中,例如运行时环境、所需的库等等,可以安全的迁移,可以在任何环境中部署。
左边,应用是直接部署在服务器或者虚拟机里面的,右边,应用是打包在独立的容器中的,可以快速启动、智能扩展、在任何环境中平滑运行。
2.3.2 什么是 Kubernetes
Kubernetes 是一个开源项目,用于统一管理容器化的应用集群。
- Kubernetes 负责在大规模服务器环境中管理容器组(pod)的扩展、复制、健康,并解决 pod 的启动、负载均衡等问题。
- Kubernetes 最初是 Google 发布的,现在已经被多家大公司支持,例如 Microsoft, RedHat, IBM, Docker。
2.3.3 K8s 的著名优势特色
2.3.3.1 一个平台搞定所有
使用 Kubernetes,部署任何应用都是小菜一碟,只要应用可以打包进容器,Kubernetes 就一定能启动它。
不管什么语言什么框架写的应用(Java, Python, Node.js),Kubernetes 都可以在任何环境中安全的启动它,物理服务器、虚拟机、云环境。
2.3.3.2 云环境无缝迁移
如果你有换云环境的需求,例如从 GCP 到 AWS,使用 Kubernetes 的话,你就不用有任何担心。
Kubernetes 完全兼容各种云服务提供商,例如 Google Cloud、Amazon、Microsoft Azure,还可以工作在 CloudStack, OpenStack, OVirt, Photon, VSphere。
2.3.3.3 高效的利用资源
看下图,左边,是4个虚拟机,黄色和蓝色部分是运行的应用,白色部分是未使用的内存和处理器资源。
Kubernetes 如果发现有节点工作不饱和,便会重新分配 pod,帮助我们节省开销,高效的利用内存、处理器等资源。
如果一个节点宕机了,Kubernetes 会自动重新创建之前运行在此节点上的 pod,在其他节点上运行。
2.3.3.4 开箱即用的自动缩放能力
网络、负载均衡、复制等特性,对于 Kubernetes 都是开箱即用的。
- pod 是无状态运行的,任何时候有 pod 宕了,立马会有其他 pod 接替它的工作,用户完全感觉不到。
- 如果用户量突然暴增,现有的 pod 规模不足了,那么会自动创建出一批新的 pod,以适应当前的需求。
- 反之亦然,当负载降下来的时候,Kubernetes 也会自动缩减 pod 的数量。
2.3.3.5 使 CI/CD 更加简单
你不必精通于 Chef 和 Ansible 这类工具,只需要对 CI 服务写个简单的脚本然后运行它,就会使用你的代码创建一个新的 pod,并部署到 Kubernetes 集群里面。
应用打包在容器中使其可以安全的运行在任何地方,例如你的 PC、一个云服务器,使得测试极其简单。
2.3.3.6 可靠性
Kubernetes 如此流行的一个重要原因是:应用会一直顺利运行,不会被 pod 或 节点的故障所中断。
如果出现故障,Kubernetes 会创建必要数量的应用镜像,并分配到健康的 pod 或节点中,直到系统恢复,而且用户不会感到任何不适。
2.4 核心概念
2.4.1 节点
从上面的图可以看出来,k8s 集群的节点有两个角色,分别为 Master 节点和 Node 节点,整个 K8s 集群Master 和 Node 节点关系如下图所示:
2.4.1.1 Master 节点
Master 节点也称为控制节点,每个 k8s 集群都有一个 Master 节点负责整个集群的管理控制,我们上面介绍的 k8s 三大能力都是经过 Master 节点发起的,Master 节点包含了以下几个组件:
- API Server:提供了 HTTP Rest 接口的服务进程,所有资源对象的增、删、改、查等操作的唯一入口;
- Controller Manager:k8s 集群所有资源对象的自动化控制中心;
- Scheduler:k8s 集群所有资源对象自动化调度控制中心;
- ETCD:k8s 集群注册服务发现中心,可以保存 k8s 集群中所有资源对象的数据。
2.4.1.2 Node
Node 节点的作用是承接 Master 分配的工作负载,它主要有以下几个关键组件:
- kubelet:负责 Pod 对应容器的创建、启停等操作,与 Master 节点紧密协作;
- kube-porxy:实现 k8s 集群通信与负载均衡的组件。
从图上可看出,在 Node 节点上面,还需要一个容器运行环境,如果使用 Docker 技术栈,则还需要在 Node 节点上面安装 Docker Engine,专门负责该节点容器管理工作。
2.4.2 Pod
Pod 是 k8s 最重要而且是最基本的一个资源对象,它的结构如下:
从以上 Pod 的结构图可以看出,它其实是容器的一个上层包装结构,这也就是为什么 K8s 可以支持多种容器类型的原因,基于这方面, k8s 的定位就是一个编排与调度工具,而容器只是它调度的一个资源对象而已。
Pod 可包含多个容器在里面,每个 Pod 至少会有一个 Pause 容器,其它用户定义的容器都共享该 Pause 容器,Pause 容器的主要作用是用于定义 Pod 的 ip 和 volume。
Pod 在 k8s 集群中的位置如下图所示:
2.4.3 Label
Label 在 k8s 中是一个非常核心的概念,我们可以将 Label 指定到对应的资源对象中
例如 Node、Pod、Replica Set、Service 等,一个资源可以绑定任意个 Label,k8s 通过 Label 可实现多维度的资源分组管理,后续可通过 Label Selector 查询和筛选拥有某些 Label 的资源对象,例如创建一个 Pod,给定一个 Label,workerid=123,后续可通过 workerid=123 删除拥有该标签的 Pod 资源。
2.4.4 Replica Set
Replica Set 目的是为了定义一个期望的场景,比如定义某种 Pod 的副本数量在任意时刻都处于 Peplica Set 期望的值
假设 Replica Set 定义 Pod 的副本数目为:replicas=2,当该 Replica Set 提交给 Master 后,Master 会定期巡检该 Pod 在集群中的数目,如果发现该 Pod 挂掉了一个,Master 就会尝试依据 Replica Set 设置的 Pod 模版创建 Pod,以维持 Pod 的数量与 Replica Set 预期的 Pod 数量相同。
通过 Replica Set,k8s 集群实现了用户应用的高可用性,而且大大减少了运维工作量。因此生产环境一般用 Deployment 或者 Replica Set 去控制 Pod 的生命周期和期望值,而不是直接单独创建 Pod。
类似 Replica Set 的还有 Deployment,它的内部实现也是通过 Replica Set 实现的,可以说 Deployment 是 Replica Set 的升级版,它们之间的 yaml 配置文件格式大部分都相同。
2.4.5 Service
Service 是 k8s 能够实现微服务集群的一个非常重要的概念
顾名思义,k8s 的 Service 就是我们平时所提及的微服务架构中的“微服务”,本文上面提及的 Pod、Replica Set 等都是为 Service 服务的资源, 如下图表示 Service、Pod、Replica Set 的关系:
从上图可看出,Service 定义了一个服务访问的入口,客户端通过这个入口即可访问服务背后的应用集群实例,而 Service 则是通过 Label Selector 实现关联与对接的,Replica Set 保证服务集群资源始终处于期望值。
以上只是一个微服务,通常来说一个应用项目会由多个不同业务能力而又彼此独立的微服务组成,多个微服务间组成了一个强大而又高可用的应用服务集群。
2.4.6 Namespace
Namespace 顾名思义是命名空间的意思,在 k8s 中主要用于实现资源隔离的目的
用户可根据不同项目创建不同的 Namespace,通过 k8s 将资源分配到不同 Namespace 中,即可实现不同项目的资源隔离:
3. k8s 组件介绍
3.1 整体架构
下图清晰表明了 Kubernetes 的架构设计以及组件之间的通信协议。
下面是更抽象的一个视图
3.1.1 Master 架构
3.1.2 Node 架构
3.2 k8s部署组件介绍
我们把一个有效的 Kubernetes 部署称为集群,您可以将 Kubernetes 集群可视化为两个部分:
控制平面与计算设备(或称为节点),每个节点都是其自己的 Linux环境,并且可以是物理机或虚拟机,每个节点都运行由若干容器组成的容器集。
3.2.1 K8s 集群架构图
以下 K8s 架构图显示了 Kubernetes 集群的各部分之间的联系:
3.2.2 k8s控制组件
3.2.2.1 控制平面
K8s 集群的神经中枢
让我们从 Kubernetes 集群的神经中枢(即控制平面)开始说起。在这里,我们可以找到用于控制集群的 Kubernetes 组件以及一些有关集群状态和配置的数据。这些核心 Kubernetes 组件负责处理重要的工作,以确保容器以足够的数量和所需的资源运行。
控制平面会一直与您的计算机保持联系。集群已被配置为以特定的方式运行,而控制平面要做的就是确保万无一失。
3.2.2.2 kube-apiserver
K8s 集群API,如果需要与您的 Kubernetes 集群进行交互,就要通过 API
Kubernetes API 是 Kubernetes 控制平面的前端,用于处理内部和外部请求。API 服务器会确定请求是否有效,如果有效,则对其进行处理。您可以通过 REST 调用、kubectl 命令行界面或其他命令行工具(例如 kubeadm)来访问 API。
3.2.2.3 kube-scheduler
K8s 调度程序,您的集群是否状况良好?如果需要新的容器,要将它们放在哪里?这些是 Kubernetes 调度程序所要关注的问题。
调度程序会考虑容器集的资源需求(例如 CPU 或内存)以及集群的运行状况。随后,它会将容器集安排到适当的计算节点。
3.2.2.4 kube-controller-manager
K8s 控制器,控制器负责实际运行集群,而 Kubernetes 控制器管理器则是将多个控制器功能合而为一
控制器用于查询调度程序,并确保有正确数量的容器集在运行。如果有容器集停止运行,另一个控制器会发现并做出响应。控制器会将服务连接至容器集,以便让请求前往正确的端点。还有一些控制器用于创建帐户和 API 访问令牌。
3.2.2.5 etcd
键值存储数据库
配置数据以及有关集群状态的信息位于 etcd(一个键值存储数据库)中。etcd 采用分布式、容错设计,被视为集群的最终事实来源。
3.2.3 k8s运行组件
3.2.3.1 k8s节点
Kubernetes 集群中至少需要一个计算节点,但通常会有多个计算节点。
容器集经过调度和编排后,就会在节点上运行。如果需要扩展集群的容量,那就要添加更多的节点。
3.2.3.2 容器集
容器集是 Kubernetes 对象模型中最小、最简单的单元。
它代表了应用的单个实例。每个容器集都由一个容器(或一系列紧密耦合的容器)以及若干控制容器运行方式的选件组成。容器集可以连接至持久存储,以运行有状态应用。
3.2.3.3 容器运行时引擎
为了运行容器,每个计算节点都有一个容器运行时引擎。
比如 Docker,但 Kubernetes 也支持其他符合开源容器运动(OCI)标准的运行时,例如 rkt 和 CRI-O。
3.2.3.4 kubelet
每个计算节点中都包含一个 kubelet,这是一个与控制平面通信的微型应用。
kublet 可确保容器在容器集内运行,当控制平面需要在节点中执行某个操作时,kubelet 就会执行该操作。
3.2.3.5 kube-proxy
每个计算节点中还包含 kube-proxy,这是一个用于优化 Kubernetes 网络服务的网络代理。
kube-proxy 负责处理集群内部或外部的网络通信——靠操作系统的数据包过滤层,或者自行转发流量。
3.2.4 k8s 存储组件
3.2.4.1 持久存储
除了管理运行应用的容器外,Kubernetes 还可以管理附加在集群上的应用数据。
Kubernetes 允许用户请求存储资源,而无需了解底层存储基础架构的详细信息。持久卷是集群(而非容器集)所特有的,因此其寿命可以超过容器集。
3.2.4.2 容器镜像仓库
Kubernetes 所依赖的容器镜像存储于容器镜像仓库中。
这个镜像仓库可以由您自己配置的,也可以由第三方提供。
3.2.4.3 底层基础架构
您可以自己决定具体在哪里运行 Kubernetes。
答案可以是裸机服务器、虚拟机、公共云提供商、私有云和混合云环境。Kubernetes 的一大优势就是它可以在许多不同类型的基础架构上运行。
3.3 k8s安装部署
4 Pod使用
Pod是kubernetes中你可以创建和部署的最小也是最简的单位。一个Pod代表着集群中运行的一个进程。
Pod中封装着应用的容器(有的情况下是好几个容器),存储、独立的网络IP,管理容器如何运行的策略选项,Pod代表着部署的一个单位:kubernetes中应用的一个实例,可能由一个或者多个容器组合在一起共享资源。
4.1 Pod 特点
Pod有两个必须知道的特点
4.1.1 网络
每一个Pod都会被指派一个唯一的Ip地址,在Pod中的每一个容器共享网络命名空间,包括Ip地址和网络端口,在同一个Pod中的容器可以同locahost进行互相通信,当Pod中的容器需要与Pod外的实体进行通信时,则需要通过端口等共享的网络资源。
4.1.2 存储
Pod能够配置共享存储卷,在Pod中所有的容器能够访问共享存储卷,允许这些容器共享数据,存储卷也允许在一个Pod持久化数据,以防止其中的容器需要被重启。
4.2 使用方式
4.2.1 自主式Pod
这种Pod本身是不能自我修复的,当Pod被创建后(不论是由你直接创建还是被其他Controller),都会被Kuberentes调度到集群的Node上,直到Pod的进程终止、被删掉、因为缺少资源而被驱逐、或者Node故障之前这个Pod都会一直保持在那个Node上,Pod不会自愈。
如果Pod运行的Node故障,或者是调度器本身故障,这个Pod就会被删除,同样的,如果Pod所在Node缺少资源或者Pod处于维护状态,Pod也会被驱逐。
4.2.2 控制器管理的Pod
Kubernetes使用更高级的称为Controller的抽象层,来管理Pod实例,Controller可以创建和管理多个Pod,提供副本管理、滚动升级和集群级别的自愈能力。
例如,如果一个Node故障,Controller就能自动将该节点上的Pod调度到其他健康的Node上。虽然可以直接使用Pod,但是在Kubernetes中通常是使用Controller来管理Pod的
4.3 自主运行Pod
4.3.1 创建资源清单
通过yaml文件或者json描述Pod和其内容器的运行环境和期望状态,例如一个最简单的运行nginx应用的pod,定义如下
1 | vi nginx-pod.yml |
4.3.1.1 参数描述
下面简要分析一下上面的Pod定义文件:
- apiVersion: 使用哪个版本的Kubernetes API来创建此对象
- kind:要创建的对象类型,例如Pod,Deployment等
- metadata:用于唯一区分对象的元数据,包括:name,UID和namespace
- labels:是一个个的key/value对,定义这样的label到Pod后,其他控制器对象可以通过这样的label来定位到此Pod,从而对Pod进行管理。(参见Deployment等控制器对象)
- spec: 其它描述信息,包含Pod中运行的容器,容器中运行的应用等等。不同类型的对象拥有不同的spec定义。详情参见API文档
4.3.2 创建Pod
使用
kubectl
创建pod
1 | kubectl apply -f nginx-pod.yml |
4.3.3 Pod操作
4.3.3.1 查看Pod列表
1 | kubectl get pods |
可以通过增加
-o wide
查看详细信息
1 | kubectl get pods -o wide |
4.3.3.2 查看描述信息
可以通过
describe
查看pod的详细信息
1 | kubectl describe pod nginx |
4.3.3.3 访问pod
可以通过
k8s
创建的虚拟IP进行访问,可以在k8s的任何一个节点访问
1 | curl 10.244.1.10 |
4.3.3.4 删除Pod
可以使用
delete
删除Pod,删除后不能进行恢复
1 | kubectl delete pod nginx |
4.4 控制器运行Pod
Pod本身不具备容错性,这意味着如果Pod运行的Node宕机了,那么该Pod无法恢复。因此推荐使用Deployment等控制器来创建Pod并管理。
4.4.1 创建资源清单
通过yaml文件或者json描述Pod和其内容器的运行环境和期望状态,例如一个最简单的运行nginx应用的pod,定义如下
1 | vi nginx-pod.yml |
4.4.2 参数描述(了解)
下面简要分析一下上面的Pod定义文件:
4.4.2.1 Replicas
副本数量
spec.replicas 是可以选字段,指定期望的pod数量,默认是1。
4.4.2.2 Selector
标签选择器
.spec.selector是可选字段,用来指定 label selector ,圈定Deployment管理的pod范围。如果被指定, .spec.selector 必须匹配 .spec.template.metadata.labels,否则它将被API拒绝。如果 .spec.selector 没有被指定, .spec.selector.matchLabels 默认是.spec.template.metadata.labels。
在Pod的template跟.spec.template不同或者数量超过了.spec.replicas规定的数量的情况下,Deployment会杀掉label跟selector不同的Pod。
4.4.2.3 Pod Template
Pod模板,.spec.template 是 .spec中唯一要求的字段。
.spec.template 是 pod template,它跟 Pod有一模一样的schema,除了它是嵌套的并且不需要apiVersion 和 kind字段。
另外为了划分Pod的范围,Deployment中的pod template必须指定适当的label(不要跟其他controller重复了,参考selector)和适当的重启策略。
.spec.template.spec.restartPolicy 可以设置为 Always , 如果不指定的话这就是默认配置。
4.4.3 创建Pod
1 | kubectl apply -f nginx-pod.yml |
创建后发现,有两个nginx的pod在运行,符合我们的预期
4.4.4 Pod操作
4.4.4.1 删除Pod
这里可以尝试删除Pod
1 | kubectl delete pod nginx-deployment-f77774fc5-cgs82 |
删除Pod后发现重新新建了一个Pod,这是因为有控制器发现少了一个Pod就会进行重新拉起来一个
4.5 镜像拉取策略
pod的镜像拉取策略分为三种:
- always(总是从官方下载镜像)
- never(从不下载镜像)
- ifnotpresent(如果本地没有镜像就从官方下载镜像)。
Kubernetes集群默认使用IfNotPresent策略
4.5.1 always
不管是否存在本地镜像,总是从远程仓库下载
4.5.1.1 修改资源清单
我们可以通过修改配置清单来配置不同的策略
1 | apiVersion: apps/v1 |
4.5.1.2 生效配置
1 | kubectl apply -f nginx-pod.yml |
4.5.1.3 查看创建过程
1 | kubectl describe pod nginx-deployment |
可以清楚的看到重新下载而没有使用本地镜像
4.5.2 IfNotPresent
Kubernetes集群的默认策略,如果有本地镜像就使用,没有则从远程仓库下载
4.5.2.1 修改资源清单
我们可以通过修改配置清单来配置不同的策略
1 | apiVersion: apps/v1 |
4.5.2.2 生效配置
1 | kubectl apply -f nginx-pod.yml |
4.5.2.3 查看创建过程
1 | kubectl describe pod nginx-deployment |
可以清楚的看到没有重新下载镜像而是使用本地的镜像。
5. Pod生命周期
5.1 各个阶段
POD中明确规定了如下几个阶段
状态值 | 说明 |
---|---|
挂起(Pending) |
Pod 已被 Kubernetes 系统接受,但有一个或者多个容器镜像尚未创建。等待时间包括调度 Pod 的时间和通过网络下载镜像的时间。 |
运行中(Running) |
该 Pod 已经绑定到了一个节点上,Pod 中所有的容器都已被创建。至少有一个容器正在运行,或者正处于启动或重启状态。 |
成功(Succeeded) |
Pod 中的所有容器都被成功终止,并且不会再重启。 |
失败(Failed) |
Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。 |
未知(Unknown) |
因为某些原因无法取得 Pod 的状态,通常是因为与 Pod 所在主机通信失败。 |
实际上还有一中状态Terminating,在代码和文档中都没有说明,但却是存在。这种情况出现杂无法获取所在主机的资源情况,一直在尝试建立连接。
5.2 Pod重启策略
在Pod中的容器可能会由于异常等原因导致其终止退出,Kubernetes提供了重启策略以重启容器。
Pod通过restartPolicy
字段指定重启策略,重启策略类型为:Always、OnFailure 和 Never,默认为 Always。
重启策略对同一个Pod的所有容器起作用,容器的重启由Node上的kubelet执行。Pod支持三种重启策略,在配置文件中通过restartPolicy
字段设置重启策略:
重启策略 | 说明 |
---|---|
Always | 当容器失效时,由kubelet自动重启该容器 |
OnFailure | 当容器终止运行且退出码不为0时,由kubelet自动重启该容器 |
Never | 不论容器运行状态如何,kubelet都不会重启该容器 |
注意:这里的重启是指在Pod的宿主Node上进行本地重启,而不是调度到其它Node上。
5.3 Pod状态转换
Pod的容器数 | Pod当前状态 | 发生的事件 | Pod结果状态 | ||
---|---|---|---|---|---|
RestartPolicy=Always | RestartPolicy=OnFailure | RestartPolicy=Never | |||
包含一个容器 | Running | 容器成功退出 | Running | Succeeded | Succeeded |
包含一个容器 | Running | 容器失败退出 | Running | Running | Failure |
包含两个容器 | Running | 1个容器失败退出 | Running | Running | Running |
包含两个容器 | Running | 容器被OOM杀掉 | Running | Running | Failure |
5.4 生命周期行为
5.4.1 初始化容器
初始化容器(
init container
)即应用程序的主容器启动之前要运行的容器,常用于为主容器执行一些预置操作,它们具有两种典型特征。
- 初始化容器必须运行完成直至结束,若某初始化容器运行失败,那么
kubernetes
需要重启它直到成功完成。(注意:如果pod
的spec.restartPolicy
字段值为“Never
”,那么运行失败的初始化容器不会被重启。) - 每个初始化容器都必须按定义的顺序串行运行。
5.4.2 容器探测
容器探测(
container probe
)是Pod
对象生命周期中的一项重要的日常任务,它是kubelet
对容器周期性执行的健康状态诊断,诊断操作由容器的处理器(handler
)进行定义。Kubernetes
支持三种处理器用于Pod
探测:
ExecAction
:在容器内执行指定命令,并根据其返回的状态码进行诊断的操作称为Exec
探测,状态码为0
表示成功,否则即为不健康状态。TCPSocketAction
:通过与容器的某TCP
端口尝试建立连接进行诊断,端口能够成功打开即为正常,否则为不健康状态。HTTPGetAction
:通过向容器IP
地址的某指定端口的指定path
发起HTTP GET
请求进行诊断,响应码为2xx
或3xx
时即为成功,否则为失败。
任何一种探测方式都可能存在三种结果:
“Success”(成功)
、“Failure”(失败)
、“Unknown”(未知)
,只有success
表示成功通过检测。
6. 健康检查
强大的自愈能力是Kubernetes这类容器编排引擎的一个重要特性,自愈的默认实现方式是自动重启发生故障的容器。
6.1 为什么需要健康检查
用户还可以利用Liveness和Readiness探测机制设置更精细的健康检查,进而实现如下需求:
- 零停机部署。
- 避免部署无效的镜像。
- 更加安全的滚动升级。
6.2 检查策略
在Pod部署到Kubernetes集群中以后,为了确保Pod处于健康正常的运行状态,Kubernetes提供了两种探针,用于检测容器的状态:
6.2.1 存活探测
Liveness是检查容器是否处于运行状态。如果检测失败,kubelet将会杀掉掉容器,并根据重启策略进行下一步的操作。如果容器没有提供Liveness Probe,则默认状态为Success;
Liveness探测器是让Kubernetes知道你的应用是否活着。如果你的应用还活着,那么Kubernetes就让它继续存在。如果你的应用程序已经死了,Kubernetes将移除Pod并重新启动一个来替换它。
让我们想象另一种情况,当我们的应用在成功启动以后因为一些原因“宕机”,或者遇到死锁情况,导致它无法响应用户请求。 在默认情况下,Kubernetes会继续向Pod发送请求,通过使用存活探针来检测,当发现服务不能在限定时间内处理请求(请求错误或者超时),就会重新启动有问题的pod。
6.2.2 就绪探测
Readiness 是检查容器是否已经处于可接受服务请求的状态。如果Readiness Probe失败,端点控制器将会从服务端点(与Pod匹配的)中移除容器的IP地址。Readiness的默认值为Failure,如果一个容器未提供Readiness,则默认是Success。
就绪探针旨在让Kubernetes知道你的应用是否准备好为请求提供服务。Kubernetes只有在就绪探针通过才会把流量转发到Pod。如果就绪探针检测失败,Kubernetes将停止向该容器发送流量,直到它通过。
一个应用往往需要一段时间来预热和启动,比如一个后端项目的启动需要连接数据库执行数据库迁移等等,一个Spring项目的启动也需要依赖Java虚拟机。即使该过程已启动,您的服务在启动并运行之前也无法运行。应用在完全就绪之前不应接收流量,但默认情况下,Kubernetes会在容器内的进程启动后立即开始发送流量。通过就绪探针探测,直到应用程序完全启动,然后才允许将流量发送到新副本。
6.2.3 两者对比
- Liveness探测和Readiness探测是两种Health Check机制,如果不特意配置,Kubernetes将对两种探测采取相同的默认行为,即通过判断容器启动进程的返回值是否为零来判断探测是否成功。
- 两种探测的配置方法完全一样,支持的配置参数也一样。不同之处在于探测失败后的行为:Liveness探测是重启容器;Readiness探测则是将容器设置为不可用,不接收Service转发的请求。
- Liveness探测和Readiness探测是独立执行的,二者之间没有依赖,所以可以单独使用,也可以同时使用。用Liveness探测判断容器是否需要重启以实现自愈;用Readiness探测判断容器是否已经准备好对外提供服务
6.2.4 如何配置
对于LivenessProbe和ReadinessProbe用法都一样,拥有相同的参数和相同的监测方式。
initialDelaySeconds:用来表示初始化延迟的时间,也就是告诉监测从多久之后开始运行,单位是秒
timeoutSeconds: 用来表示监测的超时时间,如果超过这个时长后,则认为监测失败
periodSeconds:指定每多少秒执行一次探测,Kubernetes如果连续执行3次Liveness探测均失败,则会杀掉并重启容器
6.3 使用场景
- 如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活探针; kubelet 将根据 Pod 的
restartPolicy
自动执行正确的操作。 - 如果希望容器在探测失败时被杀死并重新启动,那么请指定一个存活探针,并指定
restartPolicy
为 Always 或 OnFailure。 - 如果要仅在探测成功时才开始向 Pod 发送流量,请指定就绪探针。在这种情况下,就绪探针可能与存活探针相同,但是 spec 中的就绪探针的存在意味着 Pod 将在没有接收到任何流量的情况下启动,并且只有在探针探测成功后才开始接收流量。
- 如果您希望容器能够自行维护,您可以指定一个就绪探针,该探针检查与存活探针不同的端点。
- 如果您只想在 Pod 被删除时能够排除请求,则不一定需要使用就绪探针;在删除 Pod 时,Pod 会自动将自身置于未完成状态,无论就绪探针是否存在。当等待 Pod 中的容器停止时,Pod 仍处于未完成状态。
6.4 默认的健康检查
我们首先学习Kubernetes默认的健康检查机制:每个容器启动时都会执行一个进程,此进程由Dockerfile的CMD或ENTRYPOINT指定。
如果进程退出时返回码非零,则认为容器发生故障,Kubernetes就会根据restartPolicy重启容器
6.4.1 创建资源清单
Pod的restartPolicy设置为OnFailure,默认为Always,sleep 10; exit 1模拟容器启动10秒后发生故障
1 | vi pod-default-health.yml |
6.4.2 创建容器
1 | kubectl apply -f pod-default-health.yml |
6.4.3 监控Pod变化
1 | kubectl get pods -o wide -w |
该命令可以不断显示容器的因为失败不断重启
在上面的例子中,容器进程返回值非零,Kubernetes则认为容器发生故障,需要重启。
有不少情况是发生了故障,但进程并不会退出。比如访问Web服务器时显示500内部错误,可能是系统超载,也可能是资源死锁,此时httpd进程并没有异常退出,在这种情况下重启容器可能是最直接、最有效的解决方案,那我们如何利用HealthCheck机制来处理这类场景呢?
6.5 探针类型
探针类型是指通过何种方式来进行健康检查,K8S有三种类型的探测:HTTP,Command和TCP。
6.5.1 exec存活探针
对于命令探测,是指Kubernetes在容器内运行命令。如果命令以退出代码0返回,则容器将标记为正常。否则,它被标记为不健康。
下面的资源会在先创建一个nginx的任务,生存测试探针livenessProbe
会执行test -e /tmp/healthy
命令检查文件是否存在, 若文件存在则返回状态码 0,表示成功通过测试。
1 | apiVersion: v1 |
6.5.1.1 创建pod
创建pod
1 | kubectl create -f pod-demo.yaml |
启动后不断检测
/tmp/healthy
是否存在,不存在重启容器
6.5.1.2 创建文件
新开一个窗口写入登录pod容器,写入
healthy
文件
1 | # 登录pod容器 |
查看原来的pod状态
1 | kubectl get pods -o wide -w |
我们发现pod不在不断地重启了
6.5.2 HTTP就绪探针
HTTP探测可能是最常见的探针类型。即使应用不是HTTP服务,也可以创建一个轻量级HTTP服务器来响应探测。比如让Kubernetes通过HTTP访问一个URL,如果返回码在200到300范围内,就将应用程序标记为健康状态,否则它被标记为不健康。
上面 清单 文件 中 定义 的 httpGet 测试 中, 请求的资源路径 为/healthy
, 地址 默认 为 Pod IP, 端口使用了容器中定义的端口名称 HTTP, 这也是明确为容器指明要暴露的端口的用途之一。
1 | apiVersion: v1 |
6.5.2.1 创建pod
1 | kubectl create -f pod-demo.yaml |
我们发现nginx 一致处于未未就绪状态
6.5.2.2 查看pod详情
1 | kubectl describe pod pod-nginx-demo |
6.5.2.3 创建文件
新开一个窗口写入登录pod容器,写入
healthy
文件
1 | # 登录pod容器 |
查看原来的pod状态
1 | kubectl get pods -o wide -w |
6.5.2.4 访问
1 | curl 10.244.1.30/healthy |
再次删除
healthy
文件
1 | rm -f /usr/share/nginx/html/healthy |
再次查看pod状态,进入未就绪状态
1 | kubectl get pods -o wide -w |
6.5.3 TCP探针
TCP探测是指Kubernetes尝试在指定端口上建立TCP连接。
如果它可以建立连接,容器被认为是健康的; 如果它不能被认为是不健康的。
这常用于对gRPC或FTP服务的探测。
下面的资源清单文件,向Pod IP的80/tcp端口发起连接请求,并根据连接建立的状态判断Pod存活状态。
1 | apiVersion: v1 |
6.5.3.1 探测结果
每次探测都将获得以下三种结果之一:
- 成功:容器通过了诊断。
- 失败:容器未通过诊断。
- 未知:诊断失败,因此不会采取任何行动。
6.5.3.2 创建pod
1 | kubectl create -f pod-demo.yaml |
只要80端口正常一致就是正常状态
k8s 高级使用
1. Pod控制器
1.1 Pod控制器是什么
Pod控制器就是帮助我们自动的调度管理Pod,并满足期望的Pod数量。
Pod控制器是用于实现管理pod的中间层,确保pod资源符合预期的状态,pod的资源出现故障时,会尝试 进行重启,当根据重启策略无效,则会重新新建pod的资源。
创建为具体的控制器对象之后,每个控制器均通过API Server
提供的接口持续监控相关资源对象的当前状态,并在因故障、更新或其他原因导致系统状态发生变化时,尝试让资源的当前状态想期望状态迁移和逼近。
1.2 Pod和Pod控制器
Pod
控制器资源通过持续性地监控集群中运行着的Pod
资源对象来确保受其管控的资源严格符合用户期望的状态,例如资源副本的数量要精确符合期望等。通常,一个
Pod
控制器资源至少应该包含三个基本的组成部分:
- 标签选择器:匹配并关联
Pod
资源对象,并据此完成受其管控的Pod
资源计数。 - 期望的副本数:期望在集群中精确运行着的
Pod
资源的对象数量。 - Pod模板:用于新建
Pod
资源对象的Pod
模板资源。
1.3 控制器的必要性
自主式Pod
对象由调度器调度到目标工作节点后即由相应节点上的kubelet
负责监控其容器的存活状态,容器主进程崩溃后,kubelet
能够自动重启相应的容器。但对出现非主进程崩溃类的容器错误却无从感知,这便依赖于pod
资源对象定义的存活探测,以便kubelet
能够探知到此类故障。但若pod
被删除或者工作节点自身发生故障(工作节点上都有kubelet
,kubelet
不可用,因此其健康状态便无法保证),则便需要控制器来处理相应的容器重启和配置。
1.4 常见的控制器
Pod
控制器由master
的kube-controller-manager
组件提供,常见的此类控制器有
1.4.1 ReplicaSet
代用户创建指定数量的pod
副本数量,确保pod
副本数量符合预期状态,并且支持滚动式自动扩容和缩容功能
1.4.2 Deployment
工作在ReplicaSet
之上,用于管理无状态应用,目前来说最好的控制器。支持滚动更新和回滚功能,还提供声明式配置。
1.4.3 DaemonSet
用于确保集群中的每一个节点只运行特定的pod
副本,常用于实现系统级后台任务。比如ELK
服务
2. ReplicaSet控制器
2.1 ReplicaSet概述
ReplicaSe
t是取代早期版本中的ReplicationController
控制器,其功能基本上与ReplicationController
相同
ReplicaSet
(简称RS)是Pod
控制器类型的一种实现,用于确保由其管控的Pod
对象副本数在任意时刻都能精确满足期望的数量。ReplicaSet
控制器资源启动后会查找集群中匹配器标签选择器的Pod
资源对象,当前活动对象的数量与期望的数量不吻合时,多则删除,少则通过Pod
模板创建以补足。
2.2 ReplicaSet功能
ReplicaSet
能够实现以下功能:
2.2.1 精确反应期望值
确保Pod资源对象的数量精确反映期望值:ReplicaSet
需要确保由其控制运行的Pod副本数量精确吻合配置中定义的期望值,否则就会自动补足所缺或终止所余。
2.2.2 保证高可用
确保Pod健康运行:探测到由其管控的Pod
对象因其所在的工作节点故障而不可用时,自动请求由调度器于其他工作节点创建缺失的Pod
副本。
2.2.3 弹性伸缩
弹性伸缩:可通过ReplicaSet
控制器动态扩容或者缩容Pod
资源对象的数量。必要时还可以通过HPA
控制器实现Pod
资源规模的自动伸缩。
2.3 创建ReplicaSet
2.3.1 核心属性
spec字段一般嵌套使用以下几个属性字段:
字段值 | 类型 | 描述 |
---|---|---|
replicas | Integer | 指定期望的Pod对象副本数量 |
selector | Object | 当前控制器匹配Pod对象副本的标签选择器,支持matchLabels和matchExpressions两种匹配机制 |
template | Object | 用于定义Pod时的Pod资源信息 |
minReadySeconds | Integer | 用于定义Pod启动后多长时间为可用状态,默认为0秒 |
2.3.2 ReplicaSet示例
2.3.2.1 创建资源清单
1 | vi nginx-rs.yml |
2.3.2.2 创建rs控制器
1 | kubectl apply -f nginx-rs.yaml |
2.3.2.3查看rs控制器
1 | kubectl get rs |
2.3.2.4查看pod容器
通过查看pod可以看出pod命令是规则是前面是replicaset控制器的名称加随机生成的字符串
1 | kubectl get pods -o wide -w |
2.4 更新控制器
修改上面创建的
replicaset
示例文件,将镜像nginx:1.12
改为1.20
版本
1 | vi nginx-rs.yml |
2.4.1 应用更新
1 | kubectl apply -f nginx-rs.yaml |
2.4.1.1 查看更新流程
1 | kubectl get pods -o wide -w |
我们发现pod没有任何更新变化
2.4.1.2 查看Pod版本号
1 | kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image |
这里并没有更新pod的nginx版本号
2.4.1.3 删除pod应用
这里虽然重载了,但是已有的pod所使用的镜像仍然是1.12版本的,只是新建pod时才会使用1.20版本,这里测试先手动删除已有的pod。
1 | kubectl delete pods -l app=nginx |
2.4.1.4 查看版本
1 | kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image |
现在我们发现pod的版本已经更新正确了
2.5 RS扩缩容
可以直接通过
vim
编辑清单文件修改replicas
字段,也可以通过kubect edit
命令去编辑
kubectl
还提供了一个专用的子命令scale
用于实现应用规模的伸缩,支持从资源清单文件中获取新的目标副本数量,也可以直接在命令行通过“--replicas”
选项进行读取。
2.5.1 scale命令扩容
命令扩容一般用于短期的临时性扩容,应付完成后要记得缩容到原来水平
2.5.1.1 查看容量
可看到当前是两个节点
1 | kubectl get pods -o wide |
2.5.1.2 执行扩容
使用
scale
命令可以对集群进行扩缩容
1 | kubectl scale replicasets nginx-rs --replicas=4 |
2.5.1.3 查看扩容过程
在更新前打开新窗口,监控pod的更新变化
1 | kubectl get pods -o wide -w |
在更新前打开新窗口,监控RS的更新变化
1 | kubectl get rs -o wide -w |
我们发现扩容后只是在原来的RS集群上面增加了两个节点
2.5.2 配置文件缩容
配置文件扩容一般用于初始容量变更,长期进行扩容
2.5.2.1 查看容量
可看到当前是四个节点
1 | kubectl get pods -o wide |
2.5.2.2 应用配置
因为没有变更配置文件可以直接应用配置文件
1 | kubectl apply -f nginx-rs.yml |
2.5.2.3 查看缩容容过程
在更新前打开新窗口,监控pod的更新变化
1 | kubectl get pods -o wide -w |
在更新前打开新窗口,监控RS的更新变化
1 | kubectl get rs -o wide -w |
我们发现扩容后只是在原来的RS集群上面减少了两个节点
2.6 删除rs控制器
使用
Kubectl delete
命令删除ReplicaSet
对象时默认会一并删除其管控的各Pod
对象,有时,考虑到这些Pod
资源未必由其创建,或者即便由其创建也并非自身的组成部分,这时候可以添加“--cascade=false”
选项,取消级联关系。
2.6.1 查看集群情况
2.6.1.1 查看RS集群
1 | kubectl get rs -o wide |
2.6.1.2 查看POD
1 | kubectl get pods -o wide |
2.6.2 删除rs
删除rs可以通过参数
cascade=false
设置不删除pod
1 | kubectl delete replicasets nginx-rs --cascade=false |
2.6.3 查看集群情况
2.6.3.1 查看RS集群
1 | kubectl get rs -o wide |
2.6.3.2 查看POD
1 | kubectl get pods -o wide |
2.6.4 删除Pod
1 | kubectl delete pods nginx-rs-7rzz6 |
3. Deployment控制器
3.1 Deployment概述
Deployment为Pod和Replica Set(下一代Replication Controller)提供声明式更新
只需要在 Deployment 中描述想要的目标状态是什么,Deployment controller 就会帮您将 Pod 和ReplicaSet 的实际状态改变到您的目标状态。也可以定义一个全新的 Deployment 来创建 ReplicaSet 或者删除已有的 Deployment 并创建一个新的来替换。
3.1.1 其他特性
Deployment
控制器资源的主要职责是为了保证Pod
资源的健康运行,其大部分功能均可通过调用ReplicaSet
实现,同时还增添部分特性。
- 事件和状态查看:必要时可以查看
Deployment
对象升级的详细进度和状态。 - 回滚:升级操作完成后发现问题时,支持使用回滚机制将应用返回到前一个或由用户指定的历史记录中的版本上。
- 版本记录:对
Deployment
对象的每一个操作都予以保存,以供后续可能执行的回滚操作使用。 - 暂停和启动:对于每一次升级,都能够随时暂停和启动。
- 多种自动更新方案:一是
Recreate
,即重建更新机制,全面停止、删除旧有的Pod
后用新版本替代;另一个是RollingUpdate
,即滚动升级机制,逐步替换旧有的Pod
至新的版本。
3.2 Deployment配置
3.2.1 编辑资源清单
1 | vi nginx-deployment.yml |
3.2.2 配置项说明
3.2.2.1 配置解释
- 我们定义了一个Deployment,名字叫nginx-deployment;
- 通过spec.replicas字段定义了Pod的副本数是2;
- 通过spec.selector字段定义了被打上app: nginx的标签的Pod才会被管理;
- tmplate字段定义了这个Deployment管理的Pod应该是怎样的,具有怎样的属性;
3.2.2.2 控制器描述
总的来说一个Deploymet控制器可以由两部分组成:
3.2.3 创建控制器
1 | kubectl apply -f nginx-deployment.yml |
3.2.4 查看replicaset
ReplicaSet是一个副本控制器,ReplicaSet可以用selector来控制Pod的数量,而Deployments是一个更高层次的概念,它管理ReplicaSets,并提供对pod的声明性更新以及许多其他的功能。
1 | kubectl get replicaset -o wide |
通过查看资源对象可以看出,Deployment会自动创建相关的ReplicaSet控制器资源,并以”[DEPLOYMENT-name]-[POD-TEMPLATE-HASH-VALUE]”格式为其命名,其中的hash值由Deployment自动生成。而Pod名则是以ReplicaSet控制器的名称为前缀,后跟5位随机字符。
3.3 更新策略
ReplicaSet控制器的应用更新需要手动分成多步并以特定的次序进行,过程繁杂且容易出错,而Deployment却只需要由用户指定在Pod模板中要改动的内容,(如镜像文件的版本),余下的步骤便会由其自动完成。Pod副本数量也是一样。
Deployment控制器支持两种更新策略:滚动更新(rolling updata)和 重建更新(recreate),默认情况下为滚动更新
3.3.1 重建更新
重建更新为:先删除所有的Pod再根据新的模板创建新的Pod,中间会导致服务的不可用,用户要么使用的是新版本,要么就是旧版本
3.3.2 滚动更新
滚动更新是默认的更新策略,它在删除一些旧版本的Pod的同时补充创建一些新的Pod,更新期间服务不会中断。
滚动更新期间,应用升级期间还要确保可用的Pod对象数量不低于某些阈值。确保可以持续处理客户端请求。变动的方式和Pod对象的数量范围将通过==spec.strategy.roollingUpdata.maxSurge和spec.strategy.roollingUpdata.maxunavailable两个属性协同进行定义。两个参数用法如下:
- maxSurge:指定升级期间存在的总Pod对象数量最多以超出期望值的个数,其值可以为0或者正整数,也可以是一个期望值的百分比:例如如果期望值是3,当前的属性值为1,则表示Pod对象的总数不能超过4个。
- maxUnavailable:升级期间正常可用的Pod副本数(包括新旧版本)最多不能低于期望的个数、其值可以是0或者正整数。也可以是一个期望值的百分比,默认值为1;该值意味着如果期望值是3,那么在升级期间至少要有两个Pod对象处于正常提供服务的状态
maxSurge和maxUnavailable的数量不能同时为0,否则Pod对象的复本数量在符合用户期望的数量后无法做出合理变动以进行滚动更新操作。
3.4 更新控制器
命令扩容一般用于短期的临时性扩容,应付完成后要记得缩容到原来水平
3.4.1 命令更新
3.4.1.1 查看版本
通过命令查看pod的版本号
1 | kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image |
3.4.1.2 执行更新命令
1 | kubectl set image deployment/nginx-deployment nginx=nginx:1.15 |
3.4.1.3 查看更新过程
在更新前打开新窗口,监控pod的更新变化
1 | kubectl get pods -o wide -w |
在更新前打开新窗口,监控RS的更新变化
1 | kubectl get rs -o wide -w |
我们发现更新后新建了一个RS,并且保留原来的RS但是节点数为0用来回滚
3.4.1.4 查看版本
通过命令查看pod的版本号
1 | kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image |
3.4.2 配置更新
3.4.2.1 查看版本
通过命令查看pod的版本号
1 | kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image |
3.4.2.2 编辑资源清单
1 | vi nginx-deployment.yml |
3.4.2.3 应用更新
1 | kubectl apply -f nginx-deployment.yml |
3.4.2.4 查看更新过程
在更新前打开新窗口,监控pod的更新变化
1 | kubectl get pods -o wide -w |
在更新前打开新窗口,监控RS的更新变化
1 | kubectl get rs -o wide -w |
我们发现更新后新建了一个RS,并且保留原来的RS但是节点数为0用来回滚
3.4.2.5 查看版本
通过命令查看pod的版本号
1 | kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image |
3.4.3 回滚更新
通过
rollout
命令进行回滚操作
3.4.3.1 查看版本
通过命令查看pod的版本号
1 | kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image |
3.4.3.2 执行回滚命令
1 | kubectl rollout undo deployment/nginx-deployment |
3.4.3.3 查看更新过程
在更新前打开新窗口,监控pod的更新变化
1 | kubectl get pods -o wide -w |
在更新前打开新窗口,监控RS的更新变化
1 | kubectl get rs -o wide -w |
我们发现回滚没有创建新的rs而是将使用了原来的rs
3.4.3.4 查看版本
通过命令查看pod的版本号
1 | kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image |
3.4.4 再次回滚
3.4.4.1 查看版本
通过命令查看pod的版本号
1 | kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image |
3.4.4.2 执行回滚命令
1 | kubectl rollout undo deployment/nginx-deployment |
3.4.4.3 查看更新过程
在更新前打开新窗口,监控pod的更新变化
1 | kubectl get pods -o wide -w |
在更新前打开新窗口,监控RS的更新变化
1 | kubectl get rs -o wide -w |
我们发现回滚没有创建新的rs而是将使用了原来的rs
3.4.4.4 查看版本
通过命令查看pod的版本号
1 | kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image |
我们发现
rollout
回滚只在最近的两个版本之间来回回滚,不会回滚到在上一个版本。
3.4.5 回滚指定版本
3.4.5.1 查看历史版本
通过
rollout history
查看版本历史
1 | kubectl rollout history deployment/nginx-deployment |
3.4.5.2 历史版本内容
通过指定版本号来查看变更内容,找到需要回滚的版本,这里我会回滚到最早版本
nginx:1.7.9
1 | kubectl rollout history deployment/nginx-deployment --revision=1 |
我们找到了需要回滚的版本是
1
3.4.5.3 查看版本
通过命令查看pod的版本号
1 | kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image |
3.4.5.4 执行回滚命令
写入我们需要回滚到的指定版本
1
1 | kubectl rollout undo deployment/nginx-deployment --to-revision=1 |
3.4.5.5 查看版本
通过命令查看pod的版本号
1 | kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image |
到此我们已经回滚到了指定版本
3.5 Deployment扩缩容
3.5.1 scale命令扩容
命令扩容一般用于短期的临时性扩容,应付完成后要记得缩容到原来水平
3.5.1.1 查看当前容量
当前是两个节点
1 | kubectl get pods -o wide |
3.5.1.2 执行扩容
使用
scale
命令可以对集群进行扩缩容,扩充到4个节点
1 | kubectl scale deployment nginx-deployment --replicas=4 |
3.5.1.3 查看扩容过程
在更新前打开新窗口,监控pod的更新变化
1 | kubectl get pods -o wide -w |
在更新前打开新窗口,监控RS的更新变化
1 | kubectl get rs -o wide -w |
我们发现扩容后只是在原来的RS集群上面增加了两个节点
3.5.2 配置文件缩容
配置文件扩缩容一般用于初始容量变更,长期进行扩缩容
3.5.2.1 查看当前容量
当前是4个节点
1 | kubectl get pods -o wide |
3.5.2.2 应用配置文件
因为我们没有更改配置文件,直接应用配置文件即可
1 | kubectl apply -f nginx-deployment.yml |
3.5.2.3 查看扩容过程
在更新前打开新窗口,监控pod的更新变化
1 | kubectl get pods -o wide -w |
在更新前打开新窗口,监控RS的更新变化
1 | kubectl get rs -o wide -w |
我们发现扩容后只是在原来的RS集群上面减少了两个节点
3.6 删除Deployment
3.6.1 查看集群情况
3.6.1.1 查看Deployment
1 | kubectl get deployments -o wide |
3.6.1.2 查看POD
1 | kubectl get pods -o wide |
3.6.1.3 删除Deployment
执行删除命令删除Deployment
1 | kubectl delete deployment nginx-deployment |
3.6.1.4 查看Deployment
1 | kubectl get deployments -o wide |
3.6.1.5 查看POD
1 | kubectl get pods -o wide |
4. 数据存储
4.1 什么是数据卷
Pod
本身具有生命周期,这就带了一系列的问题,
- 当一个容器损坏之后,
kubelet
会重启这个容器,但是文件会丢失-这个容器会是一个全新的状态; - 当很多容器在同一
Pod
中运行的时候,很多时候需要数据文件的共享。
Docker
支持配置容器使用存储卷将数据持久存储于容器自身文件系统之外的存储空间之中,它们可以是节点文件系统或网络文件系统之上的存储空间。相应的,kubernetes
也支持类似的存储卷功能,不过,其存储卷是与Pod
资源绑定而非容器。
4.1.1 存储卷概述
简单来说,存储卷是定义在Pod
资源之上、可被其内部的所有容器挂载的共享目录,它关联至某外部的存储设备之上的存储空间,从而独立于容器自身的文件系统,而数据是否具有持久能力取决于存储卷自身是否支持持久机制。Pod
、容器与存储卷的关系图如下。
4.1.2 存储卷类型
Kubernetes
支持非常丰富的存储卷类型,包括本地存储(节点)和网络存储系统中的诸多存储机制,还支持Secret
和ConfigMap
这样的特殊存储资源。
例如,关联节点本地的存储目录与关联GlusterFS
存储系统所需要的配置参数差异巨大,因此指定存储卷类型时也就限定了其关联到的后端存储设备。通过命令# kubectl explain pod.spec
可以查看当前kubernetes
版本支持的存储卷类型。常用类型如下:
4.1.2.1 非持久性存储
- emptyDir
- hostPath
4.1.2.2 网络连接性存储
- SAN:iscsi
NFS:nfs、cfs
4.1.2.3 分布式存储
- glusterfs、cephfs、rbd
4.1.2.4 云端存储
- awsElasticBlockStore、azureDisk、gitRepo
4.2 emptydir
emptyDir
存储卷是Pod
对象生命周期中的一个临时目录,类似于Docker
上的“docker 挂载卷”
,在Pod
对象启动时即被创建,而在Pod
对象被移除时会被一并删除(永久删除)。
当pod的存储方案设定为emptydir的时候,pod启动时,就会在pod所在节点的磁盘空间开辟出一块空卷,最开始里面是什么都没有的,pod启动后容器产生的数据会存放到那个空卷中。空卷变成了一个临时卷供pod内的容器读取和写入数据,一旦pod容器消失,节点上开辟出的这个临时卷就会随着pod的销毁而销毁
注意:一个容器崩溃了不会导致数据的丢失,因为容器的崩溃并不移除Pod
。
4.2.1 使用场景
一般来说emptydir的用途都是用来充当临时存储空间,例如一些不需要数据持久化的微服务,我们都可以用emptydir来当做微服务pod的存储方案
- 充当临时存储空间,当pod内容器产生的数据不需要做持久化存储的时候用emptydir
- 设制检查点以从崩溃事件中恢复未执行完毕的长计算
4.2.2 使用示例
4.2.2.1 案例说明
这里定义了一个deploy
资源对象(vol-emptydir-deploy
),在其内部定义了两个容器,其中一个容器是辅助容器sidecar
,每隔10秒生成一行信息追加到index.html
文件中;
另一个是nginx
容器,将存储卷挂载到站点家目录。然后访问nginx
的html
页面验证两个容器之间挂载的emptyDir
实现共享。
4.2.2.2 创建资源清单
1 | vi vol-emptydir-deploy.yml |
4.2.2.3 创建deploy
1 | kubectl apply -f vol-emptydir-deploy.yml |
4.2.2.4 访问测试
1 | curl 10.244.1.16 |
我们发现
nginx
可以共享来自sidecar
的数据
4.2.3 测试存储卷
4.2.3.1 登录sidecar
登录
sidecar
,并查看目录文件
1 | kubectl exec vol-emptydir-deploy-86cd768757-5mtzx -c sidecar -it -- /bin/sh |
在改容器中写入文件
1 | echo "hello world" > hello |
4.2.3.2 登录nginx
再打开一个窗口登录
nginx
,查看hello文件是否存在
1 | kubectl exec vol-emptydir-deploy-86cd768757-5mtzx -c nginx -it -- /bin/sh |
删除
hello
文件
1 | rm -rf hello |
在
sidecar
中查看文件时已经删除了
4.2.4 测试文件持久性
4.2.4.1 登录sidecar
登录
sidecar
并在html中写入一段文件
1 | kubectl exec vol-emptydir-deploy-86cd768757-5mtzx -c sidecar -it -- /bin/sh |
4.2.4.2 访问测试
1 | curl 10.244.1.16 |
4.2.4.3 删除Pod
手动删除容器模拟容器销毁,用于是pod是被控制器管理的,删除后会被重建新的pod
1 | kubectl delete pod vol-emptydir-deploy-86cd768757-5mtzx |
我们发现销毁pod后新建的pod转移到node2节点了,并且pod名称也改变了
4.2.4.4 访问测试
1 | curl 10.244.2.22 |
这时候在看我们之前写入的文字不见了
4.3 hostpath
hostPath
类型的存储卷是指将工作节点上的某文件系统的目录或文件挂载于Pod
中的一种存储卷,独立于Pod
资源的生命周期,具有持久性。在Pod
删除时,数据不会丢失。
hostPath类型则是映射node文件系统中的文件或者目录到pod里。在使用hostPath类型的存储卷时,也可以设置type字段,支持的类型有文件、目录、File、Socket、CharDevice和BlockDevice。
其实这个功能就相当于docker中的-v 目录映射,只不过在k8s中的时候,pod会漂移,当pod漂移到其他node节点的时候,pod不会跨节点的去读取目录。所以说hostpath只能算一种半持久化的存储方式
4.3.1 使用场景
- 当运行的容器需要访问Docker内部结构时,如使用hostPath映射/var/lib/docker到容器;
- 当在容器中运行cAdvisor时,可以使用hostPath映射/dev/cgroups到容器中;
4.3.2 使用示例
4.3.2.1 案例说明
这里定义了一个deploy
资源对象(vol-hostpath-deploy
),在其内部定义了一个容器,将nginx的html文件映射到外部,我们外面给html写入文件,访问nginx来验证是否能够访问。
4.3.2.2 创建资源清单
1 | vi vol-hostpath-deploy.yml |
4.3.2.3 创建deploy
1 | kubectl apply -f vol-hostpath-deploy.yml |
4.3.2.4 写入文件
查看pod发现pod在node1节点,登录node1节点写入文件
1 | cd /tmp/k8s/data/volumn1/ |
4.3.2.5 访问测试
1 | curl 10.244.1.17 |
4.3.3测试存储卷
4.3.3.1 登录nginx
1 | kubectl exec vol-emptydir-deploy-86cd768757-5mtzx -c nginx -it -- /bin/sh |
4.3.3.2 登录宿主机查看
登录pod所在的节点的宿主机目录查看文件是否存在
1 | cat hello |
发现写入的文件是存在的
4.3.4 测试文件持久性
4.3.4.1 删除Pod
手动删除容器模拟容器销毁,用于是pod是被控制器管理的,删除后会被重建新的pod
1 | kubectl delete pod vol-hostpath-deploy-595699bdb4-4m8s4 |
4.3.4.2 访问测试
我们知道hostpath是在宿主机创建文件的,现在我们发现pod漂移到了node2节点,我们尝试访问,并登录容器查看
1 | curl 10.244.2.23 |
发现文件不存在符合我们的预期
4.3.4.3 再次删除容器
因为上一次删除漂移到了node2,我们再次删除让其漂移到node1
1 | kubectl delete pod vol-hostpath-deploy-595699bdb4-gd8sw |
4.3.4.4 访问测试
1 | curl 10.244.1.18 |
4.3.4.5 总结
可以发现容器被删除后,新建的pod也可以看到我们映射的目录,我们写入的文件是存在的
这有个缺点就是不能够跨容器去读取数据,如果删除后的pod被调度到其他节点的话,原来的数据也就没有了,如果能不受节点的影响,并且挂载的数据不会随生命周期的结束而结束,我们应该怎么解决呢?就是我们后面讲到的持久化存储了
5. pod调度策略
一般而言pod的调度都是通过RC、Deployment等控制器自动完成,但是仍可以通过手动配置的方式进行调度,目的就是让pod的调度符合我们的预期。
5.1 节点调度
Pod.spec.nodeName
用于强制约束将Pod调度到指定的Node节点上,这里说是“调度”,但其实指定了nodeName的Pod会直接跳过Scheduler的调度逻辑,直接写入PodList列表,该匹配规则是强制匹配。
5.1.1 创建资源清单
1 | vi pod-node-dispatch.yml |
5.1.2 应用部署
1 | kubectl apply -f pod-node-selector-dispatch.yml |
创建部署后,我们发现节点在node1的节点上
5.1.3 删除pod
删除pod应用,pod控制器会重建pod
1 | kubectl delete pod nginx-deployment-5dc7769598-n25q5 |
我们发现节点删除后,重新的应用还是落在了node1节点上,说明我们的节点调度生效的
5.2 定向调度(标签调度)
定向调度是把pod调度到具有特定标签的node节点的一种调度方式,比如把MySQL数据库调度到具有SSD的node节点以优化数据库性能。此时需要首先给指定的node打上标签,并在pod中设置nodeSelector属性以完成pod的指定调度。
5.2.1 创建标签
给指定的node打上标签
5.2.1.1 添加标签
给node2添加上
disk=ssd
的标签
1 | kubectl label nodes k8s-node2 disk=ssd |
5.2.1.2 显示标签
显示node2的所有标签
1 | kubectl label node k8s-node2 --list=true |
我们发现我们的标签已经添加上去了
5.2.3 创建资源清单
1 | vi pod-node-selector-dispatch.yml |
5.2.4 应用部署
1 | kubectl apply -f pod-node-selector-dispatch.yml |
创建部署后,我们发现两个pod都在node2的节点上
5.2.5 删除pod
删除pod应用,pod控制器会重建pod
1 | kubectl delete pod nginx-deployment-5dc7769598-n25q5 |
我们发现节点删除后,重新的应用还是落在了node2节点上,说明我们的定向调度生效的
注意:定向调度可以把pod调度到特定的node节点,但随之而来的缺点就是如果集群中不存在响应的node,即使有基本满足条件的node节点,pod也不会被调度
6. Service
Kubernetes service 定义了这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。这一组 pod 能够被 Service 访问到,通常是通过 Label selector
Service能够提供负载均衡的能力,但是在使用上有以下限制:
- 只提供4层负载均衡能力,而没有7层功能,但有时我们可能需要更多的匹配规则来转发请求,这点上4层负载均衡是不支持的
6.1 Service概述
Kubernetes Service 从逻辑上代表了一组 Pod,具体是哪些 Pod 则是由 label 来挑选,Service 有自己 IP,而且这个 IP 是不变的。
- 客户端只需要访问 Service 的 IP,Kubernetes 则负责建立和维护 Service 与 Pod 的映射关系。
- 无论后端 Pod 如何变化,对客户端不会有任何影响,因为 Service 没有变
6.1.1 为什么要有Service
当Pod宕机后重新生成时,其IP等状态信息可能会变动,Service会根据Pod的Label对这些状态信息进行监控和变更,保证上游服务不受Pod的变动而影响。
Kubernetes Pods 是有生命周期的。他们可以被创建,而且销毁不会再启动。 如果您使用Deployment来运行您的应用程序,则它可以动态创建和销毁 Pod。
一个Kubernetes的Service
是一种抽象,它定义了一组Pods
的逻辑集合和一个用于访问它们的策略 - 有的时候被称之为微服务。一个Service
的目标Pod
集合通常是由Label Selector
来决定的。
如下图所示,当Nginx Pod
作为客户端访问Tomcat Pod
中的应用时,IP
的变动或应用规模的缩减会导致客户端访问错误。而Pod
规模的扩容又会使得客户端无法有效的使用新增的Pod
对象,从而影响达成规模扩展之目的。为此,Kubernetes
特地设计了Service
资源来解决此类问题。
6.1.2 Service实现原理
Service
资源基于标签选择器将一组Pod
定义成一个逻辑组合,并通过自己的IP
地址和端口调度代理请求至组内的Pod
对象之上,如下图所示,它向客户端隐藏了真实的、处理用户请求的Pod
资源,使得客户端的请求看上去就像是由Service
直接处理并响应一样。
Service
对象的IP
地址也称为Cluster IP
,它位于Kubernetes
集群配置指定专用IP
地址的范围之内,是一种虚拟IP
地址,它在Service
对象创建后既保持不变,并且能够被同一集群中的Pod
资源所访问。Service
端口用于接收客户端请求并将其转发至其后端的Pod
中的相应端口之上,因此,这种代理机构也称为“端口代理”(port proxy
)或四层代理,工作于TCP/IP
协议栈的传输层。
Service
资源会通过API Server
持续监视着(watch
)标签选择器匹配到的后端Pod
对象,并实时跟踪各对象的变动,例如,IP
地址变动、对象增加或减少等。Service
并不直接链接至Pod
对象,它们之间还有一个中间层——Endpoints
资源对象,它是一个由IP
地址和端口组成的列表,这些IP
地址和端口则来自由Service
的标签选择器匹配到的Pod
资源。当创建service
对象时,其关联的Endpoints
对象会自动创建。
6.2 Service 的类型
Service 在 K8s 中有以下四种类型
- Clusterlp:默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 IP
- NodePort:在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过:NodePort 来访问该服务
- LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部负载均衡器,并将请求转发到:NodePort
- ExternalName:把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有kubernetes 1.7或更高版本的 kube-dns 才支持
6.3 Service示例
6.3.1 准备工作
6.3.1.1 创建deployment
1 | vi nginx-deployment.yml |
6.3.1.2 启动deployment
1 | kubectl apply -f nginx-deployment.yml |
6.3.1.3 访问测试
1 | curl 10.244.2.54 |
6.3.2 ClusterlP类型
类型为ClusterIP的service,这个service有一个Cluster-IP,其实就一个VIP,具体实现原理依靠kubeproxy组件,通过iptables或是ipvs实现。
注意:这种类型的service 只能在集群内访问
6.3.2.1 编辑资源清单
1 | vi cluster-iP-service.yml |
6.3.2.2 应用Service
1 | kubectl apply -f cluster-iP-service.yml |
6.3.2.3 访问测试
1 | curl 10.1.206.66:8000 |
6.3.2.4 删除Pod
删除pod让Pod的节点漂移再次访问Service节点
1 | kubectl delete pod nginx-deployment-6897679c4b-2hqmk |
6.3.2.5 访问测试
我们发现虽然pod的IP以及节点变化了但是
Service
访问IP没有任何变化
1 | curl 10.1.206.66:8000 |
6.3.3 NodePort
当我们需要集群外业务访问,那么ClusterIP就满足不了了,NodePort当然是其中的一种实现方案
6.3.3.1 编辑资源清单
1 | vi node-port-service.yml |
6.3.3.2 应用Service
1 | kubectl apply -f node-port-service.yml |
6.3.3.3 访问测试
1 | curl 10.1.137.200:8000 |
6.3.3.4 删除Pod
删除pod让Pod的节点漂移再次访问Service节点
1 | kubectl delete pod nginx-deployment-6897679c4b-2hqmk |
6.3.3.5 访问测试
我们发现虽然pod的IP以及节点变化了但是
Service
访问IP没有任何变化
1 | curl 10.1.137.200:8000 |
6.3.3.6 外网访问
NodePort的最主要功能是进行外网访问,我们是不能通过访问
8000
端口访问的,他是内网端口,外网访问需要访问映射出来的端口8000:31337/TCP
这里映射出来的nodeport
是31337
,还可以通过指定nodePort
来指定端口,要求端口必须大于30000
1 | curl 192.168.64.160:31337 |
通过浏览器访问
6.4 端口号区分
k8s 中
port
,nodePort
,targetPort
有什么区别呢
6.4.1 应用位置不同
- port:是service的的端口
- targetport:是pod也就是容器的端口
- nodeport:是容器所在宿主机的端口(实质上也是通过service暴露给了宿主机,而port却没有)
6.4.1 port
k8s集群内部服务之间访问service的入口。即clusterIP:port是service暴露在clusterIP上的端口
主要作用是集群内其他pod访问本pod的时候,需要的一个port,如nginx的pod访问mysql的pod,那么mysql的pod的service可以如下定义,由此可以这样理解,port是service的port,nginx访问service的33306
1 | apiVersion: v1 |
6.4.2 targetport
容器的端口(最终的流量端口)。targetPort是pod上的端口,从port和nodePort上来的流量,经过kube-proxy流入到后端pod的targetPort上,最后进入容器。
看上面的targetport,targetport说过是pod暴露出来的port端口,当nginx的一个请求到达service的33306端口时,service就会将此请求根据selector中的name,将请求转发到mysql-pod这个pod的3306端口上
6.4.3 nodeport
nodeport就很好理解了,它是集群外的客户访问,集群内的服务时,所访问的port,比如客户访问下面的集群中的nginx,就是这样的方式,ip:30001
外部流量访问k8s集群中service入口的一种方式(另一种方式是LoadBalancer),即nodeIP:nodePort是提供给外部流量访问k8s集群中service的入口
比如外部用户要访问k8s集群中的一个Web应用,那么我们可以配置对应service的type=NodePort,nodePort=30001。其他用户就可以通过浏览器http://node:30001访问到该web服务。而数据库等服务可能不需要被外界访问,只需被内部服务访问即可,那么我们就不必设置service的NodePort。
1 | apiVersion: v1 |
6.4.4 小结
nodeport是集群外流量访问集群内服务的端口类型,比如客户访问nginx,apache,port是集群内的pod互相通信用的端口类型,比如nginx访问mysql,而mysql是不需要让客户访问到的,最后targetport,顾名思义,目标端口,也就是最终端口,也就是pod的端口。
k8s部署安装
部署组件介绍
我们把一个有效的 Kubernetes 部署称为集群。您可以将 Kubernetes 集群可视化为两个部分:
控制平面与计算设备(或称为节点)。每个节点都是其自己的 Linux环境,并且可以是物理机或虚拟机。每个节点都运行由若干容器组成的容器集。
1. K8s 集群架构图
以下 K8s 架构图显示了 Kubernetes 集群的各部分之间的联系:
1.1 k8s控制组件
1.1.1 控制平面
K8s 集群的神经中枢
让我们从 Kubernetes 集群的神经中枢(即控制平面)开始说起。在这里,我们可以找到用于控制集群的 Kubernetes 组件以及一些有关集群状态和配置的数据。这些核心 Kubernetes 组件负责处理重要的工作,以确保容器以足够的数量和所需的资源运行。
控制平面会一直与您的计算机保持联系。集群已被配置为以特定的方式运行,而控制平面要做的就是确保万无一失。
1.1.2 kube-apiserver
K8s 集群API,如果需要与您的 Kubernetes 集群进行交互,就要通过 API
Kubernetes API 是 Kubernetes 控制平面的前端,用于处理内部和外部请求。API 服务器会确定请求是否有效,如果有效,则对其进行处理。您可以通过 REST 调用、kubectl 命令行界面或其他命令行工具(例如 kubeadm)来访问 API。
1.1.3 kube-scheduler
K8s 调度程序,您的集群是否状况良好?如果需要新的容器,要将它们放在哪里?这些是 Kubernetes 调度程序所要关注的问题。
调度程序会考虑容器集的资源需求(例如 CPU 或内存)以及集群的运行状况。随后,它会将容器集安排到适当的计算节点。
1.1.4 kube-controller-manager
K8s 控制器,控制器负责实际运行集群,而 Kubernetes 控制器管理器则是将多个控制器功能合而为一
控制器用于查询调度程序,并确保有正确数量的容器集在运行。如果有容器集停止运行,另一个控制器会发现并做出响应。控制器会将服务连接至容器集,以便让请求前往正确的端点。还有一些控制器用于创建帐户和 API 访问令牌。
1.1.5 etcd
键值存储数据库
配置数据以及有关集群状态的信息位于 etcd(一个键值存储数据库)中。etcd 采用分布式、容错设计,被视为集群的最终事实来源。
1.2 k8s运行组件
1.2.1 k8s节点
Kubernetes 集群中至少需要一个计算节点,但通常会有多个计算节点。
容器集经过调度和编排后,就会在节点上运行。如果需要扩展集群的容量,那就要添加更多的节点。
1.2.2 容器集
容器集是 Kubernetes 对象模型中最小、最简单的单元。
它代表了应用的单个实例。每个容器集都由一个容器(或一系列紧密耦合的容器)以及若干控制容器运行方式的选件组成。容器集可以连接至持久存储,以运行有状态应用。
1.2.3 容器运行时引擎
为了运行容器,每个计算节点都有一个容器运行时引擎。
比如 Docker,但 Kubernetes 也支持其他符合开源容器运动(OCI)标准的运行时,例如 rkt 和 CRI-O。
1.2.4 kubelet
每个计算节点中都包含一个 kubelet,这是一个与控制平面通信的微型应用。
kublet 可确保容器在容器集内运行。当控制平面需要在节点中执行某个操作时,kubelet 就会执行该操作。
1.2.5 kube-proxy
每个计算节点中还包含 kube-proxy,这是一个用于优化 Kubernetes 网络服务的网络代理。
kube-proxy 负责处理集群内部或外部的网络通信——靠操作系统的数据包过滤层,或者自行转发流量。
1.3 k8s 存储组件
1.3.1 持久存储
除了管理运行应用的容器外,Kubernetes 还可以管理附加在集群上的应用数据。
Kubernetes 允许用户请求存储资源,而无需了解底层存储基础架构的详细信息。持久卷是集群(而非容器集)所特有的,因此其寿命可以超过容器集。
1.3.2 容器镜像仓库
Kubernetes 所依赖的容器镜像存储于容器镜像仓库中。
这个镜像仓库可以由您自己配置的,也可以由第三方提供。
1.3.3 底层基础架构
您可以自己决定具体在哪里运行 Kubernetes。
答案可以是裸机服务器、虚拟机、公共云提供商、私有云和混合云环境。Kubernetes 的一大优势就是它可以在许多不同类型的基础架构上运行。
2. kubeadm部署k8s
2.1 kubeadm简介
kubeadm
是Kubernetes
项目自带的及集群构建工具,负责执行构建一个最小化的可用集群以及将其启动等的必要基本步骤,kubeadm
是Kubernetes
集群全生命周期的管理工具,可用于实现集群的部署、升级、降级及拆除。kubeadm
部署Kubernetes
集群是将大部分资源以pod
的方式运行,例如(kube-proxy
、kube-controller-manager
、kube-scheduler
、kube-apiserver
、flannel
)都是以pod
方式运行。
Kubeadm
仅关心如何初始化并启动集群,余下的其他操作,例如安装Kubernetes Dashboard
、监控系统、日志系统等必要的附加组件则不在其考虑范围之内,需要管理员自行部署。
Kubeadm
集成了Kubeadm init
和kubeadm join
等工具程序,其中kubeadm init
用于集群的快速初始化,其核心功能是部署Master节点的各个组件,而kubeadm join
则用于将节点快速加入到指定集群中,它们是创建Kubernetes
集群最佳实践的“快速路径”。另外,kubeadm token
可于集群构建后管理用于加入集群时使用的认证令牌(token
),而kubeadm reset
命令的功能则是删除集群构建过程中生成的文件以重置回初始状态。
2.2 kubeadm安装准备
2.2.1 环境要求
- 一台兼容的 Linux 主机,Kubernetes 项目为基于 Debian 和 Red Hat 的 Linux 发行版以及一些不提供包管理器的发行版提供通用的指令
- 每台机器 2 GB 或更多的 RAM (如果少于这个数字将会影响你应用的运行内存)
- 2 CPU 核或更多
- 集群中的所有机器的网络彼此均能相互连接(公网和内网都可以)
- 节点之中不可以有重复的主机名、MAC 地址或 product_uuid。请参见这里了解更多详细信息。
- 开启机器上的某些端口。请参见这里 了解更多详细信息。
- 禁用交换分区,为了保证 kubelet 正常工作,你 必须 禁用交换分区。
2.2.2 准备工作
下面初始化环境工作master节点和node节点都需要执行
2.2.2.1 关闭防火墙
1 | systemctl stop firewalld |
2.2.2.2 关闭selinux
1 | setenforce 0 |
2.2.2.3 关闭swap
1 | swapoff -a |
2.2.2.4 同步
1 | ntpdate 0.rhel.pool.ntp.org |
2.2.2.5 修改hostname
其他节点hostname都需要更改
1 | vi /etc/hostname |
写入
1 | yourhostname |
保存后执行以下
1 | hostname yourhostname |
查看设置后的hostname
1 | hostname |
2.2.2.6 修改host文件
在其他服务节点上都需要更改
1 | vi /etc/hosts |
2.2.2.7 host绑定
修改其他节点的hostname后,将域名绑定到hostname中保证其他节点能够通过hostname进行访问其他节点
1 | vi /etc/hosts |
尝试ping其他hostname
1 | ping k8s-node1 |
2.2.2.8 配置Iptables规则
1 | echo 'net.bridge.bridge-nf-call-iptables = 1' >> /etc/sysctl.conf |
2.3 Docker环境安装
参考前面章节的Docker安装
2.3.1 修改daemon.json
修改docker cgroup driver为systemd
根据文档CRI installation中的内容,对于使用systemd作为init system的Linux的发行版,使用systemd作为docker的cgroup driver可以确保服务器节点在资源紧张的情况更加稳定,因此这里修改各个节点上docker的cgroup driver为systemd。
1 | mkdir -p /etc/docker/ |
2.3.2 启动docker
1 | systemctl restart docker #启动docker |
2.4 安装kubeadm
master节点和所有node节点都需要执行
2.4.1 配置kubeadm安装源
在线安装kubeadm,使用阿里的源,本教程使用1.21 (很慢!)
1 | cat <<EOF > /etc/yum.repos.d/kubernetes.repo |
2.4.2 安装组件
安装
kubelat
、kubectl
、kubeadm
1 | yum install -y kubelet-1.21.0 kubeadm-1.21.0 kubectl-1.21.0 --disableexcludes=kubernetes |
2.4.3 查看服务版本
1 | rpm -aq kubelet kubectl kubeadm |
2.4.4 将kubelet
加入开机启动
将
kubelet
加入开机启动,这里刚安装完成不能直接启动。(因为目前还没有集群还没有建立)
1 | systemctl enable kubelet |
2.4.5 导入镜像
直接使用离线的镜像文件,在资料下的tar文件夹下
2.4.5.1 上传Image镜像
将资料文件夹下的 tar文件上传到centos服务中来
2.4.5.2 导入镜像
将上传的镜像导入到docker容器中来
1 | docker load < coredns.tar |
2.5 初始化Master
注意:在master节点执行
2.5.1 执行初始化命令
1 | kubeadm init \ |
执行需要花费一些时间,出现
successfully
表示初始化成功
2.5.2 创建配置文件
按照上面初始化成功提示创建配置文件
1 | mkdir -p $HOME/.kube |
2.6 添加flannel网络
2.6.1 创建flannel配置文件
创建网络,flannel.yml文件在”资料“文件夹里
1 | vi flannel.yml |
文件内容如下
1 | --- |
2.6.2 创建网络
1 | kubectl apply -f flannel.yml |
2.7 加入Node节点
在两个node节点都需要执行
2.7.1 安装kubeadm
参考《安装kubeadm》也需要在从节点执行一遍
2.7.2 加入集群
在子节点上执行加入命令
1 | kubeadm join 192.168.64.159:6443 --token i16hg0.9z9aq1px2mp78fi7 \ |
2.7.3 查看集群状态
在master节点输入命令检查集群状态,返回如下结果则集群状态正常
1 | kubectl get nodes |
重点查看STATUS内容为Ready时,则说明集群状态正常。
2.7.4 查看版本信息
查看集群客户端和服务端程序版本信息
1 | kubectl version --short=true |
2.7.5 查看集群信息
1 | kubectl cluster-info |
2.7.6 删除节点
有时节点出现故障,需要删除节点,方法如下
2.7.6.1 在master节点上执行
1 | kubectl drain <NODE-NAME> --delete-local-data --force --ignore-daemonsets |
2.7.6.2 在需要移除的节点上执行
1 | kubeadm reset |
3. 开启自动补全
- 当我们在使用kubectl命令时,如果不能用tab补全,将会非常麻烦,得把命令一个一个敲出来
- 但是配置自动补全之后就非常方便了
3.1 安装命令
安装bash-completion
1 | yum install bash-completion -y |
3.2 执行命令
执行bash_completion
1 | source /usr/share/bash-completion/bash_completion |
3.3 重新加载
重新加载kubectl completion
1 | source <(kubectl completion bash) |
3.4 永久生效
配置永久生效
1 | echo "source <(kubectl completion bash)" >> ~/.bashrc |
4. dashboard
4.1 创建配置文件
使用资料里的yml启动,官方yml下载后,需要把ClusterIP,改为 NodePort,否则无法远程访问
1 | vi dashboard.yml |
4.2 启动dashboard
1 | kubectl apply -f yml/dashboard.yml |
4.3 查看启动的端口
1 | kubectl get services --all-namespaces |
4.4 访问测试
访问
https://192.168.64.159:32428
地址进入dashboard界面,因为没有证书,访问需要输入token
4.5 获取Token
在master执行以下命令来获取Token
1 | kubectl -n kube-system describe $(kubectl -n kube-system get secret -n kube-system -o name | grep namespace) | grep token |
4.6 访问页面
输入Token后继续访问,这样就可以登录
dashboard