###### tags: `CKA`
[讀書會入口](/ma8jxJEdRiSwwwboYtHuAg)
# Scheduler(上)
[TOC]
## Scheduling 簡介
Scheduling: 能把 pods 被配到某 Nodes 上,讓 kubelet 可以執行它
### K8s Scheduler
- K8s 裡面 default scheduler 就是 kube-scheduler
- 是control-plane 的其中一個元件
- 也可以自定義 Scheduler
<!-- 流程:
1. pod definition 初始化時,NodeName 預設為空
2. Scheduler 利用一系列function 計算 scoring 幫你找到最適合配置 pod 的 node
3. 告訴 api server 要把這個 pod bind 到哪個 node 上面
-->

<!-- kube-scheduler 備份機制:Raft protocal -->
### Manual Scheduling
使用 binding + api requests 自行模擬 scheduler 的工作,配置 pod 要去的 node

如果 pod 無法調度,pod status 就會是變 pending


## Label & Selectors
#### Label
- 綁定到 k8s 資源的 key/value pair
- 可以加到各種Node, Pod, Service等資源上
- 方便 user 更好識別
回憶上集提過的 labels
```
"release" : "stable", "release" : "canary"
"environment" : "dev", "environment" : "qa", "environment" : "production"
"tier" : "frontend", "tier" : "backend", "tier" : "cache"
"partition" : "customerA", "partition" : "customerB"
"track" : "daily", "track" : "weekly"
```
推薦使用的 labels
<table>
<thead>
<tr>
<th>Key</th>
<th>Description</th>
<th>Example</th>
<th>Type</th>
</tr>
</thead>
<tbody><tr>
<td><a class="vglnk" href="http://app.kubernetes.io/name" rel="nofollow"><span>app</span><span>.</span><span>kubernetes</span><span>.</span><span>io</span><span>/</span><span>name</span></a></td>
<td>Application 名稱</td>
<td>mysql</td>
<td>string</td>
</tr>
<tr>
<td><a class="vglnk" href="http://app.kubernetes.io/instance" rel="nofollow"><span>app</span><span>.</span><span>kubernetes</span><span>.</span><span>io</span><span>/</span><span>instance</span></a></td>
<td>Application Instance 的識別用名稱</td>
<td>wordpress-abcxzy</td>
<td>string</td>
</tr>
<tr>
<td><a class="vglnk" href="http://app.kubernetes.io/version" rel="nofollow"><span>app</span><span>.</span><span>kubernetes</span><span>.</span><span>io</span><span>/</span><span>version</span></a></td>
<td>Application目前的版本號</td>
<td>5.7.21</td>
<td>string</td>
</tr>
<tr>
<td><a class="vglnk" href="http://app.kubernetes.io/component" rel="nofollow"><span>app</span><span>.</span><span>kubernetes</span><span>.</span><span>io</span><span>/</span><span>component</span></a></td>
<td>在 Application 架構中的元件</td>
<td>database</td>
<td>string</td>
</tr>
<tr>
<td><a class="vglnk" href="http://app.kubernetes.io/part-of" rel="nofollow"><span>app</span><span>.</span><span>kubernetes</span><span>.</span><span>io</span><span>/</span><span>part</span><span>-</span><span>of</span></a></td>
<td>更上層的 Application 名稱</td>
<td>wordpress</td>
<td>string</td>
</tr>
<tr>
<td><a class="vglnk" href="http://app.kubernetes.io/managed-by" rel="nofollow"><span>app</span><span>.</span><span>kubernetes</span><span>.</span><span>io</span><span>/</span><span>managed</span><span>-</span><span>by</span></a></td>
<td>用來管理 Application 佈署 & 操作的工具</td>
<td>helm</td>
<td>string</td>
</tr>
</tbody></table>
```yaml=
apiVersion: apps/v1
kind: StatefulSet
metadata:
labels:
app.kubernetes.io/name: mysql
app.kubernetes.io/instance: mysql-abcxzy
app.kubernetes.io/version: "5.7.21"
app.kubernetes.io/component: database
app.kubernetes.io/part-of: wordpress
app.kubernetes.io/managed-by: helm
```
- 其他範例: 把這個 pod 加上 environment=production, app=nginx 的label
```yaml=
apiVersion: v1
kind: Pod
metadata:
name: label-demo
labels:
environment: production
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
```
#### Selectors
- 透過 selector, user 可以找出對應的set的物件
- K8s 主要的core grouping primitive
- API支援兩種形式:
- equality-based (=,==,!=)
- set-based (in,notin and exists)
```bash
# 逗號代表是 AND(&&)的意思
# equality-based
kubectl get pods -l environment=production,tier=frontend
# set-based
kubectl get pods -l 'environment in (production),tier in (frontend)'
# 組合使用
kubectl get pods -l 'partition in (customerA, customerB),environment!=qa'
```
**Field Selectors**
找 k8s 對應資源狀態的清單,例如
```
metadata.name=my-service
metadata.namespace!=default
status.phase=Pending
```
```bash=
#取所有狀態是 Running 的 pod
kubectl get pods --field-selector status.phase=Running
#chained selectors
kubectl get pods --field-selector=status.phase!=Running,spec.restartPolicy=Always
# 一次取多個resource type
kubectl get statefulsets,services --all-namespaces --field-selector metadata.namespace!=default
```
Q. 試想如果流量只要導到環境為python的http server?

```yaml
apiVersion: v1
kind: Service
metadata:
name: service-example
namespace: default
spec:
selector:
svc: http-server
env: python
ports:
- name: https
port: 443
targetPort: 443
protocol: TCP
```
## Assign Pods 到 Nodes 的方法
### 1.Node Selectors
最簡單的選 node 設定條件方法, 會依據設定的 labels 配置 pod
```yaml=
#選擇帶有 accelerator=nvidia-tesla-p100 標籤的node
apiVersion: v1
kind: Pod
metadata:
name: cuda-test
spec:
containers:
- name: cuda-test
image: "registry.k8s.io/cuda-vector-add:v0.1"
resources:
limits:
nvidia.com/gpu: 1
nodeSelector:
accelerator: nvidia-tesla-p100
```
### 2.Taints(污點) & Tolerations(容忍)

可以Taint Node,確保不會有不適合的pod 被配置到指定node
設定 Pod Tolerations, 讓 pod 也可以被配置到有 taint 的node
兩個搭配確保 pod 不會配到不適合的 node 上
**對node設定**
- 給key/value pair,並指定 effect
- 可以對一個node有多個taints
- effect:
`NoSchedule`: 不 match label 就不會配到 node 上
`PreferNoSchedule`: 比較 soft 的 NoSchedule, K8s 會避免把不相容的 pod 放到 node 上
`NoExecute`: 強制踢出,一旦 taint 生效,如果 Node 裡面正在運行的 pod 沒有對應 Tolerate 設定,就會被逐出
```bash
# set taint to node
# 除非 pod 有相符的label, 否則不會assign 到node1
kubectl taint nodes node1 disk=ssd:NoSchedule
# delete taint on node
kubectl taint nodes node1 disk-
```
**對 pod 的設定**
```yaml=
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
```
使用時機:
1. 專屬某特定使用的 Node
`kubectl taint nodes nodename dedicated=groupName:NoSchedule`
2. 有特殊硬體的 Node
`kubectl taint nodes nodename special=true:NoSchedule` or `kubectl taint nodes nodename special=true:PreferNoSchedule`
3. Taint based Evictions
當 node 有問題時,可以針對 pod-level 設定 eviction behavior
像是 node controller 就有 build-in 的 taint, 例如:
- node.kubernetes.io/not-ready: Node is not ready. This corresponds to the NodeCondition Ready being "False".
- node.kubernetes.io/unreachable: Node is unreachable from the node controller. This corresponds to the NodeCondition Ready being "Unknown".
- node.kubernetes.io/memory-pressure: Node has memory pressure.
```yaml=
# tolerationSeconds define 她還可以待在這個不適合的pod多久
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
tolerationSeconds: 3600
```
### 3.Affinity(親和力)
k8s最早只有Node Selector,Affinity是後面的版本才加進來的,延伸了可以定義的條件。
好處:
- affinity/anti-affinity 更好表現選擇邏輯
- 指定 rule 是 soft/preferred, 條件不 match 還是可以配置 pod
- 利用運行在 node 上 pod 存在的 label 限制要配置的 pod,可以讓你定義哪些 pod 可以一起配在同個 node
用法:
`requiredDuringSchedulingIgnoredDuringExecution`: 除非條件符合否則不會 Schedule pod,跟 `nodeSelector`類似。
`preferredDuringSchedulingIgnoredDuringExecution`: 試著找到對應的 node ,但如果條件不 match,還是會 schedule.
```yaml=
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
```
### Taints & Tolerations vs Affinity
| Taint & Tolerations| Affinity (親和力) |
| -------- | -------- |
| 確保 node 資源被指定的 pod 使用 | 確保 pod 在配置時能被配到希望使用的 node |
## k8s-scheduler 機制
拿 create pod 流程舉例:
1. api server 先將create pod 資料寫入 etcd
2. Scheduler 知道有沒 assign Node 的 pod
3.算出結果後回 NodeName 給 api server
4. 結果 bind 回 pod definition / etcd 紀錄
5. kubelet 接手起pod

(https://harshanarayana.dev/2020/06/writing-a-custom-kubernets-scheduler/)
那 Scheduler 到底怎麼選出要配在哪個 pod 的?

如果 pod 中有指定 NodeName 則不用 Scheduler 參與,直接被調度到指定節點

### kube-scheduler 利用兩步驟選擇適合的 node
利用 Scheduling Policies , Scheduling Profiles 實現
1. Filtering: predicates
2. Scoring: priority
後來Scheduling 又有了 Scheduling framework,是個 pluggable architecture for k8s scheduler.
分為 Scheduling Cycle (依序執行), Binding Cycle (可同時執行).

### QueueSort
用來排序在 Scheduling queue 裡面的 pod, 一次只能 enable 一個
### Filter
- nodeSelector
- nodeAffinity
- affinitfy, anti-affinity
Scheduler 中,可選的Predicates

### Score
Scheduler 中,可選的Priotity


---
**其他擴展點:**
`QueueSort`: 用在調度序列排序, 一次只有一個調度器Plugin可以運作
`PreFilter`: 在Pod調度前先對cluster或pod進行的檢查, 一個調度週期只會做一次, 如果檢查失敗會中斷調度
`Filter`: 過濾不能運行Pod的節點
`PostFilter`: 節點預選後調用的, 可以用來記錄log
`PreScore`: 預先針對調度節點打分數, 如果檢查失敗會中斷調度
`Score`: 根據配置的權重針對調度節點打分數
`NormalizeScore`: 在結算節點排名之前修改節點分數, 如果檢查失敗會中斷調度
`Reserve`: 為防止資源競爭需要先幫選定的Pod預留節點上的資源
`Permit`: 針對Pod綁定前檢查或是延遲Pod綁定
`approve`: 所有Permit Plugin檢查成功後就將Pod發送出去綁定
`deny`: 只要有Permit Plugin檢查失敗, 就將Pod退回到調度序列, 並調用Unreserve
`wait`: 如果有Permit Plugin返回等待中, Pod的週期就會一直停在等待的狀態, 如果在超時前都無法完成則會進行deny的流程
`PreBind`: 在Pod綁定到節點之前執行
`Bind`: 在所有PreBind都執行完後才執行
`PostBind`: Pod成功綁定後才執行, 是綁定(Binding Cycle)的最後一個擴展點
`Unreserve`: Pod保留後被拒絕才會執行
## Reference
- Udemy
- K8s官方文件
- https://ithelp.ithome.com.tw/articles/10221929
- https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/
- confluence - 啓維的DE talk
### 討論項目:
Watch 通过 HTTP 协议与 Kubernetes API Server 建立长连接,接收 Kubernetes API Server 发来的资源变更事件。Watch 操作的实现机制使用 HTTP 协议的分块传输编码——当 client-go 调用 Kubernetes API Server 时,Kubernetes API Server 在 Response 的 HTTP Header 中设置 Transfer-Encoding 的值为 chunked,表示采用分块传输编码,客户端收到消息后,与服务端进行连接,并等待下一个数据块。
- https://cloudnative.to/blog/client-go-informer-source-code/#%E6%80%BB%E7%BB%93
- https://github.com/k8s-club/k8s-club/blob/main/articles/Informer%E6%9C%BA%E5%88%B6%20-%20%E6%A6%82%E8%BF%B0.md