不会飞的章鱼

熟能生巧,勤能补拙;念念不忘,必有回响。

Kubernetes编排原理——容器化守护进程DaemonSet

DaemonSet特征

DaemonSet的主要作用是在Kubernetes集群里运行一个Daemon Pod时,这个Pod具有3个特征:

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

DaemonSet工作原理

API对象定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
application: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
selector:
matchLabels:
name: fluentd-logging
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: fluentd-elasticsearch
# 该镜像的功能:通过fluentd将Docker容器里的日志转发到Elasticsearch
image: quay.io/fluentd-elasticsearch/fluentd:v3.0.0
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
readOnly: true
terminationGracePeriodSeconds: 30
volumes:
- name: varlog
hostPath:
path: /var/lib/docker/containers

可以看到,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文件里。

DaemonSet是如何保证每个节点上有且仅有一个被管理的Pod?

DaemonSet Controller首先从etcd里获取所有的节点列表,然后遍历所有节点。这时去检查当前这个节点是否有一个携带了name: fluentd-elasticsearch标签的Pod在运行。检查结果可能有3种情况:

  • 1,没有这种Pod,这就意味着要在该节点上创建这样一个Pod
  • 2,有这种Pod,但是数量大于1,说明要删除该节点上多余的Pod(删除节点上多余的 Pod直接调用 Kubernetes API 就可以了。);
  • 3,正好只有一个这种Pod,说明该节点是正常的。

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

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

1
2
nodeSelector:
name: <Node的名字>

不过,在 Kubernetes 项目里,nodeSelector 其实已经是一个将要被废弃的字段了。因为现在有了一个新的、功能更完善的字段可以代替它,即:nodeAffinity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity: # 声明了spec.affinity 字段
nodeAffinity:
requireDuringSchedulingIgnoredDuringExecution: # 这个nodeAffinity 必须在每次调度的时候k考虑
nodeSelectorTerms:
- matchExpressions:
- key: metadata.name
operator: In
values:
- node-test # 这个pod,将来只能运行在metadata.name是node-test的节点上

所以,我们的 DaemonSet Controller 会在创建 Pod 的时候,自动在这个 Pod 的 API对象里,加上这样一个 nodeAffinity 定义。其中,需要绑定的节点名字,正是当前正在遍历的这个 Node。

当然,DaemonSet 并不需要修改用户提交的 YAML 文件里的 Pod 模板,而是在向Kubernetes 发起请求之前,直接修改根据模板生成的 Pod 对象。

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

而 DaemonSet 自动加上的 tolerations 字段,格式如下所示:

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
metadata:
name: with-toleration
spec:
tolerations: # “容忍”所有被标记为 unschedulable“污点”的Node,“容忍”的效果是允许调度。
- key: node.kubernetes.io/unschedulable
operator: Exists
effect: NoSchedule

而在正常情况下,被标记了 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。
例如:

1
2
3
4
5
6
7
8
9
10
...
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

总结

  • 相比于 Deployment
    DaemonSet 只管理 Pod 对象,然后通过 nodeAffinity
    Toleration 这两个调度器的小功能,保证了每个节点上有且只有一个 Pod。
------ 本文结束------
如果本篇文章对你有帮助,可以给作者加个鸡腿~(*^__^*),感谢鼓励与支持!