目錄
- 如何對 StatefulSet 進行“滾動更新”(rolling update)?
- 下面重點講解一個\知識點:DaemonSet
- 列舉幾個例子:
- API 對象的定義
- 如何在指定的 Node 上創建新 Pod 呢?
- nodeAffinity 含義
如何對 StatefulSet 進行“滾動更新”(rolling update)?
你只要修改 StatefulSet 的 Pod 模板,就會自動觸發“滾動更新”:
kubectl patch statefulset mysql –type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"mysql:5.7.23"}]'
在這里,我使用了 kubectl patch 命令。它的意思是,以“補丁”的方式(JSON 格式的)修改一個 API 對象的指定字段,也就是我在后面指定的“spec/template/spec/containers/0/image”。
這樣,StatefulSet Controller 就會按照與 Pod 編號相反的順序,從最后一個 Pod 開始,逐一更新這個 StatefulSet 管理的每個 Pod。而如果更新發生了錯誤,這次“滾動更新”就會停止。此外,StatefulSet 的“滾動更新”還允許我們進行更精細的控制,比如金絲雀發布(Canary Deploy)或者灰度發布,這意味著應用的多個實例中被指定的一部分不會被更新到最新的版本。
這個字段,正是 StatefulSet 的 spec.updateStrategy.rollingUpdate 的 partition 字段。
比如,現在我將前面這個 StatefulSet 的 partition 字段設置為 2:
kubectl patch statefulset mysql -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'
其中,kubectl patch 命令后面的參數(JSON 格式的),就是 partition 字段在 API 對象里的路徑。所以,上述操作等同于直接使用 kubectl edit 命令,打開這個對象,把 partition 字段修改為 2。
這樣,我就指定了當 Pod 模板發生變化的時候,比如 MySQL 鏡像更新到 5.7.23,那么只有序號大于或者等于 2 的 Pod 會被更新到這個版本。并且,如果你刪除或者重啟了序號小于 2 的 Pod,等它再次啟動后,也會保持原先的 5.7.2 版本,絕不會被升級到 5.7.23 版本。
下面重點講解一個\知識點:DaemonSet
顧名思義,DaemonSet 的主要作用,是讓你在 Kubernetes 集群里,運行一個 Daemon Pod。 所以,這個 Pod 有如下三個特征:
- 這個 Pod 運行在 Kubernetes 集群里的每一個節點(Node)上;
- 每個節點上只有一個這樣的 Pod 實例;
- 當有新的節點加入 Kubernetes 集群后,該 Pod 會自動地在新節點上被創建出來;而當舊節點被刪除后,它上面的 Pod 也相應地會被回收掉。
這個機制聽起來很簡單,但 Daemon Pod 的意義確實是非常重要的
列舉幾個例子:
- 各種網絡插件的 Agent 組件,都必須運行在每一個節點上,用來處理這個節點上的容器網絡;
- 各種存儲插件的 Agent 組件,也必須運行在每一個節點上,用來在這個節點上掛載遠程存儲目錄,操作容器的 Volume 目錄;
- 各種監控組件和日志組件,也必須運行在每一個節點上,負責這個節點上的監控信息和日志搜集。
更重要的是,跟其他編排對象不一樣,DaemonSet 開始運行的時機,很多時候比整個 Kubernetes 集群出現的時機都要早。
這個乍一聽起來可能有點兒奇怪。但其實你來想一下:如果這個 DaemonSet 正是一個網絡插件的 Agent 組件呢?
這個時候,整個 Kubernetes 集群里還沒有可用的容器網絡,所有 Worker 節點的狀態都是 NotReady(NetworkReady=false)。這種情況下,普通的 Pod 肯定不能運行在這個集群上。所以,這也就意味著 DaemonSet 的設計,必須要有某種“過人之處”才行。
API 對象的定義
為了弄清楚 DaemonSet 的工作原理,我們還是按照老規矩,先從它的 API 對象的定義說起。
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: k8s.gcr.io/fluentd-elasticsearch:1.20 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 跟 Deployment 其實非常相似,只不過是沒有 replicas 字段;它也使用 selector 選擇管理所有攜帶了 name=fluentd-elasticsearch 標簽的 Pod。
而這些 Pod 的模板,也是用 template 字段定義的。在這個字段中,我們定義了一個使用 fluentd-elasticsearch:1.20 鏡像的容器,而且這個容器掛載了兩個 hostPath 類型的 Volume,分別對應宿主機的 /var/log 目錄和 /var/lib/docker/containers 目錄。
顯然,fluentd 啟動之后,它會從這兩個目錄里搜集日志信息,并轉發給 ElasticSearch 保存。這樣,我們通過 ElasticSearch 就可以很方便地檢索這些日志了。
需要注意的是,Docker 容器里應用的日志,默認會保存在宿主機的 /var/lib/docker/containers/{{. 容器 ID}}/{{. 容器 ID}}-json.log 文件里,所以這個目錄正是 fluentd 的搜集目標。
那么,DaemonSet 又是如何保證每個 Node 上有且只有一個被管理的 Pod 呢?
顯然,這是一個典型的“控制器模型”能夠處理的問題。
DaemonSet Controller,首先從 Etcd 里獲取所有的 Node 列表,然后遍歷所有的 Node。這時,它就可以很容易地去檢查,當前這個 Node 上是不是有一個攜帶了 name=fluentd-elasticsearch 標簽的 Pod 在運行。
而檢查的結果,可能有這么三種情況:
沒有這種 Pod,那么就意味著要在這個 Node 上創建這樣一個 Pod;
有這種 Pod,但是數量大于 1,那就說明要把多余的 Pod 從這個 Node 上刪除掉;
正好只有一個這種 Pod,那說明這個節點是正常的。
其中,刪除節點(Node)上多余的 Pod 非常簡單,直接調用 Kubernetes API 就可以了。
如何在指定的 Node 上創建新 Pod 呢?
如果你已經熟悉了 Pod API 對象的話,那一定可以立刻說出答案:用 nodeSelector,選擇 Node 的名字即可。
不過,在 Kubernetes 項目里,nodeSelector 其實已經是一個將要被廢棄的字段了。因為,現在有了一個新的、功能更完善的字段可以代替它,即:nodeAffinity。我來舉個例子:
apiVersion: v1 kind: Pod metadata: name: with-node-affinity spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: metadata.name operator: In values: - node-geektime
在這個 Pod 里,我聲明了一個 spec.affinity 字段,然后定義了一個 nodeAffinity。其中,spec.affinity 字段,是 Pod 里跟調度相關的一個字段。關于它的完整內容,我會在講解調度策略的時候再詳細闡述。
nodeAffinity 含義
而在這里,我定義的 nodeAffinity 的含義是:
requiredDuringSchedulingIgnoredDuringExecution:它的意思是說,這個 nodeAffinity 必須在每次調度的時候予以考慮。同時,這也意味著你可以設置在某些情況下不考慮這個 nodeAffinity;
這個 Pod,將來只允許運行在“metadata.name”是“node-geektime”的節點上。
在這里,你應該注意到 nodeAffinity 的定義,可以支持更加豐富的語法,比如 operator: In(即:部分匹配;如果你定義 operator: Equal,就是完全匹配),這也正是 nodeAffinity 會取代 nodeSelector 的原因之一。
所以,我們的 DaemonSet Controller 會在創建 Pod 的時候,自動在這個 Pod 的 API 對象里,加上這樣一個 nodeAffinity 定義。其中,需要綁定的節點名字,正是當前正在遍歷的這個 Node。
當然,DaemonSet 并不需要修改用戶提交的 YAML 文件里的 Pod 模板,而是在向 Kubernetes 發起請求之前,直接修改根據模板生成的 Pod 對象。這個思路,也正是我在前面講解 Pod 對象時介紹過的。
此外,DaemonSet 還會給這個 Pod 自動加上另外一個與調度相關的字段,叫作 tolerations。這個字段意味著這個 Pod,會“容忍”(Toleration)某些 Node 的“污點”(Taint)。
而 DaemonSet 自動加上的 tolerations 字段,格式如下所示:
apiVersion: v1 kind: Pod metadata: name: with-toleration spec: tolerations: - key: node.kubernetes.io/unschedulable operator: Exists effect: NoSchedule
這個 Toleration 的含義是:“容忍”所有被標記為 unschedulable“污點”的 Node;“容忍”的效果是允許調度。
而在正常情況下,被標記了 unschedulable“污點”的 Node,是不會有任何 Pod 被調度上去的(effect: NoSchedule)。可是,DaemonSet 自動地給被管理的 Pod 加上了這個特殊的 Toleration,就使得這些 Pod 可以忽略這個限制,繼而保證每個節點上都會被調度一個 Pod。當然,如果這個節點有故障的話,這個 Pod 可能會啟動失敗,而 DaemonSet 則會始終嘗試下去,直到 Pod 啟動成功。
這時,你應該可以猜到,我在前面介紹到的DaemonSet 的“過人之處”,其實就是依靠 Toleration 實現的。
假如當前 DaemonSet 管理的,是一個網絡插件的 Agent Pod,那么你就必須在這個 DaemonSet 的 YAML 文件里,給它的 Pod 模板加上一個能夠“容忍”node.kubernetes.io/network-unavailable“污點”的 Toleration。正如下面這個例子所示:
... template: metadata: labels: name: network-plugin-agent spec: tolerations: - key: node.kubernetes.io/network-unavailable operator: Exists effect: NoSchedule
在 Kubernetes 項目中,當一個節點的網絡插件尚未安裝時,這個節點就會被自動加上名為node.kubernetes.io/network-unavailable的“污點”。
而通過這樣一個 Toleration,調度器在調度這個 Pod 的時候,就會忽略當前節點上的“污點”,從而成功地將網絡插件的 Agent 組件調度到這臺機器上啟動起來。
這種機制,正是我們在部署 Kubernetes 集群的時候,能夠先部署 Kubernetes 本身、再部署網絡插件的根本原因:因為當時我們所創建的 Weave 的 YAML,實際上就是一個 DaemonSet。
至此,通過上面這些內容,你應該能夠明白,DaemonSet 其實是一個非常簡單的控制器。在它的控制循環中,只需要遍歷所有節點,然后根據節點上是否有被管理 Pod 的情況,來決定是否要創建或者刪除一個 Pod。
只不過,在創建每個 Pod 的時候,DaemonSet 會自動給這個 Pod 加上一個 nodeAffinity,從而保證這個 Pod 只會在指定節點上啟動。同時,它還會自動給這個 Pod 加上一個 Toleration,從而忽略節點的 unschedulable“污點”。
當然,你也可以在 Pod 模板里加上更多種類的 Toleration,從而利用 DaemonSet 實現自己的目的。比如,在這個 fluentd-elasticsearch DaemonSet 里,我就給它加上了這樣的 Toleration:
tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule
這是因為在默認情況下,Kubernetes 集群不允許用戶在 Master 節點部署 Pod。因為,Master 節點默認攜帶了一個叫作node-role.kubernetes.io/master的“污點”。所以,為了能在 Master 節點上部署 DaemonSet 的 Pod,我就必須讓這個 Pod“容忍”這個“污點”。
以上就是k8s編排之DaemonSet知識點詳解的詳細內容,更多關于k8s編排DaemonSet的資料請關注其它相關文章!