十一、容器化 vs 虚拟化-Kubernetes(K8s)

文章目录


前言

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.容器

  1. 容器与Docker

容器技术起源于Linux,是一种内核虚拟化技术,提供轻量级的虚拟化,以便隔离进程和资源。尽管容器技术已经出现很久,却是随着Docker的出现而变得广为人知。Docker是第一个使容器能在不同机器之间移植的系统。它不仅简化了打包应用的流程,也简化了打包应用的库和依赖,甚至整个操作系统的文件系统能被打包成一个简单的可移植的包,这个包可以被用来在任何其他运行Docker的机器上使用。

容器和虚拟机具有相似的资源隔离和分配方式,容器虚拟化了操作系统而不是硬件,更加便携和高效。

图1 容器 vs 虚拟机

相比于使用虚拟机,容器有如下优点:

  • 更高效地利用系统资源

    由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,容器对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。

  • 更快速的启动时间

    传统的虚拟机技术启动应用服务往往需要数分钟,而Docker容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间,大大节约了开发、测试、部署的时间。

  • 一致的运行环境

    开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些问题并未在开发过程中被发现。而Docker的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性。

  • 更轻松的迁移

    由于Docker确保了执行环境的一致性,使得应用的迁移更加容易。Docker可以在很多平台上运行,无论是物理机、虚拟机,其运行结果是一致的。因此可以很轻易地将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。

  • 更轻松的维护和扩展

    Docker使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker团队同各个开源项目团队一起维护了大批高质量的官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大地降低了应用服务的镜像制作成本。

  1. Docker容器典型使用流程

Docker容器有如下三个主要概念:

  • 镜像:Docker镜像里包含了已打包的应用程序及其所依赖的环境。它包含应用程序可用的文件系统和其他元数据,如镜像运行时的可执行文件路径。
  • 镜像仓库:Docker镜像仓库用于存放Docker镜像,以及促进不同人和不同电脑之间共享这些镜像。当编译镜像时,要么可以在编译它的电脑上运行,要么可以先上传镜像到一个镜像仓库,然后下载到另外一台电脑上并运行它。某些仓库是公开的,允许所有人从中拉取镜像,同时也有一些是私有的,仅部分人和机器可接入。
  • 容器:Docker容器通常是一个Linux容器,它基于Docker镜像被创建。一个运行中的容器是一个运行在Docker主机上的进程,但它和主机,以及所有运行在主机上的其他进程都是隔离的。这个进程也是资源受限的,意味着它只能访问和使用分配给它的资源(CPU、内存等)。

典型的使用流程如图2所示:

图2 Docker容器典型使用流程

  • 首先开发者在开发环境机器上开发应用并制作镜像。

    Docker执行命令,构建镜像并存储在机器上。

  • 开发者发送上传镜像命令。

    Docker收到命令后,将本地镜像上传到镜像仓库。

  • 开发者向生产环境机器发送运行镜像命令。

    生产环境机器收到命令后,Docker会从镜像仓库拉取镜像到机器上,然后基于镜像运行容器。

  1. 使用示例

下面使用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

  1. Kubernetes是什么

Kubernetes是一个很容易地部署和管理容器化的应用软件系统,使用Kubernetes能够方便对容器进行调度和编排。

对应用开发者而言,可以把Kubernetes看成一个集群操作系统。Kubernetes提供服务发现、伸缩、负载均衡、自愈甚至选举等功能,让开发者从基础设施相关配置等解脱出来。

Kubernetes可以把大量的服务器看做一台巨大的服务器,在一台大服务器上面运行应用程序。无论Kubernetes的集群有多少台服务器,在Kubernetes上部署应用程序的方法永远一样。

图1 在Kubernetes集群上运行应用程序

  1. 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,最主要的功能是下载镜像和运行容器。
  1. 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)。
  1. 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可以让您无需关心底层存储资源如何创建、释放等动作,而只需要申明您需要何种类型的存储资源、多大的存储空间。
  1. 搭建Kubernetes集群

Kubernetes网站上有多种搭建Kubernetes集群的方法,例如minikube、kubeadm等。

  1. Kubernetes对象的描述

kubernetes中资源可以使用YAML描述(如果您对YAML格式不了解,可以参考YAML语法),也可以使用JSON。其内容可以分为如下四个部分:

  • typeMeta:对象类型的元信息,声明对象使用哪个API版本,哪个类型的对象。
  • objectMeta:对象的元信息,包括对象名称、使用的标签等。
  • spec:对象的期望状态,例如对象使用什么镜像、有多少副本等。
  • status:对象的实际状态,只能在对象创建后看到,创建对象时无需指定。

图4 YAML描述文件

  1. 在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. 基础命令

  1. 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获取。

  1. create

create命令用于根据文件或输入创建集群资源。

如果已经定义了相应资源的YAML或JSON文件,直接使用以下命令即可创建文件内定义的资源。

bash 复制代码
kubectl create -f <filename>
  1. 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范围内随机分配。

  1. run

创建单Pod或Deployment的快捷命令,适合测试环境。

例如:

bash 复制代码
kubectl run <deployname> --image=nginx:latest

同时,可以在创建Pod或Deployment时指定运行的命令:

bash 复制代码
kubectl run <deployname> --image=busybox --command -- ping example.com
  1. set

在对象上设置特定功能。

例如:

滚动更新一个Deployment的容器镜像改为1.0版本:

bash 复制代码
kubectl set image deployment/<deployname> <containername>=<containername>:1.0
  1. 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
  1. explain

查看文档或参考资料。

例如:

查看Pod的相关文档:

bash 复制代码
kubectl explain pod
  1. delete

根据资源名或标签删除资源。

例如:

立刻删除该Pod:

bash 复制代码
kubectl delete pod <podname> --now 
bash 复制代码
kubectl delete -f nginx.yaml
kubectl delete deployment <deployname>

3. 部署命令

  1. rollout

管理资源的发布。

例如:

查看指定资源的部署状态:

bash 复制代码
kubectl rollout status deployment/<deployname>

查看指定资源的发布历史:

bash 复制代码
kubectl rollout history deployment/<deployname>

回滚指定资源,默认回滚至上一个版本:

bash 复制代码
kubectl rollout undo deployment/test-nginx
  1. scale

scale用于程序在负载加重或缩小时将副本进行扩容或缩小。

bash 复制代码
kubectl scale deployment <deployname> --replicas=<newnumber>
  1. autoscale

autoscale命令提供了自动根据工作负载的CPU利用率对其副本进行扩缩的功能。autoscale命令会给工作负载(Deployment、ReplicaSet、StatefulSet)指定一个副本数的范围,在实际运行中根据所有Pod的平均CPU利用率自动在指定的范围内对Pod进行扩容或缩容。如果未指定目标利用率或设置为负数,将使用默认的自动扩缩策略。

bash 复制代码
kubectl autoscale deployment <deployname> --min=<minnumber> --max=<maxnumber> --cpu-percent=<cpu>

4. 集群管理命令

  1. 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>
  1. cluster-info

查看在集群中运行的插件:

bash 复制代码
kubectl cluster-info

查看详细信息:

bash 复制代码
kubectl cluster-info dump
  1. ‌top*

显示资源(CPU/Memory/Storage)使用,该命令需要集群中的Metrics Server正常运行。

  1. taint*`

修改一个或多个节点上的污点。

  1. certificate*

修改证书资源。

5. 故障诊断和调试命令

  1. describe

describe命令类似于get,同样用于获取资源的相关信息。不同的是,get获得的是定义该资源的详细信息,而describe获得的是资源在集群内相关的状态信息。describe命令同get类似,但是describe命令不支持-o选项,对于同一类型的资源,describe命令输出的信息格式,内容域相同。

bash 复制代码
kubectl describe pod <podname>

如果发现是查询某个资源的信息,使用get命令能够获取更加详尽的信息。但是如果想要查询某个资源的状态,如某个Pod并不是在running状态,这时需要获取更详尽的状态信息时,就应该使用describe命令。

  1. logs

logs命令用于显示Pod运行中,容器内程序输出到标准输出的内容。如果要获得tail -f的方式,需使用-f选项。

bash 复制代码
kubectl logs -f <podname>
  1. exec

与Docker的exec用法相似,如果一个Pod中,有多个容器,需要使用-c选项指定容器。

bash 复制代码
kubectl exec -it <podname> -- bash
kubectl exec -it <podname> -c <containername> -- bash
  1. port-forward*

转发一个或多个本地端口至一个Pod。

例如:

侦听本地端口5000并转发到创建的Pod里的端口6000:

bash 复制代码
kubectl port-forward deploy/my-deployment 5000:6000
  1. cp

复制文件或目录到容器:

bash 复制代码
kubectl cp /tmp/foo <podname>:/tmp/bar -c <containername>

将/tmp/foo本地文件复制到远程Pod中特定容器的/tmp/bar下。

  1. auth*

检查授权。

  1. attach*

attach命令效果类似于logs -f,退出查看使用ctrl-c。如果一个Pod中有多个容器,要查看具体的某个容器的输出,需要在Pod名后使用-c containername指定运行的容器。

bash 复制代码
kubectl attach <podname> -c <containername>

6. 高级命令

  1. replace

replace命令用于对已有资源进行更新、替换。当需要更新资源的一些属性的时候,如果修改副本数量,增加、修改标签,更改镜像版本,修改端口等,都可以直接修改原YAML文件,然后执行replace命令。

bash 复制代码
kubectl replace -f <filename>

资源名称不能被更新。

  1. 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>
  1. patch

如果一个容器已经在运行,这时需要对一些容器属性进行修改,又不想删除容器,或不方便通过replace的方式进行更新。Kubernetes还提供了一种在容器运行时,直接对容器进行修改的方式,就是patch命令。 例如已存在一个Pod的标签为app=nginx1,如果需要在运行过程中,将其修改为app=nginx2。

bash 复制代码
kubectl patch pod <podname> -p '{"metadata":{"labels":{"app":"nginx2"}}}'
  1. convert*

不同的API版本之间转换配置文件。

7. 设置命令

  1. label

更新资源上的标签:

bash 复制代码
kubectl label pods my-pod new-label=newlabel
  1. annotate

更新资源上的注释:

bash 复制代码
kubectl annotate pods my-pod icon-url=http://*****
  1. completion

用于实现kubectl工具自动补全。

8. 其他命令

  1. api-versions

打印受支持的API版本:

bash 复制代码
kubectl api-versions
  1. api-resources

打印支持的API资源:

bash 复制代码
kubectl api-resources
  1. config*

修改kubeconfig文件:用于访问API,比如配置认证信息。

  1. help

所有命令帮助。

  1. version

打印客户端和服务版本信息。

bash 复制代码
kubectl version

三、Pod、Label和Namespace

1.Pod:Kubernetes中的最小调度对象

  1. 容器组(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。

  1. 创建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
  1. 使用环境变量

环境变量是容器运行环境中设定的一个变量。

环境变量为应用提供极大的灵活性,您可以在应用程序中使用环境变量,在创建容器时为环境变量赋值,容器运行时读取环境变量的值,从而做到灵活的配置,而不是每次都重新编写应用程序制作镜像。

环境变量的使用方法如下所示,配置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

  1. 容器启动命令

启动容器就是启动主进程,但有些时候,启动主进程前,需要一些准备工作。比如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
  1. 容器的生命周期

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:健康检查机制

  1. 存活探针

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)中会详细介绍。

  1. 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,则说明已经重启。

  1. 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
  1. 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处于不健康状态,然后会重启容器。

  1. 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可以设置多次循环探测,这样在实际应用中健康检查的程序就不需要多次循环,这一点在开发应用时需要注意。

  1. 配置有效的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的利器

  1. 为什么需要Label

当资源变得非常多的时候,如何分类管理就非常重要了,Kubernetes提供了一种机制来为资源分类,那就是Label(标签)。Label非常简单,但是却很强大,Kubernetes中几乎所有资源都可以用Label来组织。

Label的具体形式是key-value的标记对,可以在创建资源的时候设置,也可以在后期添加和修改。

以Pod为例,当Pod变得多起来后,就显得杂乱且难以管理,如下图所示。

图1 没有分类组织的Pod

如果我们为Pod打上不同标签,那情况就完全不同了,如下图所示。

图2 使用Label组织的Pod

  1. 添加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
  1. 修改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:资源分组

  1. 为什么需要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这个命名空间中的东西,不会造成影响。

  1. 创建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。

  1. Namespace的隔离说明

Namespace只能做到组织上划分,对运行的对象来说,它不能做到真正的隔离。举例来说,如果两个Namespace下的Pod知道对方的IP,而Kubernetes依赖的底层网络没有提供Namespace之间的网络隔离的话,那这两个Pod就可以互相访问。

四、Pod的编排与调度

1.无状态负载(Deployment)

  1. 无状态负载(Deployment)

Pod是Kubernetes创建或部署的最小单位,但是Pod是被设计为相对短暂的一次性实体,Pod可以被驱逐(当节点资源不足时)、随着集群的节点崩溃而消失。Kubernetes提供了Controller(控制器)来管理Pod,Controller可以创建和管理多个Pod,提供副本管理、滚动升级和自愈能力,其中最为常用的就是Deployment。

图1 Deployment

一个Deployment可以包含一个或多个Pod副本,每个Pod副本的角色相同,所以系统会自动为Deployment的多个Pod副本分发请求。

Deployment集成了上线部署、滚动升级、创建副本、恢复上线的功能,在某种程度上,Deployment实现无人值守的上线,大大降低了上线过程的复杂性和操作风险。

  1. 创建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
  1. 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-tdmqknginx-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
  1. 升级

在实际应用中,升级是一个常见的场景,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。

  1. 回滚

回滚也称为回退,即当发现升级出现问题时,让应用回到老的版本。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)

  1. 有状态负载(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

  1. 创建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
  1. 创建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)

  1. 普通任务(Job)和定时任务(CronJob)

Job和CronJob是负责批量处理短暂的一次性任务(short lived one-off tasks),即仅执行一次的任务,它保证批处理任务的一个或多个Pod成功结束。

  • Job:是Kubernetes用来控制批处理型任务的资源对象。批处理业务与长期伺服业务(Deployment、StatefulSet)的主要区别是批处理业务的运行有头有尾,而长期伺服业务在用户不停止的情况下永远运行。Job管理的Pod根据用户的设置把任务成功完成就自动退出(Pod自动删除)。

  • CronJob:是基于时间的Job,就类似于Linux系统的crontab文件中的一行,在指定的时间周期运行指定的Job。

任务负载的这种用完即停止的特性特别适合一次性任务,比如持续集成。

  1. 创建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同时处理工作队列

  1. 创建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

beta.kubernetes.io/os=linux

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/arch=amd64

kubernetes.io/availablezone=cn-east-3a

kubernetes.io/eniquota=12

kubernetes.io/hostname=192.168.0.212

kubernetes.io/os=linux

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:

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:

labelSelector: #Pod标签匹配规则

matchExpressions:

  • key: app

operator: In

values:

  • frontend

创建并查看部署效果,示例中根据kubernetes.io/hostname标签划分节点拓扑域,在拥有kubernetes.io/hostname标签的节点中,每个节点的标签值均不同,因此一个拓扑域中只有一个节点。当一个拓扑域中(此处为一个节点)已经存在frontend标签的Pod时,该拓扑域不会被继续调度具有相同标签的Pod。本例中只有4个节点,因此还有一个Pod处于Pending状态无法调度。

$ 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基础知识


相关推荐
落日漫游6 小时前
K8s ConfigMap配置管理全解析
云原生·容器·kubernetes
我真的是大笨蛋6 小时前
K8S-Pod(下)
java·笔记·云原生·容器·kubernetes
一个天蝎座 白勺 程序猿8 小时前
Python爬虫(47)Python异步爬虫与K8S弹性伸缩:构建百万级并发数据采集引擎
爬虫·python·kubernetes
紫金修道8 小时前
k8s的容器操作指令
云原生·容器·kubernetes
方渐鸿8 小时前
【2024】k8s集群 图文详细 部署安装使用(两万字)
java·运维·容器·kubernetes·k8s·运维开发·持续部署
喝杯白开水!8 小时前
K8s中的控制器DaemonSet、StatefulSet、Job、CronJob、Server发现、健康检查、存储卷(PV),相关知识总结
云原生·容器·kubernetes
晓衣8 小时前
2025“獬豸杯”全国电子数据取证竞赛-k8s服务器取证wp
服务器·经验分享·程序人生·网络安全·容器·kubernetes·学习方法
学亮编程手记8 小时前
K8S v1.33 版本主要新特性介绍
java·容器·kubernetes
我爱云计算8 小时前
K8S详解(5万字详细教程)
linux·运维·云原生·容器·kubernetes