本文大部分是原理,后期打算开个专栏,咱也玩玩知识付费~
一、发展史
在云计算领域有几个很常见的词汇:IaaS、PaaS、SaaS。IaaS就是基础平台即服务,国内有阿里云等;PaaS是平台即服务,在早些时候新浪云SAE较为有名;SaaS就是软件即服务,最大的Office厂商MS的Office365就是一个很好的代表。在最开始的时候PaaS基本就是人肉运维,慢慢的又出现了一系列的自动化工具,再后来专门做PaaS的一家公司创造了Docker。Docker变成了PaaS的一个标准,但是随着容器化的发展也出现了一系列的问题。容器化后容器的映射关系变得异常艰难,而且这仅仅是容器化发展的一个小小的问题。那么随着容器化的步伐,衍生出了一些列的资源管理器,最开始是Apache Mesos,Mesos由加州的伯克利大学研发出来,随后被推特选中,大规模的在推特盛行。在2019年5月,特推在旧金山开展了技术发布会,在该会上产品负责人宣布推特以后全部使用Kubernetes。第二款资源管理软件是Docker自家推出的Docker Swarm平台。Docker Swarm是一个非常轻量的资源管理平台。但是Swarm功能较为简单,而且国内云厂商阿里云在2019年7月宣布在选择资源管理框架的时候不支持Swarm,默认Kubernetes。Google其实在Kubernetes诞生10年前就使用了容器化基础架构Borg。Google为了在资源管理器上占有优势,就使用Golang开发了Kubernetes。
类似于Borg系统,Kubernetes的架构如下:
- etcd是一个可信赖的分布式键值存储平台,在v2版本中支持内存存储,在v3中新增了本地存储,因此当etcd关机后并不会丢失数据。在Kubernetes1.11之前还不支持v3版本的etcd。存储Kubernetes集群的所有重要的信息。
- APIServer是所有服务访问的统一入口。
- Scheduler负责接收任务,选择合适的节点进行任务的分配。
- Replication Controller负责维持期望的副本数目。
- Kubelet负责与Docker的容器引擎交互,实现容器的生命周期管理。
- Kube Proxy负责写入规则到iptables或者ipvs实现服务映射访问。
- CoreDNS实现为集群中的SVC创建A记录,实现域名访问。
- Dashborad实现集群的B/S访问体系。
- Ingress Controller实现七层代理,Kubernetes官方只能实现四层代理。
- Fedetation提供一个可以跨集群中心的多Kubernetes统一管理功能。
- Prometheus和ELK提供Kubernetes集群监控和日志统一分析接入平台。
二、POD
Pod分为自主式Pod和控制器管理的Pod,自主式Pod一旦死亡就会无法拉起。在Pod中,有一个容器叫做pause,只有存在Pod,该容器就会启动。在该Pod中的其他容器公用pause的网络栈和存储卷,因此在同一个Pod里容器的端口不能冲突。
-
Replication Controller用来确保容器应用的副本数量始终保持在用户定义的副本数量上,如果容器异常退出,那么将会创建新的Pod来替代,如果创建过多,过多的Pod将会被回收。在新版本的Kubernetes中,建议使用ReplicaSet取代Replication Controller,原因是RS支持标签操作。RS支持集合式的Selector。
-
虽然ReplicaSet可以独立使用,但是还是建议使用Deployment来自动管理RS,这样无需担心跟其他机制的不兼容问题。
Deployment继承了上面描述的Replication Controller全部功能。还支持事件和状态查看,回滚,版本记录,暂停和启动。
-
HPA(Horizontal Pod Autoscaling)仅适用于Deployment和ReplicaSet,在v1版本中仅支持根据Pod的CPU利用率扩容。
HPA(Horizontal Pod Autoscaler)是kubernetes(以下简称k8s)的一种资源对象,能够根据某些指标对在statefulSet、replicaController、replicaSet等集合中的pod数量进行动态伸缩,使运行在上面的服务对指标的变化有一定的自适应能力。
HPA目前支持四种类型的指标,分别是Resource、Object、External、Pods。其中在稳定版本autoscaling/v1中只支持对CPU指标的动态伸缩,在测试版本autoscaling/v2beta2中支持memory和自定义指标的动态伸缩,并以annotation的方式工作在autoscaling/v1版本中。
HPA在k8s中的结构:
apiVersion: autoscaling/v2beta2 kind: HorizontalPodAutoscaler metadata: name: php-apache namespace: default spec: # HPA的伸缩对象描述,HPA会动态修改该对象的pod数量 scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: php-apache # HPA的最小pod数量和最大pod数量 minReplicas: 1 maxReplicas: 10 # 监控的指标数组,支持多种类型的指标共存 metrics: # Object类型的指标 - type: Object object: metric: # 指标名称 name: requests-per-second # 监控指标的对象描述,指标数据来源于该对象 describedObject: apiVersion: networking.k8s.io/v1beta1 kind: Ingress name: main-route # Value类型的目标值,Object类型的指标只支持Value和AverageValue类型的目标值 target: type: Value value: 10k # Resource类型的指标 - type: Resource resource: name: cpu # Utilization类型的目标值,Resource类型的指标只支持Utilization和AverageValue类型的目标值 target: type: Utilization averageUtilization: 50 # Pods类型的指标 - type: Pods pods: metric: name: packets-per-second # AverageValue类型的目标值,Pods指标类型下只支持AverageValue类型的目标值 target: type: AverageValue averageValue: 1k # External类型的指标 - type: External external: metric: name: queue_messages_ready # 该字段与第三方的指标标签相关联,(此处官方文档有问题,正确的写法如下) selector: matchLabels: env: "stage" app: "myapp" # External指标类型下只支持Value和AverageValue类型的目标值 target: type: AverageValue averageValue: 30
HPA的特性结合第三方的监控应用,使得部署在HPA伸缩对象(statefulSet、replicaController、replicaSet)上的服务有了非常灵活的自适应能力,能够在一定限度内复制多个副本来应对某个指标的急剧飙升,也可以在某个指标较小的情况下删除副本来让出计算资源给其他更需要资源的应用使用,维持整个系统的稳定。非常适合于一些流量波动大,机器资源吃紧,服务数量多的业务场景,如:电商服务、抢票服务、金融服务等。
许多监控系统通过adapter实现了接口,给HPA提供指标数据。在这里我们具体介绍一下prometheus监控系统的adapter。
prometheus是一个知名开源监控系统,具有数据维度多,存储高效,使用便捷等特点。用户可以通过丰富的表达式和内置函数,定制自己所需要的监控数据。
prometheus-adapter在prometheus和api-server中起到了适配者的作用。prometheus-adapter接受从HPA中发来,通过apiserver aggregator中转的指标查询请求,然后根据内容发送相应的请求给prometheus拿到指标数据,经过处理后返回给HPA使用。prometheus可以同时实现metrics.k8s.io
、custom.metrics.k8s.io
、external.metrics.k8s.io
三种api接口,代替k8s自己的matrics-server,提供指标数据服务。prometheus-adapter部署能否成功的关键在于配置文件是否正确。# 指标规则,可以多个规则共存,上一个规则的结果会传给下一个规则 rules: # 计算指标数据的表达式 - metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[5m])) by (<<.GroupBy>>) # 指标重命名,支持正则表达式。这里表示删除指标名字中的"_seconds_total" name: as: "" matches: (.*)_seconds_total$ # 指标与k8s资源通过标签关联,这里将指标通过标签与k8s的namspace和pod相互关联 resources: overrides: namespace: resource: namespace pod: resource: pod # 过滤指标条件 seriesFilters: [] # 指标查询表达式,可以根据标签等条件,筛选特定的指标 seriesQuery: '{namespace!="",pod!=""}'
v1的模板可能是大家平时见到最多的也是最简单的,v1版本的HPA只支持一种指标 —— CPU。传统意义上,弹性伸缩最少也会支持CPU与Memory两种指标,为什么在Kubernetes中只放开了CPU呢?其实最早的HPA是计划同时支持这两种指标的,但是实际的开发测试中发现,内存不是一个非常好的弹性伸缩判断条件。因为和CPU不同,很多内存型的应用,并不会因为HPA弹出新的容器而带来内存的快速回收,因为很多应用的内存都要交给语言层面的VM进行管理,也就是内存的回收是由VM的GC来决定的。这就有可能因为GC时间的差异导致HPA在不恰当的时间点震荡,因此在v1的版本中,HPA就只支持了CPU一种指标。
-
StatefulSet是为了解决有状态服务的问题,应用场景包含:稳定的持久化存储(Pod重新调度后还可以访问到相同的持久化数据),稳定的网络标志(Pod重新部署后其Hostname和Podname不会发生变化),有序的删除扩展收缩(Pod是有顺序的,在容器部署或者扩展的时候要依据定义的顺序依次进行,基于init containtor实现)。
-
DaemonSet确保全部或者一些Node上运行一个Pod副本。当有Node加入集群的时候,会为他们新增一个Pod。当有Node从集群中移除的时候,这些Pod也会被回收。删除DaemonSet将会删除他们创建的所有Pod。典型的应用场景是:每个Node上运行的日志收集、监控、存储客户端等。
- Job负责批处理的任务,它保证批处理任务的一个或者多个Pod成功结束。Cron Job管理基于时间的Job。
Pod内多个容器的网络通信使用lo,各个Pod之间使用Overlay Network,Pod与Service之间使用Iptables规则,在新版本中Pod与Service使用LVS的转发机制。
Flannel是CoreOS团队针对Kubernetes设计的一个网络规划服务,简单地说,它的功能是让集群中不同节点的主机创建Docker容器都具有全集群唯一的虚拟IP地址。而且Flannel还可以在这些IP地址之间建立一个覆盖网络,通过这个覆盖网络将数据包原封不动的传递到目标容器内。
HPA与滚动更新的区别:
目前在kubernetes中,可以通过直接管理复制控制器来执行滚动更新,也可以使用deployment对象来管理底层副本集。HPA只支持后一种方法:HPA绑定到部署对象,设置部署对象的大小,部署负责设置底层副本集的大小。
HPA不能使用复制控制器的直接操作进行滚动更新,即不能将HPA绑定到复制控制器并进行滚动更新(例如,使用Kubectl滚动更新)。这不起作用的原因是,当滚动更新创建新的复制控制器时,HPA将不会绑定到新的复制控制器。
Flannel的跨主机通信机制:
在上图中有两个真实的主机,在同一个主机内的Docker容器互相通信通过Docker0网桥即可。但是跨主机通信通过Docker0网桥就无能为力了。当Docker容器启动的时候,Docker0网桥会为其分配一个IP地址,当在宿主机上安装Flanneld的时候会创建Flannel0网桥,Flannel0会监听Docker0的全部数据。当宿主机中的一个Docker向另一个宿主机的Docker容器发起网络请求的时候,数据包会被Flanneld截取,Flanneld将数据包进行封装,发送至另一台主机,另一台主机的Flanneld程序解封装数据包,通过Flannel0网桥和Docker0网桥分发数据到指定的容器。Flanneld封装的数据包如下:
三、资源清单相关
在Kubernetes中的资源实例化后叫做对象。在Kubernetes中的名称空间级别的资源有以下分类:
- 工作负载型资源:Pod、RS、Deployment、StatefulSet、DaemonSet、Job、CronJob
- 服务发现和负载均衡型资源:Service、Ingress
- 配置与存储型资源:Volume、CSI(容器存储接口)
- 特殊类型的资源:ConfigMap、Secret、DownwardAPI
集群级别的资源有:Namespace、Node、Role、ClusterRole、RoleBinding、ClusterRoleBinding。
元数据型资源:HPA、PodTemplate、LimitRange。
四、POD生命周期
如下图所示,一个Pod的创建,首先会进行容器环境的初始化,然后创建pause容器,用来共享网络栈和存储卷。然后执行多个initc,初始化容器initc的步骤是串行的。当所有的initc执行完成以后的start部分指的是在容器运行之前的操作,stop部分指的是容器运行结束的操作。在容器整个运行过程中liveness部分用来生存检测。但是在容器的必要条件全局具备的时候还要进行readiness的就绪检测的应用场景就是当一个Pod运行起来的时候,有可能Pod内部的服务还有有全部加载完成,因此需要进行就绪检测。当就绪检测通过的时候再将Pod的运行状态改成RUNNING状态。在Pod的运行过程中,一个极端的情况就是容器内应用剩下的全部都是僵尸进程了,但是表现的来的容器状态还是运行中,因此需要在整个服务的运行周期内都需要进行服务的生存检测。
Init容器与普通的容器相似,但是Init容器总是运行到成功完成为止。每个Init容器都必须在下一个容器启动完成以后运行。如果Pod的容器失败,Kubernetes将会不断的重启该Pod,直到所有的Init容器全部成功运行为止。只有当Pod对应的RestartPoliy为Never的时候才不会重新启动。
Init容器与应用容器分离,因此Init容器可以包含一系列的应用容器运行过程中不需要的实用工具。还可以进行定制化安装,由于Init容器的存在,可以大大降低应用容器的大小。Init容器使用Linux Namespace,所以相对应用程序来说具有不同的文件系统视图,因此它们具备访问Secret的权限,而应用程序的容器则不能。
在所有的Init容器没有成功之前,Pod将不会变成Ready状态,Init容器的端口将不会在Service中聚集。正在初始化中的Pod处于Pending状态,但是会将Initializing状态设置为true。如果Pod重启,所有的Init容器必须重新执行。
Init容器具有应用容器的全部字段,除去就绪检测字段readinessProbe。在Pod中每个app和int的名称必须是唯一的。
虽然initc可以进行就绪探测,但是在initc中进行就绪探测是不太友好的,因为存在initc探测成功但是指定的容器连接目标容器失败的情况,所以最好还是在主容器中进行探测。
五、探针技术
探针技术是kubelet对容器执行的定期诊断。kubelet调用容器实现的处理器有三种类型:ExecAction、TCPSocketAction、HTTPGetAction。
- ExecAction:在容器内执行指定的命令,如果命令退出的时候返回码为0,就会认为是成功的。
- TCPSocketAction:对指定的端口上的容器的IP地址进行TCP检查。如果端口打开,则会认为是成功的。
- HTTPGetAction:对指定的端口和路径上的容器的IP地址执行GET请求,如果响应码大于等于200并且小于400,则会认为是成功的。
对于每次探测,都会出现三种结果:成功、失败和未知。
探测方案存在两种形式:
- LivenessProbe:指示容器是否在运行。如果探测失败,kubelet将会杀死容器。如果容器不提供探针,默认状态是Success。
- ReadinessProbe:指示容器是否准备好服务请求。如果探测失败,端点控制器将会从与Pod匹配的所有Service的端点中删除该Pod的IP地址。初始延迟之前的就绪状态默认为Failure。如果容器不提供探针,默认状态为Success。
Pod的状态:
- Pending:Pod已经被Kubernetes接受,但是有一个或者多个容器尚未创建。等待时间包括调度Pod的时间和通过网络下载镜像的时间。
- Running:该Pod已经被绑定在一个节点上,Pod中的所有容器都被创建。至少有一个容器正在运行或者处于启动或重启状态。
- Succeeded:Pod中的所有容器都被成功终止,并且不会再重新启动。
- Faild:Pod中的所有容器都已经终止了,并且至少有一个容器是因为失败而终止的,也就是说容器非0状态或者被系统终止。
- Unknown:因为某些原因无法取得Pod的状态,通常是因为与Pod所在的主机通信失败。
六、资源控制器
Kubernetes中内建了很多类型的控制器,这些相当于一些状态机,用来控制Pod的具体行为。控制器有以下几种类型:
- ReplicationController和ReplicaSet :用来确保容器应用的副本数目维持在用户定义的副本数上。RS与RC的最大不同是支持label选择。ReplicaSet的YAML应该在spec中指定replicas的数量和selector.matchLabels标签选择器来选择通过Key-Value实现选择POD,然后通过指定template.metadata和template.spec.containters
- Deployment:为Pod的RS提供了一个声明式定义的方法,用来替代以前的ReplicationController来方便管理应用。一般使用Deployment定义无状态应用。
- 定义Deployment来创建Pod和RS
- 滚动升级和回滚应用
- 扩容和缩容
- 暂停和继续Deployment
- DaemonSet:确保全部或者一些Node运行在一个Pod副本上。当有Node加入集群的时候,也会为它们新增加一个Pod。当有Node在集群移除的时候,这些Pod也会被回收。删除DaemonSet将会删除它创建的所有Pod。
- StatefulSet:用于对待有状态服务,通常需要持久化存储,并且每个节点都需要特殊对待的服务。
- Job/CronJob
- Horizontal Pod Autoscling(HPA)
七、服务
Service也叫做SVC,用来进行服务发现。SVC中只有一种轮询算法RR,并且只提供四层的负载均衡,无法实现七层负载均衡。
在Kubernetes中SVC有一下四种类型:
-
ClusterIP:默认类型,自动分配一个仅Cluster内部可以访问的虚拟IP。在同一个Node上的Pod和Node本身可以访问nginx-svc。
在YAML配置文件中指定spec.ports.port和spec.ports.targetPort
-
NodePort:在ClusterIP基础上为Service在每台机器上绑定端口,这样就可以通过NodeIP:NodePort来访问该服务。
在YAML配置文件中指定spec.ports.port和spec.ports.targetPort以及spec.ports.nodePort,NodePort在30000往上分配。在集群外使用NodePort访问。
- LoadBalancer:在NodePort的基础上,借助Cloud Provider创建一个外部的(调用IaaS提供商的API)负载均衡器,并将请求转发到NodeIP:NodePort上。
- ExternalName:把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何代理被创建,只有v1.7或者更高版本的kube-dns来支持。通常是CNAME记录,指向FQDN。
在Kubernetes集群中,每个Node运行一个kube-proxy进程。kube-proxy负责为service实现了一种VIP的形式,而不是ExternalName。在Kubernetes v1.0版本中,代理完全在userspace中。在Kubernetes v1.1版本中,新增了iptables代理,但是并不是默认的运行模式。从Kubernetes v1.2开始,默认使用ipatables代理。在Kubernetes v1.8.0-beta0中,添加了ipvs代理。在Kubernetes v1.14版本中,默认使用ipvs代理。在Kubernetes v1.1版本中,新增加了IngressAPI,用来提供七层服务。Kubernetes在历次版本迭代中不使用DNS作为负载均衡的最主要原因是DNS负载均衡方式会在客户端缓存。
八、代理
- userspace模式:在UserSpace模式下,Client想要访问Pod必须通过iptables找到kube-proxy在到想要访问的Pod,即使是同一个主机内的Pod。而且api server也需要对kube-proxy进行监控,因此kube-proxy压力较大。
-
IPVS代理模式:这种模式下,kube-proxy会监视Kubernetes的Service对象和Endpoint对象,调用netlink接口来相应的创建ipvs规则并定期与Kubernetes的Service对象和Endpoint对象同步ipvs规则,来确保ipvs状态与期望的一致。访问服务的时候,流量将被重定向到其中一个后端的Pod。与iptables类似,ipvs类似于netfilter的hook功能,但是使用Hash表作为底层数据结构并在内核空间中工作。这意味着ipvs可以更快的进行流量重定向,并且在同步代理规则时具备更好的性能。此外,ipvs为负载均衡算法提供了更多的选项:RR(轮询调度)、LC(最小连接数)、DH(目标Hash)、SH(源Hash)、SED(最短期望延迟)、NQ(不排队调度)。IPVS模式假定在kube-proxy之前的节点上都已经安装了IPVS模块,当kube-proxy以IPVS代理模式启动的时候,kube-proxy将验证节点上是否安装了IPVS模块。如果未安装,则kube-proxy将回退iptables代理模式。
为了实现上面的各种代理模式,api server用户通过kubectl命令向api server发送创建service命令,api server接收到请求将数据存储到etcd中。kube-proxy的每个节点中都有一个叫做kube-proxy的进程,这个进程负责感知service和pod的变化,并将变化的信息写入本地的iptables或者ipvs。使用iptables的NAT等技术或者ipvs将Virtual IP的流量转至Endpoint中。
为了v1.11使用ipvs,需要手动注入两个内核模块变量,否则自动降级到iptables:
echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables echo 1 > /proc/sys/net/ipv4/ip_forward
并且:在配置文件/etc/sysconfig/kubelet中添加:
KUBE_PROXY_MODE=ipvs
并且使用modprobe在系统启动时开启如下模块:ip_vs,ip_vs_rr,ip_vs_wrr,ip_vs_sh,nf_conntrack_ipv4
- Ingress:API Server通过建立连接监听事件变化,变化的事件写入UpdateChannel中,Nginx的主进程定期拉取UpdateChannel中的数据。将拉取来的数据写到syncQueue中,SyncController定期获取syncQueue中的数据,如果特别紧急的数据可以直接通过Store协程与SyncController协程进行交互,而不需中间的流程。如果Nginx的配置文件需要重写,那么重写完成后需要reload。
Ingress是一个特殊的调度器,主要是进行SSL会话卸载,支持七层均衡,Service只能发生四层均衡,因此可以在后端的POD上只运行HTTP,外部客户端与Ingress保持HTTPS会话。
九、存储
Kubernetes的存储主要有:ConfigMap存储、Secret存储、Volume卷存储。
ConfigMap功能在Kubernetes v1.2中版本中引入,许多应用程序会从配置文件、命令行参数和环境变量中读取配置信息。ConfigMap API给我们提供了容器中的注入配置信息的机制,ConfigMap可以被用来保存单个属性,也可以用来保存整个配置文件或者JSON二进制大对象。
- 使用目录创建
- 使用文件创建
- 使用字面值创建
Secret用来存储密码、Token、秘钥等敏感数据的配置问题,Secret可以以Volume或者环境变量的方式使用。Secret有3种类型:
- Service Account:用来访问Kubernetes API,有Kubernetes自动创建,并且会自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中。
- Opaque:Base64编码格式的Secret,用来存储密码和秘钥等。
- kubernetes.io/dockerconfiguration:用来存储私有的docker registry的认证信息。
Kubernetes中的卷的生命与它封装的Pod相同。但是卷的生命要比所有容器都长,当容器重新启动的时候,存储在卷中的数据不会丢失。Kubernetes支持多种类型的卷,Pod可以使用任意数量的卷。Kubernetes支持以下类型的卷:
- awsElasticBlockStore、azureDisk、azureFile、cephfs、csi、downwardAPI、emptyDir
- fc、docker、gcePersistentDisk、gitRepo、glusterfs、hostPath、iscsi、local、nfs
- persistentVolumeClaim、projected、portworxVolume、quobyte、rbd、scaleIO、secret
- storageos、vsphereVolume
常用的卷存储类型:
- emptyDir:当Pod分配给节点时,首先创建emprtyDir卷,并且只要该Pod在该节点运行,该卷就会存在。该卷起初是空的,Pod容器可以读取和写入emptyDir卷中相同的文件,尽管该卷可以挂载到每个容器中相同或者不同路径上。当出于任何原因从节点删除该Pod的时候,emptyDir将会永久删除。emptyDir一般用于暂存空间,长时间计算崩溃恢复时的检查点和Web服务器提供的数据。
- hostPath:将主机节点的文件系统中的文件或者目录挂在到集群中,一般用于运行时需要访问Docker内部的容器(使用/var/lib/docker)或者在容器中运行cADriver(使用/dev/cgroups)。
十、PV和PVC
PV的出现主要为明确运维工程师和开发工程师的主要任务,PV是Kubernetes提供的抽象存储的概念,但是系统中可能成千上万个PV,因此出现了PVC。PVC会自动匹配PV进行绑定。PV独立于Pod之上。PV分为静态PV和动态PV。静态PV是集群管理员创建的一些PV。当管理员创建的静态PV都不满足用户时,集群可能会动态的尝试为PVC创建卷。PV一旦被绑定,就会具备排他性,PV和PVC一一对应。
PVC保护的目的是确保由Pod正在使用的PVC不会被系统移除。当启用PVC保护的Alpha功能的时候,如果一个用户删除了正在使用的PVC,则这个PVC不会立即删除,而是将会被延迟,直到这个PVC不再被任何Pod使用。
PV是以插件的形式实现的:
- GCEPersistentDisk、AWSElasticBlockStore、AzureFile、AzureDisk、FC(Fibre Channel)
- FlexVolume、Flocker、NFS、iSCSI、RBD(Ceph Block Device)、CephFS
- Cinder(OpenStack Block Storage)、Glusterfs、VsphereVolume、QuobyteVolumes
- HostPath、VMware Photon、Portworx Volumes、ScaleIO Volumes、StorageOS
PV具备一定的访问模式:
- ReadWriteOnce(RWO):该卷可以被单个节点以读写方式挂载
- ReadOnlyMany(ROX):该卷可以被多个节点以只读方式挂载
- ReadWriteMany(RWX):该卷可以被多个节点以读写方式挂载
在Kubernetes中回收策略:
- Retain(保留):手动回收
- Recycle(回收):基本删除,相当于rm -rf
- Delete(删除):关联的存储资产将被删除
目前只有NFS和HostPath支持回收策略,其他仅支持删除策略。
状态:
- Available(可用):一块空闲资源你还没有进行任何声明绑定
- Bound(已绑定):卷已经被声明绑定
- Released(已释放):声明被删除,但是资源还未被集群重新声明
- Failed(失败):该卷的自动回收
十一、StatefulSet
- 匹配Pod Name的模式为StatefulSetName-序号
- StatefulSet为每个Pod副本创建了一个DNS域名,这个域名的格式为:PodName.HeadlessServerName,也就意味着服务间是通过Pod域名来通信的而不是通过PodIP。当Node发生故障的时候,Pod会被漂移到其他的Node上,PodIP会发生变化,但是Pod的域名不会发生变化。
- StatefulSet使用Headless服务来控制Pod的域名,格式为:ServiceName.NameSpace.svc.ClusterDomainName
- 根据volumeClaimTemplate为每个Pod创建一个PVC,PVC的命名匹配模式是:VolumeClaimTemplate.name-PodName。
- 删除Pod不会删除PVC,手动删除PVC将会释放PV。
十二、集群调度
Scheduler 是Kubernetes 的调度器,主要的任务是把定义的 pod 分配到集群的节点上。要考虑的问题:
- 公平:如何保证每个节点都能被分配资源
- 资源高效利用:集群所有资源最大化被使用
- 效率:调度的性能要好,能够尽快地对大批量的 pod 完成调度工作
- 灵活:允许用户根据自己的需求控制调度的逻辑
Sheduler 是作为单独的程序运行的,启动之后会一直监听 API Server,获取 PodSpec.NodeName 为空的Pod, 对每个Pod 都会创建一个 binding,表明该 pod 应该放到哪个节点上。因为不为空的已经制定了Node节点。
调度分为几个部分:
-
首先是过滤掉不满足条件的节点,这个过程称为 predicate。
- 然后对通过的节点按照优先级排序,这个是 priority ;最后从中选择优先级最高的节点。如果中间任何一步骤有错误,就直接返回错误。
Predicate 有一系列的算法可以使用:
- PodFitsResources :节点上剩余的资源是否大于 pod 请求的资源
- PodFitsHost :如果 pod 指定了 NodeName,检查节点名称是否和 NodeName 匹配
- PodFitsHostPorts :节点上已经使用的 port 是否和 pod 申请的 port 冲突
- PodSelectorMatches :过滤掉和 pod 指定的 label 不匹配的节点
- NoDiskConflict :已经 mount 的 volume 和 pod 指定的 volume 不冲突,除非它们都是只读
如果在 predicate 过程中没有合适的节点,pod 会一直在 pending 状态,不断重试调度,直到有节点满足条件。经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程:按照优先级大小对节点排序。
优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重(该项的重要性)。这些优先级选项包括:
- LeastRequestedPriority :通过计算 CPU 和 Memory 的使用率来决定权重,使用率越低权重越高。换句话说,这个优先级指标倾向于资源使用比例更低的节点
- BalancedResourceAllocation :节点上 CPU 和 Memory 使用率越接近,权重越高。这个应该和上面的一起使用,不应该单独使用
- ImageLocalityPriority :倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高
通过算法对所有的优先级项目和权重进行计算,得出最终的结果。
除了 kubernetes 自带的调度器,你也可以编写自己的调度器。通过 spec:schedulername 参数指定调度器的名字,可以为 pod 选择某个调度器进行调度。比如下面的 pod 选择 my-scheduler 进行调度,而不是默认的 default-scheduler。
节点亲和性配置:pod.spec.nodeAffinity
- preferredDuringSchedulingIgnoredDuringExecution:软策略
- requiredDuringSchedulingIgnoredDuringExecution:硬策略
POD亲和性配置:pod.spec.affinity.podAffinity/podAntiAffinity
-
preferredDuringSchedulingIgnoredDuringExecution:软策略
- requiredDuringSchedulingIgnoredDuringExecution:硬策略
亲和性/反亲和性调度策略比较如下:
调度策略 | 匹配标签 | 操作符 | 拓扑域支持 | 调度目标 |
---|---|---|---|---|
nodeAffinity | 主机 | In, NotIn, Exists, DoesNotExist, Gt, Lt | 否 | 指定主机 |
podAffinity | POD | In, NotIn, Exists, DoesNotExist | 是 | POD与指定POD的同一个拓扑域 |
podAnitAffinity | POD | In, NotIn, Exists, DoesNotExist | 是 | POD与指定POD不在同 一拓扑域 |
节点亲和性,是 pod 的一种属性(偏好或硬性要求),它使 pod 被吸引到一类特定的节点。Taint 则相反,它使节点能够 一类特定的 pod。
Taint 和 toleration 相互配合,可以用来避免 pod 被分配到不合适的节点上。每个节点上都可以应用一个或多个 taint ,这表示对于那些不能容忍这些 taint 的 pod,是不会被该节点接受的。如果将 toleration 应用于 pod 上,则表示这些 pod 可以(但不要求)被调度到具有匹配 taint 的节点上。
污点 ( Taint ) 的组成:
使用 kubectl taint 命令可以给某个 Node 节点设置污点,Node 被设置上污点之后就和 Pod 之间存在了一种相 斥的关系,可以让 Node 拒绝 Pod 的调度执行,甚至将 Node 已经存在的 Pod 驱逐出去。
每个污点的组成如下:
key=value:effect
每个污点有一个 key 和 value 作为污点的标签,其中 value 可以为空,effect 描述污点的作用。当前 taint effect 支持如下三个选项:
- NoSchedule :表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上
- PreferNoSchedule :表示 k8s 将尽量避免将 Pod 调度到具有该污点的 Node 上
- NoExecute :表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上,同时会将 Node 上已经存在的 Pod 驱逐出去
污点的设置、查看和去除:
# 设置污点
kubectl taint nodes node1 key1=value1:NoSchedule
# 节点说明中,查找 Taints 字段 kubectl describe pod pod-name
# 去除污点
kubectl taint nodes node1 key1:NoSchedule-
容忍:
设置了污点的 Node 将根据 taint 的 effect:NoSchedule、PreferNoSchedule、NoExecute 和 Pod 之间产生 互斥的关系,Pod 将在一定程度上不会被调度到 Node 上。 但我们可以在 Pod 上设置容忍 ( Toleration ) ,意思 是设置了容忍的 Pod 将可以容忍污点的存在,可以被调度到存在污点的 Node 上。
具体的参数:pod.spec.tolerations
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
tolerationSeconds: 3600
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
- key: "key2"
operator: "Exists"
effect: "NoSchedule"
- 其中 key, vaule, effect 要与 Node 上设置的 taint 保持一致
- operator 的值为 Exists 将会忽略 value 值
- tolerationSeconds 用于描述当 Pod 需要被驱逐时可以在 Pod 上继续保留运行的时间
当不指定 key 值时,表示容忍所有的污点 key:
tolerations:
- operator: "Exists"
当不指定 effect 值时,表示容忍所有的污点作用:
tolerations:
- key: "key"
operator: "Exists"
有多个 Master 存在时,防止资源浪费,可以如下设置:
kubectl taint nodes Node-Name node-role.kubernetes.io/master=:PreferNoSchedule
指定调度节点:
-
Pod.spec.nodeName:将 Pod 直接调度到指定的 Node 节点上,会跳过 Scheduler 的调度策略,该匹配规则是强制匹配。
- Pod.spec.nodeSelector.type:通过 kubernetes 的 label-selector 机制选择节点,由调度器调度策略匹配 label, 而后调度 Pod 到目标节点,该匹配规则属于强制约束
十三、集群安全
Kubernetes 作为一个分布式集群的管理工具,保证集群的安全性是其一个重要的任务。API Server 是集群内部 各个组件通信的中介,也是外部控制的入口。所以 Kubernetes 的安全机制基本就是围绕保护 API Server 来设计 的。Kubernetes 使用了认证(Authentication)、鉴权(Authorization)、准入控制(Admission Control)三步来保证API Server的安全。
对于认证和鉴权,K8S提供了插件接口来提供认证鉴权的第三方实现,对于认证插件,主要一个插件通过认证和鉴权,则不会尝试其他插件。
三种方式:
- HTTP Token 认证:通过一个 Token 来识别合法用户
HTTP Token 的认证是用一个很长的特殊编码方式的并且难以被模仿的字符串 - Token 来表达客户的一 种方式。Token 是一个很长的很复杂的字符串,每一个 Token 对应一个用户名存储在 API Server 能访 问的文件中。当客户端发起 API 调用请求时,需要在 HTTP Header 里放入 Token - HTTP Base 认证:通过 用户名+密码 的方式认证
用户名+:+密码 用 BASE64 算法进行编码后的字符串放在 HTTP Request 中的 Heather
Authorization 域里发送给服务端,服务端收到后进行编码,获取用户名及密码 - 最严格的 HTTPS 证书认证:基于 CA 根证书签名的客户端身份认证方式
两种访问类型:
Kubenetes 组件对 API Server 的访问:kubectl、Controller Manager、Scheduler、kubelet、kube- proxy。Kubernetes 管理的 Pod 对容器的访问:Pod(dashborad 也是以 Pod 形式运行)。
Controller Manager、Scheduler 与 API Server 在同一台机器,所以直接使用 API Server 的非安全端口。访问, --insecure-bind-address=127.0.0.1。kubectl、kubelet、kube-proxy 访问 API Server 就都需要证书进行 HTTPS 双向认证
证书颁发方式:
- 手动签发:通过 k8s 集群的跟 ca 进行签发 HTTPS 证书
- 自动签发:kubelet 首次访问 API Server 时,使用 token 做认证,通过后,Controller Manager 会为kubelet 生成一个证书,以后的访问都是用证书做认证了
kubeconfig 文件包含集群参数(CA证书、API Server地址),客户端参数(上面生成的证书和私钥),集群 context 信息(集群名称、用户名)。Kubenetes 组件通过启动时指定不同的 kubeconfig 文件可以切换到不同的集群。
Pod中的容器访问API Server。因为Pod的创建、销毁是动态的,所以要为它手动生成证书就不可行了。Kubenetes使用了Service Account解决Pod 访问API Server的认证问题。
Secret 与 SA 的关系:Kubernetes 设计了一种资源对象叫做 Secret,分为两类,一种是用于 ServiceAccount 的 service-account- token, 另一种是用于保存用户自定义保密信息的 Opaque。ServiceAccount 中用到包含三个部分:Token、 ca.crt、namespace。
默认将证书和TOKEN存储到/run/secrets/kubernetes.io/serviceaccount
- token是使用 API Server 私钥签名的 JWT。用于访问API Server时,Server端认证
- ca.crt,根证书。用于Client端验证API Server发送的证书
- namespace, 标识这个service-account-token的作用域名空间
kubectl get secret --all-namespaces
kubectl describe secret default-token-5gm9r --namespace=kube-system
默认情况下,每个 namespace 都会有一个 ServiceAccount,如果 Pod 在创建时没有指定 ServiceAccount, 就会使用 Pod 所属的 namespace 的 ServiceAccount。
上面认证过程,只是确认通信的双方都确认了对方是可信的,可以相互通信。而鉴权是确定请求方有哪些资源的权 限。API Server 目前支持以下几种授权策略 (通过 API Server 的启动参数 “--authorization-mode” 设置)。
- AlwaysDeny:表示拒绝所有的请求,一般用于测试
- AlwaysAllow:允许接收所有请求,如果集群不需要授权流程,则可以采用该策略
- ABAC(Attribute-Based Access Control):基于属性的访问控制,表示使用用户配置的授权规则对用户 请求进行匹配和控制
- Webbook:通过调用外部 REST 服务对用户进行授权
- RBAC(Role-Based Access Control):基于角色的访问控制,现行默认规则
RBAC(Role-Based Access Control)基于角色的访问控制,在 Kubernetes 1.5 中引入,现行版本成为默认标准。相对其它访问控制方式,拥有以下优势:
- 对集群中的资源和非资源均拥有完整的覆盖
- 整个 RBAC 完全由几个 API 对象完成,同其它 API 对象一样,可以用 kubectl 或 API 进行操作
- 可以在运行时进行调整,无需重启 API Server
RBAC:
K8S 1.6之后支持。RBAC 引入了 4 个新的顶级资源对象:Role、ClusterRole、RoleBinding、ClusterRoleBinding,4 种对象类型均可以通过 kubectl 与 API 操作。
需要注意的是 Kubenetes 并不会提供用户管理,那么 User、Group、ServiceAccount 指定的用户又是从哪里 来的呢? Kubenetes 组件(kubectl、kube-proxy)或是其他自定义的用户在向 CA 申请证书时,需要提供一个 证书请求文件:
{
"CN": "admin",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [{
"C": "CN",
"ST": "HangZhou",
"L": "XS",
"O": "system:masters", "OU": "System"
}]
}
API Server会把客户端证书的 CN 字段作为User,把 names.O 字段作为Group。kubelet 使用 TLS Bootstaping 认证时,API Server 可以使用 Bootstrap Tokens 或者 Token authentication file 验证 =token,无论哪一种,Kubenetes 都会为 token 绑定一个默认的 User 和 Group。
Pod使用 ServiceAccount 认证时,service-account-token 中的 JWT 会保存 User 信息。
有了用户信息,再创建一对角色/角色绑定(集群角色/集群角色绑定)资源对象,就可以完成权限绑定了。
Role and ClusterRole:
在 RBAC API 中,Role 表示一组规则权限,权限只会增加(累加权限),不存在一个资源一开始就有很多权限而通过 RBAC 对其进行减少的操作。Role 可以定义在一个 namespace 中,如果想要跨 namespace 则可以创建 ClusterRole。
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" indicates the core API group
resources: ["pods"]
verbs: ["get", "watch", "list"]
ClusterRole 具有与 Role 相同的权限角色控制能力,不同的是 ClusterRole 是集群级别的,ClusterRole 可以用于:
- 集群级别的资源控制( 例如 node 访问权限 )
- 非资源型 endpoints( 例如 /healthz 访问 )
- 所有命名空间资源控制(例如 pods )
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]
RoleBinding and ClusterRoleBinding
RoloBinding 可以将角色中定义的权限授予用户或用户组,RoleBinding 包含一组权限列表(subjects),权限列 表中包含有不同形式的待授予权限资源类型(users, groups, or service accounts);RoloBinding 同样包含对被 Bind 的 Role 引用;RoleBinding 适用于某个命名空间内授权,而 ClusterRoleBinding 适用于集群范围内的授权。
将 default 命名空间的 pod-reader Role 授予 jane 用户,此后 jane 用户在 default 命名空间中将具有 pod- reader 的权限。
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: jane
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
RoleBinding 同样可以引用 ClusterRole 来对当前 namespace 内用户、用户组或 ServiceAccount 进行授权, 这种操作允许集群管理员在整个集群内定义一些通用的 ClusterRole,然后在不同的 namespace 中使用 RoleBinding 来引用。
使用 ClusterRoleBinding 可以对整个集群中的所有命名空间资源权限进行授权。
Kubernetes 集群内一些资源一般以其名称字符串来表示,这些字符串一般会在 API 的 URL 地址中出现;同时某些资源也会包含子资源。
RoleBinding 和 ClusterRoleBinding 可以将 Role 绑定到 Subjects;Subjects 可以是 groups、users 或者 service accounts。
Subjects 中 Users 使用字符串表示,它可以是一个普通的名字字符串。也可以是 email 格式的邮箱地址。甚至是一组字符串形式的数字 ID 。但是 Users 的前缀 system: 是系统 保留的,集群管理员应该确保普通用户不会使用这个前缀格式。Groups 书写格式与 Users 相同,都为一个字符串,并且没有特定的格式要求;同样 system: 前缀为系统保留。
准入控制是API Server的插件集合,通过添加不同的插件,实现额外的准入控制规则。甚至于API Server的一些主要的功能都需要通过 Admission Controllers 实现,比如 ServiceAccount。
官方文档上有一份针对不同版本的准入控制器推荐列表,其中最新的 1.14 的推荐列表是:
NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota。
列举几个插件的功能:
- NamespaceLifecycle: 防止在不存在的 namespace 上创建对象,防止删除系统预置 namespace,删除 namespace 时,连带删除它的所有资源对象。
- LimitRanger:确保请求的资源不会超过资源所在 Namespace 的 LimitRange 的限制。
- ServiceAccount: 实现了自动化添加ServiceAccount。
- ResourceQuota:确保请求的资源不会超过资源的 ResourceQuota 限制。
十四、YAML配置说明
一个YAML文件包括了apiVersion用于说明操作资源的API版本,可以通过kubectl api-versions
查看;kins用于指定内建的资源,包括以下类型:
- 工作负载:Pod,ReplicaSet,DaemonSet,StatefulSet,Deployment,Job,CrontabJob,
- 服务发现,负载均衡:Service,Ingress
- 配置,存储:ConfigMap,Secret,DownwardAPI
- 集群级别资源:Namespace,Node,Role,ClusterRole,RoleBinding,ClusterRoleBinding
- 元数据类型资源:HPA,PodTemplate,LimitRange
还有元数据metadata,主要包括以下节点:
- name,在同一个kind中,必须是唯一的
- namespace,集群级别的概念
- labels,标签,可以通过标签选择器来选择对应的资源
- annontions,注解
最重要的是spec字段,定义了用户的期望状态;status字段定义了初始状态,这个是只读的,用户不能更改。
对于YAML配置文件的说明,可以通过kubectl explain 字段.字段..
查看帮助。
spec字段必须包含containers列表,每个容器的定义包含name和image字段。对于一些容器可能需要指令,那么可以设置command列表。
K8S新一代资源指标架构:
- 核心指标流水线,由bukelet、metrics-server和aip server构成。
- 监控流水线,用于收集各类型信提供给终端用户。
日志收集:
- sidecar方式
- 在节点上部署统一收集
十五、Helm
helm 类似于Linux系统下的包管理器,如yum/apt等,可以方便快捷的将之前打包好的yaml文件快速部署进kubernetes内,方便管理维护。
- helm:一个命令行下客户端工具,主要用于kubernetes应用chart的创建/打包/发布已经创建和管理和远程Chart仓库。
- Tiller:helm的服务端,部署于kubernetes内,Tiller接受helm的请求,并根据chart生成kubernetes部署文件(helm称为release),然后提交给 Kubernetes 创建应用。Tiller 还提供了 Release 的升级、删除、回滚等一系列功能。
- Chart: helm的软件包,采用tar格式,其中包含运行一个应用所需的所有镜像/依赖/资源定义等,还可能包含kubernetes集群中服务定义
- Release:在kubernetes中集群中运行的一个Chart实例,在同一个集群上,一个Chart可以安装多次,每次安装均会生成一个新的release。
- Repository:用于发布和存储Chart的仓库
Chart install工作原理:
-
helm从制定目录或tar文件解析chart结构信息
-
helm将制定的chart结构和value信息通过gRPC协议传递给tiller
-
tiller根据chart和values生成一个release
- tiller通过json将release发送给kubernetes,生成release
Chart update工作原理:
- helm从制定的目录或tar文件解析chart结构信息
- helm将制定的chart结构和value信息通过gRPC协议传给tiller
- tiller生成release并更新制定名称的release的history
- tiller将release信息发送给kubernetes用于更新release
Chart Rollback工作原理:
- helm将会滚的release名称传递给tiller
- tiller根据release名称查找history
- tiller从history中获取到上一个release
- tiller将上一个release发送给kubernetes用于替换当前release
Chart处理依赖工作原理:
Tiller 在处理 Chart 时,直接将 Chart 以及其依赖的所有 Charts 合并为一个 Release,同时传递给 Kubernetes。因此 Tiller 并不负责管理依赖之间的启动顺序。Chart 中的应用需要能够自行处理依赖关系。
Chart文件组织:
myapp/ # Chart 目录
├── charts # 这个 charts 依赖的其他 charts,始终被安装
├── Chart.yaml # 描述这个 Chart 的相关信息、包括名字、描述信息、版本等
├── templates # 模板目录
│ ├── deployment.yaml # deployment 控制器的 Go 模板文件
│ ├── _helpers.tpl # 以 _ 开头的文件不会部署到 k8s 上,可用于定制通用信息
│ ├── ingress.yaml # ingress 的模板文件
│ ├── NOTES.txt # Chart 部署到集群后的一些信息,例如:如何使用、列出缺省值
│ ├── service.yaml # service 的 Go 模板文件
│ └── tests
│ └── test-connection.yaml
└── values.yaml # 模板的值文件,这些值会在安装时应用到 GO 模板生成部署文件
name: 由于 DNS 系统的限制,该字段限制为 63 个字符。因此,release 名称限制为 53 个字符。Kubernetes 1.3 及更早版本仅限于 24 个字符(即 14 个字符名称)。