1 DaemonSet概述
1.1 什么是DaemonSet
Deployment是 Kubernetes 中用于处理无状态服务的资源,而StatefulSet是用于支持有状态服务的资源,这两种不同的资源从状态的角度对服务进行了划分,而 DaemonSet 从不同的维度解决了集群中的问题 — 如何同时在集群中的所有节点上提供基础服务和守护进程。
DaemonSet 可以保证集群中所有的或者部分的节点都能够运行一份 Pod 副本,每当有新的节点被加入到集群时,Pod 就会在目标的节点上启动,如果节点被从集群中剔除,节点上的 Pod 也会被垃圾收集器清除;DaemonSet 的作用就像是计算机中的守护进程,它能够运行集群存储、日志收集和监控等『守护进程』,这些服务一般是集群中必备的基础服务。
使用DaemonSet的一些典型用法:
- 运行集群存储daemon(守护进程),例如在每个节点上运行Glusterd、Ceph等;
- 在每个节点运行日志收集daemon,例如Fluentd、Logstash;
- 在每个节点运行监控daemon,比如Prometheus Node Exporter、Collectd、Datadog代理、New Relic代理或 Ganglia gmond;
一个简单的用法是,在所有的 Node 上都存在一个 DaemonSet,将被作为每种类型的 daemon 使用。 一个稍微复杂的用法可能是,对单独的每种类型的 daemon 使用多个 DaemonSet,但具有不同的标志,和/或对不同硬件类型具有不同的内存、CPU要求。
1.2 编写DaemonSet Spec
1.2.1 必需字段
和其它所有 Kubernetes 配置一样,DaemonSet 需要 apiVersion、kind 和 metadata字段。
代码语言:javascript复制[root@master ~]# kubectl explain daemonset
KIND: DaemonSet
VERSION: extensions/v1beta1
DESCRIPTION:
DEPRECATED - This group version of DaemonSet is deprecated by apps/v1beta2/DaemonSet.
See the release notes for more information. DaemonSet represents the configuration of
a daemon set.
FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an object. Servers
should convert recognized schemas to the latest internalvalue, and may reject
unrecognized values. More info:https://git.k8s.io/community/contributors/devel/api-conventions.md#resources
kind <string>
Kind is a string value representing the REST resource this object represents. Servers
may infer this from the endpoint the client submits requests to. Cannot be updated. In
CamelCase. More info:https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
metadata <Object>
Standard object's metadata. More info:https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
spec <Object>
The desired behavior of this daemon set. More info:https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status
status <Object>
The current status of this daemon set. This data may be out of date by some window of
time.Populated by the system. Read-only. More info:https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status
1.2.2 Pod模板
.spec 唯一必需的字段是 .spec.template。
.spec.template 是一个 Pod 模板。 它与 Pod 具有相同的 schema,除了它是嵌套的,而且不具有 apiVersion 或 kind 字段。
Pod 除了必须字段外,在 DaemonSet 中的 Pod 模板必须指定合理的标签(查看 pod selector)。
在 DaemonSet 中的 Pod 模板必需具有一个值为 Always 的 RestartPolicy,或者未指定它的值,默认是 Always。
代码语言:javascript复制[root@master ~]# kubectl explain daemonset.spec.template.spec
1.2.3 Pod Seletor
.spec.selector 字段表示 Pod Selector,它与 Job 或其它资源的 .sper.selector 的原理是相同的。
spec.selector 表示一个对象,它由如下两个字段组成:
- matchLabels - 与 ReplicationController 的 .spec.selector 的原理相同。
- matchExpressions - 允许构建更加复杂的 Selector,可以通过指定 key、value 列表,以及与 key 和 value 列表的相关的操作符。
当上述两个字段都指定时,结果表示的是 AND 关系。
如果指定了 .spec.selector,必须与 .spec.template.metadata.labels 相匹配。如果没有指定,它们默认是等价的。如果与它们配置的不匹配,则会被 API 拒绝。
如果 Pod 的 label 与 selector 匹配,或者直接基于其它的 DaemonSet、或者 Controller(例如 ReplicationController),也不可以创建任何 Pod。 否则 DaemonSet Controller 将认为那些 Pod 是它创建的。Kubernetes 不会阻止这样做。一个场景是,可能希望在一个具有不同值的、用来测试用的 Node 上手动创建 Pod。
1.2.4 仅在某些节点上运行 Pod
如果指定了 .spec.template.spec.nodeSelector,DaemonSet 控制器将在能够与 Node 选择算符 匹配的节点上创建 Pod。 类似这种情况,可以指定 .spec.template.spec.affinity,之后 DaemonSet 控制器 将在能够与节点亲和性 匹配的节点上创建 Pod。 如果根本就没有指定,则 DaemonSet Controller 将在所有节点上创建 Pod。
2 DaemonSet使用
2.1 创建DaemonSet
以下是DaemonSet的示例spec文件,运行fluentd-elasticsearch image:
代码语言:javascript复制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中没有restart policy字段,默认为Always。如果有的话,必需将值设置成Always,否则在创建时会出现不可用错误。
DaemonSet同样会受到Taint的抵制,如果不在配置中加入匹配的Toleration,那么DaemonSet不会在拥有Taint属性的node上部署pod。上例中有如下内容:
代码语言:javascript复制tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule
原因就是系统默认为master节点增加了 “node-role.kubernetes.io/master”的Taint,以抵制普通pod部署,使master成为专用节点。因为我们预期上例DaemonSet在集群内全局部署,因此需要加入相匹配的Toleration。
如果预期DaemonSet只在特定节点上运行,可以在上述配置文件中加入.spec.template.spec.nodeSelector字段。.
spec.template.spec.nodeSelector字段内加入节点选择器(node selector)或者亲和选择器(node affinity),则DaemonSet只会在满足条件的node上部署pod。总之,可以通过Taint、Toleration、Affinity、node label控制DaemonSet部署pod的节点范围。
将以上内容保存在daemonset.yaml文件中,执行如下命令创建DaemonSet:
代码语言:javascript复制kubectl create -f https://k8s.io/examples/controllers/daemonset.yaml
2.2 DaemonSet更新
如果节点的标签被修改,DaemonSet 将立刻向新匹配上的节点添加 Pod, 同时删除不匹配的节点上的 Pod。
你可以修改 DaemonSet 创建的 Pod。不过并非 Pod 的所有字段都可更新。 下次当某节点(即使具有相同的名称)被创建时,DaemonSet 控制器还会使用最初的模板。
您可以删除一个 DaemonSet。如果使用 kubectl 并指定 --cascade=false 选项, 则 Pod 将被保留在节点上。接下来如果创建使用相同选择算符的新 DaemonSet, 新的 DaemonSet 会收养已有的 Pod。 如果有 Pod 需要被替换,DaemonSet 会根据其 updateStrategy 来替换。
2.2.1 DaemonSet 更新策略
DaemonSet 有两种更新策略:
- OnDelete: 使用 OnDelete 更新策略时,在更新 DaemonSet 模板后,只有当你手动删除老的 DaemonSet pods 之后,新的 DaemonSet Pod 才会被自动创建。跟 Kubernetes 1.6 以前的版本类似。
- RollingUpdate: 这是默认的更新策略。使用 RollingUpdate 更新策略时,在更新 DaemonSet 模板后, 老的DaemonSet pods 将被终止,并且将以受控方式自动创建新的 DaemonSet pods。 更新期间,最多只能有DaemonSet 的一个 Pod 运行于每个节点上。
2.2.2 DaemonSet 滚动更新
Kubernetes 1.6 或者更高版本中才支持 DaemonSet 滚动更新功能
2.2.2.1 启用滚动更新
启用 DaemonSet 的滚动更新功能,必须设置 .spec.updateStrategy.type 为 RollingUpdate。
你可能想设置 .spec.updateStrategy.rollingUpdate.maxUnavailable (默认为 1) 和 .spec.minReadySeconds (默认为 0)。
2.2.2.2 创建带有 RollingUpdate 更新策略的 DaemonSet
下面的 YAML 包含一个 DaemonSet,其更新策略为 ‘RollingUpdate’:
代码语言:javascript复制apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 #滚动升级时允许的最大Unavailable的pod个数
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
tolerations:
# this toleration is to have the daemonset runnable on master nodes
# remove it if your masters can't run pods
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: fluentd-elasticsearch
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
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 清单中更新策略的设置之后,创建 DaemonSet:
代码语言:javascript复制kubectl create -f https://k8s.io/examples/controllers/fluentd-daemonset.yaml
另一种方式是如果你希望使用 kubectl apply 来更新 DaemonSet 的话,也可以 使用 kubectl apply 来创建 DaemonSet:
代码语言:javascript复制kubectl apply -f https://k8s.io/examples/controllers/fluentd-daemonset.yaml
2.2.2.3 检查 DaemonSet 的滚动更新策略
首先,检查 DaemonSet 的更新策略,确保已经将其设置为 RollingUpdate:
代码语言:javascript复制kubectl get ds/fluentd-elasticsearch -o go-template='{{.spec.updateStrategy.type}}{{"n"}}' -n kube-system
如果还没在系统中创建 DaemonSet,请使用以下命令检查 DaemonSet 的清单:
代码语言:javascript复制kubectl apply -f https://k8s.io/examples/controllers/fluentd-daemonset.yaml --dry-run=client -o go-template='{{.spec.updateStrategy.type}}{{"n"}}'
两个命令的输出都应该为:
RollingUpdate
如果输出不是 RollingUpdate,请返回并相应地修改 DaemonSet 对象或者清单。
2.2.2.4 更新 DaemonSet 模板
对 RollingUpdate DaemonSet 的 .spec.template 的任何更新都将触发滚动更新。 这可以通过几个不同的 kubectl 命令来完成。
代码语言:javascript复制apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
tolerations:
# this toleration is to have the daemonset runnable on master nodes
# remove it if your masters can't run pods
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: fluentd-elasticsearch
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
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,请使用 kubectl apply:
代码语言:javascript复制kubectl apply -f https://k8s.io/examples/controllers/fluentd-daemonset-update.yaml
指令式命令
如果你使用 指令式命令 来更新 DaemonSets,请使用kubectl edit:
代码语言:javascript复制kubectl edit ds/fluentd-elasticsearch -n kube-system
只更新容器镜像
如果你只需要更新 DaemonSet 模板里的容器镜像,比如,.spec.template.spec.containers[*].image, 请使用 kubectl set image:
代码语言:javascript复制kubectl set image ds/fluentd-elasticsearch fluentd-elasticsearch=quay.io/fluentd_elasticsearch/fluentd:v2.6.0 -n kube-system
监视滚动更新状态
最后,观察 DaemonSet 最新滚动更新的进度:
代码语言:javascript复制kubectl rollout status ds/fluentd-elasticsearch -n kube-system
当滚动更新完成时,输出结果如下:
代码语言:javascript复制daemonset "fluentd-elasticsearch" successfully rolled out
2.3 DaemonSet 回滚
2.3.1 显示DaemonSet 回滚到的历史修订版本(revision)
如果只想回滚到最后一个版本,可以跳过这一步。
代码语言:javascript复制$ kubectl rollout history daemonset <daemonset-name>
daemonsets "<daemonset-name>"
REVISION CHANGE-CAUSE
1 ...
2 ... ...
在创建时,DaemonSet 的变化原因从 kubernetes.io/change-cause 注解(annotation) 复制到其修订版本中。用户可以在 kubectl 命令中设置 --record=true, 将执行的命令记录在变化原因注解中。
查看指定版本的详细信息:
代码语言:javascript复制$ kubectl rollout history daemonset <daemonset-name> --revision=1
daemonsets "<daemonset-name>" with revision #1
Pod Template:
Labels: foo=bar
Containers:
app:
Image: ...
Port: ...
Environment: ...
Mounts: ...
Volumes: ...
2.3.2 回滚到指定版本
说明: 如果 --to-revision 参数未指定,将选中最近的版本。
代码语言:javascript复制# 在 --to-revision 中指定你从步骤 1 中获取的修订版本
$ kubectl rollout undo daemonset <daemonset-name> --to-revision=<revision>
daemonset "<daemonset-name>" rolled back
2.3.3 监视 DaemonSet 回滚进度
代码语言:javascript复制$ kubectl rollout status ds/<daemonset-name>
daemonset "<daemonset-name>" successfully rolled out
2.3.4 理解 DaemonSet 修订版本
在前面的 kubectl rollout history 步骤中,你获得了一个修订版本列表,每个修订版本都存储在名为 ControllerRevision 的资源中。
要查看每个修订版本中保存的内容,可以找到 DaemonSet 修订版本的原生资源:
代码语言:javascript复制$ kubectl get controllerrevision -l <daemonset-selector-key>=<daemonset-selector-value>
NAME CONTROLLER REVISION AGE
<daemonset-name>-<revision-hash> DaemonSet/<daemonset-name> 1 1h
<daemonset-name>-<revision-hash> DaemonSet/<daemonset-name> 2 1h
每个 ControllerRevision 中存储了相应 DaemonSet 版本的注解和模板。
kubectl rollout undo 选择特定的 ControllerRevision,并用 ControllerRevision 中存储的模板代替 DaemonSet 的模板。 kubectl rollout undo 相当于通过其他命令(如 kubectl edit 或 kubectl apply) 将 DaemonSet 模板更新至先前的版本。
说明: 注意 DaemonSet 修订版本只会正向变化。也就是说,回滚完成后,所回滚到的 ControllerRevision 版本号(.revision 字段) 会增加。 例如,如果用户在系统中有版本 1 和版本 2,并从版本 2 回滚到版本 1, 带有.revision: 1 的ControllerRevision 将变为 .revision: 3。
3 实现原理
3.1 DaemonSet工作原理概述
所有的 DaemonSet 都是由控制器负责管理的,与其他的资源一样,用于管理 DaemonSet 的控制器是 DaemonSetsController,该控制器会监听 DaemonSet、ControllerRevision、Pod 和 Node 资源的变动。
大多数的触发事件最终都会将一个待处理的 DaemonSet 资源入栈,下游 DaemonSetsController 持有的多个工作协程就会从队列里面取出资源进行消费和同步。
3.2 Daemon Pods 是如何被调度的
3.2.1 通过默认调度器调度
DaemonSet 确保所有符合条件的节点都运行该 Pod 的一个副本。 通常,运行 Pod 的节点由 Kubernetes 调度器选择。 不过,DaemonSet Pods 由 DaemonSet 控制器创建和调度。这就带来了以下问题:
- Pod 行为的不一致性:正常 Pod 在被创建后等待调度时处于 Pending 状态, DaemonSet Pods 创建后不会处于Pending 状态下。这使用户感到困惑。
- Pod 抢占 由默认调度器处理。启用抢占后,DaemonSet 控制器将在不考虑 Pod 优先级和抢占 的情况下制定调度决策。
ScheduleDaemonSetPods 允许您使用默认调度器而不是 DaemonSet 控制器来调度 DaemonSets, 方法是将 NodeAffinity 条件而不是 .spec.nodeName 条件添加到 DaemonSet Pods。 默认调度器接下来将 Pod 绑定到目标主机。 如果 DaemonSet Pod 的节点亲和性配置已存在,则被替换。 DaemonSet 控制器仅在创建或修改 DaemonSet Pod 时执行这些操作, 并且不会更改 DaemonSet 的 spec.template。
此外,系统会自动添加 node.kubernetes.io/unschedulable:NoSchedule 容忍度到 DaemonSet Pods。在调度 DaemonSet Pod 时,默认调度器会忽略 unschedulable 节点。
3.2.2 污点和容忍度
3.3 Daemon Pods 通信
与 DaemonSet 中的 Pod 进行通信的几种可能模式如下:
- 推送(Push):配置 DaemonSet 中的 Pod,将更新发送到另一个服务,例如统计数据库。 这些服务没有客户端。
- NodeIP 和已知端口:DaemonSet 中的 Pod 可以使用 hostPort,从而可以通过节点 IP 访问到Pod。客户端能通过某种方法获取节点 IP 列表,并且基于此也可以获取到相应的端口。
- DNS:创建具有相同 Pod 选择算符的 无头服务, 通过使用 endpoints 资源或从 DNS 中检索到多个 A 记录来发现DaemonSet。
- Service:创建具有相同 Pod 选择算符的服务,并使用该服务随机访问到某个节点上的 守护进程(没有办法访问到特定节点)。
参考链接
Kubernetes K8S之资源控制器Daemonset详解 - 腾讯云开发者社区-腾讯云
守护进程集(Daemonsets)
容器化部署实战(九)|控制器 DaemonSet - 将守护进程容器化
K8s 控制器-DamonSet - 走看看
kubernetes之DaemonSet控制器_syztoo的博客-CSDN博客_daemonset
Kubernetes:DaemonSet剖析
Kuebernetes之DaemonSet_五星上炕的博客-CSDN博客_daemonset
DaemonSet介绍 - taotaozh - 博客园
详解 Kubernetes DaemonSet 的实现原理 - 面向信仰编程
Kubernetes DaemonSet使用详解_ghostwritten的博客-CSDN博客_daemonset
DaemonSet | Kubernetes