Kubernetes 开源知识
文档版本 01
发布日期 2022-01-26
版权所有 © 华为技术有限公司 2022。 保留一切权利。
非经本公司书面许可,任何单位和个人不得擅自摘抄、复制本文档内容的部分或全部,并不得以任何形式传 播。
商标声明
和其他华为商标均为华为技术有限公司的商标。
本文档提及的其他所有商标或注册商标,由各自的所有人拥有。
注意
您购买的产品、服务或特性等应受华为公司商业合同和条款的约束,本文档中描述的全部或部分产品、服务或 特性可能不在您的购买或使用范围之内。除非合同另有约定,华为公司对本文档内容不做任何明示或暗示的声 明或保证。
由于产品版本升级或其他原因,本文档内容会不定期进行更新。除非另有约定,本文档仅作为使用指导,本文 档中的所有陈述、信息和建议不构成任何明示或暗示的担保。
目 录
1 概述...1
2 容器与 Kubernetes...3
2.1 容器... 3
2.2 Kubernetes... 7
3 Pod、Label 和 Namespace... 13
3.1 Pod:Kubernetes 中的最小调度对象... 13
3.2 存活探针(Liveness Probe)...17
3.3 Label:组织 Pod 的利器... 20
3.4 Namespace:资源分组... 22
4 Pod 的编排与调度... 24
4.1 Deployment... 24
4.2 StatefulSet... 28
4.3 Job 和 CronJob... 32
4.4 DaemonSet...34
4.5 亲和与反亲和调度... 36
5 配置管理...44
5.1 ConfigMap... 44
5.2 Secret...45
6 Kubernetes 网络... 48
6.1 容器网络... 48
6.2 Service... 49
6.3 Ingress... 58
6.4 就绪探针(Readiness Probe)... 60
6.5 NetworkPolicy... 63
7 持久化存储...66
7.1 Volume...66
7.2 PV、PVC 和 StorageClass...68
8 认证与授权...73
8.1 ServiceAccount... 73
8.2 RBAC... 76
9 弹性伸缩...81
1 概述
Kubernetes是一个开源的容器编排部署管理平台,用于管理云平台中多个主机上的容 器化应用。Kubernetes的目标是让部署容器化的应用简单并且高效,Kubernetes提供 了应用部署、规划、更新、维护的一种机制。
对应用开发者而言,可以把Kubernetes看成一个集群操作系统。Kubernetes提供服务 发现、伸缩、负载均衡、自愈甚至选举等功能,让开发者从基础设施相关配置等解脱 出来。
您可以通过CCE控制台、Kubectl命令行、Kubernetes API使用云容器引擎所提供的 Kubernetes托管服务。在使用云容器引擎之前,你可以先行了解如下Kubernetes的相 关概念,以便您更完整的使用云容器引擎的所有功能。
容器与 Kubernetes
● 2.1 容器
● 2.2 Kubernetes
Pod、Label 和 Namespace
● 3.1 Pod:Kubernetes中的最小调度对象
● 3.2 存活探针(Liveness Probe)
● 3.3 Label:组织Pod的利器
● 3.4 Namespace:资源分组
Pod 的编排与调度
● 4.1 Deployment
● 4.2 StatefulSet
● 4.3 Job和CronJob
● 4.4 DaemonSet
● 4.5 亲和与反亲和调度
配置管理
● 5.1 ConfigMap
● 5.2 Secret
Kubernetes 网络
● 6.1 容器网络
● 6.2 Service
● 6.3 Ingress
● 6.4 就绪探针(Readiness Probe)
● 6.5 NetworkPolicy
持久化存储
● 7.1 Volume
● 7.2 PV、PVC和StorageClass
认证与授权
● 8.1 ServiceAccount
● 8.2 RBAC
弹性伸缩
● 9 弹性伸缩
2 容器与 Kubernetes
2.1 容器
容器与 Docker
容器技术起源于Linux,是一种内核虚拟化技术,提供轻量级的虚拟化,以便隔离进程 和资源。尽管容器技术已经出现很久,却是随着Docker的出现而变得广为人知。
Docker是第一个使容器能在不同机器之间移植的系统。它不仅简化了打包应用的流 程,也简化了打包应用的库和依赖,甚至整个操作系统的文件系统能被打包成一个简 单的可移植的包,这个包可以被用来在任何其他运行Docker的机器上使用。
容器和虚拟机具有相似的资源隔离和分配方式,容器虚拟化了操作系统而不是硬件,
更加便携和高效。
图2-1 容器 vs 虚拟机
相比于使用虚拟机,容器有如下优点:
● 更高效的利用系统资源
由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,容器对系统资 源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传
统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以 运行更多数量的应用。
● 更快速的启动时间
传统的虚拟机技术启动应用服务往往需要数分钟,而Docker容器应用,由于直接 运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的 启动时间,大大节约了开发、测试、部署的时间。
● 一致的运行环境
开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产 环境不一致,导致有些问题并未在开发过程中被发现。而Docker的镜像提供了除 内核外完整的运行时环境,确保了应用运行环境一致性。
● 更轻松的迁移
由于Docker确保了执行环境的一致性,使得应用的迁移更加容易。Docker可以在 很多平台上运行,无论是物理机、虚拟机,其运行结果是一致的。因此可以很轻 易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的 变化导致应用无法正常运行的情况。
● 更轻松的维护和扩展
Docker使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也 使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。
此外,Docker团队同各个开源项目团队一起维护了大批高质量的官方镜像,既可 以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的 镜像制作成本。
Docker 容器典型使用流程
Docker容器有如下三个主要概念:
● 镜像:Docker镜像里包含了已打包的应用程序及其所依赖的环境。它包含应用程 序可用的文件系统和其他元数据,如镜像运行时的可执行文件路径。
● 镜像仓库:Docker镜像仓库用于存放Docker镜像,以及促进不同人和不同电脑之 间共享这些镜像。当编译镜像时,要么可以在编译它的电脑上运行,要么可以先 上传镜像到一个镜像仓库,然后下载到另外一台电脑上并运行它。某些仓库是公 开的,允许所有人从中拉取镜像,同时也有一些是私有的,仅部分人和机器可接 入。
● 容器:Docker容器通常是一个Linux容器,它基于Docker镜像被创建。一个运行中 的容器是一个运行在Docker主机上的进程,但它和主机,以及所有运行在主机上 的其他进程都是隔离的。这个进程也是资源受限的,意味着它只能访问和使用分 配给它的资源(CPU、内存等)。
典型的使用流程如图2-2所示:
图2-2 Docker 容器典型使用流程
1. 首先开发者在开发环境机器上开发应用并制作镜像。
Docker执行命令,构建镜像并存储在机器上。
2. 开发者发送上传镜像命令。
Docker收到命令后,将本地镜像上传到镜像仓库。
3. 开发者向生产环境机器发送运行镜像命令。
生产环境机器收到命令后,Docker会从镜像仓库拉取镜像到机器上,然后基于镜 像运行容器。
使用示例
下面使用Docker将基于Nginx镜像打包一个容器镜像,并基于容器镜像运行应用,然 后推送到容器镜像仓库。
安装Docker
Docker几乎支持在所有操作系统上安装,用户可以根据需要选择要安装的Docker版 本。
在Linux操作系统下,可以使用如下命令快速安装Docker。
curl -fsSL get.docker.com -o get-docker.sh sh get-docker.sh
systemctl restart docker
Docker打包镜像
Docker提供了一种便捷的描述应用打包的方式,叫做Dockerfile,如下所示:
# 使用官方提供的Nginx镜像作为基础镜像 FROM nginx:alpine
# 执行一条命令修改Nginx镜像index.html的内容
RUN echo "hello world" > /usr/share/nginx/html/index.html
# 允许外界访问容器的80端口 EXPOSE 80
执行docker build命令打包镜像。
docker build -t hello .
其中-t表示给镜像加一个标签,也就是给镜像取名,这里镜像名为hello。. 表示在当前 目录下执行该打包命令。
执行docker images命令查看镜像,可以看到hello镜像已经创建成功。您还可以看到 一个Nginx镜像,这个镜像是从镜像仓库下载下来的,作为hello镜像的基础镜像使 用。
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE hello latest d120ec16dcea 17 minutes ago 158MB nginx alpine eeb27ee6b893 2 months ago 148MB
本地运行容器镜像
有了镜像后,您可以在本地执行docker run命令运行容器镜像。
# docker run -p 8080:80 hello
docker run命令会启动一个容器,命令中-p是将本地机器的8080端口映射到容器的80 端口,即本地机器的8080端口的流量会映射到容器的80端口,当您在本地机器访问 http://127.0.0.1:8080时,就会访问到容器中,此时浏览器中返回的内容应该就是
“hello world”。
把镜像推送到镜像仓库
华为云提供了容器镜像服务SWR,您也可以将镜像上传到SWR,下面演示如何将镜像 推送到SWR。详细的方法请参见客户端上传镜像,本文档后续的示例中将主要使用 SWR作为示例。
首先登录SWR控制台,在左侧选择“我的镜像”,然后单击右侧“客户端上传镜 像”,在弹出的窗口中单击“生成临时登录指令”,然后复制该指令在本地机器上执 行,登录到SWR镜像仓库。
上传镜像前需要给镜像取一个完整的名称,如下所示:
# 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镜像分配的版本号。
然后执行docker push命令就可以将镜像上传到SWR。
# docker push swr.cn-east-3.myhuaweicloud.com/container/hello:v1
当需要使用该镜像时,使用docker pull命令拉取(下载)该命令即可。
# docker pull swr.cn-east-3.myhuaweicloud.com/container/hello:v1
2.2 Kubernetes
Kubernetes 是什么
Kubernetes是一个很容易地部署和管理容器化的应用软件系统,使用Kubernetes能够 方便对容器进行调度和编排。
对应用开发者而言,可以把Kubernetes看成一个集群操作系统。Kubernetes提供服务 发现、伸缩、负载均衡、自愈甚至选举等功能,让开发者从基础设施相关配置等解脱 出来。
Kubernetes可以把大量的服务器看做一台巨大的服务器,在一台大服务器上面运行应 用程序。无论Kubernetes的集群有多少台服务器,在Kubernetes上部署应用程序的方 法永远一样。
图2-3 在 Kubernetes 集群上运行应用程序
Kubernetes 集群架构
Kubernetes集群包含master节点(控制节点)和node节点(计算节点/工作节点),
应用部署在node节点上,且可以通过配置选择应用部署在某些特定的节点上。
Kubernetes集群的架构如下所示:
图2-4 Kubernetes 集群架构
Master节点
Master节点是集群的控制节点,由API Server、Scheduler、Controller Manager和 ETCD四个组件构成。
● API Server:各组件互相通讯的中转站,接受外部请求,并将信息写到ETCD中。
● Controller Manager:执行集群级功能,例如复制组件,跟踪Node节点,处理节 点故障等等。
● Scheduler:负责应用调度的组件,根据各种条件(如可用的资源、节点的亲和性 等)将容器调度到Node上运行。
● ETCD:一个分布式数据存储组件,负责存储集群的配置信息。
在生产环境中,为了保障集群的高可用,通常会部署多个master,如CCE的集群高可 用模式就是3个master节点。
Node节点
Node节点是集群的计算节点,即运行容器化应用的节点。
● 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 中的基本对象
上面介绍Kubernetes集群的构成,下面将介绍Kubernetes中基本对象及它们之间的一 些关系。
图2-5 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集群,可以在华为云的CCE服务中购买,本文后续内容 都将在CCE中购买的集群上操作演示。
kubectl
kubectl是Kubernetes集群的命令行工具,您可以将kubectl安装在任意一台机器上,
通过kubectl命令操作Kubernetes集群。
CCE集群的kubectl安装请参见通过kubectl连接集群。连接后您可以执行kubectl cluster-info查看集群的信息,如下所示。
# 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可以查看集群中的Node节点信息。
# 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
Kubernetes 对象的描述
kubernetes中资源可以使用YAML描述(如果您对YAML格式不了解,可以参考YAML 语法),也可以使用JSON。其内容可以分为如下四个部分:
● typeMeta:对象类型的元信息,声明对象使用哪个API版本,哪个类型的对象。
● objectMeta:对象的元信息,包括对象名称、使用的标签等。
● spec:对象的期望状态,例如对象使用什么镜像、有多少副本等。
● status:对象的实际状态,只能在对象创建后看到,创建对象时无需指定。
图2-6 YAML 描述文件
在 Kubernetes 上运行应用
将图2-6中的内容去除status存为一个名为nginx-deployment.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
使用kubectl连接集群后,执行如下命令:
# kubectl create -f nginx-deployment.yaml deployment.apps/nginx created
命令执行后,Kubernetes集群中会创建3个Pod,使用如下命令可以查询到 Deployment和Pod:
# 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 Pod、Label 和 Namespace
3.1 Pod:Kubernetes 中的最小调度对象
Pod
Pod是Kubernetes创建或部署的最小单位。一个Pod封装一个或多个容器
(container)、存储资源(volume)、一个独立的网络IP以及管理控制容器运行方式 的策略选项。
Pod使用主要分为两种方式:
● Pod中运行一个容器。这是Kubernetes最常见的用法,您可以将Pod视为单个封装 的容器,但是Kubernetes是直接管理Pod而不是容器。
● Pod中运行多个需要耦合在一起工作、需要共享资源的容器。通常这种场景下应用 包含一个主容器和几个辅助容器(SideCar Container),如图3-1所示,例如主容 器为一个web服务器,从一个固定目录下对外提供文件服务,而辅助容器周期性 的从外部下载文件存到这个固定目录下。
图3-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 core CPU、
200Mi内存。
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表示使用文件方式创建。
$ kubectl create -f nginx.yaml pod/nginx created
Pod创建完成后,可以使用kubectl get pods命令查询Pod的状态,如下所示。
$ 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格式返回。
$ kubectl get pod nginx -o yaml
您还可以使用kubectl describe命令查看Pod的详情。
$ kubectl describe pod nginx
删除pod时,Kubernetes终止Pod中所有容器。 Kubernetes向进程发送SIGTERM信号 并等待一定的秒数(默认为30)让容器正常关闭。如果它没有在这个时间内关闭,
Kubernetes会发送一个SIGKILL信号杀死该进程。
Pod的停止与删除有多种方法,比如按名称删除,如下所示。
$ kubectl delete po nginx pod "nginx" deleted
同时删除多个Pod。
$ kubectl delete po pod1 pod2
删除所有Pod。
$ kubectl delete po --all pod "nginx" deleted
根据Label删除Pod,Label详细内容将会在下一个章节介绍。
$ kubectl delete po -l app=nginx pod "nginx" deleted
使用环境变量
环境变量是容器运行环境中设定的一个变量。
环境变量为应用提供极大的灵活性,您可以在应用程序中使用环境变量,在创建容器 时为环境变量赋值,容器运行时读取环境变量的值,从而做到灵活的配置,而不是每 次都重新编写应用程序制作镜像。
环境变量的使用方法如下所示,配置spec.containers.env字段即可。
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。
$ 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"]命令,其将会在容器启动时执行。
FROM ubuntu
ENTRYPOINT ["top", "-b"]
实际使用时,只需配置Pod的containers.command参数,该参数是list类型,第一个参 数为执行命令,后面均为命令的参数。
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参数,如下所示。
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
3.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),将在6.4 就绪探针
(Readiness Probe)中会详细介绍。
HTTP GET
HTTP GET方式是最常见的探测方法,其具体机制是向容器发送HTTP GET请求,如果 Probe收到2xx或3xx,说明容器是健康的,定义方法如下所示。
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。
$ kubectl create -f liveness-http.yaml pod/liveness-http created
如上,这个Probe往容器的80端口发送HTTP GET请求,如果请求不成功,Kubernetes 会重启容器。
查看Pod详情。
$ 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连接,如果连接成功建立,说明容器是健康 的,定义方法如下所示。
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则说明健康,定义方法如下所示。
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命令回显中有如下行。
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次后重启容器。
这些是创建时默认设置的,您也可以手动配置,如下所示。
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.3 Label:组织 Pod 的利器
为什么需要 Label
当资源变得非常多的时候,如何分类管理就非常重要了,Kubernetes提供了一种机制 来为资源分类,那就是Label(标签)。Label非常简单,但是却很强大,Kubernetes 中几乎所有资源都可以用Label来组织。
Label的具体形式是key-value的标记对,可以在创建资源的时候设置,也可以在后期添 加和修改。
以Pod为例,当Pod变得多起来后,就显得杂乱且难以管理,如下图所示。
图3-2 没有分类组织的 Pod
如果我们为Pod打上不同标签,那情况就完全不同了,如下图所示。
图3-3 使用 Label 组织的 Pod
添加 Label
Label的形式为key-value形式,使用非常简单,如下,为Pod设置了app=nginx和 env=prod两个Label。
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。
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS nginx 1/1 Running 0 50s app=nginx,env=prod
还可以使用-L只查询某些固定的Label。
$ 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。
$ 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,如下所示。
$ 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
3.4 Namespace:资源分组
为什么需要 Namespace
Label虽然好,但只用Label的话,那Label会非常多,有时候会有重叠,而且每次查询 之类的动作都带一堆Label非常不方便。Kubernetes提供了Namespace来做资源组织 和划分,使用多Namespace可以将包含很多组件的系统分成不同的组。Namespace也 可以用来做多租户划分,这样多个团队可以共用一个集群,使用的资源用Namespace 划分开。
不同的Namespace下面可以有相同的名字,Kubernetes中大部分资源可以用 Namespace划分,不过有些资源不行,它们属于全局资源,不属于某一个 Namespace,后面会逐步接触到。
通过如下命令可以查询到当前集群下的Namespace。
$ kubectl get ns
NAME STATUS AGE default Active 36m kube-node-realease Active 36m kube-public Active 36m kube-system Active 36m
到目前为止,我们都是在default Namespace下操作,当使用kubectl get而不指定 Namespace时,默认为default Namespace。
看下kube-system下面有些什么东西。
$ 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。
apiVersion: v1 kind: Namespace metadata:
name: custom-namespace
使用kubectl命令创建。
$ kubectl create -f custom-namespace.yaml namespace/custom-namespace created
您还可以使用kubectl create namespace命令创建。
$ kubectl create namespace custom-namespace namespace/custom-namespace created
在指定Namespace下创建资源。
$ kubectl create -f nginx.yaml -n custom-namespace pod/nginx created
这样在custom-namespace下,就创建了一个名为nginx的Pod。
Namespace 的隔离说明
Namespace只能做到组织上划分,对运行的对象来说,它不能做到真正的隔离。举例 来说,如果两个Namespace下的Pod知道对方的IP,而Kubernetes依赖的底层网络没 有提供Namespace之间的网络隔离的话,那这两个Pod就可以互相访问。
4 Pod 的编排与调度
4.1 Deployment
Deployment
Pod是Kubernetes创建或部署的最小单位,但是Pod是被设计为相对短暂的一次性实 体,Pod可以被驱逐(当节点资源不足时)、随着集群的节点崩溃而消失。
Kubernetes提供了Controller(控制器)来管理Pod,Controller可以创建和管理多个 Pod,提供副本管理、滚动升级和自愈能力,其中最为常用的就是Deployment。
图4-1 Deployment
一个Deployment可以包含一个或多个Pod副本,每个Pod副本的角色相同,所以系统 会自动为Deployment的多个Pod副本分发请求。
Deployment集成了上线部署、滚动升级、创建副本、恢复上线的功能,在某种程度 上,Deployment实现无人值守的上线,大大降低了上线过程的复杂性和操作风险。
创建 Deployment
以下示例为创建一个名为nginx的Deployment负载,使用nginx:latest镜像创建两个 Pod,每个Pod占用100m core CPU、200Mi内存。
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是可用 的。
$ 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,如下所示。
$ 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。
$ 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的缩写。
$ kubectl get rs
NAME DESIRED CURRENT READY AGE nginx-7f98958cdf 2 2 2 1m
这个ReplicaSet的名称为nginx-7f98958cdf,后缀-7f98958cdf也是随机生成的。
Deployment控制Pod的方式如图4-2所示,Deployment控制ReplicaSet,ReplicaSet控 制Pod。
图4-2 Deployment 通过 ReplicaSet 控制 Pod
如果使用kubectl describe命令查看Deployment的详情,您就可以看到ReplicaSet,如 下所示,可以看到有一行NewReplicaSet: nginx-7f98958cdf (2/2 replicas created),
而且Events里面事件确是把ReplicaSet的实例扩容到2个。在实际使用中您也许不会直 接操作ReplicaSet,但了解Deployment通过控制ReplicaSet来控制Pod会有助于您定位 问题。
$ 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也重新创建了。
$ 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的比例,这在很多时候是非常有用,配置如下所示。
spec:
strategy:
rollingUpdate:
maxSurge: 1 maxUnavailable: 0 type: RollingUpdate
● maxSurge:与Deployment中spec.replicas相比,可以有多少个Pod存在,默认值 是25%,比如spec.replicas为 4,那升级过程中就不能超过5个Pod存在,即按1个 的步伐升级,实际升级过程中会换算成数字,且换算会向上取整。这个值也可以 直接设置成数字。
● maxUnavailable:与Deployment中spec.replicas相比,可以有多少个Pod失效,
也就是删除的比例,默认值是25%,比如spec.replicas为4,那升级过程中就至少 有3个Pod存在,即删除Pod的步伐是1。同样这个值也可以设置成数字。
在前面的例子中,由于spec.replicas是2,如果maxSurge和maxUnavailable都为默认 值25%,那实际升级过程中,maxSurge允许最多3个Pod存在(向上取整,
2*1.25=2.5,取整为3),而maxUnavailable则不允许有Pod Unavailable(向上取 整,2*0.75=1.5,取整为2),也就是说在升级过程中,一直会有2个Pod处于运行状 态,每次新建一个Pod,等这个Pod创建成功后再删掉一个旧Pod,直至Pod全部为新 Pod。
回滚
回滚也称为回退,即当发现升级出现问题时,让应用回到老的版本。Deployment可以 非常方便的回滚到老版本。
例如上面升级的新版镜像有问题,可以执行kubectl rollout undo命令进行回滚。
$ kubectl rollout undo deployment nginx deployment.apps/nginx rolled back
Deployment之所以能如此容易的做到回滚,是因为Deployment是通过ReplicaSet控制 Pod的,升级后之前ReplicaSet都一直存在,Deployment回滚做的就是使用之前的
ReplicaSet再次把Pod创建出来。Deployment中保存ReplicaSet的数量可以使用 revisionHistoryLimit参数限制,默认值为10。
4.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来解决这个问题,其具体如下:
1. StatefulSet给每个Pod提供固定名称,Pod名称增加从0-N的固定后缀,Pod重新 调度后Pod名称和HostName不变。
2. StatefulSet通过Headless Service给每个Pod提供固定的访问域名,Service的概念 会在后面章节中详细介绍。
3. StatefulSet通过创建固定标识的PVC保证Pod重新调度后还是能访问到相同的持久 化数据。
下面将通过创建StatefulSet来体验StatefulSet的这些特性。
创建 Headless Service
如前所述,创建Statefulset需要一个Headless Service用于Pod访问,Service的概念会 在6.2 Service中详细介绍,这里先介绍Headless Service的创建方法。
使用如下文件描述Headless Service,其中:
● spec.clusterIP:必须设置为None,表示Headless Service。
● spec.ports.port:Pod间通信端口号。
● spec.ports.name:Pod间通信端口名称。
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。
# kubectl create -f headless.yaml service/nginx created
创建完成后可以查询Service。
# 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指定了持久化存储的 类型,在7.2 PV、PVC和StorageClass会详细介绍;volumeMounts是为Pod挂载 存储。当然如果不需要存储的话可以删除volumeClaimTemplates和
volumeMounts字段。
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 # 持久化存储的类型
执行如下命令创建。
# kubectl create -f statefulset.yaml statefulset.apps/nginx created
命令执行后,查询一下StatefulSet和Pod,可以看到Pod的名称后缀从0开始到2,逐个 递增。
# 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是刚刚创建的。
# 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。
# 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状态。
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGEdata-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 101sdata-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都会有下面格式的域名。
<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
实际访问时可以省略后面的.<namespace>.svc.cluster.local。
下面命令会使用tutum/dnsutils镜像创建一个Pod,进入这个Pod的容器,使用 nslookup命令查看Pod对应的域名,可以发现能解析出Pod的IP地址。这里可以看到 DNS服务器的地址是10.247.3.10,这是在创建CCE集群时默认安装CoreDNS插件,用 于提供DNS服务,后续在6 Kubernetes网络会详细介绍CoreDNS的作用。
$ 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不会被删除。
图4-3 StatefulSet 的 Pod 重建过程
下面将通过实际操作验证这一点是如何做到的,执行下面的命令,在nginx-1的目 录/usr/share/nginx/html中写入一些内容,例如将index.html的内容修改为“hello world”。
# kubectl exec nginx-1 -- sh -c 'echo hello world > /usr/share/nginx/html/index.html'
修改完后,如果在Pod中访问“http://localhost”,那就会返回“hello world”。
# kubectl exec -it nginx-1 -- curl localhost hello world
此时如果手动删除nginx-1这个Pod,然后再次查询Pod,可以看到StatefulSet重新创 建了一个名称相同的Pod,通过创建时间4s可以看出nginx-1是刚刚创建的。
# 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 仍然是访问相同的存储。
# kubectl exec -it nginx-1 -- curl localhost hello world
4.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次。
apiVersion: batch/v1 kind: Job
metadata:
name: pi-with-timeout spec:
completions: 50 # 运行的次数,即Job结束需要成功运行的Pod个数 parallelism: 5 # 并行运行Pod的数量,默认为1
backoffLimit: 5 # 表示失败Pod的重试最大次数,超过这个次数不会继续重试。
activeDeadlineSeconds: 10 # 表示Pod超期时间,一旦达到这个时间,Job及其所有的Pod都会停止。
template: # Pod定义 spec:
containers:
- name: pi image: perl command:
- perl
- "-Mbignum=bpi"
- "-wle"
- print bpi(2000) restartPolicy: Never
根据completions和parallelism的设置,可以将Job划分为以下几种类型。
表4-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/v1beta1 kind: CronJob
metadata:
name: cronjob-example spec:
schedule: "0,15,30,45 * * * *" # 定时相关配置 jobTemplate: # Job的定义 spec:
template:
spec:
restartPolicy: OnFailure containers:
- name: main image: pi
CronJob的格式从前到后就是:
● Minute
● Hour
● Day of month
● Month
● Day of week
如 "0,15,30,45 * * * * " ,前面逗号隔开的是分钟,后面第一个* 表示每小时,第二个 * 表示每个月的哪天,第三个表示每月,第四个表示每周的哪天。
如果您想要每个月的第一天里面每半个小时执行一次,那就可以设置为" 0,30 * 1 * * "
如果您想每个星期天的3am执行一次任务,那就可以设置为 "0 3 * * 0"。
更详细的CronJob格式说明请参见https://zh.wikipedia.org/wiki/Cron。
4.4 DaemonSet
DaemonSet
DaemonSet是这样一种对象(守护进程),它在集群的每个节点上运行一个Pod,且 保证只有一个Pod,这非常适合一些系统层面的应用,例如日志收集、资源监控等,这 类应用需要每个节点都运行,且不需要太多实例,一个比较好的例子就是Kubernetes 的kube-proxy。
DaemonSet跟节点相关,如果节点异常,也不会在其他节点重新创建。
图4-4 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
这里可以看出没有Deployment或StatefulSet中的replicas参数,因为是每个节点固定 一个。
Pod模板中有个nodeSelector,指定了只在有“daemon=need”的节点上才创建 Pod,如下图所示,DaemonSet只在指定标签的节点上创建Pod。如果需要在每一个节 点上创建Pod可以删除该标签。
图4-5 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 <none> 83m v1.15.6-r1-20.3.0.2.B001-15.30.2 beta.kubernetes.io/arch=amd64 ...
192.168.0.94 Ready <none> 83m v1.15.6-r1-20.3.0.2.B001-15.30.2 beta.kubernetes.io/arch=amd64 ...
192.168.0.97 Ready <none> 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 <none> 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
4.5 亲和与反亲和调度
在4.4 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: <none>
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。
另外在3.3 Label:组织Pod的利器章节还介绍自定义标签,通常情况下,对于一个大 型Kubernetes集群,肯定会根据业务需要定义很多标签。
在4.4 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: