Kubernetes

kubernetes

介绍

github版本历史

版本发布时间结束支持时间
v1.332025 年 4 月 23 日2026 年 6 月 28 日
v1.322024 年 12 月 11 日2026 年 2 月 28 日
v1.312024 年 8 月 13 日2025 年 10 月 28 日
v1.302024 年 4 月 17 日2025 年 6 月 28 日
v1.292023 年 12 月 13 日2025 年 2 月 28 日
v1.282023 年 8 月 15 日2024 年 10 月 28 日
v1.272023 年 4 月 11 日2024 年 5 月 30 日
v1.262022 年 12 月 9 日2024 年 2 月 24 日
v1.252022 年 8 月 23 日2023 年 10 月 27 日
v1.242022 年 5 月 3 日2023 年 7 月 28 日
v1.232021 年 12 月 8 日2023 年 4 月 28 日
v1.222021 年 8 月 4 日2022 年 12 月 28 日
v1.212021 年 4 月 8 日2022 年 8 月 17 日
v1.202020 年 12 月 8 日2021 年 12 月 8 日
v1.192020 年 8 月 3 日2021 年 8 月 3 日
v1.182020 年 3 月 19 日2021 年 3 月 19 日
v1.172019 年 11 月 13 日2020 年 11 月 13 日
v1.162019 年 9 月 18 日2020 年 9 月 18 日
v1.152019 年 6 月 12 日2020 年 6 月 12 日
v1.142019 年 3 月 19 日2020 年 3 月 19 日
v1.132018 年 12 月 5 日2019 年 12 月 5 日

Kubernetes 早期版本通常都有大约 9 个月的主流支持期。从 Kubernetes 1.19 开始,官方将每个版本的支持窗口从原来的 9 个月延长至 1 年的主流支持期,之后进入 2 个月的维护模式期,共计约 14 个月的支持周期。

概述

Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,使声明式配置和自动化更容易。 Kubernetes 拥有一个庞大且快速增长的生态,其服务、支持和工具的使用范围相当广泛。

Kubernetes 这个名字源于希腊语,意为“舵手”或“飞行员”。k8s 这个缩写是因为 k 和 s 之间有八个字符的关系。 Google 在 2014 年开源了 Kubernetes 项目。Kubernetes 建立在 Google 超过 15 年、大规模运行生产工作负载经验的基础上, 结合了社区中最佳想法和实践。

让我们回顾一下为何 Kubernetes 能够裨益四方。

Container_Evolution

传统部署时代:

早期,各个组织是在物理服务器上运行应用程序。 由于无法限制在物理服务器中运行的应用程序资源使用,因此会导致资源分配问题。 例如,如果在同一台物理服务器上运行多个应用程序, 则可能会出现一个应用程序占用大部分资源的情况,而导致其他应用程序的性能下降。 一种解决方案是将每个应用程序都运行在不同的物理服务器上, 但是当某个应用程式资源利用率不高时,剩余资源无法被分配给其他应用程式, 而且维护许多物理服务器的成本很高。

虚拟化部署时代:

因此,虚拟化技术被引入了。虚拟化技术允许你在单个物理服务器的 CPU 上运行多台虚拟机(VM)。 虚拟化能使应用程序在不同 VM 之间被彼此隔离,且能提供一定程度的安全性, 因为一个应用程序的信息不能被另一应用程序随意访问。

虚拟化技术能够更好地利用物理服务器的资源,并且因为可轻松地添加或更新应用程序, 而因此可以具有更高的可扩缩性,以及降低硬件成本等等的好处。 通过虚拟化,你可以将一组物理资源呈现为可丢弃的虚拟机集群。

每个 VM 是一台完整的计算机,在虚拟化硬件之上运行所有组件,包括其自己的操作系统。

容器部署时代:

容器类似于 VM,但是更宽松的隔离特性,使容器之间可以共享操作系统(OS)。 因此,容器比起 VM 被认为是更轻量级的。且与 VM 类似,每个容器都具有自己的文件系统、CPU、内存、进程空间等。 由于它们与基础架构分离,因此可以跨云和 OS 发行版本进行移植。

容器因具有许多优势而变得流行起来,例如:

Kubernetes能做什么

Kubernetes 为你提供:

架构

Kubernetes 的架构包括控制平面和数据平面。控制平面负责管理集群的状态和配置信息,包括 kube-apiserver、etcd、kube-scheduler、kube-controller-manager、cloud-controller-manager 等组件。数据平面负责容器的运行和通信,包括 kubelet、kube-proxy、容器网络等组件。

components-of-kubernetes

Kubernetes集群主要由Master NodeWorker Node两类节点组成 K8S 是属于主从设备模型(Master-Slave 架构),即有 Master 节点负责核心的调度、管理和运维,Slave 节点则在执行用户的程序。但是在 K8S 中,主节点一般被称为Master Node ,而从节点则被称为Worker Node 或者 Node。

kubernetes_architecture

Master节点

Master节点指的是集群控制节点,管理和控制整个集群,Master组件可以在集群中任何节点上运行。但是为了简单起见,通常在一台VM/机器上启动所有Master组件,并且不会在此VM/机器上运行用户容器。基本上Kubernetes所有的控制命令都是发给它,它来负责具体的执行过程,我们后面所有执行的命令基本都是在Master节点上运行的

kube-apiserver

需部署在 master 节点上;是 Kubernetes 控制面的前端。集群服务的统一入口,各组件协调者,以RESTful API提供接口服务,所有对象资源的增删改查和监听操作都交给APIServer处理后再提交给Etcd存储。

kube-scheduler

kube-scheduler 节点调度器,根据调度算法为新创建的Pod选择一个Node节点,可以任意部署,可以部署在同一个节点上,也可以部署在不同的节点上。Scheduler在调度时会对集群的结构进行分析,当前各个节点的负载,以及应用对高可用、性能等方面的需求。

kube-controller-manager

kube-controller-manager运行管理控制器,负责执行各种控制器,处理集群中常规后台任务,维持副本期望数目,一个资源对应一个控制器,而 ControllerManager 就是负责管理这些控制器的。k8s中所有资源对象的自动化控制中心,维护管理集群的状态,比如故障检测,自动扩展,滚动更新等

Controller Manager作为集群内部的管理控制中心,负责集群内的Node、Pod副本、服务端点(Endpoint)、命名空间(Namespace)、服务账号(ServiceAccount)、资源定额(ResourceQuota)等的管理,当某个Node意外宕机时,Controller Manager会及时发现并执行自动化修复流程,确保集群始终处于预期的工作状态。

Controller Manager 内部包含Replication Controller、 Node Controller、ResourceQuota Controller、 Namespace Controller、 ServiceAccountController、 Token Controller、Service Controller及Endpoint Controller等多个Controller,每种Controller都负责一种具体的控制流程,而ControllerManager正是这些Con位oller的核心管理者。

ReplicationController 用来确保容器应用的副本数始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的 Pod 来替代;而如果异常多出来的容器也会自动回收。

在新版本的 Kubernetes 中建议使用 ReplicaSet 来取代 ReplicationController。ReplicaSet 跟 ReplicationController 没有本质的不同,只是名字不一样,并且 ReplicaSet 支持新的基于集合的标签 selector (in notin),Replication Controller对选择器仅支持基于等值的关系(= !=)。

虽然 ReplicaSet 可以独立使用,但一般还是建议使用 Deployment 来自动管理 ReplicaSet,这样就无需担心跟其他机制的不兼容问题(比如 ReplicaSet 不支持rolling-update滚动更新、rollout undo回滚、scale扩缩容,暂停rollout pause和继续rollout resume,但 Deployment 支持)。

cloud-controller-manager

运行与基础云提供商交互的控制器,从k8s 1.6之后出现的新功能 也包括了多个控制器:节点(Node)控制器、路由(Route)控制器、服务控制器、数据卷(Volume)控制器

etcd

etcd是Kubernetes提供默认的存储系统,保存所有集群数据,使用时需要为etcd数据提供备份计划。分布式键值存储系统,用于保存集群状态数据,比如Pod、Service等对象信息。

DNS

群集 DNS是一个DNS服务器,能够为 Kubernetes services提供 DNS记录。 由Kubernetes启动的容器自动将这个DNS服务器包含在他们的DNS searches中。

Cluster-level Logging

Cluster-level logging,负责保存容器日志,搜索/查看日志。

Node节点

真正的工作节点,运行业务应用的容器;节点组件运行在Node,提供Kubernetes运行时环境,以及维护Pod。

kubelet

kubelet 节点管理工具,Worker Node 的监视器,以及与 Master Node 的通讯,是Master在Node节点上的Agent(代理),管理本机运行容器的生命周期,比如创建容器,Pod挂载数据卷,下载secret,获取容器和节点状态等工作,kubelet 将每个Pod转换成一组容器,可以理解为当在master上执行创建、删除对象时,kubelet负责执行从kube-apiserver下达的指令,master派到node节点代表,管理本机容器

kube-proxy

kube-proxy 网络管理器,运行在每个node节点上,管理维护node上的网络规则,提供网络代理,四层负载均衡

kube-proxy 是集群中每个节点(node)上所运行的网络代理, 实现 Kubernetes 服务(Service) 概念的一部分。kube-proxy 负责为 Service实现了一种VIP(虚拟 IP)。kube-proxy 维护节点上的一些网络规则, 这些网络规则会允许从集群内部或外部的网络会话与 Pod 进行网络通信。如果操作系统提供了可用的数据包过滤层,则 kube-proxy 会通过它来实现网络规则。 否则,kube-proxy 仅做流量转发。 kubeproxy提供以下几种代理模式:

其他问题

创建Deployment流程

因为deployment包含replicaset包含pod,所以创建deployment的时候会连续创建三种资源

1、准备好一个包含应用程序的Deployment的yml文件,然后通过kubectl客户端工具发送给ApiServer,ApiServer 接收到客户端的请求并将资源内容存储到数据库(etcd)中。 3、Deployment Controller 监控Apiserver状态发生了改变,知道了Apiserver需要创建一个deployment资源对象 4、Deployment Controller 根据Apiserver的状态信息,想要去创建一个ReplicaSet资源对象,此时Apiserver检测到了Deployment Controller 的期望状态的变化,再次记录到数据库etcd中 5、ReplicaSet 监控 Apiserver 状态发生了改变,知道了 Apiserver 需要创建一个 ReplicaSet资源对象 6、ReplicaSet 根据 Apiserver 的状态信息,想要去创建一个 Pod,此时Apiserver检测到了 ReplicaSet 的期望状态的变化,再次记录到数据库etcd中

此时最低级别的pod的创建准备工作也完成,此时实际上pod,replicaset,deployment三种资源都没有实际被创建,刚才所说的创建类似于一种演习

7、Scheduler 监控 Apiserver 状态发生了改变,发现尚未被分配到具体执行节点(node)的Pod 8、Scheduler 根据一组相关规则将pod分配到可以运行它们的节点上,并更新数据库,记录pod分配情况。 8、Kubelet 监控 Apiserver状态发生了改变,此时kubelet知道自己该创建pod了,但是它本身并没有创建容器的功能,所以它通知了有创建容器功能的docker引擎,并把创建的约束信息(如镜像名称,镜像源等)传递给docker引擎 9、docker引擎根据收到的信息开始拉取镜像,创建容器,此时,deployment才算真正创建成功

kuberproxy运行在集群各个主机上,管理网络通信,如服务发现、负载均衡。例如当有数据发送到主机时,将其路由到正确的pod或容器。对于从主机上发出的数据,它可以基于请求地址发现远程服务器,并将数据正确路由,在某些情况下会使用轮训调度算法(Round-robin)将请求发送到集群中的多个实例。

随后我们通过Kubectl提交一个映射到该Pod的Server的创建请求,Controller Manager会通过Label标签查询到相关联的Pod实例,然后生成Service的Endpoints信息;接下来,所有Node上运行的Proxy进程通过API Server查询并监听Service对象及其对应的Endpoints信息,建立一个负载均衡器来实现Service访问到后端Pod的流量转发功能;

创建deployment流程

创建Pod流程

1、客户端提交Pod创建请求,可以通过API Server的Restful API,也可以使用kubectl命令行工具。支持的数据类型包括JSON和YAML。 2、API Server处理用户请求,存储Pod数据到etcd。 3、controller-manager监控API Server实时监听 Pod 事件,Pod 控制器知道了需要创建一个pod资源对象,通过API Server将pod的配置信息存储到etcd。 4、Scheduler通过API Server实时监听 Pod 事件,发现新的未被调度的Pod。尝试为Pod分配主机。 5、过滤主机 (调度预选):调度器用一组规则过滤掉不符合要求的主机。比如Pod指定了所需要的资源量,那么可用资源比Pod需要的资源量少的主机会被过滤掉。 6、主机打分(调度优选):对第一步筛选出的符合要求的主机进行打分,在主机打分阶段,调度器会考虑一些整体优化策略,比如把容一个Replication Controller的副本分布到不同的主机上,使用最低负载的主机等。 7、选择主机:选择打分最高的主机,进行binding操作,通过API Server将结果存储到etcd中。 8、kubelet通过API Server实时监听 Pod 事件,发现新Pod 被调度到当前节点,调用 cri 接口让容器运行时启动容器。 9、绑定成功后,scheduler会调用APIServer的API在etcd中创建一个boundpod对象,描述在一个工作节点上绑定运行的所有pod信息。运行在每个工作节点上的kubelet也会定期与etcd同步boundpod信息,一旦发现应该在该工作节点上运行的boundpod对象没有更新,则调用Docker API创建并启动pod内的容器。

创建pod流程

namespace无法删除

namespace 状态为 terminating, 无法删除 Error from server (AlreadyExists): object is being deleted: namespaces "monitoring" already exists

设置容器时区

设置K8S中的容器时区 为Asia/Shanghai

解决pod调度不均衡的问题

实际上,k8s在进行调度时,计算的就是requests的值,不管你limits设置多少,k8s都不关心。所以当这个值没有达到资源瓶颈时,理论上,该节点就会一直有pod调度上去。所以这个时候就会出现调度不均衡的问题。有什么解决办法?

给每一个pod设置requests和limits,如果资源充足,最好将requests和limits设置成一样的,提高Pod的QoS

重平衡,采取人为介入或定时任务方式,根据多维度去对当前pod分布做重平衡

容器环境JVM内存配置最佳实践

在容器环境下,Java只能获取服务器的配置,无法感知容器内存限制。可以通过-XX:MaxRAMPercentage限制堆大小。

说明

补充说明

-XX:+UseContainerSupport

-XX:InitialRAMPercentage

-XX:MaxRAMPercentage、-XX:MinRAMPercentage

查看使用的容器运行时

查看Pod和Service的ip范围

pv&pvc

PVC 是命名空间级别的资源,而 PV 是集群级别的资源。

PersistentVolume

持久卷

accessModes 访问模式包括

persistentVolumeReclaimPolicy 回收策略有:

一个volume卷处于以下几个阶段之一

pv中的spec: claimRef 字段用于表示pv与pvc之间的关系。

PersistentVolumeClaim

持久卷声明

yaml中command的使用

前面说了init容器initContainers,这主要是为应用容器做前期准备工作的,一般都会用到shell脚本,这就会用到command,这里写写command的用法。

command就是将命令在创建的容器中执行,有这些命令去完成一些工作,command用法和dockerfile中的cmd差不多, command可以单独写,也可以分成command和参数args,可以参考之前的CMD去理解,例如下面的写法都可以。

另外args还有一种写法,可以理解成args后面是一个.sh文件,command来直接执行一个脚本文件,可以写相对复杂的脚本。

最后贴一个官方写的一个rabbitmq的完整例子

日志采集

日志输出方式

和虚拟机/物理机不同,K8s的容器提供标准输出stdout文件两种方式。在容器中,标准输出将日志直接输出到stdout或stderr,而DockerEngine接管stdout和stderr文件描述符,将日志接收后按照DockerEngine配置的LogDriver规则进行处理;日志打印到文件的方式和虚拟机/物理机基本类似,只是日志可以使用不同的存储方式,例如默认存储、EmptyDir、HostVolume、NFS等。 虽然使用Stdout打印日志是Docker官方推荐的方式,但大家需要注意这个推荐是基于容器只作为简单应用的场景,实际的业务场景中我们还是建议大家尽可能使用文件的方式,主要的原因有以下几点:

  1. Stdout性能问题,从应用输出stdout到服务端,中间会经过好几个流程(例如普遍使用的JSON LogDriver):应用stdout -> DockerEngine -> LogDriver -> 序列化成JSON -> 保存到文件 -> Agent采集文件 -> 解析JSON -> 上传服务端。整个流程相比文件的额外开销要多很多,在压测时,每秒10万行日志输出就会额外占用DockerEngine 1个CPU核。
  2. Stdout不支持分类,即所有的输出都混在一个流中,无法像文件一样分类输出,通常一个应用中有AccessLog、ErrorLog、InterfaceLog(调用外部接口的日志)、TraceLog等,而这些日志的格式、用途不一,如果混在同一个流中将很难采集和分析。
  3. Stdout只支持容器的主程序输出,如果是daemon/fork方式运行的程序将无法使用stdout。
  4. 文件的Dump方式支持各种策略,例如同步/异步写入、缓存大小、文件轮转策略、压缩策略、清除策略等,相对更加灵活。

因此我们建议线上应用使用文件的方式输出日志,Stdout只在功能单一的应用或一些K8s系统/运维组件中使用。

Agent部署方式

采集容器日志,Agent有两种部署方式:

两种部署方式的优劣都显而易见:

Tip:正常情况下,优先使用 DaemonSet 的方式采集日志,如果单个Pod日志量特别大,超过一般 Agent 发送吞吐量,可以单独对该 Pod 使用 Sidecar 的方式采集日志。

采集方式

DaemonSet + Stdout

如果使用容器运行时的是docker,正常情况下我们可以在节点的docker路径中找到容器的stdout的日志,默认为/var/lib/docker/containers/{containerId}/{containerId}-json.log。在Kubernetes 1.14版本之前,kubelet会在/var/log/pods///.log建立一个软链接到stdout文件中。类似如下所示:

root@master0:/var/log/pods# tree

在Kubernetes 1.14版本之后,改成了/var/log/pods/<namespace>_<pod_name>_<pod_id>/<container_name>/<num>.log的形式。

root@master-0:/var/log/pods# tree

所以,对于 Agent 采集标准输出日志来说,也就是采集节点上的这些日志文件。一种简单粗暴的采集方式是,使用DaemonSet部署日志Agent,挂载/var/log/pods目录,Agent的配置文件使用类似/var/log/pod.log去通配日志文件,采集节点上所有的容器标准输出。

但是这样的局限在于:

当然现在的一些日志Agent比如Filebeat/Fluentd都针对性的做了支持,比如可以将namespace/pod等信息注入日志中,但仍然没有解决大部分的问题。所以,这种方式只适合简单的业务场景,后续也难以满足其他更多的日志需求。

DaemonSet + 日志文件

如果 Pod 里不仅仅是输出 stdout,还包括日志文件,就需要考虑到挂载日志文件到节点上,同时采用DaemonSet部署的Agent也需要挂载相同的目录,否则采用容器化部署的Agent 无法查看到相应的文件,更无法采集。业务Pod挂载日志路径的方式有以下几种:

(1) emtpyDir

emtpyDir 的生命周期跟随Pod,Pod销毁后其中存储的日志也会消失。

(2) hostPath

生命周期和Pod无关,Pod迁移或者销毁,日志文件还保留在现有磁盘上。

为了解决隔离性,避免多个Pod打印日志到相同的路径和文件中,我们需要使用 subPathExpr 字段从 Downward API 环境变量构造 subPath 目录名。该 VolumeSubpathEnvExpansion 功能从 Kubernetes1.15 开始默认开启,在1.17 GA。

(3) Pv

Pv的访问模式包括:

对于大部分的业务来说,都是Deployment无状态部署,需要挂载同一个Pv共享;对于一些中间件等有状态服务,一般会使用StatefulSet部署,每个Pod会使用独立的Pv。

虽然同样可以在Node上找到使用Pv挂载的对应日志文件,但是Pv根据不同的底层实现,在Node上的路径会有一定的区别。目前市面上大部分日志Agent均对这些挂载方式没有感知,所以你能做的和上面使用stdout的方式类似,也就是简单粗暴的让Agent将路径都挂载,使用通配的方式采集所有的日志,使用上的局限和stdout的方式同样一致。