导图社区 K8S(1)
K8S的思维导图,包括WHAT、HOW、Docker、istio、prometheus、etcd、service mesh、云原生等内容。
编辑于2022-11-11 22:20:23时间管理-读书笔记,通过学习和应用这些方法,读者可以更加高效地利用时间,重新掌控时间和工作量,实现更高效的工作和生活。
本书是法兰教授的最新作品之一,主要阐明了设计史的来源、设计史现在的状况以及设计史的未来发展可能等三个基本问题。通过对设计史学科理论与方法的讨论,本书旨在促进读者对什么是设计史以及如何写作一部好的设计史等问题的深入认识与反思。
《计算机组成原理》涵盖了计算机系统的基本组成、数据的表示与运算、存储系统、指令系统、中央处理器(CPU)、输入输出(I/O)系统以及外部设备等关键内容。通过这门课程的学习,学生可以深入了解计算机硬件系统的各个组成部分及其相互之间的连接方式,掌握计算机的基本工作原理。
社区模板帮助中心,点此进入>>
时间管理-读书笔记,通过学习和应用这些方法,读者可以更加高效地利用时间,重新掌控时间和工作量,实现更高效的工作和生活。
本书是法兰教授的最新作品之一,主要阐明了设计史的来源、设计史现在的状况以及设计史的未来发展可能等三个基本问题。通过对设计史学科理论与方法的讨论,本书旨在促进读者对什么是设计史以及如何写作一部好的设计史等问题的深入认识与反思。
《计算机组成原理》涵盖了计算机系统的基本组成、数据的表示与运算、存储系统、指令系统、中央处理器(CPU)、输入输出(I/O)系统以及外部设备等关键内容。通过这门课程的学习,学生可以深入了解计算机硬件系统的各个组成部分及其相互之间的连接方式,掌握计算机的基本工作原理。
K8S
k8s
WHAT
架构图
WHY
(1)自动装箱
建构于容器之上,基于资源依赖及其他约束自动完成容器部署且不影响其可用性,并通过调度机制混合关键型应用和非关键型应用的工作负载于同一节点以提升资源利用率。
(2)自我修复(自愈)
支持容器故障后自动重启、节点故障后重新调度容器,以及其他可用节点、健康状态检查失败后关闭容器并重新创建等自我修复机制。
(3)水平扩展
支持通过简单命令或UI手动水平扩展,以及基于CPU等资源负载率的自动水平扩展机制。
(4)服务发现和负载均衡
Kubernetes通过其附加组件之一的KubeDNS(或CoreDNS)为系统内置了服务发现功能,它会为每个Service配置DNS名称,并允许集群内的客户端直接使用此名称发出访问请求,而Service则通过iptables或ipvs内建了负载均衡机制。
(5)自动发布和回滚
Kubernetes支持“灰度”更新应用程序或其配置信息,它会监控更新过程中应用程序的健康状态,以确保它不会在同一时刻杀掉所有实例,而此过程中一旦有故障发生,就会立即自动执行回滚操作。
(6)密钥和配置管理
Kubernetes的ConfigMap实现了配置数据与Docker镜像解耦,需要时,仅对配置做出变更而无须重新构建Docker镜像,这为应用开发部署带来了很大的灵活性。
此外,对于应用所依赖的一些敏感数据,如用户名和密码、令牌、密钥等信息,Kubernetes专门提供了Secret对象为其解耦,既便利了应用的快速开发和交付,又提供了一定程度上的安全保障。
(7)存储编排
Kubernetes支持Pod对象按需自动挂载不同类型的存储系统,这包括节点本地存储、公有云服务商的云存储
(8)批量处理执行
除了服务型应用,Kubernetes还支持批处理作业及CI(持续集成),如果需要,一样可以实现容器故障后恢复。
整体架构🍑
整体架构
图一
图二
图三
概述
图
核心知识
图
Node
runtime
runtime指的是容器运行环境,目前Kubernetes支持docker和rkt两种容器。
kube-proxy
实现服务发现和反向代理功能。
反向代理方面:kube-proxy支持TCP和UDP连接转发,默认基于Round Robin算法将客户端流量转发到与service对应的一组后端pod。
服务发现方面,kube-proxy使用etcd的watch机制,监控集群中service和endpoint对象数据的动态变化,并且维护一个service到endpoint的映射关系,从而保证了后端pod的IP变化不会对访问者造成影响。另外kube-proxy还支持session affinity。
运行在每个node上的代理组件,提供了tcp和udp的连接转发支持。
图
tu
工作原理使用iptables
监控集群中用户发布的服务,并完成负载均衡配置
每个节点的Kube-Proxy都会配置相同的负载均衡策略,使得整个集群的服务发现建立在分布式负载均衡器之上,服务调用无需经过额外的网络跳转(Network Hop)
kubelet
what
是Master在每个Node节点上面的agent
维护和管理Node上面的所有容器,
汇报当前节点的资源信息和健康状态;
Pod 的健康检查和状态汇报。
但是如果容器不是通过Kubernetes创建的,它并不会管理。
使Pod得运行状态与期望的状态一致。
每个kubelet会定时向master的apiserver汇报节点资源信息。
从不同源获取 Pod 清单,并按需求启停 Pod 的核心组件:
Pod 清单可从本地文件目录,给定的 HTTPServer 或 KubeAPIServer 等源头获取;
Kubelet 将运行时、网络和存储抽象成了 CRI,CNI,CSI。
how
图
在每个node中都会起一个kubelet的服务进程。该进程用于master下发到本节点的任务,管理pod和pod中的容器。
kubelet 的工作核心,就是一个控制循环,即:SyncLoop(图中的大圆圈)。
而驱动这个控制循环运行的事件,包括四种:Pod 更新事件;Pod 生命周期变化;kubelet 本身设置的执行周期;定时的清理事件。
kubelet 启动的时候,要做的第一件事情,就是设置 Listers,也就是注册它所关心的各种事件的 Informer。这些 Informer,就是 SyncLoop 需要处理的数据的来源。
kubelet 还负责维护着很多很多其他的子控制循环(也就是图中的小圆圈)。
这些控制循环的名字,一般被称作某某 Manager,比如 Volume Manager、Image Manager、Node Status Manager 等等。不难想到,这些控制循环的责任,就是通过控制器模式,完成 kubelet 的某项具体职责。
比如 Node Status Manager,就负责响应 Node 的状态变化,然后将 Node 的状态收集起来,并通过 Heartbeat 的方式上报给 APIServer。
再比如 CPU Manager,就负责维护该 Node 的 CPU 核的信息,以便在 Pod 通过 cpuset 的方式请求 CPU 核的时候,能够正确地管理 CPU 核的使用量和可用量。
kubelet 调用下层容器运行时的执行过程,并不会直接调用 Docker 的 API,而是通过一组叫作 CRI(Container Runtime Interface,容器运行时接口)的 gRPC 接口来间接执行的。
Pod
what
Pod是一个服务的多个进程的聚合单位,它由一个或者多个容器组成(例如Docker容器),它们共享容器存储、网络和容器运行配置项。
Pod是kubernetes中你可以创建和部署的最小也是最简单位。一个Pod代表着集群中运行的一个进程。
Pod中封装着应用的容器(有的情况下是好几个容器),存储、独立的网络IP,管理容器如何运行的策略选项。
Pod代表着部署的一个单位:kubernetes中应用的一个实例,可能由一个或者多个容器组合在一起共享资源。
Pod中共享的环境包括Linux的namespace,cgroup和其他可能的隔绝环境,这一点跟Docker容器一致
Pod中的容器总是被同时调度,有共同的运行环境
你可以把单个Pod想象成是运行独立应用的“逻辑主机”——其中运行着一个或者多个紧密耦合的应用容器
每个Pod都是应用的一个实例。如果你想平行扩展应用的话(运行多个实例),你应该运行多个Pod
创建流程
概要图
图
时序图
图
步骤
具体地,Pod 创建过程中每个模块的处理逻辑如下(这里我们以Containerd 为例,其他遵循CRI 的容器运行时的相关调用都是类似的):
(1)用户或者控制器通过kubectl、Rest API 或其他客户端向API Server 提交Pod 创建请求。
(2)API Server 将Pod 对象写入etcd 中进行永久性存储。如果写入成功,那么API Server会收到来自etcd 的确认信息并将创建结果返回给客户端。
(3)API Server 处就能反映出Pod 资源发生的变化,一个新的Pod 被创建出来。
(4)Scheduler 监听到API Server 处新的Pod 被创建。它首先会查看该Pod 是否已经被调度(spec.nodeName 是否为空)。如果该Pod 并没有被调度到任何节点,那么Scheduler会给它计算分配一个最优节点,并把它更新到spec.nodeName 中,从而完成Pod 的节点绑定。
(5)Scheduler 对Pod 的更新也将被API Server 写回到etcd 中。Scheduler 同样会监听到Pod 对象发生了变化。但是由于它已经调度过该Pod(spec.nodeName 不为空),所以它将不做任何处理。
(6)kubelet 也会一直监听API Server 处Pod 资源的变化。当其发现Pod 被分配到自己所在节点上(自身节点名称和Pod 的spec.nodeName 相等)时,kubelet 将会调用CRI gRPC向容器运行时申请启动容器。
(7)kubelet 首先会调用CRI 的RunPodSandbox 接口。Containerd 要确保PodSandbox(即Infra 容器)的镜像是否存在。因为所有PodSandbox 都使用同一个pause 镜像,如果节点上已经有运行的 Pod,那么这个 pause 镜像就已经存在。接着它会创建一个新的Network Namespace,调用CNI 接口为Network Namespace 设置容器网络,Containerd 会使用这个Network Namespace 启动PodSandbox。
(8)当PodSandbox 启动成功后,kubelet 才会在PodSandbox 下请求创建容器。这里kubelet 会先检查容器镜像是否存在,如果容器镜像不存在,则调用CRI 的PullImage 接口并通过Containerd 将容器镜像下载下来。
(9)当容器镜像下载完成后,kubelet 调用CRI 的CreateContainer 接口向容器运行时请求创建容器。
(10)当容器创建成功后,kubelet 调用CRI 的StartContainer 接口向容器运行时请求启动容器。
(11)无论容器是否创建和启动成功,kubelet 都会将最新的容器状态更新到Pod 对象的Status 中,让其他控制器也能够监听到Pod 对象的变化,从而采取相应的措施。
共享资源
一个Pod中的应用容器共享五种资源:
PID命名空间:Pod中的不同应用程序可以看到其他应用程序的进程ID。
网络命名空间:Pod中的多个容器能够访问同一个IP和端口范围。
IPC命名空间:Pod中的多个容器能够使用SystemV IPC或POSIX消息队列进行通信。
UTS命名空间:Pod中的多个容器共享一个主机名。
Volumes(共享存储卷):Pod中的各个容器可以访问在Pod级别定义的Volumes。
Pod的生命周期通过Replication Controller来管理;
通过模板进行定义,然后分配到一个Node上运行,在Pod所包含容器运行结束后,Pod结束。
Kubernetes为Pod设计了一套独特的网络配置,包括:为每个Pod分配一个IP地址,使用Pod名作为通信的主机名等。
安全策略
基于布尔值控制:这种类型的字段默认为最严格限制的值。
基于被允许的值集合控制:这种类型的字段会与这组值进行对比,以确认值被允许。
基于策略控制:设置项通过一种策略提供的机制来生成该值,这种机制能够确保指定的值落在被允许的这组值中。
图
生命周期
生命周期
挂起(Pending):
Pod 已被 Kubernetes 系统接受,但有一个或者多个容器镜像尚未创建。等待时间包括调度 Pod 的时间和通过网络下载镜像的时间,这可能需要花点时间。
运行中(Running):
该 Pod 已经绑定到了一个节点上,Pod 中所有的容器都已被创建。至少有一个容器正在运行,或者正处于启动或重启状态。
成功(Successed):
Pod 中的所有容器都被成功终止,并且不会再重启。
失败(Failed):
Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。
未知(Unkonwn):
因为某些原因无法取得 Pod 的状态,通常是因为与 Pod 所在主机通信失败。
容器探针
探针 是由 kubelet 对容器执行的定期诊断
成功:容器通过了诊断。
失败:容器未通过诊断。
未知:诊断失败,因此不会采取任何行动。
对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连接,则表明容器健康。 在下面的例子中,通过与容器内的localhost:80建立TCP连接进行健康检查:
(3)HTTPGetAction:通过容器的IP地址、端口号及路径调用HTTP Get方法,如果响应的状态码大于等于200且小于400,则认为容器健康。 在下面的例子中,kubelet定时发送HTTP请求到localhost:80/_status/healthz来进行容器应用的健康检查:
对于每种探测方式,都需要设置initialDelaySeconds和timeoutSeconds两个参数,它们的含义分别如下。
◎ initialDelaySeconds:启动容器后进行首次健康检查的等待时间,单位为s。
◎ timeoutSeconds:健康检查发送请求后等待响应的超时时间,单位为s。当超时发生时,kubelet会认为容器已经无法提供服务,将会重启该容器。 如下代码片段是StartupProbe探针的一个参考配置,可以看到,这个Pod可以有长达30×10=300s的超长启动时间:
Kubernetes的Pod可用性探针机制可能无法满足某些复杂应用对容器内服务可用状态的判断,所以Kubernetes从1.11版本开始,引入了Pod Ready++特性对Readiness探测机制进行扩展,在1.14版本时达到GA稳定版本,称其为Pod Readiness Gates。Pod Readiness Gates给予了Pod之外的组件控制某个Pod就绪的能力,通过Pod Readiness Gates机制,用户可以设置自定义的Pod可用性探测方式来告诉Kubernetes某个Pod是否可用,具体使用方式是用户提供一个外部的控制器(Controller)来设置相应Pod的可用性状态。
Master
APIServer
图
apiserver主要包括认证、授权、准入、处理者四个处理逻辑。
对外提供RESTful的Kubernetes API服务,它是系统管理指令的统一入口,任何对资源进行增删改查的操作都要交给APIServer处理后再提交给etcd。
kubectl(Kubernetes提供的客户端工具,该工具内部就是对Kubernetes API的调用)是直接和APIServer交互的。
• 提供集群管理的 REST API 接口,包括:
• 认证 Authentication;
• 授权 Authorization;
• 准入 Admission(Mutating & Valiating)。
• 提供其他模块之间的数据交互和通信的枢纽(其他模块通过 API Server 查询或修改数据,只有 APIServer 才直接操作 etcd)。
• APIServer 提供 etcd 数据缓存以减少集群对 etcd 的访问。
controller manager
如果说APIServer做的是“前台”的工作的话,那controller manager就是负责“后台”的。
每个资源一般都对应有一个控制器,而controller manager就是负责管理这些控制器的。
比如我们通过APIServer创建一个pod,当这个pod创建成功后,APIServer的任务就算完成了。
而后面保证Pod的状态始终和我们预期的一样的重任就由controller manager去保证了。
Controller Manager 是集群的大脑,是确保整个集群动起来的关键;
其作用是确保 Kubernetes 遵循声明式系统规范,确保系统的真实状态(Actual State)与用户定义的期望状态(Desired State 一致);
Controller Manager 是多个控制器的组合,每个 Controller 事实上都是一个 control loop,负责侦听其管控的对象,当对象发生变更时完成配置;
Controller 配置失败通常会触发自动重试,整个集群会在控制器不断重试的机制下确保最终一致性( Eventual Consistency)。
原理
etcd
etcd是一个高可用的键值存储系统,Kubernetes使用它来存储各个资源的状态,从而实现了Restful的API。
组成
网路层:提供网路数据读写、监听服务端口,完成集群结点之间的数据通信,收发客户端数据。
raft模块:完整实现了raft协议。
存储模块:KV存储,wal存储,snapshot管理
复制状态机:状态机的数据维护在内存中,定期持久化到磁盘,每次写请求都会持久化到wal文件,根据写请求的内容修改状态机的数据。
raft分布式协议
组成
主节点leader
候选节点candidate
从节点flower
客户端只能从主节点写数据而从节点读取数据。
raft选主流程
初始化都是flower状态结点,等待100-300ms没有收到leader的心跳之后就变成了候选节点后给大家发选票,而候选人或得大多数结点的选票就变成了leader结点。
raft事务提交
每次提交事务先提交给leader,leader会暂存数据之后,复制数据到flower结点,等待大多数结点提交事务成功之后再将leader的事务提交。
raft选举超时是如何处理
等待时间短的结点会先发选票从而变为主节点。
但是如果2个候选或得的票数一样多则加时赛。直到选的票数最多的那个。
raft心跳超时
每个结点都会记录主节点是谁,并和主节点维持一个心跳超时时间,一旦没有收到主节点的回复从节点就要重新选举候选人结点。
raft的集群中断、脑裂问题并如何恢复
结点之间部分结点丢失通信,则会导致主节点的数据不能推给多个从节点也就主节点的数据不能进行提交。
集群恢复后,原来的主节点发现自己不是选票最多的结点就会变成从节点,并回滚日志,最后主节点再把数据推给从节点从而保证数据一致性。
scheduler
职责:负责调度pod到合适的Node上。
如果把scheduler看成一个黑匣子,那么它的输入是pod和由多个Node组成的列表,输出是Pod和一个Node的绑定,
即将这个pod部署到这个Node上。Kubernetes目前提供了调度算法,但是同样也保留了接口,用户可以根据自己的需求定义自己的调度算法。
主要是接收调度POD到合适的node节点上
通过apiserver,从etcd中获取资源信息进行调度
只负责调度工作,启动工作是node节点上的kubelet负责
调度策略:预算策略(predict)、优选策略(priorities)
架构设计
特殊的 Controller,工作原理与其他控制器无差别;
特殊职责:监控当前集群所有未调度的Pod,并且获取当前集群所有节点的健康状况和资源使用情况,为待调度 Pod 选择最佳计算节点,完成调度。
调度阶段分为:
• Predict:过滤不能满足业务需求的节点,如资源不足,端口冲突等。
• Priority:按既定要素将满足调度需求的节点评分,选择最佳节点。
• Bind:将计算节点与 Pod 绑定,完成调度。
调度过程
作用是把pod调度到最佳的node上去。需要根据不同的策略考虑node的资源使用情况例如端口、内存、存储等。
组件
核心组件
概述
tu1
1
图2
核心附件
附件(add-ons)用于扩展Kubernetes的基本功能,它们通常运行于Kubernetes集群自身之上,可根据重要程度将其划分为必要和可选两个类别。
网络插件
管理员需要从众多解决方案中根据需要及项目特性选择,常用的有Flannel、Calico、Canal、Cilium和Weave Net等。
KubeDNS
CoreDNS:Kubernetes使用定制的DNS应用程序实现名称解析和服务发现功能,它自1.11版本起默认使用CoreDNS——一种灵活、可扩展的DNS服务器;之前的版本中用到的是kube-dns项目,SkyDNS则是更早一代的解决方案。
Dashboard:基于Web的用户接口,用于可视化Kubernetes集群。
Dashboard可用于获取集群中资源对象的详细信息,
例如集群中的Node、Namespace、Volume、ClusterRole和Job等,
也可以创建或者修改这些资源对象。
容器资源监控系统:监控系统是分布式应用的重要基础设施
Kubernetes常用的指标监控附件有Metrics-Server、kube-state-metrics和Prometheus等。
集群日志系统:日志系统是构建可观测分布式应用的另一个关键性基础设施,用于向监控系统的历史事件补充更详细的信息,帮助管理员发现和定位问题;
Kubernetes常用的集中式日志系统是由ElasticSearch、Fluentd和Kibana(称之为EFK)组合提供的整体解决方案。
Ingress Controller:Ingress资源是Kubernetes将集群外部HTTP/HTTPS流量引入到集群内部专用的资源类型
它仅用于控制流量的规则和配置的集合,其自身并不能进行“流量穿透”,要通过Ingress控制器发挥作用;目前,此类的常用项目有Nginx、Traefik、Envoy、Gloo、kong及HAProxy等
在这些附件中,CoreDNS、监控系统、日志系统和Ingress控制器基础支撑类服务是可由集群管理的基础设施,而Dashboard则是提高用户效率和体验的可视化工具,类似的项目还有polaris和octant等
开放接口
CRI(Container Runtime Interface):容器运行时接口,提供计算资源
概述
每个容器运行时都有特点,因此不少用户希望Kubernetes能够支持更多的容器运行时。
Kubernetes从1.5版本开始引入了CRI接口规范,通过插件接口模式,Kubernetes无须重新编译就可以使用更多的容器运行时。
CRI包含Protocol Buffers、gRPC API、运行库支持及开发中的标准规范和工具。
Docker的CRI实现在Kubernetes 1.6中被更新为Beta版本,并在kubelet启动时默认启动。 可替代的容器运行时支持是Kubernetes中的新概念。
在Kubernetes 1.3发布时,rktnetes项目同时发布,让rkt容器引擎成为除Docker外的又一选择。
然而,不管是Docker还是rkt,都用到了kubelet的内部接口,同kubelet源码纠缠不清。这种程度的集成需要对kubelet的内部机制有非常深入的了解,还会给社区带来管理压力,这就给新生代容器运行时造成了难以跨越的集成壁垒。
CRI接口规范尝试用定义清晰的抽象层清除这一壁垒,让开发者能够专注于容器运行时本身。
CRI的主要组件
kubelet使用gRPC框架通过UNIX Socket与容器运行时(或CRI代理)进行通信。
kubelet是客户端,CRI代理(shim)是服务端
Protocol Buffers API包含两个gRPC服务:ImageService和RuntimeService。
◎ ImageService提供了从仓库中拉取镜像、查看和移除镜像的功能。
◎ RuntimeService负责Pod和容器的生命周期管理,以及与容器的交互(exec/attach/port-forward)。
rkt和Docker这样的容器运行时可以使用一个Socket同时提供两个服务,在kubelet中可以用--container-runtime-endpoint和--image-service-endpoint参数设置这个Socket。
Pod和容器的生命周期管理
Pod由一组应用容器组成,其中包含共有的环境和资源约束。在CRI里,这个环境被称为PodSandbox。
Kubernetes有意为容器运行时留下一些发挥空间,它们可以根据自己的内部实现来解释PodSandbox。对
于Hypervisor类的运行时,PodSandbox会具体化为一个虚拟机。其他例如Docker,会是一个Linux命名空间。
在v1alpha1 API中,kubelet会创建Pod级别的cgroup传递给容器运行时,并以此运行所有进程来满足PodSandbox对Pod的资源保障。 在启动Pod之前,kubelet调用RuntimeService.RunPodSandbox来创建环境。这一过程包括为Pod设置网络资源(分配IP等操作)。PodSandbox被激活之后,就可以独立地创建、启动、停止和删除不同的容器了。kubelet会在停止和删除PodSandbox之前首先停止和删除其中的容器。 kubelet的职责在于通过RPC管理容器的生命周期,实现容器生命周期的钩子、存活和健康监测,以及执行Pod的重启策略等。
RuntimeService服务包括对Sandbox和Container操作的方法,下面的伪代码展示了主要的RPC方法:
2.6.4 面向容器级别的设计思路
众所周知,Kubernetes的最小调度单元是Pod,它曾经可能采用的一个CRI设计就是复用Pod对象,使得容器运行时可以自行实现控制逻辑和状态转换,这样一来,就能极大地简化API,让CRI能够更广泛地适用于多种容器运行时。但是经过深入讨论之后,Kubernetes放弃了这一想法。 首先,kubelet有很多Pod级别的功能和机制(例如crash-loop backoff机制),如果交给容器运行时去实现,则会造成很重的负担;然后,Pod标准还在快速演进。很多新功能(如初始化容器)都是由kubelet完成管理的,无须交给容器运行时实现。 CRI选择了在容器级别进行实现,使得容器运行时能够共享这些通用特性,以获得更快的开发速度。这并不意味着设计哲学的改变—kubelet要负责、保证容器应用的实际状态和声明状态的一致性。 Kubernetes为用户提供了与Pod及其中的容器进行交互的功能(kubectl exec/attach/port-forward)。
kubelet目前提供了两种方式来支持这些功能:①调用容器的本地方法;②使用Node上的工具(例如nsenter及socat)。 因为多数工具都假设Pod用Linux namespace做了隔离,因此使用Node上的工具并不是一种容易移植的方案。在CRI中显式定义了这些调用方法,让容器运行时进行具体实现。下面的伪代码显示了Exec、Attach、PortForward这几个调用需要实现的RuntimeService方法: 目前还有一个潜在的问题是,kubelet处理所有的请求连接,使其有成为Node通信瓶颈的可能。在设计CRI时,要让容器运行时能够跳过中间过程。容器运行时可以启动一个单独的流式服务来处理请求(还能对Pod的资源使用情况进行记录),并将服务地址返回给kubelet。这样kubelet就能反馈信息给API Server,使之可以直接连接到容器运行时提供的服务,并连接到客户端。
CNI(Container Network Interface):容器网络接口,提供网络资源
添加网络、删除网络、添加网络列表、删除网络列表
CNI设计的时候考虑了以下问题
容器运行时必须在调用任何插件之前为容器创建一个新的网络命名空间。
然后,运行时必须确定这个容器应属于哪个网络,并为每个网络确定哪些插件必须被执行。
网络配置采用JSON格式,可以很容易地存储在文件中。网络配置包括必填字段,如name和type以及插件(类型)。网络配置允许字段在调用之间改变值。为此,有一个可选的字段args,必须包含不同的信息。
容器运行时必须按顺序为每个网络执行相应的插件,将容器添加到每个网络中
在完成容器生命周期后,运行时必须以相反的顺序执行插件(相对于执行添加容器的顺序)以将容器与网络断开连接。
容器运行时不能为同一容器调用并行操作,但可以为不同的容器调用并行操作
容器运行时必须为容器订阅ADD和DEL操作,这样ADD后面总是跟着相应的DEL
DEL可能跟着额外的DEL,但是,插件应该允许处理多个DEL(即插件DEL应该是幂等的)
容器必须由ContainerID唯一标识。存储状态的插件应该使用(网络名称,容器ID)的主键来完成。
运行时不能调用同一个网络名称或容器ID执行两次ADD(没有相应的DEL)。换句话说,给定的容器ID必须只能添加到特定的网络一次。
常用插件
CSI(Container Storage Interface):容器存储接口,提供存储资源
资源对象
通用设计:
TypeMeta
G(roup)
K(ind)
V(ersion)
Metadata
Namespace
Name
Labels & Annotation
Finalizers
ResourceVersion
SelftLink
描述文件定义
一个资源对象需要用5个字段来描述它,分别是Group/Version、Kind、MetaData、Spec、Status。这些字段定义在YAML或JSON文件中。
Kubernetes系统中的所有的资源对象都可以采用YAML或JSON格式的描述文件来定义,下面是某个Pod文件的资源对象描述文件。
YAML Manifest File Example代码示例如下:
资源对象描述文件说明如下。
apiVersion:指定创建资源对象的资源组和资源版本,其表现形式为<group>/<version>,若是core资源组(即核心资源组)下的资源对象,其表现形式为<version>。
kind:指定创建资源对象的种类。
metadata:描述创建资源对象的元数据信息,例如名称、命名空间等。
spec:包含有关Deployment资源对象的核心信息,告诉Kubernetes期望的资源状态、副本数量、环境变量、卷等信息。
status:包含有关正在运行的Deployment资源对象的信息。
每一个Kubernetes资源对象都包含两个嵌套字段,即spec字段和status字段。其中spec字段是必需的,它描述了资源对象的“期望状态”(Desired State),而status字段用于描述资源对象的“实际状态”(Actual State),它是由Kubernetes系统提供和更新的。在任何时刻,Kubernetes控制器一直努力地管理着对象的实际状态以与期望状态相匹配。
核心对象
对象分组
RESTful风格的API
以层级结构组织在一起,每个API群组表现为一个以/apis为根路径的RESTful路径,例如/apis/apps/v1,不过名称为core的核心群组有一个专用的简化路径:/api/v1。目前,常用的API群组可归为如下两类。
1)核心群组:RESTful路径为/api/v1,在资源的配置信息apiVersion字段中引用时可以不指定路径,而仅给出版本,例如apiVersion: v1,如上面的命令kubectl api-versions结果中最后一个群组所示。 2)命名的群组:RESTful路径为/apis/groupname/VERSION,例如/apis/apps/v1,在apiVersion字段中引用它时需要移除/apis前缀,例如apiVersion: apps/v1等。 名称空间级别的每一个资源类型在API的URL路径表示都可简单抽象为形如/apis/<group>/<version>/namespaces/<namespace>/<kind-plural>的路径。例如default名称空间中,Deployment类型的路径为/apis/apps/v1/namespaces/default/deployments,通过此路径可获取到default名称空间中所有Deployment对象的列表。
API Server接收和返回的所有JSON对象都遵循同一个模式,即它们都具有kind和apiVersion字段,分别用于标识对象所属的资源类型、API群组及相关的版本,可合称为类型元数据(TypeMeta)。同时,大多数的对象或列表类型的资源还会有metadata、spec和status这3个嵌套型的字段。其中metadata字段为资源提供元数据信息,例如名称、隶属的名称空间和标签等,因而也称为对象元数据(ObjectMeta);spec字段则是由用户负责声明对象期望状态的字段,不同资源类型的期望状态描述方式各不相同,因此其嵌套支持的字段也不尽相同;而status字段则记录活动对象的当前状态信息,也称为观察状态,它由Kubernetes系统自行维护,对用户来说为只读字段,不需要在配置清单中提供,而是在查询集群中的对象时由API Server在响应中返回。 每个资源类型代表一种特定格式的数据结构,它接收并返回该格式的对象数据,同时,一个对象也可嵌套多个独立的“小”对象,并支持每个小对象的单独管理操作。例如对Pod类型的资源来说,用户可创建、更新或删除Pod对象,而每个Pod对象的metadata、spec和status字段的值又是各自独立的对象型数据,所以它们可被单独操作。需要注意的是,status对象由Kubernetes系统单独进行自动更新,且不支持用户手动操作。
图
图2
API Groups(API组) 为了更容易扩展、升级和演进API,Kubernetes将API分组为多个逻辑集合,称之为API Groups,它们支持单独启用或禁用,在不同的API Groups中使用不同的版本,允许各组以不同的速度演进,例如apps/v1、apps/v1beta2、apps/v1beta1等。API Groups以REST URL中的路径进行定义并区别彼此,每个API Group群组都表现为一个以/apis为根路径的rest路径,不过核心群组Core有个专用的简化路径/api/v1,当前支持以下两类API Groups。 (1)Core Groups(核心组),也可以称之为Legacy Groups。其作为Kubernetes核心的API,在资源对象的定义中被表示为“apiVersion:v1”,我们常用的资源对象大部分都在这个组里,例如Container、Pod、ReplicationController、Endpoint、Service、ConfigMap、Secret、Volume等。 (2)具有分组信息的API,以/apis/$GROUP_NAME/$VERSION URL路径进行标识,例如apiVersion:batch/v1、apiVersion:extensions:v1beta1、apiVersion:apps/v1beta1等。比如/apis/apps/v1在apiversion字段中的格式为“$GROUP_NAME/$VERSION”。下面是常见的一些分组说明。 ◎ apps/v1:是Kubernetes中最常见的API组,其中包含许多核心对象,主要与用户应用的发布、部署有关,例如Deployments,RollingUpdates和ReplicaSets。 ◎ extensions/VERSION:扩展API组,例如DaemonSets、ReplicaSet和Ingresses都在此版本中有重大更改。 ◎ batch/VERSION:包含与批处理和类似作业的任务相关的对象,例如Job,包括v1与v1beta1两个版本。 ◎ autoscaling/VERSION:包含与HPA相关的资源对象,目前有稳定的v1版本。 ◎ certificates.k8s.io/VERSION:包含集群证书操作相关的资源对象。 ◎ rbac.authorization.k8s.io/v1:包含RBAC权限相关的资源对象。 ◎ policy/VERSION:包含Pod安全性相关的资源对象。 如果需要实现自定义的资源对象及相应的API,则使用CRD进行扩展是最方便的。 例如,Pod的API说明如图9.5所示,由于Pod属于核心资源对象,所以不存在某个扩展API Group,页面显示为Core,在Pod的定义中为“apiVersion:v1”。 StatefulSet则属于名为apps的API组,版本号为v1,在StatefulSet的定义中为“apiVersion:apps/v1”,如图9.6所示
对象关系
关系图
对象介绍
资源分类
工作负载型资源
用于确保Pod资源对象更好地运行容器化应用。
具有同一种负载的各Pod对象需要以负载均衡的方式服务于各请求,而各种容器化应用需要彼此发现以完成工作协同。Pod资源具有生命周期,
存储型资源能够为重构的Pod对象提供持久化数据存储机制,
配置型资源能够让共享同一配置的Pod资源从中统一获取配置改动信息。这些资源作为“配置中心”为管理容器化应用的配置文件提供了极为便捷的管理机制。
集群型资源为管理集群本身的工作特性提供了配置接口,
元数据型资源用于配置集群内部其他资源的行为。
集群型资源
Kubernetes还存在一些用于定义集群自身配置信息的资源类型,它们不属于任何名称空间且仅应该由集群管理员操作。常用的集群型资源有如下几种。
Namespace:名称空间,为资源对象的名称提供了限定条件或作用范围,它为使用同一集群的多个团队或项目提供了逻辑上的隔离机制,降低或消除了资源对象名称冲突的可能性。
Kubernetes集群会内置如下4个名称空间,其中第4个名称空间是为kubelet引入租约机制后才新增的。
·default:创建名称空间级别的资源对象,但未指定从属的名称空间时将默认使用defaut名称空间。
·kube-public:用于为集群上的所有用户(包括匿名用户)提供一个公共可用的名称空间,保留给集群使用,以防某些资源在整个集群中公开可见;该名称空间的公共属性仅是约定,并非强制要求。
·kube-system:用于部署与Kubernetes系统自身相关的组件,例如kube-proxy、CNI网络插件,甚至是kube-apiserver、kube-controller-manager和kube-scheduler等控制平面组件的静态Pod等;不建议在该名称空间中运行与系统无关的工作负载。
·kube-node-lease:目前是专用于放置kubelet lease对象的名称空间,这些对象对于Kubernetes系统自身健康运行至关重要;因而同样不建议在该名称空间中运行与系统无关的工作负载。
Node:Kubernetes并不能直接管理其工作节点,但它会把由管理员添加进来的任何形式(物理机或虚拟机等)的工作节点映射为一个Node资源对象,因而节点名称(标识符)在集群中必须唯一。
Role:角色,隶属于名称空间,代表名称空间级别由规则组成的权限集合,可被RoleBinding引用。
ClusterRole:集群角色,隶属于集群而非名称空间,代表集群级别的、由规则组成的权限集合,可被RoleBinding和ClusterRoleBinding引用。
RoleBinding:用于将Role中的许可权限绑定在一个或一组用户之上,从而完成用户授权,它隶属于且仅能作用于名称空间级别。
ClusterRoleBinding:将ClusterRole中定义的许可权限绑定在一个或一组用户之上,通过引用全局名称空间中的ClusterRole将集群级别的权限授予指定用户。
工作负载型资源
Pod用于承载容器化应用,代表着Kubernetes之上工作负载的表现形式。它负责运行容器,并为容器解决环境性的依赖,例如向容器注入临时或持久化的存储空间、配置信息或密钥数据等。而诸如滚动更新、扩容和缩容一类的编排任务则由“控制器”对象负责,专用于Pod编排任务的控制器也可统称为Pod控制器。
应用程序分为无状态和有状态两种类型,无状态应用中的每个Pod实例均可被其他同类实例所取代,但有状态应用的每个Pod实例均有其独特性,必须单独标识和管理,因而它们分别由两种不同类型的Pod控制器进行管理。例如,ReplicationController、ReplicaSet和Deployment负责管理无状态应用,StatefulSet则用于管控有状态类应用。还有些应用较为独特,有些需要在集群中的每个节点上运行单个Pod资源,负责收集日志或运行系统服务等任务,该类编排操作由DaemonSet控制器对象进行,而需要在正常完成后退出故无须始终处于运行状态任务的编排工作则隶属Job控制器对象。CronJob控制器对象还能为Job型的任务提供定期执行机制。
ReplicationController:用于确保每个Pod副本在任一时刻均能满足目标数量,即它用于保证每个容器或容器组总是运行并可访问;它是上一代的无状态Pod应用控制器,建议读者使用新型控制器Deployment和ReplicaSet来取代它。
ReplicaSet:新一代ReplicationController,它与ReplicationController唯一不同之处在于支持的标签选择器不同,ReplicationController只支持“等值选择器”,而ReplicaSet还支持基于集合的选择器。
Deployment:用于管理无状态的持久化应用,例如HTTP服务等;它用于为Pod和ReplicaSet提供声明式更新,是构建在ReplicaSet之上的、更为高级的控制器。
StatefulSet:用于管理有状态的持久化应用,例如数据库服务程序;与Deployment的不同之处在于,StatefulSet会为每个Pod创建一个独有的持久性标识符,并会确保各Pod间的顺序性。
DaemonSet:用于确保每个节点都运行了某Pod的一个副本,包括后来新增的节点;而节点移除将导致Pod回收;DaemonSet常用于运行各类系统级守护进程,例如kube-proxy和Flannel网络插件,以及日志收集和临近系统的Agent应用,例如fluentd、Logstash、Prometheus的Node Exporter等。
Job:用于管理运行完成后即可终止的应用,例如批处理作业任务;Job创建一个或多个Pod,并确保其符合目标数量,直到应用完成而终止。
Service
应用服务的抽象
通过 labels 为应用提供负载均衡和服务发现
匹配 labels的 Pod IP和端口列表组成 endpoints
由 Kube-proxy 负责将服务 IP 负载均衡到这些 endpoints 上
每个默认类型 Service 都会自动分配一个 cluster IP(仅在集群内部可访问的虚拟地址)和 DNS名
其他容器可以通过该地址或 DNS 来访问服务,而不需要了解后端容器的运行
RC、RS 和 Deployment 只是保证了支撑服务的微服务 Pod 的数量,但是没有解决如何访问这些服务的问题。一个 Pod 只是一个运行服务的实例,随时可能在一个节点上停止,在另一个节点以一个新的 IP 启动一个新的Pod,因此不能以确定的 IP 和端口号提供服务。
要稳定地提供服务需要服务发现和负载均衡能力
服务发现完成的工作,是针对客户端访问的服务,找到对应的的后端服务实例
在 Kubernetes 集群中,客户端需要访问的服务就是 Service对象
每个 Service 会对应一个集群内部有效的虚拟 IP,集群内部通过虚拟 IP 访问一个服务
在 Kubernetes 集群中微服务的负载均衡是由 Kube-proxy 实现的
Kube-proxy 是 Kubernetes 集群内部的负载均衡器。它是一个分布式代理服务器,在 Kubernetes 的每个节点上都有一个;这一设计体现了它的伸缩性优势,需要访问服务的节点越多,提供负载均衡能力的 Kube-proxy 就越多,高可用节点也随之增多。与之相比,我们平时在服务器端使用反向代理作负载均衡,还要进一步解决反向代理的高可用问题。
Deployment
部署表示用户对 Kubernetes 集群的一次更新操作。
部署是一个比 RS 应用模式更广的 API 对象,可以是创建一个新的应用,更新一个已存在的应用,也可以是滚动升级一个应用。
滚动升级一个服务,实际是创建一个新的 RS,然后逐渐将新 RS 中副本数增加到理想状态,将旧 RS 中的副本数减小到 0 的复合操作;这样一个复合操作用一个 RS 是不太好描述的,所以用一个更通用的 Deployment 来描述。以 Kubernetes 的发展方向,未来对所有长期伺服型的的业务的管理,都会通过 Deployment 来管理。
DaemonSet
用于确保每个节点都运行了某Pod的一个副本,包括后来新增的节点;
而节点移除将导致Pod回收;
DaemonSet常用于运行各类系统级守护进程,例如kube-proxy和Flannel网络插件,以及日志收集和临近系统的Agent应用,例如fluentd、Logstash、Prometheus的Node Exporter等。
StatefulSet
用于管理有状态的持久化应用,例如数据库服务程序;与Deployment的不同之处在于,StatefulSet会为每个Pod创建一个独有的持久性标识符,并会确保各Pod间的顺序性。
CustomResourceDefinition
CustomResourceDefinition 是指自定义资源定义,简称CRD,
是Kubernetes 1.7 中引入的一项强大功能,它允许用户将自己的自定义对象添加到Kubernetes 集群中。
当创建新CRD 的定义时,API Server 将为指定的每个版本创建一个新的RESTful 资源路径
当集群中成功地创建了CRD,就可以像Kubernetes 原生的资源一样使用它,利用Kubernetes 的所有功能,例如其CLI、安全性、API 服务、RBAC 等。
CRD 的定义是在集群范围内的,CRD 的资源对象的作用域可以是命名空间(Namespaced)或者集群范围(Cluster-wide)。
与现有的内置对象一样,删除Namespace 也会删除该Namespace中所有自定义的对象,但不会删除CRD 的定义。
Kubernetes 还提供一系列Codegen 工具(deepcopy-gen、client-gen、lister-gen、informer-gen 等),能够自动生成该CRD 资源的Golang 版本的Clientset、Lister 及Informer,这为该资源编写控制器提供了很大便利。
CRD 就像数据库的开放式表结构,允许用户自定义Schema。有了这种开放式设计,用户可以基于CRD 定义一切需要的模型,满足不同业务的需求。社区鼓励基于CRD 的业务抽象,众多主流的扩展应用都是基于CRD 构建的,比如Istio、Knative。甚至基于CRD推出了Operator Mode 和Operator SDK,可以以极低的开发成本定义新对象,并构建新对象的控制器。
对象类资源配置规范
Kubernetes内置资源
资源组 资源种类 说 明
apps
ControllerRevision 记录资源对象所有的历史版本的资源类型
DaemonSet 在Pod资源对象的基础上提供守护进程的 资源类型
Deployment 在Pod资源对象的基础上提供支持无状态 服务的资源类型
RcplicaSet 在Pod资源对象的基倨上提供Pod副本的资源类型
StatefulSet 在Pod资源对象的基册上提供支持有状态服务的资源类型
auditregistration.k8s.io
AuditSink 审计资源类型
authentication.k8s.io
TokcnRcvicw 认证资源类型
authorization.k8s.io
LocalSubjectAccessReview 授权检査用户是否珥以在指定的命名空间 中执行操作
SelfSubjcctAccessRcvicw 授权检查用户是否可以执行操作(若不指 定spec.namespace,则在所冇的命名空间中 执行操作)
SelfSubjectRulesReview 授权枚挙用户叫以在指定的命名空间屮执 行一组操作
SubjcctAcccssRcvicw 授权检查用户是否可以执行操作
autoscaling
HorizontalPodAutoscalcr 在Pod资源对象的基础上提供水平自动伸 缩资源类型
batch
Job 提供一次性任务的资源类型
CronJob 提供定时任务的资源类型
ccrtificatcs.k8s.io
CcrtificateSigningRcqucst 提供讯书管理的资源类型
coordination.k8s.io
Leases 提供领导者选举机制的资源类型
core
node.k8s.io
RuntimeClass 提供容器运行时功能的资源类型
policy
Evictions 在Pod资源对象的基础上提供駅逐策略的 资源类型
PodDisruptionBudget 提供限制同时中断Pod的数量.以保证集 群的高可用性
PodSecurityPolicy 提供控制Pod资源安全相关策略的资源类 型
rbac.authorization.k8s.io
ClusterRole 提供RBAC集群角色的资源类型
ClustcrRoleBinding 提供RBAC集群布色绑定的资源类型
Role 提供RBAC角色的资源类型
RoleBinding 提供RBAC角色绑定的资源类型
scheduling.k8s.io PriorityClass 提供Pod资源对象优先级管理的资源类型
scttings.k8s.io PodPrcsct 在创建Pod资源对象时,可以将特定信息 注入Pod资源对象申
core
Componentstatus 该资源类型已被弃用,其用于提供获取 Kubemeles组件运行状况的资源类型
ConfigMap 提供容器内应用程序配置管理的资源类型
Endpoints 提供将外部服务器映射为内部服务的资源
Event 提供Kubemetes集群事件管理的资源类型
LimitRange 为命名空间中的每种资源对象设置资源 (硬件资源)使用限制
Namespace 提供资源对象所在的命名空间的资源类型
Node 提供Kubemetes集詳中管理T.作节点的资 源类型。每个节点都有一个唯一标识符
PersistentVolume 提供PV存储的资源类型
Persistent VolutncClaiin 提供PVC存储的资源类型
Pod 提供容器集合管理的资源类環
PodTemplate 提供用于描述预定义Pod资源对象副本数 模板的资源类型
Replicationcontroller 在Pod资源对象的基础上提供副本数保持 不变的资源类型
RcsourccQuota 提供每个命名空间配额限制的资源类型
Secret 提供有储密码、Token、密钥等敏感数据的 资源类型
Service 提供负载均衡器为Pod资源对象的代理服 务的资源类型
ServiceAccount 提供ServiceAccount认证的资源类型
events.k8s.io
Event 提供Kubemetes集群事件管理的资源类型
networking.k8s.io
RuntimcClass 提供容崙込行时功能的资源类型
Ingress 提供从Kubemetes集群外部访问集群内部 服务管理的资源类型
4
参考
【掌阅】Kubernetes源码剖析
HOW
资源管理
计算资源管理
调度器
图
样例
两个控制循环
1、第一个控制循环称为 Informer Path,
主要工作是启动一系列 Informer,用来监听(Watch)集群中 Pod、Node、Service 等与调度相关的 API 对象的变化。比如,当一个待调度 Pod 被创建出来之后,调度器就会通过 Pod Informer 的 Handler,将这个待调度 Pod 添加进调度队列;
同时,调度器还要负责对调度器缓存 Scheduler Cache 进行更新,并以这个 cache 为参考信息,来提高整个调度流程的性能。
2、第二个控制循环即为对 pod 进行调度的主循环,称为 Scheduling Path。
这一循环的工作流程是不断地从调度队列中取出待调度的 pod,运行两个步骤的算法,来选出最优 node
在集群的所有节点中选出所有“可以”运行该 pod 的节点,这一步被称为 Predicates;
在上一步选出的节点中,根据一系列优选算法对节点打分,选出“最优”即得分最高的节点,这一步被称为 Priorities。
调度完成之后,调度器就会为 pod 的 spec.NodeName 赋值这个节点,这一步称为 Bind。
而为了不在主流程路径中访问 Api Server 影响性能,调度器只会更新 Scheduler Cache 中的相关 pod 和 node 信息:这种基于乐观假设的 API 对象更新方式,在 K8s 中称为 Assume。
之后才会创建一个 goroutine 来异步地向 API Server 发起更新 Bind 操作,这一步就算失败了也没有关系,Scheduler Cache 更新后就会一切正常。
NodeAffinity:Node亲和性调度
是用于替换NodeSelector的全新调度策略。有两种节点亲和性表达。
◎ RequiredDuringSchedulingIgnoredDuringExecution:必须满足指定的规则才可以调度Pod到Node上(功能与nodeSelector很像,但是使用的是不同的语法),相当于硬限制。
◎ PreferredDuringSchedulingIgnoredDuringExecution:强调优先满足指定规则,调度器会尝试调度Pod到Node上,但并不强求,相当于软限制。多个优先级规则还可以设置权重(weight)值,以定义执行的先后顺
PodAffinity:Pod亲和与互斥调度策略
在实际的生产环境中有一类特殊的Pod调度需求:
存在某些相互依赖、频繁调用的Pod,它们需要被尽可能地部署在同一个Node节点、机架、机房、网段或者区域(Zone)内,这就是Pod之间的亲和性;
反之,出于避免竞争或者容错的需求,我们也可能使某些Pod尽可能地远离某些特定的Pod,这就是Pod之间的反亲和性或者互斥性。
Pod间的亲和性与反亲和性调度策略从Kubernetes 1.4版本开始引入。简单地说,就是相关联的两种或多种Pod是否可以在同一个拓扑域中共存或者互斥,前者被称为Pod Affinity,后者被称为Pod Anti Affinity。
那么,什么是拓扑域,
一个拓扑域由一些Node节点组成,这些Node节点通常有相同的地理空间坐标,比如在同一个机架、机房或地区,我们一般用region表示机架、机房等的拓扑区域,用Zone表示地区这样跨度更大的拓扑区域。在极端情况下,我们也可以认为一个Node就是一个拓扑区域。
为此,Kubernetes内置了如下一些常用的默认拓扑域: ◎ kubernetes.io/hostname; ◎ topology.kubernetes.io/region; ◎ topology.kubernetes.io/zone。 需要注意的是,以上拓扑域是由Kubernetes自己维护的,在Node节点初始化时,controller-manager会为Node打上许多标签,比如kubernetes.io/hostname这个标签的值就会被设置为Node节点的hostname。另外,公有云厂商提供的Kubernetes服务或者使用cloud-controller-manager创建的集群,还会给Node打上topology.kubernetes.io/region和topology.kubernetes.io/zone标签,以确定各个节点所属的拓扑域。
Taints和Tolerations(污点和容忍)
前面介绍的NodeAffinity节点亲和性,是在Pod上定义的一种属性,使得Pod能够被调度到某些Node上运行(优先选择或强制要求)。Taint则正好相反,它让Node拒绝Pod的运行。
简单地说,被标记为Taint的节点就是存在问题的节点,比如磁盘要满、资源不足、存在安全隐患要进行升级维护,希望新的Pod不会被调度过来,但被标记为Taint的节点并非故障节点,仍是有效的工作节点,所以仍需将某些Pod调度到这些节点上时,可以通过使用Toleration属性来实现。 在默认情况下,在Node上设置一个或多个Taint之后,除非Pod明确声明能够容忍这些污点,否则无法在这些Node上运行。可以用kubectl taint命令为Node设置Taint信息: 这个设置为node1加上了一个Taint。该Taint的键为key,值为value,Taint的效果是NoSchedule。这意味着除非Pod明确声明可以容忍这个Taint,否则不会被调度到node1上。 然后,需要在Pod上声明Toleration。
下面的两个Toleration都被设置为可以容忍(Tolerate)具有该Taint的Node,使得Pod能够被调度到node1上: 或 Pod的Toleration声明中的key和effect需要与Taint的设置保持一致,并且满足以下条件之一。
◎ operator的值是Exists(无须指定value)。
◎ operator的值是Equal并且value相等。 如果不指定operator,则默认值为Equal。 另外,有如下两个特例。
◎ 空的key配合Exists操作符能够匹配所有键和值。
◎ 空的effect匹配所有effect。 在上面的例子中,effect的取值为NoSchedule,还可以取值为PreferNoSchedule,这个值的意思是优先,也可以算作NoSchedule的软限制版本—一个Pod如果没有声明容忍这个Taint,则系统会尽量避免把这个Pod调度到这一节点上,但不是强制的。后面还会介绍另一个effect“NoExecute”。 系统允许在同一个Node上设置多个Taint,也可以在Pod上设置多个Toleration。Kubernetes调度器处理多个Taint和Toleration的逻辑顺序为:首先列出节点中所有的Taint,然后忽略Pod的Toleration能够匹配的部分,剩下的没被忽略的Taint就是对Pod的效果了。下面是几种特殊情况。
◎ 如果在剩余的Taint中存在effect=NoSchedule,则调度器不会把该Pod调度到这一节点上。
定义Pod驱逐行为,以应对节点故障 前面提到的NoExecute这个Taint效果对节点上正在运行的Pod有以下影响。
◎ 没有设置Toleration的Pod会被立刻驱逐。
◎ 配置了对应Toleration的Pod,如果没有为tolerationSeconds赋值,则会一直留在这一节点中。
◎ 配置了对应Toleration的Pod且指定了tolerationSeconds值,则会在指定的时间后驱逐。注意,在节点发生故障的情况下,系统将会以限速(rate-limiting)模式逐步给Node设置Taint,这样就能避免在一些特定情况下(比如Master暂时失联)有大量的Pod被驱逐。
Kubernetes会自动给Pod添加下面几种Toleration:
◎ key为node.kubernetes.io/not-ready,并配置tolerationSeconds=300;
◎ key为node.kubernetes.io/unreachable,并配置tolerationSeconds=300。
以上添加的这种自动机制保证了在某些节点发生一些临时性问题时,Pod默认能够继续停留在当前节点运行5min等待节点恢复,而不是立即被驱逐,从而避免系统的异常波动。 另外,Kubernetes从1.6版本开始引入两个与Taint相关的新特性:TaintNodesByCondition及TaintBasedEvictions,用来改善异常情况下的Pod调度与驱逐问题,比如在节点内存吃紧、节点磁盘空间已满、节点失联等情况下,是否自动驱逐某些Pod或者暂时保留这些Pod等待节点恢复正常。这个过程的完整逻辑基本如下。 (1)不断地检查所有Node状态,设置对应的Condition。
(2)不断地根据Node Condition设置对应的Taint。 (
3)不断地根据Taint驱逐Node上的Pod。
资料
https://mp.weixin.qq.com/s/ntF3yyFcmvHVraTNbKvANg
控制器
图
控制器是支撑Kubernetes声明式API的关键组件
持续监视对API Server上的API对象的添加、更新和删除等更改操作,并在发生此类事件时执行目标对象相应的业务逻辑,从而将客户端的管理指令转为对象管理所需要的真正的操作过程。
简单来说,一个具体的控制器对象是一个控制循环程序,它在单个API对象上创建一个控制循环以确保该对象的状态符合预期。
不同类型的Pod控制器在不同维度完成符合应用需求的管理模式,
例如Deployment控制器资源可基于“Pod模板”创建Pod实例,并确保实例数目精确反映期望的数量;
另外,控制器还支持应用规模中Pod实例的自动扩容、缩容、滚动更新和回滚,以及创建工作节点故障时的重建等运维管理操作。
StatefulSet控制器专用于管理有状态应用的Pod实例,其他常用的Pod控制器还有ReplicaSet、DaemonSet、Job和CronJob等,分别用于不同控制模型的工作负载
资源管理
1.计算资源管理(Compute Resources)
计算资源的配置项分为两种:
一种是资源请求(Resource Requests,简称Requests),
表示容器希望被分配到的、可完全保证的资源量,
Requests 的值会提供给 Kubernetes 调度器(Kubernetes Scheduler)以便于优化基于资源请求的容器调度;
另外一种是资源限制(Resource Limits,简称 Limits),
Limits 是容器最多能使用到的资源量的上限,这个上限值会影响节点上发生资源竞争时的解决策略。
Pod中的每个容器都可以配置以下4个参数。
spec.container[].resources.requests.cpu.
spec.container[].resources.limits.cpu.
spec.container[].resources.requests.memory.
spec.container[].resources.limits.memory.
◎ Requests和Limits都是可选的。在某些集群中如果在Pod创建或者更新时,没设置资源限制或者资源请求值,那么可能会使用系统提供一个默认值,这个默认值取决于集群的配置。
◎ 如果Request没有配置,那么默认会被设置为等于Limits。
◎ 而任何情况下Limits都应该设置为大于或者等于Requests。
基于Requests和Limits的Pod调度机制
当一个Pod创建成功时,Kubernetes调度器(Scheduler)为该Pod选择一个节点(Node)来执行。对于每种计算资源(CPU和内存)而言,每个节点都有一个能用于运行Pod的最大容量值。
调度器在调度时,首先要确保调度后该节点上所有Pod的CPU和内存的Requests总和不能超过该节点能提供给Pod使用的CPU和内存的最大容量值。
Kubernetes提供了LimitRange机制对Pod和容器的Requests和Limits配置进一步做出限制。
1)创建一个namespace
2)为namespace设置LimitRange
Kubernetes中Pod的Requests和Limits资源配置有如下特点:
如果Pod配置的Requests值等于Limits值,那么该Pod可以获得的资源是完全可靠的;
而如果Pod的Requests值小于Limits值,那么该Pod获得的资源可分成两部分:一部分是完全可靠的资源,资源量大小等于Requests值;
另外一部分是不可靠的资源,这部分资源最大等于Limits与Requests的差额值,这份不可靠的资源能够申请到多少,则取决于当时主机上容器可用资源的余量。 通过这种机制,Kubernetes可以实现节点资源的超售(Over Subscription),
比如在CPU完全充足的情况下,某机器共有32GiB内存可提供给容器使用,容器配置为Requests值1GiB,Limits值为2GiB,那么该机器上最多可以同时运行32个容器,每个容器最多可使用2GiB内存,如果这些容器的内存使用峰值错开,那么所有容器也可以一直正常运行。 超售机制能有效地提高资源的利用率,同时不会影响容器申请的完全可靠资源的可靠性。
4.资源的配额管理(Resource Quotas)
5.ResourceQuota和LimitRange实践指南
6.资源管理总结
Kubernetes中的资源管理的基础是容器和Pod的资源配置(Requests和Limits)。容器的资源配置(Requests和Limits)指定了容器请求的资源和容器能使用的资源上限,而Pod的资源配置则是Pod中所有容器的资源配置总和的上限。
通过资源配额(Resource Quota)机制,我们可以对命名空间下所有Pod使用资源的总量进行限制,
如果我们需要对用户的Pod或容器的资源配置做更多的限制,则我们可以使用资源配置范围(LimitRange)来达到这个目的。LimitRange可以有效地限制Pod和容器的资源配置的最大、最小范围,也可以限制Pod和容器的Limits与Requests的最大比例上限,此外LimitRange还可以为Pod中的容器提供默认的资源配置。
Kubernetes基于Pod的资源配置(Requests和Limits)实现了资源服务质量(QoS)。不同QoS级别的Pod在系统中拥有不同的优先级:高优先级的Pod具有更高的可靠性,可以用于运行可靠性要求较高的服务;而低优先级的Pod可以实现集群资源的超售,能有效地提高集群资源利用率。
网络资源管理
Kubernetes网络的基本概念和框架。
1.IP地址分配 Kubernetes使用各种IP范围为节点、Pod和服务分配IP地址。 ·系统会从集群的VPC网络为每个节点分配一个IP地址。该节点IP用于提供从控制组件(如Kube-proxy和Kubelet)到Kubernetes Master的连接; ·系统会为每个Pod分配一个地址块内的IP地址。用户可以选择在创建集群时通过--pod-cidr指定此范围; ·系统会从集群的VPC网络为每项服务分配一个IP地址(称为ClusterIP)。大部分情况下,该VPC与节点IP地址不在同一个网段,而且用户可以选择在创建集群时自定义VPC网络。
2.Pod出站流量 Kubernetes处理Pod的出站流量的方式主要分为以下三种:
Pod到Pod
在Kubernetes集群中,每个Pod都有自己的IP地址,运行在Pod内的应用都可以使用标准的端口号,不用重新映射到不同的随机端口号。所有的Pod之间都可以保持三层网络的连通性,比如可以相互ping对方,相互发送TCP/UDP/SCTP数据包。CNI就是用来实现这些网络功能的标准接口
。 Pod到Service
Pod的生命周期很短暂,但客户需要的是可靠的服务,因此Kubernetes引入了新的资源对象Service,其实它就是Pod前面的4层负载均衡器。Service总共有4种类型,其中最常用的类型是ClusterIP,这种类型的Service会自动分配一个仅集群内部可以访问的虚拟IP。 Kubernetes通过Kube-proxy组件实现这些功能,每台计算节点上都运行一个Kubeproxy进程,通过复杂的iptables/IPVS规则在Pod和Service之间进行各种过滤和NAT。
Pod到集群外
从Pod内部到集群外部的流量,Kubernetes会通过SNAT来处理。SNAT做的工作就是将数据包的源从Pod内部的IP:Port替换为宿主机的IP:Port。当数据包返回时,再将目的地址从宿主机的IP:Port替换为Pod内部的IP:Port,然后发送给Pod。当然,中间的整个过程对Pod来说是完全透明的,它们对地址转换不会有任何感知。 以上涉及的概念我们在后面的章节会进行详细讲解,本节只是抛砖引玉,不做深入分析。
跨主机容器网络方案
what
“三种IP”
◎ Node IP:Node节点的IP地址。
Node IP是Kubernetes集群中每个节点的物理网卡的IP地址,这是一个真实存在的物理网络,所有属于这个网络的服务器之间都能通过这个网络直接通信,不管它们中是否有部分节点不属于这个Kubernetes集群。这也表明了Kubernetes集群之外的节点访问Kubernetes集群之内的某个节点或者TCP/IP服务时,必须要通过Node IP进行通信。
◎ Pod IP:Pod的IP地址。
其次,Pod IP是每个Pod的IP地址,它是Docker Engine根据docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络,前面我们说过,Kubernetes 要求位于不同 Node 上的Pod能够彼此直接通信,所以Kubernetes里一个Pod里的容器访问另外一个Pod里的容器,就是通过Pod IP所在的虚拟二层网络进行通信的,而真实的TCP/IP流量则是通过Node IP所在的物理网卡流出的。
◎ Cluster IP:Service的IP地址。
最后,我们说说Service的Cluster IP,它也是一个虚拟的IP,但更像是一个“伪造”的IP网络,原因有以下几点。
◎ Cluster IP仅仅作用于Kubernetes Service这个对象,并由Kubernetes管理和分配IP地址(来源于Cluster IP地址池)。
◎ Cluster IP无法被Ping,因为没有一个“实体网络对象”来响应。
◎ Cluster IP只能结合Service Port组成一个具体的通信端口,单独的Cluster IP不具备TCP/IP通信的基础,并且它们属于Kubernetes集群这样一个封闭的空间,集群之外的节点如果要访问这个通信端口,则需要做一些额外的工作。
◎ 在Kubernetes集群之内,Node IP网、Pod IP网与Cluster IP网之间的通信,采用的是Kubernetes自己设计的一种编程方式的特殊的路由规则,与我们所熟知的IP路由有很大的不同。
分层
图
本地通信
本地通信是Pod内部不同容器之间的通信。因为Pod内网容器共享同一个网络协议栈,所以它们之间的通信可以通过Loopback设备完成。
同节点Pod通信
同节点Pod之间的通信,是Cni0虚拟网桥内部的通信,相当于一个二层局域网内部设备通信。
跨节点Pod通信
步骤
图
网络包通过网络接口 eth1 离开 Pod 1,通过虚拟网络接口 veth1 到达 root netns。
网络包离开 veth1,到达 cni0,查找 Pod 6 的地址。
网络包离开 cni0,重定向到 eth0。
网络包从 Master 1 离开 eth0,到达网关。
网络包离开网关,通过工作节点 1 的网络接口 eth0 到达 root netns。
网络包离开 eth0,到达 cni0,并查找 Pod 6 的地址。
网络包离开 cni0,重定向到虚拟网络接口 veth6。
网络包通过虚拟地址接口 veth6 离开 root netns。
跨主通信”的三种主流实现方法
容器“跨主通信”的三种主流实现方法:UDP、host-gw、VXLAN。
UDP和VXLAN,它们都属于隧道模式,需要封装和解封装。
纯三层网络方案,host-gw模式和Calico项目。
Host-gw模式通过在宿主机上添加一个路由规则:
<目的容器IP地址段> via <网关的IP地址> dev eth0
IP包在封装成帧发出去的时候,会使用路由表里的“下一跳”来设置目的MAC地址。这样,它就会通过二层网络到达目的宿主机。
这个三层网络方案得以正常工作的核心,是为每个容器的IP地址,找到它所对应的,“下一跳”的网关。所以说,Flannel host-gw模式必须要求集群宿主机之间是二层连通的,如果宿主机分布在了不同的VLAN里(三层连通),由于需要经过的中间的路由器不一定有相关的路由配置(出于安全考虑,公有云环境下,宿主机之间的网关,肯定不会允许用户进行干预和设置),部分节点就无法找到容器IP的“下一条”网关了,host-gw就无法工作了。
Calico项目提供的网络解决方案,与Flannel的host-gw模式几乎一样,也会在宿主机上添加一个路由规则:
<目的容器IP地址段> via <网关的IP地址> dev eth0
其中,网关的IP地址,正是目的容器所在宿主机的IP地址,而正如前面所述,这个三层网络方案得以正常工作的核心,是为每个容器的IP地址,找到它所对应的,“下一跳”的网关。区别是如何维护路由信息:
Host-gw : Flannel通过Etcd和宿主机上的flanneld来维护路由信息
Calico: 通过BGP(边界网关协议)来实现路由自治,所谓BGP,就是在大规模网络中实现节点路由信息共享的一种协议。
隧道技术(需要封装包和解包,因为需要伪装成宿主机的IP包,需要三层链通):Flannel UDP / VXLAN / Calico IPIP
三层网络(不需要封包和解封包,需要二层链通):Flannel host-gw / Calico 普通模式
Pod和Pod之外网络实体的通信。
Pod与非Pod网络的实体通信,需要经过节点上的iptables规则做源地址转换,
而此规则就是Flanneld依据命令行--ip-masq选项做的配置。
Kubelet、Container Runtime 和 CNI 插件交互
当在节点上调度 Pod 时,一启动 Pod 就会发生很多事情。这里我们仅关注与 Pod 配置网络有关的动态。一旦在节点上调度了 Pod,将配置网络并启动应用程序容器。
参考:容器式 cri 插件架构Container Runtime 与 CNI 插件的交互每个 network provider 都有一个 CNI 插件,container runtime 会调用该插件,在 Pod 启动时配置网络。使用容器化作为 container runtime,容器化 CRI 插件将调用 CNI 插件。每个 network provider 都在每个 Kubernetes 节点上安装了一个代理,以配置 Pod 网络。安装 network provider agent 后,它会随 CNI 一起配置或者在节点上创建,CRI 插件会使用它来确定要调用哪个 CNI 插件。CNI 配置文件的位置是可配置的,默认值为 /etc/cni/net.d/<config-file>。集群管理员需要在每个节点上交付 CNI 插件。CNI 插件的位置也是可配置的,默认值为 /opt/cni/bin。如果使用 containerd 作为 container runtime,则可以在 containerd config 部分下 [plugins."io.containerd.grpc.v1.cri".cni] 指定 CNI 配置和 CNI 插件的路径。本文中我们将 Flannel 作为 network provider,这里简单介绍一下 Flannel 的设置。Flanneld 是 Flannel 守护程序,通常 install-cni 作为带有初始化容器的守护程序安装在 Kubernetes 集群上。install-cni 容器创建 CNI 配置文件在每个节点上 /etc/cni/net.d/10-flannel.conflist。Flanneld 创建一个 vxlan 设备,从 apiserver 获取网络元数据,并监控 Pod 上的更新。创建 Pod 时,它将在整个集群中为所有 Pod 分配路由,这些路由允许 Pod 通过 IP 地址相互连接。Containerd CRI 插件和 CNI 插件之间的交互可以如下所示:
如上所述,kubelet 调用 Containered CRI 插件创建容器,再调用 CNI 插件为容器配置网络。Network provider CNI 插件调用其他基本 CNI 插件来配置网络。CNI 插件之间的交互如下所述。
CNI 插件之间的交互有多种 CNI 插件可帮助配置主机上容器之间的网络,本文主要讨论以下 3 个插件。
Flannel CNI 插件当使用 Flannel 作为network provider时,Containered CRI 插件使用 CNI 配置文件,调用 Flannel CNI 插件:/etc/cni/net.d/10-flannel.conflist。
pod初始化网络过程
图
创建了一个Pod后,CRI和CNI协同创建Pod所属容器,并为它们初始化网络协议栈的全过程如下:
(1)当用户在Kubernetes的Master里创建了一个Pod后,Kubelet观察到新Pod的创建,首先调用CRI(后面的runtime实现,比如dockershim、containerd等)创建Pod内的若干个容器。
(2)在这些容器里,第一个被创建的pause容器 具体逻辑是一启动就把自己永远阻塞在那里。
每个Pod内的第一个系统容器pause的作用就是占用一个Linux的network namespace。
(3)Pod内其他用户容器通过加入这个network namespace的方式共享同一个network namespace。
用户容器和pause容器之间的关系有点类似于寄居蟹和海螺。因此,Container runtime创建Pod内的用户容器时,调用的都是同一个命令:docker run--net=none。意思是只创建一个network namespace,不初始化网络协议栈。如果这个时候通过nsenter方式进入容器,会看到里面只有一个本地回环设备lo。
(4)容器的eth0是怎么创建出来的呢?答案是CNI。
CNI主要负责容器的网络设备初始化工作。Kubelet目前支持两个网络驱动,分别是Kubenet和CNI。Kubenet是一个历史产物,即将废弃,CNI有多个实现,官方自带的插件就有p2p、bridge等,这些插件负责初始化pause容器的网络设备,也就是给pause容器内的eth0分配IP等,到时候,Pod内其他容器就使用这个IP与其他容器或节点进行通信。Kubernetes主机内容器的默认组网方案是bridge。
flannel、Calico这些第三方插件解决容器之间的跨机通信问题,典型的跨机通信解决方案有bridge和overlay等。
Kubernetes网络架构综述
谈到Kubernetes的网络模型,就不能不提它著名的“单Pod单IP”模型,即每个Pod都有一个独立的IP,Pod内所有容器共享network namespace(同一个网络协议栈和IP)。 “单Pod单IP”网络模型为我们勾勒了一个Kubernetes扁平网络的蓝图,在这个网络世界里:容器是一等公民,容器之间直接通信,不需要额外的NAT,因此不存在源地址被伪装的情况;Node与容器网络直连,同样不需要额外的NAT。扁平化网络的优点在于:没有NAT带来的性能损耗,而且可追溯源地址,为后面的网络策略做铺垫,降低网络排错的难度等。 总体而言,集群内访问Pod,会经过Service;集群外访问Pod,经过的是Ingress。Service和Ingress是Kubernetes专门为服务发现而抽象出来的相关概念,后面会做详细讨论。 与CRI之于Kubernetes的runtime类似,Kubernetes使用CNI作为Pod网络配置的标准接口。需要注意的是,CNI并不支持Docker网络,也就是说,docker0网桥会被大部分CNI插件“视而不见”。 当然也有例外,Weave就是一个会处理docker0的CNI插件,具体分析请看后面章节的内容。
网络CNI
what
CNI是Kubernetes与底层网络插件之间的一个抽象层,为Kubernetes屏蔽了底层网络实现的复杂度,同时解耦了Kubernetes的具体网络插件实现。
CNI主要有两类接口:
分别是在创建容器时调用的配置网络接口:
和删除容器时调用的清理网络接口: 不论是配置网络接口还是清理网络接口,
都有两个入参,分别是网络配置和runtime配置。网络配置很好理解,runtime配置则主要是容器运行时传入的网络namespace信息。
Kubelet和CNI给出了两个默认的文件系统路径
/etc/cni/net.d用来存储CNI配置文件
/opt/cni/bin目录用来存放CNI插件的二进制文件
图
Flannel
what
flannel 是一种配置第三层网络结构的简单易用方式,该层网络专门针对 Kubernetes 设计。
flannel 在每个主机上以称为 flanneld 的单个微小二进制程序运行代理,
并负责从更大的预配置地址空间中对每个主机分配租用子网。
Flannel 直接使用 Kubernetes API 或 etcd 存储网络配置。配置包括所分配的子网及所有的辅助数据,例如主机的公共 IP 等。
flannel 支持使用多种后端机制转发数据包,例如 VXLAN、各种云集成机制等。
原文链接: https://www.infoq.cn/article/ntg12fh31hq2vkxkt48i
Flannel是CoreOS公司为Kubernetes集群设计的一个Overlay网络方案,通过实现以下两种功能,使各Node上的容器之间能够实现网络互通。
(1)为每个Node上的docker0网桥配置一个互不冲突的IP地址池。
(2)为各Node的docker0虚拟网络建立一个覆盖网络(Overlay Network),通过这个覆盖网络,将数据包原封不动地传递到目标容器中。
Flannel在每台Node上创建了一个名为flannel0的网桥,这个网桥的一端连接docker0网桥,另一端连接flanneld服务进程。
Flannel使用Kubernetes API或etcd存储网络配置数据、子网分配和其他辅助信息。对于跨主机容器之间网络数据包的转发,Flannel支持以下几种模式。
◎VxLAN:使用Linux Kernel的VxLAN功能完成VxLAN的创建和管理。
◎UDP:使用UDP完成封包和解包的操作。
◎host-gw:将主机当作网关使用,要求所有主机都在同一个局域网内,保证二层网络联通。
why
1.使集群中的不同Node主机创建的Docker容器都具有全集群唯一的虚拟IP地址。
2.建立一个覆盖网络(overlay network),通过这个覆盖网络,将数据包原封不动的传递到目标容器。
覆盖网络是建立在另一个网络之上并由其基础设施支持的虚拟网络。
覆盖网络通过将一个分组封装在另一个分组内来将网络服务与底层基础设施分离。
在将封装的数据包转发到端点后,将其解封装。
3.创建一个新的虚拟网卡flannel0接收docker网桥的数据,通过维护路由表,对接收到的数据进行封包和转发(vxlan)。
4.etcd保证了所有node上flanned所看到的配置是一致的。同时每个node上的flanned监听etcd上的数据变化,实时感知集群中node的变化。
how
跨主通信基本原理
1
各组件功能
1.Cni0:网桥设备,每创建一个pod都会创建一对 veth pair。其中一端是pod中的eth0,另一端是Cni0网桥中的端口(网卡)。Pod中从网卡eth0发出的流量都会发送到Cni0网桥设备的端口(网卡)上。Cni0 设备获得的ip地址是该节点分配到的网段的第一个地址。
2.Flannel.1: overlay网络的设备,用来进行 vxlan 报文的处理(封包和解包)。不同node之间的pod数据流量都从overlay设备以隧道的形式发送到对端。
3.Flanneld:flannel在每个主机中运行flanneld作为agent,它会为所在主机从集群的网络地址空间中,获取一个小的网段subnet,本主机内所有容器的IP地址都将从中分配。同时Flanneld监听K8s集群数据库,为flannel.1设备提供封装数据时必要的mac,ip等网络数据信息。
calico
概念
Calico 创建和管理一个扁平的三层网络(不需要overlay),每个容器会分配一个可路由的ip。
由于通信时不需要解包和封包,网络性能损耗小,易于排查,且易于水平扩展。
Calico架构
图
tu
Etcd:负责存储网络信息
BGP client:负责将Felix配置的路由信息分发到其他节点
Felix:Calico Agent,每个节点都需要运行,主要负责配置路由、配置ACLs、报告状态
BGP Route Reflector:大规模部署时需要用到,作为BGP client的中心连接点,可以避免每个节点互联
组件
图
calico包括如下重要组件:Felix,etcd,BGP Client,BGP Route Reflector。下面分别说明一下这些组件。
Felix:主要负责路由配置以及ACLS规则的配置以及下发,它存在在每个node节点上。
etcd:分布式键值存储,主要负责网络元数据一致性,确保Calico网络状态的准确性,可以与kubernetes共用;
BGPClient(BIRD), 主要负责把 Felix写入 kernel的路由信息分发到当前 Calico网络,确保 workload间的通信的有效性;
BGPRoute Reflector(BIRD), 大规模部署时使用,摒弃所有节点互联的mesh模式,通过一个或者多个 BGPRoute Reflector 来完成集中式的路由分发;
原文链接:https://blog.csdn.net/virtualization_/article/details/108465355
跨主机通信
图
集群边界路由器Ingress的管理
集群DNS域名服务管理
网络策略
Kubernetes底层网络是“全连通”的,即在同一集群内运行的所有Pod都可以自由通信。但是也支持用户根据实际需求以不同方式限制集群内Pod的连接。 例如,我们需要实现图3-22中的需求,即只允许访问default namespace的Label是app=web的Pod,default namespace的其他Pod都不允许外部访问。这个隔离需求在多租户的场景下十分普遍。Kubernetes的解决方案是Network Policy,即网络策略。
Kubernetes网络策略的知识点。
Egress表示出站流量,即Pod作为客户端访问外部服务,Pod地址作为源地址。策略可以定义目的地址和目的端口,可以根据ports和to定义规则。ports字段用来指定目的端口和协议。to(目的地址)分为IP地址段、Pod selector和Kubernetes namespace selector;
Ingress表示入站流量,Pod地址和服务作为服务端(目的地址),提供外部访问。与Egress类似,策略可以定义源地址和端口,可以根据ports和from定义规则。ports字段同样用来指定目的端口和协议。from(源地址)分为IP地址段、Pod selector和Kubernetes namespace selector;
podSelector用于指定网络策略在哪些Pod上生效,用户可以配置单个Pod或者一组Pod。可以定义单方向。空podSelector选择命名空间中的Pod。podSelector定义Network Policy的限制范围,就是规则应用到哪个Pod上。如上所示,podSelector:{}留空就是定义对Kubernetes当前namespace下的所有Pod生效。没有定义白名单的话,默认就是Deny ALL(拒绝所有);
policyTypes:指定策略类型,包括Ingress和Egress,不指定默认就是Ingress。默认情况下,一个policyTypes的值一定会包含Ingress。 说到匹配规则,namespaceSelector和podSelector是彼此独立的两个单独的匹配规则。你可以使用ipBlock字段允许来自或者到达特定IP地址段范围的Ingress或Egress流量。 最后,介绍Kubernetes网络策略与一些安全组(例如Neutron的安全组)的区别。一般来说,安全组的规则记录在上层网关,而不是每一个节点上。而Kubernetes网络是应用到每个节点上的,实现Network Policy的agent需要在每个节点上都List/Watch Kubernetes的Network Policy、namespace、Pod等资源对象,因此在实现上可能会有较大的性能影响。Network Policy agent一般通过Kubernetes DaemonSet实现每个节点上都有一个部署。
集群网络安全
集群的安全性保证
(1)保证容器与容器之间、容器与主机之间隔离,限制容器对其他容器和主机的消极影响。
(2)保证组件、用户及容器应用程序都是最小权限,限制它们的权限范围。
(3)保证集群的敏感数据的传输和存储安全。
安全机制
其中包括细粒度控制Pod 安全上下文(Pod Security Context)、
API Server 的认证、授权、审计和准入控制、数据的加密机制等。数据传输和存储的安全。
数据加密传输
Kubernetes 及其支持组件都应使用基于SSL/TLS 的HTTPS 协议来确保传输的安全性,
特别是客户与API Server、API Server 与etcd、API Server 与kubelet、etcd 成员之间这种跨节点的数据通信。
如果使用HTTPS 协议,那么在组建Kubernetes 集群时,必不可少地会涉及各个组件的数字证书的制作或签发,以及证书的参数配置。
ssl握手
图
API Server 支持在远端的etcd 上针对不同资源对象进行加密。参数“--encryption-provider-config” 用于配置不同资源对象在etcd 加密数据。加密配置示例代码如下:
集群的安全通信
图
在Kubernetes 体系中,有两套分别是etcd CA 和Kubernetes CA 颁发的证书。
etcd CA
其中etcd 客户端证书(简称为EC)、etcd 服务端证书(简称为ES)和etcd 对等证书(简称为P)都是由etcd CA 颁发和验证的,
分别用于API Server 和etcd 之间、etcd成员之间的加密通信。
在API Server 和etcd 所有实例处都应有一份etcd CA,也就是etcd 根证书,用于验证对方证书的有效性。
至于API Server 和etcd 之间的通信,都是由API Server 发起的,因此,API Server 处配置了etcd 客户端证书,etcd 处配置了etcd 服务端证书。
Kubernetes CA
用于kubelet 与API Server 之间双向主动通信。
客户端证书(即API Server 客户端证书,简称为AC)
kubelet 客户端证书(简称为KC)
服务端证书(即API Server 服务端证书,简称为AS)
kubelet 服务端证书(简称为KS)
当kubelet 主动连接API Server 时,API Server 作为服务端,应使用服务端证书AS,kubelet 作为客户端,应使用客户端证书AC;
当API Server 主动连接kubelet 时,kubelet作为服务端,应使用服务端证书KS,API Servr 作为客户端,应使用客户端证书KC。
同样,为了验证双方证书的有效性,在所有kubelet 和API Server 实例处都有一份Kubernetes CA,也就是Kubernetes 的根证书。
为什么这里至少需要两套CA(即两个根证书)来颁发证书呢
这是为了防止kubelet直接与etcd 通信,而绕过API Server 内置的所有授权机制
如果使用同一套CA,那么kubelet客户端证书能直接在etcd 服务端验证过
这里分别针对API Server 连接etcd、etcd 成员连接etcd 成员、kubelet 连接API Server和API Server 连接kubelet 这四个连接请求,
下面来看一下API Server、etcd、kubelet 三者与证书相关的配置参数。
1.API Server 连接etcd
即API Server 为客户端,etcd 为服务端。API Server 相关的配置参数如下:
2.etcd 成员连接etcd 成员 即etcd 既作为客户端又作为服务端。
因此在生成Peer 证书时,应写明此证书需有Client Auth 和Server Auth 两种用途
3.kubelet 连接API Server
kubelet 使用Kubeconfig 内的信息与API Server 建立连接,而Kubeconfig 中包含了关于证书的配置信息。事实上集群中的其他组件(例如Scheduler 和Controller Manager)也都是通过这种方式和API Server 建立连接的。
4.API Server 连接kubelet 即API Server 作为客户端,kubelet 作为服务端
数据加密存储,
相关资料
【掌阅】Kubernetes 网络权威指南
图解 Kubernetes Pod 如何获取 IP 地址
https://xie.infoq.cn/article/9179d85921467d5deca05b35b
https://www.infoq.cn/article/tiZgVs6BRkiCtBsVl0Ir
https://www.infoq.cn/article/6mdfWWGHzAdihiq9lDST
详解 Kubernetes 容器网络技能
玩转Kubernetes网络
https://www.bookstack.cn/read/sdn-handbook/container-index.md
网络基础
IPVS【掌阅】Kubernetes 网络权威指南
既然Kube-proxy已经有了iptables模式,为什么Kubernetes还选择IPVS呢?随着Kubernetes集群规模的增长,其资源的可扩展性变得越来越重要,特别是对那些运行大型工作负载的企业,其服务的可扩展性尤其重要。要知道,iptables难以扩展到支持成千上万的服务,它纯粹是为防火墙而设计的,并且底层路由表的实现是链表,对路由规则的增删改查操作都涉及遍历一次链表。 尽管Kubernetes在1.6版本中已经支持5000个节点,但使用iptables模式的Kube-proxy实际上是将集群扩展到5000个节点的最大瓶颈。一个例子是,如果我们有1000个服务并且每个服务有10个后端Pod,将在每个工作节点上至少产生10000×N(N≥4)个iptable记录,这可能使内核非常繁忙地处理每次iptables规则的刷新。 另外,使用IPVS做集群内服务的负载均衡可以解决iptables带来的性能问题。IPVS专门用于负载均衡,并使用更高效的数据结构(散列表),允许几乎无限的规模扩张。 1.IPVS的工作原理 IPVS是Linux内核实现的四层负载均衡,是LVS负载均衡模块的实现。上文也提到,IPVS基于netfilter的散列表,相对于同样基于netfilter框架的iptables有更好的性能表现和可扩展性,具体见下文的实测对比数据。 IPVS支持TCP、UDP、SCTP、IPv4、IPv6等协议,也支持多种负载均衡策略,例如rr、wrr、lc、wlc、sh、dh、lblc等。IPVS通过persistent connection调度算法原生支持会话保持功能。
网络地址转换(NAT,Network Address Translation)
是一种在IP数据包通过路由器或防火墙时重写来源IP地址或目的IP地址的技术。这种技术被普遍使用在有多台主机通过一个公有IP地址访问外部网络的私有网络中,NAT也可以被称作IP伪装(IP Masquerading),可以分为目的地址转换(DNAT)和源地址转换(SNAT)两类。
Linux中的NAT功能一般通过iptables实现。
iptables是基于Linux内核的功能强大的防火墙。
iptables/netfilter在2001年加入到2.4内核中,netfilter作为iptables内核底层的实现框架而存在, netfilter提供了一整套对hook函数管理的机制,可以在数据包流经的5处关键地方(Hook点),分别是PREROUTING(路由前)、INPUT(数据包入口)、OUTPUT(数据包出口)、FORWARD(数据包转发)、POSTROUTING(路由后),写入一定的规则对经过的数据包进行处理,规则一般的定义为“如果数据包头符合这样的条件,就这样处理数据包”。
iptables/netfilter是按照规则来工作的,这些规则分别指定了源地址、目的地址、传输协议(如TCP、UDP、CMP)和服务类型(如HTP、FP和SMTP)等。数据包与规则匹配时,iptables 就根据规则定义的方法处理这些数据包,比如放行、拒绝和丢弃等。配置防火墙的主要工作就是添加、修改和删除这些规则。 在每个关键点上,有很多已经按照优先级预先注册了的回调函数进行埋伏,设置的这些规则,就形成了一条链。INPUT规则链匹配目的地址是本机IP地址的数据报文,OUTPUT规则链匹配由本地进程发出的数据报文,FORWARD规则链匹配流经本机的数据报文,PREROUTING 规则链用来实现目的地址转换 DNAT,POSTROUTING 规则链可以用来实现源地址转换 SNAT。它们的工作流程如图2-11所示。 图2-11 iptables/netfilter的工作流程 防火墙为了达到“防火”的目的,就需要在内核中设置关卡,所有进出的报文都要通过这些关卡。经过检查后,符合放行条件的才能放行,符合阻拦条件的则需要被阻止。而这些关卡就是所谓的规则链。 每个“链”上都放置了一串规则,但是这些规则有些很相似,例如 A 类规则都是对IP地址或者端囗的过滤,B类规则是修改报文。此时能把实现相同功能的规则放在一起组成“表”。如此一来,不同功能的规则,可以放置在不同的表中进行管理。 如图2-12所示,iptables主要包含了FILTER、NAT和MANGLE三张常用表,分别负责数据包的过滤、网络地址转换及数据包内容的修改。
network namespace
它在Linux内核2.6版本引入,
作用是隔离Linux系统的设备,以及IP地址、端口、路由表、防火墙规则等网络资源。
因此,每个网络namespace里都有自己的网络设备(如IP地址、路由表、端口范围、/proc/net目录等)
从网络的角度看,network namespace使得容器非常有用,
一个直观的例子就是:由于每个容器都有自己的(虚拟)网络设备,并且容器里的进程可以放心地绑定在端口上而不必担心冲突,这就使得在一个主机上同时运行多个监听80端口的Web服务器变为可能,
每个新的Network Namespace都默认有一个本地回环LO接口,此外,所有的其他网络设备,包括物理/虚拟网络接口、网桥等,只能属于一个Network Namespace,每个Socket也只能属于一个Network Namespace。当新的network namespace被创建时,LO接口默认是关闭的,需要自己手动启动。 创建 Network Namespace 也非常简单,使用 ip netns add 后面跟着要创建的Namespace 名称,如果相同名字的 Namespace 已经存在,会产生“Cannot create namespace”的错误。 ip netns命令创建的Network Namespace会出现在/var/run/netns/目录下,如果需要管理其他不是ip netns创建的Network Namespace,只要在这个目录下创建一个指向对应 Network Namespace 文件的链接就可以。
有了不同的Network Namespace后,也就有了网络的隔离,但如果它们之间没有办法通信,也没有实际用处。要把两个网络连接起来,Linux提供了VETH pair。如前所述,可以把VETH pair当作双向的管道,从一端发送的网络数据,可以直接被另外一端接收到,也可以想象成两个 Namespace 直接通过一个特殊的虚拟网卡连接起来,可以直接通信。 可以使用ip link add type veth 创建一对VETH pair,系统自动生成VETH0和VETH1两个网络接口,如果需要指定它们的名字,则可以使用ip link add vethfoo type veth peer name vethbar,此时创建出来的两个名字就是vethfoo和vethbar。需要记住的是VETH pair无法单独存在,删除其中一个,另一个也会自动消失。 然后,可以把这对VETH pair分别放到创建的两个Namespace里,可以使用ip link set DEV netns NAME来实现。 如图2-8所示,创建两个Network Namespace,分别命名为net0与net1,同时创建了 VETH pair 对 veth0与 veth1,将它们分别加入 net0与 net1,将两个 Network Namespace连接起来。 图2-8 使用VETH pair连接两个Network Namespace 虽然 VETH pair 可以实现两个 Network Namespace 之间的通信,但是当多个Namespace需要通信的时候,就无能为力了。涉及多个网络设备之间的通信,首先想到的是交换机和路由器。因为这里要考虑的只是同一个网络,所以只用到交换机的功能,也就是前面所述的网
出自 :Linux开源网络全栈详解
Linux bridge
就是Linux系统中的网桥,但是Linux bridge的行为更像是一台虚拟的网络交换机,任意的真实物理设备(例如eth0)和虚拟设备(例如,前面讲到的veth pair和后面即将介绍的tap设备)都可以连接到Linux bridge上。需要注意的是,Linux bridge不能跨机连接网络设备。 Linux bridge与Linux上其他网络设备的区别在于,普通的网络设备只有两端,从一端进来的数据会从另一端出去。例如,物理网卡从外面网络中收到的数据会转发给内核协议栈,而从协议栈过来的数据会转发到外面的物理网络中。Linux bridge则有多个端口,数据可以从任何端口进来,进来之后从哪个口出去取决于目的MAC地址,原理和物理交换机差不多。
iptables的底层实现是netfilter
作为一个通用的、抽象的框架,提供一整套hook函数的管理机制,使得数据包过滤、包处理(设置标志位、修改TTL等)、地址伪装、网络地址转换、透明代理、访问控制、基于协议类型的连接跟踪,甚至带宽限速等功能成为可能。
netfilter的架构就是在整个网络流程的若干位置放置一些钩子,并在每个钩子上挂载一些处理函数进行处理。
IP层的5个钩子点的位置,对应iptables就是5条内置链,分别是PREROUTING、POSTROUTING、INPUT、OUTPUT和FORWARD。
当网卡上收到一个包送达协议栈时,最先经过的netfilter钩子是PREROUTING,如果确实有用户埋了这个钩子函数,那么内核将在这里对数据包进行目的地址转换(DNAT)。不管在PREROUTING有没有做过DNAT,内核都会通过查本地路由表决定这个数据包是发送给本地进程还是发送给其他机器。如果是发送给其他机器(或其他network namespace),就相当于把本地当作路由器,就会经过netfilter的FORWARD钩子,用户可以在此处设置包过滤钩子函数,例如iptables的reject函数。
所有马上要发到协议栈外的包都会经过POSTROUTING钩子,用户可以在这里埋下源地址转换(SNAT)或源地址伪装(Masquerade,简称Masq)的钩子函数。如果经过上面的路由决策,内核决定把包发给本地进程,就会经过INPUT钩子。本地进程收到数据包后,回程报文会先经过OUTPUT钩子,然后经过一次路由决策(例如,决定从机器的哪块网卡出去,下一跳地址是多少等),最后出协议栈的网络包同样会经过POSTROUTING钩子。
netfilter是Linux内核网络模块的一个经典框架,毫不夸张地说,整个Linux系统的网络安全大厦都构建在netfilter之上,构建在netfilter钩子之上的网络安全策略和连接跟踪的用户态程序就有ebtables、arptables、(IPv6版本的)ip6tables、iptables、iptables-nftables(iptables的改进版本)、conntrack(连接跟踪)等。Kubernetes网络之间用到的工具就有ebtables、iptables/ip6tables和conntrack,其中iptables是核心。
我们常说的iptables 5X5,、即5张表(table)和5条链(chain)。
5条链即iptables的5条内置链,对应上文介绍的netfilter的5个钩子。
这5条链分别是:
·INPUT链:一般用于处理输入本地进程的数据包;
·OUTPUT链:一般用于处理本地进程的输出数据包;
·FORWARD链:一般用于处理转发到其他机器/network namespace的数据包;
·PREROUTING链:可以在此处进行DNAT;
·POSTROUTING链:可以在此处进行SNAT。
除了系统预定义的5条iptables链,用户还可以在表中定义自己的链,我们将通过例子详细说明。 5张表如下所示。
·filter表:用于控制到达某条链上的数据包是继续放行、直接丢弃(drop)或拒绝(reject);
·nat表:用于修改数据包的源和目的地址;
·mangle表:用于修改数据包的IP头信息;
·raw表:iptables是有状态的,即iptables对数据包有连接追踪(connection tracking)机制,而raw是用来去除这种追踪机制的;
·security表:最不常用的表(通常,我们说iptables只有4张表,security表是新加入的特性),用于在数据包上应用SELinux。 这5张表的优先级从高到低是:raw、mangle、nat、filter、security。需要注意的是,iptables不支持用户自定义表。 不是每个链上都能挂表,iptables表与链的对应关系如表1-1所示。 表1-1 iptables表与链的对应关系 所以,如果我们扩充图1-13,给它加上iptables的5张表,那么一个网络包经过iptables的处理路径如图1-16所示。 图1-16 一个网络包经过iptables的处理路径 可能有读者会问,iptables的链(chain)的概念还比较好理解,就是对应netfilter的5处钩子,而iptables的表(table)的具体作用是什么呢?而且上文说的“不是每个链上都能挂表”这句话的表述似乎也很奇怪
iptables
在Docker和Kubernetes网络中应用甚广。例如,Docker容器和宿主机的端口映射、Kubernetes Service的默认模式、CNI的portmap插件、Kubernetes网络策略等都是通过iptables实现的
iptables的表是来分类管理iptables规则(rule)的,系统所有的iptables规则都被划分到不同的表集合中。上文也提到了,
filter表中会有过滤数据包的规则,nat表中会有做地址转换的规则。因此,iptables的规则就是挂在netfilter钩子上的函数,用来修改数据包的内容或过滤数据包,iptables的表就是所有规则的5个逻辑集合
iptables的规则是用户真正要书写的规则。一般情况下,一条iptables规则包含两部分信息:匹配条件和动作。匹配条件很好理解,即匹配数据包被这条iptables规则“捕获”的条件,例如协议类型、源IP、目的IP、源端口、目的端口、连接状态等。每条iptables规则允许多个匹配条件任意组合,从而实现多条件的匹配,多条件之间是逻辑与(&&)关系。
数据包匹配后该怎么办,常见的动作有下面几个:
·DROP:直接将数据包丢弃,不再进行后续的处理。应用场景是不让某个数据源意识到你的系统的存在,可以用来模拟宕机;
·REJECT:给客户端返回一个connection refused或destination unreachable报文。应用场景是不让某个数据源访问你的系统,善意地告诉他:我这里没有你要的服务内容;
·QUEUE:将数据包放入用户空间的队列,供用户空间的程序处理;
·RETURN:跳出当前链,该链里后续的规则不再执行;
·ACCEPT:同意数据包通过,继续执行后续的规则;
·JUMP:跳转到其他用户自定义的链继续执行。
值得一提的是,用户自定义链中的规则和系统预定义的5条链里的规则没有区别。由于自定义的链没有与netfilter里的钩子进行绑定,所以它不会自动触发,只能从其他链的规则中跳转过来,这也是JUMP动作存在的意义。 在初步认识了iptables的表、链和规则三个最重要的概念后,
接下来将以iptables在Kubernetes网络中的使用为背景,简单介绍iptables的常见用法
1.查看所有iptables规则 Kubernetes一个节点上的iptables规则输出如下。
严格地说,输出是iptables的filter表的所有规则。使用iptables命令,必须指定针对哪个表进行操作,默认是filter表。因此,如果想输出系统中nat表的所有iptables规则,可以使用如下命令: 使用-n选项将以数字形式列出信息,即将域名解析成IP地址。想输出更详细的信息,例如,经过某条rule处理的数据包字节、进出网口等信息,可以使用-v选项。 从上面的输出结果可以看出,filter表上挂了5条链,分别是INPUT、FORWARD和OUTPUT这三条系统内置链,以及KUBE-FIREWALL和KUBE-FORWARD这两条用户自定义链。iptables的内置链都有默认规则,例如在我们的例子中,INPUT、FORWARD和OUTPUT的默认规则是ACCEPT,即全部放行。用户自己定义的链后面都有一个引用计数,在我们的例子中KUBE-FIREWALL和KUBE-FORWARD都有一个引用,它们分别在INPUT和FORWARD中被引用
iptables的每条链下面的规则处理顺序是从上到下逐条遍历的,除非中途碰到DROP,REJECT,RETURN这些内置动作。如果iptables规则前面是自定义链,则意味着这条规则的动作是JUMP,即跳到这条自定义链遍历其下的所有规则,然后跳回来遍历原来那条链后面的规则。以上过程如图1-17所示。
图1-17 iptables遍历规则 在我们的例子中,iptables规则的遍历顺序应该是rule 1,1→rule 1,2→rule 2,1→rule 2,2→rule 2,3→rule 1,3。
3.配置防火墙规则策略 防火墙策略一般分为通和不通两种。如果默认策略是“全通”,例如上文的policy ACCEPT,就要定义一些策略来封堵;反之,如果默认策略是“全不通”,例如上文的policy DROP,就要定义一些策略来解封。如果是做访问控制列表(ACL),即俗称的白名单,则用解封策略更常用。
我们将用几个实际的例子来说明。
1)配置允许SSH连接
简单分析这条命令,-A的意思是以追加(Append)的方式增加这条规则。-A INPUT表示这条规则挂在INPUT链上。-s 10.20.30.40/24表示允许源(source)地址是10.20.30.40/24这个网段的连接。-p tcp表示允许TCP(protocol)包通过。--dport 22的意思是允许访问的目的端口(destination port)为22,即SSH端口。-j ACCEPT表示接受这样的连接。综上所述,这条iptables规则的意思是允许源地址是10.20.30.40/24这个网段的包发到本地TCP 22端口。除了按追加的方式添加规则,还可以使用iptables-I[chain][number]将规则插入(Insert)链的指定位置。如果不指定number,则插到链的第一条处。
2)阻止来自某个IP/网段的所有连接
如果要阻止10.10.10.10上所有的包,则可以使用以下命令:
也可以使用-j REJECT,这样就会发一个连接拒绝的回程报文,客户端收到后立刻结束。不像-j DROP那样不返回任何响应,客户端只能一直等待直到请求超时。如果要屏蔽一个网段,例如10.10.10.0/24,则可以使用-s 10.10.10.0/24或10.10.10.0/255.255.255.0。如果要“闭关锁国”,即屏蔽所有的外来包,则可以使用-s 0.0.0.0/0。
3)封锁端口
要阻止从本地1234端口建立对外连接,可以使用以下命令:
注意,我们要在OUTPUT链上挂规则是因为我们的需求是屏蔽本地进程对外的连接。如果我们要阻止外部连接访问本地1234端口,就需要在INPUT链上挂规则,命令如下: 感到困惑的读者可以复习关于netfilter的内容。
4)端口转发 有时,我们要把服务器的某个端口流量转发给另一个端口。
例如,我们对外声称Web服务在80端口上运行,但由于种种原因80端口被人占了,实际的Web服务监听在8080端口上。为了让外部客户端能无感知地依旧访问80端口,可以使用以下命令实现端口转发:
以上命令中-p tcp--dport 80-j REDIRECT--to-port 8080的意思是发到TCP 80端口的流量转发(REDIRECT)给8080端口,iptables并不会修改任何IP地址或者协议。需要注意的是,我们修改了目的端口,因此该规则应该发生在nat表上的PREROUTING链上。至于-i eth0则表示匹配eth0网卡上接收到的包。
5)禁用PING 大部分的公有云默认都是屏蔽ICMP的,即禁止ping报文。
以上命令的-p icmp-j DROP即表示匹配ICMP报文,然后丢弃。
6)删除规则 最暴力地清除当前所有的规则命令(请慎用): 要清空特定的表可以使用-t参数进行指定,例如清除nat表所有的规则如下: 删除规则的最直接的需求是解封某条防火墙策略。因此,笔者建议使用iptables的-D参数: -D表示从链中删除一条或多条指定的规则,后面跟的就是要删除的规则。 当某条链上的规则被全部清除变成空链后,可以使用-X参数删除: 以上命令删除FOO这条用户自定义的空链,但需要注意的是系统内置链无法删除。 7)自定义链 说了这么多自定义链,具体该怎么创建它呢?请看以下命令: 如上所示,我们在filter表(因为未指定表,所以可以使用-t参数指定)创建了一条用户自定义的链BAR。
4.DNAT DNAT根据指定条件修改数据包的目标IP地址和目标端口。DNAT的原理和我们上文讨论的端口转发原理差不多,差别是端口转发不修改IP地址。使用iptables做目的地址转换的一个典型例子如下: -j DNAT表示目的地址转换,-d 1.2.3.4-p tcp--dport 80表示匹配的包条件是访问目的地址和端口为1.2.3.4:80的TCP包,--to-destination表示将该包的目的地址和端口修改成10.20.30.40:8080。同样,DNAT不修改协议。如果要匹配网卡,可以用-i eth0指定收到包的网卡(i是input的缩写)。需要注意的是,DNAT只发生在nat表的PREROUTING链,这也是我们要指定收到包的网卡而不是发出包的网卡的原因。 需要注意的是,当涉及转发的目的IP地址是外机时,需要确保启用ip forward功能,即把Linux当交换机用,命令如下: 5.SNAT/网络地址欺骗 神秘的网络地址欺骗其实是SNAT的一种。SNAT根据指定条件修改数据包的源IP地址,即DNAT的逆操作。与DNAT的限制类似,SNAT策略只能发生在nat表的POSTROUTING链。具体命令如下: -j SNAT表示源地址转换,-s 192.168.1.12表示匹配的包源地址是192.168.1.12,--to-source表示将该包的源地址修改成10.172.16.1。与DNAT类似,我们也可以匹配发包的网卡,例如-o eth0(o是output的缩写)。 至于网络地址伪装,其实就是一种特殊的源地址转换,报文从哪个网卡出就用该网卡上的IP地址替换该报文的源地址,具体用哪个IP地址由内核决定。下面这条规则的意思是:源地址是10.8.0.0/16的报文都做一次Masq。 与SNAT类似,如果要控制被替换的源地址,则可以指定匹配从哪块网卡发出报文。例如:-o eth0选项指定报文从eth0出去并使用eth0的IP地址做源地址伪装。 6.保存与恢复 上述方法对iptables规则做出的改变是临时的,重启机器后就会丢失。如果想永久保存这些更改,则需要运行以下命令: iptables-save在保存系统所有iptables规则的同时,也会在标准输出打印这些规则,如有需要可以重定向到一个文件,例如: 后续,我们可以使用iptables-restore命令还原iptables-save命令备份的iptables配置,原理就是逐条执行文件里的iptables规则,如下所示:
conntrack
所有由netfilter框架实现的连接跟踪模块称作conntrack(connection tracking)。在DNAT的过程中,conntrack使用状态机启动并跟踪连接状态。为什么需要记录连接的状态呢?因为iptables/IPVS做了DNAT后需要记住数据包的目的地址被改成了什么,并且在返回数据包时将目的地址改回来。除此之外,iptables还可以依靠conntrack的状态(cstate)决定数据包的命运。其中最主要的4个conntrack状态如下: (1)NEW:匹配连接的第一个包,这表示conntrack对该数据包的信息一无所知。通常发生在收到SYN数据包时。 (2)ESTABLISHED:匹配连接的响应包及后续的包,conntrack知道该数据包属于一个已建立的连接。通常发生在TCP握手完成之后。 (3)RELATED:RELATED状态有点复杂,当一个连接与另一个ESTABLISHED状态的连接有关时,这个连接就被认为是RELATED。这意味着,一个连接要想成为RELATED,必须先有一条已经是ESTABLISHED状态的连接存在。这个ESTABLISHED状态连接再产生一个主连接之外的新连接,这个新连接就是RELATED状态。 (4)INVALID:匹配那些无法识别或没有任何状态的数据包,conntrack不知道如何处理它。该状态在分析Kubernetes故障的过程中起着重要的作用。
VXLAN的报文就是MAC in UDP
,即在三层网络的基础上构建一个虚拟的二层网络。为什么这么说呢?VXLAN的封包格式显示原来的二层以太网帧(包含MAC头部、IP头部和传输层头部的报文),被放在VXLAN包头里进行封装,再套到标准的UDP头部(UDP头部、IP头部和MAC头部),用来在底层网络上传输报文。 可以看出,VXLAN报文比原始报文多出了50个字节,包括8个字节的VXLAN协议相关的部分,8个字节的UDP头部,20个字节的IP头部和14个字节的MAC头部。这降低了网络链路传输有效数据的比例,特别是对小包。 需要注意的是,UDP目的端口是接收方VTEP设备使用的端口,IANA(Internet Assigned Numbers Authority,互联网号码分配局)分配了4789作为VXLAN的目的UDP端口。 VXLAN的配置管理使用iproute2包,这个工具是和VXLAN一起合入内核的,我们常用的ip命令就是iproute2的客户端工具。VXLAN要求Linux内核版本在3.7以上,最好为3.9以上,所以在一些旧版本的Linux上无法使用基于VXLAN的封包技术。 1.7.3 VXLAN组网必要信息 总的来说,VXLAN报文的转发过程就是:原始报文经过VTEP,被Linux内核添加上VXLAN包头及外层的UDP头部,再发送出去,对端VTEP接收到VXLAN报文后拆除外层UDP头部,并根据VXLAN头部的VNI把原始报文发送到目的服务器。 以上过程看似并不复杂,但前提是通信双方已经知道所有通信信息。第一次通信之前有以下问题待解决: ·哪些VTEP需要加到一个相同的VNI组? ·发送方如何知道对方的MAC地址? ·如何知道目的服务器在哪个节点上? 第一个问题最好回答,VTEP通常由网络管理员进行配置。另外两个问题可以归结为同一个问题:VXLAN网络的通信双方如何感知彼此并选择正确的路径传输报文?要回答这两个问题,还得回到VXLAN协议报文上,看看一个完整的VXLAN报文需要哪些信息。 ·内层报文:通信双方的IP地址已经明确,需要VXLAN填充的是对方的MAC地址,VXLAN需要一个机制来实现ARP的功能; ·VXLAN头部:只需要知道VNI。它一般是直接配置在VTEP上的,即要么是提前规划的,要么是根据内部报文自动生成的; ·UDP头部:最重要的是源地址和目的地址的端口,源地址端口是由系统生成并管理的,目的端口一般固定为IANA分配的4789端口; ·IP头部:IP头部关心的是对端VTEP的IP地址,源地址可以用很简单的方式确定,目的地址是虚拟机所在地址宿主机VTEP的IP地址,需要由某种方式来确定; ·MAC头部:确定了VTEP的IP地址,MAC地址可以通过经典的ARP方式获取,毕竟VTEP在同一个三层网络内。 总结一下,一个VXLAN报文需要确定两个地址信息:内层报文(对应目的虚拟机/容器)的MAC地址和外层报文(对应目的虚拟机/容器所在宿主机上的VTEP)IP地址。如果VNI也是动态感知的,那么VXLAN一共需要知道三个信息:内部MAC、VTEP IP和VNI。 一般有两种方式获得以上VXLAN网络的必要信息:多播和控制中心。多播的概念是同一个VXLAN网络的VTEP加入同一个多播网络。如果需要知道以上信息,就在组内发送多播来查询。控制中心的概念是在某个集中式的地方保存所有虚拟机的上述信息,自动告知VTEP它需要的信息即可。
存储资源管理
Volume类型
(1)临时目录(随着Pod的销毁而销毁)
◎emptyDir
emptyDir 设计的初衷主要是给应用充当缓存空间,或者存储中间数据,用于快速恢复。
因为emptyDir 的空间位于系统根盘,被所有容器共享,所以在磁盘的使用率较高时会触发Pod 的eviction 操作,从而影响业务的稳定。
“卷” 最初是空的。当Pod 从节点上删除时,emptyDir 卷中的数据也会被永久删除。
但当Pod 的容器因为某些原因退出再重启时,emptyDir 卷内的数据并不会丢失。
默认情况下,emptyDir 卷存储在支持该节点所使用的存储介质上,可以是本地磁盘或网络存储。
emptyDir 也可以通过将 emptyDir.medium 字段设置为 “Memory” 来通知Kubernetes 为容器安装tmpfs,此时数据被存储在内存中,速度相对于本地存储和网络存储快很多。但是在节点重启的时候,内存数据会被清除;
使用tmpfs 的内存也会计入容器的使用内存总量中,受系统的CGroup限制。
(2)配置类(将配置以Volume的形式挂载到Pod内)
◎ConfigMap:将保存在ConfigMap资源对象中的配置文件信息挂载到容器内的某个目录下。
◎Secret:将保存在Secret资源对象中的密码密钥等信息挂载到容器内的某个文件中。
◎downwardAPI:将downward API的数据以环境变量或文件的形式注入容器中。
◎gitRepo:将某Git代码库挂载到容器内的某个目录下。
(3)本地存储类
◎hostPath:将宿主机的目录或文件挂载到容器内进行使用。
常见的半持久化存储主要是hostPath 卷。hostPath 卷能将主机节点文件系统上的文件或目录挂载到指定的Pod 中。对普通用户而言一般不需要这样的卷,但是对很多需要获取节点系统信息的Pod 而言,却是非常必要的。
hostPath 的用法举例如下:
某个Pod 需要获取节点上所有Pod 的log,可以通过hostPath 访问所有Pod 的stdout输出存储目录,例如/var/log/pods 路径。
某个Pod 需要统计系统相关的信息,可以通过hostPath 访问系统的/proc 目录。 使用hostPath 的时候,除设置必需的path 属性外,用户还可以有选择性地为 hostPath 卷指定类型,支持类型包含目录、字符设备、块设备等。
另外,使用hostPath 卷需要注意如下几点:
使用同一个目录的Pod 可能会由于调度到不同的节点,导致目录中的内容有所不同。
Kubernetes 在调度时无法顾及由 hostPath 使用的资源。
Pod 被删除后,如果没有特别处理,那么hostPath 上写的数据会遗留到节点上,占用磁盘空间。
◎local:Kubernetes从v1.9版本引入,将本地存储以PV的形式提供给容器使用,并能够实现存储空间的管理。
(4)共享存储类
◎PV(Persistent Volume):将共享存储定义为一种“持久存储卷”,可以被多个容器应用共享使用。
◎PVC(Persistent Volume Claim):用户对存储资源的一次“申请”,PVC申请的对象是PV,一旦申请成功,应用就能够像使用本地目录一样使用共享存储了。
共享存储主要用于多个应用都能够使用的存储资源
例如 NFS 存储、光纤存储、GlusterFS共享文件系统等
在Kubernetes系统中通过PV/StorageClass和PVC来完成定义,并通过volumeMount挂载到容器的目录或文件进行使用。
PV可以被看作可用的存储资源,PVC则是对存储资源的需求,PV和PVC的相互关系遵循如图2-25所示的生命周期管理。 图2-25
Kubernetes的共享存储供应模式包括静态模式(Static)和动态模式(Dynamic),资源供应的结果就是创建好的PV。
◎ 静态模式:集群管理员手工创建许多PV,在定义PV时需要设置后端存储的特性。
◎ 动态模式:集群管理员无须手工创建PV,而是通过对StorageClass的设置对后端存储进行描述,标记为某种“类型(Class)”。
此时要求PVC对存储的类型进行声明,系统将自动完成PV的创建及与PVC的绑定。PVC可以声明Class为"",说明该PVC禁止使用动态模式。 图2-26描述了在静态资源供应模式下通过PV和PVC完成绑定并供Pod使用的存储管理机制。 图2-26 例如,对于NFS共享存储,可以先创建一个PV对象,对NFS存储进行引用: 然后创建一个PVC对象,通过storageClassName和Label Selector选择之前创建的PV: 之后,Pod就能够将PVC“myclaim”挂载到容器的某个目录进行使用了。 图2-27描述了在动态资源供应模式下,通过StorageClass和PVC完成资源动态绑定(系统自动生成PV)并供Pod使用的存储管理机制。
Persistent Volume Claim和Persistent Volume的生命周期
类似于Pod和Node,Pod消费Node的资源,Persistent Volume Claim则是消费Persistent Volume的资源。
Persistent Volume和Persistent Volume Claim相互关联,有着完整的生命周期管理。
准备 系统管理员规划并创建一系列的Persistent Volume,Persistent Volume在创建成功后处于可用状态。
绑定 用户创建Persistent Volume Claim来声明存储请求,包括存储大小和访问模式。
Persistent Volume Claim创建成功后进入等待状态,当Kubernetes发现有新的Persistent Volume Claim创建的时候,就会根据条件查找Persistent Volume。当有Persistent Volume匹配的时候,就会将Persistent Volume Claim和Persistent Volume进行绑定,Persistent Volume和Persistent Volume Claim都进入绑定状态。 Kubernetes只会选择可用状态的Persistent Volume,并且采取最小满足策略,而当没有Persistent Volume满足需求的时候,Persistent Volume Claim将处于等待阶段。比如现在有两个Persistent Volume可用,一个Persistent Volume的容量是50Gi,一个Persistent Volume的容量是60Gi。那么请求40Gi的Persistent Volume Claim会被绑定到50Gi的Persistent Volume,而请求100Gi的Persistent Volume Claim则处于等待状态,直到有大于100Gi的Persistent Volume出现(Persistent Volume有可能被新建或者被回收)。
使用 创建Pod的时候使用Persistent Volume Claim,Kubernetes便会查询其绑定的Persistent Volume,去调用真正的存储实现,然后将数据卷挂载到Pod中。
释放 当用户删除Persistent Volume所绑定的Persistent Volume Claim时,Persistent Volume则进入释放状态。此时Persistent Volume中可能残留着之前Persistent Volume Claim使用的数据,所以Persistent Volume并不可用,需要对Persistent Volume进行回收操作。
回收 释放的Persistent Volume需要回收才能再次使用,回收的策略可以是人工处理,或者由Kubernetes自动进行清理,如清理失败,Persistent Volume会进入失败状态。
Persistent Volume的状态包括如下。
Available:Persistent Volume创建成功后进入可用状态,等待Persistent Volume Claim消费。
Bound:Persistent Volume被分配到Persistent Volume Claim进行绑定,Persistent Volume进入绑定状态。
Released:Persistent Volume绑定的Persistent Volume Claim被删除,Persistent Volume进入释放状态,等待回收处理。
Failed:Persistent Volume执行自动清理回收策略失败后,Persistent Volume会进入失败状态。
Persistent Volume Claim的状态包括如下几项。
Pending:Persistent Volume Claim创建成功后进入等待状态,等待绑定Persistent Volume。
Bound:分配Persistent Volume给Persistent Volume Claim进行绑定,Persistent Volume Claim进入绑定状态。
存储方案选择
(1)将存储放置于容器内部
优点:配置简单,便于容器实例的水平扩展;存储性能也与直接在物理机上启动应用相当,几乎没有磁盘I/O的额外损耗。
缺点:在容器被销毁或删除之后,容器内部的存储也会一并被销毁,数据持久化保存比较困难;同时,在业务逻辑上要求每个容器实例存储的文件相互没有关联。
Volume类型的选择。
◎ 使用emptyDir类型的Volume,可供一个Pod内的多个容器共享。
◎ 也可以不使用任何Volume类型,由容器引擎(如Docker)管理应用的文件存储。
适用场景:
适合无状态容器应用,在系统运行过程中产生的临时文件可以被保存在容器的存储空间中。
如有需要保存的日志记录,则可以使用Pod内的临时存储,供另一个Sidecar容器进行文件处理。
注意事项:
需要考虑存储空间的限制。
不论是Docker为容器设置的存储目录,还是在Pod内设置的临时目录,都会耗费宿主机的磁盘空间。这就要求应用系统对存储空间的使用进行限制,不应无限使用,以至于将宿主机磁盘空间耗尽。
(2)将存储挂载在外部宿主机上
优点:数据不会因为容器销毁而丢失,可永久保存;存储性能与直接在物理机上使用相当,没有磁盘I/O的额外损耗。
缺点:容器实例使用宿主机存储目录,多实例的应用在同一台宿主机上的目录配置变得复杂,要求各实例对存储的使用互不干扰;另外,如果历史数据在后续的业务处理过程中仍然需要使用,则容器应用将对Node宿主机形成绑定关系,不利于容器应用在故障恢复时选择其他可用的Node重建。
◎ 使用hostPath类型的Volume,将宿主机目录挂载到容器内。
◎ 使用local类型的PV。
适用场景:适合有状态(Stateful)类型的容器应用,以及对磁盘I/O性能要求非常高的应用,例如数据库类的应用,包括MySQL、MongoDB、Cassandra等;
同时,这类应用应该不能随意水平扩展;还需要通过其他机制实现存储的高可用保障,例如可以使用数据远程备份机制保存数据。
注意事项:同样需要考虑存储空间的限制,不应无限使用,以至于将宿主机磁盘空间耗尽,在必要时可以考虑数据的定时清理工作。
(3)使用外部共享存储
优点:配置简单,数据的持久化、备份都由共享存储提供了解决方案,也便于容器实例的水平扩展。
缺点:由于共享存储多是网络存储的,所以在进行文件读写时都要经过网络传输,存储性能比直接在物理机上使用差很多。 Volume类型选择:使用PV或StorageClass类型的Volume。
◎ 静态PV可以选择NFS、FC、iSCSI等Volume类型。
◎ 动态PV可以选择GlusterFS、Ceph等Volume类型。
适用场景:适合有状态类型的容器应用,以及对磁盘I/O性能要求不是很高的应用,例如小型数据库类的应用。这类应用如果有水平扩展的需求,则可以考虑使用Kubernetes 的StatefulSet来部署应用和存储。 注意事项:应该考虑存储设备的成本和性能指标。
Kubernetes的卷是通过Pod实现持久化的,数据卷一般可以贯穿Pod(而不是Pod中的容器)的整个生命周期,因此,存储卷的存在时间会比Pod中的任何容器都长,并且在容器重新启动时会保留数据。
当Pod被平台删除时,不同的数据卷实现可能会有所不同,数据或被保留或被移除,如果数据被保留的话,其他Pod可以重新把该卷的数据进行加载使用。
数据卷可以分为共享和非共享两种类型,
共享型的数据卷可以被不同节点上的多个Pod同时使用,因此能够很方便地支持容器在集群各节点之间的迁移。
Kubernetes可以支持多种类型的数据卷,Pod可以同时使用多种类型和任意数量的存储卷。
在Pod中通过指定下面的字段来使用数据卷。
· spec.volumes:通过此字段提供指定的数据卷。
· spec.containers.volumeMounts:通过此字段将数据卷挂接到容器中。
· emptyDir:空目录。在Pod分配到Node上时被创建,属于Node上的本地存储,生命周期和Pod一样,没有持久性,当Pod从Node上被移除时,emptyDir中的数据会被永久删除。
· hostPath:映射Node文件系统中的文件或目录到Pod中。
· local:允许用户通过标准PVC接口访问Node节点的本地存储。物理卷的定义中需要包含描述节点亲和性的信息,Kubernetes基于该信息将容器调度到正确的Node节点上。
· rbd:Ceph块存储。
· cinder:OpenStack块存储。
· nfs:用于将现有的网络文件系统挂载到Pod中。在Pod被移除时,网络文件系统数据卷被卸载,但是其中的内容并不会被删除。这意味着在网络文件系统数据卷中可以预先填充数据,并且可以在Pod之间共享。网络文件系统可以被同时挂载到多个Pod中,并能同时进行写入。但是使用的前提是:网络文件系统服务器必须已经被部署运行,并已经设置了共享目录。
· iscsi:允许将现有的iSCSI卷挂载到容器中。Pod被移除时,iSCSI卷被卸载,但是其中的内容将被保留。这意味着iSCSI卷可以预先填充数据,并且这些数据可以在Pod之间共享。
· persistentVolumeClaim:用于将PersistentVolume挂载到Pod中,使用此类型的数据卷时,用户并不知道数据卷的详细信息。
emptyDir和hostPath都是Node节点的本地存储方式,使用emptyDir时,可以在创建时定义emptyDir.medium字段的值为“Memory”,进而选择把数据保存到tmpfs类型的本地虚拟文件系统中。此外,emptyDir是临时存储,而hostPath的数据是持久化在Node节点的文件系统中的。
物理卷和PVC是卷之外的另外两个重要的概念。Kubernetes可以把外部预创建的数据卷接入到Pod中,Pod无法对数据卷的一些参数(如卷大小、IOps等)进行配置,因为这些参数是由数据卷的提供者预先设定的,这类似于传统存储里预先划分一定的数据卷给应用挂载使用。为了更细粒度地管理数据卷,Kubernetes增加了持久化卷PV(Persistent Volume)的功能,把外置存储作为存储资源池,由平台统一管理并提供给整个集群使用。当Pod需要时,可以向平台请求所需要的存储资源,该请求就被称作PVC。PVC的内容包括访问模式、容量大小等信息。平台根据请求的内容匹配合适的资源,并把得到的PV挂载到Pod所在的主机中供Pod使用。 与普通的卷不同,PV是Kubernetes中的一个资源对象,有独立于Pod的生命周期(PV/PVC的生命周期由PV Controller来实现),创建一个PV相当于创建了一个存储资源对象。Pod通过提交PVC请求来使用PV,整个PV/PVC的使用过程可以分为以下5个阶段。
· 配置:即PV的创建,可以采用静态方式直接创建PV,也可以使用StorageClass动态创建。
· 绑定:将PV分配给PVC。
· 使用:Pod通过PVC使用该卷。
· 释放:Pod释放卷并删除PVC。
· 回收:回收PV,可以保留PV以便下次使用,也可以直接删除。 配置阶段的工作是为集群提供可用的存储卷,可以采用静态或动态两种方式进行创建,工作流程分别如图9-13和图9-14所示。 图9-13 静态配置
管理节点内所有占用磁盘空间的行为确保集群的可用性
1.日志管理 日志管理包括对系统服务日志的管理,也包括对容器日志的管理。 节点上需要通过运行logrotate 的定时任务对系统服务日志进行rotate 清理,以防止系统服务日志占用大量的磁盘空间。Logrotate 的执行周期不能过长,以防日志短时间内大量增长。同时配置日志的rotate 条件,在日志不占用太多空间的情况下,保证有足够的日志可供查看。 容器的日志管理方式因运行时的不同而有所区别:
● 如果选择Docker 作为运行时,那么除了基于系统logrotate 管理日志,还可以依赖Docker 自带的日志管理功能来设置容器日志的数量和每个日志文件的大小。Docker 写入数据之前会对日志大小进行检查和rotate 操作,确保日志文件不会超过配置的数量和大小。
● 如果选择Containerd 作为运行时,那么日志的管理是通过kubelet 定期(默认为10s)执行du 命令,来检查容器日志的数量和文件的大小的。每个容器日志的大小和可以保留的文件个数, 可以通过 kubelet 的配置参数 container-log-max-size 和container-log-max-files 进行调整。
2.hostPath 卷管理 Kubernetes 允许用户通过hostPath 卷将计算节点的文件目录挂载给容器,但目录挂载完成后,系统无法限制用户往该卷写入的数据的大小。这就导致删除容器时,如果不主动进行数据清理,数据就会遗留在节点上,占用磁盘空间。因此,用户能使用的节点路径需要被限制,即Kubernetes 可基于PodSecurityPolicy 限制hostPath 可以映射的主机文件路径。当某一文件路径开放给用户后,用户如何使用hostPath 卷就不受系统管理员的管控了。 因此,是否开放节点路径是一个需要慎重考虑的问题。
3.emptyDir 卷管理 emptyDir 卷可以理解为与Pod 生命周期绑定的、可以限制用量的主机磁盘挂载方式。1.15 版本之前,kubelet 定期执行du 命令获取容器emptyDir 卷的使用量,但是这种方式的实时性不够,且耗时过长,导致磁盘可能在计算过程中就被写满,进而影响系统服务。1.15版本之后,kubelet 使用文件系统自带的Quota 特性设置,并监控emptyDir 卷的大小,kubelet目前只支持XFS 和ext4 的文件系统。emptyDir 卷的使用情况可以实时反馈给kubelet,以防出现磁盘写满的情况。基于文件系统的Quota 特性监控emptyDir 卷的使用情况,相比du 更精准。du 无法监测文件打开后尚未关闭就被删除的情况,但是Quota 能够跟踪到已被删除而文件描述符尚未关闭的文件。
4.容器的可写层管理 容器的用户行为无法预知,如用户可能会将大量数据写入容器的可写层(比如/tmp 目录)。容器的可写层与容器的生命周期一致,数据会随着容器的重启而丢失。如果节点因为磁盘分区管理等原因,发生未开启临时存储管理限制而容器可写层却已达到上限的情况,就只能退而求其次,利用运行时的存储驱动进行限制:DeviceMapper 天然支持对每个容器的可写层限制,而overlayfs2 则需要依赖xfs_quota 特性的支持。
5.Docker 卷管理 在构建容器镜像时,可以在Dockerfile 中通过VOLUME 指令声明一个存储卷。运行时创建该容器时,会在主机上的运行时目录下创建一个子目录,并将其挂载至用户容器指定的目录。容器进程写入目标目录的数据,本质上是被写到主机的根盘或者给运行时分配的磁盘上,而不属于容器的可写层,目前Kubernetes 尚未将其纳入管控范围。当前有众多的开源容器镜像采用此方式定义存储空间,但当这样的镜像被部署到集群中时,会给系统带来潜在的隐患。如果确实需要使用该镜像,又希望存储空间能够被Kubernetes 管理,那么可以在Pod Spec 中定义一个卷,使其在容器内的路径与Dockerfile 中的路径一致。这样容器使用的存储就是定义的卷,而不是系统创建的默认卷。
容器会共享节点的根分区和运行时分区。如果容器进程在可写层或emptyDir 卷进行大量读写操作,就会导致磁盘I/O 过高,从而影响其他容器进程甚至系统进程。另外,对于网络存储卷和本地存储卷也有诸多并发问题需要解决:减少不同容器对同一个磁盘设备的I/O 竞争,以提升性能;降低并发读写,以减少后端的存储压力;减少大量传输数据对网络带宽的影响,等等。为满足这些需求,我们要对容器进程在共享分区的操作进行I/O限速。 Docker 和Containerd 运行时都基于CGroup v1。对于块
持久化存储
半持久化存储(如hostPath)
持久化存储(包含网络存储和本地存储)
引入了 StorageClass、Volume、PVC(Persistent Volume Claim)、PV(Persitent Volume) 的概念,将存储独立于Pod 的生命周期来进行管理。
StorageClass 用于指示存储的类型,不同的存储类型可以通过不同的StorageClass 来为用户提供服务。StorageClass 主要包含存储插件provisioner、卷的创建和mount 参数等字段。
PVC 由用户创建,代表用户对存储需求的声明,主要包含需要的存储大小、存储卷的访问模式、stroageclass 等类型,其中存储卷的访问模式必须与存储的类型一致,包含的三种类型如表2-17 所示。
pV 由集群管理员提前创建,或者根据PVC 的申请需求动态地创建,它代表系统后端的真实的存储空间,可以称之为卷空间。 用户通过创建PVC 来申请存储。控制器通过PVC 的StorageClass 和请求的大小声明来存储后端创建卷,进而创建PV,Pod 通过指定PVC 来引用存储。Pod、PVC、PV、StorageClass 等卷之间的相互关系如图2-23 所示。
图
支持以三种插件的形式来实现对不同存储的支持和扩展
in-tree 插件 在Kubernetes 早期版本中,接口抽象仍不完备,所有存储支持均通过in-tree 插件来实现。
in-tree 插件是指插件代码编译在Kubernetes 控制器和kubelet 中,归属于Kubernetes的核心代码库。
Kubernetes 支持的存储插件不断增加,in-tree 插件与其之间的强依赖关系导致系统维护成本过高,且插件代码与Kubernetes 的核心代码共享同一进程,因此当插件代码异常崩溃时,会导致Kubernetes 核心模块不可用。
FlexVolume 是指Kubernetes 通过调用计算节点的本地可执行文件与存储插件进行交互。
不同的存储驱动对应不同的可执行文件,该可执行文件可以实现FlexVolume 存储插件需要的attach/detach、mount/umount 等操作。
部署与Kubernetes 核心组件分离的可执行文件,使得存储驱动有独立的升级和部署周期,解决了in-tree 存储插件的强耦合问题。但是in-tree 存储插件对Kubernetes 核心组件的强依赖问题依然存在:
FlexVolume 插件需要宿主机用root 权限来安装插件驱动。
FlexVolume 存储驱动需要宿主机安装attach、mount 等工具,也需要具有root 访问权限。
CSI out-of-tree 存储插件 Kubernetes 社区引入了一个in-tree 的CSI 存储插件,用于用户和外挂的CSI 存储驱动的交互。
对于FlexVolume 基于可执行文件的方式,CSI 通过RPC 的方式与存储驱动进行交互。在设计CSI 的时候,Kubernetes 对CSI 存储驱动的打包和部署要求很少,主要定义 了 Kubernetes 的两 个相关 模块: kube-controller-manager 和 kubelet 。kube-controller-manager 模块用于感知CSI 驱动存在,kubelet 模块用于与CSI 驱动进行交互
资料
https://www.163.com/dy/article/H18M1S2M0511CUMI.html
镜像资源管理
应用管理
单个服务应用的创建和模板定义
1)负载分发模式
负载分发模式可分为轮询模式和会话保持模式。
通常可以为服务设置轮询的负载分发模式。
在客户端需要保持会话的应用场景中,应设置负载分发模式为“会话保持”。
2)主机网络模式(hostNetwork)
是否使用主机网络模式,默认值为“否”。
如果选择“是”,则表示容器使用宿主机网络,不再使用容器的Overlay网络,容器IP即宿主机IP,容器端口直接占用宿主机上的端口。在这种模式下,一个Pod将无法在同一台宿主机上启动第2个副本。 那么,
什么场景下需要使用hostNetwork模式呢?在服务需要使用固定的IP地址和端口号,以及需要提高I/O效率的时候,可以使用hostNetwork模式。需要注意的是,当容器出现故障或者容器所在的宿主机出现故障,而无法通过简单地在另一台宿主机上重建一个容器继续提供服务时,为了保证服务的高可用性,通常应该在部署这类服务的时候就引入额外的负载均衡器或其他工具来避免单点故障。
3)服务的端口号设置(Container Port、Node Port) 应用模板可能需要定义Container Port和Node Port,
Container Port为容器内的应用程序监听的端口号,
Node Port为Kubernetes集群内部的微服务需要映射到宿主机的端口号。
上例中的web-service应用设置了Node Port为30001,说明这是一个对集群外部提供服务的应用。 在Kubernetes集群内部,各个微服务通常都无须将端口号映射到宿主机上,仅需要对集群外部提供服务的应用进行设置,同时需要对端口号进行完整的管理。
4)应用配置 应用的配置主要是指应用所需的配置文件,可以通过Kubernetes的ConfigMap进行管理。
在应用能够挂载应用配置之前,运维人员需要在容器云平台上先创建ConfigMap,并发布到指定的集群中,然后在应用创建时对应用配置进行引用。例如,web-service应用在容器启动时将配置文件tomcat-conf挂载(mount)到容器内部的/config-center目录下。
5)环境变量的设置
我们可以将环境变量看作另一种应用程序所需的配置,在容器启动时进行注入,供应用程序读取。
对于应用程序所需的比较简单的可变参数,通常可以通过环境变量的形式进行设置。如果内容非常多,则应考虑使用配置文件,通过ConfigMap的方式挂载到容器内部。
6)存储卷的设置 容器一旦被销毁,容器内部的数据便会全部丢失。为了让容器内部的有用数据能持久化保存,我们可以将容器内部有用的数据通过存储卷保存到宿主机或者网络共享存储中。关于如何选择不同的存储类型,详见2.3节的说明。
7)预终止命令 Kubernetes在停止某个Pod之前,会先执行容器中的preStop Command命令(/tomcat/bin/shutdown.sh),在执行完毕后(或达到最大时间)才会停止Pod。这对于还有部分客户端请求没有完全处理的Pod来说非常重要,在停止Pod之前,应尽量让容器内部的应用程序将请求处理完成并自行退出,避免被强行Kill掉。
8)应用的健康检查 对应用的健康检查可以通过Kubernetes的LivenessProbe探针来探测,判断容器是否正常存活。如果探测到容器不健康,Kubelet则将杀掉该容器,并根据容器的重启策略对容器进行重启。健康检查可以设置的方法有httpGet、tcpSocket和exec,对一个容器仅需设置一种健康检查方法。对应用设置健康检查机制,能够实现应用的高可用。
9)应用的服务可用性检查 容器应用在发布后的运行过程中,需要确保每个应用Pod实例都能持续地提供服务,可以通过Kubernetes的ReadinessProbe探针来探测。如果对某个Pod的服务可用性探测没有得到正常的返回结果,则说明该Pod目前无法提供服务(例如,发生了排队的请求太多、内存不够等情况,则在一段时间后可以恢复正常),Kubernetes将会从Service级别隔离该Pod实例,以减少失败的服务请求响应,避免影响到业务的处理。
多服务组合的应用模板helm
Helm主要涉及Chart、Release和Repository这几个概念。
◎Chart:为一个Helm包,其中包含了运行一个应用所需的工具和资源定义,还可能包含Kubernetes集群中的服务定义,类似于Homebrew中的formula、APT中的dpkg或者Yum中的RPM文件。
图例
图
◎Release:为在Kubernetes集群上运行的一个Chart实例。在同一个集群上,一个Chart可以安装多次。例如有一个MySQL Chart,如果想在服务器上运行两个MySQL数据库,就可以基于这个Chart安装两次。每次安装都会生成新的Release,会有独立的Release名称。
◎Repository:为用于存放和共享Chart的仓库。
应用配置管理
镜像构建注意事项
在打包镜像时,需要注意以下几个关键问题。
(1)需要注意基础镜像的选择问题。选择基础镜像的两个原则:标准化与精简化。尽可能选择Docker官方发布的基础镜像,这些基础镜像通常符合标准化与精简化这两个目标。比如,它们都有Dockerfile源文件,我们可以获知此镜像是如何制作的,并可以在此基础上实现诸如软件版本、性能优化、日志及安全等方面的特殊定制,然后打包为公司级别的内部标准镜像,供各个项目使用。
(2)需要注意业务进程的启动方式。与在物理机上将自己的程序放到后台运行的方式不同,在容器化时,我们需要将自己的业务进程放到前台运行。这样一来,当业务进程由于某种原因而停止时,容器也随之销毁,我们就能及时观察到这种严重故障,并做出相应的行动来恢复系统。目前有一些系统在容器化的过程中采用了supervisord这样的工具,将业务的主进程和辅助进程放到后台启动,并交给supervisord监管,这种做法虽然在一定程度上也能实现自动重启故障进程的目标,但它将问题隐藏得更深,即使业务进程由于特殊故障始终无法重启成功,运维人员也发现不了问题,因此不建议采用这种方式启动业务进程。
(3)需要注意程序的日志输出问题。在物理机上运行业务进程时,我们通常会把程序日志输出到指定的文件中,以便更好地排查故障。但在容器化以后,我们需要改变这种做法,将程序的日志直接输出在容器的屏幕上(或者说控制台Console上),此时Docker会将这些输出日志存放到容器之外的特定文件中,第三方的日志收集工具(例如Elasticsearch)就可以方便采集这些日志并实现集中化的日志搜索和分析功能。此外,Docker也提供了统一的log命令来查看容器的日志,这推进了系统运维的标准化。Java中常用的Log4j及Slf4j日志框架都支持把日志输出到控制台的配置方式,在打包应用时,需要对日志的配置文件做出相应的修改。
(4)需要注意文件操作的问题。当业务进程运行在物理机上时,它看到的文件系统就是物理机的文件系统;但当业务进程运行在容器中时,它所访问的文件系统就是一种特殊的、被隔离的、分层模式的虚拟文件系统,在这种情况下,频繁进行I/O操作的性能比较低。为了解决这个问题,容器可以使用Volume将频繁进行操作的目录映射到容器外部(通常是物理机上);同时,Volume也是容器与外部交换文件的重要工具,因此在制作镜像和运行容器时,需要考虑Volume映射的问题,对于在程序运行过程中产生的大量临时文件和被频繁读写的文件,或者在需要跟外界交换文件时,可以选择挂载Volume。
微服务验证和授权
身份验证和授权也与安全相关,通过限制受信任用户可访问有限的Kubernetes资源来保障。
基于角色的访问控制(Role-based Access Control,RBAC)
RBAC是最佳实践,它有两个概念:角色和绑定。角色定义了一组特定权限的规则。
角色有两种:Role适用于单个命名空间,ClusterRole适用于集群中的所有命名空间。 下面是默认命名空间中的一个角色,该角色允许获取、监控和列出所有Pod。
每个角色都有三部分——API组、资源和动作: 集群角色也有类似的配置,除了没有命名空间字段,因为它适用于所有命名空间。
绑定会将一系列主题(用户、用户组或服务账户)与角色相关联。绑定有两种类型,RoleBinding和ClusterRoleBinding,它们分别对应于Role和ClusterRole。 有趣的是,你可以将ClusterRole绑定到单个命名空间中的主题。这对于定义需要应用到多个命名空间的角色很方便,一旦创建了集群角色,你可以将它们绑定到特定命名空间中的特定主题。 集群角色绑定也是类似的配置,但是注意它必须绑定一个集群角色,并且始终应用于整个集群。 注意,RBAC用于授予对Kubernetes资源的访问权限。它可以管理对服务端点的访问,但是你可能仍需要微服务中的细粒度授权
使用Kubernetes配置微服务
ConfigMap是由Kubernetes每个命名空间管理的资源,并且可以被任何Pod或容器引用。
下面是link-manager服务的ConfigMap: link-manager部署资源通过使用envFrom将其导入到Pod中:
这样做的效果是,当link-manager服务运行时,ConfigMap的data部分中的键值对被映射为环境变量: 让我们看看Argo CD如何形象地显示link-manager服务的ConfigMap。注意图5-1中顶部名为link-service-config的框体。 单击ConfigMap框可以从Argo CD界面下钻到具体的ConfigMap,如图5-2所示。 图5-1 查看link-manager服务的ConfigMap 图5-2 查看具体的ConfigMap 注意,
由于ConfigMap被用作环境变量,因此这属于静态配置。如果要更改其中任何一项内容,则需要重新启动服务。
在Kubernetes中,这可以通过以下两种方式完成:
·终止Pod(部署的副本集将创建新的Pod)。
·删除并重新创建部署(具有相同的效果,但是你无须显式地终止Pod)。
·应用其他更改并重新部署。 让我们看看在代码中是如何使用它的,代码在svc/link_manager/service/link_manager_service.go文件中: os.Getenv()标准库函数从环境变量中获取PORT和MAX_LINKS_PER_USER的值,这一点非常棒,它允许我们测试Kubernetes集群之外的服务而不影响正常的配置。
1.创建和管理ConfigMap Kubernetes提供了多种创建ConfigMap的方法:
·通过命令行中的值。
·来自一个或多个文件。
·从整个目录。
·通过直接创建ConfigMap YAML清单。
无论哪种方式,最后所有ConfigMap都是一组键值对,键和值是什么取决于创建ConfigMap的方法。在使用ConfigMap时--dry-run标志很有用,通过它可以在实际创建之前查看将要创建的ConfigMap。让我们看一些例子,下面是从命令行参数创建ConfigMap的方法: 这是测试ConfigMap最常用的方式,但是,你必须使用烦琐的--from-literal参数分别指定每个配置项。 从文件创建ConfigMap是一种更可行的方法。它与GitOps概念配合得很好,你可以在其中保留用于创建ConfigMap的源配置文件的历史记录。我们可以创建一个非常简单的名为comics.yaml的YAML文件: 接下来,使用以下命令从该文件创建ConfigMap(只是--dry-run试运行)
应用上容器云的准入条件和最佳实践
整体而言,应用上容器云的准入条件包含如下几个方面(包含但不限于)。
已建立了清晰的可自动化的编译及构建流程:应用使用如Maven、Gradle、Make或Shell等工具实现了构建编译步骤的自动化,以便在容器平台上实现自动化的编译及构建流程。
已实现应用配置参数外部化:应用已将配置参数外部化于配置文件或环境变量中,以便应用容器能适配不同的运行环境。包含特定环境的配置的容器镜像不能在整个环境(Dev、QA、Prod)中升级。为了实现可靠的发布过程,应将在较低环境中测试过的相同镜像部署到生产中。将特定环境的配置保留在容器镜像之外,例如,使用ConfigMap和Secret存储应用程序配置。
已提供合理可靠的健康检查接口:容器平台将通过健康检查接口判断容器状态,对应用服务进行状态保持。
已实现状态外部化,以及应用实例无状态化:应用状态信息存储于数据库或缓存等外部系统,应用实例本身实现无状态化。
不涉及底层的操作系统依赖及复杂的网络通信机制:应用以处理业务为主,不强依赖于底层操作系统及组播等网络通信机制,提高可移植性。
部署交付件及运行平台的大小在2GB以内:轻量级的应用便于在大规模集群中快速传输分发,更符合容器敏捷的理念。
启动时间在5分钟以内:过长的启动时间将不能发挥容器敏捷的特性。
如果应用明显不符合上述条件,则其暂时不适合运行在容器上。
在应用上容器云时,除了需要遵循以上准入条件,还需要尽量符合以下最佳实践。
在Pod定义中指定资源请求和资源限制。如果请求资源的配置不正确的话,那么应用程序可能会耗尽内存或导致CPU资源不足。指定请求的内存和CPU资源可以使集群做出适当的调度决策,以确保应用程序具有足够的可用资源。 使用Pod中断预算保护应用程序。在某些情况下,需要将应用程序容器从集群节点中逐出。例如,在管理员可以执行节点维护之前或在缩减规模时集群自动缩放器可以从集群中删除节点之前,需要驱逐Pod。为确保驱逐Pod时应用程序仍然可用,你必须定义各自的PodDistruptionBudget对象。PodDisruptionBudget是一个API对象,用于指定保证应用可用的最小副本数或最小百分比。 每个容器运行一个进程。避免在单个容器中运行多个进程。每个容器中运行一个进程可以更好地隔离进程,避免信号路由出现问题。 应用程序监视和警报。应用程序监视和警报对保持应用程序在生产中良好运行并满足业务目的至关重要。可以使用Prometheus和Grafana等监视工具来监视你的应用程序。 配置应用程序以将其日志写入stdout或stderr。容器云将收集这些日志并将其发送到集中位置(ELK、Splunk)。在分析生产问题时,应用程序日志是宝贵的资源。基于应用程序日志内容的警报有助于确保应用程序按预期运行。 考虑实施弹性措施:断路器、超时、重试、速率限制。弹性措施可以使你的应用程序在出现故障时表现更好。它们可保护你的应用程序免于过载(速率限制、断路器),并在遇到连接问题(超时、重试)时提高性能。考虑利用OpenShift Service Mesh来实现这些措施,需在应用程序中更改代码。 使用受信任的基础镜像(base image)。尽可能使用容器厂商提供的企业级容器镜像。容器厂商提供的镜像已通过测试、安全加固并有相应的技术支持。如果使用社区提供的镜像,请尽量使用你信任的社区提供的镜像。不要使用公共注册表(例如Docker Hub)中有未知来源的镜像。 使用最新版本的基础镜像。通常,仅最新版本的容器镜像包含所有可用的安全修复程序。设置CI管道,以便在构建应用程序镜像时始终提取最新版本的基础镜像,同时在更新的基础镜像可用时重建应用程序。 使用单独的构建镜像和运行时镜像。创建具有最小依赖的、单独的运行时镜像可减少攻击面,并产生较小的运行时镜像。构建镜像包含构建依赖关系,构建依赖关系对于构建应用程序是必需的,对于运行应用程序则不是必需的。 尽可能遵守受限制的安全上下文约束(SCC)。修改你的容器镜像以允许在受限制的SCC下运行。应用程序容易受到攻击,强制使用OpenShift受限制的SCC可提供最高级别的安全性,以防止在应用程序被破坏的情况下损害集群节点。 使用TLS保护应用程序组件之间的通信。应用程序组件可能会传达应受到保护的敏感数据。除非你认为基础OpenShift网络是安全的,否则你可能希望利用TLS保护应用程序组件之间的通信。考虑利用OpenShift Service Mesh从应用程序中卸载TLS管理。
平台运维管理
日志 监控 告警管理
安全管理
云平台上的安全机制至少应该从以下几方面进行设计和管理:
各级用户角色的权限管理、
租户之间资源和应用的隔离、
网络层面的安全、
与应用相关的敏感信息的管理(如密码、密钥类数据)、镜像库的安全管理,等等。
用户角色权限管理
在容器云平台上,用户的权限至少应该包括平台管理员和租户两个级别,通常还应为租户设置租户级别的管理员和普通用户这两个级别。图5-19描述了容器云平台上的用户角色权限管理。
◎ 平台管理员:负责整个平台范围内的租户管理,并为租户分配其需要的系统资源。包括租户的创建、系统资源分区的设置、资源分区与租户进行绑定等管理工作。
◎ 租户管理员:为一个租户范围内的管理员,如果系统比较复杂,则可以通过创建用户组和用户,进一步细分资源分区,将细分的资源分区与用户进行绑定等操作,完成租户范围内的安全管理。如果系统不很复杂,则租户管理员也可以作为普通用户,在其可用的资源分区上部署应用。
◎ 普通用户:主要负责应用的管理,包括应用的部署、更新、监控、查错等应用全生命周期管理。普通用户应该无须关心平台级别的资源设置,只需在可用的资源分区上管理业务应用。
可以使用Kubernetes提供的授权机制对用户设置应用的访问权限,实现应用级别的安全管理。
Kubernetes Master为用户对应用的请求进行授权,通过“授权策略”决定其访问请求能否被允许。简而言之,授权就是将不同的资源访问权限授予不同的用户。Kubernetes目前支持以下几种授权策略。 ◎AlwaysDeny:表示拒绝所有请求,仅用于测试。 ◎AlwaysAllow:表示允许接收所有请求,不启用授权机制。 ◎ABAC(Attribute-Based Access Control):基于属性的访问控制,使用用户配置的授权规则对用户的请求进行匹配和控制。 ◎Webhook:通过调用外部RESTful服务对用户的请求进行授权。 ◎RBAC:全称为Role Based Access Control,指基于角色的访问控制。 其中,Webhook方式是由外部系统完成授权的工作,本章不做说明。ABAC模式的实现基于授权策略文件,如有修改,则需要重启API Server才能生效,不够灵活和方便。
RBAC授权模式引入了4种新的资源对象:Role、ClusterRole、RoleBinding和ClusterRoleBinding。其中Role和ClusterRole都是“角色”的概念,RoleBinding和ClusterRoleBinding都是“角色绑定”的概念。角色是对一系列资源的权限集合,例如一个角色可以拥有读取Pod的权限和删除Pod的权限,是Namespace级别的;而ClusterRole是集群级别的权限集合。角色绑定则将角色与用户进行绑定,使得这些用户拥有在“角色”中定义的权限,ClusterRoleBinding则使用户拥有在“ClusterRole”中定义的集群级权限。 图5-20展示了对Pod的get/watch/list操作进行授权的Role、RoleBinding、用户及资源对象之间的授权关系。 目前,RBAC机制已与Kubernetes深度集成,建议应用系统的各资源对不同的用户进行RBAC授权设置,以实现对资源访问的安全控制。下面对RBAC的设置和应用进行说明。
图
应用系统需要配置诸如密码或密钥之类的敏感信息,使用明文的方式保存在配置文件中显然很不安全。在Kubernetes系统中,可以使用Secret资源对象来管理敏感信息,目前Secret有以下3种类型。
◎Opaque:字符串类型的加密信息。
◎kubernetes.io/dockerconfigjson:用于在拉取镜像(pull)操作时访问Docker镜像库的凭据信息。
◎kubernetes.io/service-account-token:用于ServiceAccount使用的密钥信息。
数据备份管理
功能实现
全景图
全景
服务发現
Service定义了一个服务的访问入口地址,
前端的应用(Pod)通过这个入口地址访问其背后的一组由Pod副本组成的集群实例,
Service与其后端Pod副本集群之间则是通过Label Selector来实现“无缝对接”的。
而RC的作用实际上是保证Service的服务能力和服务质量始终处于预期的标准。
流程
1.在服务Pod实例发布时(可以对应K8s发布中的Kind: Deployment),Kubelet会负责启动Pod实例,启动完成后,Kubelet会把服务的PodIP列表汇报注册到Master节点。
2.通过服务Service的发布(对应K8s发布中的Kind: Service),K8s会为服务分配ClusterIP,相关信息也记录在Master上
3.在服务发现阶段,Kube-Proxy会监听Master并发现服务ClusterIP和PodIP列表映射关系,并且修改本地的linux iptables转发规则,指示iptables在接收到目标为某个ClusterIP请求时,进行负载均衡并转发到对应的PodIP上。
4.运行时,当有消费者Pod需要访问某个目标服务实例的时候,它通过ClusterIP发起调用,这个ClusterIP会被本地iptables机制截获,然后通过负载均衡,转发到目标服务Pod实例上。
service 详解
Cluster IP和主机名
Kubernetes会从集群的可用服务IP池中为每个新创建的服务分配一个稳定的集群内访问IP地址,称为Cluster IP。
Kubernetes还会通过添加DNS条目为Cluster IP分配主机名。
Cluster IP和主机名在集群内是独一无二的,并且在服务的整个生命周期内不会更改。只有将服务从集群中删除,Kubernetes才会释放Cluster IP和主机名。
用户可以使用服务的Cluster IP或主机名访问正常运行的Pod。
用户不用担心服务出现单点故障问题,
Kubernetes会尽可能均匀地将流量分布到在多个节点上运行的Pod,
因此一个或若干个(但不是所有)节点的服务中断情况不会影响服务的整体可用性。
Kubernetes使用Kube-proxy组件管理各服务与之后端Pod的连接,该组件在每个节点上运行。
Kube-proxy是一个基于出站流量的负载平衡控制器,
它监控Kubernetes API Service并持续将服务IP(包括Cluster IP等)映射到运行状况良好的Pod,落实到主机上就是iptables/IPVS等路由规则。
访问服务的IP会被这些路由规则直接DNAT到Pod IP,然后走底层容器
spec.ClusterIP就是Service的(其中一个)访问IP,俗称虚IP(Virtual IP,即VIP)。如果用户不指定的话,那么Kubernetes Master会自动从一个配置范围内随机分配一个。
注意:服务分配的Cluster IP是一个虚拟IP,刚接触Kubernetes Service的人经常犯的错误是试图ping这个IP,然后发现它没有任何响应。实际上,这个虚拟IP只有和它的port一起使用才有作用,直接访问该IP或者想访问该IP的其他端口都是徒劳。
spec.ports[].port是Service的访问端口,
而与之对应的spec.ports[].targetPort是后端Pod的端口,Kubernetes会自动做一次映射(80->8080),具体实现机制后面会详细解释。Kubernetes Service能够支持TCP、UDP和SCTP三种协议,默认是TCP协议。
服务会随机转发到可用的后端。如果希望保持会话(同一个client永远都转发到相同的Pod),
可以把service.spec.sessionAffinity设置为ClientIP,即基于客户端源IP的会话保持,而且默认会话保持时间是10800秒。这会起到什么样的效果呢?即在3小时内,同一个客户端访问同一服务的请求都会被转发给第一个Pod。
Service的port、targetPort和NodePort。
port表示Service暴露的服务端口,也是客户端访问用的端口,例如Cluster IP:port是提供给集群内部客户访问Service的入口。需要注意的是,port不仅是Cluster IP上暴露的端口,还可以是external IP和Load Balancer IP。
Service的port并不监听在节点IP上,即无法通过节点IP:port的方式访问Service。
NodePort是Kubernetes提供给集群外部访问Service入口的一种方式(另一种方式是Load Balancer)
所以可以通过Node IP:nodePort的方式提供集群外访问Service的入口。需要注意的是,我们这里说的集群外指的是Pod网段外,例如Kubernetes节点或因特网。
NodePort为Service在Kubernetes集群的每个节点上分配一个真实的端口,即NodePort。
集群内/外部可基于集群内任何一个节点的IP:NodePort的形式访问Service。NodePort支持TCP、UDP、SCTP,默认端口范围是30000-32767,Kubernetes在创建NodePort类型Service对象时会随机选取一个。
用户也可以在Service的spec.ports.nodePort中自己指定一个NodePort端口,就像指定Cluster IP那样。如果觉得默认端口范围不够用或者太大,可以修改API Server的--service-node-port-range的参数,修改默认NodePort的范围,例如--service-node-port-range=8000-9000。
targetPort很好理解,它是应用程序实际监听Pod内流量的端口,从port和NodePort上到来的数据,最终经过Kube-proxy流入后端Pod的targetPort进入容器。 在配置服务时,可以选择定义port和targetPort的值重新映射其监听端口,这也被称为Service的端口重映射。Kube-proxy通过在节点上iptables规则管理此端口的重新映射过程。
通过域名访问服务
Kubernetes DNS服务的功能,
它是用来解析Kubernetes集群内的Pod和Service域名的,而且一般情况下只供集群内的容器使用,不给外人使用。
Kubelet负责刷新/etc/resolv.conf配置。
当Kubernetes的DNS服务Cluster IP分配后,系统(一般是指安装程序)会给Kubelet配置--cluster-dns=<dns service ip>启动参数,DNS服务的IP地址将在用户容器启动时传递,并写入每个容器的/etc/resolv.conf文件。DNS服务IP即上文提到的DNS Service的Cluster IP,可以配置成--cluster-dns=10.0.0.1。 除此之外,Kubelet的--cluster_domain=<default-local-domain>参数支持配置集群域名后缀,默认是cluster.local。
域名解析基本原理
Kubernetes DNS的命名方案也遵循可预测的模式,使各种服务的地址更容易被记住。服务不仅可以通过完全限定域名(FQDN)引用,还可以仅通过服务本身的name引用。
目前,Kubernetes DNS加载项支持正向查找(A Record)、端口查找(SRV记录)、反向IP地址查找(PTR记录)及其他功能。
对于Service,Kubernetes DNS服务器会生成三类DNS记录,分别是A记录、SRV记录和CNAME记录。
1.A记录 A记录(A Record)是用于将域或子域指向某个IP地址的DNS记录的最基本类型。记录包括域名、解析它的IP地址和以秒为单位的TTL。
TTL代表生存时间,是DNS记录上的一种到期日期。每个TTL都会告诉DNS服务器,
它应该在其缓存中保留给定记录多长时间。 Kubernetes为“normal”和“headless”服务分配不同的A Record name。
“headless”服务与“normal”服务的不同之处在于它们未分配Cluster IP且不执行负载均衡。
“normal”服务分配一个DNS A Record作为表单your-svc.your-namespace.svc.cluster.local的name(根域名可以在kubelet设置中更改)。此name解析为服务的集群IP。
“headless”服务为表单your-svc.your-namespace.svc.cluster.local的name分配一个DNS A Record。
与“normal”服务相反,此name解析的是,为服务选择的一组Pod IP。DNS不会自动将此设置解析为特定的IP,因此客户端应该负责集合中进行的负载均衡或循环选择。
A记录与普通Service和headless Service有区别。普通Service的A记录的映射关系是: 每个部分字段的含义是: ·service_name:Service名; ·namespace:Service所在namespace; ·domain:提供的域名后缀,是Kubelet通过--cluster-domain配置的,比如默认的cluster.local。 在Pod中可以通过域名{service name}.{service namespace}.svc.{domain}访问任何服务,也可以使用缩写{service name}.{service namespace}直接访问。如果Pod和Service在同一个namespace中,那么甚至可以直接使用{service name}访问。 headless Service的A记录的映射关系是: 含义跟前面的一致,本节不再赘述。 一旦启用了DNS,Pod将被分配一个DNS A记录,格式如下所示: 其中pod-ip为Pod的IP地址用-符号隔开,例如Pod IP是1.2.3.4,上面的{pod-ip}即1-2-3-4,因此对应的域名就是1-2-3-4.default.pod.cluster.local。Pod的A记录其实没什么用,都知道Pod IP了,还用查DNS吗?所以这也是一个即将废弃的特性。 如果在Pod Spec指定hostname和subdomain,那么Kubernetes DNS会额外生成Pod的A记录: 同样,后面那一串子域名pod.cluster.local是Kubelet配置的伪域名。
2.SRV记录 SRV记录是通过描述某些服务协议和地址促进服务发现的。
SRV记录通常定义一个符号名称和作为域名一部分的传输协议(如TCP),并定义给定服务的优先级、权重、端口和目标。详细内容请参阅下面的示例: 在上面的示例中,_sip是服务的符号名称,_tcp是服务的使用传输协议。记录内容代表:两个记录都定义了10的优先级。另外,第一个记录的权重为70,第二个记录的权重为20。优先级和权重通常用于建议指定使用某些服务器。记录中的最后两个值定义了要连接的端口和主机名,以便与服务通信。 Kubernetes DNS的SRV记录是按照一个约定俗成的规定实现了对服务端口的查询: SRV记录是为“normal”或“headless”服务的部分指定端口创建的。SRV记录采用_my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster.local的形式。对于常规服务,它被解析的端口号和域名是my-svc.my-namespace.svc.cluster.local。在“headless”服务的情况下,此name解析为多个answer,每个answer都支持服务。每个answer都包含含auto-generated-name.my-svc.my-namespace.svc.cluster.local表单的Pod端口号和域名。
3.CNAME记录 CNAME记录用于将域或子域指向另一个主机名。为此,CNAME使用现有的A记录作为其值。相反,A记录会解析为指定的IP地址。
此外,在Kubernetes中,CNAME记录可用于联合服务的跨集群服务发现。在整个场景中会有一个跨多个Kubernetes集群的公共服务。所有Pod都可以发现这项服务(无论这些Pod在哪个集群上)。这是一种跨集群服务发现方法。
3.7.3 DNS使用
Pod的默认主机名由Pod的metadata.name值定义。用户可以通过在可选的hostname字段中指定一个新值更改默认主机名
用户还可以在subdomain字段中自定义子域名。例如,在命名空间my-namespace中,将hostname设置为custom-host,将subdomain设置为custom-subdomain的Pod将具有完全限定的域名(FQDN)custom-host.customsubdomain.my-namespace.svc.cluster.local。 下面我们运行一个带nslookup命令的busybox容器,说明Kubernetes域名解析的使用。 大部分busybox都不带nslookup命令,因此这里给出一个带nslookup命令的busybox Pod示例yaml文件: 在default namespace下把这个Pod创建好,就可以通过kubectl exec进入busybox容器进行以下试验了。 我们先来查询Kubernetes默认的API Server服务Kubernetes的IP地址: 如上所示,Kube-dns的IP地址是10.0.0.1,而API Server的Cluster IP地址是10.0.0.10。 我们在default namespace下又创建了一个名为whoami的服务,并做以下域名解析动作: 如上所示,发起域名解析请求的busybox Pod和whoami服务均在default namespace下,因此以下所有域名都能被正确解析并且返回相同的结果。 查看busybox Pod的DNS配置文件,可以看到如下DNS Server的地址及搜索的domain列表: 其中,DNS Server的IP地址是10.0.0.1。options ndots:5的含义是当查询的域名字符串内的点字符数量超过ndots(5)值时,则认为是完整域名,直接解析,否则Linux系统会自动尝试用default.pod.cluster.local、default.svc.cluster.local或svc.cluster.local补齐域名后缀。例如,查询whoami会自动补齐成whoami.default.pod.cluster.local、whoami.default.svc.cluster.local和whoami.svc.cluster.local,查询过程中,任意一个记录匹配便返回,显然能返回DNS记录的匹配是whoami+default.svc.cluster.local。而查询whoami.default能返回DNS记录的匹配是whoami.default+svc.cluster.local。 最后,运行DNS Pod可能需要特权,即配置Kubelet的参数:--allow-privileged=true。 1.Kubernetes域名解析策略 Kubernete
Pod对象拥有生命周期,它会在自身故障或所在的工作节点故障时被替换为一个新的实例,其IP地址通常也会随之改变,这就给集群中的Pod应用间依赖关系的维护带来了麻烦:前端Pod应用(依赖方)无法基于固定地址持续跟踪后端Pod应用(被依赖方)。
为此,Kubernetes设计了有着稳定可靠IP地址的Service资源,作为一组提供了相同服务的Pod对象的访问入口,由客户端Pod向目标Pod所属的Service对象的IP地址发起访问请求,并由相关的Service对象调度并代理至后端的Pod对象。 Service是由基于匹配规则在集群中挑选出的一组Pod对象的集合、访问这组Pod集合的固定IP地址,以及对请求进行调度的方法等功能所构成的一种API资源类型,是Pod资源的代理和负载均衡器。
Service匹配Pod对象的规则可用“标签选择器”进行体现,并根据标签来过滤符合条件的资源对象,如图1-11所示。
标签是附加在Kubernetes API资源对象之上的具有辨识性的分类标识符,使用键值型数据表达,通常仅对用户具有特定意义。一个对象可以拥有多个标签,一个标签也可以附加于多个对象(通常是同一类对象)之上。 图1-11 Kubernetes资源标签
每个节点上运行的kube-proxy组件负责管理各Pod与Service之间的网络连接,它并非Kubernetes内置的代理服务器,而是一个基于出站流量的负载均衡器组件。针对每个Service,kube-proxy都会在当前节点上转换并添加相应iptables DNAT规则或ipvs规则,从而将目标地址为某Service对象的ClusterIP的流量调度至该Service根据标签选择器匹配出的Pod对象之上。
即使Service有着固定的IP地址可用作服务访问入口,但现实中用户更容易记忆和使用的还是服务的名称,Kubernetes使用定制的DNS服务为这类需求提供了自动的服务注册和服务发现功能。CoreDNS附件会为集群中的每个Service对象(包括DNS服务自身)生成唯一的DNS名称标识,以及相应的DNS资源记录,服务的DNS名称遵循标准的svc.namespace.svc.cluster-domain格式。
例如CoreDNS自身的服务名称为kube-dns.kube-system.svc.cluster.local.,则它的ClusterIP通常是10.96.0.10。 除非出于管理目的有意调整,Service资源的名称和ClusterIP在其整个生命周期内都不会发生变动。
kubelet会在创建Pod容器时,自动在/etc/resolv.conf文件中配置Pod容器使用集群上CoreDNS服务的ClusterIP作为DNS服务器,因而各Pod可针对任何Service的名称直接请求相应的服务。换句话说,Pod可通过kube-dns.kube-system.svc.cluster.local.来访问集群DNS服务。
流程图
内部服务对外暴露
NodePort
是K8s内部服务对外暴露的基础,LoadBalancer底层依赖于NodePort。
NodePort背后是Kube-Proxy,Kube-Proxy是沟通Service网络、Pod网络和节点网络的桥梁。
Ingress扮演的角色是对K8s内部服务进行集中反向代理,
通过Ingress,我们可以同时对外暴露K8s内部的多个服务,但是只需要购买1个(或者少量)LB。Ingress本质也是一种K8s的特殊Service,它也通过HostPort(80/443)对外暴露。
通过Kubectl Proxy或者Port Forward,可以在本地环境快速调试访问K8s中的服务或Pod。
K8s的Service发布主要有3种type
type=ClusterIP,表示仅内部可访问服务,
type=NodePort,表示通过NodePort对外暴露服务,
type=LoadBalancer,表示通过LoadBalancer对外暴露服务(底层对接NodePort,一般公有云才支持)。
DNS
Kubernetes DNS规范
DNS被认为是Kubernetes中的“附加”组件,集群没有它也能正常工作。然而,很少有集群不使用DNS,而且Kubernetes DNS规范被认为是标准一致性套件的一部分。如果一个发行版不提供符合Kubernetes规范的DNS服务,就不能声明它符合Kubernetes规范,该规范定义了用于定位集群中运行服务的DNS名称。 该规范是一种形式固定的DNS模式。也就是说,它定义一组特定的名称,这些名称必须基于API服务器的内容存在。Kubernetes中的Service资源是用户指定服务发现模式的主要方式。 规范中的所有记录都属于集群域(cluster domain)。通常,你会看到集群域被设置为cluster.local,通常也会在网上找到的示例中看到。在一些托管解决方案中(比如Google Kubernetes Engine(GKE))不能更改这个域,但是普通的开源Kubernetes允许你选择使用任何域。 规范要求对于每个Cluster IP都有一条包含它的A记录,其名称来源于服务名称和命名空间:service.namespace.svc.cluster-domain。示例6-1中定义的Service部署在集群域cluster.example.com中,使用dig命令的行为如示例6-3所示。
coreDNS
CoreDNS 包含一个内存态DNS,以及与其他controller 类似的控制器。
CoreDNS 的实现原理是,控制器监听Service 和Endpoint 的变化并配置DNS,客户端Pod在进行域名解析时,从 CoreDNS 中查询服务对应的地址记录
1.普通Service 通常,ClusterIP、nodePort、LoadBalancer 类型的Service 都拥有API Server 分配的ClusterIP, CoreDNS 会为这些 Service 创建 FQDN 格式为 svcname.namespace.svc.$clusterdomain: ClusterIP 的A 记录及PTR 记录,并为端口创建SRV 记录。
2.Headless Service 顾名思义,无头,是用户在Spec 显式指定ClusterIP 为None 的Sevice,对于这类Service,API Server 不会为其分配ClusterIP。CoreDNS 为此类Service 创建多条A 记录,并且目标为每个就绪的PodIP。 另外, 每个 Pod 会拥有一个 FQDN 格式为podname.svcname.namespace.svc.clusterdomain 的A 记录指向PodIP。
CoreDNS 完成域名配置后,要查询其中配置好的域名信息,则需要完成调用方的DNS配置。Kubernetes Pod 有一个与DNS 策略相关的属性DNSPolicy,默认值是ClusterFirst,在此模式下,Pod 启动后的/etc/resolv.conf 会被改写,所有的地址解析优先发送至CoreDNS
nameServer 指向的地址为在kubelet 启动参数中配置好的kube-DNS 的服务地址。当Pod 发起域名解析请求时,该请求会被发送至192.168.0.10 这个地址,而这个地址保存的是当前集群中所有服务的 FQDN 和 ClusterIP 的对应关系,因此,客户端只需查询svc1.ns1.svc.clusterdomain,即可获得ns1 下的svc1 的ClusterIP。
CoreDNS 是Kubernetes 集群插件,实现的是服务和服务ClusterIP 的域名配置,它提供的域名服务仅对集群内部客户端生效。对于有很多负载均衡设备和大量LoadBalancer 类型Service 的场景,需要自定义DNS provider,以实现与CoreDNS 相同的业务逻辑,以及与企业自有DNS 的集成。
负载均衡
负载均衡配置基于不同插件实现:
userspace
操作系统网络协议栈不同的 Hooks 点和插件:
iptables
ipvs
kube-proxy
kube-proxy 使得Kubernetes 集群基于内核模块形成了一个分布式负载均衡器,并使所有集群内的流量转发都基于这些转发规则来完成
实现机制与其他控制器类似,其本身是一个控制回路(Control Loop),
该Control Loop 监听API Server 中Service 和Endpoint 发生的变更,并针对这些信息在其所运行的节点上创建转发规则。
其关注集群的所有Service,以及对应的Endpoint 变更,并根据这些信息改写本机的iptables 或ipvs 规则。
这意味着,当前集群中所有节点的iptables/ipvs 规则完全一致,每台机器上的规则都包含了整个集群的完整负载均衡配置,
当kube-proxy 在iptables 模式下工作时,会对主机的iptables 进行改写,其目的是当用户访问服务的clusterIP、nodePort、LoadBalancerIP 地址时,能够通过iptables 的规则匹配完成数据转发。
当用户创建Service和对应的后端Pod时,Endpoints Controller会监控Pod的状态变化,
当Pod处于Running且准备就绪状态时,Endpoints Controller会生成Endpoints对象。 运行在每个节点上的Kube-proxy会监控Service和Endpoints的更新,并调用其Load Balancer模块在主机上刷新路由转发规则。
每个Pod都可以通过Liveness Probe和Readiness probe探针解决健康检查问题,当有Pod处于非准备就绪状态时,Kube-proxy会删除对应的转发规则。需要注意的是,Kube-proxy的Load Balancer模块并不那么智能,如果转发的Pod不能正常提供服务,它不会重试或尝试连接其他Pod。
Kube-proxy的Load Balancer模块实现有userspace、iptables和IPVS三种,当前主流的实现方式是iptables和IPVS。随着iptables在大规模环境下暴露出了扩展性和性能问题,越来越多的厂商开始使用IPVS模式。 Kube-proxy的转发模式可以通过启动参数--proxy-mode进行配置,有userspace、iptables、ipvs等可选项。
IPVS的工作原理
IPVS是Linux内核实现的四层负载均衡,是LVS负载均衡模块的实现。、
IPVS基于netfilter的散列表,相对于同样基于netfilter框架的iptables有更好的性能表现和可扩展性,具体见下文的实测对比数据。
IPVS支持TCP、UDP、SCTP、IPv4、IPv6等协议,也支持多种负载均衡策略,例如rr、wrr、lc、wlc、sh、dh、lblc等。
IPVS通过persistent connection调度算法原生支持会话保持功能
LVS的工作原理 简单说,当外机的数据包首先经过netfilter的PREROUTING链,然后经过一次路由决策到达INPUT链,再做一次DNAT后经过FORWARD链离开本机网路协议栈。由于IPVS的DNAT发生在netfilter的INPUT链,因此如何让网路报文经过INPUT链在IPVS中就变得非常重要了。一般有两种解决方法,一种方法是把服务的虚IP写到本机的本地内核路由表中;另一种方法是在本机创建一个dummy网卡,然后把服务的虚IP绑定到该网卡上。Kubernetes使用的是第二种方法,详见下文。
IPVS支持三种负载均衡模式:Direct Routing(简称DR)、Tunneling(也称ipip模式)和NAT(也称Masq模式)。 注:虽然有些版本的IPVS,例如华为和阿里自己维护的分支支持fullNAT,即同时支持SNAT和DNAT,但是Linux内核原生版本的IPVS只做DNAT,不做SNAT。因此,在Kubernetes Service的某些场景下,我们仍需要iptables。
DR IPVS的DR模式如图4-7所示。DR模式是应用最广泛的IPVS模式,它工作在L2,即通过MAC地址做LB,而非IP地址。在DR模式下,回程报文不会经过IPVS Director而是直接返回给客户端。因此,DR在带来高性能的同时,对网络也有一定的限制,即要求IPVS的Director和客户端在同一个局域网内。另外,比较遗憾的是,DR不支持端口映射,无法支撑Kubernetes Service的所有场景。 图4-7 IPVS的DR模式 Tunneling IPVS的Tunneling模式就是用IP包封装IP包,因此也称ipip模式
2.Kube-proxy IPVS模式参数 在运行基于IPVS的Kube-proxy时,需要注意以下参数:
·--proxy-mode:除了现有的userspace和iptables模式,IPVS模式通过--proxy-mode=ipvs进行配置;
·--ipvs-scheduler:用来指定IPVS负载均衡算法,如果不配置则默认使用roundrobin(rr)算法。支持配置的负载均衡算法有: -rr:轮询 -lc:最小连接 -dh:目的地址哈希 -sh:源地址哈希 -sed:最短时延 未来,Kube-proxy可能实现在Service的annotations配置负载均衡策略,这个功能应该也只有IPVS模式才能支持。
·--cleanup-ipvs:类似于--cleanup-iptables参数。如果设置为true,则清除在IPVS模式下创建的IPVS规则;
·--ipvs-sync-period:表示Kube-proxy刷新IPVS规则的最大间隔时间,例如5秒、1分钟等,要求大于0;
·-ipvs-min-sync-period:表示Kube-proxy刷新IPVS规则的最小时间间隔,例如5秒、1分钟等,要求大于0;
·--ipvs-exclude-cidrs:用于在清除IPVS规则时告知Kube-proxy不要清理该参数配置的网段的IPVS规则。因为我们无法区分某条IPVS规则到底是Kube-proxy创建的,还是其他用户程序的,配置该参数是为了避免误删用户自己的IPVS规则。 目前,本地local-up脚本、GCE安装脚本和kubeadm都支持通过导出环境变量(KUBE_PROXY_MODE=ipvs)切换到IPVS模式。在运行IPVS模式的Kube-proxy前,请确保主机上已经加载了IPVS所需的所有内核模块,如下所示: 最后,如果你需要在Kubernetes 1.11之前使用IPVS模式的Kube-proxy,需要打开Kubernetes的特性开关,形如--feature-gates=SupportIPVSProxyMode=true。
一旦创建一个Service和Endpoints,IPVS模式的Kube-proxy会做以下三件事情。
(1)确保一块dummy网卡(kube-ipvs0)存在。为什么要创建dummy网卡?因为IPVS的netfilter钩子挂载INPUT链,我们需要把Service的访问IP绑定在dummy网卡上让内核“觉得”虚IP就是本机IP,进而进入INPUT链。
(2)把Service的访问IP绑定在dummy网卡上。
(3)通过socket调用,创建IPVS的virtual server和real server,分别对应Kubernetes的Service和Endpoints。 下面我们用一个例子来说明:
滚动发布
Deployment是对RepcliaSet+滚动发布流程的一种包装。
控制器是支撑Kubernetes声明式API的关键组件,
它持续监视对API Server上的API对象的添加、更新和删除等更改操作,并在发生此类事件时执行目标对象相应的业务逻辑,从而将客户端的管理指令转为对象管理所需要的真正的操作过程。简单来说,一个具体的控制器对象是一个控制循环程序,它在单个API对象上创建一个控制循环以确保该对象的状态符合预期。不同类型的Pod控制器在不同维度完成符合应用需求的管理模式,例如Deployment控制器资源可基于“Pod模板”创建Pod实例,并确保实例数目精确反映期望的数量;另外,控制器还支持应用规模中Pod实例的自动扩容、缩容、滚动更新和回滚,以及创建工作节点故障时的重建等运维管理操作。图1-12中的Deployment就是这类控制器的代表实现,是目前最常用的用于管理无状态应用的Pod控制器。
工作流程
1.Deployment 控制器从 Etcd 中获取到所有携带了“app: nginx”标签的 Pod,然后统计它们的数量,这就是实际状态;
2.Deployment 对象的 Replicas 字段的值就是期望状态;
3.Deployment 控制器将两个状态做比较,然后根据比较结果,确定是创建 Pod,还是删除已有的 Pod
Deployment 控制器,由上半部分的控制器定义(包括期望状态),加上下半部分的被控制对象的模板组成的。
Deployment 实现了 Pod 的“水平扩展 / 收缩”(horizontal scaling out/in)
Deployment 实际上是一个两层控制器。
首先,它通过 ReplicaSet 的个数来描述应用的版本;
然后,它再通过 ReplicaSet 的属性(比如 replicas 的值),来保证 Pod 的副本数量。
滚动发布
滚动发布是一种高级发布机制,支持按批次滚动发布,用户体验不中断,适用于版本兼容发布。蓝绿发布则适用于版本不兼容发布。
K8s中的Deployment是对RepcliaSet+滚动发布流程的一种包装。
发布机制类似Replicaset
kind: Deployment
selector -> matchLabels
replicaSet
Deployment 控制器实际操纵的是ReplicaSet 对象,而不是 Pod 对象。
K8s中的ReplicaSet副本集可以实现应用集群
发布规范
kind: ReplicaSet
apiVersion: extensions/v1beta1
ReplicaSet具有自愈(self-healing)能力
样例:
statefulset
StatefulSet 的核心功能,就是通过某种方式记录这些状态,然后在 Pod 被重新创建时,能够为新 Pod 恢复这些状态。
应用场景
拓扑状态。应用的多个实例之间不是完全对等的关系。这些应用实例,必须按照某些顺序启动,它们再次被创建出来时也必须严格按照这个顺序才行。
并且,新创建出来的 Pod,必须和原来 Pod 的网络标识一样,这样原先的访问者才能使用同样的方法,访问到这个新 Pod。
存储状态。应用的多个实例分别绑定了不同的存储数据。对于这些应用实例来说,Pod A 第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间 Pod A 被重新创建过。这种情况最典型的例子,就是一个数据库应用的多个存储实例。
StatefulSet 这个控制器的主要作用
使用 Pod 模板创建 Pod 的时候,对它们进行编号,并且按照编号顺序逐一完成创建工作。
而当 StatefulSet 的“控制循环”发现 Pod 的“实际状态”与“期望状态”不一致,需要新建或者删除 Pod 进行“调谐”的时候,它会严格按照这些 Pod 编号的顺序,逐一完成这些操作。
高可用
kubelet 支持多种重启策略,配合Liveness Probe 可以保证应用出现故障时异常退出或不响应时能够自动重启;
ReplicaSet 和Deployment 控制器能够确保节点出现故障导致运行的Pod 被驱逐时,Pod 能被重建;
调度器可以保证新建Pod 能调到其他就绪节点,确保故障及时转移。
Pod 层面的高可用保证
实现了数据和配置与容器分离。
将配置保留在 ConfigMap 和 Secret 资源对象中, 数据则可存于PersistentVolumes 上。
因此,每当容器由于某种原因而崩溃时,Kubernetes 都会从不变的容器镜像中重新启动一个新的容器或者重新创建一个新的Pod,并将所有数据都传递给新的容器,而不管容器在哪个节点上启动。
也就是说,任何一个Pod 在故障发生时都能够被重启或替换。
Liveness Probe
通过 Exec、TCPSocket 或HTTPGet 定期检查容器的存活状态,
Kubernetes 根据检查结果进行主动重启。
Readiness Probe
亦是通过上面三种方法之一定期执行健康检查,判定容器是否准备就绪可以接收流量。
Pod 的所有容器都准备就绪时,即视为Pod 准备就绪。加到Service 的负载均衡列表中;
当Pod 尚未就绪时,会将其从Service 的负载平衡列表中删除。
在Pod 级别上,只需要设置合理的Probe 参数,即可准确控制容器的宕机时间,
最长的宕机时间为:检测周期×失败次数阈值+容器启动就绪时间。
对启动缓慢的容器进行Probe 时,应合理设置初始探针检查的延时(通过参数 “initialDelaySeconds” 设置),避免它们在启动和就绪之前被kubelet 杀死。
Node 级别的高可用性
多节点架构和可伸缩性功能确保了应用在Node 级别的高可用性。
一个应用程序作为一个实例运行在一个Pod 中。
当运行该Pod 的主机节点崩溃时,Kubernetes将会在满足其要求(CPU/内存资源、Toleration 和Affinity 等条件)的其他可用的健康节点上创建新的容器。
对于那些单实例应用程序,也可以从节点崩溃中恢复。
Cluster 层面的高可用保证 在Cluster 级别,
可以通过在多个集群上部署应用程序来实现。利用Kubernetes 的Federation 机制,建立集群联邦,通过集群联邦部署跨多云的服务
对于多云的混合解决方案,不仅可以获得更高级别的可用性,而且还可以获得更大的自由度和独立性,但同时也具有挑战性。
特别是对于有状态的应用程序,数据就是最大的挑战,需要找到多个集群中实例的同步数据的解决方案。
相应地,需要设计自己的持续集成和持续部署流程,自定义部署策略,无缝地将新的版本发布到所有集群中。
kubectl
图
Basic Commands (Beginner):
create Create a resource from a file or from stdin.
expose 使用 replication controller, service, deployment 或者 pod 并暴露它作为一个 新的 Kubernetes
Service
run 在集群中运行一个指定的镜像
set 为 objects 设置一个指定的特征
Basic Commands (Intermediate):
explain 查看资源的文档
get 显示一个或更多 resources
edit 在服务器上编辑一个资源
delete Delete resources by filenames, stdin, resources and names, or by resources and label selector
Deploy Commands:
rollout Manage the rollout of a resource
scale Set a new size for a Deployment, ReplicaSet or Replication Controller
autoscale 自动调整一个 Deployment, ReplicaSet, 或者 ReplicationController 的副本数量
Cluster Management Commands:
certificate 修改 certificate 资源.
cluster-info 显示集群信息
top Display Resource (CPU/Memory/Storage) usage.
cordon 标记 node 为 unschedulable
uncordon 标记 node 为 schedulable
drain Drain node in preparation for maintenance
taint 更新一个或者多个 node 上的 taints
Troubleshooting and Debugging Commands:
describe 显示一个指定 resource 或者 group 的 resources 详情
logs 输出容器在 pod 中的日志
attach Attach 到一个运行中的 container
exec 在一个 container 中执行一个命令
port-forward Forward one or more local ports to a pod
proxy 运行一个 proxy 到 Kubernetes API server
cp 复制 files 和 directories 到 containers 和从容器中复制 files 和 directories.
auth Inspect authorization
Advanced Commands:
diff Diff live version against would-be applied version
apply 通过文件名或标准输入流(stdin)对资源进行配置
patch 使用 strategic merge patch 更新一个资源的 field(s)
replace 通过 filename 或者 stdin替换一个资源
wait Experimental: Wait for a specific condition on one or many resources.
convert 在不同的 API versions 转换配置文件
kustomize Build a kustomization target from a directory or a remote url.
Settings Commands:
label 更新在这个资源上的 labels
annotate 更新一个资源的注解
completion Output shell completion code for the specified shell (bash or zsh)
Other Commands:
alpha Commands for features in alpha
api-resources Print the supported API resources on the server
api-versions Print the supported API versions on the server, in the form of "group/version"
config 修改 kubeconfig 文件
plugin Provides utilities for interacting with plugins.
version 输出 client 和 server 的版本信息
配置管理
◎ 用户将配置文件的内容保存到ConfigMap中,文件名可作为key,value就是整个文件的内容,多个配置文件都可被放入同一个ConfigMap。
◎ 在建模用户应用时,在Pod里将ConfigMap定义为特殊的Volume进行挂载。在Pod被调度到某个具体Node上时,ConfigMap里的配置文件会被自动还原到本地目录下,然后映射到Pod里指定的配置目录下,这样用户的程序就可以无感知地读取配置了。
◎ 在ConfigMap的内容发生修改后,Kubernetes会自动重新获取ConfigMap的内容,并在目标节点上更新对应的文件。
Secret。Secret也用于解决应用配置的问题,不过它解决的是对敏感信息的配置问题,
比如数据库的用户名和密码、应用的数字证书、Token、SSH密钥及其他需要保密的敏感配置。对于这类敏感信息,我们可以创建一个Secret对象,然后被Pod引用。
Secret中的数据要求以BASE64编码格式存放。注意,BASE64编码并不是加密的,在Kubernetes 1.7版本以后,Secret中的数据才可以以加密的形式进行保存,更加安全。
存储编排
Kubernetes支持Pod对象按需自动挂载不同类型存储系统,这包括节点本地存储、公有云服务商的云存储(如AWS和GCP等),以及网络存储系统,例如NFS、iSCSI、Gluster、Ceph、Cinder和Flocker等。
在基于Kubernetes的容器云平台上,对存储资源的使用需求通常包括以下几方面:
◎ 应用配置文件、密钥管理;
◎ 应用的数据持久化存储;
◎ 在不同的应用间共享数据存储。
Kubernetes的Volume抽象概念就是针对这些问题提供的解决方案。Kubernetes 的Volume类型非常丰富,从临时目录、宿主机目录、ConfigMap、Secret、共享存储(PV和PVC),到从v1.9版本引入的CSI(Container Storage Interface)机制,都可以满足容器应用对存储资源的需求。本节从Kubernetes支持的Volume类型、共享存储、CSI和存储资源的应用场景等方面对存储资源的管理进行说明。
资源配额和资源限制管理
在Kubernetes体系中,为了完成资源的配额管理和限制管理,可以从Namespace、Pod 和Container三个级别进行管理。
(1)在Container(容器)级别可以对两种计算资源进行管理:CPU 和内存。对它们的配置项又包括两种:一种是资源请求(Resource Requests),表示容器希望被分配到的可完全保证的资源量,Requests的值会被提供给Kubernetes调度器(Scheduler),以便于优化基于资源请求的容器调度;另一种是资源限制(Resource Limits),Limits是容器能用的资源上限,这个上限的值会影响在节点上发生资源竞争时的解决策略。图2-5显示了在设置CPU Request和Limit后,容器可以使用的CPU资源的范围。对于内存的设置是类似的。
2)在Pod级别,由于一个Pod可以包含多个Container,所以可以对Pod所含的全部容器所需的CPU和内存资源进行管理。在Kubernetes中,可以通过创建LimitRange资源对象完成设置。LimitRange对象作用于Namespace
(3)在Namespace级别,则可以通过对ResourceQuota资源对象的配置,提供一个总体的资源使用量限制:一方面可以设置该Namespace中Pod可以使用到的计算资源(CPU和内存)的总量上限;另一方面可以限制该Namespace中某种类型对象的总数量上限(包括可以创建的Pod、RC、Service、Secret、ConfigMap及PVC等对象的数量)。
未实现功能
以应用为中心的Kubernetes本身并未直接提供一套完整的“开箱即用”的应用管理体系,需要基础设施工程师基于云原生社区和生态的实际需求手动构建。
换句话说,在典型的生产应用场景中,Kubernetes还需要同网络、存储、遥测(监控和日志)、镜像仓库、负载均衡器、CI/CD工具链及其他服务整合,以提供完整且API风格统一的基础设施平台,如图1-17所示。
图
基础设施平台
下面对容器编排系统中的要素进行简单介绍。
Docker Registry和工件仓库:通过Harbor工件仓库、Docker Registry等项目实现。
网络:借助Flannel、Calico或WeaveNet等项目实现。
遥测:借助Prometheus和EFK栈(或者由Promtail、Loki和Grafana组成的PLG栈)等项目实现。
容器化工作负载:借助Kubernetes内置的工作负载控制器资源,甚至由社区扩展而来的各种Operator完成应用的自动化编排,包括自愈和自动扩缩容等;而便捷的应用打包则要借助Helm或Kustomize等项目完成。
基于容器编排系统的CI/CD:借助Jenkins、Tekton、Flagger或Kepton等项目,甚至遵循GitOps规范实现应用交付、发布和部署等。
异地多活
由于Service A依赖Service B、Service C,因此,如果在Kubernetes集群1中发生局部节点失败的故障,导致Service B不可用,那么在应用中调用Service A的代码就会失败。如何解决这个问题?考虑到此时在整个系统中,还有Kubernetes集群2与Kubernetes集群3中的Service B可以正常工作,所以,如果能让Kubernetes集群1中的Service A调用其他集群中健康的Service B,就能优雅地解决问题。但这个问题比较复杂,因为Kubernetes本身并没有提供解决方案。
一个可能的最直接的解决方案是改造Kubernetes的DNS模块。具体做法如下。
(1)首先,采用Headless Service建模我们的Service,此时Kubernetes DNS查询会直接返回该Service对应的后端Pod地址。 (2)其次,需要改造Kubernetes DNS模块,由我们自己接管这些Service的DNS记录的维护工作,因为我们要在Service的DNS记录中增加其他集群的访问地址。以Service C为例,假设它有3个Pod副本提供服务,在整个系统中就存在9个Pod副本,我们需要在每个Kubernetes集群中为Service C的DNS记录注入相关的9个Pod的入口访问地址,并且自己实现从Pod实例到DNS记录数据的同步逻辑,这就需要我们改造Kubernetes的DNS组件。虽然从理论上来说这不难实现,但从代码实现来看的确很有难度,因为需要深入掌握Kubernetes的源码,才可设计出优雅的实现。
基于Spring Cloud的Kubernetes微服务架构方案与Kubernetes DNS改造的方案相比,不但更简单地解决了在多中心集群中局部节点失败导致服务不可用的问题,也解决了不同中心之间的服务访问性能问题。我们知道,Kubernetes集群1中的Service A在调用本集群中的Service C实例时,要比调用其他两个集群中的Service C实例快得多,因为一个是局域网,一个是城域网或者广域网,因此,我们在做服务的负载均衡时,不能采用简单的轮询方式,而是要以服务的时延或者所处的区域(zone)为参考指标,实现某种差别化的负载均衡算法。Spring Cloud中的Ribbon负载均衡组件基于zone的亲和性特性恰恰实现了我们的目标,所以利用ZonePreferenceServerListFilter,Spring Cloud能够优先过滤出请求调用方处于同区域的服务实例。
CustomResourceDefinitions(自定义资源定义,简称CRD)。
自定义资源(Custom Resource)就是一种常见的扩展方式,即可将自己定义的资源添加到Kubernetes系统中。
Kubernetes系统附带了许多内置资源,但是仍有些需求需要使用自定义资源来扩展Kubernetes的功能。CustomResourceDefinitions(自定义资源定义,简称CRD)。
开发者通过CRD可以实现自定义资源,它允许用户将自己定义的资源添加到Kubernetes系统中,并像使用Kubernetes内置资源一样使用这些资源,例如,在YAML/JSON文件中带有Spec的资源定义都是对Kubernetes中的资源对象的定义,所有的自定义资源都可以与Kubernetes系统中的内置资源一样使用kubectl或client-go进行操作。
运维相关
问题排查
首先,查看Kubernetes对象的当前运行时信息,特别是与对象关联的Event事件。
这些事件记录了相关主题、发生时间、最近发生时间、发生次数及事件原因等,对排查故障非常有价值。此外,通过查看对象的运行时数据,我们还可以发现参数错误、关联错误、状态异常等明显问题。
由于Kubernetes中多种对象相互关联,因此,这一步可能会涉及多个相关对象的排查问题。
其次,对于服务、容器的问题,则可能需要深入容器内部进行故障诊断,此时可以通过查看容器的运行日志来定位具体问题。
最后,对于某些复杂问题,比如Pod调度这种全局性的问题,可能需要结合集群中每个节点上的 Kubernetes 服务日志来排查。
比如搜集 Master 上 kube-apiserver、kube-schedule、kube-controler-manager服务的日志,以及各个Node节点上的kubelet、kube-proxy服务的日志,综合判断各种信
常见问题
1.由于无法下载pause镜像导致Pod一直处于Pending的状态
(1)如果服务器可以访问Internet,并且不希望使用HTTPS的安全机制来访问gcr.io,则可以在Docker Daemon的启动参数中加上--insecure-registry gcr.io来表示可以进行匿名下载。
(2)如果Kubernetes的集群环境在内网环境中,无法访问gcr.io网站,则可以先通过一台能够访问gcr.io的机器将pause镜像下载下来,导出后,再导入内网的Docker私有镜像库中,并在kubelet的启动参数中加上--pod_infra_container_image,配置为:
2.Pod创建成功,但状态始终不是Ready,且RESTARTS的数量持续增加
問題排查
图
组件异常诊断
各组件异常分析
Master组件对集群做出全局性决策(比如调度),以及检测和响应集群事件。
如果Master组件出现问题,就可能会导致集群不可访问、Kubernetes API访问出错、各种控制器无法工作等。
节点组件在每个节点上运行,维护运行的Pod并提供Kubernetes运行时环境。
如果节点组件出现问题,就可能会导致该节点异常并且该节点Pod无法正常运行和结束。因此,根据不同的组件,可能会出现不同的异常。
kube-apiserver对外暴露了Kubernetes API,如果kube-apiserver出现异常就可能会导致:
集群无法访问,无法注册新的节点。
资源(Deployment、Service等)无法创建、更新和删除。
现有的不依赖Kubernetes API的Pods和services可以继续正常工作。
etcd用于Kubernetes的后端存储,所有的集群数据都存在这里。
保持稳定的etcd集群对于Kubernetes集群的稳定性至关重要。因此,我们需要在专用计算机或隔离环境上运行etcd集群以确保资源需求。
当etcd出现异常时可能会导致:
kube-apiserver无法读写集群状态,apiserver无法启动。
Kubernetes API访问出错。
kubectl操作异常。
kubelet无法访问apiserver,仅能继续运行已有的Pod。
kube-controller-manager和kube-scheduler分别用于控制器管理和Pod的调度,如果出现问题就可能导致: ·相关控制器无法工作。
资源(Deployment、Service等)无法正常工作。
无法注册新的节点。
Pod无法调度,一直处于Pending状态。
kubelet是主要的节点代理,如果节点宕机(VM关机)或者kubelet出现异常(比如无法启动),那么可能会导致:
该节点上的Pod无法正常运行,如果节点关机,那么当前节点上所有Pod都将停止运行。
已运行的Pod无法伸缩,也无法正常终止。
·无法启动新的Pod。
·节点会标识为不健康状态。
·副本控制器会在其他节点上启动新的Pod。
·kubelet有可能会删掉当前运行的Pod。
CoreDNS
(在1.11以及以上版本的Kubernetes中,CoreDNS是默认的DNS服务器)是k8s集群默认的DNS服务器,如果出现问题就可能导致:
无法注册新的节点。
集群网络出现问题。
Pod无法解析域名。
kube-proxy是Kubernetes在每个节点上运行网络代理。如果出现了异常,就可能导致该节点Pod通信异常。
诊断分析
健康状态检查——初诊
1.组件、插件健康状态检查 使用命令: kubectl get componentstatus
或 kubectl get cs
健康情况下运行结果如图8-48所示。 图8-48 Kubernetes组件(插件)部分默认基于systemd运行,比如kubelet、Docker等,我们需要使用以下命令确保其处于活动(active)状态: systemctl status kubelet docker
大部分Kubernetes组件运行在命名空间为“kube-system”的静态Pod之中(参见“kubeadm init”一节),
查看这些Pod的状态: kubectl get Pods -o wide -n kube-system
1.查看日志
(1)使用journalctl查看服务日志 主流的Linux系统基本上都采用Systemd来集中管理和配置系统。
使用journalctl命令来查看服务日志(比如docker): journalctl -u docker
查看并追踪kubelet的日志: journalctl -u kubelet –f
(2)使用“kubectl logs”查看容器日志
kubectl logs [-f] [-p] (POD | TYPE/NAME) [-c CONTAINER] [options]
2.查看资源详情和事件
kubectl describe命令。
kubectl describe命令用于查看一个或多个资源的详细情况,包括相关资源和事件,语法如下: kubectl describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME)
(1)查看节点 查看指定节点: kubectl describe nodes k8s-node1
查看所有节点: kubectl describe nodes
查看指定节点以及事件: kubectl describe nodes k8s-node1--show-events
注意 Node状态为NotReady时,通过查看节点事件有助于我们排查问题。
(2)查看Pod 查看指定Pod: kubectl describe Pods gitlab-84754bd77f-7tqcb
查看指定文件描述的所有资源: kubectl describe -f teamcity.yaml
如上述语法所示,“kubectl get”拥有强大的格式化输出能力,支持“json”“yaml”等,在kubectl一节中我们已经讲解过。这里主要用“-o”来查看资源配置。 (1)查看指定Pod配置 yaml版的命令如下: kubectl get Pods mssql-58b6bff865-xdxx8 -o yaml
3.查看资源配置
“kubectl get”命令
语法如下: kubectl get [(-o|--output=)json|yaml|wide|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=...] (TYPE[.VERSION][.GROUP] [NAME | -l label] | TYPE[.VERSION][.GROUP]/NAME ...) [flags] [options]
kubectl get Pods mssql-58b6bff865-xdxx8 -o yaml
容器调测
Master高可用部署架构
Master节点扮演着总控中心的角色,通过不间断地与各个工作节点(Node)通信来维护整个集群的健康工作状态,集群中各资源对象的状态则被保存在etcd数据库中。
如果Master不能正常工作,各Node就会处于不可管理状态,用户就无法管理在各Node上运行的Pod
如果Master以不安全方式提供服务(例如通过HTTP的8080端口号),则任何能够访问Master的客户端都可以通过API操作集群中的数据,可能导致对数据的非法访问或篡改。
在正式环境中应确保Master的高可用,并启用安全访问机制,至少包括以下几方面。
◎ Master的kube-apiserver、kube-controller-mansger和kube-scheduler服务至少以3个节点的多实例方式部署。
◎ Master启用基于CA认证的HTTPS安全机制。
◎ etcd至少以3个节点的集群模式部署。
◎ etcd集群启用基于CA认证的HTTPS安全机制。
◎ Master启用RBAC授权模式(详见6.2节的说明)。
在Master的3个节点之前,应通过一个负载均衡器提供对客户端的唯一访问入口地址,负载均衡器可以选择硬件或者软件进行搭建。软件负载均衡器可以选择的方案较多,本文以HAProxy搭配Keepalived为例进行说明。
部署图
主流硬件负载均衡器有F5、A10等,需要额外采购,其负载均衡配置规则与软件负载均衡器的配置类似,本文不再赘述。
部署Node的服务
在Node上需要部署Docker、kubelet、kube-proxy,在成功加入Kubernetes集群后,还需要部署CNI网络插件、DNS插件等管理组件。Docker的安装和启动详见Docker官网的说明文档。本节主要对如何部署kubelet和kube-proxy进行说明。
垃圾清理
Kubernetes系统在长时间运行后,Kubernetes Node会下载非常多的镜像,其中可能会存在很多过期的镜像。
同时因为运行大量的容器,容器退出后就变成死亡容器,将数据残留在宿主机上,这样一来,过期镜像和死亡容器都会占用大量的硬盘空间。如果硬盘空间被用光,可能会发生非常糟糕的情况,甚至会导致硬盘的损坏。为此,Kubelet会进行垃圾清理工作,即定期清理过期镜像和死亡容器。
12.5.1 镜像清理
镜像清理的策略是当硬盘空间使用率超过阈值的时候开始执行,Kubelet执行清理的时候优先清理最久没有被使用的镜像。 硬盘空间使用率的阈值通过Kubelet的启动参数--image-gc-high-threshold和--image-gc-low-threshold指定。
12.5.2 容器清理 Kubelet容器清理的相关参数如表12-7所示。 表12-7
Kubelet容器清理参数 Kubelet定时执行容器清理,每次根据以上3个参数选择死亡容器删除,通常情况下优先删除创建时间最久的死亡容器。Kubelet不会删除非Kubelet管理的容器。
参数
Dockerfile 编写原则 Dockerfile 的编写原则有如下三个:
● 要考虑分层的复用 在Dockerfile 中写的每一个命令都会编译成镜像的一层(layer)。
理论上缩小层级可以减少容器的镜像大小。但是如果将所有的指令写成一行,生成同一镜像层,就不能充分利用镜像分层在镜像编译存储和拉取上可以共性的优势。因此,推荐将具有共性的操作,例如JDK、Pyython 包的安装等单独作为一层,而其他命令要尽可能合并。
在构建镜像的过程中,经常变化的内容和基本不会变化的内容要分离开来,我们称之为动静分离。把基本不会变化的内容放在下层,创建出不同的基础镜像供上层使用。比如,可以创建各种语言(例如Golang、Java 和Python 等)的基础镜像,这些基础镜像包含最基本的语言库。我们可以在此基础上继续构建应用级别的镜像。镜像的动静分离也会缩短连续多次构建的时间。不变化的内容所在的层在一次编译后就存在了。在后续构建过程中,可以直接使用已经存在的层,而不会重新运行一次。
● 要考虑安装包的缓存清理
在Dockerfile 中指定安装包,需要在包安装结束后进行清理,示例代码如下: 清理操作需要通过&&命令和安装操作命令在同一条命令中完成,而不要另外增加一条命令来执行,因为容器镜像在构建的时候,每一条命令都会形成一个镜像层。如果清理操作独立成一条指令,则会形成两个镜像层,达不到进行镜像瘦身的目的。 与安装包一样,如果有其他临时文件信息,也需要进行清理。
● 多阶段构建
多阶段构建可以在Dockerfile 中使用多个FROM 语句,基于FROM 语句开始不同基础镜像的构建新阶段。我们可以有选择地将工件从一个阶段复制到另一个阶段,在最终镜像中只留下所需要的内容。多阶段构建的示例代码如下: 代码运行结果是产生了alpine 的最新镜像,并添加了hello 可执行文件的最终镜像。多阶段构建使得我们无须在本地系统中安装Golang 环境,也不会在本地系统中产生多余的文件。Golang 的SDK 和任何中间层都不会保存在最终镜像中。 3.容器运行的主进程 在容器诞生的早期,提倡一个容器对应一个进程,但是随着实践化案例的不断丰富,一个容器对应一个进程在很多场景下已经不再适用。因此,现在主要强调将提供单独服务的模块归类为一个容器,例如,将一个应用的数据库、代理、UI 等模块分别运行在不同的容器下。在这种场景下,一个服务模块可能会创建多个进程,而这些进程的启动和回收需要由容器的主进程来管理。 容器的主进程对由它启动的所有进程进行管理,主进程需要能够满足如下条件:
● 负责业务进程的启动,可以管理所有的业务进程。
● 可以接管孤儿进程和回收僵尸进程。
● 支持SIGTERM 信号的处理,可以向子进程传播SIGTERM 信号,优雅地结束所有子进程后,退出。 对于多进程场景下容器的主进程的选择,主要方案有Sysvinit、Upstart、Systemd、Tini、Supervisor 等,也可以通过自定义脚本更轻量级地完成主进程的任务。
容器化带来的影响
容器化带来的最直接的影响就是容器中看到的CPU 和内存大小是主机的CPU 和内存大小。很多类型的应用(例如Java 应用)会根据检测到的CPU 和内存资源信息进行运行参数的设置,而主机的CPU 和内存资源往往不是容器申请的CPU 和内存资源,这类应用的容器化必须解决资源的检测问题。 以Java 为例,早期的Java JDK 版本并不能发现自己运行在容器之内并通过CGroup来获取应用的内存和CPU 限制,而是获取了主机的CPU 和内存资源。这样存在两个问题:
● JVM 通过发现的CPU 个数来决定启动的线程数量,如果容器的CPU 资源申请比较少,但是创建的线程数量却与主机的CPU 核相匹配,那么会导致频繁的上下文切换从而产生大量开销。
● JVM 的堆大小默认是发现的内存大小的¼,如果容器的CGroup 限制内存比该值小,那么JVM 进程会经常发生OOM(Out Of Memory,内存溢出)。 最理想的解决这两个问题的方案就是升级JDK 版本,但是受限于实际应用程序的需求等,JDK 版本有时无法升级到需求的版本,所以需要有其他解决方案。
容器化的额外开销主要有以下几方面:
● CGroup 带来的额外开销。与运行在物理机上相比,经过Kubernetes Pod 启动的容器,会限制在4 级和5 级深度的CGroup 上,详见2.4.4 节中对于节点CGroup 层级的描述。经过压测对比,有些应用甚至会有2%左右的性能下降。
● 使用Docker 做运行时,可以支持json-file、syslog、journald 等多种日志驱动。向日志驱动发送日志,可以有阻塞(Blocking Mode)和非阻塞(Non-Blocking Mode)两种方式。Docker 下默认是阻塞模式,好处是不丢日志,但是如果有应用大量写日志,可能会导致应用一直处在阻塞状态,影响应用性能,甚至可能导致应用出现一些意想不到的错误。而非阻塞模式不会阻塞应用,代价就是没有发送给日志驱动的旧的日志可能被新的日志覆盖,从而导致丢失。
● 在应用Pod 容器运行的同一节点上可能还会有其他Pod 的容器。Pod 在被调度时是根据容器申请内存值的,而不是根据容器限制的内存值来进行调度的,并且节点上的容器可能会造成系统的内存压力。因此,与运行在虚拟机或物理机相比,应用容器的Page Cache/Buffer 可能会更经常被清理。
● 如果Pod 独占节点,则会有kubelet、kube-proxy、运行时等模块带来的节点资源开销。
● 容器网络架构带来的延时和抖动。 与运行在虚拟机或物理机上的网络不同,数据访问容器需要经过路由和Veth Pair 端口,还需要经过iptables 或IPVS 规则的处理,因此会产生额外的延时。数据经过Veth Pair时会触发软中断,如果软中断由于比较长的系统调用或者系统时钟中断执行等原因得不到及时处理,会产生意想不到的延时抖动。
资源规划可以显著提高应用的可用性。资源的规划包含多个维度,主要有以下几方面:
1.每个应用容器的CPU 和内存资源 不同的应用对CPU 和内存的利用会有所不同。有些应用会固定占有一定的CPU 和内存资源,在不同的时间段,资源的使用率差别不大。而有些应用会带有周期性的任务,在执行周期性任务的时候,资源使用会比较多,而当周期性任务执行完成后,资源的使用率就会下降很多。前者推荐部署成Guaranteed Pod 类型,spec.containers[].resources.limits.cpu和spec.containers[].resources.limits.memory 设置为资源消耗的最大值加上一定的预留值。后者建议部署为 Burstable Pod 类型, spec.containers[].resources.limits.cpu 和spec.containers[].resources.limits.memory 设置为资源使用的最大值加上一定的预留值,而spec.containers[].resources.requests.cpu 和spec.containers[].resources.requests.memory 设置为大部分时间的使用值,以提高节点的资源利用率。由于节点的CPU 和内存资源都是超售的,所以当资源的requests 和limits 差别越大时,节点的资源超售就越厉害。如果发现相应节点的load 值一直比较高,或者经常出现有容器出现OOM 的情况,则超售比过高,需要增加容器的资源requests,以减少同一个节点上能够调度的Pod 数量。 2.应用的存储资源 应用的存储资源包含多方面的考量: ● 存储的大小。 每个容器使用的存储的大小。 ● 容器对存储IOPS 的需求。 容器对IOPS 的需求,是决定是否采用某些类型存储的首要依据。 ● 是否需要不同的Pod 之间共享同一个存储卷。 是否需要卷支持RWM(Read Write Many)文件系统类型的存储,以便让运行在不同节点上的Pod 使用同一个存储卷,而对于块类型的存储卷,则一般不支持这种应用场景。 ● 使用本地存储还是网络存储。 对IOPS 要求比较高并且渴望实现低成本的应用而言,需要使用本地存储。但是,本地存储和节点具有强相关性,如果本地存储的节点产生问题,那么数据会丢失,应用是否可以通过多备份来解决这个问题呢?另外,还需要考虑PVC 的处理,需要把PVC 和Pod进行重建,才可以在其他的节点上运行起来。 相对于本地存储,网络存储比较容易使用,需要考虑的问题也相对较少,但是也面临IOPS 可能会比较低和成本较高的问题。 应用程序需要检测容器的根文件系统是否已被写入数据(特别是部署开源镜像的时候),如果是,需要外挂一个存储到该目录,或者修改该行为,将数据存储到其他卷内。 如果Pod 使用了emptyDir 卷,那么需要对存储到emptyDir 卷的数据(例如log)进行清理操作,否则可能会把节点空间写满,导致运行在节点上的所有容器都出问题。对emptyDir 卷需要设置sizeLimit,当写的数据超过sizeLimit 后,会被kubelet 驱逐。一般节点的根分区空间不会设置得很大,所以不建议用户通过emptyDir 卷存储大量的数据。 3.网络带宽需求 很多应用(例如日志搜集或者存储应用)会产生大量的网络流量,对网络的带宽要求比较高。如果与其他的应用部署在同一个节点,会影响其他容器的网络链路。因此,可以考虑用专门的节点来部署这种类型的应用。 另外,如果流量的管理使用负载均衡器(特别是硬件的负载均衡器),则需要对此类型的应用分配特定的设备,以防止大量的流量导致其他使用同一个负载均衡器的应用数据传输出现问题。 4.应用部署的物理拓扑分布需求 对于应用的多实例,要考虑是否需要跨不同的物理拓扑进行部署。例如,常见的跨机架部署将不同的应用实例分布到不同机架的节点上,防止一个机架由于电源或者交换机的故障而导致所有的应用实例下线。因此,集群中需要有足够多的供应用部署的不同机架的机器。 另外,要考虑跨数据中心部署、异地多活,以防止某个数据中心出问题。
升级
在进行Kubernetes的版本升级之前,需要考虑不中断正在运行的业务容器的灰度升级方案。常见的做法是:
先更新Master上Kubernetes服务的版本,再逐个或批量更新集群中的Node上Kubernetes服务的版本。
更新Node的Kubernetes服务的步骤通常包括:先隔离一个或多个Node的业务流量,
等待这些Node上运行的Pod将当前任务全部执行完成后,停掉业务应用(Pod),再更新这些Node上的kubelet和kube-proxy版本,
更新完成后重启业务应用(Pod),并将业务流量导入新启动的这些Node上,
再隔离剩余的Node,逐步完成Node的版本升级,最终完成整个集群的Kubernetes版本升级。
同时,应该考虑高版本的Master对低版本的Node的兼容性问题。高版本的Master通常可以管理低版本的Node,但版本差异不应过大,以免某些功能或API版本被弃用后,低版本的Node无法运行。
◎ 通过官网获取最新版本的二进制包kubernetes.tar.gz,解压后提取服务的二进制文件。
◎ 更新Master的kube-apiserver、kube-controller-manager、kube-scheduler服务的二进制文件和相关配置(在需要修改时更新)并重启服务。
◎ 逐个或批量隔离Node,等待其上运行的全部容器工作完成后停掉Pod,更新kubelet、kube-proxy服务文件和相关配置(在需要修改时更新),然后重启这两个服务。
Docker
定义
namespace和cgroup
Namespace
一种 Linux Kernel 提供的资源隔离方案
并保证不同的 Namespace 资源独立分配、进程彼此隔离,即不同的 Namespace 下的进程互不干扰 。
系统可以为进程分配不同的 Namespace;
Cgroups (Control Groups)
Linux 下用于对一个或一组进程进行资源控制和监控的机制
可以对诸如 CPU 使用时间、内存、磁盘 I/O 等进程所需的资源进行限制;
不同资源的具体管理工作由相应的 Cgroup 子系统(Subsystem)来实现 ;
针对不同类型的资源限制,只要将限制策略在不同的的子系统上进行关联即可 ;
Cgroups 在不同的系统资源管理子系统中以层级树(Hierarchy)的方式来组织管理:每个
Cgroup 都可以包含其他的子 Cgroup,因此子 Cgroup 能使用的资源除了受本 Cgroup 配置
的资源参数限制,还受到父 Cgroup 设置的资源限制 。
区别
namespace: 环境隔离
PID
UTS
IPC
Network
Mount
User
cgroup: 资源限制
memory
cpu
Cpuset
Blkio
容器:
Namespace 和 Cgroups 可以让程序在一个资源可控的独立(隔离)环境中运行,这个就是容器了
docker和VM差异:
docker是一个应用层的抽象,容器之间通过网络命名空间进行隔离,多个容器共享同一个操作系统内核。VM是对物理硬件层的抽象,每个VM都包含独立的操作系统,重且启动缓慢。VM主要为了提供系统环境,容器主要是为了提供应用环境。
Docker容器都可以有四种状态。
运行 已暂停 重新启动 已退出
docker组件:
docker引擎【包含Docker客户端&服务端】,
docker镜像,
docker容器,
Registry【镜像仓库】
docker的架构: C/s架构
如何控制容器占用系统资源(CPU,内存)的份额
在使用docker create命令创建容器或使用docker run 创建并运行容器的时候,可以使用-c|–cpu-shares[=0]参数来调整同期使用CPU的权重,使用-m|–memory参数来调整容器使用内存的大小。
容器和镜像的区别:
镜像是一个只读模板,包括运行容器所需的数据,其内容在构建之后就不会被改变,可以用来创建新的容器。 镜像由多个只读层组成,容器在只读层的基础上多了一个读写层。
优势
封装性:
不需要再启动内核,所以应用扩缩容时可以秒速启动。
资源利用率高,直接使用宿主机内核调度资源,性能损失小。
方便的 CPU、内存资源调整。
能实现秒级快速回滚。
一键启动所有依赖服务,测试不用为搭建环境犯愁,PE 也不用为建站复杂担心。
镜像一次编译,随处使用。
测试、生产环境高度一致(数据除外)
隔离性:
应用的运行环境和宿主机环境无关,完全由镜像控制,一台物理机上部署多种环境的镜像测试。
多个应用版本可以并存在机器上。
镜像增量分发:
由于采用了 Union FS, 简单来说就是支持将不同的目录挂载到同
一个虚拟文件系统下,并实现一种 layer 的概念,每次发布只传输
变化的部分,节约带宽。
组件
为了兼容OCI规范,Docker项目自身也做了架构调整,自1.11.0版本起,Docker引擎由一个单一组件拆分成了Docker Engine(docker-daemon)、containerd、containerd-shim和runC等4个独立的项目,并把containerd捐赠给了CNCF。
containerd是一个守护进程,它几乎囊括了容器运行时所需要的容器创建、启动、停止、中止、信号处理和删除,以及镜像管理(镜像和元信息等)等所有功能,
并通过gRPC向上层调用者公开其API,可被兼容的任何上层系统调用,
例如Docker Engine或Kubernetes等容器编排系统,并由该类系统负责镜像构建、存储卷管理和日志等其他功能。
然而,containerd只是一个高级别的容器运行时,并不负责具体的容器管理功能,它还需要向下调用类似runC一类的低级别容器运行时实现此类任务。
containerd又为其自身和低级别的运行时(默认为runC)之间添加了一个名为containerd-shim的中间层,以支持多种不同的OCI运行时,并能够将容器运行为无守护进程模式。
这意味着,每启动一个容器,containerd都会创建一个新的containerd-shim进程来启动一个runC进程,并能够在容器启动后结束该runC进程。
Docker项目组件架构与运行容器所示。
图
网络模型
Null(--net=None)
把容器放入独立的网络空间但不做任何网络配置;
用户需要通过运行 docker network 命令来完成网络配置。
Host
使用主机网络命名空间,复用主机网络。
Container
重用其他容器的网络。
Bridge(--net=bridge)
使用 Linux 网桥和 iptables 提供容器互联,
Docker 在每台主机上创建一个名叫 docker0的网桥,通过 veth pair 来连接该主机的每一个 EndPoint。
Overlay(libnetwork, libkv)
• 通过网络封包实现。
Remote(work with remote drivers)
Underlay:
• 使用现有底层网络,为每一个容器配置可路由的网络 IP。
Overlay:
• 通过网络封包实现。
默认使用bridge网络模型,容器的初次启动会虚拟化出来一个新的网卡名为docker0,
在多机器部署下docker0地址可能会冲突。所以docker对多机部署支持的不够友好。
命令操作
都是容器操作指令:
tu
CMD 用于指定容器启动时候默认执行的命令。可以被docker run指定的启动命令覆盖。
ENTRYPONIT 指令可让容器以应用程序或者服务的形式运行。一般不会被docker run指定的启动命令覆盖。dockerfile中的多个CMD & ENTRYPONIT 只有最后一个会生效。
注意区别 docker run 和RUN 一个是容器启动命令,一个是镜像构建时候所用。
copy & add
ADD & COPY 选取目标文件复制到镜像当中。是针对镜像的指令,唯一差别在于add源文件可以支持url且可以对压缩文件进行解压操作。而copy针对的是当前构建环境。
docker-compose & docker swarm
使用Docker compose可以用YAML文件来定义一组需要启动的容器,以及容器运行时的属性。docker-compose用来对这一组容器进行操作。
docker swarm 原生的Docker集群管理工具,依赖docker本身,很多重要功能依赖团队二次开发。且社区不够活跃,一般公司生产环境会选择k8s,个人项目或者容器数量较少可选swarm,只需要docker即可完成,相对较轻。
构建原则
基础系统镜像
尽量选取满足需求但较小的
建议选择 debian:wheezy 镜像,仅有
86MB 大小
清理编译生成文件、安装包的缓存等临时文件
安装各个软件时候要指定准确的版本号,并避免引入不需要的依赖
从安全的角度考虑,应用尽量使用系统的库和依赖
使用 Dockerfile 创建镜像时候要添加.dockerignore 文件或使用干净的工作目录
Docker启动
docker run命令执行流程图
初始化时也是将 rootfs 以 readonly 方式加载并检查,然而接下来利用 union mount 的方式将一个 readwrite 文件系统挂载在 readonly 的 rootfs 之上;
并且允许再次将下层的 FS(file system) 设定为 readonly 并且向上叠加;这样一组 readonly 和一个 writeable 的结构构成一个 container 的运行时态, 每一个 FS 被称作一个 FS 层。
istio
what
开箱即用的提升服务弹性的功能,
包括负载均衡、连接池、健康检测、熔断、超时、重试、限流,这些功能都是服务治理的必备功能。通过组合使用这些功能,可以让服务更具弹性。
·负载均衡。当服务有多个实例时,通过负载均衡器来分发请求,负载均衡器一般会提供轮询、随机、最少连接、哈希等算法。
·连接池。当服务调用上游服务时,可以提前创建好到上游服务的连接,当请求到来时,通过已经创建好的连接直接发送请求给上游服务,减少连接的创建时间,从而降低请求的整体耗时。我们把这些提前创建好的连接集合称为连接池。
·健康检测。当上游服务部分实例出现故障时,健康检测机制能自动检测到上游服务的不可用实例,从而避免把请求发送给上游不可用的服务实例。
·限流。当服务并发请求激增,流量增大,如果没有使用任何限流措施,这很有可能导致我们的服务无法承受如此之多的请求,进而导致服务崩溃,还可能会影响整个应用的稳定性。限流功能是当请求数过多时,直接丢掉过多的流量,防止服务被压垮,保证服务稳定。
·超时。当上游服务响应时间过长,很有可能会增加请求的整体耗时,影响服务调用方。通过设置服务调用的超时时间,当服务没有在规定的时间内返回数据,就直接取消此次请求,这样可以防止服务提供方拖垮服务调用方,防止请求的总耗时时间过长。
·重试。网络出现抖动,或者被调用的服务出现瞬时故障,这些问题都是偶发瞬时的,只需要再重新调用一次后端服务,可能请求就会成功。这种场景下就需要服务的重试功能,防止由于被调用方的服务偶发瞬时故障,导致出现服务调用不可用的情况,从而影响应用的整体稳定性与可用性。
·熔断。后端服务调用偶尔出现失败是正常情况,但是当对后端服务的调用出现大比例的调用失败,此时可能由于后端服务已经无法承受当前压力,如果我们还是继续调用后端服务,不仅不能得到响应,还有可能会把后端服务整个压垮。所以当服务出现大比例调用失败时,应停止调用后端服务,经过短暂时间间隔后,再尝试让部分请求调用后端服务,如果服务返回正常,我们就可以让更多的请求调用后端服务,直到服务恢复为正常情况。如果仍然出现无法响应的情况,我们将再次停止调用服务,这种处理后端服务调用的机制就称为熔断。 虽然一个服务出现故障的概率可能很低,但是当服务数量变多,低概率的故障事件就会必然发生。当服务数量持续增长,服务间的调用关系复杂,如果不做服务故障处理,提升服务的弹性,就很有可能因为一个服务的故障导致其他服务也出现故障,进而导致服务出现级联故障,甚至可能导致整个应用出现不可用的现象。这会严重影响我们的应用拆分为微服务后的服务可用性指标,使整个应用的用户体验变差,导致用户流失,进而影响业务发展。如果经常出现这种故障,这对业务来说无疑是毁灭性的打击。
流量管理(Pilot)
图
1
流量路由管理方式:
基于比例的流量拆分、
Istio根据输入的流量比例来确定流量分发的比重,范围限制为[0,100]。
图
基于请求内容的流量导向。
基于浏览器的分发:选择允许访问的浏览器。
基于操作系统的分发:选择允许访问的操作系统。
基于Cookie的分发:使用正则表达式来匹配Cookie内容。
图
基于HTTP请求头(header)的分发:使用正则表达式来匹配自定义的请求头。
南北流量
使用Ingress将Kubernetes中的应用暴露成对外提供的服务,针对这个对外暴露的服务可以实现灰度发布、流量管理等。我们把这种流量管理称为南北向流量管理,也就是入口请求到集群服务的流量管理。
东西流量
Istio还有侧重于集群内服务网格之间的流量管理,我们把这类管理称为东西向流量管理。本章重点介绍Istio在东西向流量方面的路由控制能力,下一章介绍南北向流量控制。
策略执行(mixer)
图
图
将组织策略应用于服务之间的互动,确保访问策略得以执行,资源在消费者之间良好分配。策略的更改是通过配置网格而不是修改应用程序代码。
Mixer提供了一种配置表达式语言(Mixer conf iguration expression language,或缩写CEXL),用来指定Mixer遥测策略配置的匹配表达式和映射表达式。
CEXL表达式将一组类型化的属性和常量映射到类型化的值。
CEXL表达式支持一部分的Go语言表达式,并以之作为CEXL的语法。
CEXL表达式自己实现了一部分Go语言的操作符,所以它只支持一部分Go语言操作符。
在CEXL表达式里可以任意加上括号。表8-1列出了CEXL表达式支持的功能。
可观察性:
集成zipkin等服务,快速了解服务之间的依赖关系,以及它们之间流量的本质和流向,从而提供快速识别问题的能力。
服务身份和安全(Istio-auth)
图
为网格中的服务提供可验证身份,并提供保护服务流量的能力,使其可以在不同可信度的网络上流转。
基本概念
Sidecar(边车):
Sidecar自定义资源描述了Sidecar代理的配置,该代理协调与其连接的工作负载实例的入站和出站通信。默认情况下,Istio将为网格中的所有Sidecar代理进行配置,使其具有到达网格中每个工作负载实例所需的必要配置,并接受与工作负载关联的所有端口上的流量。Sidecar资源提供了一种细粒度调整端口、协议的方法,使得代理能接受向工作负载转发流量或从工作负载转发流量。此外,可以限制代理在从工作负载实例转发出站流量时可以达到的服务集。
服务(Service):
绑定到服务注册表中唯一名称的应用程序行为单位。服务由运行在pod、容器、虚拟机上的工作负载实例实现的多个网络端点组成。
服务版本(Service versions):
也称为子集(subsets),在持续部署场景中,对于给定服务,可能会存在运行着应用程序二进制文件的不同变种的不同实例子集。这些变种不一定是不同的API版本,也可以是对同一服务的迭代更改,部署在不同的环境中,如生产环境、预发环境或者开发测试环境等。
源(Source):
调用目标服务的下游客户端。
主机(Host):
客户端在尝试连接服务时使用的地址。
访问模型(Access model):
应用程序在不知道各个服务版本(子集)的情况下仅对目标服务(主机)进行寻址。版本的实际选择由Sidecar代理确定,使应用程序代码能够脱离依赖服务的演变。
虚拟服务(Virtual Service):
一个虚拟服务定义了一系列针对指定服务的流量路由规则。
每个路由规则都针对特定协议定义流量匹配规则。
如果流量符合这些特征,就会根据规则发送到服务注册表中的目标服务(或者目标服务的子集或版本)。
目标规则(Destination Rule):
目标规则定义了在路由发生后应用于服务的流量的策略。这些规则指定负载均衡的配置,来自Sidecar代理的连接池大小以及异常检测设置,以便从负载均衡池中检测和驱逐不健康的主机。
服务条目(ServiceEntry)
是用于把一个服务添加到Istio抽象模型或服务注册表中,这些注册的服务是由Istio内部维护的。
添加服务条目后,Envoy代理可以将流量发送到服务,如同这个添加的服务条目是网格中的其他服务一样。
使用服务条目有很多方便之处,可以管理在网格外部运行的服务的流量:
·重定向和转发外部目标的流量,例如从其他外部系统使用的API,或者遗留系统基础架构中的服务流量。
·为外部目标服务定义流量策略,例如重试、超时和故障注入策略。
·将虚拟机(VM)中运行的服务添加到一个服务网格中,以实现混合场景下的服务管理。
·从不同的集群中逻辑添加服务到同一个服务网格中,以在Kubernetes上配置多集群模式的Istio服务网格。
架构
架构图
1
2
数据平面
由一组以 sidecar 方式部署的智能代理(Envoy)组成。
这些代理可以调节和控制微服务及 Mixer 之间所有的网络通信。
一个面向服务架构的七层代理和通信总线。
基本术语:
进程外(Out of Process)架构:
Envoy是一个独立进程,Envoy之间形成一个透明的通信网格,每个应用程序发送消息到本地主机或从本地主机接收消息,但无需关心网络拓扑。
单进程多线程模型:
Envoy使用了单进程多线程的架构模型。一个主线程管理各种琐碎的任务,而一些工作子线程则负责执行监听、过滤和转发功能。
下游(Downstream):
连接到Envoy并发送请求、接收响应的主机叫下游主机,也就是说下游主机代表的是发送请求的主机。
上游(Upstream):
与下游相对,接收请求的主机叫上游主机。
监听器(Listener):
监听器是命名网络地址,包括端口、unix domain socket等,可以被下游主机连接。
Envoy暴露一个或者多个监听器给下游主机连接。每个监听器都独立配置一些网络级别(即三层或四层)的过滤器。
当监听器接收到新连接时,配置好的本地过滤器将被实例化,并开始处理后续事件。
一般来说监听器架构用于执行绝大多数不同的代理任务,例如限速、TLS客户端认证、HTTP连接管理、MongoDB sniff ing、原始TCP代理等。
集群(Cluster):
集群是指Envoy连接的一组逻辑相同的上游主机。
xDS协议:在Envoy中xDS协议代表的是多个发现服务协议,
○ SDS/EDS(Service/Endpoint(v2) Discovery Service):节点发现服务,
针对的就是提供服务的节点,让节点可以以聚合成服务的方式提供给调用方。
在 Envoy v2 API 中,Service Discovery Service 已经更名为 Endpoint Discovery Service,因此这两个描述本质上是一个概念。
○ CDS(Cluster Discovery Service):集群发现服务,
集群指 Envoy 接管的服务集群,例如三台机器组成了一个服务集群提供 reviews 服务;
Istio 可以使用这个接口创建虚拟集群,例如一个应用可以划分出不同版本号的部署结构。
○ RDS(Route Discovery Service):路由规则发现服务,
路由规则的作用是动态转发,例如上面 BookInfo 中的指定流量只流向 v3 版本的 reviews 服务,
基于此可以实现比如请求漂移、蓝绿发布等功能。
○ LDS(Listener Discovery Service):监听器发现服务,
监听器主要作用于Envoy 的链接状态,例如像 downstreamcxtotal____(连接总数)、downstreamcxactive(活动的连接总数)等。
Envoy代理有许多功能可用于服务间通信,
例如,暴露一个或者多个监听器给下游主机连接,通过端口暴露给外部的应用程序;
通过定义路由规则处理监听器中传输的流量,并将该流量定向到目标集群,等等。
Envoy作用?
首先,Envoy是一种代理,在网络体系架构中扮演着中介的角色,可以为网络中的流量管理添加额外的功能,
包括提供安全性、隐私保护或策略等。在服务间调用的场景中,代理可以为客户端隐藏服务后端的拓扑细节,简化交互的复杂性,并保护后端服务不会过载。例如,后端服务实际上是运行的一组相同实例,每个实例能够处理一定量的负载。
其次,Envoy中的集群(Cluster)本质上是指Envoy连接到的逻辑上相同的一组上游主机。
那么客户端如何知道在与后端服务交互时要使用哪个实例或IP地址?Envoy作为代理起到了路由选择的作用,通过服务发现(SDS,Service Discovery Service),Envoy代理发现集群中的所有成员,然后通过主动健康检查来确定集群成员的健康状态,并根据健康状态,通过负载均衡策略决定将请求路由到哪个集群成员。而在Envoy代理处理跨服务实例的负载均衡过程中,客户端不需要知道实际部署的任何细节。
控制平面
负责管理和配置代理来路由流量。此外控制平面配置 Mixer 以实施策略和收集遥测数据。
1.Pilot
Pilot组件用于管理流量,可以控制服务之间的流量流动和API调用,通过Pilot可以更好地了解流量,以便在问题出现之前发现问题。这使得调用更加可靠、网络更加强健,即使遇到不利条件也能让应用稳如磐石。借助Istio的Pilot,你能够配置熔断器、超时和重试等服务级属性,并设置常见的连续部署任务,如金丝雀发布、A/B测试和基于百分比拆分流量的分阶段发布。
Pilot为Envoy代理提供服务发现功能,为智能路由和弹性能力(如超时、重试、熔断器等)提供流量管理功能。Pilot将控制流量行为的高级路由规则转换为特定于Envoy代理的配置,并在运行时将它们传播到Envoy
Istio提供了强大的开箱即用故障恢复功能,包括超时、支持超时预算和变量抖动的重试机制、发往上游服务的并发连接和请求数限制、对负载均衡池中的每个成员进行的定期主动运行状况检查,以及被动运行状况检查。 Pilot将平台特定的服务发现机制抽象化并将其合成为标准格式,符合数据平面API的任何Sidecar都可以使用这种标准格式。这种松散耦合使得Istio能够在多种环境下运行(例如Kubernetes、Consul、Nomad),同时可保持用于流量管理的操作界面相同。
2.Mixer
Istio的Mixer组件提供策略控制和遥测收集功能,将Istio的其余部分与各个后端基础设施后端的实现细节隔离开来。
Mixer是一个独立于平台的组件,负责在服务网格上执行访问控制和使用策略,并从Envoy代理和其他服务收集遥测数据。代理提取请求级属性,发送到Mixer进行评估。
Mixer中包括一个灵活的插件模型,使其能够接入到各种主机环境和后端基础设施,从这些细节中抽象出Envoy代理和Istio管理的服务。利用Mixer,你可以精细控制网格和后端基础设施后端之间的所有交互。
与必须节省内存的Sidecar代理不同,Mixer独立运行,因此它可以使用相当大的缓存和输出缓冲区,充当Sidecar的高度可伸缩且高度可用的二级缓存。 Mixer旨在为每个实例提供高可用性。它的本地缓存和缓冲区可以减少延迟时间,还有助于屏蔽后端基础设施后端故障,即使后端没有响应也是如此。
3.Citadel
Istio Citadel安全功能提供强大的身份验证功能、强大的策略、透明的TLS加密以及用于保护服务和数据的身份验证、授权和审计(AAA)工具,
Envoy可以终止或向网格中的服务发起TLS流量。为此,Citadel需要支持创建、签署和轮换证书。
Istio Citadel提供特定于应用程序的证书,可用于建立双向TLS以保护服务之间的流量,如图3-4所示。 图3-4 Istio Citadel架构
借助Istio Citadel,确保只能从经过严格身份验证和授权的客户端访问包含敏感数据的服务。
Citadel通过内置身份和凭证管理提供了强大的服务间和最终用户身份验证。可用于升级服务网格中未加密的流量,并为运维人员提供基于服务标识而不是网络控制的强制执行策略的能力。
Istio的配置策略在服务器端配置平台身份验证,但不在客户端强制实施该策略,同时允许你指定服务的身份验证要求。
Istio的密钥管理系统可自动生成、分发、轮换与撤销密钥和证书。
Istio RBAC为Istio网格中的服务提供命名空间级别、服务级别和方法级别的访问权限控制,包括易于使用的基于角色的语义、服务到服务和最终用户到服务的授权,并在角色和角色绑定方面提供灵活的自定义属性支持。 Istio可以增强微服务及其通信(包括服务到服务和最终用户到服务的通信)的安全性,且不需要更改服务代码。它为每个服务提供基于角色的强大身份机制,以实现跨集群、跨云端的交互操作。
4.Galley
Galley用于验证用户编写的Istio API配置。随着时间的推移,Galley将接管Istio获取配置、处理和分配组件的顶级责任。它负责将其他的Istio组件与从底层平台(例如Kubernetes)获取用户配置的细节中隔离开来。
总而言之,通过Pilot,Istio可在部署规模逐步扩大的过程中帮助你简化流量管理。通过Mixer,借助强健且易于使用的监控功能,能够快速有效地检测和修复问题。通过Citadel,减轻安全负担,让开发者可以专注于其他关键任务。
安全架构
安全能力
图
tu2
身份认证(Identity)。
认证策略(Policy)。 图7-1 Istio的安全架构:三大目标、四大系统功能
透明的TLS加密(Encryption)。 ·用于保护服务和数据的身份认证、
授权和审计(Authentication、Authorization、Audit,AAA)的工具。
安全组件
Citadel——用于密钥和证书管理。
Sidecar——和外围代理Proxy实现客户端和服务器之间的安全通信。
Pilot——将身份认证策略和安全命名信息分发给代理。
Mixer——管理授权和审计。
how
规则配置
流量管理配置资源
图
VirtualService 在 Istio 服务网格中定义路由规则,控制路由如何路由到服务上。
DestinationRule 是 VirtualService 路由生效后,配置应用与请求的策略集
ServiceEntry 是通常用于在 Istio 服务网格之外启用对服务的请求。
Gateway 为 HTTP/TCP 流量配置负载均衡器,最常见的是在网格的边缘的操作,以启用应用程序的入口流量。
虚拟服务( Virtual Service):
图
1
2
所谓虚拟服务,就是按照服务维度,将路由与其配套条件进行打包,以便以后管理的一种配置方式。比如之前将 reviews 的请求都路由到 v3版本,就可以打包成一个 reviews-v3 的虚拟服务。
Istio 对虚拟服务的配置是比较特别的,其并不直接指明此虚拟服务由哪些组成,而是通过描述的方式来间接地描绘此虚拟服务,这种倒置的灵活性更强。简单来说,就是可以随意地划分出一个逻辑服务单元来供配置使用。
Kubernetes service如何关联一个Istio虚拟服务
1)Envoy Proxy首先根据请求匹配一个Istio虚拟服务,每一个Proxy都会对应一个主机与虚拟服务信息列表,根据请求地址,选择其中对应的主机(Kubernetes服务),并根据选择的主机信息,找到对应的Istio虚拟服务。
2)Istio虚拟服务描述路由到目标主机的规则,并根据选择的主机信息,找到对应的Istio虚拟服务;根据规则,Proxy找到满足条件的第一个目标主机(对应到一个Kubernetes service)。
3)Proxy调用目标pod,Proxy根据满足条件的目标主机信息寻找对应的目标pod,并根据定义的流量策略(包括负载均衡等)路由到相应的pod。
图
1
例子
命名空间
执行以下kubectl命令之前,请先创建命名空间servicedependency,并为命名空间servicedependency打上标签istio-injection=enabled。
通过命令kubectl create ns servicedependency来创建命令空间,
通过命令kubectl label ns servicedependency istio-injection=enabled设置标签。
service1 的V1 v2版本定义
v1图
v2图
DestinationRule定义的策略决定了经过路由处理之后的流量的访问策略。
图
VirtualService定义了一系列针对指定服务的流量路由规则。每个路由规则都针对特定协议的匹配规则。如果流量符合这些特征,就会根据规则发送到服务注册表中的目标服务(或者目标服务的子集或版本)。具体定义可参见文件virtualservice-v1.yaml,内容摘录如下:
图
VirtualService是一个控制中心。
它的功能简单说来就是:定义一组条件,将符合该条件的流量按照在对象中配置的对应策略进行处理,最后将路由转到匹配的目标中。下面列出几个典型的应用场景。 (1)来自服务A版本1的服务,如果要访问服务B,则要将路由指向服务B的版本2。
(2)在服务X发往服务Y的HTTP请求中,如果Header包含“canary=true”,则把服务目标指向服务Y的版本3,否则发给服务Y的版本2。
(3)为从服务M到服务N的所有访问都加入延迟,以测试在网络状况不佳时的表现。
VirtualService 的配置
应用在访问目标服务时,只需要指定目标服务的地址即可,不需要额外指定其他目标资源的信息。在实际请求中到底将流量路由到哪种特征的后端上,则基于在VirtualService中配置的路由规则执行。 先看下VirtualService第1级的定义,除了hosts、gateways等通用字段,规则的主体是http、tcp和tls,都是复合字段,分别对应HTTPRoute、TCPRoute和TLSRoute,表示Istio支持的HTTP、TCP和TLS协议的流量规则。
非复合字段hosts和gateways是每种协议都要用到的公共字段,体现了VirtualService的设计思想。
(1) hosts:是一个重要的必选字段,表示流量发送的目标。可以将其理解为VirtualService定义的路由规则的标识,用于匹配访问地址,可以是一个DNS名称或IP地址。DNS 名称可以使用通配符前缀,也可以只使用短域名,也就是说若不用全限定域名FQDN,则一般的运行平台都会把短域名解析成FQDN。
(2)gateways:表示应用这些流量规则的 Gateway。VirtualService 描述的规则可以作用到网格里的 Sidecar 和入口处的 Gateway,表示将路由规则应用于网格内的访问还是网格外经过Gateway的访问。其使用方式有点绕,需要注意以下场景。
◎ 场景1:服务只是在网格内访问的,这是最主要的场景。gateways字段可以省略,实际上在VirtualService的定义中都不会出现这个字段。一切都很正常,定义的规则作用到网格内的Sidecar。
◎ 场景2:服务只是在网格外访问的。配置要关联的Gateway,表示对应Gateway进来的流量执行在这个VirtualService上定义的流量规则。
◎ 场景3:在服务网格内和网格外都需要访问。这里要给这个数组字段至少写两个元素,一个是外部访问的Gateway,另一个是保留关键字“mesh”。使用中的常见问题是忘了配置“mesh”这个常量而导致错误。我们很容易认为场景 3是场景 1和场景2的叠加,只需在内部访问的基础上添加一个可用于外部访问的Gateway。 注意:在 VirtualService 中定义的服务需要同时网格外部访问和内部访问时,gateways 字段要包含两个元素:一个是匹配发布成外部访问的Gateway名,另外一个是“mesh”这个关键字。
(3)http:是一个与 HTTPRoute 类似的路由集合,用于处理 HTTP 的流量,是 Istio中内容最丰富的一种流量规则。
(4)tls:是一个 TLSRoute 类型的路由集合,用于处理非终结的 TLS 和 HTTPS 的流量。
(5)tcp:是一个TCPRoute类型的路由集合,用于处理TCP的流量,应用于所有其他非 HTTP和 TLS端口的流量。如果在 VirtualService中对 HTTPS和 TLS没有定义对应的TLSRoute,则所有流量都会被当成TCP流量来处理,都会走到TCP路由集合上。 以上3个字段在定义上都是数组,可以定义多个元素;在使用上都是一个有序列表,在应用时请求匹配的第1个规则生效。 注意:VirtualService中的路由规则是一个数组,在应用时匹配的第1个规则生效就跳出,不会检查后面的规则。
(6) exportTo:是 Istio 1.1 在 VirtualService 上增加的一个重要字段,用于控制VirtualService 跨命名空间的可见性,这样就可以控制在一个命名空间下定义的VirtualService是否可以被其他命名空间下的Sidecar和Gateway使用了。如果未赋值,则默认全局可见。“.”表示仅应用到当前命名空间,“”表示应用到所有命名空间。在Istio 1.1中只支持“.”和“”这两种配置。
网关( Geteway ):
用于指定服务流量的网关。网关是服务网格的边界,用于处理HTTP、TCP 入口与出口流量,Service Mesh 中的服务只能通过网关对外暴露接口,以方便管控,其配置中可以定义暴露的端口以及传输层面上的配置(是否需要启用 TLS),例如之前 BookInfo 配置了一个对外暴露 80 端口的网关服务,并不绑定任何域名(这样才能以 IP 方式进行访问)。
常见用例
请求路由
我们可能要以特定方式处理请求路由的原因有多个。例如,我们可能会部署微服务的多个版本,例如运输服务,并希望仅将一小部分请求路由到新版本。
我们可以使用虚拟服务的路由规则来实现这一点:
样例
熔断
熔断器基本上是一种软件设计模式,用于检测故障并封装防止故障进一步级联的逻辑。这有助于创建有弹性的微服务应用程序,以限制故障和延迟尖峰的影响。
启用双向 TLS
双向身份验证是指双方在诸如TLS之类的身份验证协议中同时相互进行身份验证的情况。默认情况下,具有代理的服务之间的所有流量在Istio中都使用相互TLS。但是,没有代理的服务仍继续以纯文本格式接收流量。
虽然Istio将具有代理的服务之间的所有流量自动升级为双向TLS,但这些服务仍可以接收纯文本流量。我们可以选择使用PeerAuthentication策略在整个网格范围内实施双向TLS:
使用JWT进行访问控制
JSON Web令牌(JWT)是用于创建数据的标准,该数据的有效载荷中包含声明许多声明的JSON。为了在身份提供者和服务提供者之间传递经过身份验证的用户的身份和标准或自定义声明,这一点已被广泛接受。
我们可以在Istio中启用授权策略,以允许访问基于JWT的预订服务之类的服务:
核心配置对象
Istio中的资源分为三组进行管理,分别是networking.istio.io、config.istio.io及authentication.istio.io,下面将分别进行介绍。
networking.istio.io
VirtualService是一个控制中心。
它的功能简单说来就是:定义一组条件,将符合该条件的流量按照在对象中配置的对应策略进行处理,最后将路由转到匹配的目标中。下面列出几个典型的应用场景。
(1)来自服务A版本1的服务,如果要访问服务B,则要将路由指向服务B的版本2。
(2)在服务X发往服务Y的HTTP请求中,如果Header包含“canary=true”,则把服务目标指向服务Y的版本3,否则发给服务Y的版本2。
(3)为从服务M到服务N的所有访问都加入延迟,以测试在网络状况不佳时的表现。
图
1
1.Gateway 在访问服务时,不论是网格内部的服务互访,还是通过Ingress进入网格的外部流量,首先要经过的设施都是Gateway。
Gateway对象描述了边缘接入设备的概念,其中包含对开放端口、主机名及可能存在的TLS证书的定义。
网络边缘的Ingress流量,会通过对应的Istio Ingress Gateway Controller进入;
网格内部的服务互访,则通过虚拟的mesh网关进行(mesh网关代表网格内部的所有Sidecar)。
Pilot会根据Gateway和主机名进行检索,如果存在对应的VirtualService,则交由VirtualService处理;
如果是Mesh Gateway且不存在对应这一主机名的VirtualService,则尝试调用Kubernetes Service;如果不存在,则发生404错误。
2.VirtualService VirtualService对象主要由以下部分组成。
(1)Host:该对象所负责的主机名称,如果在Kubernetes集群中,则这个主机名可以是服务名。
(2)Gateway:流量的来源网关,在后面会介绍网关的概念。如果这一字段被省略,则代表使用的网关名为“mesh”,也就是默认的网格内部服务互联所用的网关。
(3)路由对象:网格中的流量,如果符合前面的Host和Gateway的条件,就需要根据实际协议对流量的处理方式进行甄别。其原因是:HTTP是一种透明协议,可以经过对报文的解析,完成更细致的控制;而对于原始的TCP流量来说,就无法完成过于复杂的任务了。
3.TCP/TLS/HTTP Route 路由对象目前可以是HTTP、TCP或者TLS中的一个,分别针对不同的协议进行工作。
每种路由对象都至少包含两部分:匹配条件和目的路由。例如,在HTTPRoute对象中就包含用于匹配的HTTPMatchRequest对象数组,以及用于描述目标服务的DestinationWeight对象,并且HTTPMatchRequest的匹配条件较为丰富,例如前面提到的http header或者uri等。
除此之外,HTTP路由对象受益于HTTP的透明性,包含很多专属的额外特性,例如超时控制、重试、错误注入等。相对来说,TCPRoute简单很多,它的匹配借助资源L4MatchAttributes对象完成,其中除Istio固有的源标签和Gateway外,仅包含地址和端口。 在匹配完成后,自然就是选择合适的目标了。
4.DestinationWeight
各协议路由的目标定义是一致的,都由DestinationWeight对象数组来完成。
DestinationWeight指到某个目标(Destination对象)的流量权重,这就意味着,多个目标可以同时为该VirtualService提供服务,并按照权重进行流量分配。
5.Destination 目标对象(Destination)由Subset和Port两个元素组成。
Subset顾名思义,就是指服务的一个子集,它在Kubernetes中代表使用标签选择器区分的不同Pod(例如两个Deployment)。Port代表的则是服务的端口。
6.小结 至此,流量经过多个对象的逐级处理,成功到达了Pod,在第7章会通过不同的案例来展示与Istio流量管理相关的功能。
config.istio.io
config.istio.io中的对象用于为Mixer组件提供配置。
在3.1节中讲到,Mixer提供了预检和报告这两个功能,这两个功能看似简单,但是因为大量适配器的存在,变得相当复杂。图3-6简单展示了Mixer对数据的处理过程。 图3-6 1.Rule Rule对象是Mixer的入口,其中包含一个match成员和一个逻辑表达式,只有符合表达式判断的数据才会被交给Action处理。逻辑表达式中的变量被称为attribute(属性),其中的内容来自Envoy提交的数据。 2.Action Action负责解决的问题就是:将符合入口标准的数据,在用什么方式加工之后,交给哪个适配器进行处理。Action包含两个成员对象:一个是Instance,使用Template对接收到的数据进行处理;一个是Handler,代表一个适配器的实例,用于接收处理后的数据。 3.Instance Instance 主要用于为进入的数据选择一个模板,并在数据中抽取某些字段作为模板的参数,传输给模板进行处理。 4.Adapter Adapter在Istio中只被定义为一个行为规范,而一些必要的实例化数据是需要再次进行初始化的,例如RedisQuota适配器中的Redis地址,或者listchecker中的黑白名单等,只有这些数据得到正式的初始化,Adapter才能被投入使用。 经过Handler实例化之后的Adapter,就具备了工作功能。有些Adapter是Istio的自身实现,例如前面提到的listchecker或者memquota;有些Adapter是第三方服务,例如Prometheus或者Datadog等。Envoy传出的数据将会通过这些具体运行的Adapter的处理,得到预检结果,或者输出各种监控、日志及跟踪数据。 5.Template 顾名思义,Template是一个模板,用于对接收到的数据进行再加工。 进入Mixer中的数据都来自Sidecar,但是各种适配器应对的需求各有千秋,甚至同样一个适配器,也可能接收各种不同形式的数据(例如Prometheus可能会在同样一批数据中获取不同的指标),Envoy提供的原始数据和适配器所需要的输入数据存在格式上的差别,因此需要对原始数据进行再加工。 Template就是这样一种工具,在用户编制模板对象之后,经过模板处理的原始数据会被转换为符合适配器输入要求的数据格式,这样就可以在Instance字段中引用了。 6.Handler Handler对象用于对Adapter进行实例化。 这组对象的命名非常令人费解,但是从其功能列表中可以看出,Mixer管理了所有第三方资源的接入,大大扩展了Istio的作用范围,其应用难度自然水涨船高,应该说还是可以理解的。
authentication.istio.io 这一组API用于定义认证策略。
它在网格级别、命名空间级别及服务级别都提供了认证策略的要求,要求在内容中包含服务间的通信认证,以及基于JWT的终端认证。这里简单介绍其中涉及的对象。 1.Policy Policy用于指定服务一级的认证策略,如果将其命名为“default”,那么该对象所在的命名空间会默认采用这一认证策略。 Policy对象由两个部分组成:策略目标和认证方法。 ◎ 策略目标包含服务名称(或主机名称)及服务端口号。 ◎ 认证方法由两个可选部分组成,分别是用于设置服务间认证的peers子对象,以及用于设置终端认证的origins子对象。 2.MeshPolicy MeshPolicy只能被命名为“default”,它代表的是所有网格内部应用的默认认证策略,其余部分内容和Policy一致。
rbac.istio.io 在Istio中实现了一个和Kubernetes颇为相似的RBAC(基于角色的)访问控制系统,其主要对象为ServiceRole和ServiceRoleBinding。 1.ServiceRole ServiceRole由一系列规则(rules)组成,每条规则都对应一条权限,其中描述了权限所对应的服务、服务路径及方法,还包含一组可以进行自定义的约束。 2.ServiceRoleBinding 和Kubernetes RBAC类似,该对象用于将用户主体(可能是用户或者服务)和ServiceRole进行绑定。
图3-6 1.Rule 2.Action 3.Instance 4.Adapter 5.Template 6.Handler Handler对象用于对Adapter进行实例化。 这组对象的命名非常令人费解,但是从其功能列表中可以看出,Mixer管理了所有第三方资源的接入,大大扩展了Istio的作用范围,其应用难度自然水涨船高,应该说还是可以理解的。
流量治理原理
流程
图
在控制面会经过如下流程:
(1)管理员通过命令行或者API创建流量规则;
(2)Pilot将流量规则转换为Envoy的标准格式;
(3)Pilot将规则下发给Envoy。
在数据面会经过如下流程:
(1)Envoy拦截Pod上本地容器的Inbound流量和Outbound流量;
(2)在流量经过Envoy时执行对应的流量规则,对流量进行治理。
路由规则配置:VirtualService
概要图
VirtualService定义了对特定目标服务的一组流量规则。
VirtualService在形式上表示一个虚拟服务
将满足条件的流量都转发到对应的服务后端,这个服务后端可以是一个服务,也可以是在DestinationRule中定义的服务的子集。 VirtualService是在Istio V1alpha3版本的API中引入的新路由定义。不同于V1alpha1版本中RouteRule使用一组零散的流量规则的组合,并通过优先级表达规则的覆盖关系,
VirtualService描述了一个具体的服务对象,在该服务对象内包含了对流量的各种处理,其主体是一个服务而不是一组规则,更易于理解。
VirtualService中的一些术语如下。
◎ Service:服务
◎ Service Version:服务版本。
◎ Source:发起调用的服务。
◎ Host:服务调用方连接和调用目标服务时使用的地址,是 Istio的几个配置中非常重要的一个概念,后面会有多个地方用到,值得注意。
与k8s的service比较
在Kubernetes中,Service和Deployment或者其他工作负载的关系通常是一对一的
而在Istio中,Service经常会对应不同的Deployment,
两种模式的灵活性高下立判,如下所述。
◎ 在Istio中,客户端只使用一个服务入口就可以访问多个不同的服务,无须客户端干预;但客户端如果直接使用Kubernetes Service,就必须分别使用两个不同的服务入口。
◎ Istio可以通过流量特征来完成对后端服务的选择,它的流量控制功能会根据每次访问产生的流量进行判断,根据判断结果来选择一个后端负责本次访问的响应。Kubernetes当然也可以这样做,但是因为Kubernetes的Service不具备选择后端的能力,所以如果它使用了Istio这种一对多的模式,则后果只能是使用轮询方式随机调用两个不同的工作负载。 而在Istio中,这种同一服务不同组别的后端被称为子集(Subset),也经常被称为服务版本。 在Istio中,建议为每个网格都设置明确的目标访问规则。在通过Istio流量控制之后,会选择明确的子集,根据该规则或者在子集中规定的流量策略来进行访问,这种规则在Istio中被称为DestinationRule。
VirtualService是Istio流量控制过程中的一个枢纽,负责对流量进行甄别和转发。 下面对本节涉及的概念进行讲解。
◎ VirtualService同样是针对主机名工作的,但注意这个字段是一个数组内容,因此它可以针对多个主机名进行工作。
VirtualService可以为多种协议的流量提供服务,除了支持本文使用的是HTTP,还支持TCP和TLS。
◎ 在http字段的下一级,就是具体的路由规则了。不难看出,这里是支持多条路由的,我们简单定义了一个默认目标:flaskapp.default.svc.cluster.local,
DestinationRule
TU
该规则有以下需要注意的地方。
◎ host:是一个必要字段,代表Kubernetes中的一个Service资源,或者一个由ServiceEntry(会在7.9节讲解出站流量时介绍)定义的外部服务。为了防止Kubernetes不同命名空间中的服务重名,这里强烈建议使用完全限定名,也就是使用FQDN来赋值。
◎ trafficPolicy:是流量策略。在DestinationRule和Subsets两级中都可以定义trafficPolicy,在Subset中设置的级别更高。
◎ subsets:在该字段中使用标签选择器来定义不同的子集。
流量治理
负载均衡
Pilot将服务发现数据通过 Envoy的标准接口下发给数据面Envoy,Envoy则根据配置的负载均衡策略选择一个实例转发请求。Istio当前支持的主要负载均衡算法包括:轮询、随机和最小连接数算法。
服务访问入口
Istio在0.8版本的V1alpha3流量规则中引入了Gateway资源对象,只定义接入点。Gateway只做四层到六层的端口、TLS配置等基本功能,VirtualService则定义七层路由等丰富内容。就这样复用了VirtualService,外部及内部的访问规则都使用VirtualService来描述。
在Istio中通过Gateway访问网格内的服务。这个Gateway和其他网格内的Sidecar一样,也是一个Envoy,从Istio的控制面接收配置,统一执行配置的规则。Gateway一般被发布为Loadbalancer类型的Service,接收外部访问,执行治理、TLS终止等管理逻辑,并将请求转发给内部的服务。
管理集群入口流量
主动进入服务网格的流量称为入口流量,这是获取服务的入口,所有外部的请求流量都会最先经过这里,这里是外部流量与内部服务通信的入口。在Istio中这个入口称为Ingress gateway,它是一个边界负载均衡器,负责接收外部的请求流量,并按相应的规则转发到网格内部服务上。Istio使用一个叫Gateway的资源对象来管理服务网格的入口
gateway
yaml
virtualservice
yaml
dynamicrule
yaml
外部接入服务治理
在Istio中是通过一个ServiceEntry的资源对象将网格外的服务注册到网格上,然后像对网格内的普通服务一样对网格外的服务访问进行治理的。 如图3-26所示,在Pilot中创建一个ServiceEntry,配置后端数据库服务的访问信息,在Istio的服务发现上就会维护这个服务的记录,并对该服务配置规则进行治理,从forecast服务向数据库发起的访问在经过Envoy时就会被拦截并进行治理。
负载均衡
Istio提供两种设置负载均衡的方式:1)标准负载均衡算法,2)会话保持,如图6-2所示。两者只能取其一。
算法
熔断
熔断模式,实现了三个状态机:Closed、Half-Open、Open,
状态机
熔断关闭状态(Closed)
对应用程序的请求能够直接引起方法的调用。代理类维护了最近调用失败的次数,如果某次调用失败,则使失败次数加1。如果最近失败次数超过了在给定时间内允许失败的阈值,则代理类切换到断开(Open)状态。此时代理开启了一个超时时钟,当该时钟超过了该时间,则切换到半断开(Half-Open)
半熔断状态(Half-Open)
该超时时间的设定是给了系统一次机会来修正导致调用失败的错误。半熔断状态允许定量的服务请求,如果调用都成功(或一定比例成功)则认为恢复了,进入熔断关闭状态;否则认为还没好,又回到熔断开启状态。
熔断开启状态(Open)
在该状态下,对应用程序的请求会立即返回错误响应。处于该状态时,对下游的调用都直接返回错误,但同时启用了一个时钟,默认的时钟达到了一定时间(这个时间一般设置成平均故障处理时间,也就是MTTR),就会进入半熔断状态。
请求进入服务网格后的数据流向
Ingress gateway是外部访问网格内服务的流量入口,所有外部请求流量都会经过这里。Ingress gateway类似于Kubernetes的Ingress Controller的实现,但是Ingress gateway并不直接使用Kubernetes的Ingress规则,在Istio中需要单独配置Ingress gateway。正由于有独立于Kubernetes的Ingress,所以它比Kubernetes的Ingress提供了更多的功能特性,比如,它可以使用服务路由管理功能。Egress gateway是一个可选的组件,它可以作为网格内服务访问外部服务的流量出口,使用它可以方便地管理网格内服务调用外部服务的行为,也可以方便地使用服务路由管理等功能。 外部请求通过Ingress gateway进入网格内部,然后被路由到网格内部的具体服务上,服务之间相互调用配合来完成用户的请求,最后由Ingress gateway把请求的响应数据返回给外部调用方。当网格中的服务需要访问外部服务时,可以直接通过跟随服务一起部署的Envoy代理来访问外部服务,也可以通过统一部署的Egress gateway来访问外部服务。
图
根据请求信息路由到服务的不同版本
yaml
流量拆分
流量导向
图
流量镜像
有时候当我们需要将服务的新版本上线时,虽然我们已经在线下经过测试,但是并没有经过生产环境的真实流量验证,对服务的性能和是否有bug并没有太大的把握,我们希望通过线上的真实流量来验证一下新版本服务,经过生产环境流量验证无误之后,再把生产环境的流量切换到新版本的服务上。在此种场景下,我们就可以使用Istio提供的流量镜像功能,实时复制线上真实的请求流量到我们的新版本服务上,验证新版本的服务。
下面以service-go服务为例: 1 apiVersion: networking.istio.io/v1alpha3
yaml
管理集群的出口流量
默认情况下,服务网格中的服务无法访问外部的服务。要想访问外部服务,可以创建ServiceEntry对象,把外部服务注册到服务网格的注册中心,这样,网格中的服务就可以访问外部服务了。同时,可以配合VirtualService更好地控制网格内服务访问外部服务的行为。本节介绍通过ServiceEntry来导入外部服务,让服务网格中的服务也可以访问外部的服务的方法。也可以使用Egress gateway配合ServiceEntry统一管理集群的出口流量;此外,还可以通过修改Istio的部署配置文件,直接放开所有对外部服务的访问流量。 1.使用ServiceEntry导入网格外部服务
(1)配置访问HTTP协议的外部服务
ServiceEntry配置
yaml
(2)配置访问HTTPS协议的外部服务 导入baidu.com服务示例:
yaml
默认情况下,网格内服务是不能访问任何外部服务的,当前可以使用如下三种方式来满足网格内服务调用外部服务的需求:
1)使用ServiceEntry,让每一个Envoy代理都可以访问特定外部服务。
2)使用Egress gateway管理出口流量,统一管理对外部服务的调用出口。
3)配置Istio直接开放对任何外部服务的调用,调用外部服务的流量由网格内服务自行管理。 第一种方式适用于集群中每台服务器都能和外部互通的网络环境。第二种方式适用于集群中存在边界服务器,或者集群中只有部分节点能与外部网络互通,所有外部流量都经过此服务器的情况。当然使用第二种方式也可以只是为了使用Egress gateway对网格的出口流量进行统一管理。第三种方式是最直接、暴力的方式,也是最不推荐的方式,因为使用这种方式的流量不经过网格,所有流量全部由网格内的服务自行处理,此时出口流量就不能享受到网格带来的路由管理等功能了,而且这也会降低服务网格的安全性。
与上述导入HTTP协议的外部服务定义基本一致,只是协议字段变成HTTPS协议,并且减少了对应的VirtualService的定义。HTTPS协议的服务也可以配置VirtualService进行更细致的流量管理,与HTTP协议的使用方法基本一致。
Istio的服务模型
Istio通过平台适配器层将平台特有的服务模型转换为 Istio通用的抽象服务模型,使得 xDS服务层无须感知底层平台的差异。
xDS服务器基于通用的服务模型构建流量配置规则,从而对底层平台的差异无感知,这样可以大大提高Pilot的可扩展性,方便支持更多的扩展平台。
通用的服务模型包含Service(服务)和ServiceInstance(服务实例)。
1.Service 每个服务都有一个完全限定的域名(FQDN)及用于监听连接的一个或多个端口。可选地,服务可以具有与其相关联的单个负载均衡器或虚拟IP地址,使得针对FQDN的DNS查询被解析为虚拟IP地址(负载均衡器IP)。例如,在Kubernetes平台上,一个服务forecast的FQDN可能是forecast.weather.svc.cluster.local,拥有虚拟IP地址10.0.1.1并监听在3002端口。
Istio的Service模型及其主要属性如下。
◎ Hostname:服务域名,为FQDN(全限定域名),在Kubernetes环境下,服务的域名形式是<name>.<namespace>.svc.cluster.local。
◎ Address:服务的虚拟 IP 地址,主要为 HTTP 服务生成 HTTP 路由的虚拟主机Domains。Envoy代理会根据路由中虚拟主机的域名转发请求。
◎ ClusterVIPs:服务于Kubernetes多集群服务网格,表示集群ID与虚拟IP的关系。Pilot在为 Envoy代理构建 Listener和 Route时,会根据代理所在的集群选用对应集群的虚拟IP。
◎ ServiceAccounts:是服务的身份标识,遵循SPIFFE规范。例如,在Kubernetes环境下,身份信息的格式为“spiffe://<domain>/ns/<namespace>/sa/<serviceaccount>”,这使Istio服务能够建立和接收与其他SPIFFE兼容系统的连接。
◎ Resolution:指示代理如何解析服务实例的地址,在大多数网格内的服务之间访问时使用 ClientSideLB,Envoy 会根据负载均衡算法从本地负载均衡池中选择一个Endpoint地址进行转发。还有 DNSLB和 Passthrough两种解析策略可选,一般用于访问服务网格外部的服务或者Kubernetes Headless(无头)服务。
◎ Attributes:定义服务的额外属性,主要用于 Mixer 遥测。特别要注意,在属性中还有一个ExportTo字段,用于定义服务的可见范围,表示本服务可被同一命名空间或者集群中的所有工作负载访问。 可见,Istio Service模型完全不同于底层平台服务模型,例如Kubernetes Service API。Istio服务模型记录了Istio生成xDS配置所需要的属性,这些关键属性由Kubernetes Service转换而来。这种抽象的与底层无关的服务模型,在很大程度上解耦了Pilot xDS Server模块与底层平台适配器,并具有很强的可扩展性。
ServiceInstance ServiceInstance 表示特定版本的服务实例,记录服务与其实例 NetworkEndpoint 的关联关系,定义与服务相关联的标签(版本等信息)。每个服务都有一个或者多个实例,服务实例是服务的实际表现形式,类似于Kubernetes中Service与Endpoint的概念。 服务实例的属性及其作用如下。
◎ Service:关联的服务,用于维护服务实例与服务的关系。
◎ Labels:服务实例的标签,可用于路由选择,例如,可以限制只有标签为 app:v1的服务实例才可以访问某个服务。
◎ ServiceAccount:服务实例的身份信息,与 Service.ServiceAccounts 属性类似,主要用于Istio双向TLS认证。
◎ Endpoint:NetworkEndpoint 类型,定义了服务实例的网络地址、位置信息、负载均衡权重及所在的网络ID等元数据。 NetworkEndpoint模型的主要属性及其含义如表14-1所示。 表14-1 NetworkEndpoint模型的主要属性及其含义 接下来,结合实际的Kubernetes环境解释NetworkEndpoint的主要属性。
◎ Address:服务实例监听的IP地址,即Pod的IP地址。
◎ Port:服务实例监听的端口号,实际上等同于服务进程监听的端口号。
◎ ServicePort:服务端口号,等同于 Service的虚拟 IP或者负载均衡 IP所匹配的服务端口。
◎ Network:网络标识,支持多网络的服务网格内路由转发。 ◎ Locality:Pod所在的区域、可用域等位置信息,用于基于位置的负载均衡策略。
◎ LbWeight:负载均衡权重,目前主要用于服务网格外部服务(ServiceEntry定义)的访问权重设置。 NetworkEndpoint模型与在 Istio 1.1中新定义的 IstioEndpoint模型很相似,这给开发者带来很大的阅读挑战和开发负担,Istio社区计划在将来的版本中使用IstioEndpoint代替NetworkEndpoint。
xDS协议
xDS协议是Envoy动态获取配置的传输协议,也是Istio与Envoy连接的桥梁。Envoy通过文件系统或者查询一个或者多个管理服务器来动态获取配置。总体来说,这些发现服务及相关 API被统称为 xDS。目前在 Istio中,Pilot主要基于 gRPC协议提供发现服务功能,本节主要介绍流式gRPC订阅。
1.xDS概述 xDS是一类发现服务的总称,包含LDS、RDS、CDS、EDS及SDS。
(1)LDS:Listener发现服务。Listener监听器控制Envoy启动端口监听(目前只支持TCP),并配置L3或L4层过滤器,在网络连接到达后,由网络过滤器堆栈开始处理。Envoy根据监听器的配置执行大多数不同的代理任务(限流、客户端认证、HTTP连接管理、TCP代理等)。
(2)RDS:Route发现服务,用于 Envoy HTTP连接管理器动态获取路由配置。路由配置包含HTTP头部修改(增加、删除HTTP头部键值)、Virtual Hosts(虚拟主机)及Virtual Hosts定义的各个路由条目。
(3)CDS:Cluster发现服务,用于动态获取Cluster信息。Envoy Cluster管理器管理着所有的上游Cluster。Envoy一般从Listener(针对TCP协议)或Route(针对HTTP)中抽象出上游Cluster,作为流量转发目标。
(4)EDS:Endpoint发现服务。在Envoy术语中,Cluster成员叫作Endpoint,对于每个Cluster,Envoy都通过EDS API动态获取Endpoint。之所以将EDS作为首选的服务发现机制,是因为: ◎ 与通过DNS解析的负载均衡器进行路由相比,Envoy能明确知道每个上游主机的信息,从而做出更加智能的负载均衡决策。 ◎ Endpoint 配置包含负载均衡权重、可用域等附加主机属性,这些属性可用于服务网格负载均衡、统计收集等。
(5)SDS:Secret发现服务,用于在运行时动态获取TLS证书。若没有SDS特性,则在 Kubernetes 环境下必须创建包含证书的 Secret,在代理启动前必须将 Secret 挂载到Sidecar容器中,如果证书过期,则需要重新部署。在使用SDS后,集中式的SDS服务器将证书分发给所有的Envoy实例,如果证书过期,则服务器会将新的证书分发,Envoy在接收到新的证书后重新加载,不用重新部署。
Envoy的配置分发
Envoy的正常工作离不开正确的配置,Envoy的基本配置包含Listener、Route、Cluster与Endpoint,而路由、认证、授权等配置的动态更新是Service Mesh发展的必然趋势。
Pilot作为服务网格的指挥官,承担着动态配置生成及下发的重任。 本节将讲解 Envoy 网络转发所需的基本配置 API 及配置分发的时机,另外,Pilot 为提高配置下发的效率,降低了配置延迟,做了许多性能优化,本节也做部分讲解。
1.配置分发的时机 从Pilot的角度来看,存在两种配置分发模式:主动模式和被动模式。主动模式指Pilot主动将配置下发到Sidecar,由Config与服务更新事件触发。被动模式指由Pilot接收Sidecar的连接请求(DiscoveryRequest),然后做出响应(DiscoveryResponse)。主动模式和被动模式的区别在于,主动模式是由底层注册中心的资源更新触发的,被动模式是由外部客户端的Envoy请求触发的。实际上,被动模式是前提,即Envoy主动发起订阅请求,订阅某些资源,Pilot 在内部维护所有客户端订阅的资源信息;当 Pilot 监听到后端注册中心的Config或者服务更新时,会根据客户端订阅的资源主动生成配置信息并下发到Envoy。 在Pilot中配置的分发由EnvoyXdsServer负责,前面多次提到了EnvoyXdsServer,本节便从配置分发的角度详细分析其工作原理。
1)被动模式 在被动模式下,EnvoyXdsServer通过StreamAggregatedResources接收Sidecar的资源订阅请求,其原理为:EnvoyXdsServer 首先接收 gRPC 连接,然后初始化 XdsConnection对象,启动DiscoveryRequest接收线程,后台任务最后循环读取请求队列Channel,如图14-19所示。 在Pilot运行时,gRPC Server接收gRPC stream上的DiscoveryRequest,然后将请求发送到请求队列(Channel)。请求处理模块作为请求队列的接收方,循环处理从 Channel中获取的 DiscoveryRequest。请求处理模块是主要的配置生成模块,如图 14-20 所示,其首先解析DiscoveryRequest获取请求资源类型(CDS、EDS、LDS、RDS),然后对每种类型分别
基于角色的访问控制(RBAC)是基于角色和特权定义的访问控制机制。
把权限分配给角色,相应角色的用户就具备了对应功能的权限。
2.Istio的RBAC Istio可以为网格中的服务提供命名空间级别、服务级别和方法级别的授权管理。在语法上包括基于角色的语义、服务到服务和最终用户到服务的授权,还可以在角色和角色绑定方面提供灵活的自定义属性支持。 Istio的整个授权配置的工作流程如图5-7所示,和其他Istio的服务管理流程一样,包括配置规则、下发规则和执行规则。 图5-7 Istio授权配置的工作流程 其中:
◎ 管理员配置授权规则,将授权配置信息存储在Kube-apiserver中;
◎ Pilot从Kube-apierver处获取授权配置策略,和下发其他规则一样将配置发送给对应服务的Envoy;
◎ Envoy在运行时基于授权策略来判断是否允许访问。
在Istio的认证中使用了两个重要的数据结构ServiceRole和ServiceRoleBinding,分别对应RBAC模型中的角色和角色绑定。
ServiceRole描述有什么权限,是一个权限的集合,表示对服务的动作。
ServiceRoleBinding描述的是将权限ServiceRole授予给指定的对象,当然,对象的描述可以是一个用户、一个用户组,也可以是一个服务。 通过这个配置就可以描述允许谁做什么,其中的“谁”就是角色授予的主体;干什么就是在角色中定义的权限。在Istio中除了配置“谁可以干什么”,还可以在上面的表达式中叠加一个条件“谁在什么条件下允许干什么”,可以认为是RBAC模型在Istio上应用的扩展。
在Istio RBAC中建议启用认证功能,在授权策略中使用认证的标识。对于老的系统,如果没有对接认证,也没有提供双向TLS,则可以通过客户端IP等来做授权控制。
Istio 通过一个独立的管理面组件 Citadel来实现密钥证书管理,
是Istio认证和授权的基础。 简单理解,数字证书就是通过证明公钥属于一个特定的实体来防止身份假冒的。
相应地,数字证书签发的原理就是CA把证书拥有者的公钥和身份信息绑在一起,使用CA专有的私钥生成正式的数字签名,表示这个证书是权威CA签发的。在证书校验时用CA的公钥对这个证书上的数字签字进行验证即可。
在 Istio 的双向 TLS 场景下,服务端维护一个在 CA 上获取的服务端证书,在客户端请求时将该证书回复给客户端。客户端使用CA的公钥进行验证,若验证通过,就可以拿到服务端的公钥,从而执行后面的步骤。当然,这是基本原理,在数字证书上除了有核心的证书所有者的公钥、证书所有者的名称,还有证书起始时间、到期时间等信息,在进行证书校验时会用到。 正因为Istio基于Citadel提供了自动生成、分发、轮换与撤销密钥和证书的功能,才避免了用户自己维护的麻烦事。
每个集群都有一个Citadel服务,Citadel服务主要做4个操作:
◎ 给每个Service Account都生成SPIFFE密钥证书对;
◎ 根据Service Account给对应的Pod分发密钥和证书对;
◎ 定期替换密钥证书;
◎ 根据需要撤销密钥证书。 在Istio的X.509证书中包含了用户身份,可以为每个工作负载都提供标识。在Istio 1.1中,Envoy可以通过SDS API来请求证书和密钥。 图5-8展示了在启用了认证后frontend服务调用forecast服务时Citadel自动维护密钥证书的细节,这是服务间双向TLS通信和访问授权控制的基础。
对应用有要求和限制 尽管Isito这样的服务网格对应用是透明的,但不代表在使用时没有任何限制。如果应用是部署在Kubernetes集群上的,需要注意Istio对Pod的设置有一些要求。
● Pod必须属于某个Kubernetes Service对象。这一点很容易理解,因为请求转发是基于Service对象的,而不是基于Pod的。另外,如果一个Pod属于多个Service对象,则不同协议的服务不能使用相同的端口号。这也很容易理解,Sidecar代理没有办法将指向同一个端口的请求发送给不同的服务。
● Pod必须添加app和version标签。Istio官方建议应该显式配置这两个标签,app标签(应用标签)用于添加分布式追踪中的上下文信息,version标签(版本标签)用于区别服务的不同版本。这也很容易理解,像蓝绿部署、灰度发布这样的功能都需要基于版本进行流量切分。
● 端口命名规则。在Istio 1.5之前,Service对象的端口名必须以协议名作为前缀。这是因为Istio想要管理7层流量就必须知道协议,然后根据协议实现不同的流量管理功能。而Kubernetes的资源定义中是不包含7层协议信息的,所以需要用户显式声明。不过这个问题在之后的版本中已经解决了,Istio已经可以自动探测HTTP和HTTP/2两种协议。另外,在Kubernetes 1.18以上版本中,也可以在Service对象中添加appProtocol:<protocol>字段以实现协议声明。
● 非TCP的协议无法被代理。Istio支持任何基于TCP的协议,如HTTP、HTTPS、gRPC及纯TCP。但是非TCP的协议,比如UDP请求就无法被代理。当然请求还是能正常运行的,只是不被Sidecar拦截而已。 除了对Pod的这些要求,还有一些其他限制,比如不能占用Istio默认的端口,这些端口大部分都是以150XX开头的,需要注意。
使用Istio提升应用的容错能力
容错能力即容忍错误的能力,通常也称作弹性(Resilience),指一个系统在出现故障时的应对能力。
Istio不仅可以帮助我们管理流量,还提供了几个故障恢复的特性,可以在运行时动态配置。这些特性确保了服务网格可以容忍一定的故障,并防止故障扩散。
通过Istio来实现熔断、超时与重试。 4.4.1 熔断器 如果一个服务提供者在出现异常时没有响应,而服务调用者在毫不知情的情况下依然发送大量的请求,则很可能耗尽网络资源,从而拖垮服务调用者本身并导致级联故障。熔断就是为了防止这种灾难而被设计出来的,其原理也很简单:熔断器会监视服务提供者的状态,一旦发现出现异常且错误发生的次数达到了设定的阈值,就会触发熔断,新进来的请求会被直接阻断并返回错误,等过一段时间服务恢复正常后再继续放行,具体流程如图4-13所示。 图4-13 熔断和超时有一点类似,都是发生故障后触发快速失败的策略,熔断的优点是还会提供自动恢复的机制。其他领域的熔断也是如此,比如股市熔断后还会重新开市,航班熔断只是暂时禁飞,电闸熔断还会被重新接通。
设计熔断器首先要关注熔断的三个状态。
● 关闭状态:熔断不生效,请求可以正常发送给服务提供者。 ● 打开状态:发现服务异常,熔断器打开,阻断请求的访问。 ● 半开状态:放一些请求过去,看看服务是否已恢复。 除了状态,还需要实现两个指标。 ● 错误阈值:熔断触发的条件,比如调用服务连续出错的次数。 ● 超时时钟:从打开状态转为半开状态的间隔时间。
Istio里提供了熔断的功能,我们只需为需要熔断的服务添加声明式配置就可以实现熔断。
在4.3节中我们简单提到了熔断需要在DestinationRule的TrafficPolicy里进行设置,这个对象中和熔断相关配置项有两个:
ConnectionPoolSettings,即上游服务的连接池设置,可以应用于TCP和HTTP服务;
OutlierDetection,即异常探测,用来跟踪上游服务中每个实例(在Kubernetes中是Pod)的状态,适用于HTTP和TCP服务。对于HTTP服务来说,连续返回5xx错误的实例将从负载均衡池中被逐出一段时间。对于TCP服务,连接超时或连接失败将被认为是错误。OutlierDetection的具体的配置项如表4-2所示。 表4-2 ConnectionPoolSettings是连接池管理的配置项,分为TCPSettings和HTTPSettings两种。
TCP设置主要包括最大连接数(maxConnections)、连接超时时间(connectTimeout)、长连接(tcpKeepalive)等,
HTTP设置主要包括每个连接的最大请求数(maxRequestsPerConnection)、请求最大数(http2MaxRequests)、请求的最大等待数(http1MaxPendingRequests)等。
需要注意的是,连接池的设置会对熔断的触发产生影响,比如将连接数设置为一个很小的值,熔断的错误阈值就很容易被触发。在使用时需要充分了解配置产生的后果
资料
[文章]
http://dockone.io/article/10047
http://dockone.io/article/2434695
https://www.bilibili.com/video/BV1G3411r7we
官网
[掌阅]Service Mesh实战:用Istio软负载实现服务网格
[掌阅]深入浅出Istio:Service Mesh快速入门与实践
[掌阅]云原生服务网格Istio:原理、实践、架构与源码解析
本书篇章组织概述如下。
◎ 原理篇:介绍Istio概念、核心功能、原理和使用方式,为后续的实践提供理论基础。其中,第 1~2 章分别介绍 Istio 的背景知识、基本工作机制、主要组件及概念模型等;第 2~7 章分别介绍 Istio 的五大块功能集,即非侵入的流量治理、可扩展的策略和遥测、可插拔的服务安全、透明的Sidecar机制及多集群服务治理。
◎ 实践篇:通过实际操作介绍如何通过一个典型应用进行Istio实践。其中,第8章讲解环境准备,完成Kubernetes与Istio平台的基础设施准备工作;第9~13章分别介绍如何实际操作一个天气预报应用在Istio平台上实现流量监控、灰度发布、流量治理、服务安全、多集群管理等功能。
◎ 架构篇:从架构角度剖析Istio多个主要组件的设计原理、关键内部流程及数据结构等内容,为高级用户提供架构与设计层面的参考。其中,第14~19章分别介绍了Pilot、Mixer、Citadel、Envoy、Pilot-agent与Galley等6个Istio核心组件。
◎ 源码篇:本篇包括第20~24章,分别介绍Istio整体的代码组织情况,以及Pilot、Mixer、Citadel、Envoy与Galley的代码结构与关键代码片段。
◎ Istio项目官网:https://istio.io/。
◎ Istio源代码:https://github.com/istio。
◎ 本书示例应用源代码:https://github.com/cloudnativebooks/cloud-native-istio。
https://lib.jimmysong.io/istio-handbook/
安装
在minikube上安装istio
安装启动Minikube 我们提供了一个国内可访问的Minikube修改版的文件,可以直接下载使用。
Mac OSX用户可通过以下命令安装Minikube:
curl -Lo minikube http://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v0.30.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
Linux用户可通过以下命令安装Minikube:
curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
Windows用户可通过以下方式下载安装Minikube:http://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v0.30.0/
minikube-windows-amd64.exe?spm=a2c4e.11153940.blogcont221687.28.7dd54cecA8DSic&f ile=minikube-windows-amd64.exe,并重命名为minikube.exe。
Minikube默认使用VirtualBox驱动来创建Kubernetes本地环境,可以通过以下命令启动:
minikube start --registry-mirror=https://registry.docker-cn.com
minikube start --registry-mirror=https://registry.docker-cn.com --driver=none --extra-config=kubeadm.ignore-preflight-errors=NumCPU --force --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers
默认会启动最新版本的Kubernetes,
也可以指定不同的Kubernetes版本
minikube start --registry-mirror=https://registry.docker-cn.com --kubernetes-version v1.12.1
minikube start --registry-mirror=https://registry.docker-cn.com --kubernetes-version v1.11.3 在执行上述命令过程中会下载Minikube ISO文件,如果由于网络原因无法下载,可以先通过其他方式获取该ISO文件,然后在启动命令中加上参数--iso-url并执行ISO文件,例如: minikube start --registry-mirror=https://registry.docker-cn.com --kubernetes-version v1.12.1 --iso-url=file://tmp/minikube-v0.30.0.iso
查看控制台
minikube dashboard
[[教程]](https://www.41sh.cn/?id=59)
https://blog.csdn.net/weixin_39714191/article/details/111861952
https://blog.csdn.net/chenxun_2010/article/details/106764240
prometheus
架构
图
核心组件,
使用 Pull (抓取)的方式去搜集被监控对象的 Metrics 数据(监控指标数据),
然后,再把这些数据保存在一个 TSDB (时间序列数据库,比如 OpenTSDB、InfluxDB 等)当中,以便后续可以按照时间进行检索。
Prometheus 剩下的组件就是用来配合这套机制的运行。
比如 Pushgateway,可以允许被监控对象以 Push 的方式向 Prometheus 推送 Metrics 数据。
而 Alertmanager,则可以根据 Metrics 信息灵活地设置报警。
通过 Grafana 对外暴露出的、可以灵活配置的监控数据可视化界面。
Metrics 数据的来源
宿主机的监控数据。
这部分数据的提供,需要借助一个由 Prometheus 维护的Node Exporter 工具。一般来说,
Node Exporter 会以 DaemonSet 的方式运行在宿主机上。其实,所谓的 Exporter,
就是代替被监控对象来对 Prometheus 暴露出可以被“抓取”的 Metrics 信息的一个辅助进程。而
Node Exporter 可以暴露给 Prometheus 采集的 Metrics 数据, 也不单单是节点的负载(Load)、CPU 、内存、磁盘以及网络这样的常规信息,它的 Metrics 指标可以说是“包罗万象”,你可以查看这个列表来感受一下。
来自于 Kubernetes 的 API Server、kubelet 等组件的 /metrics API。
常规的 CPU、内存的信息,
各个组件的核心监控指标。
比如,对于 API Server 来说,它就会在 /metrics API 里,暴露出各个 Controller 的工作队列(Work Queue)的长度、请求的 QPS 和延迟数据等等。
是检查 Kubernetes 本身工作情况的主要依据。
Kubernetes 相关的监控数据。
这部分数据,一般叫作 Kubernetes 核心监控数据(core metrics)。
这其中包括了 Pod、Node、容器、Service 等主要 Kubernetes 核心概念的 Metrics。
容器相关的 Metrics 主要来自于 kubelet 内置的 cAdvisor 服务。
在 kubelet 启动后,cAdvisor 服务也随之启动,而它能够提供的信息,可以细化到每一个容器的 CPU 、文件系统、内存、网络等资源的使用情况。
Kubernetes 核心监控数据,其实使用的是 Kubernetes 的一个非常重要的扩展能力,叫作 Metrics Server。
Metrics Server 并不是 kube-apiserver 的一部分,而是通过 Aggregator 这种插件机制,在独立部署的情况下同 kube-apiserver 一起统一对外服务的。这里,Aggregator APIServer 的工作原理,可以用如下所示的一幅示意图来表示清楚:
图
特性
Prometheus项目来自于SoundCloud,是继Kubernetes之后CNCF的第二个成员项目,是新兴的系统监控和告警工具套件,在容器和微服务领域被广泛关注和使用。其主要特性如下。
◎ 使用指标名称及键值对标识的多维度数据模型。
◎ 采用弹性查询语言PromQL。
◎ 不依赖分布式存储,为自治的单节点服务。
◎ 使用HTTP完成对监控数据的拉取。
◎ 通过网关支持时序数据的推送。
◎ 支持多种图形和Dashboard的展示。
另外,在Prometheus的整个生态系统中有各种可选组件,用于功能的扩充。
◎ 多种客户端库,用于支撑接入开发。
◎ 支持推送数据的网关组件。
◎ 使用各种第三方Exporter支持各种外部系统的指标收集。
◎ 用于告警的Alertmanager。
◎ 大量的支持工具。
Prometheus的子项目Alertmanager可用于实现告警的管理,包括告警策略、过滤及转发等。Prometheus Alertmanager具有如下优点。
(1)组策略:可以将相似类型的告警处理成单一告警。该功能在很多系统突然出现故障导致瞬间产成百上千条告警时特别有用,有助于简化告警通知。该功能可以在Alertmanager配置文件里配置实现。
(2)抑制策略:用于在某些特定告警发生时抑制其他特定的告警。比如在某条告警通知整个集群不可达时,我们可以配置Alertmanager,使所有与该集群相关的告警都不出现,这可以避免出现大量与实际问题无关的告警。
(3)静默机制:该机制可以让我们在设定的时间内抑制某种告警的出现。可以在Alertmanager网页页面配置这个功能。
(4)告警转发:Alertmanager可以在对所有接收的告警进行过滤处理后转发到配置的接收者,例如email、slack、hipchat、webhook等。
(5)标准API接口。可以根据Alertmanager提供的标准API接口将告警数据格式化后发送给Alertmanager,由Alertmanager进行过滤处理。
特点
● 强大的多维度数据模型: ✧ 时间序列数据通过metric名和键值对来区分。 ✧ 所有metrics都可以设置任意的多维标签。 ✧ 数据模型更随意,不需要刻意设置为以点分隔的字符串。 ✧ 可以对数据模型进行聚合、切割和切片操作。 ✧ 支持双精度浮点类型,标签可以设为全unicode。 ● 灵活而强大的查询语句(PromQL):在同一个查询语句中可以对多个metrics进行乘法、加法、连接、取分数位等操作。 ● 易于管理:Prometheus server是一个单独的二进制文件,可直接在本地工作,不依赖于分布式存储。 ● 高效:平均每个采样点仅占3.5B,且一个Prometheus server可以处理数百万的度量值。 ● 使用拉取(pull)模式采集时间序列数据,这样不仅有利于本机测试,而且可以避免有问题的服务器推送坏的度量值。 ● 可以采用push gateway方式把时间序列数据推送至Prometheus server端。 ● 可以通过服务发现或者静态配置获取监控的目标(target)。 ● 有多种可视化图形界面。
etcd
主要概念
Etcd是一个高可用的键值存储系统,通过Raft共识算法(The Raft Consensus Algorithm)处理日志复制以保证强一致性。
Raft:Etcd所采用的保证分布式系统强一致性的算法。
Node:一个Raft状态机实例。
Member:一个Etcd实例,它管理着一个Node,并且可以为客户端请求提供服务。
Cluster:由多个Member构成可以协同工作的Etcd集群。
Peer:对同一个Etcd集群中另外一个Member的称呼。
Client:向Etcd集群发送HTTP请求的客户端。
WAL:预写式日志,Etcd用于持久化存储的日志格式。
Snapshot:Etcd防止WAL文件过多而设置的快照,存储Etcd数据状态。
Proxy:Etcd的一种模式,为Etcd集群提供反向代理服务。
Leader:Raft算法中通过竞选而产生的处理所有数据提交的节点。
Follower:竞选失败的节点作为Raft中的从属节点,为算法提供强一致性保证。
Candidate:当Follower超过一定时间接收不到Leader的心跳时转变为Candidate开始竞选。
Term:某个节点成为Leader到下一次竞选的时间,称为一个Term。
Index:数据项编号。Raft中通过Term和Index来定位数据
架构
图
HTTP Server:用于处理用户发送的API请求以及其他Etcd节点的同步与心跳信息请求。
Store:用于处理Etcd支持的各类功能的事务,包括数据索引、节点状态变更、监控与反馈、事件处理与执行等,是Etcd对用户提供的大多数API功能的具体实现。
Raft:强一致性算法的具体实现,是Etcd的核心。
WAL:Write Ahead Log(预写式日志),是Etcd的数据存储方式,是用于向系统提供原子性和持久性的一系列技术。在使用WAL的时候,所有的修改在提交之前都要先写入日志文件中。Etcd的WAL由日志存储与快照存储两部分组成,其中,Entry负责存储具体日志的内容,而Snapshot负责在日志内容发生变化的时候保存Raft的状态。WAL会在本地磁盘的一个指定目录下分别存放日志条目与快照内容。
通信模型
Etcd属于分布式架构,其中的通信模型有两种,一种是Etcd Client同Etcd Server之间的通信,另一种是Etcd Peer之间的通信。
Client-to-Server 在默认设置下,Etcd通过主机的2379/4001端口向Client提供服务,每个主机上的应用程序都可以通过主机的2379/4001端口以HTTP+JSON的方式向Etcd读写数据。写入的数据会由Etcd同步到集群的其他节点中
Peer-to-Peer 在默认设置下,Etcd通过主机的2380/7001端口在各个节点中同步Raft状态及数据
service mesh
特征
◎ 基础设施:服务网格是一种处理服务间通信的基础设施层。
◎ 云原生:服务网格尤其适用于在云原生场景下帮助应用程序在复杂的服务拓扑间可靠地传递请求。
◎ 网络代理:在实际使用中,服务网格一般是通过一组轻量级网络代理来执行治理逻辑的。
◎ 对应用透明:轻量网络代理与应用程序部署在一起,但应用感知不到代理的存在,还是使用原来的方式工作。
Service Mesh作为透明代理,Service Mesh能做什么
·负载均衡:运行环境中微服务实例通常处于动态变化状态,而且经常可能出现个别实例不能正常提供服务、处理能力减弱、卡顿等现象。但由于所有请求对Service Mesh来说是可见的,因此可以通过提供高级负载均衡算法来实现更加智能、高效的流量分发,降低延时,提高可靠性。
·服务发现:以微服务模式运行的应用变更非常频繁,应用实例的频繁增加或减少带来的问题就是如何精确地发现新增实例以及避免将请求发送给已不存在的实例变得更加复杂。Service Mesh可以提供简单、统一、平台无关的多种服务发现机制,如基于DNS、K/V键值对存储的服务发现机制。
·熔断:动态的环境中服务实例中断或者不健康导致服务中断可能会经常发生,这就要求应用或其他工具具有快速监测并从负载均衡池中移除不提供服务实例的能力,这种能力也称熔断,以此使得应用无需消耗更多不必要的资源不断地尝试,而是快速失败或者降级,甚至这样可避免一些潜在的关联性错误。而Service Mesh可以很容易实现基于请求和连接级别的熔断机制。
·动态路由:随着服务提供商以提供高稳定性、高可用性以及高SLA服务为主要目标,为了实现所述目标,出现各种应用部署策略尽可能从技术手段达到无服务中断部署,以此避免变更导致服务的中断和稳定性降低,例如Blue/Green部署和Canary部署,但是实现这些高级部署策略通常非常困难。关于应用部署策略,可参考Etienne Tremel(https://thenewstack.io/deployment-strategies/)的文章,他对各种部署策略做了详细的比较。而如果运维人员可以轻松地将应用流量从staging环境切换到产线环境,一个版本切换到另外一个版本,或者从一个数据中心到另外一个数据中心进行动态切换,甚至可以通过一个中心控制层控制多少比例的流量被切换。那么Service Mesh提供的动态路由机制和特定的部署策略如Blue/Green部署结合起来,实现上述目标将更加容易。
·安全通信:无论何时,安全在整个公司、业务系统中都有着举足轻重的位置,也是非常难以实现和控制的部分。而微服务环境中,不同的服务实例间通信变得更加复杂,那么如何保证这些通信是在安全、有授权的情况下进行就非常重要。通过将安全机制如TLS加解密和授权实现在Service Mesh上,不仅可以避免在不同应用上的重复实现,而且很容易在整个基础设施层更新安全机制,甚至无需对应用做任何操作。
·多语言支持:由于Service Mesh作为独立运行的透明代理,很容易支持多语言。
·多协议支持:同多语言支持一样,实现多协议支持也非常容易。
·指标和分布式追踪:Service Mesh对整个基础设施层的可见性使得它不仅可以暴露单个服务的运行指标,而且可以暴露整个集群的运行指标。
·重试和最后期限:Service Mesh的重试功能避免将其嵌入到业务代码,同时最后期限使得应用允许一个请求的最长生命周期,而不是无休止的重试。 对这些功能,概括起来,即Service Mesh使得微服务具有下列性能。
·可见性(visiblity):运行时指标、分布式跟踪。
·可管理性(manageablity):服务发现、负载均衡、运行时动态路由。
·健壮性(resilience):超时重试、请求最后期限、熔断机制。
·安全性(security):服务间访问控制、TLS加密通信。
资料
书籍和网站
官网Concepts | Kubernetes
[掌阅]kubernates源码剖析
[掌阅]kubernates生产化实践之路
[掌阅]Kubernetes进阶实战
[掌阅]Kubernetes权威指南:企业级容器云实战
Kubernetes 中文指南
基于 Kubernetes 的微服务项目设计与实现
Kubernetes 安全原理与实践
其他资料
k8s概念入门
项目
https://github.com/kubernetes-in-the-enterprise
云原生
云原生架构原则
服务化原则
弹性原则
按功能切割应用
支持水平切分
自动化部署
支持服务降级
可观测原则
数据的全面采集:指标、链路跟踪、日志
数据的关联分析
统一监控视图与展现
韧性原则
所有过程自动化原则
标准化
面向终态
关注点分离
面向失败设计
零信任原则
显式验证
最少权限
假设被攻破
不能基于IP配置安全策略
身份应该成为基础设施
标准的发布流水线
架构持续演进原则
云原生架构的模式
服务化架构模式
更好地面向业务
松耦合和灵活性
服务共享和复用
Service Mesh化架构模式
SideCar模式
服务路由和可靠性保证
隔离性和安全性
为应用减负
服务调用的可观测性
服务注册发现模式
中心化Broker模式
Serverless架构模式
计算存储分离模式
分布式事务模式
两阶段提交
BASE
TCC
Saga
AT
可观测架构模式
事件驱动架构模式
网关架构模式
混沌工程模式
声明式设计模式
云原生架构的反模式
庞大的单体应用
单体应用“硬拆”为微服务
缺乏自动化能力的微服务
架构不能充分使用云的弹性能力
技术架构与组织能力不匹配
云原生技术及概念介绍
DevOps技术
基本思想:持续交付
基本理念&原则:CAMS
文化 Cultrue
自动化 Automation
度量 Measurement
共享 Sharing
工具链
项目协作:包括需求管理、任务管理、版本管理、里程碑管理等能力。
代码管理:包括代码托管、代码评审、智能代码扫描、敏感信息扫描、代码安全风控等能力。
构建:支持不同语言、不同格式的软件构建及制品打包。
测试:包括测试用例、测试计划、混沌工程等能力。
制品库:不同类型的制品仓库及其管理,如Maven仓库、Helm仓库等。
交付:自动化部署和回滚能力。
流水线:将集成、代码扫描、编译构建、制品打包、自动化部署及回滚等能力结合在一起,形成一条自动化的流水线作业。
运维:静态和动态配置管理,对基础设施和应用系统持续提供监控能力
声明式运维IaC
幂等性:反复提交也不会出现任何副作用
复杂性抽象:降低运维复杂度
GitOps:IaC运维理念的一种具体的落地方式;其核心是一个GitOps引擎,负责监控Git中应用系统的状态
阿里巴巴DevOps实践
微服务
设计约束原则
微服务个体约束:业务域划分上相互独立,按照问题域对单体应用做合理拆分
微服务与微服务之间的横向关系
可发现性:服务发布或扩缩容可被其他关联服务发现
可交互性:服务交互方式
微服务与数据层之间的纵向约束:提出数据存储隔离
全局视角下的微服务分布式约束
高效自动化运维,支持蓝绿灰度等不同发布策略
可观测能力,全链路、实时、多维度
Serverless
特征
全托管的计算服务
通用性
自动的弹性伸缩
按量计费
FaaS,最具代表性的服务形态。把应用逻辑拆分为多个函数,并通过事件驱动的方式触发执行每个函数。
开放应用模型(OAM)
核心技术思想
云原生应用,建立对应用和所需运维能力定义与描述的标准规范
提供更高层级的应用层抽象和关注点分离的定义模型
核心概念
应用组件依赖:定义和规范组成应用的组件
应用运维特征:定义和规范应用所需运维特征的集合
应用配置:定义和规范应用实例化所需的配置机制,从而能够将上述这些描述转化为具体的应用实例
应用场景案例:KubeVela
可插拔式的能力模块
工作负载与运维能力标准化交互机制
拓展能力
组件版本管理
组件间的依赖关系与参数传递
组件运维策略
Service Mesh技术
微服务架构带来的问题:服务连接、安全、控制和可观测性等
传统框架SDK解决方式的局限性
多编程语言问题:单一编程语言无法有效实现所有业务需求,多编程语言场景的出现导致相同功能的SDK需要用不同的编程语言重复开发,且不同编程语言的SDK需要同时维护和迭代,共享困难。
与应用紧耦合不便于维护:SDK与应用在同一个进程中紧密耦合,这种强绑定关系使它们无法独立快速演进,从而陷入了基础技术与业务发展相互制约的困境。
业界方案
产品
Istio
Linkerd、Consul
Conduit
标准化
xDS协议:Istio采纳了Envoy所实现的xDS协议,并将该协议当作数据平面和控制平面之间的标准协议
SMI:微软提出了Service Mesh Interface(SMI),致力于对数据平面和控制平面的标准化做更高层次的抽象,以期让Istio、Linkerd等不同的Service Mesh解决方案在服务观测、流量控制等方面实现最大程度的开源能力复用
UDPA:UDPA(Universal DataPlane API,通用数据平面API)是基于xDS协议发展起来的API标准。通过它,企业可以根据云平台的特定需求便捷地扩展。新的xDS v4将基于UDPA。
方案特点
整体收益要远大于额外IPC通信所支出的成本
非常利于实现云原生时代的零信任架构
阿里巴巴对ServiceMesh的实践历程
分布式消息队列
技术架构
客户端:提供了消息的接收与订阅API,同时内置了重试、熔断等高可用功能。
注册中心:提供了集群管理、元数据管理、路由和服务发现等功能。
计算节点:在消息队列的服务端Broker中,计算部分包含高性能的传输层以及可扩展的RPC框架,用于处理来自客户端的不同请求。
存储引擎:Broker的核心为存储引擎,某些消息队列可能会将存储引擎与计算节点拆分开来,主要是为消息提供高性能持久化,以队列方式组织消息,用以保证消息必达。
主流产品
RocketMQ:RocketMQ是用Java语言开发的,它采用“发布——订阅”模式传递消息,具有高效灵活的水平扩展能力和海量消息堆积能力
Kafka:Kafka的最大特性是可以实时处理大数据以满足各种需求场景。Kafka是用Scala语言开发的,其客户端在主流的编程语言里都有对应支持,如Scala、C++、Python、Java、GO、PHP等。
云原生BaaS(Backend as a Service)的挑战
高SLA——更稳
高性能——更快
极致弹性——随需伸缩
标准——支持跨平台
原生事件支持——具体表现形态?
阿里巴巴的RocketMQ实践
典型特性:事务最终一致性
prepare消息:不会被下游服务消费,需要上一环节业务处理完后,再进行提交或回滚
超时回查机制:如果很长时间后还没有提交或回滚,那么RocketMQ就会主动向上游服务发起回查
云原生数据库技术
特性与价值:云原生数据库的易用性、高扩展性、快速迭代、节约成本等特性,为企业提供了增强的可靠性和可伸缩性,很好地解决了企业用户的核心诉求。从资源池化、弹性扩展、智能运维,到离线、在线一体化,利用云原生数据的这些核心特性,企业可以实现存储、管理和提取数据等多种目的。
典型技术
云原生关系型数据库(RDMS)
优势
资源池化,大容量,高性价比
提供极致弹性
强大的容灾能力及可靠性
完全托管
智能化+自动化管控平台
核心技术(PolarDB)
计算存储分离技术——分布式共享存储架构
计算内存分离技术——缓冲区和计算节点分离
高可用及数据的一致性——PolarFS分布式文件系统
跨节点、跨机房的数据同步
数据及日志的复制技术——物理日志复制
跨地域高可用技术
Serverless及多租户技术
HTAP(HybridTransaction/Analytical Processing,混合事务/分析处理)
云原生多模数据库(NoSql)
多模计算技术:KV数据 → 时序数据 → 抽象为文件模型
生态兼容能力:提供数据上下游流动能力
数据库Serverless化
数据库安全
网络和账号访问控制——源IP白名单;安全组
数据库访问传输加密——SSL
数据库透明加密和存储加密——落盘加密&数据库透明加密
落盘加密是通过基础设施层的云盘加密能力实现的。
数据库透明加密是数据库特有的加密方式,即对数据文件执行实时I/O加密和解密,数据在写入磁盘之前进行加密,从磁盘读入内存时进行解密,密钥使用KMS进行管理。所谓透明,是指对应用来说是无感知的,数据在内存中仍然是解密状态。
端到端的全链路加密——TEE
备份容灾安全——风控措施
云原生大数据
企业数据需求特点:海量、类型多样化、处理实时化、智能化
数据分析能力要求:弹性扩展、结构化、半结构化或非结构化海量数据存储计算,一份存储、多种计算及低成本等
典型技术
数据仓库——离线实时一体化
多租户安全体系
数据中心安全基础设施
数据仓库安全系统
数据治理类安全产品
离线实时在线一体架构
存储与计算分离
数据加密存储
不同数据服务之间资源共享
支持智能调度,根据服务的优先级不同合理分配资源
数据湖
与数据仓库的特征区别
数据湖存储企业的全部数据,包括来自业务系统的关系数据以及来自移动应用程序、IoT设备和社交媒体的非关系数据;而数据仓库只存储关系数据。数据湖直接存储全部原始数据,数据保真度高。
· 数据湖收集数据时无须设计好数据结构,不需要像数据仓库那样事先定义模式,而是在分析时根据业务场景再给出模式,从而使数据收集更加敏捷。
· 随着人工智能(Artificial Intelligence,AI)技术的发展和成熟,数据湖除了支持商业智能(Business Intelligence,BI)之外,逐步加大机器学习的比重。数据仓库主要支持SQL查询和分析,通常是周期性地采集数据。而数据湖由于实时计算技术的加持,可以做到数据实时入湖。
· 大数据技术的发展使得低成本存储全部原始数据和进行各种计算分析成为可能,这一点是传统的数据仓库无法想象的。
云上部署数据湖的价值
低成本存储——按量付费、冷热分层
业务智能——弹性计算能力和各种计算产品
降低运维成本——内置数据湖管理能力和数据治理能力
数据湖架构
数据湖存储
数据湖加速
数据湖计算
数据湖管理
实时计算
查询语言——CQL持续查询语言
时间与水位——处理乱序数据
状态数据管理
故障恢复和高可用——保证低延迟
主动待机
被动待机
动态负载管理
数据治理
数据治理的本质就是以元数据作为“神经中枢”,收集业务数据、发现业务问题、分析业务问题、解决业务问题。
数据治理宏观架构
元数据采集层
存储层
计算层
数据质量:通过对规则和时间序列的分析,可以监控数据的波动和异常,及时发现数据仓库体系中数据的异常问题。
数据探查:分析表中数据的统计学规律(比如最大值、最小值、分位数、直方图等),以帮助数据开发工程师、数据分析师更好地理解数据。
资源优化:分析各个引擎的存储和计算任务的信息,为用户提供成本优化建议和数据仓库的最佳实践。其中,分析的产出包括数据倾斜、任务冲突、表的管理建议、数据集成任务的配置优化建议、数据开发的最佳实践等。
血缘分析:数据血缘分析的本质是图遍历。通过对节点进行正向和反向的遍历,我们还可以发现数据的传递性和复用性。在血缘图中增加数据使用者的信息,即可找到人和数据的关系,这在业务上既可以进一步分析数据资产的价值度,又可以帮助用户发现冗余资产和无效的业务流程。
服务层
在线访问的API——微服务方式
离线加工后的元数据——云数据仓库授权、数据集成和同步数据等
异步推送实时数据
云原生AI
典型技术
云原生AI训练平台——PAI-DLC,分布式训练引擎
云原生在线推理服务平台
云原生AI服务编排
云端开发
优势
开箱即用
弹性的计算资源
多人协作
困难
云上和本地的差异
编码和调试体验差异(云端网络延迟)
对大型复杂项目的支持
与本地工具的联动
陌生的开发生态
隐形成本——创建资源简单,容易忘记或忽略关闭资源
案例效果
应用上容器云的准入条件
已建立了清晰的可自动化的编译及构建流程:应用使用如Maven、Gradle、Make或Shell等工具实现了构建编译步骤的自动化,以便在容器平台上实现自动化的编译及构建流程。
已实现应用配置参数外部化:应用已将配置参数外部化于配置文件或环境变量中,以便应用容器能适配不同的运行环境。包含特定环境的配置的容器镜像不能在整个环境(Dev、QA、Prod)中升级。为了实现可靠的发布过程,应将在较低环境中测试过的相同镜像部署到生产中。将特定环境的配置保留在容器镜像之外,例如,使用ConfigMap和Secret存储应用程序配置。
已提供合理可靠的健康检查接口:容器平台将通过健康检查接口判断容器状态,对应用服务进行状态保持。
已实现状态外部化,以及应用实例无状态化:应用状态信息存储于数据库或缓存等外部系统,应用实例本身实现无状态化。
不涉及底层的操作系统依赖及复杂的网络通信机制:应用以处理业务为主,不强依赖于底层操作系统及组播等网络通信机制,提高可移植性。
部署交付件及运行平台的大小在2GB以内:轻量级的应用便于在大规模集群中快速传输分发,更符合容器敏捷的理念。
启动时间在5分钟以内:过长的启动时间将不能发挥容器敏捷的特性。 如果应用明显不符合上述条件,则其暂时不适合运行在容器上。
在应用上容器云最佳实践。
在Pod定义中指定资源请求和资源限制。如果请求资源的配置不正确的话,那么应用程序可能会耗尽内存或导致CPU资源不足。指定请求的内存和CPU资源可以使集群做出适当的调度决策,以确保应用程序具有足够的可用资源。
使用Pod中断预算保护应用程序。在某些情况下,需要将应用程序容器从集群节点中逐出。例如,在管理员可以执行节点维护之前或在缩减规模时集群自动缩放器可以从集群中删除节点之前,需要驱逐Pod。为确保驱逐Pod时应用程序仍然可用,你必须定义各自的PodDistruptionBudget对象。PodDisruptionBudget是一个API对象,用于指定保证应用可用的最小副本数或最小百分比。
每个容器运行一个进程。避免在单个容器中运行多个进程。每个容器中运行一个进程可以更好地隔离进程,避免信号路由出现问题。
应用程序监视和警报。应用程序监视和警报对保持应用程序在生产中良好运行并满足业务目的至关重要。可以使用Prometheus和Grafana等监视工具来监视你的应用程序。
配置应用程序以将其日志写入stdout或stderr。容器云将收集这些日志并将其发送到集中位置(ELK、Splunk)。在分析生产问题时,应用程序日志是宝贵的资源。基于应用程序日志内容的警报有助于确保应用程序按预期运行。 考虑实施弹性措施:断路器、超时、重试、速率限制。弹性措施可以使你的应用程序在出现故障时表现更好。它们可保护你的应用程序免于过载(速率限制、断路器),并在遇到连接问题(超时、重试)时提高性能。考虑利用OpenShift Service Mesh来实现这些措施,需在应用程序中更改代码。
使用受信任的基础镜像(base image)。尽可能使用容器厂商提供的企业级容器镜像。容器厂商提供的镜像已通过测试、安全加固并有相应的技术支持。如果使用社区提供的镜像,请尽量使用你信任的社区提供的镜像。不要使用公共注册表(例如Docker Hub)中有未知来源的镜像。
使用最新版本的基础镜像。通常,仅最新版本的容器镜像包含所有可用的安全修复程序。设置CI管道,以便在构建应用程序镜像时始终提取最新版本的基础镜像,同时在更新的基础镜像可用时重建应用程序。 使用单独的构建镜像和运行时镜像。创建具有最小依赖的、单独的运行时镜像可减少攻击面,并产生较小的运行时镜像。构建镜像包含构建依赖关系,构建依赖关系对于构建应用程序是必需的,对于运行应用程序则不是必需的。
尽可能遵守受限制的安全上下文约束(SCC)。修改你的容器镜像以允许在受限制的SCC下运行。应用程序容易受到攻击,强制使用OpenShift受限制的SCC可提供最高级别的安全性,以防止在应用程序被破坏的情况下损害集群节点。
使用TLS保护应用程序组件之间的通信。应用程序组件可能会传达应受到保护的敏感数据。除非你认为基础OpenShift网络是安全的,否则你可能希望利用TLS保护应用程序组件之间的通信。考虑利用OpenShift Service Mesh从应用程序中卸载TLS管理。 2.1.5 应用容器化迁移步骤 针对已有传统应用系统的改造迁移,通常需要经过如图2-5所示的流程。 图
12原则
1.基础代码 一份代码,多份部署。
软件开发过程中一个常见的情况是,由于项目需求的变化,一个代码仓库的代码会被分解成多个分支,以适应不同项目的特殊需求,而这些分支随着需求的变化会分解成新的不同的分支。这种树状发散的项目分支为项目的后期管理带来了极大的成本和风险。同一个应用应该基于同一份基础代码,在不同环境下的部署的区别应该只是配置或版本不同而已。 Kubernetes 中运行的程序应遵循同样的原则,无论开发环境、测试环境、还是生产环境,都应该基于同一份代码进行构建。
2.依赖管理 明确定义项目依赖。
缺少明确的依赖声明,会导致在应用的开发、编译、运行过程中依赖错误的第三方类库或版本,进而引发不可预知的系统故障。现代开发语言往往提供完备的依赖管理,可通过声明文件明确定义依赖关系,比如 Golang 的依赖管理工具包括godep、glide、go module 等。在源码编译时,这些依赖包会被编译至最终的可执行文件中。这有利于提升开发效率,降低项目风险。 容器技术的封装特性使得应用程序的运行环境是隔离的,任何可执行文件所依赖的系统服务或工具都必须以代码形式定义在Dockerfile 中,应用及应用的依赖会被同时构建到容器镜像中。
3.配置
通常,应用的配置在不同部署(预发布、生产环境、开发环境等)间会有很大差异。这类与发布环境相关的有依赖关系的应用配置应该与代码分离开来,以配置文件或环境变量的方式注入应用中。
4.后台服务
无状态应用更易于管理,在进行分布式系统规划时,应将有状态服务与无状态服务进行分离,将有状态应用作为附加资源配置给无状态应用。比如,对一个网站应用而言,可以将数据库和无状态的Web 服务进行拆分,数据库的访问URL 和用户名密码应该作为配置文件配置到Web 服务中。
5.构建、发布和运行
构建、发布和运行是软件生命周期中最重要的三个阶段。Docker 等容器技术所倡导的一次构建、到处运行的理念是对该原则的完美实现。
6.进程管理
以一个或多个无状态进程形式运行应用,进程应该是无状态的,任何数据不应该做本地持久化。我们不推荐基于用户会话构建业务逻辑,这样有利于对服务实例进行横向扩展和故障转移等运维管理。
7.端口绑定 以端口绑定的形式发布服务,容器进程运行在不同的网络命名空间,彼此隔离,应用可以运行在任意端口上。当需要将应用发布供外部客户端调用时,一个常见的做法是:通过端口映射将容器内的进程映射至主机端口。
8.并发 基于水平扩展提升系统的并发处理能力,通过运行多个应用实例并构建负载均衡,使得应用可按需伸缩,这有助于提高资源利用率,以低成本提升系统的并发处理能力。
9.易管理 快速启动是保证应用灵活部署的前提条件,应用启动速度慢会拖慢系统扩展的能力和故障恢复的能力。当出现故障转移时,启动速度直接影响应用被影响的时间周期。优雅地中止能够保证应用进程在接收中止服务信号后,先处理请求队列中的所有请求,再中止进程,以提升系统的可靠性。容器技术鼓励以小镜像快速启动应用程序,Kubernetes 的Pod允许依据应用的定义来自定义优雅中止的时间。性 容器镜像的分层文件结构和封装性,确保了其一次构建、随处运行的能力,使得研发环境中构建的容器镜像可以方便地推送至生产系统。
11.日志 Kubernetes 以事件流的形式管理日志,方便对日志进行管理和抽象,并提供多套方案,允许基于不同组件采集、分析日志。
12.管理进程 研发人员还需要利用代码对应用进程进行管理或者对不同版本的数据进行迁移。这些代码应该与应用程序一致,同样需要针对代码和配置进行版本管理。 容器是一个抽象的概念,操作系统及容器中运行的程序对容器通常是无感知的。容器技术和虚拟机的一个重要区别是:容器技术不模拟操作系统,缺少操作系统层面的隔离。在容器内部执行系统命令,返回的信息大部分是主机信息,而不是容器相关的信息,这给应用落地带来了挑战。
应用落地主要有以下两方面内容:
1.应用容器化 传统应用通常是基于物理机或虚拟机部署的,应用进程通常假定拥有其所在计算节点的所有资源。因此,某些应用会读取系统资源作为启动配置项。比如,Java 虚拟机会以操作系统内存的固定百分比作为初始堆大小。而在容器世界中,CGroup 会限制该容器能使用的最大内存,如果配置不得当,就会使得内存超出限额而被强行终止,从而导致 Java虚拟机无法完成初始化。
2.Kubernetes 集群中的应用管理 应用的每个实例是否有独特配置或数据,决定了应用是有状态还是无状态。如果是有状态应用;则定义 StatefulSet 或Operator;如果是无状态应用,则定义Deployment 或ReplicaSet。对于有状态应用,还需规划存储卷。遵循代码和配置分离的原则,配置文件不应该编译进容器镜像,如有密码证书等