k8s七 | 服务守护进程DaemonSet

这篇文章主要介绍Kubernetes中第三个重要编排对象DaemonSet守护进程的实现原理及使用方法。

一. DaemonSet 简介

DaemonSet:服务守护进程,它的主要作用是在Kubernetes集群的所有节点中运行我们部署的守护t 6 !进程,相当于在集群节点上分别部署Pod副本,如果有新节点加入集群,: 0 + rDaemonset会自动的在该h E y节点上运行我们需要部署的| b o A f l [ xPod副本,相反如果有节点退& I T ? y # D 9 ;出集群,Daemonset也会移除掉部署在旧节点的Pod副本。

1. DaemonSetn / { 4 o F的主要特 % 1 % - ) @ 2 k征:

  • 这个 Pod 运行在 Kubernetes 集群里的每& t Z j #一个节点(Node)上;
  • 每个节点上只会运行一个这样的 Pod 实例;
  • 如果新的节点加入 Kubernetes 集群后,该 Pod 会自动地在新节点上被创建出来;
  • L U 1 x z _ m当旧节点被删除后,它上面的 Pod 也相应地会被回收掉。

2. DaemonSet常用场景:r ! ; % 3

  • 网络插件的 Agent 组件,如(Flannel,Calico)需要运行在每一个节点上,用来处理这个节点上的容器网络;
  • 存储插件的 Agent 组件,如(Ceph,Glusterfs)需要运行在每一个节点@ @ G R上,用来在这个节点上挂载F远程存储目录;
  • 监控系统的数据收集j p y T ? -组件,如(Prometheus Node Exporter,Cadvisor)需要运行在每一个节点上,负责这个节点上的监控信息搜集。
  • 日志系统的数据收集组件,如(Fluent] Y X Q 6 ` 4 F h,Logstash)需要运行在每一个节点上,负责这个节点上的日志信息搜集。

二. DaemonSet的实现原理

DaemonSet 开始运行的时机,很多时候比整个 Kubf P Mernetes 集群N W C @ S d { * M出现的时机都要早。比如在创建Kubernetes集群后,Node节点上由于没有可用的容器网络c . S ) t 9 W,集群节点的状态会是NotReady,普通的Pod& _ Z n 2将无法运行,我= y d ^ J { R & l们就需要通过DaemonSet 部署一个网络插件的 Agent 组件。下面我Z R i m o / # P N们来了解下DaemonSet如何设计实现的。

1. DaemonSet是如何确保每个节点只运行一个Pod?

  1. DaemonSet的控制器模& p ` /DaemonSet Controller先从从 Etcd 里获取所有的 N` I Tode 列表;
  2. 然后遍历所有的 Node检查,当前这个 Node节点上是不是有一个携带了我们定义标签的 Pod 在运行;
  3. 如果没有定义的 Pod,那么就g * n s ) r意味着要在这个 Node 上创建这样一个 Pod;
  4. 如果有定义的 Pod,但是数量大于 1,那就说明要调用 Kubernetes API 把多余的 Pod 从这个 Node 上删除掉. 3 H 8 .
  5. 如果正好只有一个定义的 Pod,那说明这个节点是正常的。

2 . 如何只在指定的节点上运行Pod?

首先我们会想到使用nodeSelector字段来指定Node的名字,但是在 Ku8 q L E 1 O )bernetes 项目里,nodeSelector 其实已经是一个将要被废E - { x y [弃的字段了。G q 8因为,现在有了一个新的、功能更完善的字段可以代替它,即:nodeAffinity。举个例子:

apiVersion: v1
kind: Pod= l P X
metadata:
name: with-node-affinity
spec:
affini; ~ _ X K n 0ty:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms* R L r w m (:
- matchExpressionj 0 4 J q as:
- key: metadata.n! ! e ; U N ) Came
ope! e v $ h h I irator: In
valuei e Ws:
- n{ u w F s D |ode-geektime

上面文+ = / 9 O 8 2件定义了spec.affinity字段,它是 Pod 里j [ ( I 5 ) + 9和调度相关的一个字段,然后又定义了一个nodeAffinity(节点关系)。这里它的定义含义是:

  • requiredDuringSchedulingIgnoredDuringExecution:它的意思是说,这个 nodeAffinity 必须在每次调度的时候予以考虑。同时,这也意味着你可以设置在某些情况下不考虑这个 nod: e ` { leAffinity;
  • 这个 Pod,将来只允许运行在“; Hmetadata.name”是“node5 Q U y ] w-geektil } 1 b o Hmew s &”的节点上;
  • operator: In(即:部分匹配;如果你定义 operator: Equal,就是完全匹配)

DaemonSet Controller 会在创建 Pod 的时候,自动在这个 Pod 的 API 对象资源里,加L ~ R U x ] } z上这样一个 node/ Q i A - [ AAffini- { y mty 定义。其中,需要绑定的节点名字,正是当前正在遍历的这个 Node。当然,DaemonSet 并不需要修改用户提交的 YAML 文件里的 Pod 模板,而是在向 Kubernetes 发起请求之前,直接修改根据模板生成的 Pod 对象。

3. 污染与容忍

在经过上面* O & y J的流程后,DaemonSet 还. V : d会给这个 Pod 自动加上另外一个与调度相关的字段,叫作tolerationsi ; p容忍)。这个字段意味着这个 Pod,会“容忍”(Tolerx 4 M Y R K | ;a` . # 2 }tion)某些 NodD i * 8 ^ d 7e 的“污点”(Taint),即可以在有污点的节点调度运% Z 9 a 2 u行,继而保证每个节V E p m 4点上都会被调度一个 Pod。

DaemonB ! } lSet 自动添加的tolerations 字段,格式如下所示:

a% E SpiVersion:! ] ^ q ` l v1
kind: Pod
metadata:
name: with-toleration
spec:
tolerations:
- key: node.kubernetes.io/unschedulable
operat` t 0 | 2 % ! aor: Exists
effect: NoSchedule

举个例子:在 Kubernetes 项目c # d , B |中,当一个节点的网络插件尚未安装时,这个节点就会被自动加上名为node.kubernetes.io/network-unavailable的“污点”。而通过这样一个 Toleration,调度器在调度这个 Pod 的时候,就会忽略当前节点上的“污点”,从而成功地将网络插件的 Agenx v n Ht 组件调度到这台机器上启动起来。

...
t L _emplate:
metadata:
labels:
name: network-plugin-agent
spec:
tolerations:
- key: node.kubernetes.io/netN x Bwork-unavailable
operator: ExG 4 , W Oists
effec[ ` O ! .t: NoSchedule

尽管DaemonSet Pod遵守污染(taint)和容忍(toleration)k _ X e = /,但以下容忍会根据相关特性自动添加到DaemonSet 管理的Q _ EPod中。[ F 4 U K

Toleration Key 影响 版本 描述
node.kubernetes.io/nN w [ } v M P Wot-reaY s Q { e % t Edy NoExecute 1.13+ 当存在节点问题(如网络分区 s f p t )时,DaemonSet pod不会被驱逐。
node.kubernetes.io/unreachable NoExecute 1.13+ 当存在节点问题(如网络分区)7 . a 8 M %时,DaemonSet pod不会被驱逐。
node.kubernetes.io/disk-pressure Noo @ 7 K [Schedule 1.8+
node.kubernetes.io/memory-pressure NoSchedule 1 P . : a M I *.8+
nod% 3 I o 3 A Ke.kubernetes.io/unschedulable NoSchedule 1.12+ 在默认的调度程序中,DaemonSet pod允许不可调度的属性。
node.kubernetes.io/network-unavailab. | O ! v mle NoSchedule 1/ p T K , 3.12+ 使用主机网络的DaemonSet pod,默认调度器允许网络不可用属性。

K8S的调度策略还有很多,会在后面文章中& J ! o T ]专门介绍

三. DaemonSet的使用方法

现在我们通过DaemonSet部署日志收集组件Fluentd,具体的来了解DaemonSet的使用方法。
DaemonSet管R : q m H [ h理的是一个 fluentd-v ` N q % J D ;elasticsearch 镜像3 r 的 Pod,

apiVers+ | N B 1 m Tion: apps/v1
kind: Daemoc K W R f d $nSet
metad6 6 5 E  ^ Mata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
selector:
matchLabels:
na` U G = D G G ` -me: fluentd-elasticsearch
template:
metadata:n 0 T P u *
labels:
name: fluentd-elasticsearch
spec:
tolerk S ations:
- key: node-r! C b 5 a 8 Z 4 vole.kubernetes.io/ma) + ? P E Mster
effect: NoSchedule
containers:
- name: fluentd-elasticsearch
image: k8s.gcr.io/fluentd-elasticsearch:f d c 0 _ g 7 ~1.20
resources:
limits:
memory: 200M3 * c H K * Ki
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log7 F O b l
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
terminationGracePeri9 G ) 5odSecon_ G , B [ } |ds: 30
volumes| % , s v T H:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containerV d { ] + X x ? `s

可以看到,DaemonSet 跟 Deployment 其实非常相似,只不过是没有 replicas 字段,
它也使用 selector选择管理所有携带了name=fluentd-elasticsearchu 3 : . o q $ :标签的 Pod。而这些 Pod 的模板,也是用 template 字段定( z c S 4 A义的。在这个字段中z U ? M ~ % e 5 o,我们定义了一个使用 fluentd-elasticsearch:1.20 镜像的容器,这个镜像通过 fluentd 将 Docker 容器里的日志转发到 ElasticSearch 中。而且这个容器挂载了两个 hostU ? 8Path 类型的 Volume,y 4 R | w D ; 9分别对应宿主机的 /var/log 目录和 /var/lib/docker/containers 目录,然后也定义了容忍master上的污点。

需要注意X 6 R _ ! e 9的是,Docker 容器里应用的日志,默认会保存在宿主机的 /var/lib/docker/containers/{{. 容器 I8 S D X O 2D Q _ 0 * a}}/{{. 容器 ID}}-json.log 文件里,所以这个目录正是 fluentd 的搜集目标。还有我们一般在创建DaemonSet对象时都应该加上resources字段进X 4 b =行资* = G源的限制,防止它占用过多的# U 7 Z宿主机资源。

创建资源对象

$ kubp L } s 7 2 C b Uectl create -f fluentd-elasticsearch.yaml
$ kubectl get ds -& , s b l $ I &n kube-system flT k euentd-elasticsearc] = dh
NAME                    DESIRED   CURRENT   READY     UP-Th 6 g 4 l ] p J `O-DATE   AVAILABLE   NODE SELECTOR   AGE
fluentd-elasticsearch   2         2         2         2            2           <none>R - z #  ) S +;          1h
$ kub[ b Jectl get pod -n kube-system -ly E d  B name=fluentd-elasticsearch
NAME                          READY     STATUS    RESTARTS   AGE
fluentd-elasticsearch-dqfv9   1/1       Running   0          53m
fluentd-elasticsearch-pf9z5   1/1       Running   0          53m

创建资源对象后,可以看到启动了两个Pod对象,这是因为DaemonSet会根据集3 2 z ] A T群的节点来控制Pod的数量,有几个节点就会起相应的几个Pod对象,确保每个节点都有一个pod对象运行。

四.F f ~ ` Y DaemonSet的版本管理

查看DaemonSet历史版本

$ kubecO $ 3 u } `tl rollout history daemonset fluentd-elasticsearch -n kube-system
daemonsets "fluentd-elasticsen N & n Garch"
REVISION  CHANGE-CAUSE
1         <none>

更新DaemonSet中的镜像版本到 v2.2.0

$e , 3 * : [  d kubectl set image ds/flu( b b V d Sentd-elasticsearch fluentd-# [ ^ T felasticsearch=k8s.gcr.io[ ^ & = s P/fluentd-elasticsearch:v2.2.0 --record -n=kube-syste? = 7 & 8 : v O `m

--record :更新使+ g , [ Q用到的命令会出现W 5 , J T h -在 DaemonSet 的 rollout history 历史版本里面。

查看镜像更新状态

$ kubectl rollout status ds/fluentd-elasticsearch -n kube-systej ^ D /m
Waiting for daemon set "fluentd-elasticsearch" rollout to finish: 0 out of 2 new pods have been updated...
Waiting for daemon set "t p d B _ + c #fluentd-elasticsearch" rollout to finish: 0 out of 2 new podn D `s have been updated...
Waiting fo^ N } h Hr daemon set "fluentd-elastic= k ~ 7 isearch" rollout to finish: 1 of 2 updat7 N 8 $ ^ * ~ ned pods are available...
daemon set "fluentd-elas{ g q ] ~ eticsearch" successfully rolled out

Dem V U | o Dployment 管理这些版本,靠的是“一个版本对应一个 ReplicaSet 对象”。而DaemoX U I b { EnSet 控制器操作的直接就是 Pod,所以不会有 ReplicaSetJ k G t F 这样的对象参与其中。

那么,它的这些版本又是n l ] + h T ~ 8如何维护的呢?所谓,一切皆对象!在 Kubernetes 项目中,任何你觉得需要记录下来的状态,都可以被用 API 对象的方式实现。当然,“版本”也不例外。Kubernetes v1.7 之后添加了一个 API 对象,名叫 ControllerRevisil 8 p 1 B x Son,专门用来记录某种 Controller 对n j _ d # K 6 P象的版本,ControllerRevision 其实是一个通用的版本管理对象。这样,Kubernetes 项目就巧妙地避免了每种控制器都要维护一套冗余的代码和逻辑的问题。

查看 fluentd-elasticsearch对应的 Controller( V Y 9Revision:

$ kubectl get controllerrevision -n kube-system -l name=fluentd-elasticsearch
NAME                               CONTROLLER                             REVISION   AGE
fluentd-elasticsearch-64dc6799c9   daemonset.apps/fluentd-elasticseT ] D Z F a qarch   2          1h

查看当前controllerrevision的事件信息

$ kubectl des{ t t ? ) m : vcribe controllerrevision fq # p u = 8lue^ W ? j 6 intd-elasticsearch-64dc6799c9 -n kube-system
Name:         fluentd-elasticsearch-64dc6799c9
Namespace:    kube-system
Labels:       controller-revision-A 5 g l k / P L khash=2087235575
name=fluentd-elasticsT ` r x j 4earch
Annotations:  deprecated.daemonset.teo + Y E ] , ! zmplate.generation=2
kubernetes.io/change-causs | 9 e o @ U h 8e=kubectl set image ds/fluentd-elastI z / 0 v dicsearch fluentd-elasticsearch=k8s.gcr.io/fluentd-elasticsearch:v2.2.0 --record=true --namespace=kube-sy: * & = ^ m {stem
API Version:  apps/v1
Data:
Spec:
Template:
$ PatchK ? ; N x  : h X:  replace
Metadata:
Creation Ti` F H Y [ ( & xmestamp:  <nil>
Lo E A 4 | nabels:
Name:  fluentd-elasticsearch
Spec:
Containers:
Image:              k8s.gcr.io/fluentd-elasticsearch:v2.2.0
Image Pull Policy:  IfNotPrY s @ z t Nesent
Name:               fluentd-elasticsearch
...
Revision:                  2
Events:                    <none>

可以看到,这个 ConC / l / l - ~ 0trollerRevision 对象,实际上是在 Data 字段保存了该版本对应的完整的 DaQ S , p *emonSet 的 API 对象。并且,在 Annotation 字段保存了创建这个对象所使用的 kubectl 命令。

回滚Dai G z X wemonSet历史版本

$ kubectl rollout undo daemon/ ^ 0 c jset fluentd-elasticsearch --to-revisc O m a / M . aion=1 -n kube-system
daemonset.extensions/. J U a # E S w ?flu0 2 h l 4 5 N t Aentd-elasticsearch rolled back

这个 kubectD | }l rollout undo 操作,实际上相当于读取到了Revision=1的 Controllem # _rRevision 对象保存的 Data 字段。而这个 Data 字段里保存的信息,就是 Revision=1 时这个 DaemonSet 的完整 API 对象。

总结:在这篇文章O y ` U U m -中主要介绍了KuberM z I Cnetes中第三个重要的编排对象DaemonSeV G F ~t,相比于 Deployment,DaemonSei C H K @ ft 只管理 Pod 对? H q (象,a ( ^ 6 A x X然后通过 nodeAffinq L ] C M 3ity 和 Toleration 这两个调度器的小功能,保证了每个节点上有且只有一个 Pod。与此同时,DaemonSe( 6 q o & / i Ot 通过使用 ControllerRevision,来保存和管理自己对应的“版本”。其中StatefulSeA o m i j f 2t编排对象也是使用 ControllerRevision 进C Y j ; ! J L ` 0行版本管理的,这是因为在 Kubernetes 项目里,ControllerRevisio) ! ] p S / _ (nm - { Z A + v 其实是一个通用的版本管理对象。


上篇文章:k8s六 | 理解有状态应用StatefulSet
系列文章:深入理解Kuerneth * - @ xers
参考资料:深入剖析Kuberne! $ f } T dtes-张磊


关注公众号回复【~ E 8k8s】关键词获取视频教程及更多资料:

k8s七 | 服务守护进程DaemonSet