背景/分享目标
- 简单介绍k8s的基础知识
- 深度介绍k8s调度的相关知识
“细节是魔鬼” vs “好读书,不求甚解”
-
k8s是一个技术深度和广度都很高的系统;
- 广度上:本文只会覆盖到一些基本的原理和概念,并不关注具体的交互;
- 深度上:本文只会聚焦于调度本身,具体会介绍一些整理后的代码/伪代码;
K8S整体概览
-
部署发展历程
与传统的部署方式相比,k8s容器化部署,主要有两个方面的变化:
-
单机上的容器技术:把我们需要部署的程序打包成镜像/通信;
- 在本篇文章里,暂时以docker作为容器技术代名词;
- k8s:找到合适的物理机去部署容器;
-
docker基础知识介绍
docker 的角色:实现部署业务程序在单机层面的各种隔离;
-
“软件层面”的隔离:
- Namespace: 看不到物理机上的其他程序;
-
“硬件层面”的隔离:
- Cgroup: 限制了硬件资源的使用的隔离;
- AUFS/DeviceMapper: 文件的隔离;
- 单机隔离技术介绍:
-
-
namespace技术:
-
cgroup技术:
-
文件技术:
-
-
k8s介绍
k8s架构的作用:
-
容器的初始化-运行-消亡;
- 初始化:需要挑选合适的物理机部署容器;
- 运行:需要挑选出新的物理机部署容器;
挑选即是调度;
聚焦一下:只关注调度,也就是如何找到一个合适的物理机;其他全部都不关注;
K8S的调度逻辑分析
在 Kubernetes 中,调度
是指将 Pod 放置到合适的 Node 上,然后对应 Node 上的 Kubelet 才能够运行这些 pod
基础理论:
- 调度概览
调度器通过 kubernetes 的监测(Watch)机制来发现集群中新创建且尚未被调度到 Node 上的 Pod。 调度器会将发现的每一个未调度的 Pod 调度到一个合适的 Node 上来运行。
- kube-scheduler 调度流程
- kube-scheduler 给一个 pod 做调度选择包含两个步骤:
-
-
过滤
- 过滤阶段会将所有满足 Pod 调度需求/资源请求的 Node 选出来。 在过滤之后,得出一个 Node 列表,里面包含了所有可调度节点;通常情况下, 这个 Node 列表包含不止一个 Node。如果这个列表是空的,代表这个 Pod 不可调度。
-
打分
- 在打分阶段,调度器会为 Pod 从所有可调度节点中选取一个最合适的 Node。 根据当前启用的打分规则,调度器会给每一个可调度节点进行打分。
- 最后,kube-scheduler 会将 Pod 调度到得分最高的 Node 上。 如果存在多个得分最高的 Node,kube-scheduler 会从中随机选取一个。
-
个人补充的一点:
- 选定Node之后,单机的绑定/部署异步进行;调度根据结果再决定是绑定成功或者是绑定失败,重新调度;
-
-
调度框架(调度框架提案)
queueSort:这些插件对调度队列中的悬决的 Pod 排序。 一次只能启用一个队列排序插件。
preFilter:这些插件用于在过滤之前预处理或检查 Pod 或集群的信息。 它们可以将 Pod 标记为不可调度。
filter:这些插件相当于调度策略中的断言(Predicates),用于过滤不能运行 Pod 的节点。 过滤 器的调用顺序是可配置的。 如果没有一个节点通过所有过滤器的筛选,Pod 将会被标记为不可调度。
postFilter:当无法为 Pod 找到可用节点时,按照这些插件的配置顺序调用他们。 如果任何 postFilter 插件将 Pod 标记为“可调度”,则不会调用其余插件。
preScore:这是一个信息扩展点,可用于预打分工作。
score:这些插件给通过筛选阶段的节点打分。调度器会选择得分最高的节点。
reserve:这是一个信息扩展点,当资源已经预留给 Pod 时,会通知插件。 这些插件还实现了 Unreserve 接口,在 Reserve 期间或之后出现故障时调用。
permit:这些插件可以阻止或延迟 Pod 绑定。
preBind:这些插件在 Pod 绑定节点之前执行。
bind:这个插件将 Pod 与节点绑定。绑定插件是按顺序调用的,只要有一个插件完成了绑定,其余插件都会跳过。绑定插件至少需要一个。
postBind:这是一个信息扩展点,在 Pod 绑定了节点之后调用。
Talk is cheap, let's talk code
- “yaml工程师”
声明式:业务负责描述目标状态,而k8s负责更改实际状态,使其变为期望状态。
kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
apiVersion: v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
resources:
requests:
memory: "100Mi"
limits:
memory: "200Mi"
type FilterPlugin interface {
Plugin
Filter(ctx context.Context, state *CycleState, pod *v1.Pod,
nodeInfo *NodeInfo) *Status
}
// scheduleOne does the entire scheduling workflow for a single pod. It is serialized on the scheduling algorithm's host fitting.
func (sched *Scheduler) scheduleOne() {
// Run "queueSort" plguins
podInfo := sched.NextPod()
fwk, err := sched.frameworkForPod(pod)
// Run "prefilter"/"filter" plugins【filter】
// Run "prescore"/"score"/"Normalize" plugins【score】
scheduleResult, err := sched.Algorithm.Schedule(fwk, state, pod)
If err != nil {
fwk.RunPostFilterPlugins()
return
}
// Run "reserve" plugins.
err := fwk.RunReservePluginsReserve(Pod, scheduleResult.SuggestedHost);
if err != nil {
return
}
// Run "permit" plugins.
err := fwk.RunPermitPlugins(Pod, scheduleResult.SuggestedHost)
if err != nil {
return
}
// bind the pod to its host asynchronously (we can do this b/c of the assumption step above).
go func() {
waitOnPermitStatus := fwk.WaitOnPermit(Pod)
if !waitOnPermitStatus.IsSuccess() {
return
}
// Run "prebind" plugins.
preBindStatus := fwk.RunPreBindPlugins(Pod, scheduleResult.SuggestedHost)
if !preBindStatus.IsSuccess() {
return
}
// Run "bind" plugins
err := sched.bind(fwk, Pod, scheduleResult.SuggestedHost)
// Run "postbind" plugins.
fwk.RunPostBindPlugins(assumedPod, scheduleResult.SuggestedHost)
}
}()
}