介绍

daemonset 是众多控制器中的一种(其他还有deployment等等),它的作用是保证在每个节点只部署一个Pod,而且是只在pod 模板中定义的标签的节点上保证运行一个pod。

如果节点下线, DaemonSet不会在其他地方重新创建pod。但是,当将 一个新节点添加到集群中时, DaemonSet会立刻部署一个新的pod实例 。

与daemonset 相比副本控制器(rc或rs)是保证集群有固定数量的pod ,而不一定是均匀的每个节点一个这样分布。

删除了 pod那么它也会重新启个新的pod 。与ReplicaSet一样,DaemonSet 从配 pod 模板创建pod

k8s控制器DamenSet_docker

主要特征

  1. 这个 Pod 运行在 Kubernetes 集群里的每一个节点(Node)上;
  2. 每个节点上只有一个这样的 Pod 实例;
  3. 当有新的节点加入 Kubernetes 集群后,该 Pod 会自动地在新节点上被创建出来;而当旧节点被删除后,它上面的 Pod也相应地会被回收掉。

使用场景

包括pod执行系统级别的与基础结构相关的操作。例如,希望在每个节点上运行日志收集器和资源监控器。另 一个典型的例子是Kubemetes 自己的 kube-proxy进程,它需要运行在所有节点上才能使服务工作。

  1. 在集群的每个节点上运行存储 Daemon,比如 分布式存储 glusteFS 或 ceph。
  2. 在每个节点上运行日志收集 Daemon,比如 flunentd 或 logstash。
  3. 在每个节点上运行监控 Daemon,比如 Prometheus Node Exporter 或 collectd。

DaemonSet只在特定的结点上运行pod

DaemonSet pod 默认部署到集群中所有节点上,除非指定这些pod在部分节点上运行,这是通过pod模板中的nodeSelector 属性指定的,这是 DaemonSet定义的一部分似于RSet和RC中的 pod 模板。


注意节点可以被设置为不可调度防止 pod 被部署到节点上,但是DaemonSet会把pod部署到这些节点上,因为无法调度的属性只会被调度器(例如deployment等)使用,而 Daemon Set管理 pod 完全绕过调度器这是预期的,因为DaemonSet 的目的是运行系统服务,即使是在不可调度的节点上,系统服务通常也需要运行。


其实 Kubernetes 自己就在用 DaemonSet 运行系统组件。执行如下命令:

kubectl get daemonset --namespace=kube-system

k8s控制器DamenSet_公众号_02

daemonSet ​​kube-flannel-ds​​​ 和 ​​kube-proxy​​ 分别负责在每个节点上运行 flannel 和 kube-proxy 组件。

k8s控制器DamenSet_微信_03

因为 flannel 和 kube-proxy 属于系统组件,需要在命令行中通过 ​​--namespace=kube-system​​​ 指定 namespace ​​kube-system​​​。如果不指定则只返回默认 namespace ​​default​​ 中的资源。

创建DaemonSet

创建一个yaml 文件如下:

k8s控制器DamenSet_公众号_04

kubectl  create  -f  xx.yaml

当把某个节点的标签(daemonset 中pod 模板定义的节点标签)删除,那么此节点上面的pod 也会消失。  当删除 DaemonSet  也会联通pod  一起删除。 


下面详细分析两个 k8s 自己的 DaemonSet:​​kube-flannel-ds​​​ 和 ​​kube-proxy​​ 。

kube-flannel-ds

flannel 的 DaemonSet 就定义在 ​​kube-flannel.yml​​ 中:

k8s控制器DamenSet_微信_05

DaemonSet 配置文件的语法和结构与 Deployment 几乎完全一样,只是将 ​​kind​​​ 设为 ​​DaemonSet​​。

hostNetwork 指定 Pod 直接使用的是 Node 的网络,相当于 ​​docker run --network=host​​。考虑到 flannel 需要为集群提供网络连接,这个要求是合理的。

​containers​​ 定义了运行 flannel 服务的两个容器。

kube-proxy

由于无法拿到 ​​kube-proxy​​ 的 YAML 文件,只能运行如下命令查看其配置:

kubectl edit daemonset kube-proxy --namespace=kube-system

k8s控制器DamenSet_docker_06

k8s控制器DamenSet_docker_07

同样为了便于理解,这里只保留了最重要的信息。

  • ​kind: DaemonSet​​ 指定这是一个 DaemonSet 类型的资源。
  • ​containers​​​ 定义了 ​​kube-proxy​​ 的容器。
  • ​status​​​ 是当前 DaemonSet 的运行时状态,这个部分是 ​​kubectl edit​​特有的。

其实 Kubernetes 集群中每个当前运行的资源都可以通过 ​​kubectl edit​​ 查看其配置和运行状态,比如 

kubectl edit deployment nginx-deployment

DaemonSet使用

下面以 Prometheus Node Exporter 为例演示如何运行自己的 DaemonSet。

Prometheus 是流行的系统监控方案,Node Exporter 是 Prometheus 的 agent,以 Daemon 的形式运行在每个被监控节点上。

如果是直接在 Docker 中运行 Node Exporter 容器,命令为:

docker run -d \
-v "/proc:/host/proc" \
-v "/sys:/host/sys" \
-v "/:/rootfs" \
--net=host \ prom/node-exporter \
--path.procfs /host/proc \
--path.sysfs /host/sys \
--collector.filesystem.ignored-mount-points "^/(sys|proc|dev|host|etc)($|/)"

将其转换为 DaemonSet 的 YAML 配置文件 node_exporter.yml:

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: node-exporter-daemonset
spec:
template:
metadata:
labels:
app: prometheus
spec:
hostNetwork: true
containers:
- name: node-exporter
image: prom/node-exporter
imagePullPolicy: IfNotPresent
command:
- /bin/node_exporter
- --path.procfs
- /host/proc
- --path.sysfs
- /host/sys
- --collector.filesystem.ignored-mount-points
- ^/(sys|proc|dev|host|etc)($|/)
volumeMounts:
- name: proc
mountPath: /host/proc
- name: sys
mountPath: /host/sys
- name: root
mountPath: /rootfs
volumes:
- name: proc
hostPath:
path: /proc
- name: sys
hostPath:
path: /sys
- name: root
hostPath:
path: /

k8s控制器DamenSet_docker_08

  • 直接使用 Host 的网络。
  • 设置容器启动命令。
  • 通过 Volume 将 Host 路径 ​​/proc​​、​​/sys​​ 和 ​​/​​ 映射到容器中。

执行 ​​kubectl apply -f node_exporter.yml​​:

k8s控制器DamenSet_微信_09

DaemonSet ​​node-exporter-daemonset​​ 部署成功,k8s-node1 和 k8s-node2 上分别运行了一个 node exporter Pod。

然后我们来稍微看一下源码,k8s是通过daemon_controller里面的manage方法来管理Pod删减操作的:


manage方法里面首先会获取daemon pod 与 node 的映射关系,然后判断每一个 node 是否需要运行 daemon pod,然后遍历完node之后将需要创建的Pod列表和需要删除Pod的列表交给syncNodes执行。

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。

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。

DaemonSet重点方法

|-> dsc.getNodesToDaemonPods()  //获取当前状态
|
|
|-> manage -->|-> dsc.podsShouldBeOnNode() --> dsc.nodeShouldRunDaemonPod //汇总需要创建的pod和待删除的Pod
| |
| |
syncDaemonSet --> | |-> dsc.syncNodes //Pod数量一致性
|
|-> rollingUpdate //Pod版本一致性
|
|
|-> updateDaemonSetStatus //更新daemonset对象的status字段


Prometheus监控(1)


三丰,公众号:soft张三丰Prometheus监控(1)

Prometheus监控(2)

三丰,公众号:soft张三丰Prometheus监控(2)

Prometheus监控(3)

三丰,公众号:soft张三丰Prometheus监控(3)

Prometheus监控(4)

三丰,公众号:soft张三丰Prometheus监控(4)

Prometheus监控(5)

三丰,公众号:soft张三丰Prometheus监控(5)


关注公众号 soft张三丰 

k8s控制器DamenSet_docker_10