• 沒有找到結果。

PV、PVC和StorageClass_云容器引擎 CCE_Kubernetes基础知识_持久化存储_华为云

N/A
N/A
Protected

Academic year: 2022

Share "PV、PVC和StorageClass_云容器引擎 CCE_Kubernetes基础知识_持久化存储_华为云"

Copied!
87
0
0

加載中.... (立即查看全文)

全文

(1)

Kubernetes 开源知识

文档版本 01

发布日期 2022-01-26

(2)

版权所有 © 华为技术有限公司 2022。 保留一切权利。

非经本公司书面许可,任何单位和个人不得擅自摘抄、复制本文档内容的部分或全部,并不得以任何形式传 播。

商标声明

和其他华为商标均为华为技术有限公司的商标。

本文档提及的其他所有商标或注册商标,由各自的所有人拥有。

注意

您购买的产品、服务或特性等应受华为公司商业合同和条款的约束,本文档中描述的全部或部分产品、服务或 特性可能不在您的购买或使用范围之内。除非合同另有约定,华为公司对本文档内容不做任何明示或暗示的声 明或保证。

由于产品版本升级或其他原因,本文档内容会不定期进行更新。除非另有约定,本文档仅作为使用指导,本文 档中的所有陈述、信息和建议不构成任何明示或暗示的担保。

(3)

目 录

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

(4)

9 弹性伸缩...81

(5)

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

(6)

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 弹性伸缩

(7)

2 容器与 Kubernetes

2.1 容器

容器与 Docker

容器技术起源于Linux,是一种内核虚拟化技术,提供轻量级的虚拟化,以便隔离进程 和资源。尽管容器技术已经出现很久,却是随着Docker的出现而变得广为人知。

Docker是第一个使容器能在不同机器之间移植的系统。它不仅简化了打包应用的流 程,也简化了打包应用的库和依赖,甚至整个操作系统的文件系统能被打包成一个简 单的可移植的包,这个包可以被用来在任何其他运行Docker的机器上使用。

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

更加便携和高效。

2-1 容器 vs 虚拟机

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

● 更高效的利用系统资源

由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,容器对系统资 源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传

(8)

统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以 运行更多数量的应用。

● 更快速的启动时间

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

● 一致的运行环境

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

● 更轻松的迁移

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

● 更轻松的维护和扩展

Docker使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也 使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。

此外,Docker团队同各个开源项目团队一起维护了大批高质量的官方镜像,既可 以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的 镜像制作成本。

Docker 容器典型使用流程

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

● 镜像:Docker镜像里包含了已打包的应用程序及其所依赖的环境。它包含应用程 序可用的文件系统和其他元数据,如镜像运行时的可执行文件路径。

● 镜像仓库:Docker镜像仓库用于存放Docker镜像,以及促进不同人和不同电脑之 间共享这些镜像。当编译镜像时,要么可以在编译它的电脑上运行,要么可以先 上传镜像到一个镜像仓库,然后下载到另外一台电脑上并运行它。某些仓库是公 开的,允许所有人从中拉取镜像,同时也有一些是私有的,仅部分人和机器可接 入。

● 容器:Docker容器通常是一个Linux容器,它基于Docker镜像被创建。一个运行中 的容器是一个运行在Docker主机上的进程,但它和主机,以及所有运行在主机上 的其他进程都是隔离的。这个进程也是资源受限的,意味着它只能访问和使用分 配给它的资源(CPU、内存等)。

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

(9)

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的内容

(10)

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镜像分配的版本号。

(11)

● 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集群的架构如下所示:

(12)

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网络中。

(13)

● 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

(14)

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'.

(15)

执行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 描述文件

(16)

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对象的概念以 及使用方法,并介绍对象之间的关系。

(17)

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服务器,从一个固定目录下对外提供文件服务,而辅助容器周期性 的从外部下载文件存到这个固定目录下。

(18)

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 资源的实际状态,创建时不需要配置。这个示例是一个最小集,其他参数定义后面会 逐步介绍。

(19)

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:

(20)

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提供了容器生命周期钩子,在容器的生命周期的特定阶段执行调用,比如 容器在停止前希望执行某项操作,就可以注册相应的钩子函数。目前提供的生命周期 钩子函数如下所示。

(21)

● 启动后处理(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,说明容器是健康的,定义方法如下所示。

(22)

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

(23)

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 # 容器启动后多久开始探测

(24)

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变得多起来后,就显得杂乱且难以管理,如下图所示。

(25)

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

(26)

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。

(27)

看下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就可以互相访问。

(28)

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内存。

(29)

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

(30)

看到有如下两个名为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

(31)

升级

在实际应用中,升级是一个常见的场景,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回滚做的就是使用之前的

(32)

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的创建方法。

(33)

使用如下文件描述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

(34)

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

(35)

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不会被删除。

(36)

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。

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

(37)

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

(38)

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的示例。

(39)

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:

(40)

$ 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)

调度。

(41)

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:

參考文獻

Outline

相關文件

EQUIPAMENTO SOCIAL A CARGO DO INSTITUTO DE ACÇÃO SOCIAL, Nº DE UTENTES E PESSOAL SOCIAL SERVICE FACILITIES OF SOCIAL WELFARE BUREAU, NUMBER OF USERS AND STAFF. 數目 N o

Valor acrescentado bruto : Receitas mais variação de existências, menos compras de bens e serviços para revenda, menos comissões pagas, menos despesas de exploração. Excedente

Valor acrescentado bruto : Receitas do jogo e dos serviços relacionados menos compras de bens e serviços para venda, menos comissões pagas menos despesas de

Valor acrescentado bruto : Receitas do jogo e dos serviços relacionados menos compras de bens e serviços para venda, menos comissões pagas menos despesas de

。又云。會也) 我唱泥牛吼 (云聞莫舉頭。又云。呵呵) 汝和木馬嘶 (云見應合眼。又云。撫掌) 但 看五六月 (云豈可徒然。又云吁) 氷片滿長街 (云事非草草。又云苦)

令諸天敷座 (云云) 。天帝說得免井厄 (云云) 。野干廣說有人樂生惡死。有人樂死惡生 (云 云)

師有言。湖外有知音。千里通消息。未審是什麼消息。師云。高著眼。進云。還許學

[r]