什么是DeamonSet Pod

顾名思义,DaemonSet的主要作用,是在k8s集群当中,运行一个Daemon Pod。这个Pod有如下三个特征:

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

例如:

  1. 各种网络插件的Agent组件,都必须运行在每一个节点上,用来处理这个节点上的容器网络。
  2. 各种存储插件的Agent组件,也必须运行在每一个节点上,用来在这个节点桑挂载远程存储目录,操作容器的volume目录。
  3. 跟中监控组件和日志组件,也运行在每个节点上,负责这个节点上的监控信息和日志搜集。

DeamonSet开始的时机,很多时候比k8s集群出现的时机都早。例如: 当网络插件还没安装的时候, 网络插件的Agent就已经启动了, Agent就是以DaemonSet Pod启动的.

以一个DeamonSet的API对象为例说明,其YAML POD如下:

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

这个DeamonSet管理的是一个fluentd-elasticsearch镜像的Pod,通过fluentd将Docker容器里的日志转发到ElasticSearch中。这里挂载了/var/log和/var/lib/docker/containers两个目录,并且Docker 容器里应用的日志,默认会保存在宿主机的 /var/lib/docker/containers/{{. 容器 ID}}/{{. 容器 ID}}-json.log 文件里,所以这个目录正是 fluentd 的搜集目标。

DaemonSet工作流程

DaemonSet 又是如何保证每个 Node 上有且只有一个被管理的 Pod 呢?

控制器模型

DaemonSet控制器从Etcd上所有所有的Node列表,然后遍历所有的Node,查看是否有携带了name=fluentd-elasticsearch 标签的 Pod 在运行

  • 没有。那么在这个Node上创建这样一个Pod。
  • 不止一个。把多余的删掉。
  • 刚好一个。这个节点正常。

删除Pod很简单,调用API即可。

如何在指定的Node上创建新Pod

使用nodeSelector,选择Node的名字即可。

nodeSelector:
    name: <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
  1. requiredDuringSchedulingIgnoredDuringExecution:它的意思是说,这个 nodeAffinity 必须在每次调度的时候予以考虑。同时,这也意味着你可以设置在某些情况下不考虑这个 nodeAffinity;
  2. 这个 Pod,将来只允许运行在“metadata.name”是“node-geektime”的节点上。 DaemonSet Controller 会在创建 Pod 的时候,自动在这个 Pod 的 API 对象里,加上这样一个 nodeAffinity 定义。其中,需要绑定的节点名字,正是当前正在遍历的这个 Node

DaemonSet 还会给这个 Pod 自动加上另外一个与调度相关的字段,叫作 tolerations。这个字段意味着这个 Pod,会“容忍”(Toleration)某些 Node 的“污点”(Taint)。格式如下:

apiVersion: v1
kind: Pod
metadata:
  name: with-toleration
spec:
  tolerations:
  - key: node.kubernetes.io/unschedulable
    operator: Exists
    effect: NoSchedule

例如,k8s中当一个网络插件尚未安装的时候,这个节点就会自动加上名为node.kubernetes.io/network-unavailable的“污点”。这部分在Taint Toleration中描述过,DeamonSet管理的网络插件的Agent Pod就在Pod模板上能够容忍”node.kubernetes.io/network-unavailable“污点”的 Toleration,成功将网络插件的Agent组件调度到这台机器上来。

这种机制,正是我们在部署 Kubernetes 集群的时候,能够先部署 Kubernetes 本身、再部署网络插件的根本原因:因为当时我们所创建的 Weave 的 YAML,实际上就是一个 DaemonSet。

小结

DeamonSet是一个非常简单的控制器。在它的控制循环中,他会遍历所有节点,然后根据节点上是否有被管理的Pod的情况,来判断是否在节点上创建或者删除一个Pod。

在创建Pod之前,DeamonSet会自动给这个Pod加上一个nodeAffinity,从而保证这个Pod只会在指定节点上启动,同时给这个Pod加上一个Toleration,从而忽略节点的unschedulabel的污点。

本示例中的tolerations如下:

tolerations:
- key: node-role.kubernetes.io/master
  effect: NoSchedule

因为示例中的Pod要在所有的节点上运行,而master节点默认携带了一个叫作node-role.kubernetes.io/master的“污点”,所以需要toleration这个污点。

实践

创建这个DaemonSet对象:

$ kubectl create -f fluentd-elasticsearch.yaml

创建成功后,如果有N个节点,那么就能够看到N个此控制器管理的Pod。

$ kubectl get pod -n kube-system -l name=fluentd-elasticsearch
NAME                          READY     STATUS    RESTARTS   AGE
fluentd-elasticsearch-dqfv9   1/1       Running   0          53m
fluentd-elasticsearch-pf9z5   1/1       Running   0          53m

查看DeamonSet对象:

$ kubectl get ds -n kube-system fluentd-elasticsearch
NAME                    DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
fluentd-elasticsearch   2         2         2         2            2           <none>          1h

DaemonSet同样可以进行滚动更新,修改对象内容,自动触发更新,这里将镜像进行升级

$ kubectl set image ds/fluentd-elasticsearch fluentd-elasticsearch=k8s.gcr.io/fluentd-elasticsearch:v2.2.0 --record -n=kube-system

提前运行下面命令查看滚动更新过程:

$ kubectl rollout status ds/fluentd-elasticsearch -n kube-system
Waiting for daemon set "fluentd-elasticsearch" rollout to finish: 0 out of 2 new pods have been updated...
Waiting for daemon set "fluentd-elasticsearch" rollout to finish: 0 out of 2 new pods have been updated...
Waiting for daemon set "fluentd-elasticsearch" rollout to finish: 1 of 2 updated pods are available...
daemon set "fluentd-elasticsearch" successfully rolled out

非ReplicaSet对象的版本管理

对于DeamonSet这种直接操作Pod的控制器,没有ReplicaSet,其版本是通过一个 API 对象,名叫 ControllerRevision来管理的,这个对象专门记录某种 Controller 对象的版本。所以StatefulSet也是使用ControllerRevision来管理版本的

可以查看这个对象的信息:

$ kubectl get controllerrevision -n kube-system -l name=fluentd-elasticsearch
NAME                               CONTROLLER                             REVISION   AGE
fluentd-elasticsearch-64dc6799c9   daemonset.apps/fluentd-elasticsearch   2          1h

详细信息

$ kubectl describe controllerrevision fluentd-elasticsearch-64dc6799c9 -n kube-system
Name:         fluentd-elasticsearch-64dc6799c9
Namespace:    kube-system
Labels:       controller-revision-hash=2087235575
              name=fluentd-elasticsearch
Annotations:  deprecated.daemonset.template.generation=2
              kubernetes.io/change-cause=kubectl set image ds/fluentd-elasticsearch fluentd-elasticsearch=k8s.gcr.io/fluentd-elasticsearch:v2.2.0 --record=true --namespace=kube-system
API Version:  apps/v1
Data:
  Spec:
    Template:
      $ Patch:  replace
      Metadata:
        Creation Timestamp:  <nil>
        Labels:
          Name:  fluentd-elasticsearch
      Spec:
        Containers:
          Image:              k8s.gcr.io/fluentd-elasticsearch:v2.2.0
          Image Pull Policy:  IfNotPresent
          Name:               fluentd-elasticsearch
...
Revision:                  2
Events:                    <none>

在 Data 字段保存了该版本对应的完整的 DaemonSet 的 API 对象。并且,在 Annotation 字段保存了创建这个对象所使用的 kubectl 命令

将这个DaemonSet回滚到Revison版本:

$ kubectl rollout undo daemonset fluentd-elasticsearch --to-revision=1 -n kube-system
daemonset.extensions/fluentd-elasticsearch rolled back

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

DaemonSet Controller使用这个历史 API 对象,对现有的 DaemonSet 做一次 PATCH 操作(等价于执行一次 kubectl apply -f “旧的 DaemonSet 对象”),从而把这个 DaemonSet“更新”到一个旧版本

虽然是回滚,但是版本号还是递增的,因为回滚是删除重建,创建了一个新的ControllerRevision。


tags: 容器编排 应用部署