6.深入k8s:守護程序DaemonSet
阿新 • • 發佈:2020-08-09
> 轉載請宣告出處哦~,本篇文章釋出於luozhiyun的部落格:https://www.luozhiyun.com
![img](https://img.luozhiyun.com/20200809185827.jpg)
最近也一直在加班,處理專案中的事情,發現問題越多越是感覺自己的能力不足,希望自己能多學點。我覺得人生的意義就是在於能夠不斷的尋求突破吧。
這篇文章會講DaemonSet和Job與CronJob一起。在講其中某一塊內容的時候,我會將一些其他內容也關聯上,讓讀者儘可能的看明白些,然後這篇開始我會開始加入一些主要原始碼的分析。
如果覺得我講的不錯的,可以發個郵件鼓勵一下我噢~
Daemon Pod有三個主要特徵:
1. 這個 Pod 執行在 Kubernetes 叢集裡的每一個節點(Node)上;
2. 每個節點上只有一個這樣的 Pod 例項;
3. 當有新的節點加入 Kubernetes 集群后,該 Pod 會自動地在新節點上被創建出來;而當舊節點被刪除後,它上面的 Pod 也相應地會被回收掉。
Daemon Pod可以運用在網路外掛的Agent元件上、日誌元件、監控元件等。
### 建立一個DaemonSet
```yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: fluentd-elasticsearch
image: mirrorgooglecontainers/fluentd-elasticsearch:v2.4.0
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
terminationGracePeriodSeconds: 30
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
```
這個 DaemonSet,管理的是一個 fluentd-elasticsearch 映象的 Pod。通過 fluentd 將 Docker 容器裡的日誌轉發到 ElasticSearch 中。
這個DaemonSet中使用 selector 選擇管理所有攜帶了 name=fluentd-elasticsearch 標籤的 Pod。然後使用template定義了pod模板。
然後在執行這個DaemonSet後,一個叫DaemonSet Controller的控制器會從 Etcd 裡獲取所有的 Node 列表,然後遍歷所有的 Node。然後檢查Node上是不是又name=fluentd-elasticsearch 標籤的 Pod 在執行。
如果沒有這樣的pod,那麼就建立一個這樣的pod;如果node上這樣的pod數量大於1,那麼就會刪除多餘的pod。
執行:
```shell
$ kubectl apply -f ds-els.yaml
```
然後檢視執行情況:
```shell
$ kubectl get pod -n kube-system -l name=fluentd-elasticsearch
NAME READY STATUS RESTARTS AGE
fluentd-elasticsearch-nwqph 1/1 Running 0 4m11s
```
由於我這是單節點,所以只有一個pod運行了。
然後檢視一下 Kubernetes 叢集裡的 DaemonSet 物件:
```shell
$ kubectl get ds -n kube-system fluentd-elasticsearch
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
fluentd-elasticsearch 1 1 1 1 1 27m
```
然後我們來稍微看一下原始碼,k8s是通過daemon_controller裡面的manage方法來管理Pod刪減操作的:
manage方法裡面首先會獲取daemon pod 與 node 的對映關係,然後判斷每一個 node 是否需要執行 daemon pod,然後遍歷完node之後將需要建立的Pod列表和需要刪除Pod的列表交給syncNodes執行。
```go
func (dsc *DaemonSetsController) manage(ds *apps.DaemonSet, nodeList []*v1.Node, hash string) error {
// 獲取已存在 daemon pod 與 node 的對映關係
nodeToDaemonPods, err := dsc.getNodesToDaemonPods(ds)
if err != nil {
return fmt.Errorf("couldn't get node to daemon pod mapping for daemon set %q: %v", ds.Name, err)
}
// 判斷每一個 node 是否需要執行 daemon pod
var nodesNeedingDaemonPods, podsToDelete []string
for _, node := range nodeList {
nodesNeedingDaemonPodsOnNode, podsToDeleteOnNode, err := dsc.podsShouldBeOnNode(
node, nodeToDaemonPods, ds)
if err != nil {
continue
}
//將需要刪除的Pod和需要在某個節點建立Pod存入列表中
nodesNeedingDaemonPods = append(nodesNeedingDaemonPods, nodesNeedingDaemonPodsOnNode...)
podsToDelete = append(podsToDelete, podsToDeleteOnNode...)
}
podsToDelete = append(podsToDelete, getUnscheduledPodsWithoutNode(nodeList, nodeToDaemonPods)...)
//為對應的 node 建立 daemon pod 以及刪除多餘的 pods
if err = dsc.syncNodes(ds, podsToDelete, nodesNeedingDaemonPods, hash); err != nil {
return err
}
return nil
}
```
下面我們看一下podsShouldBeOnNode方法是如何判斷哪些Pod需要建立和刪除的:
在podsShouldBeOnNode會呼叫nodeShouldRunDaemonPod方法來判斷該node是否需要執行 daemon pod 以及能不能排程成功,然後獲取該node上有沒有建立該daemon pod。
通過判斷shouldRun, shouldContinueRunning將需要建立 daemon pod 的 node 列表以及需要刪除的 pod 列表獲取到,shouldSchedule 主要檢查 node 上的資源是否充足,shouldContinueRunning 預設為 true。
```go
func (dsc *DaemonSetsController) podsShouldBeOnNode(
node *v1.Node,
nodeToDaemonPods map[string][]*v1.Pod,
ds *apps.DaemonSet,
) (nodesNeedingDaemonPods, podsToDelete []string, err error) {
//判斷該 node 是否需要執行 daemon pod 以及能不能排程成功
shouldRun, shouldContinueRunning, err := dsc.nodeShouldRunDaemonPod(node, ds)
if err != nil {
return
}
//獲取該節點上的指定ds的pod列表
daemonPods, exists := nodeToDaemonPods[node.Name]
switch {
//如果daemon pod是可以執行在這個node上,但是還沒有建立,那麼建立一個
case shouldRun && !exists:
nodesNeedingDaemonPods = append(nodesNeedingDaemonPods, node.Name)
// 需要 pod 一直執行
case shouldContinueRunning:
var daemonPodsRunning []*v1.Pod
for _, pod := range daemonPods {
if pod.DeletionTimestamp != nil {
continue
}
//如果 pod 執行狀態為 failed,則刪除該 pod
if pod.Status.Phase == v1.PodFailed {
...
podsToDelete = append(podsToDelete, pod.Name)
} else {
daemonPodsRunning = append(daemonPodsRunning, pod)
}
}
//如果節點上已經執行 daemon pod 數 > 1,保留執行時間最長的 pod,其餘的刪除
if len(daemonPodsRunning) > 1 {
sort.Sort(podByCreationTimestampAndPhase(daemonPodsRunning))
for i := 1; i < len(daemonPodsRunning); i++ {
podsToDelete = append(podsToDelete, daemonPodsRunning[i].Name)
}
}
// 如果 pod 不需要繼續執行但 pod 已存在則需要刪除 pod
case !shouldContinueRunning && exists:
for _, pod := range daemonPods {
if pod.DeletionTimestamp != nil {
continue
}
podsToDelete = append(podsToDelete, pod.Name)
}
}
return nodesNeedingDaemonPods, podsToDelete, nil
}
```
DaemonSet 物件的滾動更新和StatefulSet是一樣的,可以通過 `.spec.updateStrategy.type` 設定更新策略。目前支援兩種策略:
* OnDelete:預設策略,更新模板後,只有手動刪除了舊的 Pod 後才會建立新的 Pod;
* RollingUpdate:更新 DaemonSet 模版後,自動刪除舊的 Pod 並建立新的 Pod。
具體的滾動更新可以在:[5.深入k8s:StatefulSet控制器](https://www.luozhiyun.com/archives/342)回顧一下。
### 僅在某些節點上執行 Pod
如果想讓DaemonSet在某個特定的Node上執行,可以使用nodeAffinity。
如下:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: metadata.name
operator: In
values:
- node1
```
上面的這個pod,我們指定了nodeAffinity,matchExpressions的含義是這個pod只能執行在metadata.name是node1的節點上,operator=In表示部分匹配的意思,除此之外operator還可以指定:In,NotIn,Exists,DoesNotExist,Gt,Lt等。
requiredDuringSchedulingIgnoredDuringExecution表明將pod排程到一個節點必須要滿足的規則。除了這個規則還有preferredDuringSchedulingIgnoredDuringExecution將pod排程到一個節點可能不會滿足規則
當我們使用如下命令的時候:
```shell
$ kubectl edit pod -n kube-system fluentd-elasticsearch-nwqph
...
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchFields:
- key: metadata.name
operator: In
values:
- node1
...
```
可以看到DaemonSet自動幫我們加上了affinity來進行節點排程。我們也可以自己在yaml裡面設定affinity,以此來覆蓋系統預設的配置。
### Taints and Tolerations
在k8s叢集中,我們可以給Node打上汙點,這樣可以讓pod避開那些不合適的node。在node上設定一個或多個Taint後,除非pod明確宣告能夠容忍這些汙點,否則無法在這些node上執行。
例如:
```
kubectl taint nodes node1 key=value:NoSchedule
```
上面給node1打上了一個汙點,這將阻止pod排程到node1這個節點上。
如果要移除這個汙點,可以這麼做:
```
kubectl taint nodes node1 key:NoSchedule-
```
如果我們想讓pod執行在有汙點的node節點上,我們需要在pod上宣告Toleration,表明可以容忍具有該Taint的Node。
比如我們可以宣告如下pod:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-taints
spec:
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
containers:
- name: pod-taints
image: busybox:latest
```
operator在這裡可以是Exists表示無需指定value,值為Equal表明需要指明和value相等。
NoSchedule表示如果一個pod沒有宣告容忍這個Taint,則系統不會把該Pod排程到有這個Taint的node上。除了NoSchedule外,還可以是PreferNoSchedule,表明如果一個Pod沒有宣告容忍這個Taint,則系統會盡量避免把這個pod排程到這一節點上去,但不是強制的。
在上面的fluentd-elasticsearch DaemonSet 裡,我們加上了
```
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
```
是因為在預設情況下,Kubernetes 叢集不允許使用者在 Master 節點部署 Pod。因為,Master 節點預設攜帶了一個叫作node-role.kubernetes.io/master的“汙點”。所以,為了能在 Master 節點上部署 DaemonSet 的 Pod,我就必須讓這個 Pod“容忍”這個“汙點”。
### Reference
https://www.cnblogs.com/breezey/p/9101677.html
https://kubernetes.io/docs/concepts/workloads/controllers/daemonset
https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/
https://kuboard.cn/learning/k8s-intermediate/workload/wl-dae