《kubernetes权威指南第五版》读书笔记
作者:互联网
修改kubeadm的默认配置
kubeadm的初始化控制平面(init)命令和加入节点(join)命令均可以通过指定的配置文件修改默认参数的值。kubeadm将配置文件以ConfigMap形式保存到集群中,便于后续的查询和升级工作。kubeadm config子命令提供了对这组功能的支持。
- kubeadm config print init-defaults:输出kubeadm init命令默认参数的内容。
- kubeadm config print join-defaults:输出kubeadm join命令默认参数的内容。
- kubeadm config migrate:在新旧版本之间进行配置转换。
- kubeadm config images list:列出所需的镜像列表。
- kubeadm config images pull:拉取镜像到本地。
kubeadm config print init-defaults >init-default.yaml
对生成的文件进行编辑,可以按需生成合适的配置。例如,若需要自定义镜像的仓库地址、需要安装的Kubernetes版本号及Pod的IP地址范围
kubeadm config images list:列出所需的镜像列表
如果无法访问k8s.gcr.io,则可以使用国内镜像托管站点进行下载,例如https://1nj0zren.mirror.aliyuncs.com,这可以通过修改Docker服务的配置文件(默认为/etc/docker/daemon.json)进行设置,例如:
然后,使用kubeadm config images pull命令或者docker pull命令下载上述镜像,
kubeadm config images pull --config=init-config.yaml
在镜像下载完成之后,就可以进行安装了。
在开始之前需要注意:kubeadm的安装过程不涉及网络插件(CNI)的初始化,因此kubeadm初步安装完成的集群不具备网络功能,任何Pod(包括自带的CoreDNS)都无法正常工作。而网络插件的安装往往对kubeadm init命令的参数有一定要求
创建和使用命令行插件
为了扩展kubectl的功能,Kubernetes从1.8版本开始引入插件机制,在1.14版本时达到稳定版。
用户自定义插件的可执行文件名需要以“kubectl-”开头,复制到$PATH中的某个目录(如/usr/local/bin)下,然后就可以通过kubectl <plugin-name>运行自定义插件了。
例如,通过Shell脚本实现一个名为hello的插件,其功能为在屏幕上输出字符串“hello world”。创建名为“kubectl-hello”的Shell脚本文件
通过插件机制,可以将某些复杂的kubectl命令简化为运行插件的方式。例如想创建一个命令来查看当前上下文环境(context)中的用户名,则可以通过kubectl config view命令进行查看
使用kubectl plugin list命令可以查看当前系统中已安装的插件列表
Pod深入了解
pod生命周期和重启策略
Pod在整个生命周期中被系统定义为各种状态
Pod的重启策略(RestartPolicy)应用于Pod内的所有容器
Pod的重启策略包括Always、OnFailure和Never,默认值为Always。
- Always:当容器失效时,由kubelet自动重启该容器。
- OnFailure:当容器终止运行且退出码不为0时,由kubelet自动重启该容器。
- Never:不论容器运行状态如何,kubelet都不会重启该容器。
kubelet重启失效容器的时间间隔以sync-frequency乘以2n来计算,例如1、2、4、8倍等,最长延时5min,并且在成功重启后的10min后重置该时间
Pod的重启策略与控制方式息息相关,当前可用于管理Pod的控制器包括ReplicationController、Job、DaemonSet,还可以通过kubelet管理(静态Pod)。每种控制器对Pod的重启策略要求如下
- RC和DaemonSet:必须设置为Always,需要保证该容器持续运行
- Job:OnFailure或Never,确保容器执行完成后不再重启
- kubelet:在Pod失效时自动重启它,不论将RestartPolicy设置为什么值,也不会对Pod进行健康检查
Pod常见的状态转换场景
RC的继任者其实并不是Deployment,而是ReplicaSet,因为ReplicaSet进一步增强了RC标签选择器的灵活性。之前RC的标签选择器只能选择一个标签,而ReplicaSet拥有集合式的标签选择器,可以选择多个Pod标签
其实,Kubernetes的滚动升级就是巧妙运用ReplicaSet的这个特性来实现的,同时,Deployment也是通过ReplicaSet来实现Pod副本自动控制功能的。我们不应该直接使用底层的ReplicaSet来控制Pod副本,而应该通过管理ReplicaSet的Deployment对象来控制副本
Pod健康检查和服务可用性检查
探针的种类
Kubernetes对Pod的健康状态可以通过三类探针来检查:LivenessProbe、ReadinessProbe及StartupProbe,其中最主要的探针为LivenessProbe与ReadinessProbe,kubelet会定期执行这两类探针来诊断容器的健康状况。
(1)LivenessProbe探针:用于判断容器是否存活(Running状态),如果LivenessProbe探针探测到容器不健康,则kubelet将“杀掉”该容器,并根据容器的重启策略做相应的处理。如果一个容器不包含LivenessProbe探针,那么kubelet认为该容器的LivenessProbe探针返回的值永远是Success。
(2)ReadinessProbe探针:用于判断容器服务是否可用(Ready状态),达到Ready状态的Pod才可以接收请求。对于被Service管理的Pod,Service与Pod Endpoint的关联关系也将基于Pod是否Ready进行设置。如果在运行过程中Ready状态变为False,则系统自动将其从Service的后端Endpoint列表中隔离出去,后续再把恢复到Ready状态的Pod加回后端Endpoint列表。这样就能保证客户端在访问Service时不会被转发到服务不可用的Pod实例上。需要注意的是,ReadinessProbe也是定期触发执行的,存在于Pod的整个生命周期中。
(3)StartupProbe探针:某些应用会遇到启动比较慢的情况,例如应用程序启动时需要与远程服务器建立网络连接,或者遇到网络访问较慢等情况时,会造成容器启动缓慢,此时ReadinessProbe就不适用了,因为这属于“有且仅有一次”的超长延时,可以通过StartupProbe探针解决该问题
探针均可配置以下三种实现方式
(1)ExecAction:在容器内部运行一个命令,如果该命令的返回码为0,则表明容器健康
通过运行cat/tmp/health命令来判断一个容器运行是否正常。在该Pod运行后,将在创建/tmp/health文件10s后删除该文件,而LivenessProbe健康检查的初始探测时间(initialDelaySeconds)为15s,探测结果是Fail,将导致kubelet“杀掉”该容器并重启它
(2)TCPSocketAction:通过容器的IP地址和端口号执行TCP检查,如果能够建立TCP连接,则表明容器健康。
(3)HTTPGetAction:通过容器的IP地址、端口号及路径调用HTTP Get方法,如果响应的状态码大于等于200且小于400,则认为容器健康
对于每种探测方式,都需要设置initialDelaySeconds和timeoutSeconds两个参数,它们的含义分别如下。
- initialDelaySeconds:启动容器后进行首次健康检查的等待时间,单位为s。
- timeoutSeconds:健康检查发送请求后等待响应的超时时间,单位为s。当超时发生时,kubelet会认为容器已经无法提供服务,将会重启该容器。
如下代码片段是StartupProbe探针的一个参考配置,可以看到,这个Pod可以有长达30×10=300s的超长启动时间:
pod调度策略
Deployment或RC:全自动调度
Deployment或RC的主要功能之一就是自动部署一个容器应用的多份副本,以及持续监控副本的数量,在集群内始终维持用户指定的副本数量
Pod由系统全自动完成调度。它们各自最终运行在哪个节点上,完全由Master的Scheduler经过一系列算法计算得出,用户无法干预调度过程和结果
除了使用系统自动调度算法完成一组Pod的部署,Kubernetes也提供了多种丰富的调度策略,用户只需在Pod的定义中使用NodeSelector、NodeAffinity、PodAffinity、Pod驱逐等更加细粒度的调度策略设置,就能完成对Pod的精准调度
NodeSelector:定向调度
除了用户可以自行给Node添加标签,Kubernetes也会给Node预定义一些标签,包括:
- kubernetes.io/hostname;
- beta.kubernetes.io/os(从1.14版本开始更新为稳定版,到1.18版本删除);
- beta.kubernetes.io/arch(从1.14版本开始更新为稳定版,到1.18版本删除);
- kubernetes.io/os(从1.14版本开始启用);
- kubernetes.io/arch(从1.14版本开始启用)。
NodeSelector通过标签的方式,简单实现了限制Pod所在节点的方法。亲和性调度机制则极大扩展了Pod的调度能力,主要的增强功能如下。
- 更具表达力(不仅仅是“符合全部”的简单情况)。
- 可以使用软限制、优先采用等限制方式,代替之前的硬限制,这样调度器在无法满足优先需求的情况下,会退而求其次,继续运行该Pod。
- 可以依据节点上正在运行的其他Pod的标签来进行限制,而非节点本身的标签。这样就可以定义一种规则来描述Pod之间的亲和或互斥关系。
亲和性调度功能包括节点亲和性(NodeAffinity)和Pod亲和性(PodAffinity)两个维度的设置。节点亲和性与NodeSelector类似,增强了上述前两点优势;Pod的亲和与互斥限制则通过Pod标签而不是节点标签来实现,也就是上面第4点内容所陈述的方式,同时具有前两点提到的优点
NodeAffinity:Node亲和性调度
NodeAffinity意为Node亲和性的调度策略,是用于替换NodeSelector的全新调度策略。目前有两种节点亲和性表达。
- RequiredDuringSchedulingIgnoredDuringExecution:必须满足指定的规则才可以调度Pod到Node上(功能与nodeSelector很像,但是使用的是不同的语法),相当于硬限制。
- PreferredDuringSchedulingIgnoredDuringExecution:强调优先满足指定规则,调度器会尝试调度Pod到Node上,但并不强求,相当于软限制。多个优先级规则还可以设置权重(weight)值,以定义执行的先后顺序。
IgnoredDuringExecution的意思是:如果一个Pod所在的节点在Pod运行期间标签发生了变更,不再符合该Pod的节点亲和性需求,则系统将忽略Node上Label的变化,该Pod能继续在该节点上运行。
下面的例子设置了NodeAffinity调度的如下规则。
- required DuringS chedulingIgnoredDuringExecution:要求只运行在amd64的节点上(beta.kubernetes.io/arch In amd64)。
- preferredDuringSchedulingIgnoredDuringExecution:要求尽量运行在磁盘类型为ssd(disk-type In ssd)的节点上。
NodeAffinity规则设置的注意事项如下。
- 如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都得到满足,Pod才能最终运行在指定的Node上。
- 如果nodeAffinity指定了多个nodeSelectorTerms,那么其中一个能匹配成功即可。
- 如果在nodeSelectorTerms中有多个matchExpressions,则一个节点必须满足所有matchExpressions才能运行该Pod。
PodAffinity:Pod亲和与互斥调度策略
与节点亲和性类似,Pod亲和性的操作符也包括In、NotIn、Exists、DoesNotExist、Gt、Lt。
原则上,topologyKey可以使用任意合法的标签Key赋值,但是出于性能和安全方面的考虑,对topologyKey有如下限制。
- 在Pod亲和性和RequiredDuringScheduling的Pod互斥性的定义中,不允许使用空的topologyKey。
- 如果Admission controller包含了LimitPodHardAntiAffinityTopology,那么针对Required DuringScheduling的Pod互斥性定义就被限制为kubernetes.io/hostname,要使用自定义的topologyKey,就要改写或禁用该控制器。
- 在PreferredDuringScheduling类型的Pod互斥性定义中,空的topologyKey会被解释为kubernetes.io/hostname、failure-domain.beta.kubernetes.io/zone及failure-domain.beta.kubernetes.io/region的组合。
如果不是上述情况,就可以采用任意合法的topologyKey了。
PodAffinity规则设置的注意事项如下
- 除了设置Label Selector和topologyKey,用户还可以指定Namespace列表进行限制,同样,使用Label Selector对Namespace进行选择。Namespace的定义和Label Selector及topologyKey同级。省略Namespace的设置,表示使用定义了affinity/anti-affinity的Pod所在的命名空间。如果Namespace被设置为空值(""),则表示所有命名空间。
- 在所有关联requiredDuringSchedulingIgnoredDuringExecution的matchExpressions全都满足之后,系统才能将Pod调度到某个Node上
Pod Priority Preemption:Pod优先级调度
在Kubernetes 1.8版本之前,当集群的可用资源不足时,在用户提交新的Pod创建请求后,该Pod会一直处于Pending状态,即使这个Pod是一个很重要(很有身份)的Pod,也只能被动等待其他Pod被删除并释放资源,才能有机会被调度成功。
Kubernetes 1.8版本引入了基于Pod优先级抢占(Pod Priority Preemption)的调度策略,此时Kubernetes会尝试释放目标节点上低优先级的Pod,以腾出空间(资源)安置高优先级的Pod,这种调度方式被称为“抢占式调度”。在Kubernetes 1.11版本中,该特性升级为Beta版本,默认开启,在后续的Kubernetes 1.14版本中正式Release。如何声明一个负载相对其他负载更重要?我们可以通过以下几个维度来定义:Priority:优先级;QoS:服务质量等级;系统定义的其他度量指标。
优先级抢占调度策略的核心行为分别是驱逐(Eviction)与抢占(Preemption),这两种行为的使用场景不同,效果相同。Eviction是kubelet进程的行为,即当一个Node资源不足(under resource pressure)时,该节点上的kubelet进程会执行驱逐动作,此时kubelet会综合考虑Pod的优先级、资源申请量与实际使用量等信息来计算哪些Pod需要被驱逐;当同样优先级的Pod需要被驱逐时,实际使用的资源量超过申请量最大倍数的高耗能Pod会被首先驱逐。对于QoS等级为“Best Effort”的Pod来说,由于没有定义资源申请(CPU/Memory Request),所以它们实际使用的资源可能非常大。Preemption则是Scheduler执行的行为,当一个新的Pod因为资源无法满足而不能被调度时,Scheduler可能(有权决定)选择驱逐部分低优先级的Pod实例来满足此Pod的调度目标,这就是Preemption机制
优先级为100000,数字越大,优先级越高,超过一亿的数字被系统保留,用于指派给系统组件
DaemonSet:在每个Node上都调度一个Pod
这种用法适合有这种需求的应用。
- 在每个Node上都运行一个GlusterFS存储或者Ceph存储的Daemon进程。
- 在每个Node上都运行一个日志采集程序,例如Fluentd或者Logstach。
- 在每个Node上都运行一个性能监控程序,采集该Node的运行性能数据,例如Prometheus Node Exporter、collectd、New Relic agent或者Ganglia gmond等。
DaemonSet调度不同于普通的Pod调度,所以没有用默认的Kubernetes Scheduler进行调度,而是通过专有的**DaemonSet Controller**进行调度。但是随着Kubernetes版本的改进和调度特性不断丰富,产生了一些难以解决的矛盾,最主要的两个矛盾如下。
- 普通的Pod是在Pending状态触发调度并被实例化的,DaemonSet Controller并不是在这个状态调度Pod的,这种不一致容易误导和迷惑用户。
- Pod优先级调度是被Kubernetes Scheduler执行的,而DaemonSet Controller并没有考虑到Pod优先级调度的问题,也产生了不一致的结果。
- 从Kubernetes 1.18开始,DaemonSet的调度默认切换到Kubernetes Scheduler进行,**从而一劳永逸地解决了以上问题及未来可能的新问题。因为默认切换到了Kubernetes Scheduler统一调度Pod,因此DaemonSet也能正确处理Taints和Tolerations的问题。
Taints和Tolerations(污点和容忍)
Pod的Toleration声明中的key和effect需要与Taint的设置保持一致,并且满足以下条件之一。
- operator的值是Exists(无须指定value)。
- operator的值是Equal并且value相等。
- 如果不指定operator,则默认值为Equal。
另外,有如下两个特例
- 空的key配合Exists操作符能够匹配所有键和值。
- 空的effect匹配所有effect
几种特殊情况
- 如果在剩余的Taint中存在effect=NoSchedule,则调度器不会把该Pod调度到这一节点上。
- 如果在剩余的Taint中没有NoSchedule效果,但是有PreferNoSchedule效果,则调度器会尝试不把这个Pod指派给这个节点。
Job:批处理调度
考虑到批处理的并行问题,Kubernetes将Job分以下三种类型
(1)Non-parallel Jobs:通常一个Job只启动一个Pod,除非Pod异常,才会重启该Pod,一旦此Pod正常结束,Job将结束。
(2)Parallel Jobs with a fixed completion count:并行Job会启动多个Pod,此时需要设定Job的.spec.completions参数为一个正数,当正常结束的Pod数量达至此参数设定的值后,Job结束。此外,Job的.spec.parallelism参数用来控制并行度,即同时启动几个Job来处理Work item。
(3)Parallel Jobs with a work queue:任务队列方式的并行Job需要一个独立的Queue,Work item都在一个Queue中存放,不能设置Job的.spec.completions参数,此时Job有以下特性。
- 每个Pod都能独立判断和决定是否还有任务项需要处理。
- 如果某个Pod正常结束,则Job不会再启动新的Pod。
- 如果一个Pod成功结束,则此时应该不存在其他Pod还在工作的情况,它们应该都处于即将结束、退出的状态。
- 如果所有Pod都结束了,且至少有一个Pod成功结束,则整个Job成功结束
Cronjob:定时任务
首先,确保Kubernetes的版本为1.8及以上
其次,需要掌握Cron Job的定时表达式,它基本上照搬了Linux Cron的表达式
pod的容灾调度
为了满足这种容灾场景下的特殊调度需求,在Kubernetes 1.16版本中首次引入Even Pod Spreading特性,用于通过topologyKey属性识别Zone,并通过设置新的参数topologySpreadConstraints来将Pod均匀调度到不同的Zone
关键的参数是maxSkew。maxSkew用于指定Pod在各个Zone上调度时能容忍的最大不均衡数:值越大,表示能接受的不均衡调度越大;值越小,表示各个Zone的Pod数量分布越均匀。
为了理解maxSkew,我们需要先理解skew参数的计算公式:skew[topo]=count[topo]-min(count[topo]),即每个拓扑区域的skew值都为该区域包括的目标Pod数量与整个拓扑区域最少Pod数量的差,而maxSkew就是最大的skew值
假如在上面的例子中有3个拓扑区域,分别为Zone A、Zone B及Zone C,有3个目标Pod需要调度到这些拓扑区域,那么前两个毫无疑问会被调度到Zone A和Zone B 可以手动计算每个Zone的skew,首先计算出min(count[topo])是0,对应Zone C,于是Zone A的skew=1-0=1,Zone B的skew=1-0=0,Zone C的skew=0-0=0,于是第3个Pod应该被放在Zone C,此时min(count[topo])的值就变成了1,而实际的maxSkew的值为0,符合预期设置。如果我们把maxSkew设置为2,则在这种情况下,第3个Pod被放在Zone A或Zone B都是符合要求的
StatefulSet
在创建StatefulSet之前,需要确保在Kubernetes集群中管理员已经创建好共享存储,并能够与StorageClass对接,以实现动态存储供应的模式
为了完成MongoDB集群的搭建,需要部署以下三个资源对象。
- 一个StorageClass:用于StatefulSet自动为各个应用Pod申请PVC。
- 一个Headless Service(clusterIP: None):用于设置MongoDB实例的域名。
- 一个StatefulSet
mongo-sidecar作为MongoDB集群的管理者,将使用此Headless Service来维护各个MongoDB实例之间的集群关系,以及集群规模变化时的自动更新
StatefulSet的常见应用场景
- 集群进行扩容,仅需要通过对StatefulSet进行scale操作(或修改yaml文件)
- 自动故障修复
- 实例或其所在主机发生故障,则StatefulSet将会自动重建该实例,并保证其身份(ID)和使用的数据(PVC)不变
Service
Service概念和原理
Service是Kubernetes实现微服务架构的核心概念,通过创建Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上
Service用于为一组提供服务的Pod抽象一个稳定的网络访问地址,是Kubernetes实现微服务的核心概念。通过Service的定义设置的访问地址是DNS域名格式的服务名称,对于客户端应用来说,网络访问方式并没有改变(DNS域名的作用等价于主机名、互联网域名或IP地址)。Service还提供了负载均衡器功能,将客户端请求负载分发到后端提供具体服务的各个Pod上
Service主要用于提供网络服务,通过Service的定义,能够为客户端应用提供稳定的访问地址(域名或IP地址)和负载均衡功能,以及屏蔽后端Endpoint的变化,是Kubernetes实现微服务的核心资源
Service不仅具有标准网络协议的IP地址,还以DNS域名的形式存在。Service的域名表示方法为<servicename>.<namespace>.svc.<clusterdomain>,servicename为服务的名称,namespace为其所在namespace的名称,clusterdomain为Kubernetes集群设置的域名后缀。服务名称的命名规则遵循RFC 1123规范
Service负载均衡机制
kube-proxy的代理模式、会话保持机制和基于拓扑感知的服务路由机制(EndpointSlices)
kube-proxy的代理模式
kube-proxy提供了以下代理模式(通过启动参数--proxy-mode设置)
- userspace模式:用户空间模式,由kube-proxy完成代理的实现,效率最低,不再推荐使用。
- iptables模式:kube-proxy通过设置Linux Kernel的iptables规则,实现从Service到后端Endpoint列表的负载分发规则,效率很高。但是,如果某个后端Endpoint在转发时不可用,此次客户端请求就会得到失败的响应,相对于userspace模式来说更不可靠。此时应该通过为Pod设置readinessprobe(服务可用性健康检查)来保证只有达到ready状态的Endpoint才会被设置为Service的后端Endpoint。
- ipvs模式:在Kubernetes 1.11版本中达到Stable阶段,kube-proxy通过设置Linux Kernel的netlink接口设置IPVS规则,转发效率和支持的吞吐率都是最高的。ipvs模式要求Linux Kernel启用IPVS模块,如果操作系统未启用IPVS内核模块,kube-proxy则会自动切换至iptables模式。同时,ipvs模式支持更多的负载均衡策略,如下所述。
- rr:round-robin,轮询。
- lc:least connection,最小连接数。
- dh:destination hashing,目的地址哈希。
- sh:source hashing,源地址哈希。
- sed:shortest expected delay,最短期望延时。
- nq:never queue,永不排队。
- kernelspace模式:Windows Server上的代理模式
会话保持机制
Service支持通过设置sessionAffinity实现基于客户端IP的会话保持机制,即首次将某个客户端来源IP发起的请求转发到后端的某个Pod上,之后从相同的客户端IP发起的请求都将被转发到相同的后端Pod上,配置参数为service.spec.sessionAffinity
同时,用户可以设置会话保持的最长时间,在此时间之后重置客户端来源IP的保持规则,配置参数为service.spec.sessionAffinityConfig.clientIP.timeoutSeconds
基于拓扑感知的服务路由机制制(EndpointSlices)
将外部服务定义为Service
普通的Service通过Label Selector对后端Endpoint列表进行了一次抽象,如果后端的Endpoint不是由Pod副本集提供的,则Service还可以抽象定义任意其他服务,将一个Kubernetes集群外部的已知服务定义为Kubernetes内的一个Service,供集群内的其他应用访问,常见的应用场景包括:
- 已部署的一个集群外服务,例如数据库服务、缓存服务等;
- 其他Kubernetes集群的某个服务;
- 迁移过程中对某个服务进行Kubernetes内的服务名访问机制的验证。
对于这种应用场景,用户在创建Service资源对象时不设置Label Selector(后端Pod也不存在),同时再定义一个与Service关联的Endpoint资源对象,在Endpoint中设置外部服务的IP地址和端口号
将Service暴露到集群外部
目前Service的类型如下。
- ClusterIP:Kubernetes默认会自动设置Service的虚拟IP地址,仅可被集群内部的客户端应用访问。当然,用户也可手工指定一个ClusterIP地址,不过需要确保该IP在Kubernetes集群设置的ClusterIP地址范围内(通过kube-apiserver服务的启动参数--service-cluster-ip-range设置),并且没有被其他Service使用。
- NodePort:将Service的端口号映射到每个Node的一个端口号上,这样集群中的任意Node都可以作为Service的访问入口地址,即NodeIP:NodePort。
- LoadBalancer:将Service映射到一个已存在的负载均衡器的IP地址上,通常在公有云环境中使用。
- ExternalName:将Service映射为一个外部域名地址,通过externalName字段进行设置
Service支持的网络协议
- TCP:Service的默认网络协议,可用于所有类型的Service。
- UDP:可用于大多数类型的Service,LoadBalancer类型取决于云服务商对UDP的支持。
- HTTP:取决于云服务商是否支持HTTP和实现机制。
- PROXY:取决于云服务商是否支持HTTP和实现机制。
- SCTP:从Kubernetes 1.12版本引入,到1.19版本时达到Beta阶段,默认启用,如需关闭该特性,则需要设置kube-apiserver的启动参数--feature-gates=SCTPSupport=false进行关闭
Kubernetes从1.17版本开始,可以为Service和Endpoint资源对象设置一个新的字段“AppProtocol”,用于标识后端服务在某个端口号上提供的应用层协议类型,例如HTTP、HTTPS、SSL、DNS等,该特性在Kubernetes 1.19版本时达到Beta阶段,计划于Kubernetes 1.20 版本时达到GA阶段。要使用AppProtocol,需要设置kube-apiserver的启动参数--feature-gates=ServiceAppProtocol=true进行开启,然后在Service或Endpoint的定义中设置AppProtocol字段指定应用层协议的类型
Kubernetes的服务发现机制
环境变量方式
在一个Pod运行起来的时候,系统会自动为其容器运行环境注入所有集群中有效Service的信息。Service的相关信息包括服务IP、服务端口号、各端口号相关的协议等,通过{SVCNAME}_SERVICE_HOST和{SVCNAME}_SERVICE_PORT格式进行设置。其中,SVCNAME的命名规则为:将Service的name字符串转换为全大写字母,将中横线“-”替换为下画线“_”
DNS方式
Service在Kubernetes系统中遵循DNS命名规范,Service的DNS域名表示方法为<servicename>.<namespace>.svc.<clusterdomain>,其中servicename为服务的名称,namespace为其所在namespace的名称,clusterdomain为Kubernetes集群设置的域名后缀(例如cluster.local),服务名称的命名规则遵循RFC 1123规范的要求
对于客户端应用来说,DNS域名格式的Service名称提供的是稳定、不变的访问地址,可以大大简化客户端应用的配置,是Kubernetes集群中推荐的使用方式。
目前由CoreDNS作为Kubernetes集群的默认DNS服务器提供域名解析服务
Service定义中的端口号如果设置了名称(name),则该端口号也会拥有一个DNS域名,在DNS服务器中以SRV记录的格式保存:_<portname>._<protocol>.<servicename>.<namespace>.svc.<clusterdomain>,其值为端口号的数值
Headless Service
Headless Service的概念是这种服务没有入口访问地址(无ClusterIP地址),kube-proxy不会为其创建负载转发规则,而服务名(DNS域名)的解析机制取决于该Headless Service是否设置了Label Selector
Headless Service没有设置Label Selector
如果Headless Service没有设置Label Selector,则Kubernetes将不会自动创建对应的Endpoint列表。DNS系统会根据下列条件尝试对该服务名设置DNS记录:
- 如果Service的类型为ExternalName,则对服务名的访问将直接被DNS系统转换为Service设置的外部名称(externalName);
- 如果系统中存在与Service同名的Endpoint定义,则服务名将被解析为Endpoint定义中的列表,适用于非ExternalName类型的Service。
端点分片与服务拓扑
Service的后端是一组Endpoint列表,为客户端应用提供了极大的便利。但是随着集群规模的扩大及Service数量的增加,特别是Service后端Endpoint数量的增加,kube-proxy需要维护的负载分发规则(例如iptables规则或ipvs规则)的数量也会急剧增加,导致后续对Service后端Endpoint的添加、删除等更新操作的成本急剧上升。
举例来说,假设在Kubernetes集群中有10000个Endpoint运行在大约5000个Node上,则对单个Pod的更新将需要总计约5GB的数据传输,这不仅对集群内的网络带宽浪费巨大,而且对Master的冲击非常大,会影响Kubernetes集群的整体性能,在Deployment不断进行滚动升级操作的情况下尤为突出。
Kubernetes从1.16版本开始引入端点分片(Endpoint Slices)机制,包括一个新的EndpointSlice资源对象和一个新的EndpointSlice控制器。从Kubernetes 1.17版本开始,EndpointSlice机制默认是启用的(在1.16版本中需要通过设置kube-apiserver和kube-proxy服务的启动参数--feature-gates="EndpointSlice=true"进行启用)
另外,kube-proxy默认仍然使用Endpoint对象,为了提高性能,可以设置kube-proxy启动参数--feature-gates="EndpointSliceProxying=true"让kube-proxy使用EndpointSlice,这样可以减少kube-proxy与master之间的网络通信并提高性能。Kubernetes从1.19版本开始默认开启该特性
EndpointSlice通过对Endpoint进行分片管理来实现降低Master和各Node之间的网络传输数据量及提高整体性能的目标。对于Deployment的滚动升级,可以实现仅更新部分Node上的Endpoint信息,Master与Node之间的数据传输量可以减少100倍左右,能够大大提高管理效率
默认情况下,在由EndpointSlice控制器创建的EndpointSlice中最多包含100个Endpoint,如需修改,则可以通过kube-controller-manager服务的启动参数--max-endpoints-per-slice设置,但上限不能超过1000
EndpointSlice的关键信息如下。
(1)关联的服务名称:将EndpointSlice与Service的关联信息设置为一个标签kubernetes.io/service-name=webapp,该标签标明了服务名称。
(2)地址类型AddressType:包括以下3种取值类型
- IPv4:IPv4格式的IP地址
- IPv6:IPv6格式的IP地址。
- FQDN:全限定域名。
(3)在Endpoints列表中列出的每个Endpoint的信息。
- Addresses:Endpoint的IP地址。
- Conditions:Endpoint状态信息,作为EndpointSlice的查询条件。
- Hostname:在Endpoint中设置的主机名hostname。
- TargetRef:Endpoint对应的Pod名称。
- Topology:拓扑信息,为基于拓扑感知的服务路由提供数据。
目前EndpointSlice控制器自动设置的拓扑信息如下。
- kubernetes.io/hostname:Endpoint所在Node的名称。
- topology.kubernetes.io/zone:Endpoint所在的Zone信息,使用Node标签topology.kubernetes.io/zone的值,例如上例中的Node拥有“topology.kubernetes.io/zone:north”标签。
- topology.kubernetes.io/region:Endpoint所在的Region信息,使用Node标签topology.kubernetes.io/region的值。
(4)EndpointSlice的管理控制器:通过endpointslice.kubernetes.io/managed-by标签进行设置,用于存在多个管理控制器的应用场景中
DNS服务搭建和配置指南
kubernetes中的dns发展史
在Kubernetes 1.2版本时,DNS服务是由SkyDNS提供的,它由4个容器组成:kube2sky、skydns、etcd和healthz
从Kubernetes 1.4版本开始,SkyDNS组件便被KubeDNS替换,主要考虑的是SkyDNS组件之间通信较多,整体性能不高。KubeDNS由3个容器组成:kubedns、dnsmasq和sidecar,去掉了SkyDNS中的etcd存储,将DNS记录直接保存在内存中,以提高查询性能
从Kubernetes 1.11版本开始,Kubernetes集群的DNS服务便由CoreDNS提供。CoreDNS是CNCF基金会孵化的一个项目,是用Go语言实现的高性能、插件式、易扩展的DNS服务端,目前已毕业。
CoreDNS解决了KubeDNS的一些问题,例如dnsmasq的安全漏洞、externalName不能使用stubDomains进行设置,等等。CoreDNS支持自定义DNS记录及配置upstream DNS Server,可以统一管理Kubernetes基于服务的内部DNS和数据中心的物理DNS。它没有使用多个容器的架构,只用一个容器便实现了KubeDNS内3个容器的全部功能
coreDNS的架构
修改每个Node上kubelet的启动参数,在其中加上以下两个参数。
◎ --cluster-dns=169.169.0.100:为DNS服务的ClusterIP地址。
◎ --cluster-domain=cluster.local:为在DNS服务中设置的域名。
然后重启kubelet服务。
部署CoreDNS服务
部署CoreDNS服务时需要创建3个资源对象:1个ConfigMap、1个Deployment和1个Service。在启用了RBAC的集群中,还可以设置ServiceAccount、ClusterRole、ClusterRoleBinding对CoreDNS容器进行权限设置
Deployment“coredns”主要设置CoreDNS容器应用的内容,其中,replicas副本的数量通常应该根据集群的规模和服务数量确定,如果单个CoreDNS进程不足以支撑整个集群的DNS查询,则可以通过水平扩展提高查询能力。由于DNS服务是Kubernetes集群的关键核心服务,所以建议为其Deployment设置自动扩缩容控制器,自动管理其副本数量。
另外,对资源限制部分(CPU限制和内存限制)的设置也应根据实际环境进行调整
Service“kube-dns”是DNS服务的配置,这个服务需要设置固定的ClusterIP地址,也需要将所有Node上的kubelet启动参数--cluster-dns都设置为这个ClusterIP地址
CoreDNS的配置说明
CoreDNS的主要功能是通过插件系统实现的。CoreDNS实现了一种链式插件结构,将DNS的逻辑抽象成了一个个插件,能够灵活组合使用
常用的插件如下。
- loadbalance:提供基于DNS的负载均衡功能。
- loop:检测在DNS解析过程中出现的简单循环问题。
- cache:提供前端缓存功能。
- health:对Endpoint进行健康检查。
- kubernetes:从Kubernetes中读取zone数据。
- etcd:从etcd中读取zone数据,可用于自定义域名记录。
- file:从RFC1035格式文件中读取zone数据。
- hosts:使用/etc/hosts文件或者其他文件读取zone数据,可用于自定义域名记录。
- auto:从磁盘中自动加载区域文件。
- reload:定时自动重新加载Corefile配置文件的内容。
- forward:转发域名查询到上游DNS服务器上。
- prometheus:为Prometheus系统提供采集性能指标数据的URL。
- pprof:在URL路径/debug/pprof下提供运行时的性能数据。
- log:对DNS查询进行日志记录。
- errors:对错误信息进行日志记录。
域名“cluster.local”设置了一系列插件,包括errors、health、ready、kubernetes、prometheus、forward、cache、loop、reload和loadbalance,在进行域名解析时,这些插件将以从上到下的顺序依次执行
标签:Endpoint,Service,Kubernetes,读书笔记,调度,设置,kubernetes,Pod,第五版 来源: https://www.cnblogs.com/happy-king/p/15228894.html