kubernetes权威指南第四版读书笔记
yum list kubelet --showduplicates | sort -r
kubernetes权威指南第四版读书笔记
第一章、第二章
1. HPA与之前的RC、Deployment一样,也属于一种Kubernetes资源对象。通过追踪分析指定RC控制的所有目标Pod的负载变化情况,来确定是否需要有针对性地调整目标Pod的副本数量,这是HPA的实现原理。HPA有以下两种方式作为Pod负载的度量指标:CPUUtilizationPercentage 应用程序自定义的度量指标,比如服务在每秒内的相应请求数(TPS或QPS)。CPUUtilizationPercentage是一个算术平均值,即目标Pod所有副本自 身的CPU利用率的平均值。一个Pod自身的CPU利用率是该Pod当前CPU 的使用量除以它的Pod Request的值,比如定义一个Pod的Pod Request为 0.4,而当前Pod的CPU使用量为0.2,则它的CPU使用率为50%,这样就 可以算出一个RC控制的所有Pod副本的CPU利用率的算术平均值了。在CPUUtilizationPercentage计算过程中使用到的Pod的CPU使用量通常是1min内的平均值
2. StatefulSet特性:StatefulSet里的每个Pod都有稳定、唯一的网络标识,可以用来 发现集群内的其他成员。StatefulSet控制的Pod副本的启停顺序是受控的,操作第n个Pod 时,前n-1个Pod已经是运行且准备好的状态。StatefulSet里的Pod采用稳定的持久化存储卷,通过PV或PVC来 实现,删除Pod时默认不会删除与StatefulSet相关的存储卷(为了保证数 据的安全)StatefulSet除了要与PV卷捆绑使用以存储Pod的状态数据,还要与 Headless Service配合使用,即在每个StatefulSet定义中都要声明它属于 哪个Headless Service。Headless Service与普通Service的关键区别在于, 它没有Cluster IP,如果解析Headless Service的DNS域名,则返回的是该 Service对应的全部Pod的Endpoint列表。StatefulSet在Headless Service的 基础上又为StatefulSet控制的每个Pod实例都创建了一个DNS域名:podname.headless.service name
3. PV: accessModes属性,目前有以下类型: ReadWriteOnce:读写权限,并且只能被单个Node挂载 ReadOnlyMany:只读权限,允许被多个Node挂载 ReadWriteMany:读写权限,允许被多个Node挂载
PV是有状态的对象,它的状态有以下几种:Available:空闲状态。Bound:已经绑定到某个PVC上。Released:对应的PVC已经被删除,但资源还没有被集群收 回。Failed:PV自动回收失败。Reclaiming:用户删除PVC释放对PV的占用后,系统根据PV的"reclaim policy"决定对PV执行何种回收操作。 目前,"reclaim policy"有三种方式:Retained、Recycled、Deleted。Retained:保护被PVC释放的PV及其上数据,并将PV状态改成"released",不将被其它PVC绑定。集群管理员手动通过如下步骤释放存储资源:手动删除PV,但与其相关的后端存储资源如(AWS EBS, GCE PD, Azure Disk, or Cinder volume)仍然存在。手动清空后端存储volume上的数据。手动删除后端存储volume,或者重复使用后端volume,为其创建新的PV Delete:删除被PVC释放的PV及其后端存储volume。对于动态PV其"reclaim policy"继承自其"storage class",默认是Delete。集群管理员负责将"storage class"的"reclaim policy"设置成用户期望的形式,否则需要用户手动为创建后的动态PV编辑"reclaim policy"
4. kubelet的职责在于通过RPC管理容器的生命周期,实现容器生命周 期的钩子,存活和健康监测,以及执行Pod的重启策略等
5. 目前还有一个潜在问题是,kubelet处理所有的请求连接,使其有成 为Node通信瓶颈的可能。在设计CRI时,要让容器运行时能够跳过中间 过程。容器运行时可以启动一个单独的流式服务来处理请求(还能对 Pod的资源使用情况进行记录),并将服务地址返回给kubelet。这样 kubelet就能反馈信息给API Server,使之可以直接连接到容器运行时提 供的服务,并连接到客户端
6. 目前已经有多款开源CRI项目可用于Kubernetes:Docker、CRI-O、 Containerd、frakti(基于Hypervisor的容器运行时),各CRI运行时的安 装手册可参考官网https://kubernetes.io/docs/setup/cri/的说明
第三章
1. kubelet只支持可以被API Server管理的Pod使用ConfigMap。 kubelet在本Node上通过 --manifest-url或--config自动创建的静态Pod将无 法引用ConfigMap
2. 在Pod对ConfigMap进行挂载(volumeMount)操作时,在容器 内部只能挂载为“目录”,无法挂载为“文件”。在挂载到容器内部后,在 目录下将包含ConfigMap定义的每个item,如果在该目录下原来还有其 他文件,则容器内的该目录将被挂载的ConfigMap覆盖
3. 将Pod信息注入为环境变量:
apiVersion: v1 kind: pod metadata: name: {{ template "module.name" . }} namespace: {{ $.Release.Namespace }} spec: containers: - name: {{ .Chart.Name }} image: {{ .Values.image.repository }}:{{ .Values.image.tag }} env: - name: MY_POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: MY_POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: MY_POD_IP valueFrom: fieldPath: matadata.podIP
4. 将容器资源信息注入为环 境变量
apiVersion: v1 kind: pod metadata: name: {{ template "module.name" . }} namespace: {{ $.Release.Namespace }} spec: containers: - name: {{ .Chart.Name }} image: {{ .Values.image.repository }}:{{ .Values.image.tag }} resources: requests: memory: "32Mi" cpu: "125m" limits: memory: "64Mi" cpu: "250m" env: - name: MY_CPU_REQUEST valueFrom: resourceFieldRef: containerName: test-container resource: request.cpu
5. 通过Downward API将Pod的Label、Annotation列表通过 Volume挂载为容器中的一个文件
apiVersion: v1 kind: pod metadata: name: {{ template "module.name" . }} namespace: {{ $.Release.Namespace }} labels: zone: us-est-coast cluster: test-cluster1 rack: rack-22 annotations: build: two builder: john-doe spec: containers: - name: {{ .Chart.Name }} image: {{ .Values.image.repository }}:{{ .Values.image.tag }} resources: requests: memory: "32Mi" cpu: "125m" limits: memory: "64Mi" cpu: "250m" volumeMounts:
- name: pidinfo
mountPath: /etc
readOnly: false
volumes:
- name: podinfo
downloadAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations
这里要注意“volumes”字段中downwardAPI的特殊语法,通过items 的设置,系统会根据path的名称生成文件。根据上例的设置,系统将在 容器内生成/etc/labels和/etc/annotations两个文件。Downward API的价值.在某些集群中,集群中的每个节点都需要将自身的标识(ID)及进程绑定的IP地址等信息事先写入配置文件中,进程在启动时会读取这些 信息,然后将这些信息发布到某个类似服务注册中心的地方,以实现集 群节点的自动发现功能
6. pod的生命周期:kubelet重启失效容器的时间间隔以sync-frequency乘以2n来计算,例 如1、2、4、8倍等,最长延时5min,并且在成功重启后的10min后重置该时间.
7. NodeAffinity:Node亲和性调度 NodeAffinity意为Node亲和性的调度策略,是用于替换NodeSelector 的全新调度策略。目前有两种节点亲和性表达:◎ RequiredDuringSchedulingIgnoredDuringExecution:必须满足指定的规则才可以调度Pod到Node上(功能与nodeSelector很像,但是使用 的是不同的语法),相当于硬限制 ◎ PreferredDuringSchedulingIgnoredDuringExecution:强调优先满足指定规则,调度器会尝试调度Pod到Node上,但并不强求,相当于软 限制。多个优先级规则还可以设置权重(weight)值,以定义执行的先 后顺序。IgnoredDuringExecution的意思是:如果一个Pod所在的节点在Pod运 行期间标签发生了变更,不再符合该Pod的节点亲和性需求,则系统将 忽略Node上Label的变化,该Pod能继续在该节点运行
8. PodAffinity:Pod亲和与互斥调度策略。Pod间的亲和与互斥从Kubernetes 1.4版本开始引入。这一功能让用 户从另一个角度来限制Pod所能运行的节点:根据在节点上正在运行的 Pod的标签而不是节点的标签进行判断和调度,要求对节点和Pod两个条 件进行匹配。这种规则可以描述为:如果在具有标签X的Node上运行了 一个或者多个符合条件Y的Pod,那么Pod应该(如果是互斥的情况,那么就变成拒绝)运行在这个Node上
9. Pod的互斥性调度
10. Taints和Tolerations(污点和容忍)Taint需要和Toleration配合使用,让Pod避开那些不合适的Node。在 Node上设置一个或多个Taint之后,除非Pod明确声明能够容忍这些污 点,否则无法在这些Node上运行。Toleration是Pod的属性,让Pod能够 (注意,只是能够,而非必须)运行在标注了Taint的Node上 可以用kubectl taint命令为Node设置Taint信息:kubectl taint nodes node1 key=value:NoSchedule 这个设置为node1加上了一个Taint。该Taint的键为key,值为 value,Taint的效果是NoSchedule。这意味着除非Pod明确声明可以容忍 这个Taint,否则就不会被调度到node1上.然后,需要在Pod上声明Toleration。下面的两个Toleration都被设置 为可以容忍(Tolerate)具有该Taint的Node,使得Pod能够被调度到 node1上
tolerations: - key: "key" operator: "Equal" value: value effect: "NoSchedule"
或者
tolerations: - key: "key" operator: "Exists" effect: "NoSchedule"
Pod的Toleration声明中的key和effect需要与Taint的设置保持一致, 并且满足以下条件之一。◎ operator的值是Exists(无须指定value)。◎ operator的值是Equal并且value相等。如果不指定operator,则默认值为Equal。另外,有如下两个特例。◎ 空的key配合Exists操作符能够匹配所有的键和值。◎ 空的effect匹配所有的effect。在上面的例子中,effect的取值为NoSchedule,还可以取值为 PreferNoSchedule,这个值的意思是优先,也可以算作NoSchedule的软 限制版本—一个Pod如果没有声明容忍这个Taint,则系统会尽量避免把 这个Pod调度到这一节点上,但不是强制的系统允许在同一个Node上设置多个Taint,也可以在Pod上设置多个 Toleration。Kubernetes调度器处理多个Taint和Toleration的逻辑顺序为: 首先列出节点中所有的Taint,然后忽略Pod的Toleration能够匹配的部 分,剩下的没有忽略的Taint就是对Pod的效果了。下面是几种特殊情◎ 如果在剩余的Taint中没有NoSchedule效果,但是有 PreferNoSchedule效果,则调度器会尝试不把这个Pod指派给这个节点。◎ 如果在剩余的Taint中存在effect=NoSchedule,则调度器不会把 该Pod调度到这一节点上。◎ 如果在剩余的Taint中有NoExecute效果,并且这个Pod已经在该节点上运行,则会被驱逐;如果没有在该节点上运行,则也不会再被调 度到该节点上。
11. Pod Priority Preemption:Pod优先级调度。在Kubernetes 1.8版本之前,当集群的可用资源不足时,在用户提交 新的Pod创建请求后,该Pod会一直处于Pending状态,即使这个Pod是一 个很重要(很有身份)的Pod,也只能被动等待其他Pod被删除并释放资 源,才能有机会被调度成功。Kubernetes 1.8版本引入了基于Pod优先级 抢占(Pod Priority Preemption)的调度策略,此时Kubernetes会尝试释 放目标节点上低优先级的Pod,以腾出空间(资源)安置高优先级的 Pod,这种调度方式被称为“抢占式调度”。在Kubernetes 1.11版本中,该 特性升级为Beta版本,默认开启,在后继的Kubernetes 1.14版本中正式 Release。如何声明一个负载相对其他负载“更重要”?我们可以通过以下 几个维度来定义:◎ Priority,优先级;◎ QoS,服务质量等级;◎ 系统定义的其他度量指标。优先级抢占调度策略的核心行为分别是驱逐(Eviction)与抢占 (Preemption),这两种行为的使用场景不同,效果相同。Eviction是 kubelet进程的行为,即当一个Node发生资源不足(under resource pressure)的情况时,该节点上的kubelet进程会执行驱逐动作,此时 Kubelet会综合考虑Pod的优先级、资源申请量与实际使用量等信息来计 算哪些Pod需要被驱逐;当同样优先级的Pod需要被驱逐时,实际使用的 资源量超过申请量最大倍数的高耗能Pod会被首先驱逐。对于QoS等级 为“Best Effort”的Pod来说,由于没有定义资源申请(CPU/Memory Request),所以它们实际使用的资源可能非常大。Preemption则是 Scheduler执行的行为,当一个新的Pod因为资源无法满足而不能被调度 时,Scheduler可能(有权决定)选择驱逐部分低优先级的Pod实例来满 足此Pod的调度目标,这就是Preemption机制。需要注意的是,Scheduler可能会驱逐Node A上的一个Pod以满足 Node B上的一个新Pod的调度任务
12. deployment更新。当更新Deployment时,系统创建了一个新的 ReplicaSet(nginx-deployment-3599678771),并将其副本数量扩展到 1,然后将旧的ReplicaSet缩减为2。之后,系统继续按照相同的更新策 略对新旧两个ReplicaSet进行逐个调整。最后,新的ReplicaSet运行了3个 新版本Pod副本,旧的ReplicaSet副本数量则缩减为0
13. 自动扩缩容机制。Kubernetes中的某个Metrics Server(Heapster或自定义Metrics Server)持续采集所有Pod副本的指标数据。HPA控制器通过Metrics Server的API(Heapster的API或聚合API)获取这些数据,基于用户定义 的扩缩容规则进行计算,得到目标Pod副本数量。当目标Pod副本数量与 当前副本数量不同时,HPA控制器就向Pod的副本控制器 (Deployment、RC或ReplicaSet)发起scale操作,调整Pod的副本数量, 完成扩缩容操作
14. 指标的类型Master的kube-controller-manager服务持续监测目标Pod的某种性能 指标,以计算是否需要调整副本数量。目前Kubernetes支持的指标类型 如下:◎ Pod资源使用率:Pod级别的性能指标,通常是一个比率值,例 如CPU使用率。 ◎ Pod自定义指标:Pod级别的性能指标,通常是一个数值,例如接收的请数量。◎ Object自定义指标或外部自定义指标:通常是一个数值,需要 容器应用以某种方式提供,例如通过HTTP URL“/metrics”提供,或者使 用外部服务提供的指标采集URL。Kubernetes从1.11版本开始,弃用基于Heapster组件完成Pod的CPU 使用率采集的机制,全面转向基于Metrics Server完成数据采集。Metrics Server将采集到的Pod性能指标数据通过聚合API(Aggregated API)如 metrics.k8s.io、custom.metrics.k8s.io和external.metrics.k8s.io提供给HPA 控制器进行查询
15. 扩缩容算法详解 Autoscaler控制器从聚合API获取到Pod性能指标数据之后,基于下 面的算法计算出目标Pod副本数量,与当前运行的Pod副本数量进行对 比,决定是否需要进行扩缩容操作:
即当前副本数×(当前指标值/期望的指标值),将结果向上取整。当计算结果与1非常接近时,可以设置一个容忍度让系统不做扩缩 容操作。容忍度通过kube-controller-manager服务的启动参数--horizontal- pod-autoscaler-tolerance进行设置,默认值为0.1(即10%),表示基于上 述算法得到的结果在[-10%-+10%]区间内,即[0.9-1.1],控制器都不会进行扩缩容操作。也可以将期望指标值(desiredMetricValue)设置为指标的平均值类 型,例如targetAverageValue或targetAverageUtilization,此时当前指标值 (currentMetricValue)的算法为所有Pod副本当前指标值的总和除以Pod 副本数量得到的平均值。此外,存在几种Pod异常的情况,如下所述:◎ Pod正在被删除(设置了删除时间戳):将不会计入目标Pod副本数量。◎ Pod的当前指标值无法获得:本次探测不会将这个Pod纳入目标 Pod副本数量,后续的探测会被重新纳入计算范围。◎ 如果指标类型是CPU使用率,则对于正在启动但是还未达到 Ready状态的Pod,也暂时不会纳入目标副本数量范围。可以通过kube- controller-manager服务的启动参数--horizontal-pod-autoscaler-initial- readiness-delay设置首次探测Pod是否Ready的延时时间,默认值为30s。 另一个启动参数--horizontal-pod-autoscaler-cpuinitialization-period设置首 次采集Pod的CPU使用率的延时时间。如果在HorizontalPodAutoscaler中设置了多个指标,系统就会对每个 指标都执行上面的算法,在全部结果中以期望副本数的最大值为最终结 果。如果这些指标中的任意一个都无法转换为期望的副本数(例如无法 获取指标的值),系统就会跳过扩缩容操作。最后,在HPA控制器执行扩缩容操作之前,系统会记录扩缩容建议 信息(Scale Recommendation)。控制器会在操作时间窗口(时间范围 可以配置)中考虑所有的建议信息,并从中选择得分最高的建议。这个 值可通过kube-controller-manager服务的启动参数--horizontal-pod- autoscaler-downscale-stabilization-window进行配置,默认值为5min。这 个配置可以让系统更为平滑地进行缩容操作,从而消除短时间内指标值 快速波动产生的影响
16. HorizontalPodAutoscaler配置详解 。HorizontalPodAutoscaler资源对象处于Kubernetes的API 组“autoscaling”中,目前包括v1和v2两个版本。其中autoscaling/v1仅支 持基于CPU使用率的自动扩缩容,autoscaling/v2则用于支持基于任意指 标的自动扩缩容配置,包括基于资源使用率、Pod指标、其他指标等类 型的指标数据,当前版本为autoscaling/v2beta2
apiVersion: autoscaling/v1 kind: HorizontalPodAutoscalee metadata: name: php-apache spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: php-apache minReplicas: 1 maxReplicas: 10 targetCPUUtilizationPercentage: 50
◎ scaleTargetRef:目标作用对象,可以是Deployment、 ReplicationController或ReplicaSet。◎ targetCPUUtilizationPercentage:期望每个Pod的CPU使用率都 为50%,该使用率基于Pod设置的CPU Request值进行计算,例如该值为 200m,那么系统将维持Pod的实际CPU使用值为100m。◎ minReplicas和maxReplicas:Pod副本数量的最小值和最大值, 系统将在这个范围内进行自动扩缩容操作,并维持每个Pod的CPU使用 率为50%。
17. 基于autoscaling/v2beta2的HorizontalPodAutoscaler配置
apiVersion: autoscaling/v2beta2 kind: HorizontalPodAutoscaler metadata: name: php-apache spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: php-apache minReplicas: 1 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 50
◎ scaleTargetRef:目标作用对象,可以是Deployment、 ReplicationController或ReplicaSet。◎ minReplicas和maxReplicas:Pod副本数量的最小值和最大值, 系统将在这个范围内进行自动扩缩容操作,并维持每个Pod的CPU使用 率为50%。◎ metrics:目标指标值。在metrics中通过参数type定义指标的类 型;通过参数target定义相应的指标目标值,系统将在指标数据达到目标值时(考虑容忍度的区间,见前面算法部分的说明)触发扩缩容操作。可以将metrics中的type(指标类型)设置为以下三种,可以设置一 个或多个组合,如下所述:(1)Resource:基于资源的指标值,可以设置的资源为CPU和内存。(2)Pods:基于Pod的指标,系统将对全部Pod副本的指标值进行平均值计算。(3)Object:基于某种资源对象(如Ingress)的指标或应用系统的任意自定义指标。
metrics: - type: Pods pods: metric: name: packets-per-second target: type: AverageValue averageValue: 1k
其中,设置Pod的指标名为packets-per-second,在目标指标平均值为1000时触发扩缩容操作。
18. 例子:设置指标的名称为requests-per-second,其值来源于 Ingress“main-route”,将目标值(value)设置为2000,即在Ingress的每 秒请求数量达到2000个时触发扩缩容操作:
metrics: - type: Object object: metric: name: requests-per-second describedObject: apiVersion: extensions/v1beta1 kind: Ingress name: main-route target: type: Value value: 2k
例2: 设置指标的名称为http_requests,并且该资源对象具有标签“verb=GET”,在指标平均值达到500时触发扩缩容操作
metrics: - type: Object object: metric: name: 'http_requests' selector: 'verb=GET' target: type: AverageValue value: 500
还可以在同一个HorizontalPodAutoscaler资源对象中定义多个类型的 指标,系统将针对每种类型的指标都计算Pod副本的目标数量,以最大值为准进行扩缩容操作
apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler metadata: name: php-apache namespace: default spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: php-apache minReplicas: 1 maxReplicas: 10 metrics: - type: Resource resource: name: cpu: target: type: AverageUtilization averageUtilization: 50 - type: Pods pods: metric: name: packets-per-second targetAverageValue: 1k - type: Object object: metric: name: request-per-second describedObject: apiVersion: extensions/v1beta1 kind: Ingress name: main-route target: kind: Value value: 10k
从1.10版本开始,Kubernetes引入了对外部系统指标的支持。例 如,用户使用了公有云服务商提供的消息服务或外部负载均衡器,希望基于这些外部服务的性能指标(如消息服务的队列长度、负载均衡器的 QPS)对自己部署在Kubernetes中的服务进行自动扩缩容操作。这时, 就可以在metrics参数部分设置type为External来设置自定义指标,然后就 可以通过API“external.metrics.k8s.io”查询指标数据了。当然,这同样要 求自定义Metrics Server服务已正常工作
- type: External external: metric: name: queue_message_ready selector: "queue=worker_tasks" target: type: AverageValue averageValue: 30
在使用外部服务的指标时,要安装、部署能够对接到Kubernetes HPA模型的监控系统,并且完全了解监控系统采集这些指标的机制,后 续的自动扩缩容操作才能完成
第四章 Service
1. 外部服务Service 在某些环境中,应用系统需要将一个外部数据库作为后端服务进行 连接,或将另一个集群或Namespace中的服务作为服务的后端,这时可 以通过创建一个无Label Selector的Service来实现
apiVersion: v1 kind: v1 metadata: name: mysql namespace: proxy spec: type: NodePort ports: - protocol: TCP port: 3306 targetPort: 3306 nodePort: 32306 --- apiVersion: v1 kind: Endpoints metadata: name: mysql namespace: proxy subsets: - addresses: - ip: xxx.xxx.xxx.xxx ports: - port: 3306
KubeDNS
2. 从Kubernetes 1.4版本开始,SkyDNS组件便被KubeDNS替换,主要 考虑是SkyDNS组件之间通信较多,整体性能不高。KubeDNS由3个容 器组成:kubedns、dnsmasq和sidecar,去掉了SkyDNS中的etcd存储,将 DNS记录直接保存在内存中,以提高查询性能。kubedns容器监控 Kubernetes中Service资源的变化,根据Service的名称和IP地址生成DNS 记录,并将DNS记录保存在内存中;dnsmasq容器从kubedns中获取DNS 记录,提供DNS缓存,为客户端容器应用提供DNS查询服务;sidecar提 供对kubedns和dnsmasq服务的健康检查功能
3. 从Kubernetes 1.11版本开始,Kubernetes集群的DNS服务由CoreDNS 提供。CoreDNS是CNCF基金会的一个项目,是用Go语言实现的高性 能、插件式、易扩展的DNS服务端。CoreDNS解决了KubeDNS的一些问 题,例如dnsmasq的安全漏洞、externalName不能使用stubDomains设 置,等等.CoreDNS支持自定义DNS记录及配置upstream DNS Server,可以统 一管理Kubernetes基于服务的内部DNS和数据中心的物理DNS。CoreDNS没有使用多个容器的架构,只用一个容器便实现了 KubeDNS内3个容器的全部功能
4. CoreDNS的主要功能是通过插件系统实现的。CoreDNS实现了一种 链式插件结构,将DNS的逻辑抽象成了一个个插件,能够灵活组合使 用.CoreDNS的主要功能是通过插件系统实现的。CoreDNS实现了一种 链式插件结构,将DNS的逻辑抽象成了一个个插件,能够灵活组合使 用。
常用的插件如下。◎ loadbalance:提供基于DNS的负载均衡功能。 ◎ loop:检测在DNS解析过程中出现的简单循环问题。 ◎ cache:提供前端缓存功能。 ◎ health:对Endpoint进行健康检查。 ◎ kubernetes:从Kubernetes中读取zone数据。 ◎ etcd:从etcd读取zone数据,可以用于自定义域名记录。 ◎ file:从RFC1035格式文件中读取zone数据。 ◎ hosts:使用/etc/hosts文件或者其他文件读取zone数据,可以用 于自定义域名记录。 ◎ auto:从磁盘中自动加载区域文件。 ◎ reload:定时自动重新加载Corefile配置文件的内容。 ◎ forward:转发域名查询到上游DNS服务器。 ◎ proxy:转发特定的域名查询到多个其他DNS服务器,同时提 供到多个DNS服务器的负载均衡功能。 ◎ prometheus:为Prometheus系统提供采集性能指标数据的 URL。◎ pprof:在URL路径/debug/pprof下提供运行时的性能数据。 ◎ log:对DNS查询进行日志记录。 ◎ errors:对错误信息进行日志记录。 在下面的示例中为域名“cluster.local”设置了一系列插件,包括 errors、health、kubernetes、prometheus、forward、cache、loop、reload 和loadbalance,在进行域名解析时,这些插件将以从上到下的顺序依次 执行
另外,etcd和hosts插件都可以用于用户自定义域名记录。下面是使用etcd插件的配置示例,将以“.com”结尾的域名记录配置 为从etcd中获取,并将域名记录保存在/skydns路径下:
如果用户在etcd中插入一条“10.1.1.1 mycompany.com”DNS记录:
客户端应用就能访问域名“mycompany.com”了:
forward和proxy插件都可以用于配置上游DNS服务器或其他DNS服 务器,当在CoreDNS中查询不到域名时,会到其他DNS服务器上进行查 询。在实际环境中,可以将Kubernetes集群外部的DNS纳入CoreDNS, 进行统一的DNS管理
除了使用集群范围的DNS服务(如CoreDNS),在Pod级别也能设 置DNS的相关策略和配置.在Pod的YAML配置文件中通过spec.dnsPolicy字段设置DNS策略, 例如:
◎ Default:继承Pod所在宿主机的DNS设置。 ◎ ClusterFirst:优先使用Kubernetes环境的DNS服务(如 CoreDNS提供的域名解析服务),将无法解析的域名转发到从宿主机继 承的DNS服务器。 ◎ ClusterFirstWithHostNet:与ClusterFirst相同,对于以 hostNetwork模式运行的Pod,应明确指定使用该策略。 ◎ None:忽略Kubernetes环境的DNS配置,通过spec.dnsConfig自 定义DNS配置。这个选项从Kubernetes 1.9版本开始引入,到Kubernetes 1.10版本升级为Beta版,到Kubernetes 1.14版本升级为稳定版。 自定义DNS配置可以通过spec.dnsConfig字段进行设置,可以设置下 列信息。◎ nameservers:一组DNS服务器的列表,最多可以设置3个。 ◎ searches:一组用于域名搜索的DNS域名后缀,最多可以设置6 个。◎ options:配置其他可选DNS参数,例如ndots、timeout等,以 name或name/value对的形式表示。
以下面的dnsConfig为例:
该Pod被成功创建之后,容器内的DNS配置文件/etc/resolv.conf的内 容将被系统设置为:
表示该Pod完全使用自定义的DNS配置,不再使用Kubernetes环境的 DNS服务
Ingress
使用Ingress进行负载分发时,Ingress Controller基于Ingress规则将客 户端请求直接转发到Service对应的后端Endpoint(Pod)上,这样会跳 过kube-proxy的转发功能,kube-proxy不再起作用。如果Ingress Controller提供的是对外服务,则实际上实现的是边缘路由器的功能
为了实现灵活的负载分发策略,Ingress策略可以按多种方式进行配 置,下面对几种常见的Ingress转发策略进行说明。1.转发到单个后端服务上基于这种设置,客户端到Ingress Controller的访问请求都将被转发 到后端的唯一Service上,在这种情况下Ingress无须定义任何rule。
不同的域名(虚拟主机名)被转发到不同的服务上
不使用域名的转发规则。这种配置用于一个网站不使用域名直接提供服务的场景,此时通过 任意一台运行ingress-controller的Node都能访问到后端的服务。
注意,使用无域名的Ingress转发规则时,将默认禁用非安全 HTTP,强制启用HTTPS。可以在Ingress的定义中设置一个annotation“ingress.kubernetes.io/ssl- redirect=false”来关闭强制启用HTTPS的设置:
这样,到Ingress Controller的访问就可以使用HTTP了
第5章 核心组件运行机制
Kubernetes API Server原理解析
1. 总体来看,Kubernetes API Server的核心功能是提供Kubernetes各类 资源对象(如Pod、RC、Service等)的增、删、改、查及Watch等HTTP Rest接口,成为集群内各个功能模块之间数据交互和通信的中心枢纽, 是整个系统的数据总线和数据中心。除此之外,它还有以下一些功能特 性。(1)是集群管理的API入口。 (2)是资源配额控制的入口。 (3)提供了完备的集群安全机制。
2. API Server的性能是决定Kubernetes集群整体性能的关键因素,因此 Kubernetes的设计者综合运用以下方式来最大程度地保证API Server的性 能。(1)API Server拥有大量高性能的底层代码。在API Server源码中 使用协程(Coroutine)+队列(Queue)这种轻量级的高性能并发代码, 使得单进程的API Server具备了超强的多核处理能力,从而以很快的速 度并发处理大量的请求(2)普通List接口结合异步Watch接口,不但完美解决了 Kubernetes中各种资源对象的高性能同步问题,也极大提升了Kubernetes 集群实时响应各种事件的灵敏度。(3)采用了高性能的etcd数据库而非传统的关系数据库,不仅解决 了数据的可靠性问题,也极大提升了API Server数据访问层的性能。在 常见的公有云环境中,一个3节点的etcd集群在轻负载环境中处理一个请 求的时间可以低于1ms,在重负载环境中可以每秒处理超过30000个请 求。
图5.3以一个完整的Pod调度过程为 例,对API Server的List-Watch机制进行说明
首先,借助etcd提供的Watch API接口,API Server可以监听 (Watch)在etcd上发生的数据操作事件,比如Pod创建事件、更新事 件、删除事件等,在这些事件发生后,etcd会及时通知API Server。图 5.3中API Server与etcd之间的交互箭头表明了这个过程:当一个 ReplicaSet对象被创建并被保存到etcd中后(图中的2.Create RepliatSet箭 头),etcd会立即发送一个对应的Create事件给API Server(图中的 3.Send RepliatSet Create Event箭头),与其类似的6、7、10、11箭头都 是针对Pod的创建、更新事件的。然后,为了让Kubernetes中的其他组件在不访问底层etcd数据库的 情况下,也能及时获取资源对象的变化事件,API Server模仿etcd的 Watch API接口提供了自己的Watch接口,这样一来,这些组件就能近乎 实时地获取它们感兴趣的任意资源对象的相关事件通知了。图5.3中 controller-manager、scheduler、kublet等组件与API Server之间的3个标记 有List-Watch的虚框表明了这个过程。同时,在监听自己感兴趣的资源 的时候,客户端可以增加过滤条件,以List-Watch 3为例,node1节点上 的kubelet进程只对自己节点上的Pod事件感兴趣。最后,Kubernetes List-Watch用于实现数据同步的代码逻辑。客户 端首先调用API Server的List接口获取相关资源对象的全量数据并将其缓 存到内存中,然后启动对应资源对象的Watch协程,在接收到Watch事 件后,再根据事件的类型(比如新增、修改或删除)对内存中的全量资 源对象列表做出相应的同步修改,从实现上来看,这是一种全量结合增 量的、高性能的、近乎实时的数据同步方式。
3. 从图5.6中可以看出,Kubernetes API Server作为集群的核心,负责 集群各功能模块之间的通信。集群内的各个功能模块通过API Server将 信息存入etcd,当需要获取和操作这些数据时,则通过API Server提供的 REST接口(用GET、LIST或WATCH方法)来实现,从而实现各模块之 间的信息交互。
常见的一个交互场景是kubelet进程与API Server的交互。每个Node 上的kubelet每隔一个时间周期,就会调用一次API Server的REST接口报 告自身状态,API Server在接收到这些信息后,会将节点状态信息更新 到etcd中。此外,kubelet也通过API Server的Watch接口监听Pod信息, 如果监听到新的Pod副本被调度绑定到本节点,则执行Pod对应的容器创 建和启动逻辑;如果监听到Pod对象被删除,则删除本节点上相应的Pod 容器;如果监听到修改Pod的信息,kubelet就会相应地修改本节点的Pod 容器。
另一个交互场景是kube-controller-manager进程与API Server的交互。kube-controller-manager中的Node Controller模块通过API Server提供 的Watch接口实时监控Node的信息,并做相应处理
还有一个比较重要的交互场景是kube-scheduler与API Server的交 互。Scheduler通过API Server的Watch接口监听到新建Pod副本的信息 后,会检索所有符合该Pod要求的Node列表,开始执行Pod调度逻辑, 在调度成功后将Pod绑定到目标节点上。
为了缓解集群各模块对API Server的访问压力,各功能模块都采用 缓存机制来缓存数据。各功能模块定时从API Server获取指定的资源对 象信息(通过List-Watch方法),然后将这些信息保存到本地缓存中, 功能模块在某些情况下不直接访问API Server,而是通过访问缓存数据 来间接访问API Server。
Controller Manager原理解析
1. 一般来说,智能系统和自动系统通常会通过一个“操作系统”来不断修正系统的工作状态。在Kubernetes集群中,每个Controller都是这样的 一个“操作系统”,它们通过API Server提供的(List-Watch)接口实时监控集群中特定资源的状态变化,当发生各种故障导致某资源对象的状态发生变化时,Controller会尝试将其状态调整为期望的状态。比如当某个 Node意外宕机时,Node Controller会及时发现此故障并执行自动化修复流程,确保集群始终处于预期的工作状态。Controller Manager是 Kubernetes中各种操作系统的管理者,是集群内部的管理控制中心,也 是Kubernetes自动化功能的核心。
2. Controller Manager内部包含Replication Controller、 Node Controller、ResourceQuota Controller、Namespace Controller、 ServiceAccount Controller、Token Controller、Service Controller及 Endpoint Controller这8种Controller,每种Controller都负责一种特定资源 的控制流程,而Controller Manager正是这些Controller的核心管理者
(1)Controller Manager在启动时如果设置了--cluster-cidr参数,那 么为每个没有设置Spec.PodCIDR的Node都生成一个CIDR地址,并用该 CIDR地址设置节点的Spec.PodCIDR属性,这样做的目的是防止不同节 点的CIDR地址发生冲突。
(2)逐个读取Node信息,多次尝试修改nodeStatusMap中的节点状 态信息,将该节点信息和Node Controller的nodeStatusMap中保存的节点 信息做比较。如果判断出没有收到kubelet发送的节点信息、第1次收到 节点kubelet发送的节点信息,或在该处理过程中节点状态变成非“健康”状态,则在nodeStatusMap中保存该节点的状态信息,并用Node Controller所在节点的系统时间作为探测时间和节点状态变化时间。如果 判断出在指定时间内收到新的节点信息,且节点状态发生变化,则在 nodeStatusMap中保存该节点的状态信息,并用Node Controller所在节点 的系统时间作为探测时间和节点状态变化时间。如果判断出在指定时间 内收到新的节点信息,但节点状态没发生变化,则在nodeStatusMap中 保存该节点的状态信息,并用Node Controller所在节点的系统时间作为 探测时间,将上次节点信息中的节点状态变化时间作为该节点的状态变 化时间。如果判断出在某段时间(gracePeriod)内没有收到节点状态信 息,则设置节点状态为“未知”,并且通过API Server保存节点状态。
(3)逐个读取节点信息,如果节点状态变为非“就绪”状态,则将 节点加入待删除队列,否则将节点从该队列中删除。如果节点状态为 非“就绪”状态,且系统指定了Cloud Provider,则Node Controller调用 Cloud Provider查看节点,若发现节点故障,则删除etcd中的节点信息, 并删除和该节点相关的Pod等资源的信息。
ResourceQuota Controller
目前Kubernetes支持如下三个层次的资源配额管理
(1)容器级别,可以对CPU和Memory进行限制。(2)Pod级别,可以对一个Pod内所有容器的可用资源进行限制。(3)Namespace级别,为Namespace(多租户)级别的资源限制, 包括:◎ Pod数量; ◎ Replication Controller数量; ◎ Service数量; ◎ ResourceQuota数量; ◎ Secret数量; ◎ 可持有的PV数量。
Kubernetes的配额管理是通过Admission Control(准入控制)来控制 的,Admission Control当前提供了两种方式的配额约束,分别是 LimitRanger与ResourceQuota。其中LimitRanger作用于Pod和Container, ResourceQuota则作用于Namespace,限定一个Namespace里的各类资源 的使用总额。
如图5.9所示,如果在Pod定义中同时声明了LimitRanger,则用户通 过API Server请求创建或修改资源时,Admission Control会计算当前配额 的使用情况,如果不符合配额约束,则创建对象失败。对于定义了 ResourceQuota的Namespace,ResourceQuota Controller组件则负责定期 统计和生成该Namespace下的各类对象的资源使用总量,统计结果包括 Pod、Service、RC、Secret和Persistent Volume等对象实例个数,以及该 Namespace下所有Container实例所使用的资源量(目前包括CPU和内 存),然后将这些统计结果写入etcd的resourceQuotaStatusStorage目录 (resourceQuotas/status)下。写入resourceQuotaStatusStorage的内容包含 Resource名称、配额值(ResourceQuota对象中spec.hard域下包含的资源 的值)、当前使用值(ResourceQuota Controller统计出来的值)。随后 这些统计信息被Admission Control使用,以确保相关Namespace下的资 源配额总量不会超过ResourceQuota中的限定值。
Namespace Controller
用户通过API Server可以创建新的Namespace并将其保存在etcd中, Namespace Controller定时通过API Server读取这些Namespace的信息。如 果Namespace被API标识为优雅删除(通过设置删除期限实现,即设置 DeletionTimestamp属性),则将该NameSpace的状态设置成Terminating 并保存到etcd中。同时Namespace Controller删除该Namespace下的 ServiceAccount、RC、Pod、Secret、PersistentVolume、ListRange、 ResourceQuota和Event等资源对象。
在Namespace的状态被设置成Terminating后,由Admission Controller的NamespaceLifecycle插件来阻止为该Namespace创建新的资 源。同时,在Namespace Controller删除该Namespace中的所有资源对象 后,Namespace Controller对该Namespace执行finalize操作,删除Namespace的spec.finalizers域中的信
如果Namespace Controller观察到Namespace设置了删除期限,同时 Namespace的spec.finalizers域值是空的,那么Namespace Controller将通 过API Server删除该Namespace资源。
Service Controller与Endpoints Controller
本节讲解Endpoints Controller,在这之前,让我们先看看Service、 Endpoints与Pod的关系。如图5.10所示,Endpoints表示一个Service对应 的所有Pod副本的访问地址,Endpoints Controller就是负责生成和维护所 有Endpoints对象的控制器。
它负责监听Service和对应的Pod副本的变化,如果监测到Service被 删除,则删除和该Service同名的Endpoints对象。如果监测到新的Service 被创建或者修改,则根据该Service信息获得相关的Pod列表,然后创建 或者更新Service对应的Endpoints对象。如果监测到Pod的事件,则更新 它所对应的Service的Endpoints对象(增加、删除或者修改对应的 Endpoint条目)。
那么,Endpoints对象是在哪里被使用的呢?答案是每个Node上的 kube-proxy进程,kube-proxy进程获取每个Service的Endpoints,实现了 Service的负载均衡功能。
Kubernetes Scheduler当前提供的默认调度流程分为以下两步。
(1)预选调度过程,即遍历所有目标Node,筛选出符合要求的候 选节点。为此,Kubernetes内置了多种预选策略(xxx Predicates)供用 户选择
(2)确定最优节点,在第1步的基础上,采用优选策略(xxx Priority)计算出每个候选节点的积分,积分最高者胜出。
Kubernetes Scheduler的调度流程是通过插件方式加载的“调度算法 提供者”(AlgorithmProvider)具体实现的。一个AlgorithmProvider其实 就是包括了一组预选策略与一组优先选择策略的结构体
Scheduler中可用的预选策略包含:NoDiskConflict、 PodFitsResources、PodSelectorMatches、PodFitsHost、 CheckNodeLabelPresence、CheckServiceAffinity和PodFitsPorts策略等。 其默认的AlgorithmProvider加载的预选策略Predicates包括: PodFitsPorts(PodFitsPorts)、PodFitsResources(PodFitsResources)、 NoDiskConflict(NoDiskConflict)、 MatchNodeSelector(PodSelectorMatches)和 HostName(PodFitsHost),即每个节点只有通过前面提及的5个默认预 选策略后,才能初步被选中,进入下一个流程
1)NoDiskConflict 判断备选Pod的gcePersistentDisk或AWSElasticBlockStore和备选的节 点中已存在的Pod是否存在冲突。检测过程如下。
(1)首先,读取备选Pod的所有Volume的信息(即 pod.Spec.Volumes),对每个Volume执行以下步骤进行冲突检测。
(2)如果该Volume是gcePersistentDisk,则将Volume和备选节点上 的所有Pod的每个Volume都进行比较,如果发现相同的 gcePersistentDisk,则返回false,表明存在磁盘冲突,检查结束,反馈给 调度器该备选节点不适合作为备选Pod;如果该Volume是 AWSElasticBlockStore,则将Volume和备选节点上的所有Pod的每个 Volume都进行比较,如果发现相同的AWSElasticBlockStore,则返回 false,表明存在磁盘冲突,检查结束,反馈给调度器该备选节点不适合 备选Pod。
(3)如果检查完备选Pod的所有Volume均未发现冲突,则返回true,表明不存在磁盘冲突,反馈给调度器该备选节点适合备选Pod。
2)PodFitsResources 判断备选节点的资源是否满足备选Pod的需求,检测过程如下。
(1)计算备选Pod和节点中已存在Pod的所有容器的需求资源(内 存和CPU)的总和。
(2)获得备选节点的状态信息,其中包含节点的资源信息。
(3)如果在备选Pod和节点中已存在Pod的所有容器的需求资源 (内存和CPU)的总和,超出了备选节点拥有的资源,则返回false,表 明备选节点不适合备选Pod,否则返回true,表明备选节点适合备选 Pod。
3)PodSelectorMatches 判断备选节点是否包含备选Pod的标签选择器指定的标签。
(1)如果Pod没有指定spec.nodeSelector标签选择器,则返回true。
(2)否则,获得备选节点的标签信息,判断节点是否包含备选Pod 的标签选择器(spec.nodeSelector)所指定的标签,如果包含,则返回 true,否则返回false。
4)PodFitsHost 判断备选Pod的spec.nodeName域所指定的节点名称和备选节点的名 称是否一致,如果一致,则返回true,否则返回false。
5)CheckNodeLabelPresence 如果用户在配置文件中指定了该策略,则Scheduler会通过 RegisterCustomFitPredicate方法注册该策略。该策略用于判断策略列出 的标签在备选节点中存在时,是否选择该备选节点。
(1)读取备选节点的标签列表信息
(2)如果策略配置的标签列表存在于备选节点的标签列表中,且 策略配置的presence值为false,则返回false,否则返回true;如果策略配 置的标签列表不存在于备选节点的标签列表中,且策略配置的presence 值为true,则返回false,否则返回true。
6)CheckServiceAffinity 如果用户在配置文件中指定了该策略,则Scheduler会通过 RegisterCustomFitPredicate方法注册该策略。该策略用于判断备选节点 是否包含策略指定的标签,或包含和备选Pod在相同Service和Namespace 下的Pod所在节点的标签列表。如果存在,则返回true,否则返回false。
7)PodFitsPorts 判断备选Pod所用的端口列表中的端口是否在备选节点中已被占 用,如果被占用,则返回false,否则返回true。
1)LeastRequestedPriority 该优选策略用于从备选节点列表中选出资源消耗最小的节点。
(1)计算出在所有备选节点上运行的Pod和备选Pod的CPU占用量 totalMilliCPU。
(2)计算出在所有备选节点上运行的Pod和备选Pod的内存占用量 totalMemory。
(3)计算每个节点的得分,计算规则大致如下,其中, NodeCpuCapacity为节点CPU计算能力,NodeMemoryCapacity为节点内存大小:
2)CalculateNodeLabelPriority 如果用户在配置文件中指定了该策略,则scheduler会通过 RegisterCustomPriorityFunction方法注册该策略。该策略用于判断策略列 出的标签在备选节点中存在时,是否选择该备选节点。如果备选节点的 标签在优选策略的标签列表中且优选策略的presence值为true,或者备选 节点的标签不在优选策略的标签列表中且优选策略的presence值为 false,则备选节点score=10,否则备选节点score=0。
3)BalancedResourceAllocation 该优选策略用于从备选节点列表中选出各项资源使用率最均衡的节 点。
(1)计算出在所有备选节点上运行的Pod和备选Pod的CPU占用量 totalMilliCPU。
(2)计算出在所有备选节点上运行的Pod和备选Pod的内存占用量 totalMemory。
(3)计算每个节点的得分,计算规则大致如下,其中, NodeCpuCapacity为节点的CPU计算能力,NodeMemoryCapacity为节点 的内存大小:
在Kubernetes集群中,在每个Node(又称Minion)上都会启动一个 kubelet服务进程。该进程用于处理Master下发到本节点的任务,管理 Pod及Pod中的容器。每个kubelet进程都会在API Server上注册节点自身 的信息,定期向Master汇报节点资源的使用情况,并通过cAdvisor监控 容器和节点资源。
kubelet通过以下几种方式获取自身Node上要运行的Pod清单
(1)文件:kubelet启动参数“--config”指定的配置文件目录下的文 件(默认目录为“/etc/ kubernetes/manifests/”)。通过--file-check- frequency设置检查该文件目录的时间间隔,默认为20s。
(2)HTTP端点(URL):通过“--manifest-url”参数设置。通过-- http-check-frequency设置检查该HTTP端点数据的时间间隔,默认为 20s。
(3)API Server:kubelet通过API Server监听etcd目录,同步Pod列 表
所有以非API Server方式创建的Pod都叫作Static Pod。kubelet将 Static Pod的状态汇报给API Server,API Server为该Static Pod创建一个 Mirror Pod和其相匹配。Mirror Pod的状态将真实反映Static Pod的状态。 当Static Pod被删除时,与之相对应的Mirror Pod也会被删除。
kubelet监听etcd,所有针对Pod的操作都会被kubelet监听。如果发现 有新的绑定到本节点的Pod,则按照Pod清单的要求创建该Pod。
如果发现本地的Pod被修改,则kubelet会做出相应的修改,比如在 删除Pod中的某个容器时,会通过Docker Client删除该容器。
如果发现删除本节点的Pod,则删除相应的Pod,并通过Docker Client删除Pod中的容器。
kubelet读取监听到的信息,如果是创建和修改Pod任务,则做如下 处理。
(1)为该Pod创建一个数据目录。
(2)从API Server读取该Pod清单。
(3)为该Pod挂载外部卷(External Volume)。
(4)下载Pod用到的Secret。
(5)检查已经运行在节点上的Pod,如果该Pod没有容器或Pause容 器(“kubernetes/pause”镜像创建的容器)没有启动,则先停止Pod里所 有容器的进程。如果在Pod中有需要删除的容器,则删除这些容器。
(6)用“kubernetes/pause”镜像为每个Pod都创建一个容器。该Pause 容器用于接管Pod中所有其他容器的网络。每创建一个新的Pod,kubelet 都会先创建一个Pause容器,然后创建其他容器。“kubernetes/pause”镜像 大概有200KB,是个非常小的容器镜像。
(7)为Pod中的每个容器做如下处理。
◎ 为容器计算一个Hash值,然后用容器的名称去查询对应Docker 容器的Hash值。若查找到容器,且二者的Hash值不同,则停止Docker中 容器的进程,并停止与之关联的Pause容器的进程;若二者相同,则不做任何处理。
◎ 如果容器被终止了,且容器没有指定的restartPolicy(重启策 略),则不做任何处理。
◎ 调用Docker Client下载容器镜像,调用Docker Client运行容器
容器健康检查
Pod通过两类探针来检查容器的健康状态。一类是LivenessProbe探 针,用于判断容器是否健康并反馈给kubelet。如果LivenessProbe探针探 测到容器不健康,则kubelet将删除该容器,并根据容器的重启策略做相 应的处理。如果一个容器不包含LivenessProbe探针,那么kubelet认为该 容器的LivenessProbe探针返回的值永远是Success;另一类是 ReadinessProbe探针,用于判断容器是否启动完成,且准备接收请求。 如果ReadinessProbe探针检测到容器启动失败,则Pod的状态将被修改, Endpoint Controller将从Service的Endpoint中删除包含该容器所在Pod的IP 地址的Endpoint条目
Kubernetes从1.2版本开始,将iptables作为kube- proxy的默认模式。iptables模式下的kube-proxy不再起到Proxy的作用, 其核心功能:通过API Server的Watch接口实时跟踪Service与Endpoint的 变更信息,并更新对应的iptables规则,Client的请求流量则通过iptables 的NAT机制“直接路由”到目标Pod。
根据Kubernetes的网络模型,一个Node上的Pod与其他Node上的Pod 应该能够直接建立双向的TCP/IP通信通道,所以如果直接修改iptables规 则,则也可以实现kube-proxy的功能,只不过后者更加高端,因为是全 自动模式的。与第1代的userspace模式相比,iptables模式完全工作在内 核态,不用再经过用户态的kube-proxy中转,因而性能更强
iptables与IPVS虽然都是基于Netfilter实现的,但因为定位不同,二 者有着本质的差别:iptables是为防火墙而设计的;IPVS则专门用于高 性能负载均衡,并使用更高效的数据结构(Hash表),允许几乎无限的 规模扩张,因此被kube-proxy采纳为第三代模式
与iptables相比,IPVS拥有以下明显优势:
◎ 为大型集群提供了更好的可扩展性和性能
◎ 支持比iptables更复杂的复制均衡算法(最小负载、最少连接、 加权等);
◎ 支持服务器健康检查和连接重试等功能;
◎ 可以动态修改ipset的集合,即使iptables的规则正在使用这个集 合。
由于IPVS无法提供包过滤、airpin-masquerade tricks(地址伪装)、 SNAT等功能,因此在某些场景(如NodePort的实现)下还要与iptables 搭配使用。在IPVS模式下,kube-proxy又做了重要的升级,即使用 iptables的扩展ipset,而不是直接调用iptables来生成规则链
iptables规则链是一个线性的数据结构,ipset则引入了带索引的数据 结构,因此当规则很多时,也可以很高效地查找和匹配我们可以将 ipset简单理解为一个IP(段)的集合,这个集合的内容可以是IP地址、 IP网段、端口等,iptables可以直接添加规则对这个“可变的集合”进行操 作,这样做的好处在于可以大大减少iptables规则的数量,从而减少性能 损耗
假设要禁止上万个IP访问我们的服务器,则用iptables的话,就需要 一条一条地添加规则,会在iptables中生成大量的规则;但是用ipset的 话,只需将相关的IP地址(网段)加入ipset集合中即可,这样只需设置 少量的iptables规则即可实现目标。
kube-proxy针对Service和Pod创建的一些主要的iptables规则如下
◎ KUBE-CLUSTER-IP:在masquerade-all=true或clusterCIDR指定 的情况下对Service Cluster IP地址进行伪装,以解决数据包欺骗问题。
◎ KUBE-EXTERNAL-IP:将数据包伪装成Service的外部IP地 址
◎ KUBE-LOAD-BALANCER、KUBE-LOAD-BALANCER-LOCAL:伪装Load Balancer 类型的Service流量。
◎ KUBE-NODE-PORT-TCP、KUBE-NODE-PORT-LOCAL- TCP、KUBE-NODE-PORTUDP、KUBE-NODE-PORT-LOCAL-UDP: 伪装NodePort类型的Service流量。
API Server认证管理
我们知道,Kubernetes集群中所有资源的访问和变更都是通过 Kubernetes API Server的REST API来实现的,所以集群安全的关键点就 在于如何识别并认证客户端身份(Authentication),以及随后访问权限 的授权(Authorization)这两个关键问题
Kubernetes集群提供了3种级别的客户端身份认证方式
◎ 最严格的HTTPS证书认证:基于CA根证书签名的双向数字证 书认证方式。
◎ HTTP Token认证:通过一个Token来识别合法用户
◎ HTTP Base认证:通过用户名+密码的方式认证。
CA认证大概包含下面几个步骤
(1)HTTPS通信双方的服务器端向CA机构申请证书,CA机构是 可信的第三方机构,它可以是一个公认的权威企业,也可以是企业自 身。企业内部系统一般都用企业自身的认证系统。CA机构下发根证 书、服务端证书及私钥给申请者。
(2)HTTPS通信双方的客户端向CA机构申请证书,CA机构下发 根证书、客户端证书及私钥给申请者。
(3)客户端向服务器端发起请求,服务端下发服务端证书给客户 端。客户端接收到证书后,通过私钥解密证书,并利用服务器端证书中 的公钥认证证书信息比较证书里的消息,例如,比较域名和公钥与服务 器刚刚发送的相关消息是否一致,如果一致,则客户端认可这个服务器 的合法身份。
(4)客户端发送客户端证书给服务器端,服务端在接收到证书 后,通过私钥解密证书,获得客户端证书公钥,并用该公钥认证证书信 息,确认客户端是否合法。
(5)客户端通过随机密钥加密信息,并发送加密后的信息给服务 端。在服务器端和客户端协商好加密方案后,客户端会产生一个随机的 密钥,客户端通过协商好的加密方案加密该随机密钥,并发送该随机密 钥到服务器端。服务器端接收这个密钥后,双方通信的所有内容都通过 该随机密钥加密。
上述是双向认证SSL协议的具体通信过程,这种情况要求服务器和 用户双方都有证书。单向认证SSL协议则不需要客户端拥有CA证书,对 于上面的步骤,只需将服务器端验证客户证书的过程去掉,之后协商对称密码方案和对称通话密钥时,服务器发送给客户的密码没被加密即 可。
HTTP Token的认证是用一个很长的特殊编码方式的并且难以被模 仿的字符串—Token来表明客户身份的一种方式。在通常情况下,Token 是一个很复杂的字符串,比如我们用私钥签名一个字符串后的数据就可 以被当作一个Token。此外,每个Token对应一个用户名,存储在API Server能访问的一个文件中。当客户端发起API调用请求时,需要在 HTTP Header里放入Token,这样一来,API Server就能识别合法用户和 非法用户了
HTTP Base认证:HTTP是无状态的,浏览器和Web服务器之间可以通过 Cookie来进行身份识别。桌面应用程序(比如新浪桌面客户端、 SkyDrive客户端、命令行程序)一般不会使用Cookie,那么它们与Web 服务器之间是如何进行身份识别的呢?这就用到了HTTP Base认证,这种认证方式是把“用户名+冒号+密码”用BASE64算法进行编码后的字符 串放在HTTP Request中的Header Authorization域里发送给服务端,服务 端在收到后进行解码,获取用户名及密码,然后进行用户身份鉴权
当客户端发起API Server调用时,API Server内部要先进行用户认 证,然后执行用户授权流程,即通过授权策略来决定一个API调用是否 合法。对合法用户进行授权并且随后在用户访问时进行鉴权,是权限与 安全系统的重要一环。简单地说,授权就是授予不同的用户不同的访问 权限。API Server目前支持以下几种授权策略(通过API Server的启动参 数“--authorization-mode”设置)
◎ AlwaysDeny:表示拒绝所有请求,一般用于测试。
◎ AlwaysAllow:允许接收所有请求,如果集群不需要授权流 程,则可以采用该策略,这也是Kubernetes的默认配置。
◎ ABAC(Attribute-Based Access Control):基于属性的访问控 制,表示使用用户配置的授权规则对用户请求进行匹配和控制。
◎ Webhook:通过调用外部REST服务对用户进行授权。
◎ RBAC:Role-Based Access Control,基于角色的访问控制。
◎ Node:是一种专用模式,用于对kubelet发出的请求进行访问控 制
RBAC(Role-Based Access Control,基于角色的访问控制)在 Kubernetes的1.5版本中引入,在1.6版本时升级为Beta版本,在1.8版本时 升级为GA
◎ 对集群中的资源和非资源权限均有完整的覆盖。
◎ 整个RBAC完全由几个API对象完成,同其他API对象一样,可 以用kubectl或API进行操作。
◎ 可以在运行时进行调整,无须重新启动API Server。
1.RBAC的API资源对象说明
RBAC引入了4个新的顶级资源对象:Role、ClusterRole、 RoleBinding和ClusterRoleBinding。同其他API资源对象一样,用户可以 使用kubectl或者API调用等方式操作这些资源对象
1)角色(Role)一个角色就是一组权限的集合,这里的权限都是许可形式的,不存 在拒绝的规则。在一个命名空间中,可以用角色来定义一个角色,如果 是集群级别的,就需要使用ClusterRole了
rules中的参数说明如下。
◎ apiGroups:支持的API组列表,例如“apiVersion: batch/v1”“apiVersion: extensions:v1beta1”“apiVersion: apps/v1beta1”等
◎ resources:支持的资源对象列表,例如pods、deployments、 jobs等
◎ verbs:对资源对象的操作方法列表,例如get、watch、list、delete、replace、patch等
2)集群角色(ClusterRole)
集群角色除了具有和角色一致的命名空间内资源的管理能力,因其 集群级别的范围,还可以用于以下特殊元素的授权
◎ 集群范围的资源,例如Node。
◎ 非资源型的路径,例如“/healthz”。
◎ 包含全部命名空间的资源,例如pods(用于kubectl get pods -- all-namespaces这样的操作授权)。
3)角色绑定(RoleBinding)和集群角色绑定 (ClusterRoleBinding)
角色绑定或集群角色绑定用来把一个角色绑定到一个目标上,绑定 目标可以是User(用户)、Group(组)或者Service Account。使用 RoleBinding为某个命名空间授权,使用ClusterRoleBinding为集群范围内 授权
RoleBinding可以引用Role进行授权。下面的例子中的RoleBinding将 在default命名空间中把pod-reader角色授予用户jane,这一操作可以让
jane读取default命名空间中的Pod:
例如,在下面的例子中,虽然secret-reader是一个集群角色,但是因 为使用了RoleBinding,所以dave只能读取development命名空间中的 secret
在这个例子中,Pod是一个命名空间内的资源,log就是一个下级资 源。要在一个RBAC角色中体现,就需要用斜线“/”来分隔资源和下级资 源。若想授权让某个主体同时能够读取Pod和Pod log,则可以配置 resources为一个数组
资源还可以通过名称(ResourceName)进行引用。在指定 ResourceName后,使用get、delete、update和patch动词的请求,就会被 限制在这个资源实例范围内。例如,下面的声明让一个主体只能对一个 ConFigmap进行get和update操作
3.常用的角色示例.注意,下面的例子只展示了rules部分的内容
(1)允许读取核心API组中的Pod资源:
(2)允许读写extensions和apps两个API组中的deployment资源:
(3)允许读取pods及读写jobs
(4)允许读取一个名为my-config的ConfigMap(必须绑定到一个 RoleBinding来限制到一个Namespace下的ConfigMap)
(5)读取核心组的Node资源(Node属于集群级的资源,所以必须 存在于ClusterRole中,并使用ClusterRoleBinding进行绑定):
4.常见的角色绑定示例.注意,在下面的例子中只包含subjects部分的内容。
(1)用户名alice@example.com:
(2)组名frontend-admins:
(3)kube-system命名空间中的默认Service Account:
(4)qa命名空间中的所有Service Account:
(5)所有Service Account:
(6)所有认证用户(Kubernetes 1.5以上版本):
(7)所有未认证用户(Kubernetes 1.5以上版本):
(8)全部用户(Kubernetes 1.5以上版本):
对系统角色的说明如表6.1所示
有些默认角色不是以“system:”为前缀的,这部分角色是针对用户的。其中包含超级用户角色(cluster- admin),有的用于集群一级的角色(cluster-status),还有针对 Namespace的角色(admin、edit、view)
RBAC API拒绝用户通过编辑角色或者角色绑定进行提升权限。这 一限制是在API层面做出的,因此即使RBAC没有启用也仍然有效
用户要对角色进行创建或更新操作,需要满足下列至少一个条件:
(1)拥有一个角色的所有权限,且与该角色的生效范围一致(如 果是集群角色,则是集群范围;如果是普通角色,则可能是同一个命名 空间或者整个集群)
(2)为用户显式授予针对该角色或集群角色的提权(escalate)操 作的权限(要求Kubernetes 1.12及以上版本)
对Service Account的授权管理
默认的RBAC策略为控制平台组件、节点和控制器授予有限范围的 权限,但是除kube-system外的Service Account是没有任何权限的(除了 所有认证用户都具有的discovery权限)。
这就要求用户为Service Account赋予所需的权限。细粒度的角色分 配能够提高安全性,但也会提高管理成本。粗放的授权方式可能会给 Service Account多余的权限,但更易于管理
突破了之前所说的认证和鉴权两道关卡之后,客户端的调用请求就 能够得到API Server的真正响应了吗?答案是:不能!这个请求还需要 通过Admission Control(准入控制)所控制的一个准入控制链的层层考 验,才能获得成功的响应。Kubernetes官方标准的“关卡”有30多个,还 允许用户自定义扩展
Admission Control配备了一个准入控制器的插件列表,发送给API Server的任何请求都需要通过列表中每个准入控制器的检查,检查不通 过,则API Server拒绝此调用请求。此外,准入控制器插件能够修改请 求参数以完成一些自动化任务,比如ServiceAccount这个控制器插件。 当前可配置的准入控制器插件如下
◎ AlwaysAdmit:已弃用,允许所有请求
◎ AlwaysPullImages:在启动容器之前总是尝试重新下载镜像。 这对于多租户共享一个集群的场景非常有用,系统在启动容器之前可以 保证总是使用租户的密钥去下载镜像。如果不设置这个控制器,则在 Node上下载的镜像的安全性将被削弱,只要知道该镜像的名称,任何人 便都可以使用它们了
◎ AlwaysDeny:已弃用,禁止所有请求,用于测试。
◎ DefaultStorageClass:会关注PersistentVolumeClaim资源对象的 创建,如果其中没有包含任何针对特定Storage class的请求,则为其指 派指定的Storage class。在这种情况下,用户无须在PVC中设置任何特定 的Storage class就能完成PVC的创建了。如果没有设置默认的Storage class,该控制器就不会进行任何操作;如果设置了超过一个的默认 Storage class,该控制器就会拒绝所有PVC对象的创建申请,并返回错误信息。管理员必须检查StorageClass对象的配置,确保只有一个默认 值。该控制器仅关注PVC的创建过程,对更新过程无效
◎ DefaultTolerationSeconds:针对没有设置容忍 node.kubernetes.io/not-ready:NoExecute或者 node.alpha.kubernetes.io/unreachable:NoExecute的Pod,设置5min的默认 容忍时间。
◎ DenyEscalatingExec:拦截所有exec和attach到具有特权的Pod上 的请求。如果你的集群支持运行有escalated privilege权限的容器,又希 望限制用户在这些容器内执行命令,那么强烈推荐使用它。
◎ EventReateLimit:Alpha版本,用于应对事件密集情况下对API Server造成的洪水攻击。
◎ Initializers:Alpha。用于为动态准入控制提供支持,通过修改 待创建资源的元数据来完成对该资源的修改。
◎ LimitPodHardAntiAffinityTopology:该插件启用了Pod的反亲和性调度策略设置,在设置亲和性策略参数requiredDuringSchedulingRequiredDuringExecution时要求将topologyKey 的值设置为“kubernetes.io/hostname”,否则Pod会被拒绝创建。
◎ LimitRanger:这个插件会监控进入的请求,确保请求的内容符 合在Namespace中定义的LimitRange对象里的资源限制。如果要在 Kubernetes集群中使用LimitRange对象,则必须启用该插件才能实施这 一限制。LimitRanger还能用于为没有设置资源请求的Pod自动设置默认 的资源请求,该插件会为default命名空间中的所有Pod设置0.1CPU的资 源请求
◎ MutatingAdmissionWebhook:Beta。这一插件会变更符合要求 的请求的内容,Webhook以串行的方式顺序执行
◎ NamespaceAutoProvision:这一插件会检测所有进入的具备命 名空间的资源请求,如果其中引用的命名空间不存在,就会自动创建命 名空间。
◎ NamespaceExists:这一插件会检测所有进入的具备命名空间的 资源请求,如果其中引用的命名空间不存在,就会拒绝这一创建过程。
◎ NamespaceLifecycle:如果尝试在一个不存在的Namespace中创 建资源对象,则该创建请求将被拒绝。当删除一个Namespace时,系统 将会删除该Namespace中的所有对象,包括Pod、Service等,并阻止删除 default、kube-system和kube-public这三个命名空间。
◎ NodeRestriction:该插件会限制kubelet对Node和Pod的修改行 为。为了实现这一限制,kubelet必须使用system:nodes组中用户名为 system:node:<nodeName>的Token来运行。符合条件的kubelet只能修改 自己的Node对象,也只能修改分配到各自Node上的Pod对象。在 Kubernetes 1.11以后的版本中,kubelet无法修改或者更新自身Node的 taint属性。在Kubernetes 1.13以后,这一插件还会阻止kubelet删除自己的Node资源,并限制对有kubernetes.io/或k8s.io/前缀的标签的修改。
◎ ResourceQuota:用于资源配额管理目的,作用于Namespace。 该插件拦截所有请求,以确保在Namespace上的资源配额使用不会超 标。推荐在Admission Control参数列表中将这个插件排最后一个,以免 可能被其他插件拒绝的Pod被过早分配资源
◎ ServiceAccount:这个插件将ServiceAccount实现了自动化,如 果想使用ServiceAccount对象,那么强烈推荐使用它
在API Server上设置参数即可定制我们需要的准入控制链,如果启 用多种准入控制选项,则建议设置:在Kubernetes 1.9及之前的版本中使用的参数是--admission-control,并且其中的内容是顺序相关的;在 Kubernetes 1.10及之后的版本中,该参数为--enable-admission-plugins, 并且与顺序无关。
对Kubernetes 1.10及以上版本设置如下
对Kubernetes 1.9及以下版本设置如下:
◎ 这个Token的内容来自Pod里指定路径下的一个文件 (/run/secrets/kubernetes.io/serviceaccount/token),这种Token是动态生 成的,确切地说,是由Kubernetes Controller进程用API Server的私钥(-- service-account-private-key-file指定的私钥)签名生成的一个JWT Secret。◎ 这个Token的内容来自Pod里指定路径下的一个文件 (/run/secrets/kubernetes.io/serviceaccount/token),这种Token是动态生 成的,确切地说,是由Kubernetes Controller进程用API Server的私钥(-- service-account-private-key-file指定的私钥)签名生成的一个JWT Secret。
◎ 在官方提供的客户端REST框架代码里,通过HTTPS方式与API Server建立连接后,会用Pod里指定路径下的一个CA证书 (/run/secrets/kubernetes.io/serviceaccount/ca.crt)验证API Server发来的 证书,验证是否为CA证书签名的合法证书。
◎ API Server在收到这个Token以后,采用自己的私钥(实际上是 使用service-accountkey-file参数指定的私钥,如果没有设置此参数,则 默认采用tls-private-key-file指定的参数,即自己的私钥)对Token进行合 法性验证。
我们接下来继续分析在上面的认证过程中所涉及 的Pod中的以下三个文件。
◎ /run/secrets/kubernetes.io/serviceaccount/token。
◎ /run/secrets/kubernetes.io/serviceaccount/ca.crt。
◎ /run/secrets/kubernetes.io/serviceaccount/namespace(客户端采用 这里指定的namespace作为参数调用Kubernetes API)。
这三个文件由于参与到Pod进程与API Server认证的过程中,起到了 类似secret(私密凭据)的作用,所以它们被称为Kubernetes Secret对 象。Secret从属于Service Account资源对象,属于Service Account的一部 分,在一个Service Account对象里面可以包括多个不同的Secret对象,分 别用于不同目的的认证活动
查看系统中的Service Account对象,看到有一个名为default 的Service Account对象,包含一个名为default-token-77oyg的Secret,这 个Secret同时是Mountable secrets,表明它是需要被挂载到Pod上的
default-token-77oyg包含三个数据 项,分别是token、ca.crt、namespace。联想到Mountable secrets的标记, 以及之前看到的Pod中的三个文件的文件名,我们恍然大悟:在每个 Namespace下都有一个名为default的默认Service Account对象,在这个 Service Account里面有一个名为Tokens的可以当作Volume被挂载到Pod 里的Secret,当Pod启动时,这个Secret会自动被挂载到Pod的指定目录下,用来协助完成Pod中的进程访问API Server时的身份鉴权
其中:
(1)名为Tokens的Secret用于访问API Server的Secret,也被称为 Service Account Secret。
(2)名为imagePullSecrets的Secret用于下载容器镜像时的认证过 程,通常镜像库运行在Insecure模式下,所以这个Secret为空。
(3)用户自定义的其他Secret,用于用户的进程。
如果一个Pod在定义时没有指定spec.serviceAccountName属性,则系 统会自动为其赋值为default,即大家都使用同一个Namespace下的默认 Service Account。如果某个Pod需要使用非default的Service Account,则 需要在定义时指定:
Kubernetes之所以要创建两套独立的账号系统,原因如下。
◎ User账号是给人用的,Service Account是给Pod里的进程使用 的,面向的对象不同。
◎ User账号是全局性的,Service Account则属于某个具体的 Namespace。
◎ 通常来说,User账号是与后端的用户数据库同步的,创建一个 新用户通常要走一套复杂的业务流程才能实现,Service Account的创建 则需要极轻量级的实现方式,集群管理员可以很容易地为某些特定任务 创建一个Service Account。
◎ 对于这两种不同的账号,其审计要求通常不同。
◎ 对于一个复杂的系统来说,多个组件通常拥有各种账号的配置 信息,Service Account是Namespace隔离的,可以针对组件进行一对一 的定义,同时具备很好的“便携性”。
接下来深入分析Service Account与Secret相关的一些运行机制
我们知道Controller manager创建了 ServiceAccount Controller与Token Controller这两个安全相关的控制器。其中ServiceAccount Controller一直监听Service Account和Namespace的事 件,如果在一个 Namespace中没有default Service Account,那么 ServiceAccount Controller会为该Namespace创建一个默认(default)的Service Account,这就是我们之前看到在每个Namespace下都有一个名 为default的Service Account的原因。
Secret的主要作用是保管私密数据,比如密 码、OAuth Tokens、SSH Keys等信息。将这些私密信息放在Secret对象 中比直接放在Pod或Docker Image中更安全,也更便于使用和分发
在上面的例子中,data域的各子域的值必须为BASE64编码值,其 中password域和username域BASE64编码前的值分别为value-1和value-2
一旦Secret被创建,就可以通过下面三种方式使用它
(1)在创建Pod时,通过为Pod指定Service Account来自动使用该 Secret
(2)通过挂载该Secret到Pod来使用它。
(3)在Docker镜像下载时使用,通过指定Pod的 spc.ImagePullSecrets来引用它。
第1种使用方式主要用在API Server鉴权方面.下面的例子展示了第2种使用方式:将一个Secret通过挂载的方式添加到Pod的 Volume中:
每个单独的Secret大小不能超过1MB,Kubernetes不鼓励创建大的 Secret,因为如果使用大的Secret,则将大量占用API Server和kubelet的 内存。当然,创建许多小的Secret也能耗尽API Server和kubelet的内存
我们可以通过Secret保管其他 系统的敏感信息(比如数据库的用户名和密码),并以Mount的方式将 Secret挂载到Container中,然后通过访问目录中文件的方式获取该敏感 信息。当Pod被API Server创建时,API Server不会校验该Pod引用的 Secret是否存在。一旦这个Pod被调度,则kubelet将试着获取Secret的 值。如果Secret不存在或暂时无法连接到API Server,则kubelet按一定的 时间间隔定期重试获取该Secret,并发送一个Event来解释Pod没有启动 的原因一旦Secret被Pod获取,则kubelet将创建并挂载包含Secret的 Volume。只有所有Volume都挂载成功,Pod中的Container才会被启动。 在kubelet启动Pod中的Container后,Container中和Secret相关的Volume将 不会被改变,即使Secret本身被修改。为了使用更新后的Secret,必须删 除旧Pod,并重新创建一个新Pod
若想启用PodSecurityPolicy机制,则需要在kube-apiserver服务的启 动参数--enable-admission-plugins中进行设置
在开启PodSecurityPolicy准入控制器后,Kubernetes默认不允许创建 任何Pod,需要创建PodSecurityPolicy策略和相应的RBAC授权策略 (Authorizing Policies),Pod才能创建成功
创建一个PodSecurityPolicy,配置文件psp-non- privileged.yaml的内容如下
在PodSecurityPolicy对象中可以设置下列字段来控制Pod运行时的各 种安全策略
1.特权模式相关配置.privileged:是否允许Pod以特权模式运行。
2.宿主机资源相关配置
(1)hostPID:是否允许Pod共享宿主机的进程空间。
(2)hostIPC:是否允许Pod共享宿主机的IPC命名空间。
(3)hostNetwork:是否允许Pod使用宿主机网络的命名空间
(4)hostPorts:是否允许Pod使用宿主机的端口号,可以通过 hostPortRange字段设置允许使用的端口号范围,以[min, max]设置最小 端口号和最大端口号。
(5)Volumes:允许Pod使用的存储卷Volume类型,设置为“*”表 示允许使用任意Volume类型,建议至少允许Pod使用下列Volume类型。
◎ configMap ◎ downwardAPI ◎ emptyDir ◎ persistentVolumeClaim ◎ secret ◎ projected
(6)AllowedHostPaths:允许Pod使用宿主机的hostPath路径名称, 可以通过pathPrefix字段设置路径的前缀,并可以设置是否为只读属性
结果为允许Pod访问宿主机上以“/foo”为前缀的路径,包 括“/foo”“/foo/”“/foo/bar”等,但不能访问“/fool”“/etc/foo”等路径,也不允 许通过“/foo/../”表达式访问/foo的上层目录
(7)FSGroup:设置允许访问某些Volume的Group ID范围,可以 将规则(rule字段)设置为MustRunAs、MayRunAs或RunAsAny。
◎ MustRunAs:需要设置Group ID的范围,例如1~65535,要求 Pod的securityContext.fsGroup设置的值必须属于该Group ID的范围
◎ MayRunAs:需要设置Group ID的范围,例如1~65535,不强 制要求Pod设置securityContext.fsGroup。
◎ RunAsAny:不限制Group ID的范围,任何Group都可以访问 Volume。
(8)ReadOnlyRootFilesystem:要求容器运行的根文件系统(root filesystem)必须是只读的。
(9)allowedFlexVolumes:对于类型为flexVolume的存储卷,设置 允许使用的驱动类型,例子如下。
3.用户和组相关配置
(1)RunAsUser:设置运行容器的用户ID(User ID)范围,规则 字段(rule)的值可以被设置为MustRunAs、MustRunAsNonRoot或 RunAsAny。
◎ MustRunAs:需要设置User ID的范围,要求Pod的 securityContext.runAsUser设置的值必须属于该User ID的范围。
◎ MustRunAsNonRoot:必须以非root用户运行容器,要求Pod的 securityContext.runAsUser设置一个非0的用户ID,或者镜像中在USER字 段设置了用户ID,建议同时设置allowPrivilegeEscalation=false以避免不 必要的提升权限操作。
◎ RunAsAny:不限制User ID的范围,任何User都可以运行。
(2)RunAsGroup:设置运行容器的Group ID范围,规则字段的值 可以被设置为MustRunAs、MustRunAsNonRoot或RunAsAny。
◎ MustRunAs:需要设置Group ID的范围,要求Pod的 securityContext.runAsGroup设置的值必须属于该Group ID的范围。
◎ MustRunAsNonRoot:必须以非root组运行容器,要求Pod的 securityContext.runAsUser设置一个非0的用户ID,或者镜像中在USER字 段设置了用户ID,建议同时设置allowPrivilegeEscalation=false以避免不必要的提升权限操作
◎ RunAsAny:不限制Group ID的范围,任何Group的用户都可以 运行
(3)SupplementalGroups:设置容器可以额外添加的Group ID范 围,可以将规则(rule字段)设置为MustRunAs、MayRunAs或 RunAsAny。
◎ MustRunAs:需要设置Group ID的范围,要求Pod的 securityContext.supplementalGroups设置的值必须属于该Group ID范围。
◎ MayRunAs:需要设置Group ID的范围,不强制要求Pod设置 securityContext.supplementalGroups。
◎ RunAsAny:不限制Group ID的范围,任何supplementalGroups 的用户都可以运行。
4.提升权限相关配置
(1)AllowPrivilegeEscalation:设置容器内的子进程是否可以提升 权限,通常在设置非root用户(MustRunAsNonRoot)时进行设置
(2)DefaultAllowPrivilegeEscalation:设置AllowPrivilegeEscalation 的默认值,设置为disallow时,管理员还可以显式设置AllowPrivilegeEscalation来指定是否允许提升权限。
5.Linux能力相关配置
(1)AllowedCapabilities:设置容器可以使用的Linux能力列表,设 置为“*”表示允许使用Linux的所有能力(如NET_ADMIN、SYS_TIME 等)
(2)RequiredDropCapabilities:设置不允许容器使用的Linux能力 列表。
(3)DefaultAddCapabilities:设置默认为容器添加的Linux能力列 表,例如SYS_TIME等
6.SELinux相关配置
seLinux:设置SELinux参数,可以将规则字段(rule)的值设置为 MustRunAs或RunAsAny。
◎ MustRunAs:要求设置seLinuxOptions,系统将对Pod的 securityContext.seLinuxOptions设置的值进行校验。
◎ RunAsAny:不限制seLinuxOptions的设置。
7.其他Linux相关配置
(1)AllowedProcMountTypes:设置允许的ProcMountTypes类型列 表,可以设置allowedProcMountTypes或DefaultProcMount。
(2)AppArmor:设置对容器可执行程序的访问控制权限
(3)Seccomp:设置允许容器使用的系统调用(System Calls)的 profile。
(4)Sysctl:设置允许调整的内核参数
例2:要求Pod运行用户为非特权用户;禁止提升权限;不允许使用 宿主机网络、端口号、IPC等资源;限制可以使用的Volume类型,等 等
此外,Kubernetes建议使用RBAC授权机制来设置针对Pod安全策略 的授权,通常应该对Pod的ServiceAccount进行授权
例如,可以创建如下ClusterRole(也可以创建Role)并将其设置为 允许使用PodSecurityPolicy
然后创建一个ClusterRoleBinding与用户和ServiceAccount进行绑 定
Kubernetes网络模型设计的一个基础原则是:每个Pod都拥有一个独 立的IP地址,并假定所有Pod都在一个可以直接连通的、扁平的网络空 间中。所以不管它们是否运行在同一个Node(宿主机)中,都要求它们 可以直接通过对方的IP进行访问。设计这个原则的原因是,用户不需要 额外考虑如何建立Pod之间的连接,也不需要考虑如何将容器端口映射 到主机端口等问题
在Kubernetes的世界里,IP是以Pod为单位进行分配的。一 个Pod内部的所有容器共享一个网络堆栈(相当于一个网络命名空间, 它们的IP地址、网络设备、配置等都是共享的)。按照这个网络原则抽 象出来的为每个Pod都设置一个IP地址的模型也被称作IP-per-Pod模型
由于Kubernetes的网络模型假设Pod之间访问时使用的是对方Pod的 实际地址,所以一个Pod内部的应用程序看到的自己的IP地址和端口与 集群内其他Pod看到的一样。它们都是Pod实际分配的IP地址。将IP地址 和端口在Pod内部和外部都保持一致,也就不需要使用NAT来进行地址 转换了。Kubernetes的网络之所以这么设计,主要原因就是可以兼容过 去的应用。当然,我们使用Linux命令“ip addr show”也能看到这些地 址,和程序看到的没有什么区别。所以这种IP-per-Pod的方案很好地利 用了现有的各种域名解析和发现机制
为每个Pod都设置一个IP地址的模型还有另外一层含义,那就是同 一个Pod内的不同容器会共享同一个网络命名空间,也就是同一个Linux 网络协议栈。这就意味着同一个Pod内的容器可以通过localhost来连接对 方的端口。这种关系和同一个VM内的进程之间的关系是一样的,看起 来Pod内容器之间的隔离性减小了,而且Pod内不同容器之间的端口是共享的,就没有所谓的私有端口的概念了
Kubernetes对集群网络有如下要求:
(1)所有容器都可以在不用NAT的方式下同别的容器通信
(2)所有节点都可以在不用NAT的方式下同所有容器通信,反之 亦然。
(3)容器的地址和别人看到的地址是同一个地址
在Linux的网络命名空间中可以有自己独立的路由表及独立的 iptables设置来提供包转发、NAT及IP包过滤等功能
为了隔离出独立的协议栈,需要纳入命名空间的元素有进程、套接 字、网络设备等。进程创建的套接字必须属于某个命名空间,套接字的 操作也必须在命名空间中进行。同样,网络设备也必须属于某个命名空 间。因为网络设备属于公共资源,所以可以通过修改属性实现在命名空 间之间移动。当然,是否允许移动与设备的特征有关
1.网络命名空间的实现
Linux的网络协议栈是十分复杂的,为了支持独立的协议栈,相关 的这些全局变量都必须被修改为协议栈私有。最好的办法就是让这些全 局变量成为一个Net Namespace变量的成员,然后为协议栈的函数调用 加入一个Namespace参数。这就是Linux实现网络命名空间的核心
在新生成的私有命名空间中只有回环设备(名为“lo”且是停止状 态),其他设备默认都不存在,如果我们需要,则要一一手工建立。 Docker容器中的各类网络栈设备都是Docker Daemon在启动时自动创建 和配置的
所有的网络设备(物理的或虚拟接口、桥等在内核里都叫作Net Device)都只能属于一个命名空间。当然,物理设备(连接实际硬件的 设备)通常只能关联到root这个命名空间中。虚拟的网络设备(虚拟的 以太网接口或者虚拟网口对)则可以被创建并关联到一个给定的命名空 间中,而且可以在这些命名空间之间移动
由于网络命名空间代表的是一个独立的协议栈,所以它 们之间是相互隔离的,彼此无法通信,在协议栈内部都看不到对方。那 么有没有办法打破这种限制,让处于不同命名空间的网络相互通信,甚 至和外部的网络进行通信呢?答案就是“有,应用Veth设备对即可”。 Veth设备对的一个重要作用就是打通互相看不到的协议栈之间的壁垒, 它就像一条管子,一端连着这个网络命名空间的协议栈,一端连着另一 个网络命名空间的协议栈。所以如果想在两个命名空间之间通信,就必 须有一个Veth设备对。后面会介绍如何操作Veth设备对来打通不同命名 空间之间的网络
我们可以使用Linux iproute2系列配置工具中的IP命令来操作网络命 名空间。注意,这个命令需要由root用户运行.创建一个命名空间:
也可以先通过bash命令进入内部的shell界面,然后执行各种命令
Veth设备对
引入Veth设备对是为了在不同的网络命名空间之间通信,利用它可 以直接将两个网络命名空间连接起来。由于要连接两个网络命名空间, 所以Veth设备都是成对出现的,很像一对以太网卡,并且中间有一根直 连的网线。既然是一对网卡,那么我们将其中一端称为另一端的peer在Veth设备的一端发送数据时,它会将数据直接发送到另一端,并触发 另一端的接收操作
1.Veth设备对的操作命令
接下来看看如何创建Veth设备对,如何连接到不同的命名空间,并 设置它们的地址,让它们通信。
创建Veth设备对:
创建后,可以查看Veth设备对的信息。使用ip link show命令查看所有网络接口:
这时可在外面这个命名空间中看两个设备的情况
只剩一个veth0设备了,已经看不到另一个设备了,另一个设备已经被转移到另一个网络命名空间中了
现在看到的结果是,两个不同的命名空间各自有一个Veth的“网线 头”,各显示为一个Device(在Docker的实现里面,它除了将Veth放入容 器内,还将它的名字改成了eth0,简直以假乱真,你以为它是一个本地 网卡吗)
现在可以通信了吗?不行,因为它们还没有任何地址,我们现在给 它们分配IP地址:
网桥
Linux可以支持多个不同的网络,它们之间能够相互通信,如何将 这些网络连接起来并实现各网络中主机的相互通信呢?可以用网桥。网 桥是一个二层的虚拟网络设备,把若干个网络接口“连接”起来,以使得 网络接口之间的报文能够互相转发。网桥能够解析收发的报文,读取目 标MAC地址的信息,和自己记录的MAC表结合,来决策报文的转发目 标网络接口。为了实现这些功能,网桥会学习源MAC地址(二层网桥 转发的依据就是MAC地址)。在转发报文时,网桥只需要向特定的网 口进行转发,来避免不必要的网络交互。如果它遇到一个自己从未学习 到的地址,就无法知道这个报文应该向哪个网络接口转发,就将报文广 播给所有的网络接口(报文来源的网络接口除外)
在实际的网络中,网络拓扑不可能永久不变。设备如果被移动到另 一个端口上,却没有发送任何数据,网桥设备就无法感知到这个变化, 网桥还是向原来的端口转发数据包,在这种情况下数据就会丢失。所以 网桥还要对学习到的MAC地址表加上超时时间(默认为5min)。如果 网桥收到了对应端口MAC地址回发的包,则重置超时时间,否则过了 超时时间后,就认为设备已经不在那个端口上了,它就会重新广播发送
在实际的网络中,网络拓扑不可能永久不变。设备如果被移动到另 一个端口上,却没有发送任何数据,网桥设备就无法感知到这个变化, 网桥还是向原来的端口转发数据包,在这种情况下数据就会丢失。所以 网桥还要对学习到的MAC地址表加上超时时间(默认为5min)。如果 网桥收到了对应端口MAC地址回发的包,则重置超时时间,否则过了 超时时间后,就认为设备已经不在那个端口上了,它就会重新广播发 送
Linux内核支持网口的桥接(目前只支持以太网接口)。但是与单 纯的交换机不同,交换机只是一个二层设备,对于接收到的报文,要么 转发,要么丢弃。运行着Linux内核的机器本身就是一台主机,有可能 是网络报文的目的地,其收到的报文除了转发和丢弃,还可能被送到网 络协议栈的上层(网络层),从而被自己(这台主机本身的协议栈)消 化,所以我们既可以把网桥看作一个二层设备,也可以把它看作一个三层设备
Linux内核是通过一个虚拟的网桥设备(Net Device)来实现桥接 的。这个虚拟设备可以绑定若干个以太网接口设备,从而将它们桥接起 来。如图7.3所示,这种Net Device网桥和普通的设备不同,最明显的一 个特性是它还可以有一个IP地址
网桥设备br0绑定了eth0和eth1。对于网络协议栈的上 层来说,只看得到br0就行。因为桥接是在数据链路层实现的,上层不 需要关心桥接的细节,所以协议栈上层需要发送的报文被送到br0,网 桥设备的处理代码判断报文该被转发到eth0还是eth1,或者两者皆转 发;反过来,从eth0或从eth1接收到的报文被提交给网桥的处理代码, 在这里会判断报文应该被转发、丢弃还是被提交到协议栈上层
而有时eth0、eth1也可能会作为报文的源地址或目的地址,直接参 与报文的发送与接收,从而绕过网桥
brctl addbr xxxxx
brctl addif xxxxx ethx
网桥的物理网卡作为一个网口,由于在链路层工作,就不再需要IP 地址了,这样上面的IP地址自然失效
ifconfig ethx 0.0.0.0
给网桥配置一个IP地址
ifconfig brxx xxx.xxx.xxx.xxx
在Linux网络协议栈中有一组回调函数挂接点,通过这些挂接点挂 接的钩子函数可以在Linux网络栈处理数据包的过程中对数据包进行一 些操作,例如过滤、修改、丢弃等。整个挂接点技术叫作Netfilter和 iptables
Netfilter负责在内核中执行各种挂接的规则,运行在内核模式中; 而iptables是在用户模式下运行的进程,负责协助和维护内核中Netfilter 的各种规则表。二者互相配合来实现整个Linux网络协议栈中灵活的数 据包处理机制
这些挂接点能挂接的规则也分不同的类型(也就是规则表 Table),我们可以在不同类型的Table中加入我们的规则。目前主要支 持的Table类型有:RAW、MANGLE、NAT和FILTER
上述4个Table(规则链)的优先级是RAW最高,FILTER最低
路由
如果主机与目的主机没有直接相连,那么主机会将IP报文发送给默认的路由器,然后由路由 器来决定往哪里发送IP报文
标准的Docker支持以下4类网络模式。
◎ host模式:使用--net=host指定
◎ container模式:使用--net=container:NAME_or_ID指定
◎ none模式:使用--net=none指定
◎ bridge模式:使用--net=bridge指定,为默认设置
在Kubernetes管理模式下通常只会使用bridge模式
在bridge模式下,Docker Daemon第1次启动时会创建一个虚拟的网桥,默认的名称是docker0,然后按照RPC1918的模型在私有网络空间中 给这个网桥分配一个子网。针对由Docker创建的每一个容器,都会创建 一个虚拟的以太网设备(Veth设备对),其中一端关联到网桥上,另一 端使用Linux的网络命名空间技术,映射到容器内的eth0设备,然后从网桥的地址段内给eth0接口分配一个IP地址
其中ip1是网桥的IP地址,Docker Daemon会在几个备选地址段里给 它选一个地址,通常是以172开头的一个地址。这个地址和主机的IP地 址是不重叠的。ip2是Docker在启动容器时,在这个地址段选择的一个 没有使用的IP地址分配给容器。相应的MAC地址也根据这个IP地址,在 02:42:ac:11:00:00和02:42:ac:11:ff:ff的范围内生成,这样做可以确保不会 有ARP冲突
启动后,Docker还将Veth对的名称映射到eth0网络接口。ip3就是主 机的网卡地址。在一般情况下,ip1、ip2和ip3是不同的IP段,所以在默认不做任何 特殊配置的情况下,在外部是看不到ip1和ip2的
这样做的结果就是,在同一台机器内的容器之间可以相互通信,不 同主机上的容器不能相互通信,实际上它们甚至有可能在相同的网络地 址范围内(不同主机上的docker0的地址段可能是一样的)
为了让它们跨节点互相通信,就必须在主机的地址上分配端口,然后通过这个端口路由或代理到容器上。这种做法显然意味着一定要在容 器之间小心谨慎地协调好端口的分配,或者使用动态端口的分配技术
在我们 的例子中,容器就是图7.8中的容器1和容器2。容器1和容器2共享一个 网络的命名空间,共享一个命名空间的结果就是它们好像在一台机器上 运行,它们打开的端口不会有冲突,可以直接使用Linux的本地IPC进行 通信(例如消息队列或者管道)。其实,这和传统的一组普通程序运行 的环境是完全一样的,传统程序不需要针对网络做特别的修改就可以移 植了,它们之间的互相访问只需要使用localhost就可以
Pod之间的通信
每一个Pod都有一个真实的全局IP地址,同一个Node内的不同Pod之间可以直接采用对方Pod的IP地址通信,而且不需要采用其他发现机制,例如DNS、Consul或者etcd
Pod容器既有可能在同一个Node上运行,也有可能在不同的Node上运行,所以通信也分为两类:同一个Node内Pod之间的通信和不同Node上Pod之间的通信
1.同一个Node内Pod之间的通信
我们看一下同一个Node内两个Pod之间的关系
可以看出,Pod1和Pod2都是通过Veth连接到同一个docker0网桥上 的,它们的IP地址IP1、IP2都是从docker0的网段上动态获取的,它们和 网桥本身的IP3是同一个网段的
另外,在Pod1、Pod2的Linux协议栈上,默认路由都是docker0的地址,也就是说所有非本地地址的网络数据,都会被默认发送到docker0 网桥上,由docker0网桥直接中转
综上所述,由于它们都关联在同一个docker0网桥上,地址段相 同,所以它们之间是能直接通信的
Pod的地址是与docker0在同一个网段的,我们知道docker0网段与宿 主机网卡是两个完全不同的IP网段,并且不同Node之间的通信只能通过 宿主机的物理网卡进行,因此要想实现不同Node上Pod容器之间的通 信,就必须想办法通过主机的这个IP地址进行寻址和通信
要想支持不同Node上Pod之间的通信,就要满足两个条 件:
(1)在整个Kubernetes集群中对Pod的IP分配进行规划,不能有冲 突;
(2)找到一种办法,将Pod的IP和所在Node的IP关联起来,通过这 个关联让Pod可以互相访问
根据条件1的要求,我们需要在部署Kubernetes时对docker0的IP地址 进行规划,保证每个Node上的docker0地址都没有冲突。我们可以在规划后手工配置到每个Node上,或者做一个分配规则,由安装的程序自己 去分配占用。例如,Kubernetes的网络增强开源软件Flannel就能够管理 资源池的分配
根据条件2的要求,Pod中的数据在发出时,需要有一个机制能够知 道对方Pod的IP地址挂在哪个具体的Node上。也就是说先要找到Node对 应宿主机的IP地址,将数据发送到这个宿主机的网卡,然后在宿主机上 将相应的数据转发到具体的docker0上。一旦数据到达宿主机Node,则 那个Node内部的docker0便知道如何将数据发送到Pod
在图7.10中,IP1对应的是Pod1,IP2对应的是Pod2。Pod1在访问 Pod2时,首先要将数据从源Node的eth0发送出去,找到并到达Node2的 eth0。即先是从IP3到IP4的递送,之后才是从IP4到IP2的递送
这里使用虚拟机来完成实验。如果要部署在物理机器上或者云服务 商的环境中,则涉及的网络模型很可能稍微有所不同。不过,从网络角 度来看,Kubernetes的机制是类似且一致的
Kubernetes的网络模型要求每个Node上的容器都可以相互访问
默认的Docker网络模型提供了一个IP地址段是172.17.0.0/16的docker0网桥。每个容器都会在这个子网内获得IP地址,并且将docker0 网桥的IP地址(172.17.42.1)作为其默认网关。需要注意的是,Docker 宿主机外面的网络不需要知道任何关于这个172.17.0.0/16的信息或者知 道如何连接到其内部,因为Docker的宿主机针对容器发出的数据,在物 理网卡地址后面都做了IP伪装MASQUERADE(隐含NAT)。也就是 说,在网络上看到的任何容器数据流都来源于那台Docker节点的物理IP 地址。这里所说的网络都指连接这些主机的物理网络
在Kubernetes的网络模型中,每台主机上的docker0网桥都是可以被 路由到的。也就是说,在部署了一个Pod时,在同一个集群内,各主机 都可以访问其他主机上的Pod IP,并不需要在主机上做端口映射。综上 所述,我们可以在网络层将Kubernetes的节点看作一个路由器。如果将 实验环境改画成一个网络图
为了支持Kubernetes网络模型,我们采取了直接路由的方式来实 现,在每个Node上都配置相应的静态路由项,例如在192.168.1.129这个 Node上配置了两个路由项
# route add -net 10.1.20.0 netmask 255.255.255.0 gw 192.168.1.130 # route add -net 10.1.30.0 netmask 255.255.255.0 gw 192.168.1.131
这意味着,每一个新部署的容器都将使用这个Node(docker0的网 桥IP)作为它的默认网关。而这些Node(类似路由器)都有其他 docker0的路由信息,这样它们就能够相互连通了
我们检查的第1个容器是运行 了“google_containers/pause:latest”镜像的容器,它使用了Docker默认的网 络模型 bridge;而我们检查的第2个容器,也就是在RC/Pod中定义运行 的php-redis容器,使用了非默认的网络配置和映射容器的模型,指定了 映射目标容器为“google_containers/ pause:latest”
首先,一个Pod内的所有容器都需要共用同一个IP地址,这就意味 着一定要使用网络的容器映射模式。然而,为什么不能只启动第1个Pod 中的容器,而将第2个Pod中的容器关联到第1个容器呢?我们认为 Kubernetes是从两方面来考虑这个问题的:首先,如果在Pod内有多个容 器的话,则可能很难连接这些容器;其次,后面的容器还要依赖第1个 被关联的容器,如果第2个容器关联到第1个容器,且第1个容器死掉的 话,第2个容器也将死掉。启动一个基础容器,然后将Pod内的所有容器 都连接到它上面会更容易一些。因为我们只需要为基础的这个 Google_containers/pause容器执行端口映射规则,这也简化了端口映射的 过程。所以我们启动Pod后的网络模型类似于图7.13
在这种情况下,实际Pod的IP数据流的网络目标都是这个 google_containers/pause容器。图7.13有点儿取巧地显示了是 google_containers/pause容器将端口80的流量转发给了相关的容器。而 pause容器只是看起来转发了网络流量,但它并没有真的这么做。实际 上,应用容器直接监听了这些端口,和google_containers/pause容器共享 了同一个网络堆栈。这就是为什么在Pod内部实际容器的端口映射都显 示到google_containers/pause容器上了
综上所述,google_containers/pause容器实际上只是负责接管这个 Pod的Endpoint,并没有做更多的事情
生成service之后iptables变化
第1行是挂在PREROUTING链上的端口重定向规则,所有进入的流 量如果满足20.1.244.75: 80,则都会被重定向到端口33761。第2行是挂 在OUTPUT链上的目标地址NAT,做了和上述第1行规则类似的工作, 但针对的是当前主机生成的外出流量。所有主机生成的流量都需要使用 这个DNAT规则来处理。简而言之,这两个规则使用了不同的方式做了 类似的事情,就是将所有从节点生成的发送给20.1.244.75:80的流量重定 向到本地的33761端口
至此,目标为Service IP地址和端口的任何流量都将被重定向到本地 的33761端口。这个端口连到哪里去了呢?这就到了kube-proxy发挥作用 的地方了。这个kube-proxy服务给每一个新创建的服务都关联了一个随 机的端口号,并且监听那个特定的端口,为服务创建相关的负载均衡对象。在我们的实验中,随机生成的端口刚好是33761。通过监控Node1上 的Kubernetes-Service的日志,在创建服务时可以看到下面的记录
Kubernetes的kube-proxy看起来只是一个夹层,但实际上它只是在 Node上运行的一个服务。上述重定向规则的结果就是针对目标地址为服 务IP的流量,将Kubernetes的kube-proxy变成了一个中间的夹层
CNI是由CoreOS公司提出的另一种容器网络规范,现在已经被 Kubernetes、rkt、Apache Mesos、Cloud Foundry和Kurma等项目采纳。 另外,Contiv Networking, Project Calico、Weave、SR-IOV、Cilium、 Infoblox、Multus、Romana、Plumgrid和Midokura等项目也为CNI提供网 络插件的具体实现。图7.18描述了容器运行环境与各种网络插件通过 CNI进行连接的模型
1.CNI规范概述
CNI提供了一种应用容器的插件化网络解决方案,定义对容器网络 进行操作和配置的规范,通过插件的形式对CNI接口进行实现。CNI是 由rkt Networking Proposal发展而来的,试图提供一种普适的容器网络解 决方案。CNI仅关注在创建容器时分配网络资源,和在销毁容器时删除 网络资源,这使得CNI规范非常轻巧、易于实现,得到了广泛的支持
在CNI模型中只涉及两个概念:容器和网络
◎ 容器(Container):是拥有独立Linux网络命名空间的环境, 例如使用Docker或rkt创建的容器。关键之处是容器需要拥有自己的 Linux网络命名空间,这是加入网络的必要条件
◎ 网络(Network):表示可以互连的一组实体,这些实体拥有 各自独立、唯一的IP地址,可以是容器、物理机或者其他网络设备(比 如路由器)等
对容器网络的设置和操作都通过插件(Plugin)进行具体实现, CNI插件包括两种类型:CNI Plugin和IPAM(IP Address Management) Plugin。CNI Plugin负责为容器配置网络资源,IPAM Plugin负责对容器 的IP地址进行分配和管理。IPAM Plugin作为CNI Plugin的一部分,与 CNI Plugin一起工作
2.CNI Plugin插件详解
CNI Plugin包括3个基本接口的定义:添加(ADD)、删除 (DELETE)、检查(CHECK)和版本查询(VERSION)。这些接口的具体实现要求插件提供一个可执行的程序,在容器网络添加或删除时 进行调用,以完成具体的操作
(1)添加:将容器添加到某个网络。主要过程为在Container Runtime创建容器时,先创建好容器内的网络命名空间(Network Namespace),然后调用CNI插件为该netns进行网络配置,最后启动容 器内的进程
......
3.IPAM Plugin插件详解
为了减轻CNI Plugin对IP地址管理的负担,在CNI规范中设置了一个 新的插件专门用于管理容器的IP地址(还包括网关、路由等信息),被 称为IPAM Plugin。通常由CNI Plugin在运行时自动调用IPAM Plugin完 成容器IP地址的分配
IPAM Plugin负责为容器分配IP地址、网关、路由和DNS,典型的 实现包括host-local和dhcp。与CNI Plugin类似,IPAM插件也通过可执行 程序完成IP地址分配的具体操作。IPAM可执行程序也处理传递给CNI插 件的环境变量和通过标准输入(stdin)传入的网络配置参数
Kubernetes目前支持两种网络插件的实现
◎ CNI插件:根据CNI规范实现其接口,以与插件提供者进行对接
◎ kubenet插件:使用bridge和host-local CNI插件实现一个基本的 cbr0。
为了在Kubernetes集群中使用网络插件,需要在kubelet服务的启动 参数上设置下面两个参数
◎ --network-plugin-dir:kubelet启动时扫描网络插件的目录
◎ --network-plugin:网络插件名称,对于CNI插件,设置为cni即 可,无须关注--network-plugin-dir的路径。对于kubenet插件,设置为 kubenet,目前仅实现了一个简单的cbr0 Linux网桥
目前已有多个开源项目支持以CNI网络插件的形式部署到 Kubernetes集群中,进行Pod的网络设置和网络策略的设置,包括 Calico、Canal、Cilium、Contiv、Flannel、Romana、Weave Net等
为了实现细粒度的容器间网络访问隔离策略,Kubernetes从1.3版本 开始,由SIG-Network小组主导研发了Network Policy机制,目前已升级 为networking.k8s.io/v1稳定版本。Network Policy的主要功能是对Pod间 的网络通信进行限制和准入控制,设置方式为将Pod的Label作为查询条 件,设置允许访问或禁止访问的客户端Pod列表。目前查询条件可以作 用于Pod和Namespace级别
为了使用Network Policy,Kubernetes引入了一个新的资源对象 NetworkPolicy,供用户设置Pod间网络访问的策略。但仅定义一个网络 策略是无法完成实际的网络隔离的,还需要一个策略控制器(Policy Controller)进行策略的实现。策略控制器由第三方网络组件提供,目前 Calico、Cilium、Kube-router、Romana、Weave Net等开源项目均支持网 络策略的实现
Network Policy的工作原理如图7.19所示,policy controller需要实现 一个API Listener,监听用户设置的NetworkPolicy定义,并将网络访问规 则通过各Node的Agent进行实际设置(Agent则需要通过CNI网络插件实 现)
apiversion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: test-network-policy namespace: default spec: podSelector: matchLabels: role: db PolicyTypes: - Ingress - Egress ingress: - from: - ipBlock: cidr: 172.17.0.0/16 except: - 172.17.1.0/24 - namespaceSelector: matchLabels: role: frontend ports: - protocol: TCP port: 6379 egress: - to: - ipBlock: cidr: 10.0.0.0/24 ports: - protocol: TCP port: 5987
主要的参数说明如下。 ◎ podSelector:用于定义该网络策略作用的Pod范围,本例的选 择条件为包含“role=db”标签的Pod。 ◎ policyTypes:网络策略的类型,包括ingress和egress两种,用 于设置目标Pod的入站和出站的网络限制。 ◎ ingress:定义允许访问目标Pod的入站白名单规则,满足from条件的客户端才能访问ports定义的目标Pod端口号.- from:对符合条件的客户端Pod进行网络放行,规则包括基于客户 端Pod的Label、基于客户端Pod所在的Namespace的Label或者客户端的IP 范围。- ports:允许访问的目标Pod监听的端口号。
◎ egress:定义目标Pod允许访问的“出站”白名单规则,目标Pod 仅允许访问满足to条件的服务端IP范围和ports定义的端口号
- to:允许访问的服务端信息,可以基于服务端Pod的Label、基于服 务端Pod所在的Namespace的Label或者服务端IP范围。
- ports:允许访问的服务端的端口号。
通过本例的NetworkPolicy设置,对目标Pod的网络访问的效果如 下。
◎ 该网络策略作用于Namespace“default”中含有“role=db”Label的 全部Pod。
◎ 允许与目标Pod在同一个Namespace中的包 含“role=frontend”Label的客户端Pod访问目标Pod。
◎ 允许属于包含“project=myproject”Label的Namespace的客户端 Pod访问目标Pod。
◎ 允许从IP地址范围“172.17.0.0/16”的客户端Pod访问目标Pod, 但是不包括IP地址范围“172.17.1.0/24”的客户端。
◎ 允许目标Pod访问IP地址范围“10.0.0.0/24”并监听5978端口的服 务
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny spec: podSelector: {} policyType: - Ingress
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadate: name: allow-all spec: podSelector: {} ingress: - {} policyType: - Ingress
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny spec: podSelector: {} policyTypes: - Egress
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-allow spec: podSelector: {} egress: - {} policyType: - Egress
kind: NetworkPolicy metadata: name: deny-all spec: podSelector: {} policyType: - Ingress - Egress
Flannel之所以可以搭建Kubernetes依赖的底层网络,是因为它能实 现以下两点
(1)它能协助Kubernetes,给每一个Node上的Docker容器都分配互 相不冲突的IP地址。
(2)它能在这些IP地址之间建立一个覆盖网络(Overlay Network),通过这个覆盖网络,将数据包原封不动地传递到目标容器内
flanneld进程并不简单,它上连etcd,利用etcd来管理可分配的IP地 址段资源,同时监控etcd中每个Pod的实际地址,并在内存中建立了一 个Pod节点路由表;它下连docker0和物理网络,使用内存中的Pod节点 路由表,将docker0发给它的数据包包装起来,利用物理网络的连接将 数据包投递到目标flanneld上,从而完成Pod到Pod之间的直接地址通 信
Flannel之间的底层通信协议的可选技术包括UDP、VxLan、AWS VPC等多种方式。通过源flanneld封包、目标flanneld解包,最终docker0 收到的就是原始的数据,对容器应用来说是透明的,感觉不到中间 Flannel的存在
--bip=10.244.1.5/24
Flannel完美地实现了对Kubernetes网络的支持,但是它引入了多个 网络组件,在网络通信时需要转到flannel0网络接口,再转到用户态的 flanneld程序,到对端后还需要走这个过程的反过程,所以也会引入一 些网络的时延损耗
另外,Flannel模型默认采用了UDP作为底层传输协议,UDP本身是 非可靠协议,虽然两端的TCP实现了可靠传输,但在大流量、高并发的应用场景下还需要反复测试,确保没有问题
Calico的主要组件如下
◎ Felix:Calico Agent,运行在每个Node上,负责为容器设置网 络资源(IP地址、路由规则、iptables规则等),保证跨主机容器网络互 通。
◎ etcd:Calico使用的后端存储。
◎ BGP Client:负责把Felix在各Node上设置的路由信息通过BGP 协议广播到Calico网络。
◎ Route Reflector:通过一个或者多个BGP Route Reflector来完成 大规模集群的分级路由分发
◎ CalicoCtl:Calico命令行管理工具。
PV是对底层网络共享存储的抽象,将共享存储定义为一种“资源”, 比如Node也是一种容器应用可以“消费”的资源。PV由管理员创建和配 置,它与共享存储的具体实现直接相关,例如GlusterFS、iSCSI、RBD 或GCE或AWS公有云提供的共享存储,通过插件式的机制完成与共享 存储的对接,以供应用访问和使用
PVC则是用户对存储资源的一个“申请”。就像Pod“消费”Node的资 源一样,PVC能够“消费”PV资源。PVC可以申请特定的存储空间和访问 模式
,StorageClass和动态资源供应的机制得到了完 善,实现了存储卷的按需创建,在共享存储的自动化管理进程中实现了 重要的一步,通过StorageClass的定义,管理员可以将存储资源定义为某种类别 (Class),正如存储设备对于自身的配置描述(Profile),例如“快速 存储”“慢速存储”“有数据冗余”“无数据冗余”等。用户根据StorageClass 的描述就能够直观地得知各种存储资源的特性,就可以根据应用对存储 资源的需求去申请存储资源了
apiVersion: v1 kind: PersistentVolume metadata: name: pv1 spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle storageClassName: slow nfs: path: /tmp server: 172.17.0.2
1.存储能力(Capacity)
描述存储设备具备的能力,目前仅支持对存储空间的设置 (storage=xx),未来可能加入IOPS、吞吐率等指标的设置
2.存储卷模式(Volume Mode)
Kubernetes从1.13版本开始引入存储卷类型的设置 (volumeMode=xxx),可选项包括Filesystem(文件系统)和Block(块 设备),默认值为Filesystem
目前有以下PV类型支持块设备类型:◎ AWSElasticBlockStore ◎ AzureDisk ◎ FC ◎ GCEPersistentDisk ◎ iSCSI ◎ Local volume ◎ RBD(Ceph Block Device) ◎ VsphereVolume(alpha)
apiVersion: v1 kind: PersestentVolume metadata: name: pv2 spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain volumeMode: Block fc: targetWWNs: ["50060e801049cdf1"] lun: 0 readOnly: false
3.访问模式(Access Modes) 对PV进行访问模式的设置,用于描述用户的应用对存储资源的访 问权限。访问模式如下
◎ ReadWriteOnce(RWO):读写权限,并且只能被单个Node挂 载。◎ ReadOnlyMany(ROX):只读权限,允许被多个Node挂载。 ◎ ReadWriteMany(RWX):读写权限,允许被多个Node挂载。
某些PV可能支持多种访问模式,但PV在挂载时只能使用一种访问 模式,多种访问模式不能同时生效
PV可以设定其存储的类别,通过storageClassName参数指定一个 StorageClass资源对象的名称。具有特定类别的PV只能与请求了该类别 的PVC进行绑定。未设定类别的PV则只能与不请求任何类别的PVC进行绑定
5.回收策略(Reclaim Policy)
通过PV定义中的persistentVolumeReclaimPolicy字段进行设置,可 选项如下。◎ 保留:保留数据,需要手工处理。 ◎ 回收空间:简单清除文件的操作(例如执行rm -rf /thevolume/* 命令)。◎ 删除:与PV相连的后端存储完成Volume的删除操作(如AWS EBS、GCE PD、Azure Disk、OpenStack Cinder等设备的内部Volume清 理)。目前,只有NFS和HostPath两种类型的存储支持Recycle策略;AWS EBS、GCE PD、Azure Disk和Cinder volumes支持Delete策略
6.挂载参数(Mount Options)
在将PV挂载到一个Node上时,根据后端存储的特点,可能需要设 置额外的挂载参数,可以根据PV定义中的mountOptions字段进行设置
apiVersion: v1 kind: PersistentVolume metadata: name: gce-disk-l spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce mountOptions: - hard - nolock - nfservers=3 gcePersistentDisk: fsType: ext4 pdName: gce-disk-l
目前,以下PV类型支持设置挂载参数:
◎ AWSElasticBlockStore ◎ AzureDisk ◎ AzureFile ◎ CephFS ◎ Cinder (OpenStack block storage) ◎ GCEPersistentDisk ◎ Glusterfs ◎ NFS ◎ Quobyte Volumes ◎ RBD (Ceph Block Device) ◎ StorageOS◎ VsphereVolume ◎ iSCSI
7.节点亲和性(Node Affinity)
PV可以设置节点亲和性来限制只能通过某些Node访问Volume,可 以在PV定义中的nodeAffinity字段进行设置。使用这些Volume的Pod将 被调度到满足条件的Node上
某个PV在生命周期中可能处于以下4个阶段(Phaes)之一。 ◎ Available:可用状态,还未与某个PVC绑定。 ◎ Bound:已与某个PVC绑定。 ◎ Released:绑定的PVC已经删除,资源已释放,但没有被集群 回收。◎ Failed:自动资源回收失败。
PVC作为用户对存储资源的需求申请,主要包括存储空间请求、访 问模式、PV选择条件和存储类别等信息的设置。下例声明的PVC具有 如下属性:申请8GiB存储空间,访问模式为ReadWriteOnce,PV 选择条 件为包含标签“release=stable”并且包含条件为“environment In [dev]”的 标签,存储类别为“slow”(要求在系统中已存在名为slow的 StorageClass)
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: myclaim spec: accessModes: - ReadWriteOnce resources: request: storage: 8Gi storageClassName: slow selector: matchLabels: release: "stable" matchExpressions: - {key: environment,operator: In,values: [dev]}
PVC的关键配置参数说明如下。
◎ 资源请求(Resources):描述对存储资源的请求,目前仅支持 request.storage的设置,即存储空间大小。
◎ 访问模式(Access Modes):PVC也可以设置访问模式,用于描述用户应用对存储资源的访问权限。其三种访问模式的设置与PV的 设置相同
◎ 存储卷模式(Volume Modes):PVC也可以设置存储卷模式, 用于描述希望使用的PV存储卷模式,包括文件系统和块设备
◎ PV选择条件(Selector):通过对Label Selector的设置,可使 PVC对于系统中已存在的各种PV进行筛选。系统将根据标签选出合适 的PV与该PVC进行绑定。选择条件可以使用matchLabels和 matchExpressions进行设置,如果两个字段都设置了,则Selector的逻辑 将是两组条件同时满足才能完成匹配
◎ 存储类别(Class):PVC 在定义时可以设定需要的后端存储 的类别(通过storageClassName字段指定),以减少对后端存储特性的 详细信息的依赖。只有设置了该Class的PV才能被系统选出,并与该 PVC进行绑定
PVC也可以不设置Class需求。如果storageClassName字段的值被设 置为空(storageClassName=""),则表示该PVC不要求特定的Class,系 统将只选择未设定Class的PV与之匹配和绑定。PVC也可以完全不设置 storageClassName字段,此时将根据系统是否启用了名为 DefaultStorageClass的admission controller进行相应的操作
◎ 未启用DefaultStorageClass:等效于PVC设置storageClassName 的值为空(storageClassName=""),即只能选择未设定Class的PV与之 匹配和绑定
◎ 启用DefaultStorageClass:要求集群管理员已定义默认的 StorageClass。如果在系统中不存在默认的StorageClass,则等效于不启 用DefaultStorageClass的情况。如果存在默认的StorageClass,则系统将 自动为PVC创建一个PV(使用默认StorageClass的后端存储),并将它 们进行绑定。集群管理员设置默认StorageClass的方法为,在StorageClass的定义中加上一个annotation“storageclass.kubernetes.io/is- default-class= true”。如果管理员将多个StorageClass都定义为default,则 由于不唯一,系统将无法为PVC创建相应的PV
PV和PVC的生命周期
我们可以将PV看作可用的存储资源,PVC则是对存储资源的需 求,PV和PVC的相互关系遵循如图8.1所示的生命周期
◎ 静态模式:集群管理员手工创建许多PV,在定义PV时需要将 后端存储的特性进行设置
◎ 动态模式:集群管理员无须手工创建PV,而是通过 StorageClass的设置对后端存储进行描述,标记为某种类型。此时要求 PVC对存储的类型进行声明,系统将自动完成PV的创建及与PVC的绑 定。PVC可以声明Class为"",说明该PVC禁止使用动态模式
资源绑定
在用户定义好PVC之后,系统将根据PVC对存储资源的请求(存储 空间和访问模式)在已存在的PV中选择一个满足PVC要求的PV,一旦 找到,就将该PV与用户定义的PVC进行绑定,用户的应用就可以使用 这个PVC了。如果在系统中没有满足PVC要求的PV,PVC则会无限期处 于Pending状态,直到等到系统管理员创建了一个符合其要求的PV。PV 一旦绑定到某个PVC上,就会被这个PVC独占,不能再与其他PVC进行 绑定了。在这种情况下,当PVC申请的存储空间比PV的少时,整个PV 的空间就都能够为PVC所用,可能会造成资源的浪费。如果资源供应使 用的是动态模式,则系统在为PVC找到合适的StorageClass后,将自动创 建一个PV并完成与PVC的绑定
资源使用
Pod使用Volume的定义,将PVC挂载到容器内的某个路径进行使 用。Volume的类型为persistentVolumeClaim,在后面的示例中再进行详 细说明。在容器应用挂载了一个PVC后,就能被持续独占使用。不过, 多个Pod可以挂载同一个PVC,应用程序需要考虑多个实例共同访问一 块存储空间的问题
资源释放
当用户对存储资源使用完毕后,用户可以删除PVC,与该PVC绑定 的PV将会被标记为“已释放”,但还不能立刻与其他PVC进行绑定。通过 之前PVC写入的数据可能还被留在存储设备上,只有在清除之后该PV 才能再次使用
资源回收
对于PV,管理员可以设定回收策略,用于设置与之绑定的PVC释 放资源之后如何处理遗留数据的问题。只有PV的存储空间完成回收, 才能供新的PVC绑定和使用。回收策略详见下节的说明
下面通过两张图分别对在静态资源供应模式和动态资源供应模式 下,PV、PVC、StorageClass及Pod使用PVC的原理进行说明
图8.2描述了在静态资源供应模式下,通过PV和PVC完成绑定,并 供Pod使用的存储管理机制
图8.3描述了在动态资源供应模式下,通过StorageClass和PVC完成 资源动态绑定(系统自动生成PV),并供Pod使用的存储管理机制
StorageClass详解
StorageClass作为对存储资源的抽象定义,对用户设置的PVC申请屏 蔽后端存储的细节,一方面减少了用户对于存储资源细节的关注,另一 方面减轻了管理员手工管理PV的工作,由系统自动完成PV的创建和绑 定,实现了动态的资源供应。基于StorageClass的动态资源供应模式将 逐步成为云平台的标准存储配置模式
接下来,需要为这两个工作组分别定义一个 Context,即运行环 境。这个运行环境将属于某个特定的命名空间
通过kubectl config set-context命令定义Context,并将Context置于之 前创建的命名空间中
使用kubectl config use-context <context_name>命令设置当前运行环 境。
下面的命令将把当前运行环境设置为ctx-dev
kubectl config use-context ctx-dev
为了避免系统挂掉,该Node会选择“清理”某些Pod来释放资源,此 时每个Pod都可能成为牺牲品。但有些Pod担负着更重要的职责,比其他 Pod更重要,比如与数据存储相关的、与登录相关的、与查询余额相关 的,即使系统资源严重不足,也需要保障这些Pod的存活,Kubernetes中 该保障机制的核心如下
◎ 通过资源限额来确保不同的Pod只能占用指定的资源
◎ 允许集群的资源被超额分配,以提高集群的资源利用率。
◎ 为Pod划分等级,确保不同等级的Pod有不同的服务质量 (QoS),资源不足时,低等级的Pod会被清理,以确保高等级的Pod稳 定运行。
1.详解Requests和Limits参数.以CPU为例,图10.3显示了未设置Limits和设置了Requests、Limits 的CPU使用率的区别
尽管Requests和Limits只能被设置到容器上,但是设置Pod级别的 Requests和Limits能大大提高管理Pod的便利性和灵活性,因此在 Kubernetes中提供了对Pod级别的Requests和Limits的配置对于CPU和 内存而言,Pod的Requests或Limits是指该Pod中所有容器的Requests或 Limits的总和(对于Pod中没有设置Requests或Limits的容器,该项的值 被当作0或者按照集群配置的默认值来计算)。下面对CPU和内存这两 种计算资源的特点进行说明
基于Requests和Limits的Pod调度机制
当一个Pod创建成功时,Kubernetes调度器(Scheduler)会为该Pod 选择一个节点来执行。对于每种计算资源(CPU和Memory)而言,每 个节点都有一个能用于运行Pod的最大容量值。调度器在调度时,首先 要确保调度后该节点上所有Pod的CPU和内存的Requests总和,不超过该 节点能提供给Pod使用的CPU和Memory的最大容量值
这里需要注意:可能某节点上的实际资源使用量非常低,但是已运 行Pod配置的Requests值的总和非常高,再加上需要调度的Pod的 Requests值,会超过该节点提供给Pod的资源容量上限,这时Kubernetes 仍然不会将Pod调度到该节点上。如果Kubernetes将Pod调度到该节点 上,之后该节点上运行的Pod又面临服务峰值等情况,就可能导致Pod资 源短缺。
kubelet在启动Pod的某个容器时,会将容器的Requests和Limits值转化为相应的容器启动参数传递给容器执行器(Docker或者rkt)
如果容器的执行环境是Docker,那么容器的如下4个参数是这样传 递给Docker的
1)spec.container[].resources.requests.cpu
这个参数会转化为core数(比如配置的100m会转化为0.1),然后 乘以1024,再将这个结果作为--cpu-shares参数的值传递给docker run命 令。在docker run命令中,--cpu-share参数是一个相对权重值(Relative Weight),这个相对权重值会决定Docker在资源竞争时分配给容器的资 源比例
这里需要区分清楚的是:这个参数对于Kubernetes而言是绝对值, 主要用于Kubernetes调度和管理;同时Kubernetes会将这个参数的值传递 给docker run的--cpu-shares参数。--cpu-shares参数对于Docker而言是相对 值,主要用于资源分配比例
2)spec.container[].resources.limits.cpu
这个参数会转化为millicore数(比如配置的1被转化为1000,而配置 的100m被转化为100),将此值乘以100000,再除以1000,然后将结果 值作为--cpu-quota参数的值传递给docker run命令。docker run命令中另 外一个参数--cpu-period默认被设置为100000,表示Docker重新计量和分 配CPU的使用时间间隔为100000μs(100ms)
Docker的--cpu-quota参数和--cpu-period参数一起配合完成对容器 CPU的使用限制:比如Kubernetes中配置容器的CPU Limits为0.1,那么 计算后--cpu-quota为10000,而--cpu-period为100000,这意味着Docker在100ms内最多给该容器分配10ms×core的计算资源用量,10/100=0.1 core 的结果与Kubernetes配置的意义是一致的
注意:如果kubelet的启动参数--cpu-cfs-quota被设置为true,那么 kubelet会强制要求所有Pod都必须配置CPU Limits(如果Pod没有配置, 则集群提供了默认配置也可以)。从Kubernetes 1.2版本开始,这个-- cpu-cfs-quota启动参数的默认值就是true
计算资源相关常见问题分析
(1)Pod状态为Pending,错误信息为FailedScheduling。如果Kubernetes调度器在集群中找不到合适的节点来运行Pod,那么这个Pod 会一直处于未调度状态,直到调度器找到合适的节点为止。每次调度器 尝试调度失败时,Kubernetes都会产生一个事件
对大内存页(Huge Page)资源的支持
在现代操作系统中,内存是以Page(页,有时也可以称之为 Block)为单位进行管理的,而不以字节为单位,包括内存的分配和回 收都基于Page。典型的Page大小为4KB,因此用户进程申请1MB内存就需要操作系统分配256个Page,而1GB内存对应26万多个Page!
为了实现快速内存寻址,CPU内部以硬件方式实现了一个高性能的 内存地址映射的缓存表——TLB(Translation Lookaside Buffer),用来 保存逻辑内存地址与物理内存的对应关系。若目标地址的内存页物理地 址不在TLB的缓存中或者TLB中的缓存记录失效,CPU就需要切换到低 速的、以软件方式实现的内存地址映射表进行内存寻址,这将大大降低 CPU的运算速度。针对缓存条目有限的TLB缓存表,提高TLB效率的最 佳办法就是将内存页增大,这样一来,一个进程所需的内存页数量会相 应减少很多。如果把内存页从默认的4KB改为2MB,那么1GB内存就只 对应512个内存页了,TLB的缓存命中率会大大增加。这是不是意味着 我们可以任意指定内存页的大小,比如1314MB的内存页?答案是否定 的,因为这是由CPU来决定的,比如常见的Intel X86处理器可以支持的 大内存页通常是2MB,个别型号的高端处理器则支持1GB的大内存页
在Linux平台下,对于那些需要大量内存(1GB以上内存)的程序 来说,大内存页的优势是很明显的,因为Huge Page大大提升了TLB的 缓存命中率,又因为Linux对Huge Page提供了更为简单、便捷的操作接 口,所以可以把它当作文件来进行读写操作。Linux使用Huge Page文件 系统hugetlbfs支持巨页,这种方式更为灵活,我们可以设置Huge Page的 大小,比如1GB、2GB甚至2.5GB,然后设置有多少物理内存用于分配 Huge Page,这样就设置了一些预先分配好的Huge Page。可以将 hugetlbfs文件系统挂载在/mnt/huge目录下,通过执行下面的指令完成设 置
mkdir /mnt/huge mount -t hugetlbfs nodev /mnt/huge
在设置完成后,用户进程就可以使用mmap映射Huge Page目标文件 来使用大内存页了,Intel DPDK便采用了这种做法,测试表明应用使用 大内存页比使用4KB的内存页性能提高了10%~15%
Kubernetes 1.14版本对Linux Huge Page的支持正式更新为GA稳定 版。我们可以将Huge Page理解为一种特殊的计算资源:拥有大内存页 的资源。而拥有Huge Page资源的Node也与拥有GPU资源的Node一样, 属于一种新的可调度资源节点(Schedulable Resource Node)
Huge Page也支持ResourceQuota来实现配额限制,类似CPU或者 Memory,但不同于CPU或者内存,Huge Page资源属于不可超限使用的 资源,拥有Huge Page能力的Node会将自身支持的Huge Page的能力信息 自动上报给Kubernetes Master
为此,Kubernetes引入了一个新的资源类型hugepages-<size>,来表 示大内存页这种特殊的资源,比如hugepages-2Mi表示2MiB规格的大内 存页资源。一个能提供2MiB规格Huge Page的Node,会上报自己拥有 Hugepages-2Mi的大内存页资源属性,供需要这种规格的大内存页资源 的Pod使用,而需要Huge Page资源的Pod只要给出相关的Huge Page的声 明,就可以被正确调度到匹配的目标Node上了。相关例子如下
apiVersion: v1 kind: Pod metadata: generateName: hugepages-volume- spec: containers: - image: fedora:latest command: - sleep: - inf name: example volumeMounts: - mountPath: /hugepages name: hugepage resource: limits: hugepages-2Mi: 100Mi memory: 100Mi request: memory:100Mi volumes: - name: hugepage emptyDir: medium: HugePages
Kubernetes提供了LimitRange机制对Pod和容器的 Requests和Limits配置进一步做出限制。在下面的示例中,将说明如何 将LimitsRange应用到一个Kubernetes的命名空间中,然后说明 LimitRange的几种限制方式,比如最大及最小范围、Requests和Limits的 默认值、Limits与Requests的最大比例上限等
apiVersion: v1 kind: LimitRange metadata: name: mylimits spec: limits: - max: cpu: "4" memory: 2Gi min: cpu: 200m memory: 6Mi maxLimitRequestRatio: cpu: 3 memory: 2 type: Pod - default: cpu: 300m memory: 200Mi defaultRequest: cpu: 200m memory: 100Mi max: cpu: "2" memory: 1Gi min: cpu: 100m memory: 3Mi maxLimitRequestRatio: cpu: 5 memory: 4 type: Container