文章目录
- 前言
- 一、介绍
- 二、容器与Kubernetes
-
- 1.容器
- 2.Kubernetes
- 3.使用Kubectl命令操作集群
-
- [1. kubectl](#1. kubectl)
- [2. 基础命令](#2. 基础命令)
- [3. 部署命令](#3. 部署命令)
- [4. 集群管理命令](#4. 集群管理命令)
- [5. 故障诊断和调试命令](#5. 故障诊断和调试命令)
- [6. 高级命令](#6. 高级命令)
- [7. 设置命令](#7. 设置命令)
- [8. 其他命令](#8. 其他命令)
- 三、Pod、Label和Namespace
-
- 1.Pod:Kubernetes中的最小调度对象
- [2.Liveness Probe:健康检查机制](#2.Liveness Probe:健康检查机制)
- 3.Label:组织Pod的利器
- 4.Namespace:资源分组
- 四、Pod的编排与调度
- 五、配置管理
- 六、Kubernetes网络
-
- 1.容器网络
- 2.Service
- 3.Ingress
- [4.就绪探针(Readiness Probe)](#4.就绪探针(Readiness Probe))
- 5.网络策略(NetworkPolicy)
- 七、持久化存储
- 八、认证与授权
- 九、弹性伸缩
前言
Kubernetes
Kubernetes 是一个开源的容器编排引擎,用来对容器化应用进行自动化部署、扩缩和管理。此开源项目由云原生计算基金会(CNCF)托管。
一、介绍
1.概述
Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,方便进行声明式配置和自动化。Kubernetes 拥有一个庞大且快速增长的生态系统,其服务、支持和工具的使用范围广泛。
帮助了解 Kubernetes 系统的各个部分以及 Kubernetes 用来表示集群的抽象概念, 并帮助你更深入地理解 Kubernetes 是如何工作的。
Kubernetes是一个开源的容器编排部署管理平台,用于管理云平台中多个主机上的容器化应用。Kubernetes的目标是让部署容器化的应用简单并且高效,Kubernetes提供了应用部署、规划、更新、维护的一种机制。
对应用开发者而言,可以把Kubernetes看成一个集群操作系统。Kubernetes提供服务发现、伸缩、负载均衡、自愈甚至选举等功能,让开发者从基础设施相关配置等解脱出来。
可以通过管理控制台、Kubectl命令行、Kubernetes API,使用云容器引擎所提供的Kubernetes托管服务。
2.基本概念
云容器引擎(Cloud Container Engine,简称CCE)提供高度可扩展的、高性能的企业级Kubernetes集群。借助云容器引擎,您可以在云上轻松部署、管理和扩展容器化应用程序。
云容器引擎提供Kubernetes原生API,支持使用kubectl,且提供图形化控制台,让您能够拥有完整的端到端使用体验,使用云容器引擎前,建议您先了解相关的基本概念。
-
集群(Cluster)
集群指容器运行所需要的云资源组合,关联了若干云服务器节点、负载均衡等云资源。您可以理解为集群是"同一个子网中一个或多个弹性云服务器(又称:节点)"通过相关技术组合而成的计算机群体,为容器运行提供了计算资源池。
-
节点(Node)
在Kubernetes集群中,节点是运行容器化应用程序的工作主机,它们可以是物理服务器或虚拟机,并通过网络连接形成集群。每个节点都安装了必要的组件,如容器运行时(如Docker)和kubelet(用于管理容器)。节点资源被Kubernetes统一调度和管理,用于部署和运行实例(Pod)(容器的最小部署单元),是集群的基础运行环境,保障应用程序的高可用性和弹性扩展。
关于节点的更多操作请参见创建节点。
-
节点池(NodePool)
在Kubernetes集群中,节点池是一组具有相同配置和属性的节点集合。这些节点通常具有相同的硬件规格、操作系统版本和Kubernetes节点配置。节点池可以方便地实现集群资源的批量管理与扩展。您可以根据需求创建不同规模和配置的节点池,以满足不同应用程序的负载调度需求,确保资源高效利用。同时,节点池支持弹性伸缩,可根据工作负载自动调整节点数量,从而优化资源利用效率,提升集群的灵活性和可扩展性。
关于节点池的更多操作请参见创建节点池。
-
虚拟私有云(VPC)
虚拟私有云是通过逻辑方式进行网络隔离,提供安全、隔离的网络环境。您可以在VPC中定义与传统网络无差别的虚拟网络,同时提供弹性IP、安全组等高级网络服务。
通过VPC,CCE集群可以实现节点与容器网络的安全隔离,同时支持弹性公网IP和带宽配置,满足集群的灵活扩展需求。
关于虚拟私有云的更多操作请参见创建虚拟私有云和子网。
-
安全组
安全组是一个逻辑上的分组,为同一个VPC内具有相同安全保护需求并相互信任的弹性云服务器提供访问策略。安全组创建后,用户可以在安全组中定义各种访问规则,当弹性云服务器加入该安全组后,即受到这些访问规则的保护。
关于安全组的更多操作请参见添加安全组规则。
集群、虚拟私有云、安全组和节点的关系如图1,同一个Region下可以有多个虚拟私有云(VPC)。虚拟私有云由一个个子网组成,子网与子网之间的网络交互通过子网网关完成,而集群就是建立在某个子网中。因此,存在以下三种场景:
- 不同集群可以创建在不同的虚拟私有云中。
- 不同集群可以创建在同一个子网中。
- 不同集群可以创建在不同的子网中。
图1 集群、VPC、安全组和节点的关系
- 实例(Pod)
在Kubernetes中,Pod是部署应用或服务的最小基本单位。一个Pod可以封装一个或多个应用容器,多个容器通常共享存储和网络资源。每个Pod都有一个独立的网络IP地址,这使得 Pod内的容器可以相互通信,并且可以被集群内的其他Pod访问。同时,Kubernetes提供多种策略选项来管理容器的运行方式,包括重启策略、资源请求和限制、生命周期钩子等。
图2 实例(Pod)
- 容器(Container)
一个通过Docker镜像创建的运行实例被称为容器。在一个节点(宿主机)上可以运行多个容器。容器的实质是进程,但与直接在宿主机上执行的进程不同,容器进程运行于属于自己的独立的命名空间中。这些命名空间提供了一种隔离机制,使得每个容器都有自己的文件系统、网络接口、进程 ID 等,从而实现了操作系统级别的隔离。
图3 实例Pod、容器Container、节点Node的关系
-
工作负载
工作负载是在Kubernetes上运行的应用程序。无论您的工作负载是单个组件还是协同工作的多个组件,您都可以在Kubernetes上的一组Pod中运行它。在Kubernetes中,工作负载是对一组Pod的抽象模型,用于描述业务的运行载体,包括Deployment、StatefulSet、DaemonSet、Job、CronJob等多种类型。
- 无状态工作负载:即Kubernetes中的"Deployment",无状态工作负载支持弹性伸缩与滚动升级,适用于实例完全独立、功能相同的场景,如Web服务器(NGINX)、博客平台(WordPress)等。
- 有状态工作负载:即Kubernetes中的"StatefulSet",有状态工作负载支持实例有序部署和删除,每个Pod都有一个持久的标识符,并且可以相互通信,适用于需要持久化存储和实例间相互通信的应用,如分布式键值存储系统(ETCD)、高可用的数据库(MySQL-HA)等。
- 创建守护进程集:即Kubernetes中的"DaemonSet",守护进程集确保全部(或者某些)节点都运行一个Pod实例,支持会自动将Pod部署到新加入集群的节点上,它适用于需要在每个节点上运行的服务,如日志收集(fluentd)、监控代理(Prometheus Node Exporter)等。
- 普通任务:即Kubernetes中的"Job",普通任务是一次性运行的任务,确保指定数量的Pod成功完成执行。适用于需要在集群中执行一次性任务的场景,如数据备份、批量处理等。
- 定时任务:即Kubernetes中的"CronJob",定时任务是按照指定时间周期运行的任务。适用于需要定期执行的任务,如定时数据同步、定时生成报告等。
关于工作负载的更多操作请参见创建工作负载。
图4 工作负载与Pod的关系
- 镜像(Image)
镜像(Image)是一个模板,是容器应用打包的标准格式,用于创建容器。或者说,镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。在部署容器化应用时可以指定镜像,镜像可以来自于 Docker Hub、容器镜像服务或者用户的私有镜像仓库。例如,开发者可以创建一个包含特定应用程序及其所有依赖的镜像,确保在任何环境中都能以相同的方式运行。
镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
关于镜像的更多操作请参见上传镜像。
图5 镜像、容器、工作负载的关系
-
命名空间(Namespace)
命名空间是对一组资源和对象的抽象整合,允许您将相关的资源和对象(如Pods、Services、Deployments等)组织在一起,形成一个逻辑上的分组。不同命名空间中的数据彼此隔离,但它们仍可以共享同一个集群的基础资源(如CPU、内存、存储等)。您可以在不同的命名空间中部署不同的环境,例如开发环境、测试环境和生产环境,这样可以确保环境之间的隔离,同时便于管理和维护。
在Kubernetes中,大部分资源对象都是命名空间级别的,如Pods、Services、Replication Controllers和Deployments等,这意味着它们属于某一个命名空间(默认是default)。但仍有一部分资源是集群级别的,例如Node、PersistentVolumes等,它们不属于任何命名空间,为所有命名空间中的资源提供服务。
关于命名空间的更多操作请参见创建命名空间。
-
服务(Service)
在Kubernetes中,Service用于定义Pods的访问策略。Service类型的取值以及行为如下:
-
ClusterIP:这是默认的Service类型,它会在集群内部为Service分配一个唯一的IP地址。这个IP地址只在集群内部可用,外部无法直接访问。ClusterIP类型的Service通常用于集群内部的通信。
-
NodePort:NodePort类型的Service会在集群的所有节点上打开一个静态端口(NodePort),通过这个端口可以访问Service。这个类型的Service允许外部流量通过节点绑定的弹性IP和指定的端口访问Service,从而实现对外提供服务。
-
LoadBalancer:利用云服务提供商的负载均衡器,将Service暴露给外部网络。外部的负载均衡器可以将流量转发到集群中NodePort服务和ClusterIP服务。
-
DNAT:使用DNAT网关为集群节点提供网络地址转换服务,使多个节点可以共享使用弹性IP。与直接为节点绑定弹性IP的方式相比,DNAT方式增强了可靠性,弹性IP无需与单个节点绑定,任何节点状态的异常不影响其访问。
-
关于服务的更多操作请参见服务概述。
-
路由(Ingress)
Kubernetes集群中的Ingress用于管理外部访问集群内服务的规则。它提供基于域名、路径的路由功能,支持负载均衡、TLS终止和SSL证书管理。通过Ingress,可以将多个服务的流量统一管理,对外暴露一个入口点,简化网络配置,提升集群的可扩展性和安全性,是实现微服务架构中服务暴露的重要方式。
关于路由的更多操作请参见路由概述。
-
网络策略(NetworkPolicy)
Kubernetes集群中的NetworkPolicy用于定义Pod之间的网络通信策略。它通过指定允许或拒绝的流量规则,控制Pod之间的访问关系,增强集群的网络安全。NetworkPolicy支持基于Pod标签、IP地址和端口的规则配置,能够限制入站和出站流量,防止未经授权的通信,从而保护集群内部服务的安全性。
关于网络策略的更多操作请参见配置网络策略(NetworkPolicy)限制Pod访问的对象。
-
配置项(ConfigMap)
Kubernetes集群中的ConfigMap用于存储配置数据,它可以将配置信息(如配置文件、命令行参数等)从Pod中分离出来,以键值对的形式存储。通过ConfigMap,用户可以轻松地在多个Pod之间共享和更新配置,而无需重新构建镜像。它支持多种数据格式(如YAML、JSON),方便灵活地管理应用程序的配置,确保配置的可维护性和可扩展性。
关于配置项的更多操作请参见创建配置项。
-
密钥(Secret)
Kubernetes集群中的Secret用于存储敏感信息(如密码、密钥、证书等),它以加密形式存储数据,确保敏感信息的安全性。Secret可以通过挂载或环境变量的形式在Pod中使用,也可以用于存储集群内部的认证信息。通过Secret,用户可以将敏感信息与应用程序代码分离,降低泄露风险,同时实现对敏感数据的集中管理和动态更新,保障集群的安全性和灵活性
关于密钥的更多操作请参见创建密钥。
-
标签(Label)
在Kubernetes中,标签(Label)是附加到资源对象(如Pod、Service、Deployment等)上的键值对。标签的主要作用是为这些对象提供额外的、语义化的元数据,以便于用户和系统能够更容易地识别、组织和管理资源。
-
标签选择器(LabelSelector)
在Kubernetes中,标签选择器是一种强大的机制,极大地简化了资源管理和操作的复杂性。它允许用户根据资源对象上的标签来选择和分组这些对象,可以对选中的资源组执行批量操作,如流量分配、扩缩容、更新配置、监控状态等。
-
注解(Annotation)
Annotation与Label类似,也使用key/value键值对的形式进行定义。但它们在用途和约束上有所不同。
Label更多地用于资源的选择和管理,具有严格的命名规则,它定义的是Kubernetes对象的元数据(Metadata),并且用于Label Selector为用户提供选择资源的能力。
Annotation则是用户任意定义的"附加"信息,Kubernetes系统不会直接使用这些注解来控制资源的行为,但它存储的额外信息可以被外部工具获取,用于扩展Kubernetes的功能。
-
存储卷(PersistentVolume)
PersistentVolume(PV)是集群的一块存储资源,可以是本地磁盘或网络存储。它具有独立于Pod的生命周期,这意味着即使使用PV的Pod被删除,PV中的数据也不会丢失。
-
存储声明(PersistentVolumeClaim)
PersistentVolumeClaim (PVC) 用户对存储资源PV的请求,它指定了存储的大小、访问模式等要求,Kubernetes会自动匹配合适的PV来满足这些要求。
PV和PVC之间的关系类似于Pod和Node的关系:Pod消耗Node资源,而PVC消耗PV资源。
-
工作负载弹性伸缩(HPA)
Horizontal Pod Autoscaling,简称HPA,是Kubernetes中实现POD水平自动伸缩的功能。HPA允许Kubernetes集群根据CPU使用率、内存使用率或其他选择的指标自动增加或减少 Pod 的数量。您可以设置目标指标的阈值,HPA会根据这些阈值自动调整Pod的数量,以保持应用的性能。
关于工作负载弹性伸缩的更多操作请参见创建HPA策略。
-
节点弹性伸缩(Cluster Autoscale)
Kubernetes集群中的节点弹性伸缩是根据集群负载动态调整节点数量的功能。当业务负载增加时,自动添加新节点以扩展资源;负载降低时,自动移除多余节点以节省成本。它可以结合集群的资源使用情况(如 CPU、内存利用率)和预设规则,实现节点的自动增减,确保集群资源与业务需求相匹配,提升资源利用效率和集群的灵活性。
关于节点弹性伸缩的更多操作请参见创建节点弹性策略。
-
亲和性与反亲和性
在应用没有容器化之前,原先一个虚机上会装多个组件,进程间会有通信。但在做容器化拆分的时候,往往直接按进程拆分容器,比如业务进程一个容器,监控日志处理或者本地数据放在另一个容器,并且有独立的生命周期。这时如果分布在网络中两个较远的点,请求经过多次转发,性能会很差。
-
亲和性:可以实现就近部署,增强网络能力实现通信上的就近路由,减少网络的损耗。如:应用A与应用B两个应用频繁交互,所以有必要利用亲和性让两个应用尽可能地靠近,甚至在一个节点上,以减少因网络通信而带来的性能损耗。
-
反亲和性:主要是出于高可靠性考虑,尽量分散实例,某个节点故障的时候,对应用的影响只是N分之一或者只是一个实例。如:当应用采用多副本部署时,有必要采用反亲和性让各个应用实例打散分布在各个节点上,以提高可用性。
-
关于亲和性与反亲和性的更多操作请参见工作负载调度策略概述。
-
资源配额(Resource Quota)
允许管理员为命名空间设置资源使用总和的限制,例如CPU、内存、磁盘空间和网络带宽等。
-
资源限制(Limit Range)
默认情况下,K8s中所有容器都没有任何CPU和内存限制。LimitRange用来给命名空间中的对象(如Pod等)增加资源限制。
LimitRange对象提供的限制能够实现以下能力:
-
在一个命名空间中对每个Pod或容器的最小/最大资源使用量进行限制。
-
在一个命名空间中对每个PersistentVolumeClaim能申请的最小/最大存储空间进行限制。
-
在一个命名空间中对一种资源的申请值和限制值的比值进行控制。
-
设置一个命名空间中对计算资源的默认申请/限制值,并且自动在运行时注入到多个容器中。
-
-
环境变量
环境变量是指容器运行环境中设定的一个变量,您可以在创建容器模板时设定不超过30个的环境变量。环境变量可以在工作负载部署后修改,为工作负载提供了极大的灵活性。
在CCE中设置环境变量与Dockerfile中的"ENV"效果相同。
-
模板(Chart)
Kubernetes集群可以通过Helm实现软件包管理,这里的Kubernetes软件包被称为模板(Chart)。Helm对于Kubernetes的关系类似于在Ubuntu系统中使用的apt命令,或是在CentOS系统中使用的yum命令,它能够快速查找、下载和安装模板(Chart)。
模板(Chart)是一种Helm的打包格式,它只是描述了一组相关的集群资源定义,而不是真正的容器镜像包。模板中仅仅包含了用于部署Kubernetes应用的一系列YAML文件,您可以在Helm模板中自定义应用程序的一些参数设置。在模板的实际安装过程中,Helm会根据模板中的YAML文件定义在集群中部署资源,相关的容器镜像并不会包含在模板包中,而是依旧从YAML中定义好的镜像仓库中进行拉取。
对于应用开发者而言,需要将容器镜像包发布到镜像仓库,并通过Helm的模板将安装应用时的依赖关系统一打包,预置一些关键参数,来降低应用的部署难度。
对于应用使用者而言,可以使用Helm查找模板(Chart)包并支持调整自定义参数。Helm会根据模板包中的YAML文件直接在集群中安装应用程序及其依赖,应用使用者不用编写复杂的应用部署文件,即可以实现简单的应用查找、安装、升级、回滚、卸载。
关于模板的更多操作请参见模板概述。
-
API Server
API Server(即kube-apiserver组件)是Kubernetes集群的核心组件之一,是整个Kubernetes系统的统一入口。API Server负责处理所有来自客户端的API请求,供用户、集群中的不同部分和集群外部组件相互通信,所有对集群资源(如Pod、Service、Deployment等)的操作都必须通过API Server完成。
API Server的关键特性和功能如下:
- Kubernetes API接口暴露:API Server提供了一个RESTful API,用于管理和操作Kubernetes资源,如Pod、Service、Deployment等。
- 认证与授权:
- 认证(Authentication):验证请求者的身份(如通过 Token、客户端证书、用户名密码等)。
- 授权(Authorization):检查已认证用户是否有权执行请求的操作(如通过 RBAC、ABAC 等策略)。
- 准入控制(Admission Control):在资源创建/更新/删除前,通过准入控制器(Admission Controllers)对请求进行校验或修改。
- API版本管理:支持多版本API(如v1、apps/v1等),方便功能迭代和兼容性维护。
- 与其他组件的交互:
- etcd:读写etcd以持久化集群状态。
- kube-controller-manager:通过API监听资源变化,执行控制逻辑(如节点控制器、 replication 控制器)。
- kube-scheduler:通过API获取待调度Pod,更新调度结果。
- kubelet:向API上报节点和Pod状态,接收Pod配置并执行。
- kubectl:用户通过kubectl调用API操作集群。
更多关于API Server的配置说明,请参见kube-apiserver。
- 调度、抢占和驱逐
在 Kubernetes 中,调度(scheduling)指的是确保 Pod 匹配到合适的节点, 以便 kubelet 能够运行它们。 抢占(Preemption)指的是终止低优先级的 Pod 以便高优先级的 Pod 可以调度到 Node 上的过程。 驱逐(Eviction)是在资源匮乏的节点上,主动让一个或多个 Pod 失效的过程。
更多关于调度、抢占和驱逐的配置说明,请参见Kubernetes 文档/概念/调度、抢占和驱逐。
二、容器与Kubernetes
1.容器
- 容器与Docker
容器技术起源于Linux,是一种内核虚拟化技术,提供轻量级的虚拟化,以便隔离进程和资源。尽管容器技术已经出现很久,却是随着Docker的出现而变得广为人知。Docker是第一个使容器能在不同机器之间移植的系统。它不仅简化了打包应用的流程,也简化了打包应用的库和依赖,甚至整个操作系统的文件系统能被打包成一个简单的可移植的包,这个包可以被用来在任何其他运行Docker的机器上使用。
容器和虚拟机具有相似的资源隔离和分配方式,容器虚拟化了操作系统而不是硬件,更加便携和高效。
图1 容器 vs 虚拟机
相比于使用虚拟机,容器有如下优点:
-
更高效地利用系统资源
由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,容器对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。
-
更快速的启动时间
传统的虚拟机技术启动应用服务往往需要数分钟,而Docker容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间,大大节约了开发、测试、部署的时间。
-
一致的运行环境
开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些问题并未在开发过程中被发现。而Docker的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性。
-
更轻松的迁移
由于Docker确保了执行环境的一致性,使得应用的迁移更加容易。Docker可以在很多平台上运行,无论是物理机、虚拟机,其运行结果是一致的。因此可以很轻易地将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。
-
更轻松的维护和扩展
Docker使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker团队同各个开源项目团队一起维护了大批高质量的官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大地降低了应用服务的镜像制作成本。
- Docker容器典型使用流程
Docker容器有如下三个主要概念:
- 镜像:Docker镜像里包含了已打包的应用程序及其所依赖的环境。它包含应用程序可用的文件系统和其他元数据,如镜像运行时的可执行文件路径。
- 镜像仓库:Docker镜像仓库用于存放Docker镜像,以及促进不同人和不同电脑之间共享这些镜像。当编译镜像时,要么可以在编译它的电脑上运行,要么可以先上传镜像到一个镜像仓库,然后下载到另外一台电脑上并运行它。某些仓库是公开的,允许所有人从中拉取镜像,同时也有一些是私有的,仅部分人和机器可接入。
- 容器:Docker容器通常是一个Linux容器,它基于Docker镜像被创建。一个运行中的容器是一个运行在Docker主机上的进程,但它和主机,以及所有运行在主机上的其他进程都是隔离的。这个进程也是资源受限的,意味着它只能访问和使用分配给它的资源(CPU、内存等)。
典型的使用流程如图2所示:
图2 Docker容器典型使用流程

-
首先开发者在开发环境机器上开发应用并制作镜像。
Docker执行命令,构建镜像并存储在机器上。
-
开发者发送上传镜像命令。
Docker收到命令后,将本地镜像上传到镜像仓库。
-
开发者向生产环境机器发送运行镜像命令。
生产环境机器收到命令后,Docker会从镜像仓库拉取镜像到机器上,然后基于镜像运行容器。
- 使用示例
下面使用Docker将基于Nginx镜像打包一个容器镜像,并基于容器镜像运行应用,然后推送到容器镜像仓库。
(1) 安装Docker
Docker几乎支持在所有操作系统上安装,用户可以根据需要选择要安装的Docker版本。
以"CentOS 7.5 64bit(40GiB)"操作系统为例,使用华为云镜像快速安装Docker。
①执行以下命令,添加yum源。
bash
yum install epel-release -y
yum clean all
②执行以下命令,安装需要的软件包。
bash
yum install -y yum-utils device-mapper-persistent-data lvm2
③执行以下命令,设置Docker yum源。
bash
yum-config-manager --add-repo https://mirrors.huaweicloud.com/docker-ce/linux/centos/docker-ce.repo
sed -i 's+download.docker.com+mirrors.huaweicloud.com/docker-ce+' /etc/yum.repos.d/docker-ce.repo
④执行以下命令,查看可用的Docker版本。
bash
yum list docker-ce --showduplicates | sort -r
回显结果如下:
bash
Loading mirror speeds from cached hostfile
Loaded plugins: fastestmirror
docker-ce.x86_64 3:26.1.4-1.el7 docker-ce-stable
docker-ce.x86_64 3:26.1.3-1.el7 docker-ce-stable
docker-ce.x86_64 3:26.1.2-1.el7 docker-ce-stable
...
⑤执行以下命令,安装指定版本的Docker。建议安装的Docker版本在18.06.0(包含)至24.0.9(包含)之间,以便后续设置镜像加速器,关于镜像加速器的详细说明以及适用区域请参见设置镜像加速器。
bash
sudo yum install docker-ce-24.0.9 docker-ce-cli-24.0.9 containerd.io
⑥执行以下命令,启动Docker服务。
bash
systemctl enable docker # 设置Docker服务在系统启动时自动启动
systemctl start docker # 启动Docker服务
⑦检查安装结果。
bash
docker --version
回显结果如下:
bash
Docker version 24.0.9, build 2936816
(2) Docker打包镜像
Docker提供了一种便捷的描述应用打包的方式,叫做Dockerfile。通过Dockerfile定制一个简单的Nginx镜像。
①通过设置镜像加速器,可以对部分常用的开源镜像下载进行加速,帮助解决由于运营商网络原因导致从第三方镜像仓库(如 Docker Hub)拉取镜像时出现下载慢甚至失败的问题。
②在mynginx路径下,创建一个名为Dockerfile的文件。
bash
mkdir mynginx
cd mynginx
touch Dockerfile
③执行以下命令,编辑Dockerfile文件。
bash
vim Dockerfile
增加文件内容如下:
bash
# 使用Nginx镜像作为基础镜像
FROM nginx:latest
# 用"hello world"覆盖index.html原有内容
RUN echo "hello world" > /usr/share/nginx/html/index.html
# 允许外界访问容器的80端口
EXPOSE 80
执行以下命令,打包镜像。
bash
docker build -t hello .
其中-t表示给镜像加一个标签,也就是给镜像取名,这里镜像名为hello。结尾的符号. 表示在当前目录下执行该打包命令。
⑤执行以下命令,查看镜像是否创建成功。
bash
docker images
回显结果如下,则说明hello镜像已经创建成功。
bash
REPOSITORY TAG IMAGE ID CREATED SIZE
hello latest 1ff61881be30 10 seconds ago 236MB
(3) 把镜像推送到镜像仓库
①登录SWR控制台,在左侧选择"我的镜像",然后单击右侧"客户端上传镜像",在弹出的窗口中单击"生成临时登录指令",然后复制该指令在本地机器上执行,登录到SWR镜像仓库。
②上传镜像前需要给镜像取一个完整的名称,如下所示:
bash
docker tag hello swr.cn-east-3.myhuaweicloud.com/container/hello:v1
这里swr.cn-east-3.myhuaweicloud.com是仓库地址,每个区域的地址不同,v1则是hello镜像分配的版本号。
- swr.cn-east-3.myhuaweicloud.com是仓库地址,每个区域的地址不同。
- container是组织名,组织一般在SWR中创建,如果没有创建则首次上传的时候会自动创建,组织名在单个区域内全局唯一,需要选择合适的组织名称。
- v1则是hello镜像分配的版本号。
③执行以下命令,将镜像上传至SWR。
bash
docker push swr.cn-east-3.myhuaweicloud.com/container/hello:v1
④执行以下命令,即可拉取(下载)该镜像。
bash
docker pull swr.cn-east-3.myhuaweicloud.com/container/hello:v1
2.Kubernetes
- Kubernetes是什么
Kubernetes是一个很容易地部署和管理容器化的应用软件系统,使用Kubernetes能够方便对容器进行调度和编排。
对应用开发者而言,可以把Kubernetes看成一个集群操作系统。Kubernetes提供服务发现、伸缩、负载均衡、自愈甚至选举等功能,让开发者从基础设施相关配置等解脱出来。
Kubernetes可以把大量的服务器看做一台巨大的服务器,在一台大服务器上面运行应用程序。无论Kubernetes的集群有多少台服务器,在Kubernetes上部署应用程序的方法永远一样。
图1 在Kubernetes集群上运行应用程序
- Kubernetes集群架构
Kubernetes集群包含控制节点和工作节点,应用部署在工作节点上,且可以通过配置选择应用部署在某些特定的节点上。
Kubernetes集群的架构如下所示:
图2 Kubernetes集群架构
(1) 控制节点
控制节点是集群的控制节点,由API Server、Scheduler、Controller Manager和ETCD四个组件构成。
- API Server:各组件互相通讯的中转站,接受外部请求,并将信息写到ETCD中。
- Controller Manager:执行集群级功能,例如复制组件,跟踪工作节点,处理节点故障等等。
- Scheduler:负责应用调度的组件,根据各种条件(如可用的资源、节点的亲和性等)将容器调度到Node上运行。
- ETCD:一个分布式数据存储组件,负责存储集群的配置信息。
在生产环境中,为了保障集群的高可用,通常会部署多个控制节点,如CCE的集群高可用模式就是3个控制节点。
(2) 工作节点
工作节点是集群的计算节点,即运行容器化应用的节点。
- kubelet:kubelet主要负责同Container Runtime打交道,并与API Server交互,管理节点上的容器。
- kube-proxy:应用组件间的访问代理,解决节点上应用的访问问题。
- Container Runtime:容器运行时,如Docker,最主要的功能是下载镜像和运行容器。
- Kubernetes的扩展性
Kubernetes开放了容器运行时接口(CRI)、容器网络接口(CNI)和容器存储接口(CSI),这些接口让Kubernetes的扩展性变得最大化,而Kubernetes本身则专注于容器调度。
- CRI(Container Runtime Interface):容器运行时接口,提供计算资源,CRI隔离了各个容器引擎之间的差异,而通过统一的接口与各个容器引擎之间进行互动。
- CNI(Container Network Interface):容器网络接口,提供网络资源,通过CNI接口,Kubernetes可以支持不同网络环境。例如CCE就是开发的CNI插件支持Kubernetes集群运行在VPC网络中。
- CSI(Container Storage Interface):容器存储接口,提供存储资源,通过CSI接口,Kubernetes可以支持各种类型的存储。例如CCE就可以方便地对接块存储(EVS)、文件存储(SFS)和对象存储(OBS)。
- Kubernetes中的基本对象
图3 Kubernetes基本对象
- Pod
Pod是Kubernetes创建或部署的最小单位。一个Pod封装一个或多个容器(container)、存储资源(volume)、一个独立的网络IP以及管理控制容器运行方式的策略选项。 - Deployment
Deployment是对Pod的服务化封装。一个Deployment可以包含一个或多个Pod,每个Pod的角色相同,所以系统会自动为Deployment的多个Pod分发请求。 - StatefulSet
StatefulSet是用来管理有状态应用的对象。和Deployment相同的是,StatefulSet管理了基于相同容器定义的一组Pod。但和Deployment不同的是,StatefulSet为它们的每个Pod维护了一个固定的ID。这些Pod是基于相同的声明来创建的,但是不能相互替换,无论怎么调度,每个Pod都有一个永久不变的ID。 - Job
Job是用来控制批处理型任务的对象。批处理业务与长期伺服业务(Deployment)的主要区别是批处理业务的运行有头有尾,而长期伺服业务在用户不停止的情况下永远运行。Job管理的Pod根据用户的设置把任务成功完成就自动退出(Pod自动删除)。 - CronJob
CronJob是基于时间控制的Job,类似于Linux系统的crontab,在指定的时间周期运行指定的任务。 - DaemonSet
DaemonSet是这样一种对象(守护进程),它在集群的每个节点上运行一个Pod,且保证只有一个Pod,这非常适合一些系统层面的应用,例如日志收集、资源监控等,这类应用需要每个节点都运行,且不需要太多实例,一个比较好的例子就是Kubernetes的kube-proxy。 - Service
Service是用来解决Pod访问问题的。Service有一个固定IP地址,Service将访问流量转发给Pod,而且Service可以给这些Pod做负载均衡。 - Ingress
Service是基于四层TCP和UDP协议转发的,Ingress可以基于七层的HTTP和HTTPS协议转发,可以通过域名和路径做到更细粒度的划分。 - ConfigMap
ConfigMap是一种用于存储应用所需配置信息的资源类型,用于保存配置数据的键值对。通过ConfigMap可以方便地做到配置解耦,使得不同环境有不同的配置。 - Secret
Secret是一种加密存储的资源对象,您可以将认证信息、证书、私钥等保存在Secret中,而不需要把这些敏感数据暴露到镜像或者Pod定义中,从而更加安全和灵活。 - PersistentVolume(PV)
PV指持久化数据存储卷,主要定义的是一个持久化存储在宿主机上的目录,比如一个NFS的挂载目录。 - PersistentVolumeClaim(PVC)
Kubernetes提供PVC专门用于持久化存储的申请,PVC可以让您无需关心底层存储资源如何创建、释放等动作,而只需要申明您需要何种类型的存储资源、多大的存储空间。
- 搭建Kubernetes集群
Kubernetes网站上有多种搭建Kubernetes集群的方法,例如minikube、kubeadm等。
- Kubernetes对象的描述
kubernetes中资源可以使用YAML描述(如果您对YAML格式不了解,可以参考YAML语法),也可以使用JSON。其内容可以分为如下四个部分:
- typeMeta:对象类型的元信息,声明对象使用哪个API版本,哪个类型的对象。
- objectMeta:对象的元信息,包括对象名称、使用的标签等。
- spec:对象的期望状态,例如对象使用什么镜像、有多少副本等。
- status:对象的实际状态,只能在对象创建后看到,创建对象时无需指定。
图4 YAML描述文件
- 在Kubernetes上运行应用
(1) 将图4中的内容去除status存为一个名为nginx-deployment.yaml的文件,如下所示:
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:alpine
resources:
requests:
cpu: 100m
memory: 200Mi
limits:
cpu: 100m
memory: 200Mi
imagePullSecrets:
- name: default-secret
(2) 使用kubectl连接集群后,执行如下命令:
bash
# kubectl create -f nginx-deployment.yaml
deployment.apps/nginx created
(3) 命令执行后,Kubernetes集群中会创建3个Pod,使用如下命令可以查询到Deployment和Pod:
bash
# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 3/3 3 3 9s
# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-685898579b-qrt4d 1/1 Running 0 15s
nginx-685898579b-t9zd2 1/1 Running 0 15s
nginx-685898579b-w59jn 1/1 Running 0 15s
到此为止,了解了容器和Docker、Kubernetes集群、Kubernetes基本概念,并通过一个示例了解kubectl的最基本使用,后续将深入介绍Kubernetes对象的概念以及使用方法,并介绍对象之间的关系。
3.使用Kubectl命令操作集群
1. kubectl
kubectl是Kubernetes集群的命令行工具,您可以将kubectl安装在任意一台机器上,通过kubectl命令操作Kubernetes集群。
CCE集群的kubectl安装请参见通过kubectl连接集群。连接后您可以执行kubectl cluster-info查看集群的信息,如下所示。
bash
# kubectl cluster-info
Kubernetes master is running at https://*.*.*.*:5443
CoreDNS is running at https://*.*.*.*:5443/api/v1/namespaces/kube-system/services/coredns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
执行kubectl get nodes可以查看集群中的节点信息。
bash
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
192.168.0.153 Ready <none> 7m v1.15.6-r1-20.3.0.2.B001-15.30.2
192.168.0.207 Ready <none> 7m v1.15.6-r1-20.3.0.2.B001-15.30.2
192.168.0.221 Ready <none> 7m v1.15.6-r1-20.3.0.2.B001-15.30.2
更多kubectl命令请参考kubectl 快速参考。
2. 基础命令
- get
get命令用于获取集群的一个或一些资源的详细信息。
该命令可以列出集群所有资源的详细信息,包括集群节点、运行的Pod、Deployment、Service等。
集群中可以创建多个命名空间,未指定命名空间的情况下,默认指定为--namespace=default,即查询default命名空间下的资源。
例如:
获取所有Pod的详细信息:
bash
kubectl get pod -o wide
获取所有命名空间下的运行的所有Pod:
bash
kubectl get pod --all-namespaces
获取所有命名空间下的运行的所有Pod的标签:
bash
kubectl get pod --show-labels
获取该节点的所有命名空间:
bash
kubectl get namespace
类似可以使用"kubectl get svc","kubectl get nodes","kubectl get deploy"等获取其他资源的信息。
以YAML格式输出Pod的详细信息:
bash
kubectl get pod <podname> -o yaml
以JSON格式输出Pod的详细信息:
bash
kubectl get pod <podname> -o json
bash
kubectl get pod rc-nginx-2-btv4j -o=custom-columns=LABELS:.metadata.labels.app
其中LABELS为显示的列标题,可以自己设置,".metadata.labels.app"为查询的数据需要按照之前的YAML或JSON获取。
- create
create命令用于根据文件或输入创建集群资源。
如果已经定义了相应资源的YAML或JSON文件,直接使用以下命令即可创建文件内定义的资源。
bash
kubectl create -f <filename>
- expose
expose将一个资源包括Pod、Deployment等公开为一个新的Service。
bash
kubectl expose deployment <deployname> --port=81 --type=NodePort --target-port=80 --node-port=31000 --name=<service-name>
以上命令会给Deployment创建一个NodePort类型服务,--port为服务端口(用于集群访问),--type为服务类型,--target-port为服务对应后端Pod的端口,--node-port表示NodePort端口(用于集群外访问)。其中--node-port为可选参数,未指定时,集群将在30000~32767范围内随机分配。
- run
创建单Pod或Deployment的快捷命令,适合测试环境。
例如:
bash
kubectl run <deployname> --image=nginx:latest
同时,可以在创建Pod或Deployment时指定运行的命令:
bash
kubectl run <deployname> --image=busybox --command -- ping example.com
- set
在对象上设置特定功能。
例如:
滚动更新一个Deployment的容器镜像改为1.0版本:
bash
kubectl set image deployment/<deployname> <containername>=<containername>:1.0
- edit
edit提供了另一种更新资源的操作。
例如:
使用edit直接更新Pod的命令为:
bash
kubectl edit pod po-nginx-btv4j
上面命令的效果等效于:
bash
kubectl get pod po-nginx-btv4j -o yaml >> /tmp/nginx-tmp.yaml
vim /tmp/nginx-tmp.yaml
# do some changes here
kubectl replace -f /tmp/nginx-tmp.yaml
- explain
查看文档或参考资料。
例如:
查看Pod的相关文档:
bash
kubectl explain pod
- delete
根据资源名或标签删除资源。
例如:
立刻删除该Pod:
bash
kubectl delete pod <podname> --now
bash
kubectl delete -f nginx.yaml
kubectl delete deployment <deployname>
3. 部署命令
- rollout
管理资源的发布。
例如:
查看指定资源的部署状态:
bash
kubectl rollout status deployment/<deployname>
查看指定资源的发布历史:
bash
kubectl rollout history deployment/<deployname>
回滚指定资源,默认回滚至上一个版本:
bash
kubectl rollout undo deployment/test-nginx
- scale
scale用于程序在负载加重或缩小时将副本进行扩容或缩小。
bash
kubectl scale deployment <deployname> --replicas=<newnumber>
- autoscale
autoscale命令提供了自动根据工作负载的CPU利用率对其副本进行扩缩的功能。autoscale命令会给工作负载(Deployment、ReplicaSet、StatefulSet)指定一个副本数的范围,在实际运行中根据所有Pod的平均CPU利用率自动在指定的范围内对Pod进行扩容或缩容。如果未指定目标利用率或设置为负数,将使用默认的自动扩缩策略。
bash
kubectl autoscale deployment <deployname> --min=<minnumber> --max=<maxnumber> --cpu-percent=<cpu>
4. 集群管理命令
- cordon、drain、uncordon
有时候会遇到这样一个场景,一个节点需要升级,但是在该节点上又有许多运行的Pod,或者该节点已经瘫痪,需要保证业务功能的完善,则需要使用这组命令将该节点上运行的Pod调度到其他节点上。使用步骤如下:
(1) 使用cordon命令将一个节点标记为不可调度。这意味着新的Pod将不会被调度到该节点上。
bash
kubectl cordon <nodename>
CCE中默认为节点私网IP。
(2) 使用drain命令,驱逐该节点上的Pod,将运行在该节点上运行的Pod平滑的搬迁到其他节点上。
bash
kubectl drain <nodename> --ignore-daemonsets --delete-emptydir-data
--ignore-daemonsets表示忽略DaemonSet所控制的Pod,--delete-emptydir-data表示即使存在使用emptyDir(腾空节点时将删除本地数据)的Pod也会继续腾空节点。
(3) 对该节点进行一些节点维护的操作,如重置节点等。
(4) 节点维护完后,使用uncordon命令解锁该节点,使其重新变得可调度。
bash
kubectl uncordon <nodename>
- cluster-info
查看在集群中运行的插件:
bash
kubectl cluster-info
查看详细信息:
bash
kubectl cluster-info dump
- top*
显示资源(CPU/Memory/Storage)使用,该命令需要集群中的Metrics Server正常运行。
- taint*`
修改一个或多个节点上的污点。
- certificate*
修改证书资源。
5. 故障诊断和调试命令
- describe
describe命令类似于get,同样用于获取资源的相关信息。不同的是,get获得的是定义该资源的详细信息,而describe获得的是资源在集群内相关的状态信息。describe命令同get类似,但是describe命令不支持-o选项,对于同一类型的资源,describe命令输出的信息格式,内容域相同。
bash
kubectl describe pod <podname>
如果发现是查询某个资源的信息,使用get命令能够获取更加详尽的信息。但是如果想要查询某个资源的状态,如某个Pod并不是在running状态,这时需要获取更详尽的状态信息时,就应该使用describe命令。
- logs
logs命令用于显示Pod运行中,容器内程序输出到标准输出的内容。如果要获得tail -f的方式,需使用-f选项。
bash
kubectl logs -f <podname>
- exec
与Docker的exec用法相似,如果一个Pod中,有多个容器,需要使用-c选项指定容器。
bash
kubectl exec -it <podname> -- bash
kubectl exec -it <podname> -c <containername> -- bash
- port-forward*
转发一个或多个本地端口至一个Pod。
例如:
侦听本地端口5000并转发到创建的Pod里的端口6000:
bash
kubectl port-forward deploy/my-deployment 5000:6000
- cp
复制文件或目录到容器:
bash
kubectl cp /tmp/foo <podname>:/tmp/bar -c <containername>
将/tmp/foo本地文件复制到远程Pod中特定容器的/tmp/bar下。
- auth*
检查授权。
- attach*
attach命令效果类似于logs -f,退出查看使用ctrl-c。如果一个Pod中有多个容器,要查看具体的某个容器的输出,需要在Pod名后使用-c containername指定运行的容器。
bash
kubectl attach <podname> -c <containername>
6. 高级命令
- replace
replace命令用于对已有资源进行更新、替换。当需要更新资源的一些属性的时候,如果修改副本数量,增加、修改标签,更改镜像版本,修改端口等,都可以直接修改原YAML文件,然后执行replace命令。
bash
kubectl replace -f <filename>
资源名称不能被更新。
- apply*
apply命令提供了比patch,edit等更严格的更新资源的方式。通过apply,用户可以将资源的configuration使用source control的方式维护在版本库中。每次有更新时,将配置文件推送server,然后使用kubectl apply将更新应用到资源。Kubernetes会在应用更新前将当前配置文件中的配置同已经应用的配置做比较,并只更新更改的部分,而不会主动更改任何用户未指定的部分。apply命令的使用方式同replace相同,不同的是,apply不会删除原有资源,然后创建新的。apply直接在原有资源的基础上进行更新。同时kubectl apply还会在资源中添加一条注释,标记当前的apply,类似于git操作。
bash
kubectl apply -f <filename>
- patch
如果一个容器已经在运行,这时需要对一些容器属性进行修改,又不想删除容器,或不方便通过replace的方式进行更新。Kubernetes还提供了一种在容器运行时,直接对容器进行修改的方式,就是patch命令。 例如已存在一个Pod的标签为app=nginx1,如果需要在运行过程中,将其修改为app=nginx2。
bash
kubectl patch pod <podname> -p '{"metadata":{"labels":{"app":"nginx2"}}}'
- convert*
不同的API版本之间转换配置文件。
7. 设置命令
- label
更新资源上的标签:
bash
kubectl label pods my-pod new-label=newlabel
- annotate
更新资源上的注释:
bash
kubectl annotate pods my-pod icon-url=http://*****
- completion
用于实现kubectl工具自动补全。
8. 其他命令
- api-versions
打印受支持的API版本:
bash
kubectl api-versions
- api-resources
打印支持的API资源:
bash
kubectl api-resources
- config*
修改kubeconfig文件:用于访问API,比如配置认证信息。
- help
所有命令帮助。
- version
打印客户端和服务版本信息。
bash
kubectl version
三、Pod、Label和Namespace
1.Pod:Kubernetes中的最小调度对象
- 容器组(Pod)
容器组(Pod)是Kubernetes创建或部署的最小单位。一个Pod封装一个或多个容器(Container)、存储资源(Volume)、一个独立的网络IP以及管理控制容器运行方式的策略选项。
Pod使用主要分为两种方式:
-
Pod中运行一个容器。这是Kubernetes最常见的用法,您可以将Pod视为单个封装的容器,但是Kubernetes是直接管理Pod而不是容器。
-
Pod中运行多个需要耦合在一起工作、需要共享资源的容器。通常这种场景下应用包含一个主容器和几个辅助容器(SideCar Container),如图1所示,例如主容器为一个web服务器,从一个固定目录下对外提供文件服务,而辅助容器周期性的从外部下载文件存到这个固定目录下。
图1 Pod

实际使用中很少直接创建Pod,而是使用Kubernetes中称为Controller的抽象层来管理Pod实例,例如Deployment和Job。Controller可以创建和管理多个Pod,提供副本管理、滚动升级和自愈能力。通常,Controller会使用Pod Template来创建相应的Pod。
- 创建Pod
kubernetes中资源可以使用YAML描述(如果您对YAML格式不了解,可以参考YAML语法),也可以使用JSON,如下示例描述了一个名为nginx的Pod,这个Pod中包含一个名为container-0的容器,使用nginx:alpine镜像,使用的资源为100m CPU、200Mi内存。
yaml
apiVersion: v1 # Kubernetes的API Version
kind: Pod # Kubernetes的资源类型
metadata:
name: nginx # Pod的名称
spec: # Pod的具体规格(specification)
containers:
- image: nginx:alpine # 使用的镜像为 nginx:alpine
name: container-0 # 容器的名称
resources: # 申请容器所需的资源
limits:
cpu: 100m
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
imagePullSecrets: # 拉取镜像使用的证书,在CCE上必须为default-secret
- name: default-secret
如上面YAML的注释,YAML描述文件主要为如下部分:
- metadata:一些名称/标签/namespace等信息。
- spec:Pod实际的配置信息,包括使用什么镜像,volume等。
如果去查询Kubernetes的资源,您会看到还有一个status字段,status描述kubernetes资源的实际状态,创建时不需要配置。这个示例是一个最小集,其他参数定义后面会逐步介绍。
Pod定义好后就可以使用kubectl创建,如果上面YAML文件名称为nginx.yaml,则创建命令如下所示,-f表示使用文件方式创建。
bash
$ kubectl create -f nginx.yaml
pod/nginx created
Pod创建完成后,可以使用kubectl get pods命令查询Pod的状态,如下所示。
yaml
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 40s
可以看到此处nginx这个Pod的状态为Running,表示正在运行;READY为1/1,表示这个Pod中有1个容器,其中1个容器的状态为Ready。
可以使用kubectl get命令查询具体Pod的配置信息,如下所示,-o yaml表示以YAML格式返回,还可以使用-o json,以JSON格式返回。
bash
$ kubectl get pod nginx -o yaml
您还可以使用kubectl describe命令查看Pod的详情。
bash
$ kubectl describe pod nginx
删除pod时,Kubernetes终止Pod中所有容器。 Kubernetes向进程发送SIGTERM信号并等待一定的秒数(默认为30)让容器正常关闭。如果它没有在这个时间内关闭,Kubernetes会发送一个SIGKILL信号终止该进程。
Pod的停止与删除有多种方法,比如按名称删除,如下所示。
bash
$ kubectl delete po nginx
pod "nginx" deleted
同时删除多个Pod。
bash
$ kubectl delete po pod1 pod2
删除所有Pod。
bash
$ kubectl delete po --all
pod "nginx" deleted
根据Label删除Pod,Label详细内容将会在下一个章节介绍。
bash
$ kubectl delete po -l app=nginx
pod "nginx" deleted
- 使用环境变量
环境变量是容器运行环境中设定的一个变量。
环境变量为应用提供极大的灵活性,您可以在应用程序中使用环境变量,在创建容器时为环境变量赋值,容器运行时读取环境变量的值,从而做到灵活的配置,而不是每次都重新编写应用程序制作镜像。
环境变量的使用方法如下所示,配置spec.containers.env字段即可。
yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx:alpine
name: container-0
resources:
limits:
cpu: 100m
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
env: # 环境变量
- name: env_key
value: env_value
imagePullSecrets:
- name: default-secret
执行如下命令查看容器中的环境变量,可以看到env_key这个环境变量,其值为env_value。
bash
$ kubectl exec -it nginx -- env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=nginx
TERM=xterm
env_key=env_value
环境变量还可以引用ConfigMap和Secret,具体使用方法请参见在环境变量中引用ConfigMap和在环境变量中引用Secret。
- 容器启动命令
启动容器就是启动主进程,但有些时候,启动主进程前,需要一些准备工作。比如MySQL类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的MySQL服务器运行之前做完。这些操作,可以在制作镜像时通过在Dockerfile文件中设置ENTRYPOINT或CMD来完成,如下所示的Dockerfile中设置了ENTRYPOINT ["top", "-b"]命令,其将会在容器启动时执行。
bash
FROM ubuntu
ENTRYPOINT ["top", "-b"]
实际使用时,只需配置Pod的containers.command参数,该参数是list类型,第一个参数为执行命令,后面均为命令的参数。
yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx:alpine
name: container-0
resources:
limits:
cpu: 100m
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
command: # 启动命令
- top
- "-b"
imagePullSecrets:
- name: default-secret
- 容器的生命周期
Kubernetes提供了容器生命周期钩子,在容器的生命周期的特定阶段执行调用,比如容器在停止前希望执行某项操作,就可以注册相应的钩子函数。目前提供的生命周期钩子函数如下所示。
- 启动后处理(PostStart):容器启动后触发。
- 停止前处理(PreStop):容器停止前触发。
实际使用时,只需配置Pod的lifecycle.postStart
或lifecycle.preStop
参数,如下所示。
yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx:alpine
name: container-0
resources:
limits:
cpu: 100m
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
lifecycle:
postStart: # 启动后处理
exec:
command:
- "/postStart.sh"
preStop: # 停止前处理
exec:
command:
- "/preStop.sh"
imagePullSecrets:
- name: default-secret
2.Liveness Probe:健康检查机制
- 存活探针
Kubernetes提供了自愈的能力,具体就是能感知到容器崩溃,然后能够重启这个容器。但是有时候例如Java程序内存泄漏了,程序无法正常工作,但是JVM进程却是一直运行的,对于这种应用本身业务出了问题的情况,Kubernetes提供了Liveness Probe机制,通过检测容器响应是否正常来决定是否重启,这是一种很好的健康检查机制。
毫无疑问,每个Pod最好都定义Liveness Probe,否则Kubernetes无法感知Pod是否正常运行。
Kubernetes支持如下三种探测机制。
- HTTP GET:向容器发送HTTP GET请求,如果Probe收到2xx或3xx,说明容器是健康的。
- TCP Socket:尝试与容器指定端口建立TCP连接,如果连接成功建立,说明容器是健康的。
- Exec:Probe执行容器中的命令并检查命令退出的状态码,如果状态码为0则说明容器是健康的。
与存活探针对应的还有一个就绪探针(Readiness Probe),将在就绪探针(Readiness Probe)中会详细介绍。
- HTTP GET
HTTP GET方式是最常见的探测方法,其具体机制是向容器发送HTTP GET请求,如果Probe收到2xx或3xx,说明容器是健康的,定义方法如下所示。
yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-http
spec:
containers:
- name: liveness
image: nginx:alpine
livenessProbe: # liveness probe
httpGet: # HTTP GET定义
path: /
port: 80
imagePullSecrets:
- name: default-secret
创建这个Pod。
bash
$ kubectl create -f liveness-http.yaml
pod/liveness-http created
如上,这个Probe往容器的80端口发送HTTP GET请求,如果请求不成功,Kubernetes会重启容器。
查看Pod详情。
yaml
$ kubectl describe po liveness-http
Name: liveness-http
......
Containers:
liveness:
......
State: Running
Started: Mon, 03 Aug 2020 03:08:55 +0000
Ready: True
Restart Count: 0
Liveness: http-get http://:80/ delay=0s timeout=1s period=10s #success=1 #failure=3
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-vssmw (ro)
......
可以看到Pod当前状态是Running,Restart Count为0,说明没有重启。如果Restart Count不为0,则说明已经重启。
- TCP Socket
TCP Socket尝试与容器指定端口建立TCP连接,如果连接成功建立,说明容器是健康的,定义方法如下所示。
yaml
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-tcp
spec:
containers:
- name: liveness
image: nginx:alpine
livenessProbe: # liveness probe
tcpSocket:
port: 80
imagePullSecrets:
- name: default-secret
- Exec
Exec即执行具体命令,具体机制是Probe执行容器中的命令并检查命令退出的状态码,如果状态码为0则说明健康,定义方法如下所示。
yaml
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-exec
spec:
containers:
- name: liveness
image: nginx:alpine
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
livenessProbe: # liveness probe
exec: # Exec定义
command:
- cat
- /tmp/healthy
imagePullSecrets:
- name: default-secret
上面定义在容器中执行cat /tmp/healthy
命令,如果成功执行并返回0,则说明容器是健康的。上面定义中,30秒后命令会删除/tmp/healthy
,这会导致Liveness Probe判定Pod处于不健康状态,然后会重启容器。
- Liveness Probe高级配置
上面liveness-http的describe命令回显中有如下行。
bash
Liveness: http-get http://:80/ delay=0s timeout=1s period=10s #success=1 #failure=3
这一行表示Liveness Probe的具体参数配置,其含义如下:
- delay:延迟,delay=0s,表示在容器启动后立即开始探测,没有延迟时间
- timeout:超时,timeout=1s,表示容器必须在1s内进行响应,否则这次探测记作失败
- period:周期,period=10s,表示每10s探测一次容器
- success:成功,#success=1,表示连续1次成功后记作成功
- failure:失败,#failure=3,表示连续3次失败后会重启容器
以上存活探针表示:容器启动后立即进行探测,如果1s内容器没有给出回应则记作探测失败。每次间隔10s进行一次探测,在探测连续失败3次后重启容器。
这些是创建时默认设置的,您也可以手动配置,如下所示。
yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-http
spec:
containers:
- name: liveness
image: nginx:alpine
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10 # 容器启动后多久开始探测
timeoutSeconds: 2 # 表示容器必须在2s内做出相应反馈给probe,否则视为探测失败
periodSeconds: 30 # 探测周期,每30s探测一次
successThreshold: 1 # 连续探测1次成功表示成功
failureThreshold: 3 # 连续探测3次失败表示失败
initialDelaySeconds
一般要设置大于0,这是由于很多情况下容器虽然启动成功,但应用就绪也需要一定的时间,需要等就绪时间之后才能返回成功,否则就会导致probe经常失败。
另外failureThreshold
可以设置多次循环探测,这样在实际应用中健康检查的程序就不需要多次循环,这一点在开发应用时需要注意。
- 配置有效的Liveness Probe
- Liveness Probe应该检查什么
一个好的Liveness Probe
应该检查应用内部所有关键部分是否健康,并使用一个专有的URL访问,例如/health,当访问/health
时执行这个功能,然后返回对应结果。这里要注意不能做鉴权,不然probe就会一直失败导致陷入重启的死循环。
另外检查只能限制在应用内部,不能检查依赖外部的部分,例如当前端web server不能连接数据库时,这个就不能看成web server不健康。
- Liveness Probe必须轻量
Liveness Probe
不能占用过多的资源,且不能占用过长的时间,否则所有资源都在做健康检查,这就没有意义了。例如Java应用,就最好用HTTP GET方式,如果用Exec方式,JVM启动就占用了非常多的资源。
3.Label:组织Pod的利器
- 为什么需要Label
当资源变得非常多的时候,如何分类管理就非常重要了,Kubernetes提供了一种机制来为资源分类,那就是Label(标签)。Label非常简单,但是却很强大,Kubernetes中几乎所有资源都可以用Label来组织。
Label的具体形式是key-value的标记对,可以在创建资源的时候设置,也可以在后期添加和修改。
以Pod为例,当Pod变得多起来后,就显得杂乱且难以管理,如下图所示。
图1 没有分类组织的Pod

如果我们为Pod打上不同标签,那情况就完全不同了,如下图所示。
图2 使用Label组织的Pod

- 添加Label
Label的形式为key-value形式,使用非常简单,如下,为Pod设置了app=nginx
和env=prod
两个Label。
yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels: # 为Pod设置两个Label
app: nginx
env: prod
spec:
containers:
- image: nginx:alpine
name: container-0
resources:
limits:
cpu: 100m
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
imagePullSecrets:
- name: default-secret
Pod有了Label后,在查询Pod的时候带上--show-labels
就可以看到Pod的Label。
bash
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx 1/1 Running 0 50s app=nginx,env=prod
还可以使用-L只查询某些固定的Label。
bash
$ kubectl get pod -L app,env
NAME READY STATUS RESTARTS AGE APP ENV
nginx 1/1 Running 0 1m nginx prod
对已存在的Pod,可以直接使用kubectl label命令直接添加Label。
bash
$ kubectl label pod nginx creation_method=manual
pod/nginx labeled
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx 1/1 Running 0 50s app=nginx, creation_method=manual,env=prod
- 修改Label
对于已存在的Label,如果要修改的话,需要在命令中带上--overwrite
,如下所示。
bash
$ kubectl label pod nginx env=debug --overwrite
pod/nginx labeled
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx 1/1 Running 0 50s app=nginx,creation_method=manual,env=debug
4.Namespace:资源分组
- 为什么需要Namespace
Label虽然好,但只用Label的话,那Label会非常多,有时候会有重叠,而且每次查询之类的动作都带一堆Label非常不方便。
Kubernetes提供了Namespace来做资源组织和划分,使用多Namespace可以将包含很多组件的系统分成不同的组。Namespace也可以用来做多租户划分,这样多个团队可以共用一个集群,使用的资源用Namespace划分开。
不同的Namespace下的资源名称可以相同,Kubernetes中大部分资源可以用Namespace划分,不过有些资源不行,例如Node、PV等,它们属于全局资源,不属于某一个Namespace,后面会逐步接触到。
通过如下命令可以查询到当前集群下的Namespace。
bash
$ kubectl get ns
NAME STATUS AGE
default Active 36m
kube-node-lease Active 36m
kube-public Active 36m
kube-system Active 36m
到目前为止,我们都是在default Namespace下操作,当使用kubectl get而不指定Namespace时,默认为default Namespace。
看下kube-system下面有些什么东西。
bash
$ kubectl get po --namespace=kube-system
NAME READY STATUS RESTARTS AGE
coredns-7689f8bdf-295rk 1/1 Running 0 9m11s
coredns-7689f8bdf-h7n68 1/1 Running 0 11m
everest-csi-controller-6d796fb9c5-v22df 2/2 Running 0 9m11s
everest-csi-driver-snzrr 1/1 Running 0 12m
everest-csi-driver-ttj28 1/1 Running 0 12m
everest-csi-driver-wtrk6 1/1 Running 0 12m
icagent-2kz8g 1/1 Running 0 12m
icagent-hjz4h 1/1 Running 0 12m
icagent-m4bbl 1/1 Running 0 12m
可以看到kube-system有很多Pod,其中coredns是用于做服务发现、everest-csi是用于对接存储服务、icagent是用于对接监控系统。
这些通用的、必须的应用放在kube-system这个命名空间中,能够做到与其他Pod之间隔离,在其他命名空间中不会看到kube-system这个命名空间中的东西,不会造成影响。
- 创建Namespace
使用如下方式定义Namespace。
yaml
apiVersion: v1
kind: Namespace
metadata:
name: custom-namespace
使用kubectl命令创建。
bash
$ kubectl create -f custom-namespace.yaml
namespace/custom-namespace created
您还可以使用kubectl create namespace命令创建。
bash
$ kubectl create namespace custom-namespace
namespace/custom-namespace created
在指定Namespace下创建资源。
bash
$ kubectl create -f nginx.yaml -n custom-namespace
pod/nginx created
这样在custom-namespace下,就创建了一个名为nginx的Pod。
- Namespace的隔离说明
Namespace只能做到组织上划分,对运行的对象来说,它不能做到真正的隔离。举例来说,如果两个Namespace下的Pod知道对方的IP,而Kubernetes依赖的底层网络没有提供Namespace之间的网络隔离的话,那这两个Pod就可以互相访问。
四、Pod的编排与调度
1.无状态负载(Deployment)
- 无状态负载(Deployment)
Pod是Kubernetes创建或部署的最小单位,但是Pod是被设计为相对短暂的一次性实体,Pod可以被驱逐(当节点资源不足时)、随着集群的节点崩溃而消失。Kubernetes提供了Controller(控制器)来管理Pod,Controller可以创建和管理多个Pod,提供副本管理、滚动升级和自愈能力,其中最为常用的就是Deployment。
图1 Deployment
一个Deployment可以包含一个或多个Pod副本,每个Pod副本的角色相同,所以系统会自动为Deployment的多个Pod副本分发请求。
Deployment集成了上线部署、滚动升级、创建副本、恢复上线的功能,在某种程度上,Deployment实现无人值守的上线,大大降低了上线过程的复杂性和操作风险。
- 创建Deployment
以下示例为创建一个名为nginx的Deployment负载,使用nginx:latest镜像创建两个Pod,每个Pod占用100m CPU、200Mi内存。
yaml
apiVersion: apps/v1 # 注意这里与Pod的区别,Deployment是apps/v1而不是v1
kind: Deployment # 资源类型为Deployment
metadata:
name: nginx # Deployment的名称
spec:
replicas: 2 # Pod的数量,Deployment会确保一直有2个Pod运行
selector: # Label Selector
matchLabels:
app: nginx
template: # Pod的定义,用于创建Pod,也称为Pod template
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:latest
name: container-0
resources:
limits:
cpu: 100m
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
imagePullSecrets:
- name: default-secret
从这个定义中可以看到Deployment的名称为nginx,spec.replicas
定义了Pod的数量,即这个Deployment控制2个Pod;spec.selector
是Label Selector(标签选择器),表示这个Deployment会选择Label为app=nginx的Pod;spec.template
是Pod的定义,内容与Pod中的定义完全一致。
将上面Deployment的定义保存到deployment.yaml
文件中,使用kubectl创建这个Deployment。
使用kubectl get
查看Deployment和Pod,可以看到READY
值为2/2,前一个2表示当前有2个Pod运行,后一个2表示期望有2个Pod,AVAILABLE
为2表示有2个Pod是可用的。
bash
$ kubectl create -f deployment.yaml
deployment.apps/nginx created
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 2/2 2 2 4m5s
- Deployment如何控制Pod
继续查询Pod,如下所示。
bash
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-7f98958cdf-tdmqk 1/1 Running 0 13s
nginx-7f98958cdf-txckx 1/1 Running 0 13s
如果删掉一个Pod,您会发现立马会有一个新的Pod被创建出来,如下所示,这就是前面所说的Deployment会确保有2个Pod在运行,如果删掉一个,Deployment会重新创建一个,如果某个Pod故障或有其他问题,Deployment会自动拉起这个Pod。
bash
$ kubectl delete pod nginx-7f98958cdf-txckx
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-7f98958cdf-tdmqk 1/1 Running 0 21s
nginx-7f98958cdf-tesqr 1/1 Running 0 1s
看到有如下两个名为nginx-7f98958cdf-tdmqk
和nginx-7f98958cdf-tesqr
的Pod, 其中nginx是直接使用Deployment的名称,-7f98958cdf-tdmqk和-7f98958cdf-tesqr是kubernetes随机生成的后缀。
您也许会发现这两个后缀中前面一部分是相同的,都是7f98958cdf,这是因为Deployment不是直接控制Pod的,Deployment是通过一种名为ReplicaSet的控制器控制Pod,通过如下命令可以查询ReplicaSet,其中rs是ReplicaSet的缩写。
bash
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-7f98958cdf 2 2 2 1m
这个ReplicaSet的名称为nginx-7f98958cdf
,后缀-7f98958cdf也是随机生成的。
Deployment控制Pod的方式如图2所示,Deployment控制ReplicaSet,ReplicaSet控制Pod。
图2 Deployment通过ReplicaSet控制Pod

如果使用kubectl describe
命令查看Deployment的详情,您就可以看到ReplicaSet,如下所示,可以看到有一行NewReplicaSet: nginx-7f98958cdf (2/2 replicas created)
,而且Events里面事件确是把ReplicaSet的实例扩容到2个。在实际使用中您也许不会直接操作ReplicaSet,但了解Deployment通过控制ReplicaSet来控制Pod会有助于您定位问题。
bash
$ kubectl describe deploy nginx
Name: nginx
Namespace: default
CreationTimestamp: Sun, 16 Dec 2018 19:21:58 +0800
Labels: app=nginx
...
NewReplicaSet: nginx-7f98958cdf (2/2 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 5m deployment-controller Scaled up replica set nginx-7f98958cdf to 2
- 升级
在实际应用中,升级是一个常见的场景,Deployment能够很方便地支撑应用升级。
Deployment可以设置不同的升级策略,有如下两种。
- RollingUpdate:滚动升级,即逐步创建新Pod再删除旧Pod,为默认策略。
- Recreate:替换升级,即先把当前Pod删掉再重新创建Pod。
Deployment的升级可以是声明式的,也就是说只需要修改Deployment的YAML定义即可,比如使用kubectl edit
命令将上面Deployment中的镜像修改为nginx:alpine。修改完成后再查询ReplicaSet和Pod,发现创建了一个新的ReplicaSet,Pod也重新创建了。
bash
$ kubectl edit deploy nginx
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-6f9f58dffd 2 2 2 1m
nginx-7f98958cdf 0 0 0 48m
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-6f9f58dffd-tdmqk 1/1 Running 0 1m
nginx-6f9f58dffd-tesqr 1/1 Running 0 1m
Deployment可以通过maxSurge和maxUnavailable两个参数控制升级过程中同时重新创建Pod的比例,这在很多时候是非常有用,配置如下所示。
yaml
spec:
strategy:
rollingUpdate:
maxSurge: 0.25
maxUnavailable: 0.25
type: RollingUpdate
-
maxSurge:表示在滚动更新过程中,允许超出期望副本数的最大实例数或比例,即决定可以同时创建多少个新Pod替换旧Pod,默认值为25%。实际升级过程中,比例会换算为绝对数,并向上取整。
-
例如spec.replicas为2,默认状态下最多同时创建2*0.25=1个(向上取整)Pod,即系统中最多同时存在3个Pod。
-
maxUnavailable:表示在滚动更新过程中,允许处于不可用状态的最大实例数量或比例,即实际运行Pod数可低于期望副本数的最大限制,默认为25%。实际升级过程中,比例会换算为绝对数,并向下取整。
-
例如spec.replicas为2,默认状态下最多有2*0.25=0个(向下取整)Pod失效,即实际运行Pod数不可低于期望副本数,系统中最少有2个Pod处于运行状态。换言之,在升级过程中,一直会有2个Pod处于运行状态,每次新建一个Pod,等这个Pod创建成功后再删掉一个旧Pod,直至Pod全部为新Pod。
- 回滚
回滚也称为回退,即当发现升级出现问题时,让应用回到老的版本。Deployment可以非常方便地回滚到老版本。
例如上面升级的新版镜像有问题,可以执行kubectl rollout undo命令进行回滚。
bash
$ kubectl rollout undo deployment nginx
deployment.apps/nginx rolled back
Deployment之所以能如此容易地做到回滚,是因为Deployment是通过ReplicaSet控制Pod的,升级后之前ReplicaSet都一直存在,Deployment回滚做的就是使用之前的ReplicaSet再次把Pod创建出来。Deployment中保存ReplicaSet的数量可以使用revisionHistoryLimit参数限制,默认值为10。
2.有状态负载(StatefulSet)
- 有状态负载(StatefulSet)
Deployment控制器下的Pod都有个共同特点,那就是每个Pod除了名称和IP地址不同,其余完全相同。需要的时候,Deployment可以通过Pod模板创建新的Pod;不需要的时候,Deployment就可以删除任意一个Pod。
但是在某些场景下,这并不满足需求,比如有些分布式的场景,要求每个Pod都有自己单独的状态时,比如分布式数据库,每个Pod要求有单独的存储,这时Deployment无法满足业务需求。
分布式有状态应用的特点主要是应用中每个部分的角色不同(即分工不同),比如数据库有主备、Pod之间有依赖,在Kubernetes中部署有状态应用对Pod有如下要求:
-
Pod能够被别的Pod找到,要求Pod有固定的标识。
-
每个Pod有单独存储,Pod被删除恢复后,必须读取原来的数据,否则状态就会不一致。
Kubernetes提供了StatefulSet来解决这个问题,其具体如下:
-
StatefulSet给每个Pod提供固定名称,Pod名称增加从0-N的固定后缀,Pod重新调度后Pod名称和HostName不变。
-
StatefulSet通过Headless Service给每个Pod提供固定的访问域名。
-
StatefulSet通过创建固定标识的PVC保证Pod重新调度后还是能访问到相同的持久化数据。
图1 StatefulSet
- 创建Headless Service
如前所述,创建Statefulset需要一个Headless Service用于Pod访问。
使用如下文件描述Headless Service,其中:
- spec.clusterIP:必须设置为None,表示Headless Service。
- spec.ports.port:Pod间通信端口号。
- spec.ports.name:Pod间通信端口名称。
yaml
apiVersion: v1
kind: Service # 对象类型为Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- name: nginx # Pod间通信的端口名称
port: 80 # Pod间通信的端口号
selector:
app: nginx # 选择标签为app:nginx的Pod
clusterIP: None # 必须设置为None,表示Headless Service
执行如下命令创建Headless Service。
bash
# kubectl create -f headless.yaml
service/nginx created
创建完成后可以查询Service。
bash
# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 5s
- 创建Statefulset
Statefulset的YAML定义与其他对象基本相同,主要有两个差异点:
-
serviceName指定了Statefulset使用哪个Headless Service,需要填写Headless Service的名称。
-
volumeClaimTemplates是用来申请持久化声明PVC ,这里定义了一个名为data的模板,它会为每个Pod创建一个PVC,storageClassName指定了持久化存储的类型,在PV、PVC和StorageClass会详细介绍;volumeMounts是为Pod挂载存储。当然如果不需要存储的话可以删除volumeClaimTemplates和volumeMounts字段。
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
serviceName: nginx # headless service的名称
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: container-0
image: nginx:alpine
resources:
limits:
cpu: 100m
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts: # Pod挂载的存储
- name: data
mountPath: /usr/share/nginx/html # 存储挂载到/usr/share/nginx/html
imagePullSecrets:
- name: default-secret
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: csi-nas # 持久化存储的类型
执行如下命令创建。
bash
# kubectl create -f statefulset.yaml
statefulset.apps/nginx created
命令执行后,查询一下StatefulSet和Pod,可以看到Pod的名称后缀从0开始到2,逐个递增。
bash
# kubectl get statefulset
NAME READY AGE
nginx 3/3 107s
# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-0 1/1 Running 0 112s
nginx-1 1/1 Running 0 69s
nginx-2 1/1 Running 0 39s
此时如果手动删除nginx-1这个Pod,然后再次查询Pod,可以看到StatefulSet重新创建了一个名称相同的Pod,通过创建时间5s可以看出nginx-1是刚刚创建的。
bash
# kubectl delete pod nginx-1
pod "nginx-1" deleted
# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-0 1/1 Running 0 3m4s
nginx-1 1/1 Running 0 5s
nginx-2 1/1 Running 0 1m10s
进入容器查看容器的hostname,可以看到同样是nginx-0、nginx-1和nginx-2。
bash
# kubectl exec nginx-0 -- sh -c 'hostname'
nginx-0
# kubectl exec nginx-1 -- sh -c 'hostname'
nginx-1
# kubectl exec nginx-2 -- sh -c 'hostname'
nginx-2
同时可以看一下StatefulSet创建的PVC,可以看到这些PVC,都以"PVC名称-StatefulSet名称-编号"的方式命名,并且处于Bound状态。
bash
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-nginx-0 Bound pvc-f58bc1a9-6a52-4664-a587-a9a1c904ba29 1Gi RWX csi-nas 2m24s
data-nginx-1 Bound pvc-066e3a3a-fd65-4e65-87cd-6c3fd0ae6485 1Gi RWX csi-nas 101s
data-nginx-2 Bound pvc-a18cf1ce-708b-4e94-af83-766007250b0c 1Gi RWX csi-nas 71s
StatefulSet的网络标识
StatefulSet创建后,可以看下Pod是有固定名称的,那Headless Service是如何起作用的呢,那就是使用DNS,为Pod提供固定的域名,这样Pod间就可以使用域名访问,即便Pod被重新创建而导致Pod的IP地址发生变化,这个域名也不会发生变化。
Headless Service创建后,每个Pod的IP都会有下面格式的域名。
yaml
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
例如上面的三个Pod的域名就是:
- nginx-0.nginx.default.svc.cluster.local
- nginx-1.nginx.default.svc.cluster.local
- nginx-2.nginx.default.svc.cluster.local
实际访问时可以省略后面的..svc.cluster.local。
下面命令会使用tutum/dnsutils镜像创建一个Pod,进入这个Pod的容器,使用nslookup命令查看Pod对应的域名,可以发现能解析出Pod的IP地址。这里可以看到DNS服务器的地址是10.247.3.10,这是在创建CCE集群时默认安装CoreDNS插件,用于提供DNS服务,后续在Kubernetes网络会详细介绍CoreDNS的作用。
bash
$ kubectl run -i --tty --image tutum/dnsutils dnsutils --restart=Never --rm /bin/sh
If you don't see a command prompt, try pressing enter.
/ # nslookup nginx-0.nginx
Server: 10.247.3.10
Address: 10.247.3.10#53
Name: nginx-0.nginx.default.svc.cluster.local
Address: 172.16.0.31
/ # nslookup nginx-1.nginx
Server: 10.247.3.10
Address: 10.247.3.10#53
Name: nginx-1.nginx.default.svc.cluster.local
Address: 172.16.0.18
/ # nslookup nginx-2.nginx
Server: 10.247.3.10
Address: 10.247.3.10#53
Name: nginx-2.nginx.default.svc.cluster.local
Address: 172.16.0.19
此时如果手动删除这两个Pod,查询被StatefulSet重新创建的Pod的IP,然后使用nslookup命令解析Pod的域名,可以发现nginx-0.nginx和nginx-1.nginx仍然能解析到对应的Pod。这就保证了StatefulSet网络标识不变。
StatefulSet存储状态
上面说了StatefulSet可以通过PVC做持久化存储,保证Pod重新调度后还是能访问到相同的持久化数据,在删除Pod时,PVC不会被删除。
图2 StatefulSet的Pod重建过程
点击放大
下面将通过实际操作验证这一点是如何做到的,执行下面的命令,在nginx-1的目录/usr/share/nginx/html中写入一些内容,例如将index.html的内容修改为"hello world"。
bash
# kubectl exec nginx-1 -- sh -c 'echo hello world > /usr/share/nginx/html/index.html'
修改完后,如果在Pod中访问"http://localhost",那就会返回"hello world"。
bash
# kubectl exec -it nginx-1 -- curl localhost
hello world
此时如果手动删除nginx-1这个Pod,然后再次查询Pod,可以看到StatefulSet重新创建了一个名称相同的Pod,通过创建时间4s可以看出nginx-1是刚刚创建的。
bash
# kubectl delete pod nginx-1
pod "nginx-1" deleted
# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-0 1/1 Running 0 14m
nginx-1 1/1 Running 0 4s
nginx-2 1/1 Running 0 13m
再次访问该Pod的index.html页面,会发现仍然返回"hello world",这说明这个Pod仍然是访问相同的存储。
bash
# kubectl exec -it nginx-1 -- curl localhost
hello world
3.普通任务(Job)和定时任务(CronJob)
- 普通任务(Job)和定时任务(CronJob)
Job和CronJob是负责批量处理短暂的一次性任务(short lived one-off tasks),即仅执行一次的任务,它保证批处理任务的一个或多个Pod成功结束。
-
Job:是Kubernetes用来控制批处理型任务的资源对象。批处理业务与长期伺服业务(Deployment、StatefulSet)的主要区别是批处理业务的运行有头有尾,而长期伺服业务在用户不停止的情况下永远运行。Job管理的Pod根据用户的设置把任务成功完成就自动退出(Pod自动删除)。
-
CronJob:是基于时间的Job,就类似于Linux系统的crontab文件中的一行,在指定的时间周期运行指定的Job。
任务负载的这种用完即停止的特性特别适合一次性任务,比如持续集成。
- 创建Job
以下是一个Job配置,其计算π到2000位并打印输出。Job结束需要运行50个Pod,这个示例中就是打印π 50次,并行运行5个Pod,Pod如果失败最多重试5次。
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: pi-with-timeout
spec:
completions: 50 # 运行的次数,即Job结束需要成功运行的Pod个数
parallelism: 5 # 并行运行Pod的数量,默认为1
backoffLimit: 5 # 表示失败Pod的重试最大次数,超过这个次数不会继续重试。
activeDeadlineSeconds: 100 # 表示Pod超期时间,一旦达到这个时间,Job及其所有的Pod都会停止。
template: # Pod定义
spec:
containers:
- name: pi
image: perl
command:
- perl
- "-Mbignum=bpi"
- "-wle"
- print bpi(2000)
restartPolicy: Never
根据completions和parallelism的设置,可以将Job划分为以下几种类型。
表1 任务类型|Job类型
|说明
使用示例
一次性Job
创建一个Pod直至其成功结束
数据库迁移
固定结束次数的Job
依次创建一个Pod运行直至completions个成功结束
处理工作队列的Pod
固定结束次数的并行Job
依次创建多个Pod运行直至completions个成功结束
多个Pod同时处理工作队列
并行Job
创建一个或多个Pod直至有一个成功结束
多个Pod同时处理工作队列
- 创建CronJob
相比Job,CronJob就是一个加了定时的Job,CronJob执行时是在指定的时间创建出Job,然后由Job创建出Pod。
apiVersion: batch/v1
kind: CronJob
metadata:
name: cronjob-example
spec:
schedule: "0,15,30,45 * * * *" # 定时相关配置
jobTemplate: # Job的定义
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: pi
image: perl
command:
-
perl
-
"-Mbignum=bpi"
-
"-wle"
-
print bpi(2000)
CronJob的格式从前到后就是:
Minute
Hour
Day of month
Month
Day of week
如 "0,15,30,45 * * * * " ,前面逗号隔开的是分钟,后面第一个* 表示每小时,第二个 * 表示每个月的哪天,第三个表示每月,第四个表示每周的哪天。
如果您想要每个月的第一天里面每半个小时执行一次,那就可以设置为" 0,30 * 1 * * " 如果您想每个星期天的3am执行一次任务,那就可以设置为 "0 3 * * 0"。
4.守护进程集(DaemonSet)
守护进程集(DaemonSet)
DaemonSet(守护进程集)在集群的每个节点上运行一个Pod,且保证只有一个Pod,非常适合一些系统层面的应用,例如日志收集、资源监控等,这类应用需要每个节点都运行,且不需要太多实例,一个比较好的例子就是Kubernetes的kube-proxy。
DaemonSet跟节点相关,如果节点异常,也不会在其他节点重新创建。
图1 DaemonSet
点击放大
创建DaemonSet
下面是一个DaemonSet的示例。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx-daemonset
labels:
app: nginx-daemonset
spec:
selector:
matchLabels:
app: nginx-daemonset
template:
metadata:
labels:
app: nginx-daemonset
spec:
nodeSelector: # 节点选择,当节点拥有daemon=need时才在节点上创建Pod
daemon: need
containers:
- name: nginx-daemonset
image: nginx:alpine
resources:
limits:
cpu: 250m
memory: 512Mi
requests:
cpu: 250m
memory: 512Mi
imagePullSecrets:
- name: default-secret
这里可以看出DaemonSet没有Deployment或StatefulSet中的replicas参数,因为DaemonSet会在每个目标节点上固定部署一个Pod。
Pod模板中有个nodeSelector,指定了只在有"daemon=need"的节点上才创建Pod,如下图所示,DaemonSet只在指定标签的节点上创建Pod。如果需要在每一个节点上创建Pod可以删除该标签。
图2 DaemonSet在指定标签的节点上创建Pod
点击放大
创建DaemonSet:
$ kubectl create -f daemonset.yaml
daemonset.apps/nginx-daemonset created
查询发现nginx-daemonset没有Pod创建。
$ kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
nginx-daemonset 0 0 0 0 0 daemon=need 16s
$ kubectl get pods
No resources found in default namespace.
这是因为节点上没有daemon=need这个标签,使用如下命令可以查询节点的标签。
$ kubectl get node --show-labels
NAME STATUS ROLES AGE VERSION LABELS
192.168.0.212 Ready 83m v1.15.6-r1-20.3.0.2.B001-15.30.2 beta.kubernetes.io/arch=amd64 ...
192.168.0.94 Ready 83m v1.15.6-r1-20.3.0.2.B001-15.30.2 beta.kubernetes.io/arch=amd64 ...
192.168.0.97 Ready 83m v1.15.6-r1-20.3.0.2.B001-15.30.2 beta.kubernetes.io/arch=amd64 ...
给192.168.0.212这个节点打上标签,然后再查询,发现已经创建了一个Pod,并且这个Pod是在192.168.0.212这个节点上。
$ kubectl label node 192.168.0.212 daemon=need
node/192.168.0.212 labeled
$ kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
nginx-daemonset 1 1 0 1 0 daemon=need 116s
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx-daemonset-g9b7j 1/1 Running 0 18s 172.16.3.0 192.168.0.212
再给192.168.0.94这个节点打上标签,发现又创建了一个Pod:
$ kubectl label node 192.168.0.94 daemon=need
node/192.168.0.94 labeled
$ kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
nginx-daemonset 2 2 1 2 1 daemon=need 2m29s
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx-daemonset-6jjxz 0/1 ContainerCreating 0 8s 192.168.0.94
nginx-daemonset-g9b7j 1/1 Running 0 42s 172.16.3.0 192.168.0.212
如果修改掉192.168.0.94节点的标签,可以发现DaemonSet会删除这个节点上的Pod。
$ kubectl label node 192.168.0.94 daemon=no --overwrite
node/192.168.0.94 labeled
$ kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
nginx-daemonset 1 1 1 1 1 daemon=need 4m5s
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx-daemonset-g9b7j 1/1 Running 0 2m23s 172.16.3.0 192.168.0.212
5.亲和与反亲和调度
在守护进程集(DaemonSet)中讲到使用nodeSelector选择Pod要部署的节点,其实Kubernetes还支持更精细、更灵活的调度机制,那就是亲和(affinity)与反亲和(anti-affinity)调度。
Kubernetes支持节点和Pod两个层级的亲和与反亲和。通过配置亲和与反亲和规则,可以允许您指定硬性限制或者偏好,例如将前台Pod和后台Pod部署在一起、某类应用部署到某些特定的节点、不同应用部署到不同的节点等等。
Node Affinity(节点亲和)
您肯定也猜到了亲和性规则的基础肯定也是标签,先来看一下CCE集群中节点上有些什么标签。
$ kubectl describe node 192.168.0.212
Name: 192.168.0.212
Roles:
Labels: beta.kubernetes.io/arch=amd64
failure-domain.beta.kubernetes.io/is-baremetal=false
failure-domain.beta.kubernetes.io/region=cn-east-3
failure-domain.beta.kubernetes.io/zone=cn-east-3a
kubernetes.io/availablezone=cn-east-3a
kubernetes.io/hostname=192.168.0.212
node.kubernetes.io/subnetid=fd43acad-33e7-48b2-a85a-24833f362e0e
os.architecture=amd64
os.name=EulerOS_2.0_SP5
os.version=3.10.0-862.14.1.5.h328.eulerosv2r7.x86_64
这些标签都是在创建节点的时候CCE会自动添加上的,下面介绍几个在调度中会用到比较多的标签。
failure-domain.beta.kubernetes.io/region:表示节点所在的区域,如果上面这个节点标签值为cn-east-3,表示节点在上海一区域。
failure-domain.beta.kubernetes.io/zone:表示节点所在的可用区(availability zone)。
kubernetes.io/hostname:节点的hostname。
另外在Label:组织Pod的利器章节还介绍自定义标签,通常情况下,对于一个大型Kubernetes集群,肯定会根据业务需要定义很多标签。
在守护进程集(DaemonSet)中介绍了nodeSelector,通过nodeSelector可以让Pod只部署在具有特定标签的节点上。如下所示,Pod只会部署在拥有gpu=true这个标签的节点上。
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
nodeSelector: # 节点选择,当节点拥有gpu=true时才在节点上创建Pod
gpu: true
...
通过节点亲和性规则配置,也可以做到同样的事情,如下所示。
apiVersion: apps/v1
kind: Deployment
metadata:
name: gpu
labels:
app: gpu
spec:
selector:
matchLabels:
app: gpu
replicas: 3
template:
metadata:
labels:
app: gpu
spec:
containers:
- image: nginx:alpine
name: gpu
resources:
requests:
cpu: 100m
memory: 200Mi
limits:
cpu: 100m
memory: 200Mi
imagePullSecrets:
- name: default-secret
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
-
matchExpressions:
-
key: gpu
operator: In
values:
- "true"
看起来这要复杂很多,但这种方式可以得到更强的表达能力,后面会进一步介绍。
这里affinity表示亲和,nodeAffinity表示节点亲和,requiredDuringSchedulingIgnoredDuringExecution非常长,不过可以将这个分作两段来看:
前半段requiredDuringScheduling表示下面定义的规则必须强制满足(require)才会调度Pod到节点上。
后半段IgnoredDuringExecution表示已经在节点上运行的Pod不需要满足下面定义的规则,即去除节点上的某个标签,那些需要节点包含该标签的Pod不会被重新调度。
另外操作符operator的值为In,表示标签值需要在values的列表中,其他operator取值如下。
NotIn:标签的值不在某个列表中
Exists:某个标签存在
DoesNotExist:某个标签不存在
Gt:标签的值大于某个值(字符串比较)
Lt:标签的值小于某个值(字符串比较)
需要说明的是并没有nodeAntiAffinity(节点反亲和),因为NotIn和DoesNotExist可以提供相同的功能。
下面来验证这段规则是否生效,首先给192.168.0.212这个节点打上gpu=true的标签。
$ kubectl label node 192.168.0.212 gpu=true
node/192.168.0.212 labeled
$ kubectl get node -L gpu
NAME STATUS ROLES AGE VERSION GPU
192.168.0.212 Ready 13m v1.15.6-r1-20.3.0.2.B001-15.30.2 true
192.168.0.94 Ready 13m v1.15.6-r1-20.3.0.2.B001-15.30.2
192.168.0.97 Ready 13m v1.15.6-r1-20.3.0.2.B001-15.30.2
创建这个Deployment,可以发现所有的Pod都部署在了192.168.0.212这个节点上。
$ kubectl create -f affinity.yaml
deployment.apps/gpu created
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
gpu-6df65c44cf-42xw4 1/1 Running 0 15s 172.16.0.37 192.168.0.212
gpu-6df65c44cf-jzjvs 1/1 Running 0 15s 172.16.0.36 192.168.0.212
gpu-6df65c44cf-zv5cl 1/1 Running 0 15s 172.16.0.38 192.168.0.212
节点优先选择规则
上面讲的requiredDuringSchedulingIgnoredDuringExecution是一种强制选择的规则,节点亲和还有一种优先选择规则,即preferredDuringSchedulingIgnoredDuringExecution,表示会根据规则优先选择哪些节点。
为演示这个效果,先为上面的集群添加一个节点,且这个节点跟另外三个节点不在同一个可用区,创建完之后查询节点的可用区标签,如下所示,新添加的节点在cn-east-3c这个可用区。
$ kubectl get node -L failure-domain.beta.kubernetes.io/zone,gpu
NAME STATUS ROLES AGE VERSION ZONE GPU
192.168.0.100 Ready 7h23m v1.15.6-r1-20.3.0.2.B001-15.30.2 cn-east-3c
192.168.0.212 Ready 8h v1.15.6-r1-20.3.0.2.B001-15.30.2 cn-east-3a true
192.168.0.94 Ready 8h v1.15.6-r1-20.3.0.2.B001-15.30.2 cn-east-3a
192.168.0.97 Ready 8h v1.15.6-r1-20.3.0.2.B001-15.30.2 cn-east-3a
下面定义一个Deployment,要求Pod优先部署在可用区cn-east-3a的节点上,可以像下面这样定义,使用preferredDuringSchedulingIgnoredDuringExecution规则,给cn-east-3a设置权重(weight)为80,而gpu=true权重为20,这样Pod就优先部署在cn-east-3a的节点上。
apiVersion: apps/v1
kind: Deployment
metadata:
name: gpu
labels:
app: gpu
spec:
selector:
matchLabels:
app: gpu
replicas: 10
template:
metadata:
labels:
app: gpu
spec:
containers:
- image: nginx:alpine
name: gpu
resources:
requests:
cpu: 100m
memory: 200Mi
limits:
cpu: 100m
memory: 200Mi
imagePullSecrets:
- name: default-secret
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
operator: In
values:
-
cn-east-3a
-
weight: 20
preference:
matchExpressions:
- key: gpu
operator: In
values:
- "true"
来看实际部署后的情况,可以看到部署到192.168.0.212这个节点上的Pod有5个,而192.168.0.100上只有2个。
$ kubectl create -f affinity2.yaml
deployment.apps/gpu created
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
gpu-585455d466-5bmcz 1/1 Running 0 2m29s 172.16.0.44 192.168.0.212
gpu-585455d466-cg2l6 1/1 Running 0 2m29s 172.16.0.63 192.168.0.97
gpu-585455d466-f2bt2 1/1 Running 0 2m29s 172.16.0.79 192.168.0.100
gpu-585455d466-hdb5n 1/1 Running 0 2m29s 172.16.0.42 192.168.0.212
gpu-585455d466-hkgvz 1/1 Running 0 2m29s 172.16.0.43 192.168.0.212
gpu-585455d466-mngvn 1/1 Running 0 2m29s 172.16.0.48 192.168.0.97
gpu-585455d466-s26qs 1/1 Running 0 2m29s 172.16.0.62 192.168.0.97
gpu-585455d466-sxtzm 1/1 Running 0 2m29s 172.16.0.45 192.168.0.212
gpu-585455d466-t56cm 1/1 Running 0 2m29s 172.16.0.64 192.168.0.100
gpu-585455d466-t5w5x 1/1 Running 0 2m29s 172.16.0.41 192.168.0.212
上面这个例子中,对于节点排序优先级如下所示,有个两个标签的节点排序最高,只有cn-east-3a标签的节点排序第二(权重为80),只有gpu=true的节点排序第三,没有的节点排序最低。
图1 优先级排序顺序
点击放大
这里您看到Pod并没有调度到192.168.0.94这个节点上,这是因为这个节点上部署了很多其他Pod,资源使用较多,所以并没有往这个节点上调度,这也侧面说明preferredDuringSchedulingIgnoredDuringExecution是优先规则,而不是强制规则。
工作负载亲和(podAffinity)
节点亲和的规则只能影响Pod和节点之间的亲和,Kubernetes还支持Pod和Pod之间的亲和,例如将应用的前端和后端部署在一起,从而减少访问延迟。Pod亲和同样有requiredDuringSchedulingIgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution两种规则。
说明:
对于工作负载亲和来说,使用requiredDuringSchedulingIgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution规则时, topologyKey字段不允许为空。
来看下面这个例子,假设有个应用的后端已经创建,且带有app=backend的标签。
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
backend-658f6cb858-dlrz8 1/1 Running 0 2m36s 172.16.0.67 192.168.0.100
将前端frontend的pod部署在backend一起时,可以做如下Pod亲和规则配置。
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
labels:
app: frontend
spec:
selector:
matchLabels:
app: frontend
replicas: 3
template:
metadata:
labels:
app: frontend
spec:
containers:
- image: nginx:alpine
name: frontend
resources:
requests:
cpu: 100m
memory: 200Mi
limits:
cpu: 100m
memory: 200Mi
imagePullSecrets:
- name: default-secret
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- backend
创建frontend然后查看,可以看到frontend都创建到跟backend一样的节点上了。
$ kubectl create -f affinity3.yaml
deployment.apps/frontend created
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
backend-658f6cb858-dlrz8 1/1 Running 0 5m38s 172.16.0.67 192.168.0.100
frontend-67ff9b7b97-dsqzn 1/1 Running 0 6s 172.16.0.70 192.168.0.100
frontend-67ff9b7b97-hxm5t 1/1 Running 0 6s 172.16.0.71 192.168.0.100
frontend-67ff9b7b97-z8pdb 1/1 Running 0 6s 172.16.0.72 192.168.0.100
这里有个topologyKey字段(用于划分拓扑域),意思是先圈定topologyKey指定的范围,当节点上的标签键、值均相同时会被认为同一拓扑域,然后再选择下面规则定义的内容。这里每个节点上都有kubernetes.io/hostname,所以看不出topologyKey起到的作用。
如果backend有两个Pod,分别在不同的节点上。
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
backend-658f6cb858-5bpd6 1/1 Running 0 23m 172.16.0.40 192.168.0.97
backend-658f6cb858-dlrz8 1/1 Running 0 2m36s 172.16.0.67 192.168.0.100
给192.168.0.97和192.168.0.94打上prefer=true的标签。
$ kubectl label node 192.168.0.97 prefer=true
node/192.168.0.97 labeled
$ kubectl label node 192.168.0.94 prefer=true
node/192.168.0.94 labeled
$ kubectl get node -L prefer
NAME STATUS ROLES AGE VERSION PREFER
192.168.0.100 Ready 44m v1.15.6-r1-20.3.0.2.B001-15.30.2
192.168.0.212 Ready 91m v1.15.6-r1-20.3.0.2.B001-15.30.2
192.168.0.94 Ready 91m v1.15.6-r1-20.3.0.2.B001-15.30.2 true
192.168.0.97 Ready 91m v1.15.6-r1-20.3.0.2.B001-15.30.2 true
将podAffinity的topologyKey定义为prefer,则节点拓扑域的划分如图2所示。
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: prefer
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- backend
图2 拓扑域示意图
点击放大
调度时,会根据prefer标签划分节点拓扑域,本示例中192.168.0.97和192.168.0.94被划作同一拓扑域。如果当拓扑域中运行着app=backend的Pod,即使该拓扑域中并非所有节点均运行了app=backend的Pod(本例该拓扑域中仅192.168.0.97节点上存在app=backend的Pod),frontend同样会部署在此拓扑域中(这里的192.168.0.97或192.168.0.94)。
$ kubectl create -f affinity3.yaml
deployment.apps/frontend created
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
backend-658f6cb858-5bpd6 1/1 Running 0 26m 172.16.0.40 192.168.0.97
backend-658f6cb858-dlrz8 1/1 Running 0 5m38s 172.16.0.67 192.168.0.100
frontend-67ff9b7b97-dsqzn 1/1 Running 0 6s 172.16.0.70 192.168.0.97
frontend-67ff9b7b97-hxm5t 1/1 Running 0 6s 172.16.0.71 192.168.0.97
frontend-67ff9b7b97-z8pdb 1/1 Running 0 6s 172.16.0.72 192.168.0.94
工作负载反亲和(podAntiAffinity)
前面讲了Pod的亲和,通过亲和将Pod部署在一起,有时候需求却恰恰相反,需要将Pod分开部署,例如Pod之间部署在一起会影响性能的情况。
说明:
对于工作负载反亲和来说,使用requiredDuringSchedulingIgnoredDuringExecution规则时, Kubernetes默认的准入控制器 LimitPodHardAntiAffinityTopology要求topologyKey字段只能是kubernetes.io/hostname。如果您希望使用其他定制拓扑逻辑,可以更改或者禁用该准入控制器。
下面例子中定义了反亲和规则,这个规则表示根据kubernetes.io/hostname标签划分节点拓扑域,且如果该拓扑域中的某个节点上已经存在带有app=frontend标签的Pod,那么拥有相同标签的Pod将不能被调度到该拓扑域内的其他节点上。
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
labels:
app: frontend
spec:
selector:
matchLabels:
app: frontend
replicas: 5
template:
metadata:
labels:
app: frontend
spec:
containers:
- image: nginx:alpine
name: frontend
resources:
requests:
cpu: 100m
memory: 200Mi
limits:
cpu: 100m
memory: 200Mi
imagePullSecrets:
- name: default-secret
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname #节点拓扑域
labelSelector: #Pod标签匹配规则
matchExpressions:
- key: app
operator: In
values:
- frontend
$ kubectl create -f affinity4.yaml
deployment.apps/frontend created
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
frontend-6f686d8d87-8dlsc 1/1 Running 0 18s 172.16.0.76 192.168.0.100
frontend-6f686d8d87-d6l8p 0/1 Pending 0 18s
frontend-6f686d8d87-hgcq2 1/1 Running 0 18s 172.16.0.54 192.168.0.97
frontend-6f686d8d87-q7cfq 1/1 Running 0 18s 172.16.0.47 192.168.0.212
frontend-6f686d8d87-xl8hx 1/1 Running 0 18s 172.16.0.23 192.168.0.94
五、配置管理
1.ConfigMap
2.Secret
六、Kubernetes网络
1.容器网络
2.Service
3.Ingress
4.就绪探针(Readiness Probe)
5.网络策略(NetworkPolicy)
七、持久化存储
1.Volume
2.PV、PVC和StorageClass
八、认证与授权
1.ServiceAccount
2.RBAC
九、弹性伸缩
1.弹性伸缩
本文的引用仅限自我学习如有侵权,请联系作者删除。
参考知识
Kubernetes基础知识