再也不踩坑的kubernetes实战指南
上QQ阅读APP看书,第一时间看更新

2.2 Kubernetes基础

Kubernetes致力于提供跨主机集群的自动部署、扩展、高可用以及运行应用程序容器的平台,其遵循主从式架构设计,其组件可以分为管理单个节点(Node)组件和控制平面组件。Kubernetes Master是集群的主要控制单元,用于管理其工作负载并指导整个系统的通信。Kubernetes控制平面由各自的进程组成,每个组件都可以在单个主节点上运行,也可以在支持高可用集群的多个节点上运行。本节主要介绍Kubernetes的重要概念和相关组件。

2.2.1 Master节点

Master节点是Kubernetes集群的控制节点,在生产环境中不建议部署集群核心组件外的任何Pod,公司业务的Pod更是不建议部署到Master节点上,以免升级或者维护时对业务造成影响。

Master节点的组件包括:

  • APIServer。APIServer是整个集群的控制中枢,提供集群中各个模块之间的数据交换,并将集群状态和信息存储到分布式键-值(key-value)存储系统Etcd集群中。同时它也是集群管理、资源配额、提供完备的集群安全机制的入口,为集群各类资源对象提供增删改查以及watch的REST API接口。APIServer作为Kubernetes的关键组件,使用Kubernetes API和JSON over HTTP提供Kubernetes的内部和外部接口。
  • Scheduler。Scheduler是集群Pod的调度中心,主要是通过调度算法将Pod分配到最佳的节点(Node),它通过APIServer监听所有Pod的状态,一旦发现新的未被调度到任何Node节点的Pod(PodSpec.NodeName为空),就会根据一系列策略选择最佳节点进行调度,对每一个Pod创建一个绑定(binding),然后被调度的节点上的Kubelet负责启动该Pod。Scheduler是集群可插拔式组件,它跟踪每个节点上的资源利用率以确保工作负载不会超过可用资源。因此Scheduler必须知道资源需求、资源可用性以及其他约束和策略,例如服务质量、亲和力/反关联性要求、数据位置等。Scheduler将资源供应与工作负载需求相匹配以维持系统的稳定和可靠,因此Scheduler在调度的过程中需要考虑公平、资源高效利用、效率等方面的问题。
  • Controller Manager。Controller Manager是集群状态管理器(它的英文直译名为控制器管理器),以保证Pod或其他资源达到期望值。当集群中某个Pod的副本数或其他资源因故障和错误导致无法正常运行,没有达到设定的值时,Controller Manager会尝试自动修复并使其达到期望状态。Controller Manager包含NodeController、ReplicationController、EndpointController、NamespaceController、ServiceAccountController、ResourceQuotaController、ServiceController和TokenController,该控制器管理器可与API服务器进行通信以在需要时创建、更新或删除它所管理的资源,如Pod、服务断点等。
  • Etcd。Etcd由CoreOS开发,用于可靠地存储集群的配置数据,是一种持久性、轻量型、分布式的键-值(key-value)数据存储组件。Etcd作为Kubernetes集群的持久化存储系统,集群的灾难恢复和状态信息存储都与其密不可分,所以在Kubernetes高可用集群中,Etcd的高可用是至关重要的一部分,在生产环境中建议部署为大于3的奇数个数的Etcd,以保证数据的安全性和可恢复性。Etcd可与Master组件部署在同一个节点上,大规模集群环境下建议部署在集群外,并且使用高性能服务器来提高Etcd的性能和降低Etcd同步数据的延迟。

2.2.2 Node节点

Node节点也被称为Worker或Minion,是主要负责部署容器(工作负载)的单机(或虚拟机),集群中的每个节点都必须具备容器的运行环境(runtime),比如Docker及其他组件等。

Kubelet作为守护进程运行在Node节点上,负责监听该节点上所有的Pod,同时负责上报该节点上所有Pod的运行状态,确保节点上的所有容器都能正常运行。当Node节点宕机(NotReady状态)时,该节点上运行的Pod会被自动地转移到其他节点上。

Node节点包括:

  • Kubelet,负责与Master通信协作,管理该节点上的Pod。
  • Kube-Proxy,负责各Pod之间的通信和负载均衡。
  • Docker Engine,Docker引擎,负载对容器的管理。

2.2.3 Pod

1. 什么是Pod

Pod可简单地理解为是一组、一个或多个容器,具有共享存储/网络及如何运行容器的规范。Pad包含一个或多个相对紧密耦合的应用程序容器,处于同一个Pod中的容器共享同样的存储空间(Volume,卷或存储卷)、IP地址和Port端口,容器之间使用localhost:port相互访问。根据Docker的构造,Pod可被建模为一组具有共享命令空间、卷、IP地址和Port端口的Docker容器。

Pod包含的容器最好是一个容器只运行一个进程。每个Pod包含一个pause容器,pause容器是Pod的父容器,它主要负责僵尸进程的回收管理。

Kubernetes为每个Pod都分配一个唯一的IP地址,这样就可以保证应用程序使用同一端口,避免了发生冲突的问题。

一个Pod的状态信息保存在PodStatus对象中,在PodStatus中有一个Phase字段,用于描述Pod在其生命周期中的不同状态,参考表2-1。

表2-1 Pod状态字段Phase的不同取值

2. Pod探针

Pod探针用来检测容器内的应用是否正常,目前有三种实现方式,参考表2-2。

表2-2 Pod探针的实现方式

Pod探针每次检查容器后可能得到的容器状态,如表2-3所示。

表2-3 Pod探针检查容器后可能得到的状态

Kubelet有两种探针(即探测器)可以选择性地对容器进行检测,参考表2-4。

表2-4 探针的种类

3. Pod镜像拉取策略和重启策略

Pod镜像拉取策略。用于配置当节点部署Pod时,对镜像的操作方式,参考表2-5。

表2-5 镜像拉取策略

Pod重启策略。在Pod发生故障时对Pod的处理方式参考表2-6。

表2-6 Pod重启策略

4. 创建一个Pod

在生产环境中,很少会单独启动一个Pod直接使用,经常会用Deployment、DaemonSet、StatefulSet等方式调度并管理Pod,定义Pod的参数同时适应于Deployment、DaemonSet、StatefulSet等方式。

在Kubeadm安装方式下,kubernetes系统组件都是用单独的Pod启动的,当然有时候也会单独启动一个Pod用于测试业务等,此时可以单独创建一个Pod。

创建一个Pod的标准格式如下:

2.2.4 Label和Selector

当Kubernetes对系统的任何API对象如Pod和节点进行“分组”时,会对其添加Label(key=value形式的“键-值对”)用以精准地选择对应的API对象。而Selector(标签选择器)则是针对匹配对象的查询方法。注:键-值对就是key-value pair。

例如,常用的标签tier可用于区分容器的属性,如frontend、backend;或者一个release_track用于区分容器的环境,如canary、production等。

1. 定义Label

应用案例:

公司与xx银行有一条专属的高速光纤通道,此通道只能与192.168.7.0网段进行通信,因此只能将与xx银行通信的应用部署到192.168.7.0网段所在的节点上,此时可以对节点进行Label(即加标签):

    [root@K8S-master01 ~]# kubectl label node K8S-node02 region=subnet7
    node/K8S-node02 labeled

然后,可以通过Selector对其筛选:

最后,在Deployment或其他控制器中指定将Pod部署到该节点:

也可以用同样的方式对Service进行Label:

查看Labels:

还可以查看所有Version为v1的svc:

其他资源的Label方式相同。

2. Selector条件匹配

Selector主要用于资源的匹配,只有符合条件的资源才会被调用或使用,可以使用该方式对集群中的各类资源进行分配。

假如对Selector进行条件匹配,目前已有的Label如下:

选择app为reviews或者productpage的svc:

选择app为productpage或reviews但不包括version=v1的svc:

选择labelkey名为app的svc:

3. 修改标签(Label)

在实际使用中,Label的更改是经常发生的事情,可以使用overwrite参数修改标签。

修改标签,比如将version=v1改为version=v2:

4. 删除标签(Label)

删除标签,比如删除version:

2.2.5 Replication Controller和ReplicaSet

Replication Controller(复制控制器,RC)和ReplicaSet(复制集,RS)是两种部署Pod的方式。因为在生产环境中,主要使用更高级的Deployment等方式进行Pod的管理和部署,所以本节只对Replication Controller和Replica Set的部署方式进行简单介绍。

1. Replication Controller

Replication Controller可确保Pod副本数达到期望值,也就是RC定义的数量。换句话说,Replication Controller可确保一个Pod或一组同类Pod总是可用。

如果存在的Pod大于设定的值,则Replication Controller将终止额外的Pod。如果太小,Replication Controller将启动更多的Pod用于保证达到期望值。与手动创建Pod不同的是,用Replication Controller维护的Pod在失败、删除或终止时会自动替换。因此即使应用程序只需要一个Pod,也应该使用Replication Controller。Replication Controller类似于进程管理程序,但是Replication Controller不是监视单个节点上的各个进程,而是监视多个节点上的多个Pod。

定义一个Replication Controller的示例如下。

2. ReplicaSet

ReplicaSet是支持基于集合的标签选择器的下一代Replication Controller,它主要用作Deployment协调创建、删除和更新Pod,和Replication Controller唯一的区别是,ReplicaSet支持标签选择器。在实际应用中,虽然ReplicaSet可以单独使用,但是一般建议使用Deployment(部署)来自动管理ReplicaSet,除非自定义的Pod不需要更新或有其他编排等。

定义一个ReplicaSet的示例如下:

2.2.6 Deployment

虽然ReplicaSet可以确保在任何给定时间运行的Pod副本达到指定的数量,但是Deployment(部署)是一个更高级的概念,它管理ReplicaSet并为Pod和ReplicaSet提供声明性更新以及许多其他有用的功能,所以建议在实际使用中,使用Deployment代替ReplicaSet。

如果在Deployment对象中描述了所需的状态,Deployment控制器就会以可控制的速率将实际状态更改为期望状态。也可以在Deployment中创建新的ReplicaSet,或者删除现有的Deployment并使用新的Deployment部署所用的资源。

1. 创建Deployment

创建一个Deployment文件,并命名为dc-nginx.yaml,用于部署三个Nginx Pod:

示例解析

  • nginx-deployment:Deployment的名称。
  • replicas:创建Pod的副本数。
  • selector:定义Deployment如何找到要管理的Pod,与template的label(标签)对应。? template字段包含以下字段:
    •  app nginx使用label(标签)标记Pod。
    •  spec 表示Pod运行一个名字为nginx的容器。
    •  image 运行此Pod使用的镜像。
    •  Port 容器用于发送和接收流量的端口。

使用kubectlcreate创建此Deployment:

    [root@K8S-master01 2.2.8.1]# kubectl create -f dc-nginx.yaml
    deployment.apps/nginx-deployment created

使用kubectlget或者kubectldescribe查看此Deployment:

其中,

  • NAME:集群中Deployment的名称。
  • DESIRED:应用程序副本数。
  • CURRENT:当前正在运行的副本数。
  • UP-TO-DATE:显示已达到期望状态的被更新的副本数。
  • AVAILABLE:显示用户可以使用的应用程序副本数,当前为1,因为部分Pod仍在创建过程中。
  • AGE:显示应用程序运行的时间。

查看此时Deployment rollout的状态:

再次查看此Deployment:

查看此Deployment创建的ReplicaSet:

注意

ReplicaSe(t复制集,RS)的命名格式为[DEPLOYMENT-NAME]-[POD-TEMPLATE-HASH-VALUE]POD- TEMPLATE-HASH-VALUE,是自动生成的,不要手动指定。

查看此Deployment创建的Pod:

2. 更新Deployment

一般对应用程序升级或者版本迭代时,会通过Deployment对Pod进行滚动更新。

注意

当且仅当Deployment的Pod模板(即.spec.template)更改时,才会触发Deployment更新,例如更新label(标签)或者容器的image(镜像)。

假如更新Nginx Pod的image使用nginx:1.9.1:

当然也可以直接编辑Deployment,效果相同:

使用kubectl rollout status查看更新状态:

查看ReplicaSet:

通过describe查看Deployment的详细信息:

在describe中可以看出,第一次创建时,它创建了一个名为nginx-deployment-5c689d88bb的ReplicaSet,并直接将其扩展为3个副本。更新部署时,它创建了一个新的ReplicaSet,命名为nginx-deployment-6987cdb55b,并将其副本数扩展为1,然后将旧的ReplicaSet缩小为2,这样至少可以有2个Pod可用,最多创建了4个Pod。以此类推,使用相同的滚动更新策略向上和向下扩展新旧ReplicaSet,最终新的ReplicaSet可以拥有3个副本,并将旧的ReplicaSet缩小为0。

3. 回滚Deployment

当新版本不稳定时,可以对其进行回滚操作,默认情况下,所有Deployment的rollout历史都保留在系统中,可以随时回滚。

假设我们又进行了几次更新:

使用kubectl rollout history查看部署历史:

查看Deployment某次更新的详细信息,使用--revision指定版本号:

使用kubectl rollout undo回滚到上一个版本:

再次查看更新历史,发现REVISION5回到了canary:v1:

使用--to-revision参数回到指定版本:

4. 扩展Deployment

当公司访问量变大,三个Pod已无法支撑业务时,可以对其进行扩展。

使用kubectl scale动态调整Pod的副本数,比如增加Pod为5个:

查看Pod,此时Pod已经变成了5个:

5. 暂停和恢复Deployment更新

Deployment支持暂停更新,用于对Deployment进行多次修改操作。

使用kubectl rollout pause暂停Deployment更新:

然后对Deployment进行相关更新操作,比如更新镜像,然后对其资源进行限制:

通过rollout history可以看到没有新的更新:

使用kubectl rollout resume恢复Deployment更新:

可以查看到恢复更新的Deployment创建了一个新的RS(复制集):

可以查看Deployment的image(镜像)已经变为nginx:1.9.1

6. 更新Deployment的注意事项

(1)清理策略

在默认情况下,revision保留10个旧的ReplicaSet,其余的将在后台进行垃圾回收,可以在.spec.revisionHistoryLimit设置保留ReplicaSet的个数。当设置为0时,不保留历史记录。

(2)更新策略

  • .spec.strategy.type==Recreate,表示重建,先删掉旧的Pod再创建新的Pod。
  • .spec.strategy.type==RollingUpdate,表示滚动更新,可以指定maxUnavailable和maxSurge来控制滚动更新过程。
    •  .spec.strategy.rollingUpdate.maxUnavailable,指定在回滚更新时最大不可用的Pod数量,可选字段,默认为25%,可以设置为数字或百分比,如果maxSurge为0,则该值不能为0。
    •  .spec.strategy.rollingUpdate.maxSurge可以超过期望值的最大Pod数,可选字段,默认为25%,可以设置成数字或百分比,如果maxUnavailable为0,则该值不能为0。

(3)Ready策略

.spec.minReadySeconds是可选参数,指定新创建的Pod应该在没有任何容器崩溃的情况下视为Ready(就绪)状态的最小秒数,默认为0,即一旦被创建就视为可用,通常和容器探针连用。

2.2.7 StatefulSet

StatefulSet(有状态集)常用于部署有状态的且需要有序启动的应用程序。

1. StatefulSet的基本概念

StatefulSet主要用于管理有状态应用程序的工作负载API对象。比如在生产环境中,可以部署ElasticSearch集群、MongoDB集群或者需要持久化的RabbitMQ集群、Redis集群、Kafka集群和ZooKeeper集群等。

和Deployment类似,一个StatefulSet也同样管理着基于相同容器规范的Pod。不同的是,StatefulSet为每个Pod维护了一个粘性标识。这些Pod是根据相同的规范创建的,但是不可互换,每个Pod都有一个持久的标识符,在重新调度时也会保留,一般格式为StatefulSetName-Number。比如定义一个名字是Redis-Sentinel的StatefulSet,指定创建三个Pod,那么创建出来的Pod名字就为Redis-Sentinel-0、Redis-Sentinel-1、Redis-Sentinel-2。而StatefulSet创建的Pod一般使用Headless Service(无头服务)进行通信,和普通的Service的区别在于Headless Service没有ClusterIP,它使用的是Endpoint进行互相通信,Headless一般的格式为:

statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster. local。

说明:

  • serviceName为Headless Service的名字。
  • 0..N-1为Pod所在的序号,从0开始到N-1。
  • statefulSetName为StatefulSet的名字。
  • namespace为服务所在的命名空间。
  • .cluster.local为Cluster Domain(集群域)。

比如,一个Redis主从架构,Slave连接Master主机配置就可以使用不会更改的Master的Headless Service,例如Redis从节点(Slave)配置文件如下:

其中,redis-sentinel-master-ss-0.redis-sentinel-master-ss.public-service.svc.cluster.local是Redis Master的Headless Service。具体Headless可以参考2.2.13节。

2. 使用StatefulSet

一般StatefulSet用于有以下一个或者多个需求的应用程序:

  • 需要稳定的独一无二的网络标识符。
  • 需要持久化数据。
  • 需要有序的、优雅的部署和扩展。
  • 需要有序的、自动滚动更新。

如果应用程序不需要任何稳定的标识符或者有序的部署、删除或者扩展,应该使用无状态的控制器部署应用程序,比如Deployment或者ReplicaSet。

3. StatefulSet的限制

StatefulSet是Kubernetes 1.9版本之前的beta资源,在1.5版本之前的任何Kubernetes版本都没有。

Pod所用的存储必须由PersistentVolume Provisioner(持久化卷配置器)根据请求配置StorageClass,或者由管理员预先配置。

为了确保数据安全,删除和缩放StatefulSet不会删除与StatefulSet关联的卷,可以手动选择性地删除PVC和PV(关于PV和PVC请参考2.2.12节)。

StatefulSet目前使用Headless Service(无头服务)负责Pod的网络身份和通信,但需要创建此服务。

删除一个StatefulSet时,不保证对Pod的终止,要在StatefulSet中实现Pod的有序和正常终止,可以在删除之前将StatefulSet的副本缩减为0。

4. StatefulSet组件

定义一个简单的StatefulSet的示例如下:

其中,

  • kind: Service定义了一个名字为Nginx的Headless Service,创建的Service格式为nginx-0.nginx.default.svc.cluster.local,其他的类似,因为没有指定Namespace(命名空间),所以默认部署在default。
  • kind: StatefulSet定义了一个名字为web的StatefulSet,replicas表示部署Pod的副本数,本实例为2。
  • volumeClaimTemplates表示将提供稳定的存储PV(持久化卷)作持久化,PV可以是手动创建或者自动创建。在上述示例中,每个Pod将配置一个PV,当Pod重新调度到某个节点上时,Pod会重新挂载volumeMounts指定的目录(当前StatefulSet挂载到/usr/share/nginx/html),当删除Pod或者StatefulSet时,不会删除PV。

在StatefulSet中必须设置Pod选择器(.spec.selector)用来匹配其标签(.spec.template.metadata.labels)。在1.8版本之前,如果未配置该字段(.spec.selector),将被设置为默认值,在1.8版本之后,如果未指定匹配Pod Selector,则会导致StatefulSet创建错误。

当StatefulSet控制器创建Pod时,它会添加一个标签statefulset.kubernetes.io/pod-name,该标签的值为Pod的名称,用于匹配Service。

5. 创建StatefulSet

创建StatefulSet之前,需要提前创建StatefulSet持久化所用的PersistentVolumes(持久化卷,以下简称PV,也可以使用emptyDir不对数据进行保留),当然也可以使用动态方式自动创建PV,关于PV将在2.2.12节进行详解,本节只作为演示使用,也可以先阅读2.2.12节进行了解。

本例使用NFS提供静态PV,假如已有一台NFS服务器,IP地址为192.168.2.2,配置的共享目录如下:

Nginx0-5作为StatefulSet Pod的PV的数据存储目录,使用PersistentVolume创建PV,文件如下:

具体参数的配置及其含义,可参考2.2.12节。

创建PV:

查看PV:

创建StatefulSet:

查看PVC和PV,可以看到StatefulSet创建的两个Pod的PVC已经和PV绑定成功:

6. 部署和扩展保障

Pod的部署和扩展规则如下:

  • 对于具有N个副本的StatefulSet,将按顺序从0到N-1开始创建Pod。
  • 当删除Pod时,将按照N-1到0的反顺序终止。
  • 在缩放Pod之前,必须保证当前的Pod是Running(运行中)或者Ready(就绪)。
  • 在终止Pod之前,它所有的继任者必须是完全关闭状态。

StatefulSet的pod.Spec.TerminationGracePeriodSeconds不应该指定为0,设置为0对StatefulSet的Pod是极其不安全的做法,优雅地删除StatefulSet的Pod是非常有必要的,而且是安全的,因为它可以确保在Kubelet从APIServer删除之前,让Pod正常关闭。

当创建上面的Nginx实例时,Pod将按web-0、web-1、web-2的顺序部署3个Pod。在web-0处于Running或者Ready之前,web-1不会被部署,相同的,web-2在web-1未处于Running和Ready之前也不会被部署。如果在web-1处于Running和Ready状态时,web-0变成Failed(失败)状态,那么web-2将不会被启动,直到web-0恢复为Running和Ready状态。

如果用户将StatefulSet的replicas设置为1,那么web-2将首先被终止,在完全关闭并删除web-2之前,不会删除web-1。如果web-2终止并且完全关闭后,web-0突然失败,那么在web-0未恢复成Running或者Ready时,web-1不会被删除。

7. StatefulSet扩容和缩容

和Deployment类似,可以通过更新replicas字段扩容/缩容StatefulSet,也可以使用kubectlscale或者kubectlpatch来扩容/缩容一个StatefulSet。

(1)扩容

将上述创建的sts副本增加到5个(扩容之前必须保证有创建完成的静态PV,动态PV和emptyDir):

    [root@K8S-master01 2.2.7]# kubectl scale sts web --replicas=5
    statefulset.apps/web scaled

查看Pod及PVC的状态:

也可使用以下命令动态查看:

    kubectl get pods -w -l app=nginx

(2)缩容

在一个终端动态查看:

在另一个终端将副本数改为3:

    [root@K8S-master01 ~]# kubectl patch sts web -p '{"spec":{"replicas":3}}'
    statefulset.apps/web patched

此时可以看到第一个终端显示web-4和web-3的Pod正在被删除(或终止):

查看状态,此时PV和PVC不会被删除:

8. 更新策略

在Kubernetes 1.7以上的版本中,StatefulSet的.spec.updateStrategy字段允许配置和禁用容器的自动滚动更新、标签、资源限制以及StatefulSet中Pod的注释等。

(1)On Delete策略

OnDelete更新策略实现了传统(1.7版本之前)的行为,它也是默认的更新策略。当我们选择这个更新策略并修改StatefulSet的.spec.template字段时,StatefulSet控制器不会自动更新Pod,我们必须手动删除Pod才能使控制器创建新的Pod。

(2)RollingUpdate策略

RollingUpdate(滚动更新)更新策略会更新一个StatefulSet中所有的Pod,采用与序号索引相反的顺序进行滚动更新。

比如Patch一个名称为web的StatefulSet来执行RollingUpdate更新:

查看更改后的StatefulSet:

然后改变容器的镜像进行滚动更新:

如上所述,StatefulSet里的Pod采用和序号相反的顺序更新。在更新下一个Pod前,StatefulSet控制器会终止每一个Pod并等待它们变成Running和Ready状态。在当前顺序变成Running和Ready状态之前,StatefulSet控制器不会更新下一个Pod,但它仍然会重建任何在更新过程中发生故障的Pod,使用它们当前的版本。已经接收到请求的Pod将会被恢复为更新的版本,没有收到请求的Pod则会被恢复为之前的版本。

在更新过程中可以使用kubectl rollout status sts/<name>来查看滚动更新的状态:

查看更新后的镜像:

(3)分段更新

StatefulSet可以使用RollingUpdate更新策略的partition参数来分段更新一个StatefulSet。分段更新将会使StatefulSet中其余的所有Pod(序号小于分区)保持当前版本,只更新序号大于等于分区的Pod,利用此特性可以简单实现金丝雀发布(灰度发布)或者分阶段推出新功能等。注:金丝雀发布是指在黑与白之间能够平滑过渡的一种发布方式。

比如我们定义一个分区"partition":3,可以使用patch直接对StatefulSet进行设置:

然后再次patch改变容器的镜像:

删除Pod触发更新:

    kubectl delete po web-2
    pod "web-2" deleted

此时,因为Podweb-2的序号小于分区3,所以Pod不会被更新,还是会使用以前的容器恢复Pod。

将分区改为2,此时会自动更新web-2(因为之前更改了更新策略),但是不会更新web-0和web-1:

按照上述方式,可以实现分阶段更新,类似于灰度/金丝雀发布。查看最终的结果如下:

9. 删除StatefulSet

删除StatefulSet有两种方式,即级联删除和非级联删除。使用非级联方式删除StatefulSet时,StatefulSet的Pod不会被删除;使用级联删除时,StatefulSet和它的Pod都会被删除。

(1)非级联删除

使用kubectldeletestsxxx删除StatefulSet时,只需提供--cascade=false参数,就会采用非级联删除,此时删除StatefulSet不会删除它的Pod:

由于此时删除了StatefulSet,因此单独删除Pod时,不会被重建:

当再次创建此StatefulSet时,web-0会被重新创建,web-1由于已经存在而不会被再次创建,因为最初此StatefulSet的replicas是2,所以web-2会被删除,如下(忽略AlreadyExists错误):

(2)级联删除

省略--cascade=false参数即为级联删除:

    [root@K8S-master01 2.2.7]# kubectl delete statefulset web
    statefulset.apps "web" deleted
    [root@K8S-master01 2.2.7]# kubectl get po
    No resources found.

也可以使用-f参数直接删除StatefulSet和Service(此文件将sts和svc写在了一起):

2.2.8 DaemonSet

DaemonSet(守护进程集)和守护进程类似,它在符合匹配条件的节点上均部署一个Pod。

1. 什么是DaemonSet

DaemonSet确保全部(或者某些)节点上运行一个Pod副本。当有新节点加入集群时,也会为它们新增一个Pod。当节点从集群中移除时,这些Pod也会被回收,删除DaemonSet将会删除它创建的所有Pod。

使用DaemonSet的一些典型用法:

  • 运行集群存储daemon(守护进程),例如在每个节点上运行Glusterd、Ceph等。
  • 在每个节点运行日志收集daemon,例如Fluentd、Logstash。
  • 在每个节点运行监控daemon,比如Prometheus Node Exporter、Collectd、Datadog代理、New Relic代理或Ganglia gmond。
2. 编写DaemonSet规范

创建一个DaemonSet的内容大致如下,比如创建一个fluentd的DaemonSet:

(1)必需字段

和其他所有Kubernetes配置一样,DaemonSet需要apiVersion、kind和metadata字段,同时也需要一个.spec配置段。

(2)Pod模板

.spec唯一需要的字段是.spec.template。.spec.template是一个Pod模板,它与Pod具有相同的配置方式,但它不具有apiVersion和kind字段。

除了Pod必需的字段外,在DaemonSet中的Pod模板必须指定合理的标签。

在DaemonSet中的Pod模板必须具有一个RestartPolicy,默认为Always。

(3)Pod Selector

.spec.selector字段表示Pod Selector,它与其他资源的.spec.selector的作用相同。

.spec.selector表示一个对象,它由如下两个字段组成:

  • matchLabels,与ReplicationController的.spec.selector的作用相同,用于匹配符合条件的Pod。
  • matchExpressions,允许构建更加复杂的Selector,可以通过指定key、value列表以及与key和value列表相关的操作符。

如果上述两个字段都指定时,结果表示的是AND关系(逻辑与的关系)。

.spec.selector必须与.spec.template.metadata.labels相匹配。如果没有指定,默认是等价的,如果它们的配置不匹配,则会被API拒绝。

(4)指定节点部署Pod

如果指定了.spec.template.spec.nodeSelector,DaemonSet Controller将在与Node Selector(节点选择器)匹配的节点上创建Pod,比如部署在磁盘类型为ssd的节点上(需要提前给节点定义标签Label):

提示

Node Selector同样适用于其他Controller。

3. 创建DaemonSet

在生产环境中,公司业务的应用程序一般无须使用DaemonSet部署,一般情况下只有像Fluentd(日志收集)、Ingress(集群服务入口)、Calico(集群网络组件)、Node-Exporter(监控数据采集)等才需要使用DaemonSet部署到每个节点。本节只演示DaemonSet的使用。

比如创建一个nginxingress:

此时会在每个节点创建一个Pod:

注意

因为笔者的Master节点删除了Taint(Taint和Toleration见2.2.18),所以也能部署Ingress或者其他Pod,在生产环境下,在Master节点最好除了系统组件外不要部署其他Pod。

4. 更新和回滚DaemonSet

如果修改了节点标签(Label),DaemonSet将立刻向新匹配上的节点添加Pod,同时删除不能匹配的节点上的Pod。

在Kubernetes 1.6以后的版本中,可以在DaemonSet上执行滚动更新,未来的Kubernetes版本将支持节点的可控更新。

DaemonSet滚动更新可参考:https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/。

DaemonSet更新策略和StatefulSet类似,也有OnDelete和RollingUpdate两种方式。

查看上一节创建的DaemonSet更新方式:

提示

如果是其他DaemonSet,请确保更新策略是RollingUpdate(滚动更新)。

(1)命令式更新

    kubectl edit ds/<daemonset-name>
    kubectl patch ds/<daemonset-name> -p=<strategic-merge-patch>

(2)更新镜像

    kubectl set image
ds/<daemonset-name><container-name>=<container-new-image>--record=true

(3)查看更新状态

    kubectl rollout status ds/<daemonset-name>

(4)列出所有修订版本

    kubectl rollout history daemonset <daemonset-name>

(5)回滚到指定revision

    kubectl rollout undo daemonset <daemonset-name> --to-revision=<revision>

DaemonSet的更新和回滚与Deployment类似,此处不再演示。

2.2.9 ConfigMap

一般用ConfigMap管理一些程序的配置文件或者Pod变量,比如Nginx配置、MavenSetting配置文件等。

1. 什么是ConfigMap

ConfigMap是一个将配置文件、命令行参数、环境变量、端口号和其他配置绑定到Pod的容器和系统组件。ConfigMaps允许将配置与Pod和组件分开,这有助于保持工作负载的可移植性,使配置更易于更改和管理。比如在生产环境中,可以将Nginx、Redis等应用的配置文件存储在ConfigMap上,然后将其挂载即可使用。

相对于Secret,ConfigMap更倾向于存储和共享非敏感、未加密的配置信息,如果要在集群中使用敏感信息,最好使用Secret。

2. 创建ConfigMap

可以使用kubectl create configmap命令从目录、文件或字符值创建ConfigMap:

    kubectl create configmap <map-name><data-source>

说明:

  • map-name,ConfigMap的名称。
  • data-source,数据源,数据的目录、文件或字符值。

数据源对应于ConfigMap中的键-值对(key-value pair),其中,

  • key:文件名或密钥。
  • value:文件内容或字符值。

(1)从目录创建ConfigMap

可以使用kubectl create configmap命令从同一个目录中的多个文件创建ConfigMap。

创建一个配置文件目录并且下载两个文件作为测试配置文件:

创建ConfigMap,默认创建在default命名空间下,可以使用-n更改NameSpace(命名空间):

查看当前的ConfigMap:

可以看到,ConfigMap的内容与测试的配置文件内容一致:

(2)从文件创建ConfigMap

可以使用kubectl create configmap命令从单个文件或多个文件创建ConfigMap。

例如以configure-pod-container/configmap/kubectl/game.properties文件建立ConfigMap:

查看当前的ConfigMap:

也可以使用--from-file多次传入参数以从多个数据源创建ConfigMap:

查看当前的ConfigMap:

(3)从ENV文件创建ConfigMap

可以使用--from-env-file从ENV文件创建ConfigMap。

首先创建/下载一个测试文件,文件内容为key=value的格式:

创建ConfigMap:

查看当前的ConfigMap:

注意

如果使用--from-env-file多次传递参数以从多个数据源创建ConfigMap时,仅最后一个ENV生效。

(4)自定义data文件名创建ConfigMap

可以使用以下命令自定义文件名:

    kubectl create configmap game-config-3
--from-file=<my-key-name>=<path-to-file>

比如将game.properties文件定义为game-special-key:

(5)从字符值创建ConfigMaps

可以使用kubectl create configmap与--from-literal参数来定义命令行的字符值:

3. ConfigMap实践

本节主要讲解ConfigMap的一些常见使用方法,比如通过单个ConfigMap定义环境变量、通过多个ConfigMap定义环境变量和将ConfigMap作为卷使用等。

(1)使用单个ConfigMap定义容器环境变量

首先在ConfigMap中将环境变量定义为键-值对(key-value pair):

    kubectl create configmap special-config --from-literal=special.how=very

然后,将ConfigMap中定义的值special.how分配给Pod的环境变量SPECIAL_LEVEL_KEY:

(2)使用多个ConfigMap定义容器环境变量

首先定义两个或多个ConfigMap:

然后,在Pod中引用ConfigMap:

(3)将ConfigMap中所有的键-值对配置为容器的环境变量

创建含有多个键-值对的ConfigMap:

使用envFrom将ConfigMap所有的键-值对作为容器的环境变量,其中ConfigMap中的键作为Pod中的环境变量的名称:

(4)将ConfigMap添加到卷

大部分情况下,ConfigMap定义的都是配置文件,不是环境变量,因此需要将ConfigMap中的文件(一般为--from-file创建)挂载到Pod中,然后Pod中的容器就可引用,此时可以通过volume进行挂载。

例如,将名称为special-config的ConfigMap,挂载到容器的/etc/config/目录下:

此时Pod运行,会执行command的命令,即执行ls /etc/config/

    special.level
    special.type

注意

/etc/config/会被覆盖。

(5)将ConfigMap添加到卷并指定文件名

使用path字段可以指定ConfigMap挂载的文件名,比如将special.level挂载到/etc/config,并指定名称为keys:

此时启动Pod时会打印:very

(6)指定特定路径和文件权限

方式和Secret类似,可参考2.2.10.4节的内容。

4. ConfigMap限制

(1)必须先创建ConfigMap才能在Pod中引用它,如果Pod引用的ConfigMap不存在,Pod将无法启动。

(2)Pod引用的键必须存在于ConfigMap中,否则Pod无法启动。

(3)使用envFrom配置容器环境变量时,默认会跳过被视为无效的键,但是不影响Pod启动,无效的变量会记录在事件日志中,如下:

(4)ConfigMap和引用它的Pod需要在同一个命名空间。

2.2.10 Secret

Secret对象类型用来保存敏感信息,例如密码、令牌和SSH Key,将这些信息放在Secret中比较安全和灵活。用户可以创建Secret并且引用到Pod中,比如使用Secret初始化Redis、MySQL等密码。

1. 创建Secret

创建Secret的方式有很多,比如使用命令行Kubelet或者使用Yaml/Json文件创建等。

(1)使用Kubectl创建Secret

假设有些Pod需要访问数据库,可以将账户密码存储在username.txt和password.txt文件里,然后以文件的形式创建Secret供Pod使用。

创建账户信息文件:

以文件username.txt和password.txt创建Secret:

查看Secret:

默认情况下,get和describe命令都不会显示文件的内容,这是为了防止Secret中的内容被意外暴露。可以参考2.2.10.2一节的方式解码Secret。

(2)手动创建Secret

手动创建Secret,因为每一项内容必须是base64编码,所以要先对其进行编码:

    [root@K8S-master01 ~]# echo -n "admin" | base64
    YWRtaW4=
    [root@K8S-master01 ~]# echo -n "1f2d1e2e67df" | base64
    MWYyZDFlMmU2N2Rm

然后,创建一个文件,内容如下:

最后,使用该文件创建一个Secret:

    [root@K8S-master01 ~]# kubectl create -f db-user-secret.yaml
    secret/mysecret created
2. 解码Secret

Secret被创建后,会以加密的方式存储于Kubernetes集群中,可以对其进行解码获取内容。

首先以yaml的形式获取刚才创建的Secret:

然后通过--decode解码Secret:

    [root@K8S-master01 ~]# echo "MWYyZDFlMmU2N2Rm" | base64 --decode
    1f2d1e2e67df
3. 使用Secret

Secret可以作为数据卷被挂载,或作为环境变量以供Pod的容器使用。

(1)在Pod中使用Secret

在Pod中的volume里使用Secret:

①首先创建一个Secret或者使用已有的Secret,多个Pod可以引用同一个Secret。

②在spec.volumes下增加一个volume,命名随意,spec.volumes.secret.secretName必须和Secret对象的名字相同,并且在同一个Namespace中。

③将spec.containers.volumeMounts加到需要用到该Secret的容器中,并且设置spec.containers.volumeMounts.readOnly = true。

④使用spec.containers.volumeMounts.mountPath指定Secret挂载目录。

例如,将名字为mysecret的Secret挂载到Pod中的/etc/foo:

用到的每个Secret都需要在spec.volumes中指明,如果Pod中有多个容器,每个容器都需要自己的volumeMounts配置块,但是每个Secret只需要一个spec.volumes,可以根据自己的应用场景将多个文件打包到一个Secret中,或者使用多个Secret。

(2)自定义文件名挂载

挂载Secret时,可以使用spec.volumes.secret.items字段修改每个key的目标路径,即控制Secret Key在容器中的映射路径。

比如:

上述挂载方式,将mysecret中的username存储到了/etc/foo/my-group/my-username文件中,而不是/etc/foo/username(不指定items),由于items没有指定password,因此password不会被挂载。如果使用了spec.volumes.secret.items,只有在items中指定的key才会被挂载。

挂载的Secret在容器中作为文件,我们可以在Pod中查看挂载的文件内容:

(3)Secret作为环境变量

Secret可以作为环境变量使用,步骤如下:

①创建一个Secret或者使用一个已存在的Secret,多个Pod可以引用同一个Secret。

②为每个容器添加对应的Secret Key环境变量env.valueFrom.secretKeyRef。

比如,定义SECRET_USERNAME和SECRET_PASSWORD两个环境变量,其值来自于名字为mysecret的Secret:

挂载成功后,可以在容器中使用此变量:

    $ echo $SECRET_USERNAME
    admin
    $ echo $SECRET_PASSWORD
    1f2d1e2e67df
4. Secret文件权限

Secret默认挂载的文件的权限为0644,可以通过defaultMode方式更改权限:

更改的Secret挂载到/etc/foo目录的文件权限为0400。新版本可以直接指定400。

5. imagePullSecret

在拉取私有镜像库中的镜像时,可能需要认证后才可拉取,此时可以使用imagePullSecret将包含Docker镜像注册表密码的Secret传递给Kubelet,然后即可拉取私有镜像。

Kubernetes支持在Pod中指定Registry Key,用于拉取私有镜像仓库中的镜像。

首先创建一个镜像仓库账户信息的Secret:

如果需要访问多个Registry,则可以为每个注册表创建一个Secret,在Pods拉取镜像时,Kubelet会合并imagePullSecrets到.docker/config.json。注意Secret需要和Pod在同一个命名空间中。

创建完imagePullSecrets后,可以使用imagePullSecrets的方式引用该Secret:

6. 使用案例

本节演示的是Secret的一些常用配置,比如配置SSH密钥、创建隐藏文件等。

(1)定义包含SSH密钥的Pod

首先,创建一个包含SSH Key的Secret:

    $kubectl create secret generic ssh-key-secret
--from-file=ssh-privatekey=/path/to/.ssh/id_rsa
--from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub

然后将其挂载使用:

上述密钥会被挂载到/etc/secret-volume。注意,挂载SSH Key需要考虑安全性的问题。

(2)创建隐藏文件

为了将数据“隐藏”起来(即文件名以句点符号开头的文件),可以让Key以一个句点符号开始,比如定义一个以句点符号开头的Secret:

挂载使用:

此时会在/etc/secret-volume下创建一个.secret-file的文件。

2.2.11 HPA

1. 什么是HPA

HPA(Horizontal Pod Autoscaler,水平Pod自动伸缩器)可根据观察到的CPU、内存使用率或自定义度量标准来自动扩展或缩容Pod的数量。HPA不适用于无法缩放的对象,比如DaemonSet。

HPA控制器会定期调整RC或Deployment的副本数,以使观察到的平均CPU利用率与用户指定的目标相匹配。

HPA需要metrics-server(项目地址:https://github.com/kubernetes-incubator/metrics-server)获取度量指标,由于在高可用集群安装中已经安装了metrics-server,所以本节的实践部分无须再次安装。

2. HPA实践

在生产环境中,总会有一些意想不到的事情发生,比如公司网站流量突然升高,此时之前创建的Pod已不足以撑住所有的访问,而运维人员也不可能24小时守着业务服务,这时就可以通过配置HPA,实现负载过高的情况下自动扩容Pod副本数以分摊高并发的流量,当流量恢复正常后,HPA会自动缩减Pod的数量。

本节将测试实现一个Web服务器的自动伸缩特性,具体步骤如下:

首先启动一个Nginx服务:

临时开启nginx-server的端口,实际使用时需要定义service:

    kubectl expose deployment nginx-server --port=80

使用kubectl autoscale创建HPA:

    [root@K8S-master01 ~]# kubectl autoscale deployment nginx-server
--cpu-percent=10 --min=1 --max=10

此HPA将根据CPU的使用率自动增加和减少副本数量,上述设置的是CPU使用率超过10%(--cpu-percent参数指定)即会增加Pod的数量,以保持所有Pod的平均CPU利用率为10%,允许最大的Pod数量为10(--max),最少的Pod数为1(--min)。

查看当前HPA状态,因为未对其发送任何请求,所以当前CPU使用率为0%:

查看当前Nginx的Service地址:

增加负载:

    [root@K8S-master01 ~]# while true; do wget -q -O- http://10.108.160.23 >
/dev/null; done

1分钟左右再次查看HPA:

再次查看Pod,可以看到nginx-server的Pod已经在扩容阶段:

在增加负荷的终端,按Ctrl+C键终止访问。

停止1分钟后再次查看HPA和deployment,此时副本已经恢复为1:

2.2.12 Storage

本节介绍Kubernetes Storage的相关概念与使用,一般做持久化或者有状态的应用程序才会用到Storage。

1. Volumes

Container(容器)中的磁盘文件是短暂的,当容器崩溃时,kubelet会重新启动容器,但最初的文件将丢失,Container会以最干净的状态启动。另外,当一个Pod运行多个Container时,各个容器可能需要共享一些文件。Kubernetes Volume可以解决这两个问题。

(1)背景

Docker也有卷的概念,但是在Docker中卷只是磁盘上或另一个Container中的目录,其生命周期不受管理。虽然目前Docker已经提供了卷驱动程序,但是功能非常有限,例如从Docker 1.7版本开始,每个Container只允许一个卷驱动程序,并且无法将参数传递给卷。

另一方面,Kubernetes卷具有明确的生命周期,与使用它的Pod相同。因此,在Kubernetes中的卷可以比Pod中运行的任何Container都长,并且可以在Container重启或者销毁之后保留数据。Kubernetes支持多种类型的卷,Pod可以同时使用任意数量的卷。

从本质上讲,卷只是一个目录,可能包含一些数据,Pod中的容器可以访问它。要使用卷Pod需要通过.spec.volumes字段指定为Pod提供的卷,以及使用.spec.containers.volumeMounts字段指定卷挂载的目录。从容器中的进程可以看到由Docker镜像和卷组成的文件系统视图,卷无法挂载其他卷或具有到其他卷的硬链接,Pod中的每个Container必须独立指定每个卷的挂载位置。

(2)卷的类型

Kubernetes支持的卷的类型有很多,以下为常用的卷。

①awsElasticBlockStore(EBS)

awsElasticBlockStore卷挂载一个AWS EBS Volume到Pod中,与emptyDir卷不同的是,当移除Pod时EBS卷的内容不会被删除,这意味着可以将数据预先放置在EBS卷中,并且可以在Pod之间切换该数据。

使用awsElasticBlockStore卷的限制:

  • 运行Pod的节点必须是AWS EC2实例。
  • AWS EC2实例需要和EBS卷位于同一区域和可用区域。
  • EBS仅支持挂载卷的单个EC2实例。

在将Pod与EBS卷一起使用之前,需要先创建EBS卷,确保该卷的区域与集群的区域匹配,并检查size和EBS卷类型是否合理:

   aws ec2 create-volume --availability-zone=eu-west-1a --size=10
--volume-type=gp2

AWS EBS示例配置:

②CephFS

CephFS卷允许将一个已经存在的卷挂载到Pod中,和emptyDir卷不同的是,当移除Pod时,CephFS卷的内容不会被删除,这意味着可以将数据预先放置在CephFS卷中,并且可以在Pod之间切换该数据。CephFS卷可以被多个写设备同时挂载。

和AWS EBS一样,需要先创建CephFS卷后才能使用它。

关于CephFS的更多内容,可以参考以下文档:

https://github.com/kubernetes/examples/tree/master/staging/volumes/cephfs/

③ConfigMap

ConfigMap卷也可以作为volume使用,存储在ConfigMap中的数据可以通过ConfigMap类型的卷挂载到Pod中,然后使用该ConfigMap中的数据。引用ConfigMap对象时,只需要在volume中引用ConfigMap的名称即可,同时也可以自定义ConfigMap的挂载路径。

例如,将名称为log-config的ConfigMap挂载到Pod的/etc/config目录下,挂载的文件名称为path指定的值,当前为log_level:

注意

ConfigMap需要提前创建。

④emptyDir

和上述volume不同的是,如果删除Pod,emptyDir卷中的数据也将被删除,一般emptyDir卷用于Pod中的不同Container共享数据。它可以被挂载到相同或不同的路径上。

默认情况下,emptyDir卷支持节点上的任何介质,可能是SSD、磁盘或网络存储,具体取决于自身的环境。可以将emptyDir.medium字段设置为Memory,让Kubernetes使用tmpfs(内存支持的文件系统),虽然tmpfs非常快,但是tmpfs在节点重启时,数据同样会被清除,并且设置的大小会被计入到Container的内存限制当中。

使用emptyDir卷的示例,直接指定emptyDir为{}即可:

⑤GlusterFS

GlusterFS(以下简称为GFS)是一个开源的网络文件系统,常被用于为Kubernetes提供动态存储,和emptyDir不同的是,删除Pod时GFS卷中的数据会被保留。

关于GFS的使用示例请参看3.1节。

⑥hostPath

hostPath卷可将节点上的文件或目录挂载到Pod上,用于Pod自定义日志输出或访问Docker内部的容器等。

使用hostPath卷的示例。将主机的/data目录挂载到Pod的/test-pd目录:

hostPath卷常用的type(类型)如下。

  • type为空字符串:默认选项,意味着挂载hostPath卷之前不会执行任何检查。
  • DirectoryOrCreate:如果给定的path不存在任何东西,那么将根据需要创建一个权限为0755的空目录,和Kubelet具有相同的组和权限。
  • Directory:目录必须存在于给定的路径下。
  • FileOrCreate:如果给定的路径不存储任何内容,则会根据需要创建一个空文件,权限设置为0644,和Kubelet具有相同的组和所有权。
  • File:文件,必须存在于给定路径中。
  • Socket:UNIX套接字,必须存在于给定路径中。
  • CharDevice:字符设备,必须存在于给定路径中。
  • BlockDevice:块设备,必须存在于给定路径中。

⑦NFS

NFS卷也是一种网络文件系统,同时也可以作为动态存储,和GFS类似,删除Pod时,NFS中的数据不会被删除。NFS可以被多个写入同时挂载。

关于NFS的使用,请参考第3章。

⑧persistentVolumeClaim

persistentVolumeClaim卷用于将PersistentVolume(持久化卷)挂载到容器中,PersistentVolume分为动态存储和静态存储,静态存储的PersistentVolume需要手动提前创建PV,动态存储无需手动创建PV。

⑨Secret

Secret卷和ConfigMap卷类似,详情见2.2.10节。

⑩SubPath

有时可能需要将一个卷挂载到不同的子目录,此时使用volumeMounts.subPath可以实现不同子目录的挂载。

本示例为一个LAMP共享一个卷,使用subPath卷挂载不同的目录:

更多volume可参考:

    https://kubernetes.io/docs/concepts/storage/volumes/
2. PersistentVolume

管理计算资源需要关注的另一个问题是管理存储,PersistentVolume子系统为用户和管理提供了一个API,用于抽象如何根据使用类型提供存储的详细信息。为此,Kubernetes引入了两个新的API资源:PersistentVolume和PersistentVolumeClaim。

PersistentVolume(简称PV)是由管理员设置的存储,它同样是集群中的一类资源,PV是容量插件,如Volumes(卷),但其生命周期独立使用PV的任何Pod,PV的创建可使用NFS、iSCSI、GFS、CEPH等。

PersistentVolumeClaim(简称PVC)是用户对存储的请求,类似于Pod,Pod消耗节点资源,PVC消耗PV资源,Pod可以请求特定级别的资源(CPU和内存),PVC可以请求特定的大小和访问模式。例如,可以以一次读/写或只读多次的模式挂载。

虽然PVC允许用户使用抽象存储资源,但是用户可能需要具有不同性质的PV来解决不同的问题,比如使用SSD硬盘来提高性能。所以集群管理员需要能够提供各种PV,而不仅是大小和访问模式,并且无须让用户了解这些卷的实现方式,对于这些需求可以使用StorageClass资源实现。

目前PV的提供方式有两种:静态或动态。

静态PV由管理员提前创建,动态PV无需提前创建,只需指定PVC的StorageClasse即可。

(1)回收策略

当用户使用完卷时,可以从API中删除PVC对象,从而允许回收资源。回收策略会告诉PV如何处理该卷,目前卷可以保留、回收或删除。

  • Retain:保留,该策略允许手动回收资源,当删除PVC时,PV仍然存在,volume被视为已释放,管理员可以手动回收卷。
  • Recycle:回收,如果volume插件支持,Recycle策略会对卷执行rm -rf清理该PV,并使其可用于下一个新的PVC,但是本策略已弃用,建议使用动态配置。
  • Delete:删除,如果volume插件支持,删除PVC时会同时删除PV,动态卷默认为Delete。

(2)创建PV

在使用持久化时,需要先创建PV,然后再创建PVC,PVC会和匹配的PV进行绑定,然后Pod即可使用该存储。

创建一个基于NFS的PV:

说明

  • capacity:容量。
  • accessModes:访问模式。包括以下3种:
    •  ReadWriteOnce:可以被单节点以读写模式挂载,命令行中可以被缩写为RWO。
    •  ReadOnlyMany:可以被多个节点以只读模式挂载,命令行中可以被缩写为ROX。
    •  ReadWriteMany:可以被多个节点以读写模式挂载,命令行中可以被缩写为RWX。
  • storageClassName:PV的类,一个特定类型的PV只能绑定到特定类别的PVC。
  • persistentVolumeReclaimPolicy:回收策略。
  • mountOptions:非必须,新版本中已弃用。
  • nfs:NFS服务配置。包括以下两个选项:
    •  path:NFS上的目录
    •  server:NFS的IP地址

创建的PV会有以下几种状态:

  • Available(可用),没有被PVC绑定的空间资源。
  • Bound(已绑定),已经被PVC绑定。
  • Released(已释放),PVC被删除,但是资源还未被重新使用。
  • Failed(失败),自动回收失败。

可以创建一个基于hostPath的PV:

(3)创建PVC

创建PVC需要注意的是,各个方面都符合要求PVC才能和PV进行绑定,比如accessModes、storageClassName、volumeMode都需要相同才能进行绑定。

创建PVC的示例如下:

比如上述基于hostPath的PV可以使用以下PVC进行绑定,storage可以比PV小:

然后创建一个Pod指定volumes即可使用这个PV:

注意

claimName需要和上述定义的PVC名称task-pv-claim一致。

3. StorageClass

StorageClass为管理员提供了一种描述存储“类”的方法,可以满足用户不同的服务质量级别、备份策略和任意策略要求的存储需求,一般动态PV都会通过StorageClass来定义。

每个StorageClass包含字段provisioner、parameters和reclaimPolicy,StorageClass对象的名称很重要,管理员在首次创建StorageClass对象时设置的类的名称和其他参数,在被创建对象后无法再更新这些对象。

定义一个StorageClass的示例如下:

(1)Provisioner

StorageClass有一个provisioner字段,用于指定配置PV的卷的类型,必须指定此字段,目前支持的卷插件如表2-7所示。

表2-7 卷插件的类型

注意

provisioner不仅限于此处列出的内部provisioner,还可以运行和指定外部供应商。例如,NFS不提供内部配置程序,但是可以使用外部配置程序,外部配置方式参见以下网址:https://github.com/kubernetes-incubator/external-storage

(2)ReclaimPolicy

回收策略,可以是Delete、Retain,默认为Delete。

(3)MountOptions

通过StorageClass动态创建的PV可以使用MountOptions指定挂载参数。如果指定的卷插件不支持指定的挂载选项,就不会被创建成功,因此在设置时需要进行确认。

(4)Parameters

PVC具有描述属于StorageClass卷的参数,根据具体情况,取决于provisioner,可以接受不同类型的参数。比如,type为io1和特定参数iopsPerGB是EBS所具有的。如果省略配置参数,将采用默认值。

4. 定义StorageClass

StorageClass一般用于定义动态存储卷,只需要在Pod上指定StorageClass的名字即可自动创建对应的PV,无须再手工创建。

以下为常用的StorageClass定义方式。

(1)AWS EBS

说明

  • type:io1、gp2、sc1、st1,默认为gp2。详情可查看以下网址的内容:
  • iopsPerGB:仅适用于io1卷,即每GiB每秒的I/O操作。
  • fsType:Kubernetes支持的fsType,默认值为:ext4。

(2)GCE PD

说明

  • type:pd-standard或pd-ssd,默认为pd-standard。
  • replication-type:none或regional-pd,默认值为none。

(3)GFS

说明

  • resturl:Gluster REST服务/Heketi服务的URL,这是GFS动态存储必需的参数。
  • restauthenabled:用于对REST服务器进行身份验证,此选项已被启用。如果需要启用身份验证,只需指定restuser、restuserkey、secretName或secretNamespace其中一个即可。
  • restuser:访问Gluster REST服务的用户。
  • secretNamespace,secretName:与Gluster REST服务交互时使用的Secret。这些参数是可选的,如果没有身份认证不用配置此参数。该Secret使用type为kubernetes.io/glusterfs的Secret进行创建,例如:
  • clusterid:Heketi创建集群的ID,可以是一个列表,用逗号分隔。
  • gidMin,gidMax:StorageClass的GID范围,可选,默认为2000-2147483647。
  • volumetype:创建的GFS卷的类型,主要分为以下3种:
    •  Replica卷:volumetype: replicate:3,表示每个PV会创建3个副本。
    •  Disperse/EC卷:volumetype: disperse:4:2,其中4是数据,2是冗余。
    •  Distribute卷:volumetype: none。

当使用GFS作为动态配置PV时,会自动创建一个格式为gluster-dynamic-<claimname>的Endpoint和Headless Service,删除PVC会自动删除PV、Endpoint和Headless Service。

(4)Ceph RBD

说明

  • monitors:Ceph的monitor,用逗号分隔,此参数是必需的。
  • adminId:Ceph客户端的ID,默认为admin。
  • adminSecretName:adminId的Secret名称,此参数是必需的,该Secret必须是kubernetes.io/rbd类型。
  • adminSecretNamespace:Secret所在的NameSpace(命名空间),默认为default。
  • pool:Ceph RBD池,默认为rbd。
  • userId:Ceph客户端ID,默认值与adminId相同。
  • userSecretName:和adminSecretName类似,必须与PVC存在于同一个命名空间,创建方式如下:
  • imageFormat:Ceph RBD镜像格式,默认值为2,旧一些的为1。
  • imagefeatures:可选参数,只有设置imageFormat为2时才能使用,目前仅支持layering。

更多详情请参考以下网址:

https://kubernetes.io/docs/concepts/storage/storage-classes/

4. 动态存储卷

动态卷的配置允许按需自动创建PV,如果没有动态配置,集群管理员必须手动创建PV。

动态卷的配置基于StorageClass API组中的API对象storage.K8S.io。

(1)定义GCE动态预配置

要启用动态配置,集群管理员需要为用户预先创建一个或多个StorageClass对象,比如创建一个名字为slow且使用gce提供存储卷的StorageClass:

再例如创建一个能提供SSD磁盘的StorageClass:

用户通过定义包含StorageClass的PVC来请求动态调配的存储。在Kubernetesv 1.6之前,是通过volume.beta.kubernetes.io/storage-class注解来完成的。在1.6版本之后,此注解已弃用。

例如,创建一个快速存储类,定义的PersistentVolumeClaim如下:

注意

storageClassName要与上述创建的StorageClass名字相同。

之后会自动创建一个PV与该PVC进行绑定,然后Pod即可挂载使用。

(2)定义GFS动态预配置

可以参考3.1节定义一个GFS的StorageClass:

之后定义一个PVC:

PVC一旦被定义,系统便发出Heketi进行相应的操作,在GFS集群上创建brick,再创建并启动一个volume。

然后定义一个Pod使用该存储卷:

claimName为上述创建的PVC的名称。

2.2.13 Service

Service主要用于Pod之间的通信,对于Pod的IP地址而言,Service是提前定义好并且是不变的资源类型。

1. 基本概念

Kubernetes Pod具有生命周期的概念,它可以被创建、删除、销毁,一旦被销毁就意味着生命周期的结束。通过ReplicaSet能够动态地创建和销毁Pod,例如进行扩缩容和执行滚动升级。每个Pod都会获取到它自己的IP地址,但是这些IP地址不总是稳定和可依赖的,这样就会导致一个问题:在Kubernetes集群中,如果一组Pod(比如后端的Pod)为其他Pod(比如前端的Pod)提供服务,那么如果它们之间使用Pod的IP地址进行通信,在Pod重建后,将无法再进行连接。

为了解决上述问题,Kubernetes引用了Service这样一种抽象概念:逻辑上的一组Pod,即一种可以访问Pod的策略——通常称为微服务。这一组Pod能够被Service访问到,通常是通过Label Selector(标签选择器)实现的。

举个例子,有一个用作图片处理的backend(后端),运行了3个副本,这些副本是可互换的,所以frontend(前端)不需要关心它们调用了哪个backend副本,然而组成这一组backend程序的Pod实际上可能会发生变化,即便这样frontend也没有必要知道,而且也不需要跟踪这一组backend的状态,因为Service能够解耦这种关联。

对于Kubernetes集群中的应用,Kubernetes提供了简单的Endpoints API,只要Service中的一组Pod发生变更,应用程序就会被更新。对非Kubernetes集群中的应用,Kubernetes提供了基于VIP的网桥的方式访问Service,再由Service重定向到backend Pod。

2. 定义Service

一个Service在Kubernetes中是一个REST对象,和Pod类似。像所有REST对象一样,Service的定义可以基于POST方式,请求APIServer创建新的实例。例如,假定有一组Pod,它们暴露了9376端口,同时具有app=MyApp标签。此时可以定义Service如下:

上述配置创建一个名为my-service的Service对象,它会将请求代理到TCP端口为9376并且具有标签app=MyApp的Pod上。这个Service会被分配一个IP地址,通常称为ClusterIP,它会被服务的代理使用。

需要注意的是,Service能够将一个接收端口映射到任意的targetPort。默认情况下,targetPort将被设置为与Port字段相同的值。targetPort可以设置为一个字符串,引用backend Pod的一个端口的名称。

Kubernetes Service能够支持TCP和UDP协议,默认为TCP协议。

3. 定义没有Selector的Service

Service抽象了该如何访问Kubernetes Pod,但也能够抽象其他类型的backend,例如:

  • 希望在生产环境中访问外部的数据库集群。
  • 希望Service指向另一个NameSpace中或其他集群中的服务。
  • 正在将工作负载转移到Kubernetes集群,和运行在Kubernetes集群之外的backend。

在任何这些场景中,都能定义没有Selector的Service:

由于这个Service没有Selector,就不会创建相关的Endpoints对象,可以手动将Service映射到指定的Endpoints:

注意

Endpoint IP地址不能是loopback(127.0.0.0/8)、link-local(169.254.0.0/16)或者link-local多播地址(224.0.0.0/24)。

访问没有Selector的Service与有Selector的Service的原理相同。请求将被路由到用户定义的Endpoint,该示例为1.2.3.4:9376。

ExternalName Service是Service的特例,它没有Selector,也没有定义任何端口和Endpoint,它通过返回该外部服务的别名来提供服务。

比如当查询主机my-service.prod.svc时,集群的DNS服务将返回一个值为my.database.example.com的CNAME记录:

4. VIP和Service代理

在Kubernetes集群中,每个节点运行一个kube-proxy进程。kube-proxy负责为Service实现了一种VIP(虚拟IP)的形式,而不是ExternalName的形式。在Kubernetesv 1.0版本中,代理完全是userspace。在Kubernetesv 1.1版中新增了iptables代理,从Kubernetesv 1.2版起,默认是iptables代理。从Kubernetesv 1.8版开始新增了ipvs代理,生产环境建议使用ipvs模式。

在Kubernetesv 1.0版中Service是4层(TCP/UDP over IP)概念,在Kubernetesv 1.1版中新增了Ingress API(beta版),用来表示7层(HTTP)服务。

(1)iptables代理模式

这种模式下kube-proxy会监视Kubernetes Master对Service对象和Endpoints对象的添加和移除。对每个Service它会创建iptables规则,从而捕获到该Service的ClusterIP(虚拟IP)和端口的请求,进而将请求重定向到Service的一组backend中的某个Pod上面。对于每个Endpoints对象,它也会创建iptables规则,这个规则会选择一个backend Pod。

默认的策略是随机选择一个backend,如果要实现基于客户端IP的会话亲和性,可以将service.spec.sessionAffinity的值设置为ClusterIP(默认为None)。

和userspace代理类似,网络返回的结果都是到达Service的IP:Port请求,这些请求会被代理到一个合适的backend,不需要客户端知道关于Kubernetes、Service或Pod的任何信息。这比userspace代理更快、更可靠,并且当初始选择的Pod没有响应时,iptables代理能够自动重试另一个Pod。

(2)ipvs代理模式

在此模式下,kube-proxy监视Kubernetes Service和Endpoint,调用netlink接口以相应地创建ipvs规则,并定期与Kubernetes Service和Endpoint同步ipvs规则,以确保ipvs状态与期望保持一致。访问服务时,流量将被重定向到其中一个后端Pod。

与iptables类似,ipvs基于netfilter钩子函数,但是ipvs使用哈希表作为底层数据结构并在内核空间中工作,这意味着ipvs可以更快地重定向流量,并且在同步代理规则时具有更好的性能,此外,ipvs为负载均衡算法提供了更多的选项,例如:

  • rr 轮询
  • lc 最少连接
  • dh 目标哈希
  • sh 源哈希
  • sed 预计延迟最短
  • nq 从不排队
5. 多端口Service

在许多情况下,Service可能需要暴露多个端口,对于这种情况Kubernetes支持Service定义多个端口,但使用多个端口时,必须提供所有端口的名称,例如:

6. 发布服务/服务类型

对于应用程序的某些部分(例如前端),一般要将服务公开到集群外部供用户访问。这种情况下都是用Ingress通过域名进行访问。

Kubernetes ServiceType(服务类型)主要包括以下几种:

  • ClusterIP 在集群内部使用,默认值,只能从集群中访问。
  • NodePort 在所有节点上打开一个端口,此端口可以代理至后端Pod,可以通过NodePort从集群外部访问集群内的服务,格式为NodeIP:NodePort。
  • LoadBalancer 使用云提供商的负载均衡器公开服务,成本较高。
  • ExternalName 通过返回定义的CNAME别名,没有设置任何类型的代理,需要1.7或更高版本kube-dns支持。

以NodePort为例。如果将type字段设置为NodePort,则Kubernetes将从--service-node-port-range参数指定的范围(默认为30000-32767)中自动分配端口,也可以手动指定NodePort,并且每个节点将代理该端口到Service。

一般格式如下:

常用的服务访问是NodePort和Ingress(关于Ingress参看2.2.14节),其他服务访问方式详情参看以下网址:

https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types

2.2.14 Ingress

Ingress为Kubernetes集群中的服务提供了入口,可以提供负载均衡、SSL终止和基于名称的虚拟主机,在生产环境中常用的Ingress有Treafik、Nginx、HAProxy、Istio等。

1. 基本概念

在Kubernetesv 1.1版中添加的Ingress用于从集群外部到集群内部Service的HTTP和HTTPS路由,流量从Internet到Ingress再到Services最后到Pod上,通常情况下,Ingress部署在所有的Node节点上。

Ingress可以配置提供服务外部访问的URL、负载均衡、终止SSL,并提供基于域名的虚拟主机。但Ingress不会暴露任意端口或协议。

2. 创建一个Ingress

创建一个简单的Ingress如下:

上述host定义该Ingress的域名,将其解析至任意Node上即可访问。

如果访问的是foo.bar.com/foo,则被转发到service1的4200端口。

如果访问的是foo.bar.com/bar,则被转发到service2的8080端口。

(1)Ingress Rules

  • host:可选,一般都会配置对应的域名。
  • path:每个路径都有一个对应的serviceName和servicePort,在流量到达服务之前,主机和路径都会与传入请求的内容匹配。
  • backend:描述Service和Port的组合。对Ingress匹配主机和路径的HTTP与HTTPS请求将被发送到对应的后端。

(2)默认后端

没有匹配到任何规则的流量将被发送到默认后端。默认后端通常是Ingress Controller的配置选项,并未在Ingress资源中指定。

3. Ingress类型

(1)单域名

单个域名匹配多个path到不同的service:

此时,访问foo.bar.com/foo到service1的4200。访问foo.bar.com/bar到service2的8080。

(2)多域名

基于域名的虚拟主机支持将HTTP流量路由到同一IP地址的多个主机名:

此时,访问foo.bar.com到service1,访问bar.foo.com到service2。

(3)基于TLS的Ingress

首先创建证书,生产环境的证书为公司购买的证书:

    kubectl -n default create secret tls nginx-test-tls --key=tls.key
--cert=tls.crt

定义Ingress(此示例为Traefik,nginx-ingress将traefik改为nginx即可):

4. 更新Ingress

更新Ingress可以直接使用kubectl edit ingress INGRESS-NAME进行更改,也可以通过kubectlapply-f NEW-INGRESS-YAML.yaml进行更改。

更多Ingress配置请参考第5章Nginx Ingress的内容。

2.2.15 Taint和Toleration

Taint能够使节点排斥一类特定的Pod,Taint和Toleration相互配合可以用来避免Pod被分配到不合适的节点,比如Master节点不允许部署系统组件之外的其他Pod。每个节点上都可以应用一个或多个Taint,这表示对于那些不能容忍这些Taint的Pod是不会被该节点接受的。如果将Toleration应用于Pod上,则表示这些Pod可以(但不要求)被调度到具有匹配Taint的节点上。

1. 概念

给节点增加一个Taint:

    [root@K8S-master01 2.2.8]# kubectl taint nodes K8S-node01 key=value:NoSchedule
    node/K8S-node01 tainted

上述命令给K8S-node01增加一个Taint,它的key对应的就是键,value对应就是值,effect对应的就是NoSchedule。这表明只有和这个Taint相匹配的Toleration的Pod才能够被分配到K8S-node01节点上。按如下方式在PodSpec中定义Pod的Toleration,就可以将Pod部署到该节点上。

方式一:

方式二:

一个Toleration和一个Taint相匹配是指它们有一样的key和effect,并且如果operator是Exists(此时toleration不指定value)或者operator是Equal,则它们的value应该相等。

注意两种情况:

  • 如果一个Toleration的key为空且operator为Exists,表示这个Toleration与任意的key、value和effect都匹配,即这个Toleration能容忍任意的Taint:
    tolerations:
    - operator: "Exists"
  • 如果一个Toleration的effect为空,则key与之相同的相匹配的Taint的effect可以是任意值:
    tolerations:
    - key: "key"
      operator: "Exists"

上述例子使用到effect的一个值NoSchedule,也可以使用PreferNoSchedule,该值定义尽量避免将Pod调度到存在其不能容忍的Taint的节点上,但并不是强制的。effect的值还可以设置为NoExecute。

一个节点可以设置多个Taint,也可以给一个Pod添加多个Toleration。Kubernetes处理多个Taint和Toleration的过程就像一个过滤器:从一个节点的所有Taint开始遍历,过滤掉那些Pod中存在与之相匹配的Toleration的Taint。余下未被过滤的Taint的effect值决定了Pod是否会被分配到该节点,特别是以下情况:

  • 如果未被过滤的Taint中存在一个以上effect值为NoSchedule的Taint,则Kubernetes不会将Pod分配到该节点。
  • 如果未被过滤的Taint中不存在effect值为NoExecute的Taint,但是存在effect值为PreferNoSchedule的Taint,则Kubernetes会尝试将Pod分配到该节点。
  • 如果未被过滤的Taint中存在一个以上effect值为NoExecute的Taint,则Kubernetes不会将Pod分配到该节点(如果Pod还未在节点上运行),或者将Pod从该节点驱逐(如果Pod已经在节点上运行)。

例如,假设给一个节点添加了以下的Taint:

    kubectl taint nodes K8S-node01 key1=value1:NoSchedule
    kubectl taint nodes K8S-node01 key1=value1:NoExecute
    kubectl taint nodes K8S-node01 key2=value2:NoSchedule

然后存在一个Pod,它有两个Toleration:

在上述例子中,该Pod不会被分配到上述节点,因为没有匹配第三个Taint。但是如果给节点添加上述3个Taint之前,该Pod已经在上述节点中运行,那么它不会被驱逐,还会继续运行在这个节点上,因为第3个Taint是唯一不能被这个Pod容忍的。

通常情况下,如果给一个节点添加了一个effect值为NoExecute的Taint,则任何不能容忍这个Taint的Pod都会马上被驱逐,任何可以容忍这个Taint的Pod都不会被驱逐。但是,如果Pod存在一个effect值为NoExecute的Toleration指定了可选属性tolerationSeconds的值,则该值表示是在给节点添加了上述Taint之后Pod还能继续在该节点上运行的时间,例如:

表示如果这个Pod正在运行,然后一个匹配的Taint被添加到其所在的节点,那么Pod还将继续在节点上运行3600秒,然后被驱逐。如果在此之前上述Taint被删除了,则Pod不会被驱逐。

删除一个Taint:

    kubectl taint nodes K8S-node01 key1:NoExecute-

查看Taint:

2. 用例

通过Taint和Toleration可以灵活地让Pod避开某些节点或者将Pod从某些节点被驱逐。下面是几种情况。

(1)专用节点

如果想将某些节点专门分配给特定的一组用户使用,可以给这些节点添加一个Taint(kubectl taint nodes nodename dedicated=groupName:NoSchedule),然后给这组用户的Pod添加一个相对应的Toleration。拥有上述Toleration的Pod就能够被分配到上述专用节点,同时也能够被分配到集群中的其他节点。如果只希望这些Pod只能分配到上述专用节点中,那么还需要给这些专用节点另外添加一个和上述Taint类似的Label(例如:dedicated=groupName),然后给Pod增加节点亲和性要求或者使用NodeSelector,就能将Pod只分配到添加了dedicated=groupName标签的节点上。

(2)特殊硬件的节点

在部分节点上配备了特殊硬件(比如GPU)的集群中,我们只允许特定的Pod才能部署在这些节点上。这时可以使用Taint进行控制,添加Taint如kubectl taint nodes nodename special=true:NoSchedule或者kubectl taint nodes nodename special=true:PreferNoSchedule,然后给需要部署在这些节点上的Pod添加相匹配的Toleration即可。

(3)基于Taint的驱逐

属于alpha特性,在每个Pod中配置在节点出现问题时的驱逐行为。

3. 基于Taint的驱逐

之前提到过Taint的effect值NoExecute,它会影响已经在节点上运行的Pod。如果Pod不能忍受effect值为NoExecute的Taint,那么Pod将会被马上驱逐。如果能够忍受effect值为NoExecute的Taint,但是在Toleration定义中没有指定tolerationSeconds,则Pod还会一直在这个节点上运行。

在Kubernetes 1.6版以后已经支持(alpha)当某种条件为真时,Node Controller会自动给节点添加一个Taint,用以表示节点的问题。当前内置的Taint包括:

  • node.kubernetes.io/not-ready 节点未准备好,相当于节点状态Ready的值为False。
  • node.kubernetes.io/unreachable Node Controller访问不到节点,相当于节点状态Ready的值为Unknown。
  • node.kubernetes.io/out-of-disk 节点磁盘耗尽。
  • node.kubernetes.io/memory-pressure 节点存在内存压力。
  • node.kubernetes.io/disk-pressure 节点存在磁盘压力。
  • node.kubernetes.io/network-unavailable 节点网络不可达。
  • node.kubernetes.io/unschedulable 节点不可调度。
  • node.cloudprovider.kubernetes.io/uninitialized 如果Kubelet启动时指定了一个外部的cloudprovider,它将给当前节点添加一个Taint将其标记为不可用。在cloud-controller-manager的一个controller初始化这个节点后,Kubelet将删除这个Taint。

使用这个alpha功能特性,结合tolerationSeconds,Pod就可以指定当节点出现一个或全部上述问题时,Pod还能在这个节点上运行多长时间。

比如,一个使用了很多本地状态的应用程序在网络断开时,仍然希望停留在当前节点上运行一段时间,愿意等待网络恢复以避免被驱逐。在这种情况下,Pod的Toleration可以这样配置:

注意

Kubernetes会自动给Pod添加一个key为node.kubernetes.io/not-ready的Toleration并配置tolerationSeconds=300,同样也会给Pod添加一个key为node.kubernetes.io/unreachable的Toleration并配置tolerationSeconds=300,除非用户自定义了上述key,否则会采用这个默认设置。

这种自动添加Toleration的机制保证了在其中一种问题被检测到时,Pod默认能够继续停留在当前节点运行5分钟。这两个默认Toleration是由DefaultTolerationSeconds admission controller添加的。

DaemonSet中的Pod被创建时,针对以下Taint自动添加的NoExecute的Toleration将不会指定tolerationSeconds:

  • node.alpha.kubernetes.io/unreachable
  • node.kubernetes.io/not-ready

这保证了出现上述问题时DaemonSet中的Pod永远不会被驱逐。

2.2.16 RBAC

1. RBAC基本概念

RBAC(Role-Based Access Control,基于角色的访问控制)是一种基于企业内个人用户的角色来管理对计算机或网络资源的访问方法,其在Kubernetes 1.5版本中引入,在1.6时升级为Beta版本,并成为Kubeadm安装方式下的默认选项。启用RBAC需要在启动APIServer时指定--authorization-mode=RBAC。

RBAC使用rbac.authorization.K8S.io API组来推动授权决策,允许管理员通过Kubernetes API动态配置策略。

RBAC API声明了4种顶级资源对象,即Role、ClusterRole、RoleBinding、ClusterRoleBinding,管理员可以像使用其他API资源一样使用kubectl API调用这些资源对象。例如:kubectl create -f(resource).yml。

2. Role和ClusterRole

Role和ClusterRole的关键区别是,Role是作用于命名空间内的角色,ClusterRole作用于整个集群的角色。

在RBAC API中,Role包含表示一组权限的规则。权限纯粹是附加允许的,没有拒绝规则。

Role只能授权对单个命名空间内的资源的访问权限,比如授权对default命名空间的读取权限:

ClusterRole也可将上述权限授予作用于整个集群的Role,主要区别是,ClusterRole是集群范围的,因此它们还可以授予对以下内容的访问权限:

  • 集群范围的资源(如Node)。
  • 非资源端点(如/healthz)。
  • 跨所有命名空间的命名空间资源(如Pod)。

比如,授予对任何特定命名空间或所有命名空间中的secret的读权限(取决于它的绑定方式):

3. RoleBinding和ClusterRoleBinding

RoleBinding将Role中定义的权限授予User、Group或Service Account。RoleBinding和ClusterRoleBinding最大的区别与Role和ClusterRole的区别类似,即RoleBinding作用于命名空间,ClusterRoleBinding作用于集群。

RoleBinding可以引用同一命名空间的Role进行授权,比如将上述创建的pod-reader的Role授予default命名空间的用户jane,这将允许jane读取default命名空间中的Pod:

说明

  • roleRef:绑定的类别,可以是Role或ClusterRole。

RoleBinding也可以引用ClusterRole来授予对命名空间资源的某些权限。管理员可以为整个集群定义一组公用的ClusterRole,然后在多个命名空间中重复使用。

比如,创建一个RoleBinding引用ClusterRole,授予dave用户读取development命名空间的Secret:

ClusterRoleBinding可用于在集群级别和所有命名空间中授予权限,比如允许组manager中的所有用户都能读取任何命名空间的Secret:

4. 对集群资源的权限控制

在Kubernetes中,大多数资源都由其名称的字符串表示,例如pods。但是一些Kubernetes API涉及的子资源(下级资源),例如Pod的日志,对应的Endpoint的URL是:

    GET /api/v1/namespaces/{namespace}/pods/{name}/log

在这种情况下,pods是命名空间资源,log是Pod的下级资源,如果对其进行访问控制,要使用斜杠来分隔资源和子资源,比如定义一个Role允许读取Pod和Pod日志:

针对具体资源(使用resourceNames指定单个具体资源)的某些请求,也可以通过使用get、delete、update、patch等进行授权,比如,只能对一个叫my-configmap的configmap进行get和update操作:

注意

如果使用了resourceNames,则verbs不能是list、watch、create、deletecollection等。

5. 聚合ClusterRole

从Kubernetes 1.9版本开始,Kubernetes可以通过一组ClusterRole创建聚合ClusterRoles,聚合ClusterRoles的权限由控制器管理,并通过匹配ClusterRole的标签自动填充相对应的权限。

比如,匹配rbac.example.com/aggregate-to-monitoring: "true"标签来创建聚合ClusterRole:

然后创建与标签选择器匹配的ClusterRole向聚合ClusterRole添加规则:

6. Role示例

以下示例允许读取核心API组中的资源Pods(只写了规则rules部分):

允许在extensions和apps API组中读写deployments:

允许对Pods的读和Job的读写:

允许读取一个名为my-config的ConfigMap(必须绑定到一个RoleBinding来限制到一个命名空间下的ConfigMap):

允许读取核心组Node资源(Node属于集群级别的资源,必须放在ClusterRole中,并使用ClusterRoleBinding进行绑定):

允许对非资源端点/healthz和所有其子资源路径的Get和Post请求(必须放在ClusterRole并与ClusterRoleBinding进行绑定):

7. RoleBinding示例

以下示例绑定为名为“alice@example.com”的用户(只显示subjects部分):

绑定为名为“frontend-admins”的组:

绑定为kube-system命名空间中的默认Service Account:

绑定为qa命名空间中的所有Service Account:

绑定所有Service Account:

绑定所有经过身份验证的用户(v1.5+):

绑定所有未经过身份验证的用户(v1.5+):

对于所有用户:

8. 命令行的使用

权限的创建可以使用命令行直接创建,较上述方式更加简单、快捷,下面我们逐一介绍常用命令的使用。

(1)kubectl create role

创建一个Role,命名为pod-reader,允许用户在Pod上执行get、watch和list:

    kubectl create role pod-reader --verb=get --verb=list --verb=watch
--resource=pods

创建一个指定了resourceNames的Role,命名为pod-reader:

    kubectl create role pod-reader --verb=get --resource=pods
--resource-name=readablepod --resource-name=anotherpod

创建一个命名为foo,并指定APIGroups的Role:

    kubectl create role foo --verb=get,list,watch --resource=replicasets.apps

针对子资源创建一个名为foo的Role:

    kubectl create role foo --verb=get,list,watch --resource=pods,pods/status

针对特定/具体资源创建一个名为my-component-lease-holder的Role:

    kubectl create role my-component-lease-holder --verb=get,list,watch,update
--resource=lease --resource-name=my-component

(2)kubectl create clusterrole

创建一个名为pod-reader的ClusterRole,允许用户在Pod上执行get、watch和list:

    kubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods

创建一个名为pod-reader的ClusterRole,并指定resourceName:

    kubectl create clusterrole pod-reader --verb=get --resource=pods
--resource-name=readablepod --resource-name=anotherpod

使用指定的apiGroup创建一个名为foo的ClusterRole:

    kubectl create clusterrole foo --verb=get,list,watch
--resource=replicasets.apps

使用子资源创建一个名为foo的ClusterRole:

    kubectl create clusterrole foo --verb=get,list,watch
--resource=pods,pods/status

使用non-ResourceURL创建一个名为foo的ClusterRole:

    kubectl create clusterrole "foo" --verb=get --non-resource-url=/logs/*

使用指定标签创建名为monitoring的聚合ClusterRole:

    kubectl create clusterrole monitoring
--aggregation-rule="rbac.example.com/aggregate-to-monitoring=true"

(3)kubectl create rolebinding

创建一个名为bob-admin-binding的RoleBinding,将名为admin的ClusterRole绑定到名为acme的命名空间中一个名为bob的user:

    kubectl create rolebinding bob-admin-binding --clusterrole=admin --user=bob
--namespace=acme

创建一个名为myapp-view-binding的RoleBinding,将名为view的ClusterRole,绑定到acme命名空间中名为myapp的ServiceAccount:

    kubectl create rolebinding myapp-view-binding --clusterrole=view
--serviceaccount=acme:myapp --namespace=acme

(4)kubectl create clusterrolebinding

创建一个名为root-cluster-admin-binding的clusterrolebinding,将名为cluster-admin的ClusterRole绑定到名为root的user:

    kubectl create clusterrolebinding root-cluster-admin-binding
--clusterrole=cluster-admin --user=root

创建一个名为myapp-view-binding的clusterrolebinding,将名为view的ClusterRole绑定到acme命名空间中名为myapp的ServiceAccount:

    kubectl create clusterrolebinding myapp-view-binding --clusterrole=view
--serviceaccount=acme:myapp

2.2.17 CronJob

CronJob用于以时间为基准周期性地执行任务,这些自动化任务和运行在Linux或UNIX系统上的CronJob一样。CronJob对于创建定期和重复任务非常有用,例如执行备份任务、周期性调度程序接口、发送电子邮件等。

对于Kubernetes 1.8以前的版本,需要添加--runtime-config=batch/v2alpha1=true参数至APIServer中,然后重启APIServer和Controller Manager用于启用API,对于1.8以后的版本无须修改任何参数,可以直接使用,本节的示例基于1.8以上的版本。

1. 创建CronJob

创建CronJob有两种方式,一种是直接使用kubectl创建,一种是使用yaml文件创建。

使用kubectl创建CronJob的命令如下:

    kubectl run hello --schedule="*/1 * * * *" --restart=OnFailure --image=busybox
-- /bin/sh -c "date; echo Hello from the Kubernetes cluster"

对应的yaml文件如下:

说明

本例创建一个每分钟执行一次、打印当前时间和Hello from the Kubernetes cluster的计划任务。

查看创建的CronJob:

等待1分钟可以查看执行的任务(Jobs):

CronJob每次调用任务的时候会创建一个Pod执行命令,执行完任务后,Pod状态就会变成Completed,如下所示:

可以通过logs查看Pod的执行日志:

    $ kubectl logs -f hello-1558779360-jcp4r
    Sat May 25 10:16:23 UTC 2019
    Hello from the Kubernetes cluster

如果要删除CronJob,直接使用delete即可:

    kubectl delete cronjob hello
2. 可用参数的配置

定义一个CronJob的yaml文件如下:

其中各参数的说明如下(可以按需修改):

  • schedule 调度周期,和Linux一致,分别是分时日月周。
  • restartPolicy 重启策略,和Pod一致。
  • concurrencyPolicy 并发调度策略。可选参数如下:
    •  Allow 允许同时运行多个任务。
    •  Forbid 不允许并发运行,如果之前的任务尚未完成,新的任务不会被创建。
    •  Replace 如果之前的任务尚未完成,新的任务会替换的之前的任务。
  • Suspend 如果设置为true,则暂停后续的任务,默认为false。
  • successfulJobsHistoryLimit 保留多少已完成的任务,按需配置。
  • failedJobsHistoryLimit 保留多少失败的任务。

相对于Linux上的计划任务,Kubernetes的CronJob更具有可配置性,并且对于执行计划任务的环境只需启动相对应的镜像即可。比如,如果需要Go或者PHP环境执行任务,就只需要更改任务的镜像为Go或者PHP即可,而对于Linux上的计划任务,则需要安装相对应的执行环境。此外,Kubernetes的CronJob是创建Pod来执行,更加清晰明了,查看日志也比较方便。可见,Kubernetes的CronJob更加方便和简单。

更多CronJob的内容,可以参考Kubernetes的官方文档:https://kubernetes.io/docs/home/。