K8S系统(三)——K8S介绍及相关名词说明
【前言】
按照系列的前两篇文章操作,不出意外的话,K8S集群已经部署好。在正式开始后面的实操前,我们需要熟悉下K8S及其相关名词,先对其有概念,接下来的操作才更加丝滑
【K8S的起源】
谈到 Kubernetes,就绕不开 Docker。
2010 年几个搞 IT 的年轻人在旧金山使用谷歌 go 语言开发了一个创建和管理容器的工具供公司内部使用,命名为 Docker。其寓意是用 Docker 这艘船承载 container(集装箱)以封装和支撑各类应用服务。这一阶段 Docker 是一个闭源工具,其技术仅限于企业内部使用,没有引起 IT 行业的任何关注。当时 Docker 所在的公司在激烈的市场竞争下步履维艰。
2013 年 3 月公司就要坚持不下去了,以 Solomon Hykes 为首的技术团队沮丧之余,不忍让 Docker 这样的前沿容器技术付之东流,抱着破罐子破摔试一试的心态,将 Docker 在 GitHub 社区上彻底开源。开源之时使用的一句宣传标语是“Build Once, Run Everywhere”,即“一次构建,随处运行”。万万没想到,不开则已,一开惊人。这就像打开了容器领域的潘多拉魔盒,越来越多的 IT 工程师发现了 Docker 的优点,大家蜂拥而至加入 Docker 开源社区学习并作出贡献。Docker 人气迅速攀升,所在公司迅速起死回生,这项技术的传播速度之快,令早就在使用和推广容器等 IT 基础设施技术的 Google 等互联网国际大厂瞠目结舌。
随着 Docker 在容器技术开源社区站稳脚跟,并在多个场合和商业应用场景挑战了 Google 的切身利益。Google 管理层深感不妙,在 Docker 项目刚兴起的同一年就迅速祭出一剑:将内部生产验证的容器栈管理工具 MCTFY (Let Me Container That For You) 开源。另一方面,Google 还有意支持 Docker 的多个竞争对手和项目发展,但事与愿违,面对 Docker 的强势崛起和渗透,Google 的这些举措表现得毫无招架之力。战局节节败退,Google 作为财力雄厚的互联网财阀,直接开出高价有意买断 Docker。但 Docker 的技术总监兼联合创始人 Solomon Hykes 估计是一位理想主义者,雄心万丈的他对抛来的这枚橄榄枝不屑一顾。技术牛人就是这么率真、耿直!
容器战场的连番失利和收购意图被漠视,促使 Google 高层改变策略,2014 年6 月在开源社区放出了内部雪藏近十年的底层核心技术,即大规模集群管理系统 Borg,这就是 Kubernetes 的前身。IT 江湖,技术为先。如同当年 Docker 横空出世一样,Kubernetes 再一次改写了容器市场的格局。2015 年 7 月 Kubernetes 1.0 正式发布。同年 Google 宣布成立 CNCF,全称 Cloud Native Computing Foundation(云原生计算基金会),建立以 Kubernetes 为中心的云原生解决方案开放生态体系。
2017 年 Docker 将容器运行时部分 Containerd 捐献给 CNCF 社区,同年 10 月又宣布将 Docker 企业版内置到 Kubernetes 项目,持续了几年的容器编排之争终于落下帷幕。
【K8S简介】
Kubernetes是一个开源容器编排引擎,kubernetes是希腊语『舵手』的意思,简称K8s,是用8代替名字中间的8个字符“ubernete”而成的缩写,它是Google大规模集群管理系统Borg的开源版本,深受公司内部Borg和Omega项目的影响。Kubernetes 已然成为事实上的容器编排标准,是运维、开发人员使用最流行的微服务、DevOps、CI/CD 云原生底座,其将超过15年的Google在大规模生产工作负载方面的经验与社区中最好的想法和实践相结合,其于2014年9月发布第一个版本,2015年7月发布第一个正式版本。
K8S的目标是让部署容器化的应用简单并且高效(powerful),提供了应用部署,规划,更新,维护的一种机制。在Kubernetes中,我们可以创建多个容器,每个容器里面运行一个应用实例,然后通过内置的负载均衡策略,实现对这一组应用实例的管理、发现、访问,而这些细节都不需要运维人员去进行复杂的手工配置和处理。
K8S的出现不仅主宰了容器编排的市场,更改变了过去的运维方式,不仅将开发与运维之间边界变得更加模糊,而且让DevOps这一角色变得更加清晰,每一个软件工程师都可以通过Kubernetes来定义服务之间的拓扑关系、线上的节点个数、资源使用量并且能够快速实现水平扩容、蓝绿部署等在过去复杂的运维操作。
【K8S特点】
自我修复:一旦某个容器出现异常,能够迅速启动新的容器
弹性伸缩:按需自动对集群中正在运行的容器数量进行调整
服务发现:通过自动发现的形式找到当前 Service 所依赖的新生服务
负载均衡:如果 Service 启动了多个容器,能够自动实现请求的负载均衡
版本回退:若发现新发布的程序版本有问题,可以立即回退到原来的版本
存储编排:可以根据容器自身的需求自动创建存储卷,支持本地存储、公共云提供商等
【K8S组件】
Cluster
Cluster 是计算、存储和网络资源的集合,Kubernetes 利用这些资源运行各种基于容器的应用
Master
集群控制节点,每个集群需要至少一个master节点负责集群的管控。Master 是 Cluster 的大脑,它的主要职责是调度,即决定将应用放在哪里运行。Master运行Linux操作系统,可以是物理机或者虚拟机。为了实现高可用,可以运行多个 Master
Node
Node 的职责是运行容器应用。Node 由 Master 管理,Node 负责监控并汇报容器的状态,并根据 Master 的要求管理容器的生命周期。Node 运行在 Linux 操作系统,可以是物理机或者是虚拟机。工作负载节点,由master分配容器到这些node工作节点上,然后node节点上的docker负责容器的运行
Pod
Pod是kubernetes的最小控制单元,容器都是运行在pod中的,一个pod中可以有1个或者多个容器。Pod 中的容器会作为一个整体被 Master 调度到一个 Node 上运行。在k8s集群中, 一般不直接创建Pod,而是通过控制器和模版配置来管理和调度
Kubernetes 引入 Pod 主要基于下面两个目的:
可管理性
有些容器天生就是需要紧密联系,一起工作。Pod 提供了比容器更高层次的抽象,将它们封装到一个部署单元中。Kubernetes 以 Pod 为最小单位进行调度、扩展、共享资源、管理生命周期
通信和资源共享
Pod 中的所有容器使用同一个网络 namespace,即相同的 IP 地址和 Port 空间。它们可以直接用 localhost 通信。同样的,这些容器可以共享存储,当 Kubernetes 挂载 volume 到 Pod,本质上是将 volume 挂载到 Pod 中的每一个容器
Master组件
Master组件提供集群的管理控制中心,可以在集群中任何节点上运行。但是为了简单起见,通常在一台VM/机器上启动所有Master组件,并且不会在此VM/机器上运行用户容器
kube-apiserver
所有服务访问的统一入口
kube-apiserver用于暴露Kubernetes API,使用的是RESTAPI接口,也是唯一一个与 etcd集群通信的组件。kube-apiserver是所有服务访问的唯一入口,任何的资源请求/调用操作都是通过kube-apiserver提供的接口进行,提供了认证、授权、访问控制、API注册和发现等机制,其他所有组件都必须通过它提供的API来操作资源数据,通过对相关的资源数据“全量查询”+“变化监听”,这些组件可以很“实时”地完成相关的业务功能
etcd
etcd是Kubernetes提供默认的存储系统,保存所有集群数据,使用时需要为etcd数据提供备份计划。
kube-controller-manager
运行管理控制器,维持副本期望数量。负责维护集群的状态,比如副本期望数量、故障检测、自动扩展、滚动更新等。集群中处理常规任务的后台线程。逻辑上,每个控制器是一个单独的进程,但为了降低复杂性,它们都被编译成单个二进制文件,并在单个进程中运行。生命周期管理。相比之下,APIServer负责接收用户的请求,并完成集群内资源的“增删改”,而controller manager系统中扮演的角色是在一旁默默地管控这些资源,确保它们永远保持在用户所预期的状态
这些控制器包括:
Service控制器
节点(Node)控制器
路由(Route)控制器
卷(Volume)控制器
副本(Replication)控制器:负责维护系统中每个副本中的pod
端点(Endpoints)控制器:填充Endpoints对象(即连接Services&Pods)
Service Account和Token控制器:为新的Namespace创建默认帐户访问API Token
kube-scheduler
负责选择合适的节点进行任务分配。它的作用是根据特定的调度算法将pod调度到指定的工作节点Node上,这一过程通常被称为绑定(bind)
kubectl
直接和容器引擎交互实现容器的生命周期管理
cloud-controller-manager
云控制器管理器。云控制器管理器负责与底层云提供商的平台交互。云控制器管理器是Kubernetes版本1.6中引入的,还是Alpha的功能。云控制器管理器仅运行云提供商特定的(controller loops)控制器循环。可以通过将--cloud-providerflag设置为external启动kube-controller-manager ,来禁用控制器循环
Node组件
节点组件运行在Node,提供Kubernetes运行时环境,以及维护Pod。Worker 节点实现就相对比较简单了,它主要由 kubelet 和 kube-proxy 两部分组成
kubelet
kubelet是工作节点执行操作的 agent,负责具体的容器生命周期管理,根据从数据库中获取的信息来管理容器,并上报 pod 运行状态等。负责维护本节点上 Pod 任务的生命周期,通过控制 Docker 引擎来创建、启动、停止和删除,同事定时上报节点状态信息到 API Server
kube-proxy
负责写入规则到IPVS来实现服务映射访问。kube-proxy在每个工作节点上都有一个,是一个简单的网络访问代理,同时也是一个 Load Balancer。它负责将访问到某个服务的请求具体分配给工作节点上同一类标签的 Pod。kube-proxy 实质就是通过操作防火墙规则(iptables或者ipvs)来实现 Pod 的映射。负责为 Service 提供 cluster 内部的服务发现和负载均衡
cni
容器运行时。容器运行环境是负责运行容器的软件,Kubernetes 支持多个容器运行环境: Docker、 containerd、cri-o、 rktlet 以及任何实现 Kubernetes CRI(容器运行环境接口)
推荐组件
dashboard 用户界面,给K8S集群提供一个B/S结构的访问界面
CoreDNS 为集群中的SVC创建一个域名到IP的对应关系解析
IngressController 官方只能够实现四层的网络代理,而 Ingress 可以实现七层的代理
Federation 提供可以跨集群中心多K8S统一管理功能
Prometheus 提供K8S集群的服务即系统监控
ELK 提供K8S集群日志统一分析介入平台
Fluentd 负责保存容器日志,搜索/查看日志
Flannel 容器网络通信方案
K8S组件调用流程
以部署一个nginx服务来说明kubernetes系统各个组件调用关系:
首先要明确,一旦kubernetes环境启动之后,master和node都会将自身的信息存储到etcd数据库中
一个nginx服务的安装请求会首先被发送到master节点的apiServer组件
apiServer组件会调用scheduler组件来决定到底应该把这个服务安装到哪个node节点上
在此时,它会从etcd中读取各个node节点的信息,然后按照一定的算法进行选择,并将结果告知apiServer
apiServer调用controller-manager去调度Node节点安装nginx服务
kubelet接收到指令后,会通知cri,然后由cri来启动一个nginx的pod
pod是kubernetes的最小操作单元,容器必须跑在pod中至此,
一个nginx服务就运行了,如果需要访问nginx,就需要通过kube-proxy来对pod产生访问的代理
K8S技术生态全景图
【K8S资源对象】
Pod
使用 Docker 时,若在一台主机上创建两个容器 A、B,容器之间通过Namespace 进行隔离,容器 A、B 都有自己的网络空间、IP 地址等等。如果容器 A 和 B 之间需要交换数据,Docker 允许我们将容器端口暴露给主机,通过端口映射到主机让两个容器进行通讯,当然也可以通过共享网络栈等方式进行容器之间的通讯。但这些配置比较繁琐,从另一个角度考虑,有些容器就应该在一起,并且它们之间应该能够见面,也就是通过 localhost 的方式互相访问。但是如果采用标准容器方案无法实现,除非把两个不同的进程封装到同一个容器中,或者是容器 A 采用容器 B 的网络栈,但是这样会存在安全方面的隐患,因此,Kubernetes 提出了一个新的概念,叫做 Pod。
从上图可以,Pod 包含一个或多个容器,所以 Pod 有时也称为容器组,是 Kubernetes 资源管理的最小单位。每个 Pod 可以包含上图的容器 A、B,以及一个特殊的 Pause 容器。Pause 容器是 Pod 启动时第一个启动的容器,其他容器共享 Pause 容器的网络栈和 Volume 挂载卷。
- 公用网络栈 Pod 中的容器没有自己独立的 IP 地址,它们使用的是 Pause 容器的 IP 地址,即它们之间的访问可以通过 localhost 的方式进行访问,也就意味着在同一个 Pod 中,不同容器之间的端口不能冲突。
- 公用 Volume 挂载卷,也就是说如果 Pause 容器挂载了一个网络存储,那么 Pod 中的其他容器都可以访问这个网络存储。
Kubernetes 提供了多种部署 Pod 的方式,主要分为两种:
- 自主式 Pod:
即在 Yaml 中指定 Kind=Pod 的方式。 - 控制器管理 Pod:
控制器名称 | 描述 |
---|---|
Replication Controller (RC) | 用于确保容器应用的副本数始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的Pod来代替;而异常多出来的容器也会自动回收。 |
ReplicaSet | 与 RC 类似,支持支持集合式的 Selector。API 和 Kind 类型与 RC 不同: apiVersion: extensions/v1beat1 Kind: ReplicaSet |
Deployement | 为 Pod 和 RS 提供了一个声明式定义方法,用来替代以前的 RC,方便管理应用,提供了 Pod 的滚动升级和回滚特性。 |
Horizontal Pod Autoscaling (HPA) | 是一种资源对象,支持 Pod 自动横向扩容。实现原理为:通过追踪分析所有控制 Pod 的负载变化情况,来确定是否需要针对性地调整目标 Pod 的副本数。 |
StatefulSet | 解决有状态服务的问题(相对于无状态的 Deployement 和 ReplicaSet),其应用场景包括稳定的持久化存储、稳定的网络标志、有序部署、有序扩展和有序收缩等。 |
DaemonSet | 确保全部或部分 Node 上运行一个 Pod 副本。当有 Node 加入集群时,也会为新 Node 创建一个新的 Pod。当有 Node 从集群移除时,回收 Pod。删除 DaemonSet 将删除它所创建的所有 Pod。 |
Job | 负责批处理任务,即仅执行一次的任务,它确保批处理任务的一个或者多个 Pod 成功运行。 |
CronJob | 管理基于时间的Job,即: l 在指定时间点只运行一次。 l 周期性地在给定时间点运行。 |
Service
假设 Kubernetes 集群中运行了好多 Pod,Kubernetes 在创建每个 Pod 时会为每个 Pod 分配一个虚拟的 Pod IP 地址,Pod IP 是一个虚拟的二层网络地址。
集群之间不同机器之间 Pod 的通讯,其真实的 TCP/IP 流量是通过Node 节点所在的物理网卡流出的(Node IP)。由于Pod IP 是 Kubernetes 集群内部的一些私有 IP 地址,因此 Kubernetes 集群内部的程序才可以访问 Pod,Kubernetes 集群之外的程序没有办法访问。然而我们部署的许多应用都需要提供给外部客户端访问,因此,可以通过kubernetes 的服务发现(Service)将这些服务暴露给客户端,然后客户端就可以通过 IP+Port 的方式访问多个 Pod。
为什么说是多个 Pod 呢?因为 Service 为我们提供了复杂的均衡机制。例如我们通过 Deployment 部署了一个 Tomcat,replicas 设置为 3,Service 提供了多种负载均衡策略,针对不同的请求路由到不同的 Pod 上
常见服务发布方式:
ClusrterIP
当发布服务时,Kubernetes 会为服务默认分配一个虚拟的 IP,即 ClusterIP,这也是 Service 默认的类型。ClusterIP 更像是一个“伪造”的IP 网络,原因有以下几点:
- ClusterIP 仅仅作用于 Kubernetes Service 这个对象,并由 Kubernetes管理和分配 IP 地址(来源于 ClusterIP 地址池)。
- ClusterIP 无法被 Ping,因为没有一个“实体网络对象”来响应。
- ClusterIP 只能结合 Service Port 组成一个具体的通讯端口,单独的ClusterIP 不具备 TCP/IP 通讯的基础,它们属于 Kubernetes 集群这个封闭的空间,集群之外的节点若要访问这个通讯端口,则需要做一些额外的配置。
- 在 Kubernetes 集群之外,Node IP 网、Pod IP 网与 ClusterIP网之间的通讯,采用的是 Kubernetes 自己设计的一种特殊路由规则,与我们所熟知的 IP 路由有很大的不同。
NodePort
根据上述的分析和总结,我们知道 Service 的ClusterIP 属于 Kubernetes 集群内部的地址,无法在集群外部直接使用这个地址。
那么矛盾来了,实际上平常开发的许多业务肯定有一部分服务是要提供给Kubernetes 集群外部的应用或者让用户来访问的,典型的就是 Web 端的服务模块,因此在发布这些 Service 时,可以采用 NodePort 的方式。
LoadBalancer
但 NodePort 还没有完全解决外部访问 Service 的所有问题,比如负载均衡问题,假如我们的集群中有 10 个 Node,则此时最好设一个负载均衡器,外部的请求只需访问此负载均衡器的 IP 地址,由负载均衡器负责转发流量到后面某个 Node 的 NodePort 上。 LoadBalancer 组件独立于 Kubernetes 集群之外,通常是一个硬件的负载均衡器,或者是以软件方式实现的,例如 HAProxy 或者 Nginx。我们自己的服务器上安装k8s是没有llb的,k8s的LoadBalancer类型的Service依赖于外部的云(如谷歌云,亚马逊云)提供的,Load Balancer Metallb的作用就是通过k8s原生的方式提供LB类型的Service支持,开箱即用。可自行安装Metallb实现LoadBalancer
ExternalName
表示把集群外部的服务引入到集群内部中来,即实现了集群内部pod和集群外部服务的通信
存储
对于服务,我们经常将其分为两大类:有状态服务、无状态服务。
有状态服务常见的例如调度器、Apache 等。对于 Docker 来说,其更适应于无状态服务,但是 Kubernetes 的目标是作为未来基础设施的平台,其必须要攻克有状态服务,那有状态服务有些数据需要持久化,需要保存起来。因此Kubernetes 引入了多种存储:
- ConfigMap:专门用来存储配置文件,就像配置文件中心
- Secret:存储一些需要加密的数据
- Volume:用来存储一些数据
- PV:Persistent Volume,一个动态的存储
ConfigMap
ConfigMap 功能在 Kubernetes 1.2 版本中引入,许多应用程序会从配置文件、命令行参数或者环境变量中读取配置信息。
ConfigMap API 提供了向容器中注入配置信息的机制,ConfigMap 可以用来保存单个属性,也可以保存整个配置文件或者 JSON 二进制对象。
Secret
Secret 解决了密码、token、密钥等敏感数据的配置,而不需要把这些敏感数据暴露到镜像或者 Pod Spec 中。
Secret 可以作为 Volume 或者环境变量的方式使用。
Secret 有三种类型:
- ServiceAccount:用来访问 Kubernetes API,由 Kubernetes 自动创建,并且会自动挂载到 Pod 的 /run/secrets/http://kubernetes.io/serviceaccount目录中
- Opaque:base64 编码格式的 Secret,用来存储密码、密钥等
- http://kubernetes.io/dockerconfigjson:用来存储私有 docker registry 的认证信息
Volume
容器磁盘上的文件生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。
首先,当容器崩溃时,Kubelet 会重启它,但是容器中的文件将丢失,因为容器会以干净的状态(镜像最初的状态)重新启动。
其次,在 Pod 中同时运行多个容器时,这些容器之间通常需要共享文件。Kubernetes 中的 Volume 抽象就很好地解决了这些问题。目前,Kubernetes支持多种类型的 Volume,例如 GlusterFs、Ceph 等先进的分布式文件系统。
Volume 的使用也比较简单,在大多数情况下,我们先在 Pod 上声明一个Volume,然后在容器里引用该 Volume 并 Mount 到容器里的某个目录上即可。
Persistent Volume
之前我们提到的 Volume 是定义在 Pod 上的,属于“计算资源”的一部分,而实际上,“网络存储”是相对独立于“计算资源”而存在的一种实体资源。
比如在使用虚拟机的情况下,我们通常会先定义一个网络存储,然后从中划出一个“网盘”并挂在到虚拟机上。Persistent Volume(简称 PV)和与之关联的Persistent Volume Clain(简称 PVC)也起到了类似的作用。
PV 可以理解成 Kubernetes 集群中某个网络存储对应的一块存储,它与Volume 很类似,但有以下区别:
- PV 只能是网络存储,不属于任何 Node,但可以在每个 Node 上访问。
- PV 并不是定义在 Pod 上的,而是独立于 Pod 之外定义。
- PV 目前只有一种类型:GCE Persistent Disks、NFS、RBD、iSCSCI、AWS ElasticBlockStore、CluserFS 等。
k8s 的声明模式
了解了上述资源对象后,我们接下来就是如何操作这些对象。命令模式和声明模式是 Kubernetes 操作资源对象的一个重要基础,只有先了解了它们之间的区别才能理解我们为什么要用声明的方式来创建资源。举例来说,假设要完成一个任务,这个任务是要运行 5 个 nginx 副本,在命令模式下需要运行 5 次 docker run,生成 5 个相同的副本,简言之,命令模式要求对方完全按照特定的指示来做,它自己没有任何自主的想法。而声明模式把以上所有步骤全部省掉,直接把需要做的事情在一个文件里声明,在文件里设置一个参数,最终运行 5 个 Nginx 副本。
那么问题来了,如果在运行这 5 个 Nginx 副本中间发生了错误,会出现什么样的情况呢?对于命令模式来说,会产生不可预期的后果,又或者需要额外的干预,比如之前的命令发生了错误,只运行了 3 个副本,我们必须查看一下之前的命令是成功还是失败、有几个副本运行。如果之前运行了 3 个,那我们还需要再发出 2 次命令,运行 2 次。但是在声明模式下,我们把任务交代给谁,谁就负责来查看现在到底有几个副本,如果运行的副本数量不是 5 个,它就会一直试图去恢复 5 个副本,直至成功。
那如果这个命令重复运行了 2 次,情况又如何?对于命令模式来说,如果运行 2 次,就会产生 2 倍的副本;而如果声明模式运行了 2 次,因为声明的是一件事情,除非第二次修改了命令,否则只要命令是一样的,那么就只会生成 5 个副本,副本数量不会增加。这也是命令模式和声明模式最关键的区别所在。
参考链接:
https://baike.baidu.com/item/kubernetes/22864162?fr=aladdin
https://blog.csdn.net/qq_25854057/article/details/122223620
https://zhuanlan.zhihu.com/p/403308354